feat(desktop): render Mermaid diagrams in markdown file preview - #40531
feat(desktop): render Mermaid diagrams in markdown file preview#40531liuhao1024 wants to merge 1 commit into
Conversation
When previewing .md files in the right-rail, fenced code blocks with language 'mermaid' are now rendered as SVG diagrams instead of showing raw source text. Root cause: the custom MarkdownCode component override in preview-file.tsx routes ALL fenced blocks through ShikiHighlighter, bypassing Streamdown's internal mermaid plugin dispatch. Even if @streamdown/mermaid were wired as a plugin, the custom components.code would intercept before the plugin could fire. Fix: detect language === 'mermaid' in MarkdownCode and delegate to a lazy-loaded MermaidBlock component that calls mermaid.render() with securityLevel 'strict'. The mermaid library is code-split via React.lazy so it only loads when a mermaid block is actually encountered. Changes: - apps/desktop/package.json: add mermaid ^11.15.0 dependency - apps/desktop/src/components/chat/mermaid-block.tsx: new component - apps/desktop/src/app/chat/right-rail/preview-file.tsx: mermaid detection Fixes NousResearch#38654
|
Nice to see this land. The file preview is the right place to start — Related: #40493 is about getting the same rendering in the chat assistant responses (the markdown-text.tsx component in the chat surface, not the file preview). The |
alpindiay
left a comment
There was a problem hiding this comment.
PR #40531 Review -- Mermaid diagram rendering in markdown preview
Summary: Clean feature implementation that adds Mermaid diagram rendering to the file preview pane. Good use of lazy loading and Suspense.
What Changed
- New dependency: mermaid@^11.15.0 added to apps/desktop/package.json
- Preview file integration (preview-file.tsx): Detects language === 'mermaid' in fenced code blocks and renders via lazy-loaded MermaidBlock component wrapped in Suspense.
- New component (mermaid-block.tsx): 84-line component handling three states -- loading spinner, error display (with source toggle), and rendered SVG.
Findings
| Severity | Category | Details |
|---|---|---|
| PASS | Security | Mermaid initialized with securityLevel: 'strict' -- prevents XSS via malicious diagram markup. dangerouslySetInnerHTML is used for the rendered SVG, but strict mode sanitizes output, making this safe. |
| PASS | Correctness | Clean-up via cancelled flag pattern in useEffect is correct. Singleton loadMermaid() avoids duplicate imports. Proper error boundary with try/catch. |
| MEDIUM | Tests | No tests added. The MermaidBlock component has multiple states (loading, error, rendered) and async rendering logic. A basic smoke test (e.g., renders a valid chart, shows error on invalid syntax) would significantly improve confidence. |
| PASS | Performance | lazy() + Suspense means the large mermaid library is only loaded when a mermaid code block is actually encountered. Good code-splitting. |
| INFO | Style | Math.random().toString(36).slice(2, 10) for generating element IDs is technically not collision-proof but is fine for per-render unique IDs. No practical issue. |
Verdict: APPROVE with suggestion
The implementation is solid. I would strongly recommend adding a basic test file for mermaid-block.tsx in a follow-up PR, covering at minimum:
- Renders a simple valid mermaid chart (flowchart/graph)
- Displays error state on invalid syntax
- Cleanup on unmount
No blocking issues -- this is good to merge.
Salvaged from #40531; surgically reapplied onto current main (i18n'd preview-file.tsx). mermaid dep already present on main. Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Salvaged from #40531; surgically reapplied onto current main (i18n'd preview-file.tsx). mermaid dep already present on main. Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Salvaged from #40531; surgically reapplied onto current main (i18n'd preview-file.tsx). mermaid dep already present on main. Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
|
Merged via #40630 — thanks @liuhao1024 for the original implementation in this PR; your commit is preserved (rebase-merge) with a follow-up that routes the preview pane through the shared embeds registry that landed in #52935. |
Salvaged from NousResearch#40531; surgically reapplied onto current main (i18n'd preview-file.tsx). mermaid dep already present on main. Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Salvaged from NousResearch#40531; surgically reapplied onto current main (i18n'd preview-file.tsx). mermaid dep already present on main. Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Salvaged from NousResearch#40531; surgically reapplied onto current main (i18n'd preview-file.tsx). mermaid dep already present on main. Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Salvaged from NousResearch#40531; surgically reapplied onto current main (i18n'd preview-file.tsx). mermaid dep already present on main. Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Salvaged from NousResearch#40531; surgically reapplied onto current main (i18n'd preview-file.tsx). mermaid dep already present on main. Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Summary
Render Mermaid diagrams in the Desktop markdown file preview. Fenced code blocks with language
mermaidare now rendered as interactive SVG diagrams instead of showing raw source text.Fixes #38654
Root Cause
The custom
MarkdownCodecomponent override inpreview-file.tsxroutes all fenced code blocks throughShikiHighlighter, bypassing Streamdown's internal mermaid plugin dispatch. Even if@streamdown/mermaidwere wired as a plugin, the customcomponents.codewould intercept before the plugin could fire.Solution
Detect
language === 'mermaid'insideMarkdownCodeand delegate to a lazy-loadedMermaidBlockcomponent that callsmermaid.render()withsecurityLevel: 'strict'. The mermaid library is code-split viaReact.lazyso it only loads when a mermaid block is actually encountered.Changes
apps/desktop/package.jsonmermaid^11.15.0 dependencyapps/desktop/src/components/chat/mermaid-block.tsxapps/desktop/src/app/chat/right-rail/preview-file.tsxImplementation Details
MermaidBlockis loaded viaReact.lazy()— the ~2MB mermaid library is only fetched when a user opens a file containing mermaid blocksmermaid.render()usessecurityLevel: 'strict'which strips<script>tags and event handlers from generated SVGdocument.documentElement.classList.contains('dark')language === 'mermaid'— all other fenced blocks continue through ShikiHighlighter as beforeTesting
.mdfile with a mermaid block: