From ba5a8a6cc924da67d1dacfb9f09598c8f3d9d396 Mon Sep 17 00:00:00 2001 From: Graham Barber Date: Thu, 14 May 2026 22:16:12 -0700 Subject: [PATCH] fix: render mermaid before flipping into presenter mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous retry-on-visibility fix didn't help because the issue isn't visibility — it's the slot reassignment in presenter mode. Only the current and next slides get slotted into named slots; the rest are pulled out of the render tree entirely and lose their layout boxes. Mermaid's text-width measurements then come back NaN and it errors with . Fix: expose a window.__morkdeckMermaidReady promise from mermaid.eta and await it before sending presentation.start. Lit's initial render defaults to audience mode (every slide in the default slot, layout boxes present), so mermaid measures successfully; only after every block has its SVG do we flip into presenter mode and reassign slots. The SVGs travel with their slides through the reassignment. Verified the presenter tab now lands a 1109×189 mermaid SVG on initial load, no NaN transforms, no refresh needed. Co-Authored-By: Claude Opus 4.7 --- packages/core/templates/partials/mermaid.eta | 14 +++++-- packages/wc/components/presentation/wc.ts | 41 ++++++++++++++------ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/packages/core/templates/partials/mermaid.eta b/packages/core/templates/partials/mermaid.eta index 8e0b522..a6e1fd1 100644 --- a/packages/core/templates/partials/mermaid.eta +++ b/packages/core/templates/partials/mermaid.eta @@ -58,11 +58,19 @@ async function renderUnrendered() { await mermaid.run({ querySelector: "pre.mermaid", suppressErrors: false }) } -await renderUnrendered() +// Expose readiness so the presentation component can wait until every +// mermaid block has been rendered before flipping into presenter mode. +// In presenter mode only the current and next slides are slotted; the +// rest are pulled out of the render tree and lose their layout boxes — +// at which point mermaid's text-width measurements come back as NaN +// and it errors out trying to set . +// Running mermaid while every slide is still in the default audience +// slot ensures all blocks render with real dimensions; the SVGs then +// travel with their slides through any subsequent slot reassignment. +window.__morkdeckMermaidReady = renderUnrendered() +await window.__morkdeckMermaidReady document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") renderUnrendered() }) -// window.focus catches the case where a newly-spawned tab gains OS focus -// without firing visibilitychange (it was never marked hidden). window.addEventListener("focus", () => renderUnrendered()) diff --git a/packages/wc/components/presentation/wc.ts b/packages/wc/components/presentation/wc.ts index edaca70..256c578 100644 --- a/packages/wc/components/presentation/wc.ts +++ b/packages/wc/components/presentation/wc.ts @@ -393,18 +393,37 @@ export class PresentationWC extends LitElement { if (ctx.pausedAt !== this.pausedAt) this.pausedAt = ctx.pausedAt; }); - document.addEventListener("readystatechange", () => { - if (document.readyState === "complete") { - const currentSlide = window.location.hash.substring(1); - - this.presentation.send({ - presentationId: this.uuid, - type: "presentation.start", - slides: this.slides, - currentSlide: currentSlide || undefined, - role: initialRole, - }); + document.addEventListener("readystatechange", async () => { + if (document.readyState !== "complete") return; + + // Wait for any mermaid diagrams to render BEFORE flipping into + // presenter mode. In presenter mode only the current and next + // slides are slotted; the rest are pulled out of the render tree. + // Mermaid can't measure text inside an unrendered element and + // fails with NaN transforms. Letting it render while every slide + // is still in the default audience slot lets the SVGs land in the + // DOM with real dimensions; they then travel with their slide + // through any later slot reassignment. + const mermaidReady = (globalThis as { + __morkdeckMermaidReady?: Promise; + }).__morkdeckMermaidReady; + if (mermaidReady) { + try { + await mermaidReady; + } catch { + // mermaid failures should not block the rest of startup. + } } + + const currentSlide = window.location.hash.substring(1); + + this.presentation.send({ + presentationId: this.uuid, + type: "presentation.start", + slides: this.slides, + currentSlide: currentSlide || undefined, + role: initialRole, + }); }); // 1s tick for timer + clock displays. -- 2.51.2