Skip to content

webviews tests: frame tests read layout after the frame paints - #23374

Merged
teamleaderleo merged 4 commits into
feat-cmux-nextfrom
scroll-jitter-2/post-paint-frames
Oct 21, 2026
Merged

teamleaderleo merged 4 commits into
feat-cmux-nextfrom
scroll-jitter-2/post-paint-frames

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

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 requestAnimationFrame callback 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?) calls read(frame) after each painted frame until it returns false. 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.ts runs from check-browser-test-lanes. It fails a browser test that does any of these inside a requestAnimationFrame callback, unless a // raf-read: <reason> comment says why:

    • reads layout there;
    • reschedules a sampler from there;
    • wraps a frame in a named awaiter (const frame = () => new Promise(r => requestAnimationFrame(r)));
    • awaits a frame inline and then reads layout in the same function.

    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:

    • A chat with rich history opens at its latest line and holds it (agent-pane-stream-jitter).
    • 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).
    • Thread switching is already covered by chat-open-perf, which now samples post-paint.

Verification

  • Every changed browser test ran locally on cmux16 with CMUX_BROWSER_TESTS=1 and 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.
  • agent-pane-chat-column failed 1 of 13 once ("a streaming reply keeps the view at its end…", resting 12 px off). It passed 13/13 on the rerun, and base passed 13/13. I'm watching it in CI.
  • Guard, unit tests and vp check are clean.

Agent session: cmux16

🤖 Generated with Claude Code

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
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by scroll-jitter-2 on cmux16 · session

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review e3a2b63: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr23374 -f ref=scroll-jitter-2/post-paint-frames -f platform=macos -f backend_mode=local

Passes: CI passes on e3a2b63a15.

CI passes on e3a2b63a15 (run 39601299709 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

web and size checks: green

CI fast guards failed on 3561e42c50 (https://github.com/manaflow-ai/cmux/actions/runs/39597389773). It does not block the merge; a red guard merged into main breaks it for every open PR.
The log named no failed step; see the run.

Agents: python3 scripts/ci/guard_attribution.py fix applies the mechanical fixes locally.

Tour of e3a2b63a15 (run): no media. The tour did not run: only webview code changed (23 path(s)), which the gallery advisory run checks in Linux Chromium on the pull request (its artifact holds any captures); add the media-tour-mac label to tour it on a Mac.

teamleaderleo and others added 2 commits October 21, 2026 05:07
…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
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-o97ov created for this PR by the cmux coordinator.

@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Found 2 test failures on Blacksmith runners:

Failures

Test View Logs
test/thread-minimap-feel.test.ts/thread-minimap-feel.test View Logs
test/thread-minimap-feel.test.ts/thread-minimap-feel.test View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

base-red-watch: cmux-next web / web / react-apps-check (2) went red on feat-cmux-next in https://github.com/manaflow-ai/cmux/actions/runs/39601369539 (first red at 8083ae2, last green at d6dcbed). Suspect: #23374 (commit 8083ae2, which touches webviews/test/chat-open-perf.test.ts). If it is yours, fix forward on feat-cmux-next.

teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…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>
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…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>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…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>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…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>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…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>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant