// @vitest-environment node import { describe, expect, it } from 'vitest'; import { buildSrcdoc } from '../../src/runtime/srcdoc'; // Regression coverage for the "deck-stage shows a sliver of content in the // top-left with the rest of the preview black" symptom. Root cause: the // srcdoc deck bridge injected `place-content: center !important` on // `.stage, .deck-stage, .deck-shell` for ALL deck-mode artifacts, even // framework decks (DECK_SKELETON_HTML in apps/daemon/src/prompts/ // deck-framework.ts) whose `fit()` already centers a `transform-origin: // top left` stage with an explicit `translate(tx, ty)` that assumes the // stage's natural layout position is (0, 0). Forcing place-content on // the shell re-centered the implicit grid track, doubled the offset, and // pushed the scaled stage off-screen. // // The fix: detect the framework deck via its `id="deck-stage"` marker and // skip the place-content override for it. Legacy / non-framework decks that // authored their own `.stage` grid still get it. // // Framework decks get the INVERSE fix instead (acceptance #47). Their skeleton // documents `.deck-shell` as plain block flow so the stage's natural top-left // is (0, 0); generated decks routinely re-declare it as a centering flex // container, which makes the stage a flex item whose default `flex-shrink: 1` // collapses `width: 1920px` to the pane width — a 16:9 canvas silently renders // portrait. Restoring block flow + no shrink is a no-op on a compliant deck. function frameworkDeckHtml(): string { return [ '', '
', '
', '
slide 1
', '
slide 2
', '
', '
', '', '', ].join('\n'); } function legacyDeckHtml(): string { return [ '', '
', '
', '
slide 1
', '
slide 2
', '
', '
', '', ].join('\n'); } function horizontalTrackDeckHtml(): string { return [ '', '
', '
slide 1
', '
slide 2
', '
', '', ].join('\n'); } describe('injectDeckBridge — framework-deck detection (#deck-stage)', () => { it('skips the place-content fix when the deck carries the framework #deck-stage marker', () => { const out = buildSrcdoc(frameworkDeckHtml(), { deck: true }); expect(out).not.toContain('place-content: center !important'); // The bridge script itself must still ship — the framework's own // fit() handles centering, but the host-side counter / keyboard // bridge still needs the slide-state postMessage channel. expect(out).toMatch(/]*data-od-deck-bridge/); }); it('recognizes the canonical v1 marker before legacy navigation probing', () => { const html = frameworkDeckHtml().replace('', ''); const out = buildSrcdoc(html, { deck: true }); expect(out).toContain('var odDeckProtocolVersion = 1;'); expect(out).toContain('odHasExternalSlideMessageListener = false || odDeckProtocolVersion === 1'); expect(out).toContain("data.type !== \"od:deck-ready\""); }); it('keeps a framework deck stage at its authored size when the shell is a flex container', () => { const out = buildSrcdoc(frameworkDeckHtml(), { deck: true }); expect(out).toMatch(/]*data-od-deck-fix/); expect(out).toContain('.deck-shell { display: block !important; }'); expect(out).toContain('.deck-stage { flex-shrink: 0 !important; }'); }); it('keeps injecting the place-content fix for legacy / non-framework decks', () => { const out = buildSrcdoc(legacyDeckHtml(), { deck: true }); expect(out).toMatch(/]*data-od-deck-fix/); expect(out).toContain('.stage:not(:has(> .slide))'); expect(out).toMatch(/]*data-od-deck-bridge/); }); it('does not center a horizontal stage whose direct children are slides', () => { const out = buildSrcdoc(horizontalTrackDeckHtml(), { deck: true }); const fix = out.match(/