3.1.0
✨ Features
allowedLinkPrefixes/allowedImagePrefixesnow support protocol-only prefixes (#27). Use'https://'to allow all HTTPS links while still blocking insecurehttp://, or'mailto:'/'tel:'for those protocols. The'*'wildcard continues to allow onlyhttp/https.<Streamdown {content} allowedLinkPrefixes={['https://', 'mailto:']} />
- Disable Mermaid mouse-wheel zoom (#29).
controls.mermaidnow accepts an object{ enabled?, mouseWheelZoom? }so you can turn off wheel-zoom while keeping pan and the zoom buttons (enabledstill toggles the whole toolbar). The plain boolean form still works.<Streamdown {content} controls={{ mermaid: { mouseWheelZoom: false } }} />
🐛 Bug Fixes
- Paragraphs in loose list items now render correctly (#28). A list item containing a blank line is now detected as loose, and its content renders as separate paragraph blocks instead of being glued onto one line.
Behavior note: loose-list item content is now wrapped in
<p>(CommonMark-correct, matching the original Streamdown). The default theme adds no extra margin (theme.paragraph.baseis empty), so vertical spacing is unchanged for most users. - Streaming parser overhaul — fixed a family of incomplete-markdown completion bugs that produced stray or broken markup mid-stream:
- A half-streamed closing marker (
**bold*,__under_,~~strike~,***both*) no longer leaves a stray*/_/~after the text. - Incomplete links/images on lines with multiple brackets no longer duplicate text or get a misplaced
]; incomplete images are completed with the image marker instead of being closed as citations. - Multiple incomplete citations on one line are now each closed (previously only one
]was appended). - Alignment blocks:
[center]/[right]with empty content tokenize, consecutive blocks parse as siblings, formatting completion works inside blocks, and an unclosed code fence inside an unclosed block is closed in the right order. A lone trailing[center]/[right]line is left as-is until content streams in. - Code fences quoted inside blockquotes/alerts (
> \```) are now detected, so fenced content in alerts is no longer mangled mid-stream. - MDX-looking tags inside code fences or math blocks are no longer tracked/auto-closed as components.
- Incomplete footnote references (
[^label) are completed with the footnote marker again (the citation handler was closing them first).
- A half-streamed closing marker (
- Description lists no longer drop lines whose detail contains a colon (
: Time: 10:30 AMused to vanish entirely — the per-line regex disagreed with the block rule). - Table rowspan cleanup —
^^continuation markers no longer leak a stray^cell, and a cell with content plus a trailing^keeps its full text when merged into the cell above (the last character used to be dropped). - Nested same-name MDX components — the incomplete-markdown parser now pairs closing tags with the innermost open tag (LIFO) and no longer double-counts closes that are part of a same-line pair, so streamed nested components are auto-closed in the right order.
- Spaced thematic breaks —
- - - -now parses as a horizontal rule per CommonMark instead of a bullet list.Behavior notes: a bare trailing
[text(no](on the line) is now consistently completed as an inline citation[text]— it becomes a link as soon as](urlstreams in. GFM tables keep genuinely empty cells (| a | | b |); use consecutive pipes (||) for colspan, which is unchanged.
⚡ Performance
- Faster streaming parse — hot-path regexes in the list, alert, and description-list tokenizers are now precompiled once at module load instead of being rebuilt on every chunk, the citations tokenizer's
start()hook returns the actual bracket index so marked can skip ahead instead of re-invoking it at every position, and a duplicate regex evaluation was removed from the math tokenizer. Streaming benchmark suites (src/tests/streaming-perf*.spec.ts) are included to keep this measurable. - Incremental block splitting —
parseBlocksno longer re-lexes the whole document on every streamed chunk. On append-only updates (the streaming case) it seals completed blocks and re-lexes only the last two, falling back to a full parse for any non-append edit or when token raws don't reconstruct the input (e.g. tab normalization). The tokenizer options object is also built once and shared instead of being rebuilt twice per chunk. Equivalence with the full parse is locked in by a property test (parse-blocks-incremental.test.ts) that streams pathological documents (tables forming row by row, setext headings, fences, footnote continuations) and compares at every chunk. Combined with the tokenizer work above, total parse cost on a representative streamed LLM answer (~1 token per chunk) drops ~12× vs 3.0.1, and the end-of-stream cost-growth ratio falls from ~240× to ~8×.
📝 Documentation
- Clarified that the Tailwind
@sourcepath is relative to your stylesheet, with asrc/routesexample for newer SvelteKit project layouts (#24). - Documented KaTeX math usage — importing the
Mathcomponent and using$…$/$$…$$(#21). - Documented Shiki theme usage: the built-in
github-dark/github-lightthemes, registering extra themes viashikiThemes, dynamic light/dark switching, and migrating off the removedshikiPreloadThemesprop (#25). - Added a Streaming Performance & Memoization section explaining how parsing work is reused across streaming updates (#18).
🔒 Supply Chain & Release
- Added an npm publish workflow using trusted publishing (OIDC, no long-lived token) with provenance, plus
repositoryandpublishConfigmetadata (#22). Requires enabling the Trusted Publisher for this package on npmjs.com before the first automated release.