Skip to content

fix(ui): 0.33.0 adoption feedback from hosts (ui 0.34.0) - #1402

Merged
backnotprop merged 3 commits into
mainfrom
fix/ui-0.33-adoption-feedback
Aug 27, 2026
Merged

fix(ui): 0.33.0 adoption feedback from hosts (ui 0.34.0)#1402
backnotprop merged 3 commits into
mainfrom
fix/ui-0.33-adoption-feedback

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

Four follow-ups from the Workspaces adoption of @plannotator/ui 0.33.0. All additive, no new dependencies, no server changes; Plannotator's own behavior is unchanged (its entries fill the slots eagerly before first render and the single-file builds inline everything as before). This becomes ui 0.34.0 on the already published core 0.25.0.

1. Mermaid kept a KaTeX chunk alive for hosts

Mermaid 11.15.0 typesets $$...$$ labels through its own import("katex") in renderKatexUnsanitized. There is nothing to configure it away: legacyMathML / forceLegacyMathML only pick the output mode, the guard around the import is the @mermaid-js/tiny build marker, and no hook accepts a renderer. The import only runs for a label matching Mermaid's $$ regex, but chunk emission is static, so a host that registered mathRendererLoader and aliased utils/math-default-loader still built a katex-*.js chunk, and because that chunk had two dynamic importers (the host's loader module and the Mermaid runtime) Rollup kept it separate from the host's loader chunk: a math document fetched the 57-byte loader chunk plus the shared 261 KB KaTeX chunk.

The fix is option (b) realized as option (c): a bundler-facing redirect of the katex specifier, for importers inside the mermaid package only, to a new package module utils/mermaid-math-slot, whose default export renders through the math slot with Mermaid's own options passed through (throwOnError: true, displayMode: true, the MathML output mode), so a KaTeX renderer produces exactly the markup Mermaid produced from its direct import. MermaidBlock awaits loadMathRenderer() before rendering a diagram whose source carries a $$ label (a resolved promise on Plannotator's eagerly filled slot, the host's loader otherwise); an empty slot at render time throws a message naming the cause into the block's existing error panel. The recipe (a resolveId plugin, not a plain alias, since a plain katex alias would also rewrite the host's own loader) is in HANDOFF.md "Lazy renderers and eager entries", item 2.

Measurement on a scratch Vite 6 consumer of this checkout (loader registered, math-default-loader aliased, a document with inline math, a display block and a flowchart with a $$ label):

chunks carrying the KaTeX body who imports it JS chunks total
before 1 (katex-*.js, 261 KB, shared) mermaid.core-*.js dynamically, host loader chunk statically 367
after (redirect) 1 (host-katex-*.js, 261 KB, host-owned) the entry's own import() only; mermaid.core-*.js has no KaTeX import 366

So: one KaTeX chunk, owned by the host, one file fetched for a math document. Plannotator does not redirect; its Mermaid keeps its direct KaTeX, inlined by the single-file builds, and tests/entry-assets.test.ts now also pins that mermaid-math-slot.ts never names katex (which would re-create the chunk). No test in the repo renders a real Mermaid diagram with a math label (Mermaid does not render under happy-dom) and nothing in Plannotator's own documents exercises $$ labels; the bridge is pinned by utils/mermaid-math-slot.test.ts (delegation with Mermaid's exact options, KaTeX parity, the empty-slot error, the label regex) and the pre-render warm by two new cases in components/DiagramBlock.lazyRetry.test.tsx.

2. Double banner on bridge failure

HtmlViewer gains bridgeErrorDisplay?: 'banner' | 'none'. 'banner' is the default and renders the [data-bridge-error] strip exactly as 0.33.0 did, so Plannotator and existing hosts are unchanged. 'none' renders no strip and no dismiss button for either state while onBridgeUnavailable fires exactly as before and the version-mismatch console warning still logs once. Both values are tested in HtmlViewer.bridgeAsset.test.tsx. Documented in README and HANDOFF: the package owns the strip by default; a host that renders its own passes 'none'.

3. HANDOFF alias regex

