Repository navigation
Agent chat: no thread minimap by default (the column of marks down the left edge) - #23735
Conversation
The default arm of thread-minimap-layout is full since #22784, so every agent chat draws the full-height ScrollMap (a dash per message and a view box) down the transcript's left edge, and the compact rail's list shows a second scroller since #23285. These tests fail until the default is off and the list stops scrolling on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16027-a80b-79c0-ab31-500e58883d27&host=cosmic-gray
Leo on the nightly (2026-10-21): a column of dashes and a thumb box down the left edge of the chat, and a second scroller. That is the full-height ScrollMap, the default arm since #22784 (lit band #22879), and the compact rail's own list, which shows a scroller since #23285 dropped its hiding. - thread-minimap-layout gets an off arm, and off is the default: no map, no rail, nothing at the left edge. Previous and Next Turn and the bookmarked-prompt steps still work. full and compact stay selectable from cmux.experiments for dogfood. - The compact rail's list is overflow: hidden: it still scrolls from script and focus (band follow, scrub, Tab) but never draws a scroller, so the transcript keeps its one shared thin scroller (SCROLLBARS-THIN). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16027-a80b-79c0-ab31-500e58883d27&host=cosmic-gray
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Passes: CI passes on CI passes on Written by web and size checks: green
Tour of Gallery before/after for
States
Pick other states with a |
|
Owned by glass-regressions on cosmic-gray · session |
|
Bead |
|
Review (subagent, correctness first): SHIP at 6334120b6e43e44f3cd4cb17929ba386caf52a16 Reviewed 0518bf1 in full. 6334120b only reformats it (oxfmt: one wrapped description string in What I checked:
Non-blocking nits (fine as follow-ups):
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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. |
|
Review (subagent, correctness first): SHIP at 0af436b 0af436b is the head on the remote. Its parent is 0518bf1, which I reviewed in full (see my earlier comment). |
|
Found 1 test failure on Blacksmith runners: Failure
|
|
Shipped in:
|
… the chat (#24058) * test(webviews): the wave rail is the default and its band reaches the bottom (red) Leo 2026-10-22: "it wouldn't show the highlighted bottom text where I was scrolling at the bottom, and it just didn't go to the bottom" and "I didn't want the mini map." These fail until the compact rail (the wave from #22228) is the default arm again and the rail's own list keeps the whole band in view, reaching its end at the bottom of the chat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * fix(webviews): the wave rail is the thread rail again, and its band reaches the bottom Leo 2026-10-22: "Why did you turn it into a whole mini map? ... I didn't want the mini map." and "IT'S JUST THE FUCKING LINES THEY DONT FUCKIG ANIMTATE". His "the rail feels short" meant the band never reached the bottom ticks: "it wouldn't show the highlighted bottom text where I was scrolling at the bottom, and it just didn't go to the bottom." - thread-minimap-layout defaults to compact again: the Codex rail from #22228, one tick per prompt, the band that follows the scroll and the Dock-style magnify under the pointer. The full-height map (#22784) and off stay selectable from cmux.experiments. - The rail's own list (a chat with more prompts than the rail has room for) kept only the band's first row in view, a third of the way down, so the prompts below it sat under the bottom fade or past the rail's end. railScrollTop keeps the whole band in view and clear of the fades, and a band holding the last prompt scrolls the list to its end, where no fade draws: at the bottom of the chat the bottom ticks show lit. - The list keeps overflow: hidden (#23735), so it never draws a scroller, and the rail stays sticky with no height: no layout shift on a tab switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * test(webviews): the rail's lit ticks stay clear of the transcript's faded edges (red) Review of #24058: the rail is inside .acpmux-scroll, whose mask fades rows out 18 px under its top once scrolled and 18 px (56 px with Jump to latest) above its foot while more lies below. In a short pane the rail filled the view less 8 px each end, so the first and last lit ticks drew inside those fades at every offset short of the chat's ends: the bottom ticks Leo asked to see lit were hidden again. - thread-minimap-bottom: a 320 px pane, every offset; and the model's fade constants must cover the CSS's bands. - thread-minimap-feel (Chromium): a 420 px window, the real mask's bands read from the scroller's computed style. - thread-minimap-rail: railHeight leaves room for the bands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * fix(webviews): the rail keeps its ticks clear of the transcript's faded edges The rail sits inside the transcript, so the transcript's mask fades it: 18 px under the top once scrolled, 18 px (56 px with Jump to latest) above the view's foot while more lies below. railRoom keeps the rail's rows out of both bands at their widest (so the rail never moves as they come and go), and the rail centers on the view as before, moved only as far as that takes. App passes the mask's foot (the bottom stack's top), so a composer grown while following does not cover the bottom ticks either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * test(webviews): every lit tick draws whole in the rail, down to a 96 px pane (red) Review round 2 of #24058: in a 96 px transcript (the CSS minimum) with four prompts the rail was 18 px tall, and a band in the middle of the chat put its lit tick at 10..20 px, past the rail's end. The list's own 40 px edge fades also covered a rail that short. - thread-minimap-room: model sweep over pane heights 96 to 640 px, 1 to 70 prompts, every band of 1 to 4 prompts scrolled down and back up. Every lit tick must sit whole inside the rail, and, where the pane has room, clear of the transcript's faded edges. railHeight takes the band's size (a new fifth argument; the red commit ignores it). - thread-minimap-tiny: renders panes 96 to 336 px, 4 to 70 prompts, offsets down the whole chat, and checks every lit tick against the rail, the list's fades and the transcript's mask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * fix(webviews): the rail is never shorter than the band, down to a 96 px pane Review round 2 of #24058: in a 96 px transcript the rail was 18 px, shorter than its own 40 px edge fades, and a band in the middle of the chat drew its lit tick past the rail's end (railScrollTop fell back to 0 when the fade clearances conflicted). - railHeight takes the number of prompts on screen and never returns less than their rows: in a pane too short for the band and the transcript mask's clearance, the clearance gives way first, up to the whole view. railTop then centers the rail in the view. - The list's edge fades are a quarter of a short rail at most (railFade, --acpmux-minimap-fade) and never draw over the band (railFades). - railScrollTop keeps the band whole inside the list when the fade clearances conflict, instead of falling back to 0. - The rail hides only if the view has no room for one row; the transcript's 96 px minimum always has room. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * test(webviews): the bottom rail test reads only the mask fades that draw, and has room to run maskFades took the smallest --acpmux-fade-bottom in conversation.css, which is one of the 0px rules (find open, Reduce Transparency, Increase Contrast) that turn the fade off, so the CSS check failed at once. Those rules draw nothing; only the nonzero bands count. The two render sweeps take about 5 and 7 s in jsdom, past bun's 5 s default when run with plain `bun test` (CI's runner raises it to 30 s), so the file sets 60 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * test(webviews): the short-pane rail sweeps run in a full-height window, as the app does The rail caps its height at 70% of the window (globalThis.innerHeight), and bun's globalThis has none, so the rail fell back to 70% of the pane. In a 320 px pane that is 224 px, centered, nowhere near the transcript's 18 px fades, so the short-pane test passed on the code it was written against (34b6d18) as well as on the fix. In the app the window is taller than its transcript and the rail filled the pane less 8 px a side, into the fades. Both sweeps now run with a 900 px window: the 320 px test fails on 34b6d18 again, and the tiny sweep fails at 96 to 176 px on 3eec766 (it failed at 96 to 128 before). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * test(webviews): a token streaming into the last reply builds no tick again (red) #24058 review round 3: every streamed token gives the transcript a new rows snapshot, and the rail maps every prompt again for it, building each tick's label and element: O(prompts) work per token on the main thread, now that the rail is the default. thread-minimap-stream renders a 300-prompt chat and streams 20 tokens into the last reply, counting the tick labels the rail builds (each tick it renders reads its label string once). It expects none per token, the card to still preview the streamed reply, and a new prompt to add its tick while the earlier tick elements stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * fix(webviews): a streamed token renders no tick on the rail #24058 review round 3: each streamed token is a new rows snapshot, and the rail mapped every prompt again for it, building each tick's label and element: O(prompts) per token. - minimapTurns also returns `ticks` (each prompt's key, id and text), which keeps the previous list, by identity, while the prompts are the same. It compares only the turns from the first changed one on; the earlier ones are the previous objects. - The rail draws its rows through a memoized TickList that takes `ticks` and the band, hover, focus and card state, so a render that changes none of these (a token in the live reply) renders no row. `turns` still changes with the reply, so the card previews it as it streams. - Per token the rail now does the reply's turn (as before) plus a compare from the changed turn to the end; the ticks are not visited. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * test(webviews): a token reads rows bounded by what changed, and a hovered rail keeps the band (red) #24058 review round 4. - thread-minimap-stream: minimapTurns reads (through a counting proxy) the same few rows for a streamed token in a 300- and a 3000-prompt chat, given the transcript's change point; it used to compare every row from the first. A mid-chat edit and an inserted row read the same turns as a fresh read, and a change point for other rows is ignored. A new prompt keeps every earlier tick element, not only the first. - thread-minimap-bottom: with a tick hovered, and with one focused, scrolling the 90-prompt chat from top to bottom keeps every lit tick inside the rail's unfaded area. The rail used to stop following the band while hovered or focused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * fix(webviews): a token reads only the live turn's rows, and a hovered rail keeps the band #24058 review round 4. - minimapTurns took the first changed row by comparing every row from the first, O(rows) per token. VirtualTranscript already finds that point once per snapshot (its arriving rows); it now keeps it (RowsChange) and passes it to the rail, which uses it when it is the step from the rail's last rows to these and compares from the start otherwise. The kept turns are found by binary search. A streamed token now reads the live turn's rows only: 1202 row reads for both a 300- and a 3000-prompt chat in the test (12002 for 3000 before). - A turn whose reply streamed on past its preview (replyPreview stops once full) keeps its object, so the turn list stays the same object too. - The rail followed the band only with no tick hovered or focused, so scrolling the chat under a hovered or focused rail took the band out of it. It follows always now; railScrollTop keeps the list where it is while the whole band shows clear of the fades, so the tick under the pointer moves only when the band would leave. Only a scrub holds the list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * test(webviews): the magnified tick follows a still pointer, and a token copies no turns (red) #24058 review round 5. - thread-minimap-bottom: hover tick 3 with the pointer at y 35, then wheel the chat with the pointer still: once the rail's list moves, the magnified tick must be the one under the pointer at every offset. It stayed tick 3. - thread-minimap-stream: the previous turns behind a counting proxy: a streamed token whose preview is still filling must update the list in place (same array) with about log2(prompts) reads, at 300 and 3000 prompts. It copied every earlier turn (slice). And, through VirtualTranscript: another chat with repeating row ids, the same chat replaced wholesale, then cut, each show their own tick labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * fix(webviews): the magnified tick follows a still pointer, and a token copies no turns #24058 review round 5. - The rail's list follows the band with a tick hovered, but hover is a row index set on pointerenter, and a list that scrolls under a still pointer fires none: the old tick stayed magnified while another sat under the pointer. The list records the pointer's last clientY (pointerover, pointermove; cleared when the pointer leaves the rail), and when it moves the list while hovered, the hovered row and the card's are the row now under that point. - minimapTurns copied every earlier turn (slice) for each snapshot, O(prompts) per token while a reply's preview fills. It now reads only the turns from the changed one, compares them with the previous list's, and updates that list in place when they differ (the previous result is spent once passed in). The live turn is found by binary search: about log2(prompts) reads. ThreadMinimap renders on each snapshot and reads the list then; `ticks` keeps its own identity rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * Test: the rail keeps committed turns, and follows the pointer when focus scrolls it Red at this commit (#24058 review round 6): - a dropped render (the next rows read, never committed) leaves the committed turns as they were; minimapTurns writes the previous list in place, so the committed list takes the dropped prompt; - End or an arrow key that focuses a tick past the list's end, with the pointer still on the rail, magnifies the tick under the pointer; the focus scroll only moves listTop, so the old tick stays magnified. The still-pointer test now puts the list 140 px down, so the rail's own offset counts, and the default test reads a stored cmux.experiments choice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue * Agent chat: the rail leaves committed turns alone and follows the pointer on any list scroll Two findings from #24058 review round 6: - minimapTurns wrote the previous turns list in place, during render. A render React drops then left the committed rail holding rows it never committed. The result now carries a spare list (the one before its turns) and changed turns go there, never into the previous list; a record of how far the spare still holds the committed turns means a streamed token writes the live turn alone, still no copy of the others. ThreadMinimap stores the committed result in a layout effect, not in the memo. - A key that focuses a tick past the list's end scrolls the list under a pointer that did not move, and only listTop followed. The list's scroll handler now remaps the hovered tick from the last pointer position, as the follow effect does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-5eec-7700-94d7-9a3a1f7d889a&host=Air-Blue --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Leo on the nightly (2026-10-21): a column of dashes, one per message, plus a thumb box down the left edge of the agent chat, and a second scroller.
What it was
thread-minimap-layoutexperiment since cmux-next agent pane: the thread minimap spans the transcript's height #22784 (644642e, 2026-10-20). Before that, the compact Codex rail from cmux NEXT: the thread minimap feels like the Codex rail (band tracks every frame, jumps land, 250 ms card) #22228 was on by default. A minimap has been on by default ever since it first landed (225ea27, 2026-10-06), so this is not a re-add after a removal: no PR ever turned it off. cmux-next agent pane: the minimap lights where you are and the message it previews #22879 (33596f5) added the lit band of marks under the view.scrollbar-width: none/::-webkit-scrollbar, which SCROLLBARS-THIN forbids), so that list drew its own scroller next to the transcript's.thread-minimap-layout. Only the experiment default, acmux.experimentslocalStorage key, and the gallery or test harness overrides do.Change
thread-minimap-layoutgets anoffarm, andoffis now the default. The transcript draws no map, no rail and no marks; its one scroller is the shared thin overlay one. The transcript never reserved a gutter for the rail (the rail only used the column's existing inset), so there is nothing to reclaim. Previous and Next Turn and the bookmarked-prompt steps still work.fullandcompactstay selectable for dogfood:cmux.experiments={"thread-minimap-layout":"full"}.overflow: hidden. It still scrolls from script and on focus (it keeps the band in view, a scrub reaches past an edge, Tab), but it never draws a scroller, and it uses none of the properties check-scrollbars forbids.webviews/test/thread-minimap-default-off.test.tsxchecks three things: the shipped arm isoff; a 30-prompt chat with no override has no.acpmux-minimap,.acpmux-scrollmapor[data-tick]in the DOM and keeps the centered column; Previous Turn still steps the transcript. It also checks that the list's CSS never scrolls and that the map uses no accent or Highlight color. The existing rail tests already pin their arm (compact/full); only their comments changed.Testing
bun test test/thread-minimap-default-off.test.tsxfails 3 of 3 on 727fd4e (test only). The default arm is "full", a.acpmux-minimapis in the DOM, and the list CSS isoverflow-y: auto.bun run checkpasses at the head. check-scrollbars has no webviews hits; its remaining hits are Swift base debt.Observed / Not run
agent-pane.thread-minimap, and with this change the default cell shows no rail.Changelog
Webview only; no native UI change.
Agent session: cosmic-gray
🤖 Generated with Claude Code