Fix mobile chip-strip overlap, reveal-replay stagger, scroll-linked text-fill
Three clone-fidelity fixes from manual review of the acceptance screenshots. Fix 1 — mobile nav chip-strip overlap (generate/css.ts). A horizontally scrollable flex strip (overflow-x:auto flex <ul>) of nowrap chips collapsed because the base viewport reported min-width:0px on the chips (a mobile-only strip that is 0-wide at desktop), so the generator emitted `min-w-0`, letting the chips shrink below their content instead of overflowing — the nowrap text then collided. Suppress `min-w-0` for a nowrap flex item whose flex parent scrolls horizontally; scoped away from legitimate min-w-0 truncation (whose parent does not scroll-x). Verified on a fresh ooni.com clone: 0 chip overlaps at 375, scrollWidth 776 > clientWidth 375. Fix 2 — reveal-replay stagger leaves tiles unpainted (generate/motion.ts). DittoMotion re-hid each revealed element and replayed its entrance on scroll-into-view preserving the captured delay/duration, so a fast scroll / full-page screenshot caught most grid tiles mid-entrance. Cap the replayed delay (<=300ms) and duration (<=600ms), settle each tile per-element a bounded time after it enters view, and make the global failsafe settle (not re-animate) so nothing stays hidden. Validator settle path (__dittoMotionStop) unchanged. Verified: cropin cotton renders 12/12 crop tiles in a normal full-page pass; motion gate reveals 24/24. Fix 3 — scroll-linked text-fill frozen at end state (capture/stabilize.ts, capture/capture.ts, generate/css.ts). A view-timeline text-fill (animation-duration resolves to `auto`) was baked filled: the clone has no scroll timeline, so emitting the animation made it jump to its end keyframe via fill-mode:both. Capture-side, cancel scroll/view-timeline animations before each snapshot so the DOM records the at-rest state; generation-side, suppress the animation-* props when animation-duration is `auto` (we render the at-rest state, never replay a scroll-linked animation). Teach the motion gate to exclude these from the static-CSS expectation so the gate stays honest. Verified on a fresh ooni clone: the "world's no.1 pizza ovens" em is white/gray (rgb 240,240, 240), not filled yellow, at rest; motion gate still passes. Tests: +7 fixtures (css scroll-strip + scroll-timeline, reveal-replay caps, motion-gate scroll-timeline exclusion); 123/123 compiler tests green, full test suite green, typecheck clean. No gate regression on the fresh ooni run vs the 20260704-024247 baseline (all gate pass/fail unchanged; perceptual within 0.0004; motion restored to pass). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f3d433c024
commit
afab12ff14
@@ -5,7 +5,7 @@ import { tagElements, captureInteractions, type InteractionCapture } from "./int
|
||||
import { captureMotion, probeReveals, type MotionCapture } from "./motion.js";
|
||||
import {
|
||||
promoteLazyMedia, settleCarousels, settleScrollReveals, neutralizePreReveal,
|
||||
forceRevealForShot, restoreRevealForShot,
|
||||
forceRevealForShot, restoreRevealForShot, neutralizeScrollTimelineAnimations,
|
||||
} from "./stabilize.js";
|
||||
import {
|
||||
enumerateFramesInPage, planForFrameUrl, graftFrameIntoSnapshot, frameHasRenderableContent,
|
||||
@@ -965,6 +965,14 @@ export async function captureSite(opts: {
|
||||
await page.waitForTimeout(80);
|
||||
}
|
||||
|
||||
// Scroll-linked animations (animation-timeline: scroll()/view()) are held at their
|
||||
// end keyframe by `fill-mode:both` after the dwell-scroll pass, even with scroll reset
|
||||
// to 0 — so the walk would bake the frozen END state (e.g. a text-fill stuck at 100%).
|
||||
// Cancel them here so the snapshot records the genuine AT-REST (unscrolled, 0%) values.
|
||||
// Time-based reveals use the default document timeline and are untouched.
|
||||
const scrollAnimsCanceled = await neutralizeScrollTimelineAnimations(page);
|
||||
if (scrollAnimsCanceled) log({ event: "scroll_timeline_anims_canceled", viewport: vw, count: scrollAnimsCanceled });
|
||||
|
||||
// Bound the in-page DOM walk: page.evaluate has no default timeout, so a
|
||||
// pathologically large/animated DOM (e.g. asana.com) could hang forever.
|
||||
const snapshot: PageSnapshot = await Promise.race([
|
||||
|
||||
Reference in New Issue
Block a user