Skip to content

Catalog: Mermaid diagram previewer - #5269

Open
QuiltSimon wants to merge 9 commits into
masterfrom
ux-mermaid
Open

Catalog: Mermaid diagram previewer#5269
QuiltSimon wants to merge 9 commits into
masterfrom
ux-mermaid

Conversation

@QuiltSimon

@QuiltSimon QuiltSimon commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Renders Mermaid diagrams in the catalog file preview.
.mmd / .mermaid files preview as diagrams when browsing a bucket or package
and from quilt_summarize.json ("types": ["mermaid"]); the plain-text source
stays reachable from the view selector, and a definition that does not parse
reports Mermaid's error in place of the diagram.

Changes

  • New loader (Preview/loaders/Mermaid.tsx) and renderer (Preview/renderers/Mermaid/),
    registered in the loader chain, FileType, PreviewData, viewModes, and the
    quilt_summarize.json schema + summarize.ts types.
  • Docs: a Mermaid section in docs/Catalog/VisualizationDashboards.md.

Two decisions worth a reviewer's attention:

  • Reads S3 directly, not the /preview endpoint. That endpoint's allowlist is
    csv/excel/fcs/ipynb/parquet/vcf/bed/txt and it returns a truncated head/tail —
    a partial graph definition is a syntax error, not a shorter diagram. So the loader
    follows Markdown.jsx and gets the object from S3. No preview-Lambda change and
    no _VERSIONS["preview"] bump are needed.
  • Lazy-loaded, securityLevel: 'strict'. mermaid is ESM-only and ~84MB unpacked;
    it is loaded via RT.mkLazy so it lands in its own chunk rather than the entry
    bundle. 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 katex
dep from 0.16.21. In 0.16.47 the deep source path Notebook.jsx imported
(katex/contrib/auto-render/auto-render) resolves through the ./* export wildcard
to raw TypeScript, which webpack's node_modules-excluded ts-loader will not parse —
npm run build fails without this. It now imports the supported subpath
katex/contrib/auto-render (dist/contrib/auto-render.js), same
renderMathInElement default export, arity 2.

Verified

  • npm run typecheck — clean
  • npm run lint (oxfmt + oxlint) — clean
  • npm test — 123 files, 1118 passed / 1 skipped, including 4 new detect cases
  • npm run build — webpack compiled with 0 errors; confirmed the entry chunk
    contains only the literal "mermaid" enum string and no library internals, so the
    code-split holds
  • markdownlint on the changed docs + CHANGELOG — clean

Not 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 in quiltdata/deployment, which
can only pin a SHA that exists on master — so it follows this merge, as the
deployment log does for every catalog change (Bump catalog to e015c1d0 (#2619)).
quiltdata/enterprise needs nothing: it has no filetype allowlist, only the
SUMMARIZE_KEY / README_KEYS string 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.

  • Registers Mermaid throughout preview detection, rendering, view-mode, and preview-data plumbing.
  • Reads complete diagram definitions directly from S3 with the existing large-object gate.
  • Extends the shared quilt_summarize.json contract and Catalog documentation.
  • Updates Mermaid and KaTeX dependencies and switches Notebook to KaTeX’s supported auto-render export.

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

Filename Overview
catalog/app/components/Preview/loaders/Mermaid.tsx Adds extension and summary-driven loading of complete Mermaid source from S3, guarded by the established one-megabyte preview gate.
catalog/app/components/Preview/renderers/Mermaid/Mermaid.tsx Lazily renders strict-security Mermaid SVG with unique IDs, stale-effect protection, loading feedback, and parse-error presentation.
catalog/app/components/Preview/renderers/Mermaid/index.tsx Integrates the renderer through the existing lazy-component and two-argument preview-renderer conventions.
catalog/app/components/Preview/load.jsx Registers Mermaid in the preview loader chain without changing the established explicit-mode, summary-type, and extension precedence.
catalog/app/containers/Bucket/viewModes.ts Adds Mermaid to the file-view selector while retaining plain text as the alternate mode.
shared/schemas/quilt_summarize.json Extends the shared summary configuration enum with the Mermaid viewer type.
catalog/app/components/Preview/renderers/Notebook.jsx Moves KaTeX auto-render to its supported exported subpath to remain build-compatible with the resolved KaTeX version.
catalog/package.json Adds Mermaid as a runtime dependency; controlled builds remain pinned through the committed lockfile and npm-ci workflows.

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]
Loading

Reviews (1): Last reviewed commit: "Catalog: Mermaid diagram previewer" | Re-trigger Greptile

Context used:

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.
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 29.53586% with 167 lines in your changes missing coverage. Please review.
✅ Project coverage is 34.87%. Comparing base (aee6db6) to head (8905319).

Files with missing lines Patch % Lines
catalog/app/components/Markdown/mermaid/panZoom.ts 3.00% 111 Missing and 18 partials ⚠️
catalog/app/components/Markdown/mermaid/index.ts 40.00% 26 Missing and 4 partials ⚠️
...atalog/app/components/Preview/loaders/Markdown.jsx 11.11% 8 Missing ⚠️
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     
Flag Coverage Δ
catalog 34.87% <29.53%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.
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.

2 participants