fix(clock-face): trace canvases were photographed at the wrong resolution - #354
Merged
Conversation
The trace driver sizes each canvas's backing store from getBoundingClientRect, and on a frozen story page it stops the rAF loop after the first frame — so that one measurement is permanent. Studio's stylesheet is injected by the wasm bundle after boot, so a paint that beats it measures a box the card does not end up with and bakes a bitmap the browser then squeezes into the real one. Both outcomes are stable, which is why clock-face baselines alternated between two renderings and every studio-touching PR collected a bot commit. A ResizeObserver per driver repaints on box change, so the styled size wins no matter which paint got there first. The canvas box moves to an inline style at the same time: it applies on the first layout, and it keeps the box independent of the width/height attributes the paint writes — otherwise an unstyled canvas at dpr > 1 would take its box from those attributes and the new repaint would feed itself. Verified live by delaying the stylesheet 1500ms: the canvases painted at the pre-stylesheet box and stayed there; with the fix they converge on the styled size as soon as a frame runs. A local capture now matches the correct CI variant to a max channel delta of 1 inside the trace band.
The clock-face oscillation was invisible to the capture: a canvas painted for the wrong box is a perfectly stable render, so the stable pair passed on it and the auto-commit shipped whichever one the run reached. The ready gate now asserts the invariant directly — no `canvas.ux-box-sized-canvas` may be photographed while its backing store disagrees with its CSS box — in the same idiom as the data-preview-painted and font gates. A repeat of this class becomes a story-ready timeout instead of a bot commit.
…aded main carries the wrong-resolution render for clock-face crowd__md and default__lg — the single faint row with no midline this branch's fix diagnoses. Every other clock-face baseline on main is already correct; run 31024986361 captured this branch and reproduced them byte-for-byte, naming exactly these two as stale. The bytes restored here are that branch's own copies, which the same run confirmed identical to a fresh capture in the pinned CI environment — not a local capture. Without this the merge would take main's side (this branch had not touched the files) and reintroduce the degraded pair.
Captured in the pinned CI environment by run: https://github.com/light-player/lightplayer/actions/runs/31026385720
Contributor
CI refreshed the story baselines on this branchThe Review every PNG in the PR's Files changed view (swipe / onion-skin). 12 more file(s)
|
Two CI captures of the same tree agree byte-for-byte on the whole clock-face family — the comparison that can falsify an oscillation, and the one nothing in the pipeline does on its own. Also records that main held the degraded render for only two of the stories: an oscillating baseline leaves the set mixed, so a branch that has not touched those files silently takes main's side on merge. Flags exploration/node-ui/status-indicators sm as a fresh member of the bistable set: byte-identical in the first capture, 304 significant px in the second, in a story family with no canvas — a different mechanism.
Conflicts: the two clock-face baselines this branch had pinned. main's side wins both — PR #345 landed the clock transport hero, so those images are new CONTENT (525px and 355px tall against the pinned 507px), not the degraded re-render the pin was defending against. The pinned bytes are the old layout and no longer mean anything. That leaves those two baselines whatever main's pre-fix capture produced. With the resize repaint in this branch they converge on one render instead of alternating, so at worst the next capture refreshes them once.
Captured in the pinned CI environment by run: https://github.com/light-player/lightplayer/actions/runs/31058006664
Conflicts: five clock-face baselines that main and this branch both rewrote. Ours win — they are the post-fix capture (run 31058006664), while main's are pre-fix renders its own captures keep rewriting: the last three commits touching these files on main are all auto-refresh bot commits, and the dimensions match, so this is the oscillation churning, not a content change.
Captured in the pinned CI environment by run: https://github.com/light-player/lightplayer/actions/runs/31061049233
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.
















Fixes the open defect
docs/defects/2026-08-05-clock-face-baselines-oscillate.md: clock-face story baselines alternated between two renderings, so every studio-touching PR collected avalidate-storiesbot commit and no branch reached a stable head.Not a settling race
The starting hypothesis was that the face's per-paint self-freeze admits two settling points. The pixel diff of the two committed variants of
clock-face__crowd__lg(7 506 px, max Δ243, confined to the eight trace canvases) says otherwise:Root cause
paint_cardsizes the canvas backing store fromgetBoundingClientRect(), and on a frozen story page the driver stops the rAF loop after its first frame — so that one measurement is permanent. Studio's stylesheet is injected by the wasm bundle after boot (index.htmlsays so), so a paint that beats it measures a box the card does not end up with. Both outcomes are stable terminals; the stable-pair capture passes on either.Reproduced live by serving the story build with the tailwind stylesheet delayed 1500 ms: the canvases painted at the pre-stylesheet box (backing 2160×84 for a box that settles at 126.66×42) and stayed there.
Fix
ResizeObserverper driver repaints on box change — it runs even though the rAF loop has stopped, which is the property the frozen page needs.style: it applies on the first layout, and it keeps the box independent of thewidth/heightattributes the paint writes (otherwise an unstyled canvas at dpr > 1 takes its box from those attributes and the repaint feeds itself).canvas.ux-box-sized-canvashas a backing store that disagrees with its box — a repeat becomes a story-ready timeout instead of a silent bot commit.Verification
d7712f79was the correct render and main currently holds the degraded bitmap.just check,just test-studio-host,cargo fmt --checkall clean.Expect one baseline refresh. main's clock-face baselines hold the degraded render, so the first
validate-storiesrun here will legitimately auto-commit them. The acceptance check for this defect is that the run after that reports no drift.Note: PR #349 carries the same defect file with
status: open; whichever lands second will need a trivial conflict resolution in favour of this one.🤖 Generated with Claude Code