Render diagrams in the markdown pipeline#5
Open
petrovo-as wants to merge 1 commit into
Open
Conversation
Mermaid and Vega were rendered by mutating the DOM after React had committed it: the pipeline produced an HTML string holding plain code blocks, React wrote it via dangerouslySetInnerHTML, and an effect then walked that DOM replacing each <pre> with SVG. That left the content with two writers. React reassigns innerHTML whenever the string changes and knows nothing about the SVG, so a re-render wiped the diagrams; the effect then had to put them back, cancelling whatever render was in flight. The data-*-claimed attributes and the isCancelled plumbing existed only to arbitrate between the two writers, and they did not manage it: a superseding pass skipped the blocks a cancelled pass had claimed, leaving them as code with nothing left to render them. Render diagrams where KaTeX already renders them — in the pipeline, before the HTML reaches React. Blocks are substituted inside an inert <template>, so the SVG travels with the tab's renderedHTML instead of living only in the DOM. The content has a single writer again, and switching tabs cannot lose a diagram because nothing re-renders on a switch. This drops the claim attributes, the cancellation plumbing and the revert-then-rerender path. A theme switch re-runs the pipeline, the same path as a content change. Also: - Search highlighting skips <svg> subtrees. Now that SVG is part of renderedHTML, a <mark> injected into one is a foreign element the renderer drops, taking the label with it. - setActiveTab() ignores clicks on the already-active tab. It wrote the store with identical values, re-rendering subscribers for nothing. - Declare the devtools feature so the MARKVIEW_DEVTOOLS branch in setup() compiles. The cfg named a feature that was never declared, so the block was dead code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Replaces #4, which patched the claim protocol without fixing the underlying race. This removes the need for the protocol instead.
The problem
Mermaid and Vega render by mutating the DOM after React has committed it: the pipeline emits an HTML string with plain code blocks, React writes it via
dangerouslySetInnerHTML, and an effect inMarkdownViewthen walks that DOM replacing each<pre>with SVG.That leaves the content with two writers. React reassigns
innerHTMLwhenever the string changes and knows nothing about the SVG, so a re-render wipes the diagrams; the effect then has to put them back, cancelling whatever render is in flight. Thedata-*-claimedattributes and theisCancelledplumbing exist only to arbitrate between the two writers — and they don't manage it. A superseding pass skips the blocks the cancelled pass claimed, so the blocks stay as code with nothing left to render them.The change
Render diagrams where KaTeX already renders them: in the pipeline, before the HTML reaches React. Blocks are substituted inside an inert
<template>, so the SVG travels with the tab'srenderedHTMLinstead of living only in the DOM.The content has a single writer again. Switching tabs cannot lose a diagram, because nothing re-renders on a switch — the SVG is already in the string. The claim attributes, the cancellation plumbing and the revert-then-rerender path are all gone;
mermaid.tsandvega.tsare now just "source in, SVG out". A theme switch re-runs the pipeline, the same path as a content change.Net: -225 / +158, mostly deletions.
Vega needs a real element with a width to lay out against, so it renders into an offscreen host sized to
.markdown-body's content box, removed again before returning. The live document never sees it.Also in here
<svg>subtrees. Now that SVG is part ofrenderedHTML, a<mark>injected into one is a foreign element the renderer drops — taking the label with it.setActiveTab()ignores clicks on the already-active tab. It wrote the store with identical values, re-rendering subscribers for nothing. This was a real bug: clicking the active tab reliably turned its diagrams back into code blocks.devtoolsfeature so theMARKVIEW_DEVTOOLSbranch insetup()compiles. Thecfgnamed a feature that was never declared, so the block was dead code (it also produces a build warning today).Testing
Verified by hand in a release build on Linux: switching between tabs with diagrams, clicking the active tab, Ctrl+F highlighting, and theme switching, plus a document covering Vega-Lite (fixed and
"width": "container"), plain Vega, Mermaid, and a deliberately malformed spec — a bad diagram renders an error block without costing the rest of the document.🤖 Generated with Claude Code