Repository navigation
Render mermaid diagrams in the theme's own colours - #8
Merged
Merged
Conversation
Adds mermaid behind a dynamic import, so a document without a ```mermaid
fence never loads the library. The entry chunk does not grow (731 -> 722 KB);
mermaid arrives as its own chunks and already code-splits per diagram type.
Raw JS is not installed bytes — Tauri's default compression feature
brotli-compresses embedded assets, so 3.4 MB of JavaScript costs +888 KB in
Inkwell.app.
Diagrams are built after DOMPurify has run, replacing the code block it
produced. Inkwell sanitizes with USE_PROFILES: { html: true }, which strips
SVG; widening that profile to admit mermaid's output would weaken sanitizing
for every document, so mermaid's own strict-mode sanitizer is the boundary for
the SVG it generates instead.
Theming reads Inkwell's CSS custom properties and feeds them to mermaid's base
theme, so a diagram is drawn in the palette of whichever theme is showing
rather than in mermaid's default lavender. Toggling re-reads the variables and
redraws, and the render cache is keyed by theme plus source so a toggle can
never serve back a diagram drawn in the other palette.
Diagrams that colour a set of things — pie slices, gitGraph branches, timeline
sections — cannot be driven from a single accent, and deriving them from one
colour made every pie slice identical. They use a validated categorical set
instead: eight hues in fixed order, stepped separately per surface. Checked
against the real preview backgrounds, worst adjacent colour-blind separation
is 9.1 light / 8.4 dark and worst normal-vision separation 19.6 / 19.3.
Details:
- A cached diagram is swapped in synchronously so typing elsewhere does not
make every diagram flicker; anything new stays a code block until ready.
- Every DOM swap is guarded on isConnected — a 150 ms debounce plus async
rendering means a render can land on a preview that no longer exists.
- data-source-line moves from the <code> element onto the diagram, so
click-to-edit and scroll sync keep working on the diagram itself.
- A diagram that fails to parse shows the message and keeps the fence visible.
suppressErrorRendering stops mermaid injecting error nodes into the document.
- If the import fails, fences stay plain code blocks.
Adds --error to both themes; there was no token for it.
Co-Authored-By: Claude Opus 5 (1M context) <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.
Renders ```mermaid fences as diagrams, drawn in Inkwell's own colours.
Stacked on #7 — both change
renderPreview, so this is based onfeat/local-imagesto keep the diff readable. Merge #7 first; GitHub willretarget this to
mainand CI runs then.Cost: +888 KB installed, nothing at startup
mermaid is ~3.4 MB of JavaScript, which sounds fatal for a "~6 MB" app. It is
not, for two measured reasons.
It is not in the entry chunk. The library is behind a dynamic
import("mermaid")that only fires the first time a document actually containsa mermaid fence. The entry chunk is smaller than before (731 KB → 722 KB —
chunk-boundary churn, not a real saving). mermaid already code-splits per
diagram type, so a document with a flowchart never loads the gantt, cytoscape
or katex chunks.
Raw JS is not installed bytes.
tauri = { version = "2" }keeps the defaultcompressionfeature, which brotli-compresses embedded frontend assets:distrawInkwell.appLocal release builds run about 1 MB heavier than CI's — the shipped v0.2.4 in
/Applicationsis 4,540 KB against 5,572 KB for the same commit built here.Applying the delta, a shipped build lands near 5.4 MB. README's "~6 MB" and
the "30x smaller than Electron" claim both survive untouched.
Theme
The ask was a style that reads on both day and night. Rather than freezing one
palette that compromises on both, mermaid's
basetheme is driven fromInkwell's own CSS custom properties —
--bg-preview,--bg-code-block,--text-primary,--text-secondary,--border,--accent,--font-sans—with
darkModeset to match. A diagram is drawn in the palette of whichevertheme is showing, and toggling re-reads the variables, re-initialises mermaid
and redraws. The render cache is keyed by theme plus source, so a toggle can
never serve back a diagram drawn in the other palette.
One thing this exposed: diagrams that colour a set of things — pie slices,
gitGraph branches, timeline sections — cannot come from a single accent. Derived
from one colour, every pie slice rendered identically; the chart was unreadable
in both themes. Those slots now use a validated categorical set: eight hues in
fixed order, stepped separately per surface rather than flipped. Checked against
the real preview backgrounds (#fafafa / #252525): worst adjacent colour-blind
separation 9.1 light / 8.4 dark (OKLab ΔE, ≥8 target), worst normal-vision
separation 19.6 / 19.3 (≥15 floor). Past eight slots mermaid falls back to its
own derived colours rather than inventing a ninth hue.
--erroris new in both theme files; there was no token for it, and the parseerror message needs one that reads on either background.
Rendering after sanitizing
Inkwell sanitizes with
USE_PROFILES: { html: true }, which strips SVG.Widening that profile to admit mermaid's output would weaken sanitizing for
every document, so the diagram is built after DOMPurify has run, replacing the
code block it produced. mermaid's own
securityLevel: "strict"sanitizer is theboundary for the SVG it generates — the documented contract, and the reason the
profile does not have to move.
Behaviour details
make every diagram flicker. Anything new stays a code block until ready.
pre.isConnected— a 150 ms debounce plus asyncrendering means a render can land on a preview that no longer exists.
new entry per keystroke.
data-source-linemoves from the<code>element onto the diagram, soclick-to-edit and scroll sync keep working on the diagram itself. (The
attribute is on
<code>, not<pre>— markdown-it renders fence attrs there.)so the source stays editable.
suppressErrorRenderingstops mermaid injectingits own error nodes into the document — verified: zero stray nodes on
body.rendered as before this PR.
Verified
Headless WebKit against the built
dist, serving the app's real CSP as an HTTPheader, plus the real
.appon macOS:unsafe-eval; the policy is unchanged.its source; a plain ```js fence is untouched.
#f6f8fa→#2d2d2d, node text#1a1a1a→#e0e0e0— the--bg-code-block/--text-primaryvalues.data-source-linepresent on every rendered diagram..appon macOS: diagrams render in WKWebView under the shipped CSP, andthe Theme button redraws them.