Skip to content

preview: Fix mermaid labels clipped in preview - #529

Closed
junereycasuga wants to merge 1 commit into
ZenNotes:mainfrom
junereycasuga:fix/mermaid-improvements
Closed

junereycasuga wants to merge 1 commit into
ZenNotes:mainfrom
junereycasuga:fix/mermaid-improvements

Conversation

@junereycasuga

Copy link
Copy Markdown
Contributor

Summary

Fixes clipped mermaid diagram labels in the preview by ensuring text metrics are measured against the same loaded font that will paint the final SVG.

Mermaid measures text in a temporary element appended to document.body. We were passing fontFamily: "inherit" in the theme, so the measurement font could resolve differently from the font actually used inside .prose-zen. Rendering also often ran before the active font finished loading, so labels were sized for a fallback font and then clipped once the real font painted.

Changes

  • Read --z-text-font as a concrete font-family stack and pass it to mermaid's themeVariables.fontFamily.
  • Await document.fonts.ready before calling mermaid.render() so text metrics are computed against the loaded font.

Screenshots

Before

SCR-20260802-juow

After

SCR-20260802-juwz

@junereycasuga junereycasuga changed the title Preview: Fix mermaid labels clipped in preview preview: Fix mermaid labels clipped in preview Aug 2, 2026
@adibhanna

Copy link
Copy Markdown
Contributor

Merged into the v2.21.0 release branch as 9ee84c8. Thank you, this is a good catch and the diagnosis was exactly right.

For anyone reading later, the root cause in one line: body uses --z-interface-font (sans) while .prose-zen uses --z-text-font (mono by default), and mermaid measures HTML labels in a temp element attached to body. So fontFamily: "inherit" measured sans and painted mono, and every label came out narrower than its own text.

Verified by driving the built app over CDP and measuring each label's content width against its foreignObject box:

labels overflowing worst overflow
before 15 / 15 20.0px
after 0 / 16 0px

Three extra checks beyond the screenshots:

  • With a user-chosen text font (text_font = "Georgia"), the --z-text-font read path resolves and measures correctly. Worth noting since the variable is unset until someone picks a font, so the default case only exercises the hardcoded fallback.
  • Sequence, pie, and class diagrams, which mermaid measures with its own font config rather than themeVariables, show no regression.
  • document.fonts.ready sits after the blocks.length === 0 early return, so notes without mermaid never wait on it. That ordering matters because preview swaps in its whole DOM only after renderMermaidBlocks resolves.

One follow-up commit on top (dd6bc46): the fallback stack was written twice in one file, as readThemeFont's default parameter and again at the only call site. It is now a named constant with a comment tying it to the --z-text-font fallback in index.css, since a drift between the measured stack and the painted stack is precisely what brings the clipping back. Resolved stack is byte-identical, no behavior change.

Closing here because the merge landed on the release branch rather than through the GitHub button, so this cannot auto-close. Ships in 2.21.0.

@adibhanna adibhanna closed this Aug 2, 2026
@junereycasuga
junereycasuga deleted the fix/mermaid-improvements branch August 3, 2026 00:57
@junereycasuga

Copy link
Copy Markdown
Contributor Author

thanks @adibhanna

@adibhanna adibhanna mentioned this pull request Aug 3, 2026
MichaelC001 pushed a commit to MichaelC001/zennotes that referenced this pull request Aug 4, 2026
MichaelC001 pushed a commit to MichaelC001/zennotes that referenced this pull request Oct 19, 2026
ZenNotes drew ```mermaid fences with mermaid.js. It worked, but it was the
heaviest stack in the renderer (mermaid.core alone is 140 kB gzipped before
any diagram chunk, about 800 kB across all of its chunks), it laid diagrams
out asynchronously through temporary DOM, and its labels were HTML inside
foreignObject, the layer behind every clipped-label bug here (ZenNotes#529, and the
getBoundingClientRect patch ZenNotes#911 needed). Theming was a 160-line
themeVariables map, rebuilt and re-rendered on every theme change.

Pele (pele.run, obsidianmd/pele, MIT) reads the same Mermaid syntax with its
own parsers and lays diagrams out itself, returning one SVG string
synchronously. Before switching, every example in Mermaid 11.17's docs that
mermaid.js accepts (476, across all 32 doc pages) rendered in Pele, as did
every fence in the repo and the demo tour; only ZenUML, which ZenNotes never
registered, is unsupported. In Chromium those 476 diagrams take 83 ms
instead of 4.2 s.

What changes:
- lib/mermaid-render.ts loads Pele through a new pele-runtime module, so the
  async chunk has a name the perf harness and the packaged-web browser test
  can hold off the boot path, and keeps the shared (fonts, source) cache.
  Once the runtime and a diagram type are loaded, peekMermaidSvg draws a new
  diagram on the spot, so typing one in live preview no longer shows a
  pending block. Fonts are the only theme input: labels are measured on a
  canvas in the note's text font, after any loading font settles.
- The cache trims the source first. Preview keeps a fence's trailing newline
  and the editor trims its body, so split view used to draw every diagram
  twice under two keys, the double work the shared cache exists to remove.
- Colour moved to CSS. A .pele rule in index.css maps --pele-* onto the
  --z-* tokens with the roles the themeVariables had (nodes on bg-2 with a
  bg-3 border, subgraphs on bg-softer, edges grey-1, series in the old pie
  and git order). One drawing is right in every theme, custom CSS themes
  included, and a theme switch repaints without a relayout.
- The ZenNotes#911 getBoundingClientRect patch and its test are gone: labels are SVG
  text and wrap inside their box.
- Images and links in diagrams stay off, as mermaid's strict mode had them,
  so drawing a diagram never fetches anything.
- Preview shows a render error as text in .zen-diagram-error. The old branch
  wrote the message into innerHTML, and the message quotes the diagram
  source, so a crafted fence could inject markup.
- pele 1.6.0 replaces mermaid in the five workspaces that declared it,
  pinned exactly while Pele moves fast. mermaid stays installed for
  Excalidraw's Mermaid-to-Excalidraw dialog, which still lazy-loads it; the
  chunk comments, preload deferral lists and perf patterns name both.

What it deliberately does not do: no maxWidth is passed, so Pele never flips
a wide left-to-right flowchart to top-down; the author's direction stands
and the existing pan/zoom handles width. Pele's own zoom is not used.

What users will notice: layouts are Pele's (node placement differs from
mermaid.live, mindmaps draw as an outline tree), $$ math inside a label and
raw HTML other than <br> and bold or italic show as written, and %%{init}%%
theme settings give way to the app theme. The in-app manual, the website
docs and the MCP instructions say so.
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