fix(muya): give every mermaid render its own id (#5023) by Jocs · Pull Request #5465 · marktext/marktext · GitHub
Skip to content

fix(muya): give every mermaid render its own id (#5023) - #5465

Merged
Jocs merged 2 commits into
developfrom
fix/mermaid-concurrent-render-id-5023
Sep 20, 2026
Merged

Jocs merged 2 commits into
developfrom
fix/mermaid-concurrent-render-id-5023

Conversation

@Jocs

@Jocs Jocs commented Sep 19, 2026

Copy link
Copy Markdown
Member

Fixes #5023.

The bug

Opening a document that holds several mermaid blocks renders some of them wrong: one block shows two different diagrams drawn on top of each other, another one is blank. Clicking the block to reveal its code and collapsing it again re-renders that single diagram correctly, which is why the damage looks like it "heals" itself.

Root cause

Every DiagramPreview renders itself as it mounts, so a document with N mermaid blocks starts N renders back to back.

mermaid.run() does not let the caller name the render — it derives the id from Date.now() (utils.InitIDGenerator falls back to () => Date.now() whenever deterministicIds is off, which is the default), so two renders that begin in the same millisecond get the same id. Mermaid then resolves that id document-wide:

// mermaid/dist/chunks/mermaid.core/chunk-55IACEB6.mjs
const getDiagramElement = (id, securityLevel) => {
  const root = securityLevel === 'sandbox' ? ... : select('body');
  const svg = root.select(`[id="${id}"]`);   // first match in the whole document
  return svg;
};

Renders are serialized by mermaid's own queue, so by the time the second diagram of a colliding pair draws, the first one's finished <svg> is already sitting in the document carrying that id. select('body').select('[id=…]') returns that earlier element, so the second diagram is painted into the first block — and the second block is handed back its own untouched, empty <svg>.

Hence the two reported symptoms at once: superimposed diagrams in the earlier block, a blank one later.

The fix

Render through mermaid.render(id, code, target) and mint the id from a module counter, so concurrent renders can never share one. Everything else about the render is unchanged — target is still passed so text is measured in the same place as before, and bindFunctions is still applied.

The sanitize(code) → innerHTML → removeAttribute('data-processed') dance goes away with it: it only existed to stage the code where run() would pick it up, and render() takes the code directly.

Verification

New e2e spec packages/muya/e2e/tests/diagrams/mermaid.spec.ts → concurrent mermaid diagrams: mounts 12 diagrams with per-diagram node labels and asserts each preview holds exactly its own three nodes and a unique svg id.

Before the fix (develop, 12 diagrams), previews land like this:

{ index: 1, svgIds: ['mermaid-1789832030622'],
  labels: ['N1-one','N1-two','N1-three','N8-one','N8-two','N8-three'], nodeGroups: 6 }
{ index: 8, svgIds: ['mermaid-1789832030622'], labels: [], nodeGroups: 0 }

Also reproduced in the packaged renderer itself (build:unpack + a 24-diagram document driven through launchWithMarkdown): 4 of 5 runs corrupted before the change, 5 of 5 clean after it.

  • pnpm -C packages/muya test — 253 files / 1899 tests pass
  • pnpm -C packages/muya lint, lint:types, check-circular — clean
  • Full muya e2e suite: the only failures are the 14 selection/IME/toolbar specs that already fail on develop in this environment; the new spec is red before the change and green after.

🤖 Generated with Claude Code

Jocs and others added 2 commits September 19, 2026 23:58
`mermaid.run()` names each render `mermaid-${Date.now()}` and its renderers
then resolve that name with a document-wide lookup, so two diagrams whose
renders start in the same millisecond draw into a single `<svg>`. A document
that mounts several mermaid blocks at once hits this on open: the earlier
block ends up holding both diagrams superimposed and the later one renders
empty.

Render through `mermaid.render(id, code, target)` instead, minting the id from
a counter so concurrent renders can never collide. Passing the code straight
to the renderer also drops the sanitize round-trip that only existed to stage
it for `run()`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mount twelve mermaid blocks at once and assert each preview holds exactly its
own three nodes under a unique svg id. Twelve makes the millisecond id
collision near-certain, and the per-diagram node labels name both the block
that lost its diagram and the one that received it when the assertion fails.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Jocs
Jocs force-pushed the fix/mermaid-concurrent-render-id-5023 branch from 32e520b to 2d505bd Compare September 19, 2026 15:58
@Jocs
Jocs merged commit 2ec36d3 into develop Sep 20, 2026
7 checks passed
@Jocs
Jocs deleted the fix/mermaid-concurrent-render-id-5023 branch September 20, 2026 00:32
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.

[Bug] Repeated superimposed display occurred when rendering mermaid flowchart.

1 participant