Skip to content

feat(ui): @plannotator/ui 0.40.0 — Mermaid 12.0.0 (ELK by default), loaded lazily - #1557

Merged
backnotprop merged 6 commits into
mainfrom
feat/ui-0.40.0-mermaid-12
Sep 17, 2026
Merged

backnotprop merged 6 commits into
mainfrom
feat/ui-0.40.0-mermaid-12

Conversation

@backnotprop

@backnotprop backnotprop commented Sep 17, 2026

Copy link
Copy Markdown
Owner

What changed

@plannotator/ui 0.40.0 on @plannotator/core 0.25.3: Mermaid 11.17.2 → 12.0.0 (exact pin), loaded lazily in the plan editor, on top of the theme-aware Mermaid work from #1556.

  • mermaid 12.0.0, exact. We take 12's defaults (owner ruling): ELK is the default layout for flowchart / state / class / ER / requirement (elkjs is bundled in mermaid itself now), the legacy flowchart / class / state diagram ids are gone, and the build targets Safari 17.4+ / ES2024. No API or type break: bun run typecheck is clean, MERMAID_CONFIG (securityLevel: 'strict', still pinned by test) and the 0.39 theme mapping apply unchanged, every rendered SVG id keeps its 11.x shape ({renderId}-flowchart-{node}-{n}, {renderId}-L_{from}_{to}_{n}, {renderId}-state-…, {renderId}-edge{n}, {renderId}-classId-…, {renderId}-entity-…, global actor{n} / root-{n} in sequence), and the only DOM-order change is that g.edgePaths children are now in declaration order. The minimumReleaseAge gate was respected: 12.0.0 was published 2026-09-10T07:43Z and the install ran after 2026-09-17T07:43Z; nothing was added to minimumReleaseAgeExcludes.
  • Lazy import in the plan editor. packages/editor/App.tsx no longer imports @plannotator/ui/utils/mermaid-eager; the runtime resolves through utils/mermaid's existing import('mermaid') on the first MermaidBlock render. MermaidBlock shows the source fence under a role="status" "Rendering diagram…" line until the first SVG lands (never the error panel as a placeholder), and applyMermaidTheme is keyed on the runtime object so the lazily loaded runtime is themed on its first render. mermaid-eager stays exported for hosts that want startup registration. tests/entry-assets.test.ts now asserts the eager marker is absent from both bundles; DiagramBlock.lazyRetry.test.tsx pins the pending state.
  • Theme sweep on 12: 52 palettes × 2 modes × 15 diagrams = 104 combinations, 17,160 text-on-fill pairs and 9,880 line-on-canvas pairs measured from the rendered SVG in Chromium, 0 failures (4.5:1 text / 3:1 lines) and 0 render errors; the theme mapping is unchanged, the geometry under it is ELK's (harness identical to the feat(ui): Mermaid diagrams follow the active color theme and mode #1556 sweep).
  • HANDOFF.md gains the "Mermaid 12 (0.40.0)" section Workspaces is waiting for (ELK default, the concrete SVG id patterns, the edgePaths order change and what a host should do, the Safari 17.4+ floor, the lazy-load contract, the theming contract with and without tokens, the publish order); README and AGENTS updated; the visual-explainer skill's render gate expects Mermaid 12.
  • feat(core): carry elementContext through html-anchor host helpers #1549 is included: this branch is rebased on a42aefc8, so core 0.25.3 carries parseHtmlElementContext / MAX_ELEMENT_CONTEXT_BYTES / MAX_PAGE_URL_LENGTH on @plannotator/core/html-anchor and the element-context round trip. That is also why the ui pin moves to 0.25.3.

Bundle sizes (gzip level 9; main = a42aefc8)

