Repository navigation
Conversation
An auto-height stage now lays out every frame of the current scenario when it loads, frame handlers included, within one task so none of them paints, and sizes itself to the tallest. It measures again when the width, theme, fonts, images, a slot's size or the scenario change. Every frame's caption shares one grid cell, so the controls row keeps the height of the longest caption. Agent-Session: b5f9f841-35c5-4ec1-95a8-6485f64d254b Agent-Session: d7e3a290-82a4-4f66-9b3c-7a52a51add18 Agent-Session: 3bc0a8d7-44d9-4b00-8484-66b3718cf367
Running each frame's handlers inside the stage the reader sees let their leftovers reach the frame on screen: a later frame's finished motion, and deferred work that landed after the stage had stepped back. A hidden copy of the stage now shows every frame of the scenario as a reader would, reports the tallest, and goes away. The visible stage takes that height, or its current frame's if that is taller. The copy is made again when the stage, scenario, theme, width or a slot's content height changes. It posts nothing but its measurement. The scene test no longer expects the stage to shrink on its last frame; it keeps the tallest frame's height by design. Agent-Session: b5f9f841-35c5-4ec1-95a8-6485f64d254b Agent-Session: d7e3a290-82a4-4f66-9b3c-7a52a51add18 Agent-Session: 3bc0a8d7-44d9-4b00-8484-66b3718cf367
The measuring copy stepped through frames as a reader would, which took about a second on an eleven-frame animation, so the stage grew once after it appeared. The copy is thrown away, so it can show every frame as an instant jump within one task; what a frame leaves behind does not matter there. It now reports about when the visible stage first does. A new stage keeps its first height until that measurement arrives, or for a second at most, so it grows once, straight to its tallest frame. Agent-Session: b5f9f841-35c5-4ec1-95a8-6485f64d254b Agent-Session: d7e3a290-82a4-4f66-9b3c-7a52a51add18 Agent-Session: 3bc0a8d7-44d9-4b00-8484-66b3718cf367
milanb17
added a commit
that referenced
this pull request
Nov 3, 2026
Ports #1459 onto the animations branch, where the block it changed had since split into a frame player, a clip player and a paint-in. An animation with `height: "auto"` changed height as the reader stepped, because the stage resized to its current frame, and the caption row moved when one caption wrapped and the next did not. Now a hidden copy of the stage, transparent and inert at the same width, shows every frame of the scenario as instant jumps within one task and reports the tallest; the visible stage takes the larger of that and its current frame. The copy is made again when the stage, scenario, theme, width or a slot's content height changes, gets the visible stage's slot heights, and posts nothing but `measured`. A new stage keeps its starting height until measured, or for a second. Every stop's caption shares one grid cell with only the current one visible, in both players. What a frame's handlers leave behind in the copy never reaches the stage the reader sees: the copy is thrown away. Clips have one timeline and are not measured. Agent-Session: cbacb871-2558-4fe4-9272-77524bb21683 Agent-Session: b315a4f8-1333-461c-9be6-366ceb231b7f Agent-Session: 62100749-c77c-4d19-ad48-c6d4ba1e867b
milanb17
added a commit
that referenced
this pull request
Nov 4, 2026
Stacked on #1483 (the references and the scene API live there). An animation's `whiteboard.scene` draws every card as the same box, so a reader cannot tell a database from the service in front of it, or a third-party API from one of ours, without reading each title. This gives cards three presentational options and leaves the kinds out of the API: the typings and the authoring instructions say which form fits what, and two references draw their cards that way.   ## The options On `SceneCardOptions` (actor, store, table), and as `data-form`, `data-outline` and `data-color` on a hand-made `wb-node`: | Option | Values | Drawn as | | --- | --- | --- | | `form` | `box` (default), `pill`, `cut`, `cylinder`, `drum`, `stack`, `window`, `bars` | border radii, `corner-shape: bevel`, a lid pseudo-element for the cylinder and the drum, a box-shadow stack, a title-bar rule, side rules | | `outline` | `solid` (default), `dashed`, `dotted` | border style | | `color` | `ink`, `accent`, `teal`, `violet` | border colour at rest (and the card's dot); a frame's `tone` takes over while set | `teal` and `violet` join the stage theme as `--wb-teal` (the software map's storage colour) and `--wb-violet` (the merged purple), so both follow light and dark. Highlights, tones, `enter`/`exit`/`only` and the card's content work unchanged on every form; the stack keeps its echoes under the highlight ring. The option is called `form` rather than shape because lint reserves "shape" in identifiers. ## Suggested defaults, not a schema `whiteboard.d.ts` and the scene section of `animation.md` suggest: `window` for a client, `bars` for a service or worker of this codebase, `cylinder` for a database, `drum` for a cache, `stack` for a queue or topic, `pill` for a start or end, `cut` for a branch, `outline: "dashed"` for a third-party service, `teal` for stores and `violet` for queues. Nothing in the runtime knows those words. The request-across-layers and cache-fill-evict references use them; their READMEs say so. ## Verification - Every animation browser test, including the references harness stepping all nine references through every frame: 30 passed. The stage kit variables test passes with the two new colours. - `pnpm lint`, `pnpm format` and the canvas typecheck (which holds `whiteboard.d.ts` to the runtime's types) are clean. - The screenshots are frames the harness saved (`VITE_ANIMATION_SHOTS=1`), dark theme. Not checked: light theme and an isolated Desktop. The screenshots live on `pr-assets/scene-card-forms`, as `pr-assets/home-pr-pills` did; delete that branch with this PR. ## Second commit: a frame's remaining time along its step underline While an animation plays, the marker under the step showing is drawn left to right over the frame's duration, on a wash of the full underline, so the one mark that says "this step" also says how much of it is left. Frames without labels sweep a ring around the current dot. Designed first on the "Play timer" page of the Animations Scratchpad Paper file. - Only while playing: a frame reached by hand draws its underline whole, as before. - Pause holds the line where it is and Play resumes from there; a jump to another frame forgets it; Replay starts over. - Time up with the stage still settling holds the line full. - The strip and dots draw it with a CSS animation from inline custom properties (`--frame-timer-fill`, `--frame-timer-ms`, `--frame-timer-rest`), so nothing ticks in React. Reduced motion shows the whole underline at once, as today. - Two browser tests in `blocks.browser.test.tsx`: play runs the clock on the current step, pause holds it with a fraction between 0 and 1, a hand step drops it; and an unlabelled animation sweeps its current dot. ## Third commit: steady height, ported from #1459 #1459 could not merge onto the animations branch mechanically, since the block it changed had split into a frame player, a clip player and a paint-in. Its changes are ported here by hand: the hidden measuring copy (`measure` / `slotSizes` on init, `measured` back), `contentBottom` and `measureFrames` in the runtime, the block's `measuredHeight` rule with its one-second fallback and remeasure triggers (stage, scenario, theme, width, slot heights), and the caption row keeping its tallest caption, now in `StopCaption` for both players. Clips are not measured. Its tests come along; the animation and block browser suites pass (97). #1459 can be closed once this merges. ## Fourth commit: an expanded animation scales up Expand gave the stage the overlay's width but never made the drawing bigger. Now the stage keeps its inline width fullscreen and the block scales the whole canvas, code peeks included, to fit the room the controls leave, at most 2x, centred; a room smaller than the inline stage scrolls as before. The measured-height logic is untouched because the stage still lays out at one width. One browser test expands a block in a wide host and checks the stage scales above 1x, keeps its inline layout width, and its footprint grows with it. Seen while checking, not from these commits: a scene with four declared columns folded to two rows at a 614px stage keeps a 150px stage height, so the second row is clipped, inline and expanded alike. Reproduced on the commit before the steady-height port. Worth its own look. ## Fifth to seventh commits: scene layout, pacing, groups, change marks and a legend - **Cards size to their content**, 140 to 300px, centred in their columns, so a folded scene no longer stretches a card into a slab around a line of text. - **Edges are straight and level**: a fanned pair's offset moves the whole line, and scene rows centre their cards so an edge between a short card and a tall one runs level. - **Play replays a still frame**: a new stage's first frame, or the frame after a jump or Replay, is shown again with its motion when Play reaches it. A send's chip travels at half its old speed, 900 to 2400 ms, after a short lead. One browser test covers the replay. - **`s.group({ id, at, title, color })`** draws a washed, titled box behind the cards in its cells; hidden when the scene folds. - **`change: "added" | "modified" | "removed"`** on a card marks what the review did to that part: dot and left stripe in the change colour, struck title when removed. A frame's tone still colours the card over it. - **`s.legend([...])`** draws one row under the scene, a card-shaped glyph with the entry's marks and a label each. - The typings and `animation.md` describe all of it; request-across-layers groups its server, marks the API as modified and carries a legend. The cylinder form's text sits centred in its body. ## Later commits: layout room, routing, the state-only dot, an architecture reference - Storage-form labels centre in their drum or cylinder; the card dot shows only with a tone or change mark. - A new reference, `video-platform-architecture`: four grouped layers, forms, a dashed CDN and a legend, with upload and playback scenarios. - An edge bows clear of a card by the card's reach across the line, not its longest side, and a card near the line is passed toward the stage's middle; a frame hides other frames' chips. - A grouped scene opens its rows and columns to 72px so the layers stand apart and cross-layer edges can be followed. - Fullscreen, the stage always lays out at its inline width and scales to fit, down to 0.4x beside the tour pane, so it never folds, loses its groups, or measures a folded height.
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.
What
An animation with
height: "auto"changed height as you stepped through its frames, because the stage resized to fit whatever the current frame showed. The caption row also changed height when one caption wrapped and another didn't.animation-block.tsx,stage-runtime.ts,stage-protocol.ts): next to the visible stage, the block loads a hidden copy of the stage, transparent and inert, at the same width. It starts withmeasure: true, shows every frame of the current scenario as an instant jump within one task, and reports the tallest asmeasured. Then it's removed. The visible stage uses the larger of that height and its current frame's height. The copy is made again when the stage, scenario, theme, width or a slot's content height changes. It receives the visible stage's slot heights, and it posts nothing butmeasured.Why a separate copy
The first version measured inside the visible stage by running each frame's handlers within a single task. Handlers assume their frame is the one showing, so their leftovers reached the frame on screen: a later frame's finished
whiteboard.animatemotion, andsetTimeoutwork that landed after the stage had stepped back. The copy does the same single-task pass, but it's thrown away afterwards, so what a frame leaves behind never reaches the stage the reader sees.Reviewer notes
blocks.browser.test.tsx:Checked
blocks.browser.test.tsx(61 tests),pnpm lintand typecheck all pass.