Skip to content

fix(kit): grid-stack the slides deck so it cross-fades in normal flow#197

Merged
benvinegar merged 2 commits into
mainfrom
fix/slides-kit-crossfade-in-flow
Jul 1, 2026
Merged

fix(kit): grid-stack the slides deck so it cross-fades in normal flow#197
benvinegar merged 2 commits into
mainfrom
fix/slides-kit-crossfade-in-flow

Conversation

@benvinegar

Copy link
Copy Markdown
Member

Problem

The slides kit swapped slides with display:none/display:block — measurable, but it can't cross-fade. So agents hand-roll decks with position:absolute slides stacked over a min-height stage to fade between them. That layout is a trap:

  • The absolute overlay grows the document's scrollHeight but not its box.
  • The surface-page height bridge's ResizeObserver watches the box, not scrollHeight.
  • Once the fixed re-measure timers stop (10s), the bridge goes blind to the deck's real height and the frame freezes clipped — the taller slide's content is cut off, nondeterministically depending on whether a timer happened to land after layout settled ("uncached looks smaller").

This was confirmed empirically (fixed width, past the 10s timer window): growing an absolute slide's content changed scrollHeight +317px, the box Δ0, the ResizeObserver never fired, and the frame stayed clipped. Changing the iframe width did fire the RO (width is part of the box), which is why live window-resizes always self-corrected and this was hard to catch.

Fix

Give the kit a cross-fade that stays in normal flow: grid-stack every .slide into the same 1/1 cell (they overlap, but the container still sizes to the tallest slide) and fade with opacity + visibility. Now the deck's true height lives in the box the ResizeObserver can see, so it self-corrects. Inactive slides are visibility:hidden (out of the tab order + a11y tree), with a prefers-reduced-motion opt-out. The injected controls (.deck-ctl) auto-place to the row below the stacked slides.

Also:

  • DESIGN_GUIDE.md — expand the position: fixed ban into a "keep content in normal flow" rule that names the absolute-over-min-height trap and gives the grid-stack recipe, pointing agents at the kit.
  • test/kits.test.ts — regression test pinning the in-flow contract (display:grid + grid-area:1/1; forbids position:absolute).

Verification

  • npm test — 415 pass; npm run typecheck + npm run lint clean.
  • Rendered a real two-slide deck through renderHtmlPage({ kits: ["slides"] }) in headless chromium: height stable across slide switches (518px both), box == scrollHeight, controls render below the deck, active opacity:1 / inactive visibility:hidden.

🤖 Generated with Claude Code

benvinegar and others added 2 commits July 1, 2026 14:51
The `slides` kit swapped slides with display:none — measurable but no
cross-fade — so agents hand-rolled decks with `position:absolute` slides
stacked over a `min-height` stage to fade between them. That layout is a
trap: the absolute overlay grows `scrollHeight` but not the document box,
and the surface-page height bridge's ResizeObserver watches the BOX, not
scrollHeight. So once the fixed re-measure timers stop (10s), the frame
goes blind to the deck's real height and freezes clipped — the taller
slide's content gets cut off, nondeterministically depending on whether a
timer landed after layout settled ("uncached looks smaller").

Give the kit a cross-fade that stays IN FLOW: grid-stack every `.slide`
into the same `1/1` cell (they overlap, but the container still sizes to
the tallest slide) and fade with opacity + visibility. Now the deck's true
height lives in the box the ResizeObserver can see, so it self-corrects.

Also document the out-of-flow trap in DESIGN_GUIDE alongside the existing
`position: fixed` ban, with the grid-stack recipe, and add a regression
test pinning the in-flow contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@benvinegar benvinegar merged commit cc6504c into main Jul 1, 2026
9 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