Repository navigation
Agent chat: the wave rail draws only the ticks in its view (36 ms to 1.7 ms a frame on a 2,000-row chat) - #25152
Conversation
|
Owned by rail-opus-01 on huge-blue · session |
|
Bead |
…ort chat, list semantics Review of #25152 (gpt-6-astra): - The focused, hovered and card rows resolve by their prompt's key when older turns renumber them, so the focused tick stays drawn and keeps focus. - A list that no longer overflows (another chat, fewer prompts) goes back to its top, and tickRanges clamps a stale offset, so the rail never draws blank. - The list is an ol; each tick sits in a 10 px li (acpmux-minimap__entry) with aria-posinset and aria-setsize for its place among every prompt; spacers are aria-hidden. - The append test asserts the exact window and every kept tick's identity again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f7b-11f6-73e0-8276-7c0e3bb3ead4&host=huge-blue
a487804 to
b979455
Compare
|
Found 80 test failures on Blacksmith runners: Failures
...and 70 more test failures. View all on Blacksmith
|
|
|
… their prompts by a key map Re-review of #25152 (gpt-6-astra): - The tick the page's focus is on (a pointer's click focuses one too, without the keyboard's grow or card) is tracked by key from focus and blur on the list, and always drawn, so scrolling never unmounts it. - The focused, hovered and card rows resolve through a key-to-row map built once per change of the ticks, and the state is set to the resolved rows, so a cut of earlier turns leaves no search per token and the card stays on its prompt. This replaces the prepend-only shift of those rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f7b-11f6-73e0-8276-7c0e3bb3ead4&host=huge-blue
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
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)
Probably yours: CI failed on
Failures:
Matched log linesNot re-run automatically: Written by red: web / Web status; NEW: green on the base ab362b8; do not merge until green; see lane-rules Gallery before/after for
States
Pick other states with a |
|
|
A 2,000-row chat mounted 1,000 tick buttons, and every layout the transcript forces laid them all out. The rail now draws the rows in its list's view plus 12 past each edge, and the focused, hovered and described rows; spacers keep the list as tall as every row. Keys and Tab that move past the drawn rows draw the row, then focus it. The list's offset is kept from its own scroll and writes, so a commit reads no layout to place it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f7b-11f6-73e0-8276-7c0e3bb3ead4&host=huge-blue
…ort chat, list semantics Review of #25152 (gpt-6-astra): - The focused, hovered and card rows resolve by their prompt's key when older turns renumber them, so the focused tick stays drawn and keeps focus. - A list that no longer overflows (another chat, fewer prompts) goes back to its top, and tickRanges clamps a stale offset, so the rail never draws blank. - The list is an ol; each tick sits in a 10 px li (acpmux-minimap__entry) with aria-posinset and aria-setsize for its place among every prompt; spacers are aria-hidden. - The append test asserts the exact window and every kept tick's identity again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f7b-11f6-73e0-8276-7c0e3bb3ead4&host=huge-blue
… their prompts by a key map Re-review of #25152 (gpt-6-astra): - The tick the page's focus is on (a pointer's click focuses one too, without the keyboard's grow or card) is tracked by key from focus and blur on the list, and always drawn, so scrolling never unmounts it. - The focused, hovered and card rows resolve through a key-to-row map built once per change of the ticks, and the state is set to the resolved rows, so a cut of earlier turns leaves no search per token and the card stays on its prompt. This replaces the prepend-only shift of those rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f7b-11f6-73e0-8276-7c0e3bb3ead4&host=huge-blue
… goes A focused button removed with its chat or prompt may send no blur. The key now carries its chat and is dropped once it names no tick of the chat on the rail, so a returning chat does not hold that tick drawn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f7b-11f6-73e0-8276-7c0e3bb3ead4&host=huge-blue
…shows and has focus A rail that hides (fewer prompts than it needs, too little room) removes its focused button without a blur. The key now holds only while the rail is shown, and is dropped whenever the page's focus is not in the list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f7b-11f6-73e0-8276-7c0e3bb3ead4&host=huge-blue
A rail hidden for lack of room (or prompts) unmounts its list, and the next one mounts at scrollTop 0. The cached offset now resets while no list is mounted, so the placement effect scrolls the new list to the band and draws its rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f7b-11f6-73e0-8276-7c0e3bb3ead4&host=huge-blue
1c98439 to
072e2e1
Compare
|
|
|
Orphaned: no activity for 14 h, and no lane holds a lease on it. Adopted by the orphan sweep: merged |
The list item around each tick set its own height: 10px, a raw value the design-token ratchet refuses. The tick inside it is already a 10 px block with no margin, so a plain block item is the same height without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1587ac999477cd86120a
|
|
…and card Hiding the rail cleared the page-focus key but left the keyboard-focused row, the hovered row and the card's row, so a keyboard-focused tick's card and grown tick came back with the rail. When the rail goes, those go and the preview is dismissed; only on the change, so a rail that mounts hidden cools no hover group. Test: keyboard focus, hide, show at the end: no card and the old tick is not held drawn (fails without this commit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1587ac999477cd86120a
|
Shipped in:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
The compact (wave) rail, which is on by default, now draws only the ticks in its own view. On a 2,000-row chat it used to mount 1,000 tick buttons. Every layout the transcript forced (it measures rows on each commit) then laid all of them out. That cost 36 ms a frame at p95 while scrolling and 44 ms while a reply streamed, which is the lag you can feel.
Leo (2026-10-25): "the mini map VS Code code thing ... I feel like we should have that. Although I don't know if that actually, like, causes potential lag."
This PR measures that lag and removes most of it from the rail that ships. The full-height minimap (#22784) stays off; its numbers are at the end.
Change
threadMinimap/tickWindow.ts(new):tickRangespicks the rows to draw.aria-describedbyalways have their element.ThreadMinimap.tsxTickList: draws those ranges, with anaria-hiddenspacer for each gap.railScrollTopand each row's y are unchanged.ol. Each drawn tick sits in a 10 pxliwitharia-posinsetandaria-setsizegiving its place among every prompt, drawn or not. Spacers arearia-hidden. The tick buttons, their labels andaria-currentare unchanged.reveal). Tab and Shift-Tab past the drawn rows do the same, so Tab still walks every prompt.list.scrollTopafter a commit. It keeps the offset it last set or that the list's scroll event reported (listOffset). A list that no longer overflows (another chat, fewer prompts) goes back to its top.Review fixes (gpt-6-astra, CHANGES at a487804)
tickRangesclamps a stale offset. Tests:thread-minimap-tick-window.test.ts: the clamp.ol/liwith posinset/setsize. Test: "each drawn tick is a list item that names its place among every prompt".thread-minimap-streamagain checks that the drawn ticks are exactly the window's rows before and after a new prompt, and that every tick still in the window is the same element.Re-review fixes (gpt-6-astra at b979455)
P2, a clicked tick unmounted by scrolling: the page-focused tick is kept by key. Test: "a clicked tick keeps focus while the chat scrolls it out of the rail's window". It clicks the latest tick, scrolls the chat to its top, and checks that the tick is still connected and focused and that ArrowUp goes to the one above it.
P2, a search on every token after a cut: the key-to-row map replaces
findIndex, and the state is reconciled. Test: "after earlier turns go, streaming does no search of the ticks, and the card stays on its prompt".findIndex(303,000 visits before the fix). The active tick and the card are still on "Question 1999", now row 1499.P2 at 3663c32, a stale page-focus key: the key now carries its chat, and is dropped once it names no tick of the chat on the rail (a focused button removed with its chat or prompt may send no blur). Test: "a focused tick that goes away with its chat is not held drawn when the chat comes back". It clicks a tick, swaps in another chat, swaps the first back and scrolls to its top, and checks that the old tick is not drawn.
P2 at 87e959e, a page-focus key outliving a hidden rail: the key holds only while the rail is shown, and is dropped whenever the page's focus is not in the list (a hidden rail, an unmounted list, any removal without a blur). Test: "a focused tick whose rail hides and comes back in the same chat is not held drawn". It clicks the first tick, cuts the same chat to 3 prompts so the rail goes, restores it and scrolls to the end, and checks that tick 0 is not drawn.
P2 at cbe60e2, a remounted list under a stale offset: the cached list offset resets while no list is mounted, so a rail hidden for lack of room (or prompts) is placed from its top again when it comes back. Test: "a scrolled rail hidden for lack of room comes back at its place, its rows drawn". It shrinks a 2,000-turn pane scrolled to the end to 5 px so the rail goes, restores 600 px, and checks that the new list is scrolled to the band with its first and last visible rows (and the latest prompt) drawn.
Each new test fails on the head before its fix and passes now.
All four new tests fail on the previous head (5 fail, 7 pass across the three files) and pass at this head.
Numbers (same harness both ways, re-run at this head)
The bench from #25173 (
webviews/bench/thread-minimap-frame-cost.bench.ts, landed): a production build of the pane's dev entry and a stand-in acpmux with 1,000 turns (2,000 rows), Chromium at 1200x800. Each scenario runs for 4 s: idle; a wheel step of -120 every 16 ms; and a reply streaming 30 chunks/s with a new turn every second. Cost is main-thread busy time per painted frame, from a Chrome trace, as the median over 3 rounds. "Before" is base feat-cmux-next at the same commit.Extra cost over rail off:
Long tasks over 50 ms went from 2–13 per run to 0–1.
Not met: the 1 ms target for scroll and stream. Run to run, p95 moves by 2–3 ms. Across the three 3-round runs of this change, the stream p95 extra was 2.7, 7.8 and 5.8 ms.
liwrapper added for semantics costs little, within noise. Compared 3 rounds each: no wrapper 25.7 ms,display: contents26.0 ms, blockli28.8 ms at stream p95. I kept the blockli, becausedisplay: contentsdrops list semantics in some engines.Full-height minimap (#22784), same harness, before this change: +57 ms scroll and +112 ms stream at p95 over rail off. It stays off, and no setting was added.
Captures (dark theme, 1,000-turn chat, compact rail, at this head)
The before and after captures are pixel-identical (PIL
ImageChops.differencebbox isNonefor all three).nav.liwith posinset 472 and setsize 1000 inside anol.Tests
test/thread-minimap-long-chat.test.tsx(9 tests) andtest/thread-minimap-tick-window.test.ts(2 tests).thread-minimap-stream(exact window and identity, above).thread-minimap-tinyandthread-minimap-bottom: their jsdom scroll clamp now counts spacer heights.vp lintandvp fmt --checkare clean on the touched files.Size
853 changed lines (802 added, 51 removed): 268 in product code and 585 in tests. Over 800 only through the review-round regression tests. The bench (337 lines) moved to #25173.
Risk
Low to medium. It is limited to the compact rail's DOM: the rows drawn, the list's offset, and the list markup (
ol/liwhere it was adivof buttons). The main risk is a row missing for one frame. That could happen when the browser scrolls the list by more than 12 rows on focus before React renders the new range. The focused row itself is always drawn.No new timers, observers or animation loops. Per-token work in the rail is O(1) unless older turns arrive. A key lookup (
findIndex) runs only when a held row's index no longer matches its prompt, which happens when older turns arrive, never per token.Overlaps
pr-overlaplists Agent chat: the thread rail shows every prompt of the chat, read in the background #25160 (every prompt on the rail, item G+H), which conflicts inThreadMinimap.tsx.ThreadMinimap.tsx.thread-minimap-stream.test.tsx.Agent session: huge-blue
🤖 Generated with Claude Code