Repository navigation
webviews tests: frame tests read layout after the frame paints - #23374
Conversation
A requestAnimationFrame callback runs before the frame's style, layout, ResizeObserver callbacks and paint, so a read there can see a state the frame never shows. test/support/paintedFrames.ts adds two page globals, cmuxAfterPaint (read after each painted frame) and cmuxPaintFrame (drive one frame, resolve once it has painted), and every browser test that sampled in frame callbacks now uses them. scripts/check-frame-reads.ts, run from check-browser-test-lanes, fails a browser test that reads layout in a frame callback, reschedules a sampler from one, or wraps a frame in a named awaiter, unless a "// raf-read: <reason>" comment says why. New regression tests on the helper: a rich-history chat opens at its latest line and holds it (agent-pane-stream-jitter), and a long chat opened at its end holds its latest line on every painted frame while history pages in above it (agent-pane-history-paging). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d18-f5e3-77e3-a3e6-159012f35d5f&host=cmux16
|
Owned by scroll-jitter-2 on cmux16 · session |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Passes: CI passes on CI passes on Written by web and size checks: green
Agents: Tour of |
…t read too agent-pane-pr-dock and agent-pane-tab-row awaited two frames inside the page and then read layout, which runs in a microtask of the second frame's callbacks, before its layout and paint. Both now await cmuxPaintFrame, and check-frame-reads flags a function that awaits a frame inline and then reads layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d18-f5e3-77e3-a3e6-159012f35d5f&host=cmux16
…rame wait A read before `await new Promise(... requestAnimationFrame ...)` is of the frame before, so it no longer flags the wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d18-f5e3-77e3-a3e6-159012f35d5f&host=cmux16
|
Bead |
|
base-red-watch: |
…23414) #23374 merged after the fixed-wait ratchet (#23365) ran CI, and it added a 3 s waitForTimeout in two tests, which puts feat-cmux-next over its baseline. Both waits are observation windows: the test records every painted frame for 3 s after the chat opens and asserts none jumped, so the time is the test. They carry a fixed-wait marker instead of a raised baseline. Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Shipped in:
|
…23415) * test(webviews): a fixed-wait check names fixed waits and honors a reasoned marker Fails until scripts/check-fixed-waits.ts exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * webviews: a ratchet refuses new fixed-time waits in tests check:fixed-waits (in `bun run check`) names each test wait for a fixed number of ms: page.waitForTimeout, Bun.sleep, a setTimeout that resolves a promise after a non-zero delay, and calls to a local helper built on one. A fixed wait passes on an idle machine and fails on a loaded runner that wakes it late, or hides the bug it waits out (#23323). A test waits for a condition instead, uses fake timers when the timing is the behavior, or keeps the wait with a `// fixed-wait: <reason>` comment. The 398 waits from before the rule have per-file counts in scripts/fixed-waits-baseline.tsv. A file fails with more unmarked waits than its row, and with fewer until the row is lowered (--update-baseline), so the count only moves down; the sweep PRs lower it. pageSwitch's 16 ms no-requestAnimationFrame fallback keeps its wait with a reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): the fixed-wait check counts helper uses, block-body timers and exported helpers Fails on the first detector: a helper with no delay parameter counted only at its definition, a helper passed as a value (act(settle)) not at all, a timer callback with a block body was missed, and so was an exported helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * webviews: the fixed-wait check counts every use of a sleep helper From review. A wait inside a named function makes it a helper, and each use counts instead of the definition: a call, or the helper passed or returned as a value (act(settle), return { open }), applied outward through helpers built on helpers. So another `await settle()` in a file raises its count. A helper whose delay parameter defaults to 0 waits only when a use passes a delay. An exported helper also counts at its definition, since its callers are in other files. Also caught: timer callbacks with a block body that resolve the promise, window/globalThis.setTimeout and Bun.sleepSync. A marker counts anywhere in the wait's statement, or on a comment-only line above it, so a trailing marker no longer covers the next line. The scan covers every source under test/ (harnesses, latency helpers), not just *.test files, and skips fixtures. A malformed baseline row now fails instead of passing as NaN. The baseline is regenerated: 819 waits in 132 files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): a new wait inside an existing helper must raise the fixed-wait count Review of #23365 found that a second wait or helper call added inside a function that already waits was not counted. These cases fail on the current detector. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * fix(webviews): count every fixed wait and every helper use, inside helpers too A wait counted only through the outer uses of its enclosing helper, so a new wait or helper call inside a helper that already waited changed nothing. Now each unmarked wait site counts, and each use of a helper outside its own body, so any new wait raises the count. Waits in exported helpers count at the site, which replaces the export rule. Export lists, typeof and member declaration names are no longer uses. The baseline is regenerated: 989 waits in 132 files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): U2 waits for conditions, not fixed times The 19 U2 unit-test files had 142 fixed waits under the stricter detector; none are left and their baseline rows are gone. - Timing under test runs on bun's fake timers: the new tab's picked-name fade, the summary card's running-time tick, the kept-attachments rest timer, the transcript's anchor settle, the tooltip's scroll quiet time (now also checked inside the quiet window) and the link bubble's no-hover-card check. - Polls that already waited on a condition (render card, dropped chips, KaTeX, prompt accepted) yield a macrotask per try instead of sleeping. - The image viewer's actions wait for the host request (or the refusal label); the changelog pages wait for their release to draw. - Menu and composer settle() helpers and PreviewCard's teardown yield one macrotask: act() flushes the rest, and the 20 ms were slack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): yield a task before the no-bubble and one-request asserts The hover test now lets promises and a later task run before it asserts no bubble, and the image viewer waits one more task so a duplicate request would show. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): mark the two 3 s frame-watch windows as fixed waits #23374 merged after the fixed-wait ratchet (#23365) ran CI, and it added a 3 s waitForTimeout in two tests, which puts feat-cmux-next over its baseline. Both waits are observation windows: the test records every painted frame for 3 s after the chat opens and asserts none jumped, so the time is the test. They carry a fixed-wait marker instead of a raised baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): lower the fixed-wait baseline after the base merge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…3480) #23154 added diff-paint-perf with a probe that read layout in a requestAnimationFrame callback; #23374's frame-read rule (check:browser-lanes) landed meanwhile, so webviews lint went red on feat-cmux-next for every PR. The probe now reads in window.cmuxAfterPaint (test/support/paintedFrames.ts) and marks each milestone at the painted frame's time. Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…23416) * test(webviews): a fixed-wait check names fixed waits and honors a reasoned marker Fails until scripts/check-fixed-waits.ts exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * webviews: a ratchet refuses new fixed-time waits in tests check:fixed-waits (in `bun run check`) names each test wait for a fixed number of ms: page.waitForTimeout, Bun.sleep, a setTimeout that resolves a promise after a non-zero delay, and calls to a local helper built on one. A fixed wait passes on an idle machine and fails on a loaded runner that wakes it late, or hides the bug it waits out (#23323). A test waits for a condition instead, uses fake timers when the timing is the behavior, or keeps the wait with a `// fixed-wait: <reason>` comment. The 398 waits from before the rule have per-file counts in scripts/fixed-waits-baseline.tsv. A file fails with more unmarked waits than its row, and with fewer until the row is lowered (--update-baseline), so the count only moves down; the sweep PRs lower it. pageSwitch's 16 ms no-requestAnimationFrame fallback keeps its wait with a reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): the fixed-wait check counts helper uses, block-body timers and exported helpers Fails on the first detector: a helper with no delay parameter counted only at its definition, a helper passed as a value (act(settle)) not at all, a timer callback with a block body was missed, and so was an exported helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * webviews: the fixed-wait check counts every use of a sleep helper From review. A wait inside a named function makes it a helper, and each use counts instead of the definition: a call, or the helper passed or returned as a value (act(settle), return { open }), applied outward through helpers built on helpers. So another `await settle()` in a file raises its count. A helper whose delay parameter defaults to 0 waits only when a use passes a delay. An exported helper also counts at its definition, since its callers are in other files. Also caught: timer callbacks with a block body that resolve the promise, window/globalThis.setTimeout and Bun.sleepSync. A marker counts anywhere in the wait's statement, or on a comment-only line above it, so a trailing marker no longer covers the next line. The scan covers every source under test/ (harnesses, latency helpers), not just *.test files, and skips fixtures. A malformed baseline row now fails instead of passing as NaN. The baseline is regenerated: 819 waits in 132 files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): a new wait inside an existing helper must raise the fixed-wait count Review of #23365 found that a second wait or helper call added inside a function that already waits was not counted. These cases fail on the current detector. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * fix(webviews): count every fixed wait and every helper use, inside helpers too A wait counted only through the outer uses of its enclosing helper, so a new wait or helper call inside a helper that already waited changed nothing. Now each unmarked wait site counts, and each use of a helper outside its own body, so any new wait raises the count. Waits in exported helpers count at the site, which replaces the export rule. Export lists, typeof and member declaration names are no longer uses. The baseline is regenerated: 989 waits in 132 files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): U1 waits for conditions, not fixed times Removes the fixed sleeps from the U1 batch's 20 unit-test files. - Hover and dwell delays (session hover card, sidebar preview, minimap turn preview, URL chip grace, settings backdrop and theme dwell, scroll anchor settle, warm New Tab post, prompt delivery deadlines) run on bun:test's fake timers: each test checks the behavior is absent one tick before its delay and present at it, so a slow runner cannot change the result. - Async results (shell reads, sign-in polls, client reconnects and resyncs, pasted image chips, KaTeX and diagram rendering, the code-card handoff, changelog and settings host calls, menu picks) are awaited with bounded `until` loops that yield with setTimeout 0 and fail naming what they waited for. - The settings claim-paint test counts calls and stream events in flight and waits until all are delivered, rather than waiting 300 ms. - The reveal frame helper in transcript-switch-back is left for the shared post-paint frame helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): mark the two 3 s frame-watch windows as fixed waits #23374 merged after the fixed-wait ratchet (#23365) ran CI, and it added a 3 s waitForTimeout in two tests, which puts feat-cmux-next over its baseline. Both waits are observation windows: the test records every painted frame for 3 s after the chat opens and asserts none jumped, so the time is the test. They carry a fixed-wait marker instead of a raised baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): lower the fixed-wait baseline after the base merge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…23454) * test(webviews): mark the two 3 s frame-watch windows as fixed waits #23374 merged after the fixed-wait ratchet (#23365) ran CI, and it added a 3 s waitForTimeout in two tests, which puts feat-cmux-next over its baseline. Both waits are observation windows: the test records every painted frame for 3 s after the chat opens and asserts none jumped, so the time is the test. They carry a fixed-wait marker instead of a raised baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): B2 waits for conditions, not fixed times Removes the fixed sleeps from the B2 batch's 18 browser-test files. - Daemon round trips: the stand-in acpmux daemons wake a bounded `until` on each message they take, so a test waits for the cancel, prompt, view or offer reply itself, and fails naming what never came. - Absence checks (no cancel, no prompt, no /btw, no armed stop, no second view, no card) wait for a later positive signal first: the page answers a lag notice sent after the input with its refetch, or draws a record sent after it (a failover note or a reply chunk), so anything it would have sent or drawn has arrived before the assertion. Counts of exactly one also wait for that signal. - Stop's 5 s grace and the composer's activation settle run on Playwright's page.clock: the steer and focus-content tests move past them instead of sleeping through them. - The turn footer waits for the footers' opacity to reach its end value; the diff find and working counts read until the state holds (bounded). - Layout settles convert to the shared painted-frame helper (support/paintedFrames.ts): two painted frames after a mount or a font load, the page's finite animations finished first after a fold, a hover, a focus or a resize, and 20 painted frames for the latency probe's frame budget. thin-scrollers waits for data-cmux-scrolling. - reopened-chat-history waits on the daemon's answers (nothing in flight, an older read answered) instead of a 250 ms quiet window. - Two waits keep a reasoned marker: the stream jitter test's chunk cadence is the input under test, and dev-server-stop polls the process table because nothing reports a grandchild's exit. - The stream jitter test's remaining settle and observation windows are left for the frame work (scroll glides are JS-driven, so animation state cannot say they ended). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…23462) * test(webviews): a fixed-wait check names fixed waits and honors a reasoned marker Fails until scripts/check-fixed-waits.ts exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * webviews: a ratchet refuses new fixed-time waits in tests check:fixed-waits (in `bun run check`) names each test wait for a fixed number of ms: page.waitForTimeout, Bun.sleep, a setTimeout that resolves a promise after a non-zero delay, and calls to a local helper built on one. A fixed wait passes on an idle machine and fails on a loaded runner that wakes it late, or hides the bug it waits out (#23323). A test waits for a condition instead, uses fake timers when the timing is the behavior, or keeps the wait with a `// fixed-wait: <reason>` comment. The 398 waits from before the rule have per-file counts in scripts/fixed-waits-baseline.tsv. A file fails with more unmarked waits than its row, and with fewer until the row is lowered (--update-baseline), so the count only moves down; the sweep PRs lower it. pageSwitch's 16 ms no-requestAnimationFrame fallback keeps its wait with a reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): the fixed-wait check counts helper uses, block-body timers and exported helpers Fails on the first detector: a helper with no delay parameter counted only at its definition, a helper passed as a value (act(settle)) not at all, a timer callback with a block body was missed, and so was an exported helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * webviews: the fixed-wait check counts every use of a sleep helper From review. A wait inside a named function makes it a helper, and each use counts instead of the definition: a call, or the helper passed or returned as a value (act(settle), return { open }), applied outward through helpers built on helpers. So another `await settle()` in a file raises its count. A helper whose delay parameter defaults to 0 waits only when a use passes a delay. An exported helper also counts at its definition, since its callers are in other files. Also caught: timer callbacks with a block body that resolve the promise, window/globalThis.setTimeout and Bun.sleepSync. A marker counts anywhere in the wait's statement, or on a comment-only line above it, so a trailing marker no longer covers the next line. The scan covers every source under test/ (harnesses, latency helpers), not just *.test files, and skips fixtures. A malformed baseline row now fails instead of passing as NaN. The baseline is regenerated: 819 waits in 132 files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): a new wait inside an existing helper must raise the fixed-wait count Review of #23365 found that a second wait or helper call added inside a function that already waits was not counted. These cases fail on the current detector. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * fix(webviews): count every fixed wait and every helper use, inside helpers too A wait counted only through the outer uses of its enclosing helper, so a new wait or helper call inside a helper that already waited changed nothing. Now each unmarked wait site counts, and each use of a helper outside its own body, so any new wait raises the count. Waits in exported helpers count at the site, which replaces the export rule. Export lists, typeof and member declaration names are no longer uses. The baseline is regenerated: 989 waits in 132 files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * ci(web): run the fixed-wait ratchet in the feat-cmux-next warn branch Under CMUX_NEXT_STYLE_GUARDS=warn the webviews-check step runs bun run check as separate commands, so check:fixed-waits never ran there. It fails the step, like check:browser-lanes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): mark the two 3 s frame-watch windows as fixed waits #23374 merged after the fixed-wait ratchet (#23365) ran CI, and it added a 3 s waitForTimeout in two tests, which puts feat-cmux-next over its baseline. Both waits are observation windows: the test records every painted frame for 3 s after the chat opens and asserts none jumped, so the time is the test. They carry a fixed-wait marker instead of a raised baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): B1 waits for conditions, not fixed times Browser tests in batch B1 wait for the state they then assert instead of sleeping: waitForFunction on focus, results, visibility and editor text (ui-a11y, editor-csp, wheel pass-through, subagent groups), the daemon's own signals (a background request heard or answered, a call heard, the first prompt, the trust check) instead of sleep-polling, the chat list's websocket frame and the stand-in host's pending answers (New Tab first frame and composer), the parked page's claim hook and the dev server's listen line (settings open frames), finished finite animations before measuring an open menu, and the img error event after the inline handler would have run (locale). "Nothing happens" checks get a positive signal first: a console marker after the key for Ctrl-B, the pane's own Escape listener for the question card. The hover check in ui-a11y runs the page's clock past the delay instead of the wall clock. Waits whose delay is the scenario (late daemon and host answers, the moving layout in chat-find) and the no-hint check carry fixed-wait markers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue * test(webviews): B1 frame waits on the shared painted-frame helper The question card's arrival wait and New Tab list motion's "first rows settle" wait now use window.cmuxPaintFrame (test/support/paintedFrames), the latter followed by the rows' finite animations finishing. The inline double requestAnimationFrame waits added in the first commit move to the same helper. Baseline regenerated on the merged base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
In progress: CI running. Independent review passed; its one real finding (inline frame waits followed by reads in pr-dock and tab-row) is fixed in the second commit.
What
A
requestAnimationFramecallback runs before the frame's style, layout, ResizeObserver callbacks and paint. A test that reads layout there can see a state the frame never paints, such as the transcript's last row 30 px low on an opened chat's first frame. This PR makes reading after paint the one way frame tests sample.webviews/test/support/paintedFrames.ts(new):installPaintedFrames(page)defines two page globals:window.cmuxAfterPaint(read, drive?)callsread(frame)after each painted frame until it returnsfalse.drive(frame)runs in the frame's callbacks first. It returns a stop function.window.cmuxPaintFrame(drive?)resolves with the frame time once that frame has painted.A read runs in a MessageChannel task posted from the frame callback, so it runs after the frame renders.
Migrated every browser test that sampled in frame callbacks: agent-pane-stream-jitter, -chat-column, -history-paging, -queue-collapse, -reading-place, -steer, -switch-frames, chat-open-perf, code-block-highlight-height, hover-row-sweep, new-tab-composer-still, new-tab-first-frame-project, new-tab-warm-first-frame, reopened-chat-history, settings-open-frames, transcript-fling, transcript-fold-anchor-browser, plus agent-pane-pr-dock and agent-pane-tab-row, which awaited two frames inside the page and then read layout.
Guard:
webviews/scripts/check-frame-reads.tsruns fromcheck-browser-test-lanes. It fails a browser test that does any of these inside arequestAnimationFramecallback, unless a// raf-read: <reason>comment says why:const frame = () => new Promise(r => requestAnimationFrame(r)));Unit tests are in
test/check-frame-reads.test.ts.Pending (
FRAME_READS_PENDING): these wait for other lanes' open PRs, and the guard fails once one of them reads after paint so the entry gets removed:New regression tests on the helper:
Verification
CMUX_BROWSER_TESTS=1and all of them pass, except settings-open-frames. Its dev server doesn't start on cmux16, and base fails the same way here, so CI is the judge for that file.vp checkare clean.Agent session: cmux16
🤖 Generated with Claude Code