Skip to content

feat: support mermaid charts in markdown renderer#1889

Merged
jschwxrz merged 5 commits intomainfrom
feat-render-mermaid-charts-7z2y1
May 11, 2026
Merged

feat: support mermaid charts in markdown renderer#1889
jschwxrz merged 5 commits intomainfrom
feat-render-mermaid-charts-7z2y1

Conversation

@jschwxrz
Copy link
Copy Markdown
Collaborator

@jschwxrz jschwxrz commented May 5, 2026

No description provided.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 5, 2026

Greptile Summary

Adds Mermaid diagram rendering support to the existing markdown renderer by intercepting ```mermaid code blocks and replacing them with a new MermaidDiagram React component. Rendering is serialized through a module-level promise queue and the pre wrapper is stripped when its only child is a mermaid diagram.

  • mermaid-renderer.ts: Dynamic-imports mermaid and serializes all mermaid.render calls through a single promise queue to avoid concurrent initialization conflicts. Calls mermaid.initialize with securityLevel: 'strict' before each render.
  • mermaid-diagram.tsx: Manages loading / error / success states, renders the returned SVG via dangerouslySetInnerHTML, and supports both full and compact layout variants.
  • markdown-renderer.tsx: Routes mermaid code blocks to MermaidDiagram in both useFullComponents and useCompactComponents, threads isDark into the compact hook, and unwraps the surrounding pre element for mermaid-only blocks.

Confidence Score: 4/5

Safe to merge with awareness of the SVG injection surface and the mermaid global-state reset on every render.

The core rendering pipeline is well-structured: the promise queue correctly serializes mermaid renders, cancellation prevents stale setState calls, and the pre-wrapper stripping logic works for the react-markdown output shape. The two areas that warrant a second look are the dangerouslySetInnerHTML path (SVG from AI-generated diagram content bypasses React's XSS protection and trusts mermaid's internal DOMPurify entirely) and mermaid.initialize being called on every queued render rather than only on theme changes, which resets mermaid's full global state unnecessarily and could interact poorly with future mermaid updates.

mermaid-renderer.ts and mermaid-diagram.tsx deserve the most attention — the initialize-on-every-render pattern and the dangerouslySetInnerHTML injection are both concentrated there.

Security Review

  • SVG injection via dangerouslySetInnerHTML (mermaid-diagram.tsx:84): Mermaid's rendered SVG is injected directly into the DOM. Protection relies solely on mermaid's internal DOMPurify pass (securityLevel: 'strict'). Because diagram content can originate from AI model output, a mermaid parser bug or DOMPurify bypass would allow script execution in the Electron renderer process. An independent sanitization step (e.g. calling DOMPurify directly before injection) would add defence-in-depth.

Important Files Changed

Filename Overview
src/renderer/lib/ui/mermaid-renderer.ts New module: serialized mermaid render queue; calls mermaid.initialize on every render which resets global state unnecessarily.
src/renderer/lib/ui/mermaid-diagram.tsx New React component wrapping mermaid rendering with loading/error states; uses dangerouslySetInnerHTML for SVG output and has a duplicate error message in the fallback path.
src/renderer/lib/ui/markdown-renderer.tsx Intercepts mermaid code blocks in both full and compact variants, strips the pre wrapper around MermaidDiagram, and threads isDark into useCompactComponents correctly.
package.json Adds mermaid ^11.12.2 as a production dependency.
pnpm-lock.yaml Lockfile updated to pin mermaid 11.12.2.

Sequence Diagram

sequenceDiagram
    participant MD as MarkdownRenderer
    participant CC as code component
    participant MD2 as MermaidDiagram
    participant MR as mermaid-renderer (queue)
    participant LIB as mermaid lib

    MD->>CC: render code block (className="language-mermaid")
    CC->>CC: renderMermaidCodeBlock()
    CC-->>MD2: <MermaidDiagram chart=... isDark=... />
    MD->>pre: render pre (children = <MermaidDiagram/>)
    pre->>pre: isOnlyMermaidDiagramChild() → true
    pre-->>MD: <>{children}</> (wrapper stripped)

    MD2->>MR: renderMermaidDiagram({ id, chart, theme })
    Note over MR: chains onto renderQueue promise
    MR->>LIB: mermaid.initialize(config)
    MR->>LIB: mermaid.render(id, chart)
    LIB-->>MR: { svg }
    MR-->>MD2: svg string
    MD2->>MD2: setState({ kind: 'rendered', svg })
    MD2-->>MD: <div dangerouslySetInnerHTML={svg} />
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
src/renderer/lib/ui/mermaid-renderer.ts:23-35
**`mermaid.initialize` called on every render, resetting global state**

`mermaid.initialize(config)` is called inside `render` on every invocation of `renderMermaidDiagram`, even when the theme hasn't changed. Mermaid's `initialize` resets its entire internal state (clearing layout caches, re-registering diagram types, etc.), which is both wasteful and fragile. If mermaid is ever updated to schedule async work inside `initialize`, it would escape the render queue's serialization. The last-used theme should be tracked at module scope so `initialize` is only called when the theme actually changes.

### Issue 2 of 3
src/renderer/lib/ui/mermaid-diagram.tsx:82-85
**`dangerouslySetInnerHTML` bypasses React's XSS protection for mermaid SVG output**

The SVG string from `mermaid.render` is injected directly into the DOM. While `securityLevel: 'strict'` instructs mermaid to run DOMPurify over the output, this relies entirely on mermaid's internal sanitizer being correct and up-to-date. In this app, diagram content originates from AI model output, which can include crafted mermaid syntax. If a mermaid parser bug or a DOMPurify bypass is ever found, arbitrary SVG/script content would execute in the renderer process. Consider running the returned SVG through an independent sanitization step (e.g. DOMPurify directly in this file) before injecting it.

### Issue 3 of 3
src/renderer/lib/ui/mermaid-diagram.tsx:51-58
**Duplicate "Unable to render Mermaid diagram." message in error fallback path**

The error UI always shows `"Unable to render Mermaid diagram."` as a bold title, then renders `visibleState.message` below it. `errorMessage()` returns the same string `'Unable to render Mermaid diagram.'` as a fallback when the thrown value is not an `Error` or has an empty message — so when that path is taken, the same sentence appears twice in the error box. The mermaid-specific error text (e.g. a parse error string) should be shown in the `message` slot only when it differs from the generic title, or the title should be omitted in the generic fallback.

Reviews (1): Last reviewed commit: "feat(renderer): render mermaid code fenc..." | Re-trigger Greptile

Comment thread src/renderer/lib/ui/mermaid-renderer.ts
Comment thread src/renderer/lib/ui/mermaid-diagram.tsx
Comment thread src/renderer/lib/ui/mermaid-diagram.tsx
@jschwxrz jschwxrz requested a review from Davidknp May 5, 2026 20:52
@jschwxrz jschwxrz merged commit a0e283b into main May 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant