fix(kit): grid-stack the slides deck so it cross-fades in normal flow#197
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The
slideskit swapped slides withdisplay:none/display:block— measurable, but it can't cross-fade. So agents hand-roll decks withposition:absoluteslides stacked over amin-heightstage to fade between them. That layout is a trap:scrollHeightbut not its box.ResizeObserverwatches the box, notscrollHeight.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
.slideinto the same1/1cell (they overlap, but the container still sizes to the tallest slide) and fade withopacity+visibility. Now the deck's true height lives in the box theResizeObservercan see, so it self-corrects. Inactive slides arevisibility:hidden(out of the tab order + a11y tree), with aprefers-reduced-motionopt-out. The injected controls (.deck-ctl) auto-place to the row below the stacked slides.Also:
DESIGN_GUIDE.md— expand theposition: fixedban into a "keep content in normal flow" rule that names the absolute-over-min-heighttrap 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; forbidsposition:absolute).Verification
npm test— 415 pass;npm run typecheck+npm run lintclean.renderHtmlPage({ kits: ["slides"] })in headless chromium: height stable across slide switches (518px both),box == scrollHeight, controls render below the deck, activeopacity:1/ inactivevisibility:hidden.🤖 Generated with Claude Code