Repository navigation
Agent chat: streaming replies grow by whole words, with room above the composer and no Markdown flips - #23094
Conversation
|
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)
Gallery before/after for
States
Pick other states with a Unclear: CI failed on
Failures:
Matched log linesNot re-run automatically: Written by red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base ef40118; does not block
Agents: Tour of |
|
Owned by projects-a4 on cosmic-gray · session |
|
Owned by projects-a4 on cosmic-gray · session |
|
Found 7 test failures on Blacksmith runners: Failures
|
|
Bead |
…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
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
…oads as the app does 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
…adences 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
…ck holds until its line ends 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
113b889 to
91e0ad3
Compare
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
|
|
|
codex-review: SHIP at 11a3ffb
|
…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
|
Review (independent subagent): SHIP at c98ae83 Review rounds:
CI at c98ae83:
|
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
* 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>
|
Shipped in:
|
…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>
…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>
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:wordEdgecomes 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 toggleagentPane.debug.streamWords(on) switches back to characters.StreamReveal.stallMs, typically before a tool call), the held last word shows instead of waiting for the next delta.COMMIT_MS - 1after 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.-,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.FOLLOW_BOTTOM_DEADBAND_PX). The frame test checks that the pin holds while streaming.Frame budget test
test/streaming-text-frames.test.tsstreams 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:test/browser/streaming-text.clock.tstimes 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:
test/code-handoff-height.test.tschecks that the plain and highlighted cards are the same height in both engines.test/streaming-markdown-forming.test.tschecks every prefix of the fixture for flips.test/snapshot-save-throttle.test.tscovers the 250 ms reload copy.Gallery
The gallery entry
agent-pane.streaming-textstreams 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:
Full quality: streaming-text-claude-chromium.mp4
Codex cadence, 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
Observed / Not run
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",wordEdgereturns 53 for 60) and the other five pass. At fca0cb22 all 8 pass.test/streaming-text-frames.test.ts,test/code-handoff-height.test.tsandtest/agent-pane-recap-card.test.tspass. TheCmuxNextAgentPanemodule, where the tunable lives, compiled in the fast tier. Every remaining red is also red on base:RemoteChildSessionsTestsdoesn't compile (fixed on base by cmux-next tests: RemoteChildSessionsTests' fake client states isReadOnly (base red: tests don't compile) #23362).base-autofix-repair.sh.agent-pane-scrollbar-full-height's two "grows and shrinks" tests end with the composer card 20 px off its starting height. They failed 3 of 3 times at basebc70489aon cmux16.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.bun run checkpasses, prepush-gate passes,test/streaming-text-frames.test.tspasses 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.rowsHeightinApp.tsx, where Agent chat: growing the composer never moves the transcript or its scrollbar #23097's heldroomreplaceddockSpaceand the runway stays on top):bun run checkpasses, prepush-gate passes, the 54 unit tests pass, andtest/agent-pane-scrollbar-full-height.test.ts(Agent chat: growing the composer never moves the transcript or its scrollbar #23097's tests) andtest/code-handoff-height.test.tspass.test/streaming-text-frames.test.tspassed 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.vp lintpass, prepush-gate passes, the 54 unit tests pass, andtest/code-handoff-height.test.tspasses in both engines.test/streaming-text-frames.test.tspasses 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 checkfails onlycheck:switch-fetches, oncomposer/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).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.tson 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.shcould not start there (nodeis not on cmux16's PATH); itsvp checkpassed in the check step and CI runs it.Follow-up
incrementalMarkdown.ts'sfenceOpenis now read only bytest/incremental-markdown.test.ts.drawframe loop is not cancelled on unmount (gallery only).model.tsestimates 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 (
RevealedMarkdownFADE_MS), and growth at the latest row already glides into place)Agent session: cosmic-gray
🤖 Generated with Claude Code