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.