The documented bridge-script alias is anchored to /^\.\/bridge-script$/ (the relative sibling form, which is the only form the package uses internally: srcdoc.ts, useHtmlAnnotation.ts, index.ts), with a resolvable replacement, and the text explains why the unanchored /\/bridge-script$/ was wrong: it matched any specifier ending in /bridge-script, including another package's entry or a deeper import in the host's own tree. Verified on the scratch consumer: with the anchored alias the viewer chunk drops from 553 kB to 366 kB and the bridge literal count goes from 2 (chunk plus asset) to 1 (asset only).

4. resetMathRenderer() over-reach

Reset now empties the renderer and its source, forgets a load in flight (a stale in-flight result no longer fills the slot; the next loadMathRenderer() invokes the registered loader afresh) and keeps the registered loader. setMathRendererLoader(null) is the explicit way back to the package default and getMathRendererLoader() reads the registration. setMathRendererLoader itself is unchanged (a load already in flight at registration still fills the slot, since the component that started it is waiting on the result). New tests: register a loader, set a renderer, reset, assert the loader is still registered and the next load uses it and never the default; the in-flight discard; the explicit null. utils/math.test.ts was the only test relying on reset clearing the loader and now drops it explicitly. The other reset-style helpers were reviewed: resetIdentityProvider and resetIdentityGenerator reset exactly what they name, and __setMermaidRuntimeLoaderForTests is a stand-in by name, so nothing else changes.

Verification

  • bun run typecheck (including the strict-consumer gate): clean.
  • bun test packages/ui packages/editor: 869 pass, 579 skip, 0 fail.
  • DOM_TESTS=1 on MathBlock.firstPaint, InlineMarkdown, MermaidBlock, DiagramBlock.lazyRetry, srcdoc, htmlPinpointProtocol, htmlLiveProtocol, HtmlViewer.bridgeAsset, HtmlViewer.vimHud, App.htmlChrome, mermaid-math-slot, math: 264 pass, 0 fail.
  • build:review + build:hook + tests/entry-assets.test.ts: 26 pass, markers unchanged. Sizes against the main build in the primary checkout: hook index.html 21,822,225 bytes vs 21,818,752 (+3.5 KB), review.html 17,570,771 vs 17,567,628 (+3.1 KB), which is the new module and prop code.
  • guides.show build:viewer + check:manifest: manifest in sync, viewer hash unchanged.

AI-assisted (Claude) under maintainer direction.

Four follow-ups from the Workspaces adoption of @plannotator/ui 0.33.0.

1. Mermaid kept a shared KaTeX chunk alive in host builds: the runtime's own
   import("katex") for $$ labels has no flag and no hook, so a host with a
   mathRendererLoader and the math-default-loader alias still emitted a
   katex-*.js chunk with two dynamic importers, and a math document fetched
   two files. New alias target utils/mermaid-math-slot renders Mermaid's
   labels through the math slot with Mermaid's own options; MermaidBlock
   awaits loadMathRenderer() before a diagram whose source carries a $$
   label. Measured on a scratch Vite 6 consumer: one shared KaTeX chunk
   before (367 chunks), one host-owned KaTeX chunk after (366 chunks).
   Plannotator does not redirect and its bundles keep Mermaid's direct KaTeX.
2. HtmlViewer bridgeErrorDisplay ('banner' default, 'none') so a host that
   renders its own notice from onBridgeUnavailable gets no package strip.
3. HANDOFF bridge-script alias anchored to /^\.\/bridge-script$/ so it
   cannot catch another package's bridge-script entry.
4. resetMathRenderer() keeps the registered loader and discards a stale
   in-flight result; setMathRendererLoader(null) and getMathRendererLoader
   added. Other reset helpers reviewed and left as is.

AI-assisted (Claude) under maintainer direction.
Version bump and lockfile refresh for the ui 0.34.0 release carrying the 0.33.0 adoption feedback fixes. Core stays 0.25.0.

AI-assisted (Claude) under maintainer direction.
Layout-agnostic importer test for the Mermaid KaTeX redirect (hoisted, Bun
isolated and pnpm layouts all end in node_modules/mermaid/), the slot module
resolved from the host's config file rather than the Mermaid importer, the
note that only a direct setMathRendererLoader(null) unregisters a loader,
and the duplicate no-katex source pin dropped from the slot test (the
entry-assets one stays).

AI-assisted (Claude) under maintainer direction.
@backnotprop
backnotprop merged commit 8e0c51f into main Aug 27, 2026
64 of 67 checks passed
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.

1 participant