Artifact main (11.17.2, eager) this branch (12.0.0, lazy) delta
apps/hook/dist/index.html 22,796,940 B (gz 6,979,161) 24,642,091 B (gz 7,542,302) +1,845,151 B (+8.1%), gz +563,141
apps/hook/dist/review.html 18,274,838 B (gz 5,709,003) 18,274,838 B (gz 5,709,003) unchanged (never carried Mermaid)
portal entry chunk index-*.js 4,800,846 B 4,140,744 B −660,102 B (−13.7%)
portal mermaid.core-*.js (lazy, first diagram) in entry 639,505 B (gz 151,758) new chunk
portal elk-*.js (Mermaid's own import, first ELK layout) none 1,469,604 B (gz 448,505) new chunk

Honest reading: the single-file builds (hook, review, opencode, the compiled binary) inline every dynamic import through inlineDynamicImports, so the lazy import saves nothing there — the +1.85 MB on index.html is Mermaid 12's own runtime plus ELK, and it would be the same with the eager import kept (the earlier eval measured a 61-byte difference). The lazy import only moves bytes on a chunked build: the share portal's entry chunk drops by 660 KB, and mermaid.core (plus ELK, which Mermaid fetches itself either way) is downloaded only when a document actually has a diagram.

Browser floor

Mermaid 12 is built for Safari 17.4+ and ES2024 (upstream release note; Node 22.12+ for anything importing it server-side). A host that must render diagrams on an older Safari stays on ui 0.39.x. Nothing else in @plannotator/ui moved its floor.

Publishing (by hand, in this order — CI never publishes these packages)

# after merge, from main
cd packages/core && npm publish            # @plannotator/core 0.25.3 FIRST
cd ../ui && bun run --cwd . smoke:package  # preflight: source + packed manifest, pnpm consumer install
bun pm pack && npm publish                 # @plannotator/ui 0.40.0 (pins core 0.25.3 exactly)

--provenance does not work from a local publish (no OIDC); --tag next first and npm dist-tag add … latest after the consumer preflight is the established pattern.

Security

Mermaid 12 brings chevrotain 11.1.2 (via langium), which pins lodash-es exactly 4.17.23 (CVE-2026-4800 high, CVE-2026-2950 medium) beside the 4.18.1 main already resolves through dagre-d3-es; a root "overrides": { "lodash-es": "4.18.1" } forces the single 4.18.1 copy (grep -o '"lodash-es@[0-9.]*"' bun.lock | sort -u → only 4.18.1), and chevrotain still parses every grammar under it: all 15 eval diagrams render with zero errors in the built plan editor, typecheck / ui utils tests / MermaidBlock DOM tests / smoke:package pass, and the rebuilt bundles are byte-identical in size to the table above.

Verification

  • bun run typecheck clean; bun test packages/ui/utils packages/core packages/shared (1930 pass); DOM_TESTS=1 bun test packages/ui/components/MermaidBlock* packages/ui/components/Viewer* DiagramBlock.lazyRetry (92 pass); bun test scripts/dom-test-allowlist.test.ts tests/entry-assets.test.ts apps/skills/extra/plannotator-visual-explainer/SKILL.test.ts (33 pass, including the built-HTML marker checks against the fresh dist/).
  • bun run --cwd packages/ui smoke:package: passes ("Verified @plannotator/ui@0.40.0 packs, resolves AnnotationPanel/Viewer/StickyHeaderLane/parser, and installs externally with @plannotator/core@0.25.3").
  • bun run --cwd apps/guides-show check:manifest: in sync (the viewer bundle carries no Mermaid).
  • Full builds: bun run --cwd apps/review build && bun run build:hook && bun run build:opencode && bun run build:portal.
  • Sign-off page (local file, not an artifact): /private/tmp/claude-501/-Users-ramos-plannotator-plannotator/34d56d5a-33a8-4878-92b2-3f4318214ae2/scratchpad/mermaid12-v040/index.html — every palette × mode on 11 vs 12 with the contrast numbers, side-by-side before/after for six palettes, and the size table.

Bump @plannotator/ui's mermaid dependency from ^11.17.2 to an exact 12.0.0
and regenerate bun.lock. Mermaid 12 lays flowchart, state, class, ER and
requirement diagrams out with ELK by default (elkjs is now bundled in
mermaid itself), targets Safari 17.4+ / ES2024, and drops the legacy
flowchart/class/state diagram ids. We take 12's defaults rather than
pinning the 11 ones. No API or type changes were needed: MERMAID_CONFIG
(securityLevel 'strict') and the theme mapping apply unchanged, every
generated SVG id keeps its 11.x shape, and the katex import Mermaid makes
for $$ labels is the same call (the math-slot bridge is untouched).

The visual-explainer skill's render gate now expects Mermaid 12.
Drop the eager registration import (@plannotator/ui/utils/mermaid-eager)
from the plan editor entry. Since Mermaid 12 the runtime plus ELK is about
1.8 MB larger, so a plan with no diagram must not pay for it: the block now
resolves the runtime through utils/mermaid's own import('mermaid') on the
first MermaidBlock render. In the chunked share-portal build that moves
mermaid.core (~640 KB) out of the entry chunk; the single-file builds still
inline it through inlineDynamicImports, so nothing changes there except
Mermaid 12's own size.

MermaidBlock shows the source fence under a role=status "Rendering
diagram" line until the first render lands, never the error panel, and
applyMermaidTheme is keyed on the runtime object so the lazily loaded
runtime is themed on its first render like an eagerly registered one.
mermaid-eager stays exported for hosts that want startup registration.

tests/entry-assets.test.ts now asserts the eager Mermaid marker is absent
from both app bundles; DiagramBlock.lazyRetry.test.tsx pins the pending
state.
Add the "Mermaid 12 (0.40.0)" section hosts need to adopt the bump: ELK
layout by default and the other 12 changes we take, the concrete SVG id
patterns (unchanged 11 -> 12, per family), the g.edgePaths declaration-order
change and the select-by-id rule, the Safari 17.4+ / ES2024 floor, the
lazy-load contract (the plan editor no longer imports mermaid-eager; how a
host keeps startup registration; the pending state; measured chunk and
single-file sizes), the theming contract with and without tokens, the 0.40
contrast re-sweep (104 combinations, 0 failures), and the publish order
core 0.25.3 -> ui 0.40.0. Retitle the theme and element-context sections
to 0.40.0, fold the two unreleased publishing bullets into the current
pair, and update the 0.32.0 lazy-renderer text, README and AGENTS for the
new policy.
Carries the #1549 html-anchor element-context exports (parseHtmlElementContext,
MAX_ELEMENT_CONTEXT_BYTES, MAX_PAGE_URL_LENGTH) that @plannotator/ui 0.40.0
imports. Publish this before ui 0.40.0.
Mermaid 12.0.0 (exact), lazy runtime loading in the plan editor, the
theme-aware diagram mapping from #1556, and the #1549 element-context host
seam. Pins @plannotator/core 0.25.3 exactly; bun.lock records both
workspace versions. Publish core 0.25.3 first, then this.
Comment thread bun.lock Fixed
Comment thread bun.lock Fixed
Mermaid 12 pulls chevrotain 11.1.2 (via langium), which pins lodash-es
exactly 4.17.23 (CVE-2026-4800 high, CVE-2026-2950 medium), so the
workspace resolved a second, vulnerable copy beside the 4.18.1 that
dagre-d3-es already resolves. A root "overrides" entry forces every
lodash-es range to 4.18.1 (published 2026-04-01, past the 7-day gate);
bun.lock now records a single lodash-es@4.18.1. chevrotain still parses
every diagram grammar under it: all 15 eval diagrams render with zero
errors in the built plan editor, typecheck, the ui utils and MermaidBlock
tests, and the package smoke pass, and the built bundles are byte-for-byte
the same size.
@backnotprop
backnotprop merged commit 2a51b26 into main Sep 17, 2026
28 checks passed
@backnotprop
backnotprop deleted the feat/ui-0.40.0-mermaid-12 branch September 17, 2026 08:13
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