Catalog: Mermaid diagram previewer - #5269
Open
QuiltSimon wants to merge 9 commits into
Open
Conversation
Render .mmd/.mermaid files as diagrams in the file preview, in bucket and
package browsing and from quilt_summarize.json ("types": ["mermaid"]), with
the plain-text source reachable from the view selector.
The loader reads the object from S3 rather than the preview endpoint: that
endpoint returns a truncated head/tail, which turns a large diagram into a
syntax error rather than a shorter diagram. mermaid is lazy-loaded to keep it
out of the entry chunk and rendered at its strict security level, since the
diagram definition is customer data.
Adding mermaid raises katex to ^0.16.47, whose package exports no longer map
the deep source path Notebook.jsx imported; it now imports the supported
katex/contrib/auto-render subpath, same default export.
# Conflicts: # catalog/CHANGELOG.md
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #5269 +/- ##
==========================================
- Coverage 34.92% 34.87% -0.05%
==========================================
Files 741 744 +3
Lines 23804 24037 +233
Branches 6429 6479 +50
==========================================
+ Hits 8314 8384 +70
- Misses 13746 13887 +141
- Partials 1744 1766 +22
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Drop mermaid's temp measuring node on both error paths and on unmount: it appends that node to document.body to measure text and removes it after, but its parse-error paths throw first, stranding the node and mermaid's own error graph outside the preview pane. Distinct ids meant one orphan per failed render; a same-id re-render already self-cleaned. Raise maxTextSize to the loader's fetch ceiling. Past its 50k-char default mermaid silently substitutes a "text size exceeded" graph, which renders as if it were the diagram. Spread caller props before dangerouslySetInnerHTML so an author-set `children` from quilt_summarize.json cannot replace the SVG, and derive the DOM id from useId rather than a module-level counter.
A ```mermaid fence in a .md file rendered as plain monospace: `mermaid` is not a registered hljs grammar, and the .mmd previewer only claims that extension, so a diagram embedded in Markdown never reached it. The fence renderer emits a <pre> holding the escaped source, and the diagram is drawn into that node after sanitization -- SANITIZE_OPTS carries no svg tags, so an SVG emitted into the HTML string is stripped before it reaches the DOM. A fence whose definition does not parse keeps showing its source.
Diagrams are authored as ```mermaid fences inside markdown documents, which is where they now render -- every markdown surface funnels through components/Markdown's Container, so a README, a .md file, quilt_summarize markdown and Qurator's replies all draw them. A previewer keyed on a .mmd/.mermaid file extension served no real authoring practice, so it goes: the loader, the renderer, the FileType and PreviewData variants, the viewModes label, and the quilt_summarize "mermaid" type. The mermaid dependency stays -- the fence pass loads it.
A dense diagram rendered to fit was unreadable and had no way in. Wheel-zoom with Ctrl/Cmd (also what a trackpad pinch sends) anchored on the cursor, drag to pan once zoomed, double-click to step in, hover controls, and +/-/0/arrows once the diagram has focus. Driven by the SVG's own viewBox rather than a CSS transform, so the diagram stays crisp at every zoom level instead of scaling a raster. Cursor anchoring maps the pointer through the SVG's own CTM, which stays exact however preserveAspectRatio letterboxes the diagram in its box. A plain wheel is left alone: a diagram mid-page must not trap the reader's scroll. The view is clamped inside the diagram, so panning cannot wander into empty space, and zoom-out stops at fit. The viewBox math is pure functions in viewBox.ts, tested directly -- mermaid needs a real browser, so the geometry is where the behaviour can be pinned down without one.
setPointerCapture rejects a pointer id the browser no longer considers active -- a release or cancel that beats the handler -- and the throw escaped onPointerDown as an uncaught error. Capture only keeps the drag alive once the cursor leaves the diagram; pointermove still tracks without it, so the failure is worth swallowing rather than surfacing. Found by driving the real gestures in a browser against the built module.
QuiltSimon
pushed a commit
that referenced
this pull request
Sep 2, 2026
Fixes from review of #5269: - apply() reset the cursor to 'grab' on every pointermove, so a drag showed the idle hand from its first pixel. It now leaves the cursor alone mid-drag. - Arrow keys called preventDefault() even at fit, where the pan is a no-op -- and every diagram is a tab stop, so a keyboard reader lost page scrolling with no way to tell why. They now defer to the page when there is nowhere to pan, the rule the wheel handler already followed. - The mermaid chunk import sat outside render()'s try and the promise was floating, so a stale chunk after a redeploy became an unhandled rejection rather than degrading to the visible source, which is the contract utils/hljs states for its own lazy grammars. - The fence label was matched case-sensitively, so ```Mermaid -- valid on GitHub, where authors learn the syntax -- silently rendered as monospace. - A stale effect returned out of the render loop before the temp-node sweep, stranding mermaid's measuring node. - maxTextSize raised mermaid's own guard 20x for input this file calls untrusted; dropped, so the library's ceiling applies. - getRenderer's memoize keyed on a fresh object literal every render: the cache never hit and grew a MarkdownIt + DOMPurify pair per render. Keyed on the identities that actually select a renderer. - The zoom controls and the focusable diagram had a hover state but no focus ring, against DESIGN.md. The comment over `node.innerHTML` no longer claims mermaid's DOMPurify pass is equivalent to the app's: it admits the <style> element SANITIZE_OPTS forbids by name, and runs against mermaid's own transitive dompurify. The view switch reinstates FileType.Mermaid -- removed earlier in this branch as a standalone .mmd previewer, which is not what it is here: a mode on a markdown document, offered only when that document has a fence to draw.
Fixes from review of #5269: - apply() reset the cursor to 'grab' on every pointermove, so a drag showed the idle hand from its first pixel. It now leaves the cursor alone mid-drag. - Arrow keys called preventDefault() even at fit, where the pan is a no-op -- and every diagram is a tab stop, so a keyboard reader lost page scrolling with no way to tell why. They now defer to the page when there is nowhere to pan, the rule the wheel handler already followed. - The mermaid chunk import sat outside render()'s try and the promise was floating, so a stale chunk after a redeploy became an unhandled rejection rather than degrading to the visible source, which is the contract utils/hljs states for its own lazy grammars. - The fence label was matched case-sensitively, so ```Mermaid -- valid on GitHub, where authors learn the syntax -- silently rendered as monospace. - A stale effect returned out of the render loop before the temp-node sweep, stranding mermaid's measuring node. - maxTextSize raised mermaid's own guard 20x for input this file calls untrusted; dropped, so the library's ceiling applies. - getRenderer's memoize keyed on a fresh object literal every render: the cache never hit and grew a MarkdownIt + DOMPurify pair per render. Keyed on the identities that actually select a renderer. - The zoom controls and the focusable diagram had a hover state but no focus ring, against DESIGN.md. The comment over `node.innerHTML` no longer claims mermaid's DOMPurify pass is equivalent to the app's: it admits the <style> element SANITIZE_OPTS forbids by name, and runs against mermaid's own transitive dompurify. The view switch reinstates FileType.Mermaid -- removed earlier in this branch as a standalone .mmd previewer, which is not what it is here: a mode on a markdown document, offered only when that document has a fence to draw.
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.
Renders Mermaid diagrams in the catalog file preview.
.mmd/.mermaidfiles preview as diagrams when browsing a bucket or packageand from
quilt_summarize.json("types": ["mermaid"]); the plain-text sourcestays reachable from the view selector, and a definition that does not parse
reports Mermaid's error in place of the diagram.
Changes
Preview/loaders/Mermaid.tsx) and renderer (Preview/renderers/Mermaid/),registered in the loader chain,
FileType,PreviewData,viewModes, and thequilt_summarize.jsonschema +summarize.tstypes.docs/Catalog/VisualizationDashboards.md.Two decisions worth a reviewer's attention:
/previewendpoint. That endpoint's allowlist iscsv/excel/fcs/ipynb/parquet/vcf/bed/txtand it returns a truncated head/tail —a partial graph definition is a syntax error, not a shorter diagram. So the loader
follows
Markdown.jsxand gets the object from S3. No preview-Lambda change andno
_VERSIONS["preview"]bump are needed.securityLevel: 'strict'. mermaid is ESM-only and ~84MB unpacked;it is loaded via
RT.mkLazyso it lands in its own chunk rather than the entrybundle. Diagram definitions are customer data, so mermaid's sanitizer stays on and
click/script directives in the graph are refused.
Incidental: katex import path
mermaid depends on
katex@^0.16.47, which raises this repo's existing direct katexdep from 0.16.21. In 0.16.47 the deep source path
Notebook.jsximported(
katex/contrib/auto-render/auto-render) resolves through the./*export wildcardto raw TypeScript, which webpack's
node_modules-excluded ts-loader will not parse —npm run buildfails without this. It now imports the supported subpathkatex/contrib/auto-render(dist/contrib/auto-render.js), samerenderMathInElementdefault export, arity 2.Verified
npm run typecheck— cleannpm run lint(oxfmt + oxlint) — cleannpm test— 123 files, 1118 passed / 1 skipped, including 4 newdetectcasesnpm run build— webpack compiled with 0 errors; confirmed the entry chunkcontains only the literal
"mermaid"enum string and no library internals, so thecode-split holds
markdownlinton the changed docs + CHANGELOG — cleanNot verified: rendering against real diagrams in a running stack — no dev stack was
brought up for this.
Follow-up
Deploying this needs a
_VERSIONS["catalog"]bump inquiltdata/deployment, whichcan only pin a SHA that exists on
master— so it follows this merge, as thedeployment log does for every catalog change (
Bump catalog to e015c1d0 (#2619)).quiltdata/enterpriseneeds nothing: it has no filetype allowlist, only theSUMMARIZE_KEY/README_KEYSstring constants.🤖 Generated with Claude Code
Greptile Summary
The PR adds lazy-loaded Mermaid diagram previews for
.mmd,.mermaid, and summary-configured files while preserving a plain-text view and in-place parse errors.quilt_summarize.jsoncontract and Catalog documentation.Confidence Score: 5/5
The PR appears safe to merge, with no concrete blocking or independently actionable non-blocking issue identified.
The new preview path follows the existing loader, renderer, view-mode, S3-access, and dependency-locking contracts, and the investigated lifecycle, renderer-signature, schema, and installation paths did not establish an observable defect.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR F[Bucket or package file] --> D{Preview detection} D -->|.mmd or .mermaid| L[Mermaid loader] D -->|summary type: mermaid| L L --> G{Object exceeds automatic threshold?} G -->|Yes| C[Ask user to load preview] G -->|No or approved| S[Read complete object from S3] S --> P[Create Mermaid preview data] P --> V{Selected view} V -->|Mermaid| R[Lazy-load Mermaid and render strict SVG] V -->|Plain Text| T[Render source text] R --> E{Definition parses?} E -->|Yes| O[Display diagram] E -->|No| X[Display Mermaid error]Reviews (1): Last reviewed commit: "Catalog: Mermaid diagram previewer" | Re-trigger Greptile
Context used: