Skip to content

Agent chat: streaming replies grow by whole words, with room above the composer and no Markdown flips - #23094

Merged
teamleaderleo merged 19 commits into
feat-cmux-nextfrom
feat/next-streaming-text
Oct 21, 2026
Merged

teamleaderleo merged 19 commits into
feat-cmux-nextfrom
feat/next-streaming-text

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

A streaming reply in the agent chat should read like ChatGPT's or Claude desktop's. That means steady words paced to the token rate, and no block that draws one way and then flips as its Markdown forms. It also means room under the newest line instead of the line sitting on the composer's edge, and 60 fps on long turns.

Base already paces the reveal to the arrival rate (streamReveal.ts), parses incrementally and holds back half-written fences, tables and links (safeTail.ts). This PR closes the rest:

  • Whole words. The reply grows a word at a time, and each word fades in. It never shows a letter or half a word at a time. wordEdge comes from the stalled Agent chat: streaming replies grow by whole words, no block cursor #22339 (credit to its author). CJK text grows a character at a time. A word over 48 characters, such as a URL, shows as it arrives. The Debug toggle agentPane.debug.streamWords (on) switches back to characters.
  • The end of a reply and pauses. Until the whole reply shows, it keeps its streaming parse, so a code span or bold run that the last word closes never flashes its raw Markdown for a frame. When text stops arriving for 600 ms (StreamReveal.stallMs, typically before a tool call), the held last word shows instead of waiting for the next delta.
  • No frames skipped in WebKit. The reveal loop used to skip any frame that came less than COMMIT_MS - 1 after the last commit. WebKit rounds frame times to whole milliseconds and they jitter around 17 ms, so on a 60 Hz display one frame in three to five stalled. The loop now learns the display's frame time and skips a frame only on displays faster than 60 Hz. A 120 Hz display still commits 60 times a second. There is now one loop per row, and it reads the latest text itself instead of restarting on every snapshot.
  • No Markdown flips. These no longer draw as text first and then turn into their block: a bare list marker (- , 2.), heading hashes before their space, a rule line, a lone * or _, and an open italic run. A fence closer still being typed is held back along with the newline before it, so a code card never shrinks by a line as it closes.
  • The closing fence moves nothing in WebKit. Pierre keeps a strip for its horizontal scrollbar under the code rows. That strip is 6 px in WebKit, so the highlighted card came out 6 px taller than the plain card it replaces, and everything under it jumped. The strip now hangs into the card's bottom padding.
  • Runway. The transcript keeps 24 px under its last row, on top of the bottom stack's room, so the newest line sits about 32 px above the composer. The runway is always there, so a reply starting or ending moves nothing. It stays well under Agent chat: a reopened chat shows its whole history, with no band above the composer #23064's 96 px "no blank band" limit. With cmux NEXT: the agent turn looks like Codex: finished runs fold, one shimmering live line at the bottom, short labels, check and time on hover #23081 (chat-turn-codex-style's live status line), whichever PR lands second subtracts that line's measured height from the runway, so the two don't stack.
  • Less main-thread work per frame. The page's reload copy of the transcript was serialized into sessionStorage on every snapshot, which is every frame while streaming. It is now written at most every 250 ms, and immediately when the page hides or unloads.
  • Auto-follow is unchanged: the transcript follows only when you're within 4 px of the bottom (FOLLOW_BOTTOM_DEADBAND_PX). The frame test checks that the pin holds while streaming.

Frame budget test

test/streaming-text-frames.test.ts streams a 5,000-character reply (streamingTextFixture.ts: paragraphs, headings, lists, a code block, a table). The reply goes through the real transcript, with the bottom stack drawn over it, in Chromium and WebKit, at Claude's and Codex's chunk cadences. It samples every display frame and fails on:

  • any block above the one being written that moves or changes height
  • any element that changes tag as it forms
  • the pinned bottom drifting more than 4 px
  • a frame's main-thread work over 16.7 ms at p95, or any frame over 50 ms, in either engine, on two runs in a row. Work is the script of every task between two frames plus the layout left for the frame. test/browser/streaming-text.clock.ts times React's scheduler tasks (renders, commits, effects and the layout they force), frame callbacks, resize observers, timers and queued microtasks. Work is wall-clock time, so a starved host can break it once. That is why it gets a second run, while shifts, flips and the pin fail on the first.

Frame intervals are reported but not held. On CI they measure the host as much as the page: shard 3 runs other dev-server browser files beside this one, and at 91e0ad3 one frame in twelve came 33 ms apart. The long frames are Pierre highlighting the closed fence on the main thread (about 120 ms in Chromium, 1.1 s in WebKit). That only happens on the test's dev page, which has no highlight worker; the app's page highlights in one (highlightPool.ts). That time comes off a frame's work, and the report lists those frames separately. Outside them, on cmux16 the intervals were p95 16.7 ms and max 16.8 ms in Chromium, and p95 19 ms and max 26 ms in WebKit.

Results on cmux16:

before after
Elements that changed tag mid-stream 2 0
Gap from newest line to composer, p50 11 px 35 px
Code card height change on closing fence (WebKit) +6 px 0
Reveal frames with no step (WebKit, Claude / Codex cadence) 72 / 121 0 / 0
Reveal frames with no step (Chromium) 0 / 1 0 / 1
Frame work p95 / max, Chromium (both cadences, 3 runs) 9.3-10.9 / 15-41 ms
Frame work p95 / max, WebKit (both cadences, 3 runs; one 207 ms layout passed on its second run) 6-7 / 15-26 ms

test/code-handoff-height.test.ts checks that the plain and highlighted cards are the same height in both engines. test/streaming-markdown-forming.test.ts checks every prefix of the fixture for flips. test/snapshot-save-throttle.test.ts covers the 250 ms reload copy.

Gallery

The gallery entry agent-pane.streaming-text streams the frame test's reply at both cadences. The Linux gallery run takes its stills. These recordings come from cmux16 at fca0cb22, which has the same tree as 91e0ad3.

Claude cadence, Chromium:

streaming text claude chromium

Full quality: streaming-text-claude-chromium.mp4

Codex cadence, WebKit:

streaming text codex webkit

Full quality: streaming-text-codex-webkit.mp4

No capture: the only native change is a Debug Settings toggle (agentPane.debug.streamWords, on by default). Everything visible happens in the webview transcript, which the gallery recordings above show.

Changelog

  • Changed: Agent chat replies stream in steadily a word at a time, blocks no longer change shape as their Markdown forms, and there is room between the newest line and the composer.

Observed / Not run

  • Red/green on cmux16: cd webviews && bun test src/agent-session/acpmux/conversation/RevealedMarkdown.test.tsx. At the test commit 24956479 the three new tests fail (the code span shows "Run `bun", the paused reply stays at "Let me check the", wordEdge returns 53 for 60) and the other five pass. At fca0cb22 all 8 pass.
  • CI at c98ae83: test/streaming-text-frames.test.ts, test/code-handoff-height.test.ts and test/agent-pane-recap-card.test.ts pass. The CmuxNextAgentPane module, where the tunable lives, compiled in the fast tier. Every remaining red is also red on base:
  • At 11a3ffb, agent-pane-recap-card's fit test failed because of this PR: the pane was sized for a chat with no runway. c98ae83 fixes it.
  • Gate on cmux16 at 11a3ffb (review fixes: highlight time counted once, scroll listeners timed): bun run check passes, prepush-gate passes, test/streaming-text-frames.test.ts passes 4 of 4 at load 28-37 with no retry. Work p95 / max was 10 / 15.2 ms (Chromium, Claude), 9.6 / 22.9 ms (Chromium, Codex), 7 / 25 ms (WebKit, Claude) and 6 / 30 ms (WebKit, Codex). Chromium's only long task (119-123 ms) is Pierre tokenizing the closed fence.
  • Gate on cmux16 at e2875bd (base merged at 8214250; its one conflict was rowsHeight in App.tsx, where Agent chat: growing the composer never moves the transcript or its scrollbar #23097's held room replaced dockSpace and the runway stays on top): bun run check passes, prepush-gate passes, the 54 unit tests pass, and test/agent-pane-scrollbar-full-height.test.ts (Agent chat: growing the composer never moves the transcript or its scrollbar #23097's tests) and test/code-handoff-height.test.ts pass. test/streaming-text-frames.test.ts passed 4 of 4 at load 20-33: 0 shifts, 0 flips, pin 1 px. One WebKit Codex run went over the work budget once, with a 207 ms forced layout, and passed on its second run. An earlier run at load 25-36 failed two engines on long tasks of 540-650 ms with a grammar load taking 850 ms (normally 20). That run predates the retry.
  • Gate on cmux16 at df8c327: typecheck and vp lint pass, prepush-gate passes, the 54 unit tests pass, and test/code-handoff-height.test.ts passes in both engines. test/streaming-text-frames.test.ts passes 4 of 4 at load 8-20: 0 shifts, 0 flips, work p95 7.1 ms in Chromium and 4 ms in WebKit. bun run check fails only check:switch-fetches, on composer/keptAttachments.ts (from cmux-next composer: a chat's unsent pictures and files come back when it is reopened or restored #23139, already on the base 9912487; this PR doesn't touch it).
  • At 91e0ad3, CI's react-apps-check (3) failed the frame test in Chromium: a 300 ms frame and a 33.3 ms p95 interval. Everything else held: 0 shifts, 0 flips, pin 1 px. df8c327 changed the timing budget to per-frame work. Review of df8c327 found that its work figure missed commit-time layout, layout effects and resize observers, e2875bd times every task instead, and 11a3ffb fixes the second review's findings. CI never ran df8c327, because the PR conflicted with base.
  • Earlier gate on cmux16 at fca0cb22: typecheck, vp lint, bun run check, prepush-gate, the 54 unit tests this PR touches, test/code-handoff-height.test.ts (both engines) pass. test/streaming-text-frames.test.ts on cmux16 at load 36-98 had 0 layout shifts and 0 tag flips in Chromium, but missed the timing budgets (one 10 s frame, after which the view rested 326 px from its end for 2 frames), and WebKit timed out at 180 s. That is host load; PR CI's browser lane runs this test. scripts/web-lint.sh could not start there (node is not on cmux16's PATH); its vp check passed in the check step and CI runs it.
  • The Swift tunable test was added but not run locally (no Mac builds here). CI compiles it.
  • Frame timings on cmux16 are noisy (load ~130). Chromium max-frame spikes of 100 to 300 ms appeared at both base and head while the host was loaded. The Chromium budgets are meant for the CI runner.

Follow-up

  • incrementalMarkdown.ts's fenceOpen is now read only by test/incremental-markdown.test.ts.
  • The gallery entry's draw frame loop is not cancelled on unmount (gallery only).
  • model.ts estimates a wide code block's scrollbar at 15 px (SCROLLBAR_HEIGHT), which is too high now that the strip hangs into the padding. The virtualizer corrects it on measure, but the estimate should match.

Leo-ask: ask-76c0ef9f1c (this PR adds the 24 px runway under a streaming reply. The caret was already removed by #22730, words already fade in (RevealedMarkdown FADE_MS), and growth at the latest row already glides into place)

Agent session: cosmic-gray

🤖 Generated with Claude Code

@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@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 c98ae83: 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=pr23094 -f ref=feat/next-streaming-text -f platform=macos -f backend_mode=local

Gallery before/after for c98ae83: 16 states rendered, 14 images differ, 1409 more touched states not rendered. Images are in the run's artifact, not on this public PR.

gh run download 39594033591 -R manaflow-ai/cmux -n gallery-advisory-23094-c98ae8366ca2d9ad414b1843a6567d6bb18a85e4 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.chat-width/standard Apple System Colors Light changed 7.43% own App.tsx
agent-pane.chat-width/standard Apple System Colors changed 6.24% own App.tsx
agent-pane.chat-width/wide Apple System Colors Light changed 7.43% own App.tsx
agent-pane.chat-width/wide Apple System Colors changed 6.24% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors Light changed 7.43% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors changed 6.24% own App.tsx
agent-pane.composer-floats/scrolled-to-end Apple System Colors Light changed 13.1% own App.tsx
agent-pane.composer-floats/scrolled-to-end Apple System Colors changed 12.9% own App.tsx
agent-pane.composer-floats/composer-grown Apple System Colors changed 10.7% own App.tsx
agent-pane.composer-floats/composer-grown Apple System Colors Light changed 10.6% own App.tsx
agent-pane.docked-chat/new-chat Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/new-chat Apple System Colors unchanged 0% own App.tsx
agent-pane.docked-chat/after-turn Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/after-turn Apple System Colors unchanged 0% own App.tsx
agent-pane.docked-chat/working Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/working Apple System Colors unchanged 0% own App.tsx
agent-pane.docked-chat/typing-in-dock Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/typing-in-dock Apple System Colors unchanged 0% own App.tsx
agent-pane.docked-chat/long-title Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/long-title Apple System Colors unchanged 0% own App.tsx
agent-pane.orchestration-lab/local-queue Apple System Colors Light unchanged 0% own App.tsx
agent-pane.orchestration-lab/local-queue Apple System Colors unchanged 0% own App.tsx
agent-pane.orchestration-lab/huge-blue Apple System Colors Light unchanged 0% own App.tsx
agent-pane.orchestration-lab/huge-blue Apple System Colors unchanged 0% own App.tsx
agent-pane.orchestration-lab/cloud-fair-share Apple System Colors Light unchanged 0% own App.tsx
agent-pane.orchestration-lab/cloud-fair-share Apple System Colors unchanged 0% own App.tsx
agent-pane.streaming-follow-motion/stream Apple System Colors Light unchanged 0% own App.tsx
agent-pane.streaming-follow-motion/stream Apple System Colors unchanged 0% own App.tsx
agent-pane.streaming-text/claude Apple System Colors Light new 0% own App.tsx
agent-pane.streaming-text/claude Apple System Colors new 0% own App.tsx
agent-pane.streaming-text/codex Apple System Colors Light new 0% own App.tsx
agent-pane.streaming-text/codex Apple System Colors new 0% own App.tsx

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Unclear: web / react-apps-check failed: no known signature; failed step: Check webview test shards.

CI failed on c98ae8366c (run 39594033497 attempt 1): 1 code, 1 unknown.
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped.

Job Verdict Why
web / react-apps-check (3) code a web test failed
web / react-apps-check unknown no known signature; failed step: Check webview test shards

Failures:

  • seen on other PRs agent-pane-scrollbar-full-height.test.ts a composer that grows and shrinks leaves the rows a reader reads where they were on every frame: failed (job)
  • seen on other PRs agent-pane-scrollbar-full-height.test.ts a composer that grows and shrinks keeps a followed view at its end on every frame: failed (job)
Matched log lines
web / react-apps-check (3): (fail) a composer that grows and shrinks leaves the rows a reader reads where they were on every frame [4314.22ms]

Not re-run automatically: web / react-apps-check (3), web / react-apps-check are not machine failures.

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.

red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base ef40118; does not block
red: web / Web status; base red: the same failure on the base ef40118; does not block
red: web / react-apps-check; base red: the same failure on the base ef40118; does not block
red: web / react-apps-check (3); NEW: Expected: <= 1 Received: 20 at <anonymous> (/home/runner/_work/cmux/cmux/webviews/test/agent-pane-scrollbar-full-height.test.ts:441:53) (+1 more) is not red on the base ef40118; do not merge until green; see lane-rules

CI fast guards failed on c98ae8366c (https://github.com/manaflow-ai/cmux/actions/runs/39594033497). 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 c98ae8366c (run): no media. The tour job failed before the tour ran (build or setup); the run log has the output.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by projects-a4 on cosmic-gray · session

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by projects-a4 on cosmic-gray · session

@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Found 7 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmpme6f8tc2/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmppn5maueq/summary-missing.json (live):/summary-missing.json (live): View Logs
test/agent-pane-scrollbar-full-height.test.ts/agent-pane-scrollbar-full-height.test View Logs
test/agent-pane-scrollbar-full-height.test.ts/agent-pane-scrollbar-full-height.test View Logs
test_a_pull_request_that_changes_the_shortcut_inputs_still_rewrites_them (main.Sett
ingsOnlyArtifactLeavesTheActionFollowers.test_a_pull_request_that_changes_the_shortcut_
inputs_still_rewrites_them) (job='autofix')/
test_a_pull_request_that_changes_the_shortcut_inputs_still_rewrites_them (main.Sett
ingsOnlyArtifactLeavesTheActionFollowers.test_a_pull_request_that_changes_the_shortcut_
inputs_still_rewrites_them) (job='autofix')
View Logs
test_regenerated_action_exports_bring_the_followers (main.SettingsOnlyArtifactLeave
sTheActionFollowers.test_regenerated_action_exports_bring_the_followers) (job='autofix'
)/
test_regenerated_action_exports_bring_the_followers (main.SettingsOnlyArtifactLeave
sTheActionFollowers.test_regenerated_action_exports_bring_the_followers) (job='autofix'
)
View Logs
test_settings_exports_alone (main.SettingsOnlyArtifactLeavesTheActionFollowers.test
_settings_exports_alone) (job='autofix')/
test_settings_exports_alone (main.SettingsOnlyArtifactLeavesTheActionFollowers.test
_settings_exports_alone) (job='autofix')
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

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

teamleaderleo and others added 13 commits October 21, 2026 01:32
…e last line

A streaming reply now grows a whole word at a time, each word fading in, instead of a
letter at a time (wordEdge from #22339; CJK grows per character, words over 48 chars show as
they arrive). The Debug toggle agentPane.debug.streamWords (on) switches back to characters.

The transcript keeps 24px of runway under its last row over the bottom stack's room, so the
streaming line sits above the composer instead of on its edge. It is always there, so a reply
that starts or ends moves nothing.

Co-authored-by: Leo Li <cheerleaderleo@outlook.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
…y 250 ms

A streaming reply sends a snapshot every display frame, and each one serialized the whole
transcript into sessionStorage for a reload of the page: main-thread time that grew with the
chat, every frame. It is now written at most every 250 ms with the latest snapshot, and at once
when the page hides or unloads.

Adds the What's New highlight for smooth streaming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
…ming

A list marker, heading hashes, rule line, lone emphasis marker or open
italic run no longer draws as text first and then turns into its block.
A fence closer still being typed is held with the newline before it, so
a code card never shrinks a line when it closes, and the open-fence card
is read from the parsed block so a held opener can't reopen a closed one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
…n card

Fails in WebKit: Pierre's scrollbar strip makes the highlighted card 6px taller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
…d in WebKit

The strip hangs into the card's bottom padding, so text under a closing fence stays put.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
…ebKit's jittery frame times

WebKit's frame times are whole milliseconds and jitter around 17 ms, so a
14 ms frame after a 19 ms one fell under the COMMIT_MS - 1 gate and skipped
its reveal step: the streaming-text harness counted 64 to 119 stalled frames
a reply in WebKit, none in Chromium. A 120 Hz display still steps on every
other frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
The reveal loop learns the display's frame time and skips a frame only on a
display faster than 60 Hz, so a 60 Hz display commits every frame however its
frame times jitter, and a 120 Hz one still commits 60 times a second. One loop
per row reads the latest text itself instead of restarting on every snapshot.
WebKit stalls in the frame harness: 72 -> 0 (Claude cadence), 121 -> 0 (Codex).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
…rd shows after a pause

Review of #23094: the last word shows a frame after the row stops streaming, and that frame
parses without safeTail (a literal backtick). A last word with no space after it stays hidden
while the agent works on a tool call. A long word grows in 48-character jumps once part shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
…d shows during a pause

A reply draws as streaming until all of it shows, so the frame after the stream ends parses the
held last word's span with safeTail. The last word counts as whole once no text has come for
StreamReveal.stallMs; the loop waits that out without committing. A long word is measured from its
start, so once part of it shows the rest grows as it arrives. A tick clears its handle first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
@teamleaderleo
teamleaderleo force-pushed the feat/next-streaming-text branch from 113b889 to 91e0ad3 Compare October 21, 2026 08:33
teamleaderleo and others added 4 commits October 21, 2026 02:00
CI's react-apps-check shard 3 failed the frame test on 91e0ad3 with a
300 ms frame and a 33.3 ms p95 interval in Chromium. The long frames are
Pierre highlighting the closed fence on the main thread: the test's dev
page has no highlight worker, the app's page does (highlightPool.ts).
The 33 ms intervals came while CI ran other dev-server browser files
beside it. Neither is the reveal's work.

The harness now times each frame's work (React's render through a
Profiler, and the style and layout it caused) and marks frames that
waited on a main-thread highlight. The test holds p95 work under 16.7 ms
and any frame under 50 ms in both engines; intervals are reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
…treaming-text

# Conflicts:
#	webviews/src/agent-session/acpmux/App.tsx
The work figure counted React's render time and the layout left at the
sample, so the layout a commit forces (the follow effect reads
scrollHeight), layout effects, resize observers and the reveal's frame
callbacks went unmeasured. streaming-text.clock.ts now wraps the task
sources before React's scheduler loads (MessagePort onmessage, frame
callbacks, timers, queueMicrotask, ResizeObserver) and a frame's work is
the script of every task since the last sample plus the layout left.

Pierre's synchronous highlighting on this dev page (draws and tokenizing,
which the app does in a worker) comes off a frame's work; intervals are
tagged by when highlighting ran, so the long interval after it is tagged.
Work is wall-clock, so a starved host can break it once: the work budget
fails on two runs in a row; layout shifts, flips and pin fail at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
Review of e2875bd: WebKit's File.render tokenizes inside itself, and both
calls were taken off a frame's work, so a highlight frame went about 1 s
negative and could hide real work beside it; only the outermost call
counts now. Event listeners are timed too (the transcript's scroll
handler, and the layout its reads force), the comments name what stays
untimed, a work-budget retry warns, the resize observer callback keeps
its `this`, and the budget comment carries the new measurements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

PR capture check / pr-capture-check is red on 24 of 30 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

codex-review: SHIP at 11a3ffb

  • Reviewed the current diff, its focused tests, and the affected call paths; no correctness bug found.

teamleaderleo and others added 2 commits October 21, 2026 04:37
…he chat

CI at 11a3ffb: "the card leaves the turn's footer in view above it" sized
the pane so the last row ended 4 px above the stack, which no longer fits
now that the transcript keeps TRANSCRIPT_RUNWAY under its last row (it
overflowed with "spare: 24"). It now sizes the pane to the runway plus
4 px. TRANSCRIPT_RUNWAY moves to model.ts with the other row geometry so
the test can import it without the pane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d2c-9556-70a3-8034-46aaacf15f3a&host=cosmic-gray
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (independent subagent): SHIP at c98ae83

Review rounds:

  • Earlier rounds: round 2 passed fca0cb22. 91e0ad3 is the same series, which the range-diff confirms.
  • df8c327: changes requested. The per-frame work figure counted React's render and the residual layout. It missed commit-time layout, layout effects and resize observers.
  • e2875bd: changes requested. This commit merged base and times every main-thread task. The merge resolution of rowsHeight (Agent chat: growing the composer never moves the transcript or its scrollbar #23097's held room, with the runway on top) passed. Finding: WebKit's nested highlight time was subtracted twice, and the scroll listener wasn't timed.
  • 11a3ffb: SHIP. It counts each highlight once, times event listeners, warns on a retry, and updates the comments.
  • 2707e29 (second base merge, clean) and c98ae83: SHIP. c98ae83 sizes the recap card's fit test to leave the runway; the constant moved to model.ts.

CI at c98ae83:

  • Passed, this PR's: streaming-text-frames, code-handoff-height and agent-pane-recap-card. The CmuxNextAgentPane module compiled in the fast tier.
  • Red, also red on base:
    • RemoteChildSessionsTests.FakeClient doesn't conform to AgentSessionRemoteClient. This failed fast tier, generated files and swift test. It is fixed on base by cmux-next tests: RemoteChildSessionsTests' fake client states isReadOnly (base red: tests don't compile) #23362, and this PR touches no file in that target.
    • "Companion workflows" in cmux-next checks: scripts/cmux-next/base-autofix-repair.sh doesn't exist, and this PR touches no scripts or workflows.
    • agent-pane-scrollbar-full-height's two "grows and shrinks" tests: the composer card ends 20 px off its starting height. They failed 3 of 3 times at base bc70489a on cmux16. react-apps-check, Web status and the shard check are wrappers of this red.
  • Still pending: Release and Debug compile are still queued.

@teamleaderleo
teamleaderleo merged commit 00f97d8 into feat-cmux-next Oct 21, 2026
@teamleaderleo
teamleaderleo deleted the feat/next-streaming-text branch October 21, 2026 13:46
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
Keeps both sides of App.tsx: base's TRANSCRIPT_RUNWAY (#23094) joins the
lifted room's rows end and the unlifted end the lift is measured against.
The jitter file's new frame wait uses base's cmuxPaintFrame, as its
browser-lane check now asks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fdc7-79b0-b869-8ac78b0edd90&host=cosmic-gray
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
* test(webviews): base passes the fixed-wait ratchet again

#23094 merged after the guard (#23365) with 9 new fixed waits, so
check:fixed-waits fails on base and on every PR into feat-cmux-next.

- snapshot-save-throttle: the 250 ms save timer runs on bun's fake
  timers; nothing is written at 249 ms, one write at 250 ms. (The old
  300 ms sleep passed with any delay under 300 ms.)
- streaming-text harness and frames test: the reveal drain and the
  settle window are sampled, so the windows are the test; the 500 ms
  before sampling has no start-up-done signal. Marked fixed-wait.
- RevealedMarkdown: the 20 ms in afterEach is teardown with no assert
  after it. Marked fixed-wait.

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): reveal-message runs its row wait on the fake clock

#22967 added three fixed waits after the guard: the wait for a
revealed row to be drawn polls on setTimeout, so the three tests that
watch it now run on bun's fake timers and advance the clock instead of
sleeping. Breaking the poll, the stale-chat check or the cancel of an
earlier mark each fails its test.

Also: the frames test's start-up comment no longer guesses the cause.

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): queued-images waits for snapshots; a deleted test's baseline row passes

#22975 merged two unmarked polls in agent-pane-queued-images (CI ran before the
fixed-wait guard); they now wait on the client's snapshots. #23339 deleted
session-sidebar-preview.test.tsx while it still had a baseline row, so the check
failed every PR; a row whose file is gone now passes and drops on --update-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): drop the deleted test's baseline row; queued-images' wait fails before bun's timeout

Review nits on #23452: the stale session-sidebar-preview row would have let a new file at that
path keep 6 waits, and a 5 s condition timeout lost its message to bun's 5 s test timeout.

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>
@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
…es reruns them on the current tip before the merge (#23516)

* gh-merge-green: a check path the base changed after the PR's fast gates reruns them on the current tip before the merge

A pull request whose CI ran before a check landed on the base could merge without ever passing it: #22967,
#22975 and #23094 merged into feat-cmux-next after #23365 added check:fixed-waits and turned it red on the base.
Into feat-cmux-next, scripts/ci/merge_check_paths.py reads the base's scripts/ci/check-paths.txt (one list, shared
with next-autoland); when a check path changed after the base the PR's cmux-next/fast-gates status names, it
dispatches cmux-next-fast-gates.yml for the PR, which merges it into the current tip and runs the cheap gates and
the webviews checks, and merges only on a fresh success. Until the list is on the base the gate passes with a note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* gh-merge-green check-path gate: an answer that is not JSON refuses; the other test fakes have no check-paths.txt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* gh-merge-green: the check-path gate's wait counts in the summary, notes filter through sed, and --override waives its red

From the review of #23516.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* tests: register test_merge_check_paths.py in the execution registry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&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
…ecks on a dispatch (#23503)

* cmux-next fast gates: check paths a merger reads, and the webviews checks on a dispatch

A pull request whose CI ran before a check landed on the base could merge without ever passing it:
#22967, #22975 and #23094 merged after #23365 added check:fixed-waits and turned it red on the base.
scripts/ci/check-paths.txt lists the check scripts, their baselines, webviews' package.json, the
fast gates and the workflows; next-autoland and gh-merge-green read it from the base and dispatch
the fast gates when one changed after the pull request's fast-gates base. A dispatch now also runs
ci-web's fail-mode webviews checks on the pull request merged with the current tip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* cmux-next fast gates: the webviews checks fail only on findings the pull request adds over the base

A replay of #22975 merged with 01ea78b (the commit that added check:fixed-waits) shows the base already red
with two findings and the merge adding a third of its own; charging the base's red to every dispatched pull
request would stall them all during a base red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* test: each fake commit changes a file

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* check paths: only the workflows a dispatch reruns, not every workflow

A dispatch reruns only the fast gates, so another workflow's change makes nothing stale that it could catch.
On 2026-10-21, 144 of 1610 feat-cmux-next commits changed some workflow, about one every ten minutes; with
these five, 62 commits changed a check path, about one every 23 minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* fast gates: review on #23503: every gate input is a check path, the webviews checks run for a catalog or protocol spec change, findings are counted, a dispatch only restores the cache

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* fast gates: a dispatch only restores the pinned lint tools cache too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* check paths: the ratchets' allowlists and vite-plus's lint config (from ci-speed's research)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

* tests: register test_check_paths.py in the execution registry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bfb-0a99-78c2-b078-ca1fc578ff04&host=cosmic-gray

---------

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