feat(components): render streaming markdown with @lobehub/streamdown - #1002
Merged
Merged
Conversation
Replace Vercel streamdown. A streaming turn lazy-loads @lobehub/streamdown for tail-only re-lexing, paced reveal, and in-flight character fade; finished text renders through react-markdown. A failed engine load (Safari < 16.4 cannot parse its lookbehind literals) falls back to static rendering. Code blocks, highlighting through the existing worker, and Mermaid rendering/download move into Lody-owned components, keeping the data-streamdown styling and test hooks. A pnpm patch keeps remend's HTML-tag completion off so a TeX comparison such as p<q no longer drops the streamed remainder. Refs LodyAI#421 Model: claude-opus-5-5
… a patch @lobehub/streamdown 1.4.0 exposes the remend options (lobehub/streamdown#5), so the streaming renderer passes { htmlTags: false } directly and the pnpm patch is removed. 1.4.0 is excluded from the minimum release age because it was adopted on release day. Refs LodyAI#421 Model: claude-opus-5-5
Innei
marked this pull request as ready for review
September 26, 2026 09:31
Contributor
|
Thanks |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issue
Refs #421
Problem / pressure
While a turn streams, Vercel
streamdownre-runs remend +markedlexing over the whole message on every frame (#421, cause 1), and streamed text arrives in visible chunk-sized jumps. Its word-levelanimatedmode wraps every word of the turn in a span, so it was never enabled. Much of its bundled UI was already switched off or overridden: Mermaid full-screen and pan/zoom were disabled, andindex.csshad to fight its utility classes with!important.Summary
@lobehub/streamdown: finished blocks are frozen, only the open tail block is re-lexed, reveal commits are throttled to 48–96 ms, and only in-flight characters fade in (settled text has no spans). Finished text renders through plainreact-markdown. The streaming renderer stays mounted 1 s after the stream ends so its buffered tail finishes revealing, then hands off.remendpatch and the lazy@pierre/diffsimport). A failed lazy load resolves to the static renderer instead of breaking the turn.streamdownbundled:markdown-code-block.tsx(container, copy button, token body),markdown-code-highlight.ts(tokens from the existing highlight worker, main-thread fallback, bounded cache), andmarkdown-mermaid-block.tsx(render, copy, SVG/PNG/source download through a new armedMenuinui/armed-overlays.tsx).data-streamdown="…"attributes stay as the styling and test hooks.remendHTML tags.@lobehub/streamdowncompleted the tail withremend's defaults, which would re-introduce thep<qtruncation fixed in fix(components): preserve Markdown math document tails #843. Fixed upstream: 1.4.0 adds aremendoption (lobehub/streamdown#5) and the renderer passes{ htmlTags: false }. 1.4.0 is added tominimumReleaseAgeExcludebecause it was adopted on release day.data-incompleteand an unclosed Mermaid fence stays a code block until it closes.remark-math+rehype-katexdirectly with the options@streamdown/mathpassed. Dead Vercel-specific CSS, Tailwind content globs, and thepackageExtensionsshim are removed.ai-gui/AGENTS.mdrule,ai-gui/README.md,mermaid-diagram-rendering.md, and a bilingual note2026-09-26-lobehub-streamdown.Visual explanation
flowchart TD T[MarkdownRenderer text] --> N[normalizeTexMathDelimiters] N --> S{isStreaming, or ended less than 1 s ago?} S -- no --> RM[react-markdown<br/>whole document] S -- yes --> L[lazy import @lobehub/streamdown] L -- load failed --> RM L -- loaded --> SD[Streamdown<br/>smooth reveal, frozen blocks,<br/>remend tail htmlTags off, char fade] RM --> C[shared components] SD --> C C --> PRE[pre handler reads language, meta, unclosed flag] PRE --> CB[MarkdownFencedCodeBlock<br/>worker tokens] PRE --> DF[MarkdownDiffBlock] PRE --> MM[MarkdownMermaidBlock<br/>closed fences only]Demo recording
Recorded in the locally built desktop app (
apps/electronbuild of this branch, launched through thee2ePlaywright Electron harness in an isolated profile). The agent is a synthetic ACP script that streams a fixed Markdown reply in 2–10 character chunks, so no model or user data is involved; the recording script stays under the ignorede2e/artifacts/. It shows the paced reveal and fade of in-flight text, the code block and table while streaming, thep<qline keeping the rest of the reply, the Mermaid fence staying a code block until it closes, and the handoff to static rendering (0 fade spans afterwards).streaming-markdown-demo.mp4
Before / after
react-markdownstreamdown, overridden in CSSdata-streamdownhooksTest plan
pnpm --filter @lody/components typecheck— pass (re-run after the 1.4.0 bump).NODE_ENV=test vitest runinpackages/components— 509 files, 4224 tests pass.tests/markdown-streaming-reparse.test.ts: streaming→static handoff (fade spans only while streaming), unclosed-fence flag, and thep<qmath tail on both paths. The streamingp<qcase fails without theremendoption and passes with it.tests/markdown-mermaid-fullscreen.test.tsxpasses unchanged against the new block markup.oxlint --type-awareon changed files (0 errors), Oxfmt,pnpm run lint:i18n,pnpm run docs check,pnpm check:public-boundary, andpnpm install --frozen-lockfile— pass.THIRD_PARTY_NOTICES.mdnot regenerated (the generator also rewrites ~9k lines of unrelated stale entries).Context handoff
Original user prompt
Show original prompt
Shared conversation
Status: unavailable
Reason: Authored in a Claude Code CLI session, not a Lody conversation, so there is no shared Lody conversation link to publish.