Skip to content

Agent chat: no thread minimap by default (the column of marks down the left edge) - #23735

Merged
teamleaderleo merged 3 commits into
feat-cmux-nextfrom
glass-regressions/focus-ring-scroller
Oct 21, 2026
Merged

teamleaderleo merged 3 commits into
feat-cmux-nextfrom
glass-regressions/focus-ring-scroller

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

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

Change

  • thread-minimap-layout gets an off arm, and off is 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. full and compact stay selectable for dogfood: cmux.experiments = {"thread-minimap-layout":"full"}.
  • The compact rail's list is 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.
  • New webviews/test/thread-minimap-default-off.test.tsx checks three things: the shipped arm is off; a 30-prompt chat with no override has no .acpmux-minimap, .acpmux-scrollmap or [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

  • Red: bun test test/thread-minimap-default-off.test.tsx fails 3 of 3 on 727fd4e (test only). The default arm is "full", a .acpmux-minimap is in the DOM, and the list CSS is overflow-y: auto.
  • Green: at the head, that test plus thread-minimap-rail, thread-scroll-map-rail, thread-bookmarks, thread-turn-keys, thread-minimap-current and hover-intent-cards: 28 pass, 0 fail.
  • bun run check passes at the head. check-scrollbars has no webviews hits; its remaining hits are Swift base debt.

Observed / Not run

Changelog

  • Changed: the agent chat no longer shows the thread minimap (the column of marks down the transcript's left edge) by default.

Webview only; no native UI change.

Agent session: cosmic-gray

🤖 Generated with Claude Code

teamleaderleo and others added 2 commits October 21, 2026 13:49
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
@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 0af436b: 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=pr23735 -f ref=glass-regressions/focus-ring-scroller -f platform=macos -f backend_mode=local

Passes: CI passes on 0af436b86e.

CI passes on 0af436b86e (run 39681951024 attempt 1).

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

web and size checks: green

CI fast guards passes on 0518bf1148 (run). This covers only the fast guards, not CI: CI's result is ci-status.

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

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

gh run download 39681950899 -R manaflow-ai/cmux -n gallery-advisory-23735-0af436b86e2d6d77ca782d6fdceac009f51b8c6d (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.thread-minimap/rest Apple System Colors changed 11.5% own ThreadMinimap.tsx
agent-pane.thread-minimap/rest Apple System Colors Light changed 11.5% own ThreadMinimap.tsx
agent-pane.thread-minimap/scrubbing Apple System Colors Light changed 14.4% own ThreadMinimap.tsx
agent-pane.thread-minimap/scrubbing Apple System Colors changed 14.4% own ThreadMinimap.tsx
agent-pane.thread-minimap/seventy-prompts Apple System Colors Light changed 13.3% own ThreadMinimap.tsx
agent-pane.thread-minimap/seventy-prompts Apple System Colors changed 12.9% own ThreadMinimap.tsx
agent-pane.thread-minimap/long-chat Apple System Colors Light changed 0.938% own ThreadMinimap.tsx
agent-pane.thread-minimap/long-chat Apple System Colors changed 0.93% own ThreadMinimap.tsx
agent-pane.thread-minimap/bookmarked Apple System Colors Light changed 0.901% own ThreadMinimap.tsx
agent-pane.thread-minimap/bookmarked Apple System Colors changed 0.892% own ThreadMinimap.tsx
agent-pane.chat-width/standard Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.chat-width/standard Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.chat-width/wide Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.chat-width/wide Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.chat-width/wide-blocks Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.chat-width/wide-blocks Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.composer-floats/scrolled-to-end Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.composer-floats/scrolled-to-end Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.composer-floats/composer-grown Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.composer-floats/composer-grown Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/new-chat Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/new-chat Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/after-turn Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/after-turn Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/working Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/working Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/typing-in-dock Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/typing-in-dock Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/long-title Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.docked-chat/long-title Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.orchestration-lab/local-queue Apple System Colors Light unchanged 0% imports ThreadMinimap.tsx (1 hops)
agent-pane.orchestration-lab/local-queue Apple System Colors unchanged 0% imports ThreadMinimap.tsx (1 hops)

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by glass-regressions on cosmic-gray · session

@teamleaderleo teamleaderleo added the risk:low Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent, correctness first): SHIP at 6334120b6e43e44f3cd4cb17929ba386caf52a16

Reviewed 0518bf1 in full. 6334120b only reformats it (oxfmt: one wrapped description string in layout.experiment.ts and one quote style in the new test; git diff 0518bf11 6334120b has no other change).

What I checked:

  • Experiment wiring: off is a valid arm and the new default. validateExperiments still holds (3 arms, default is an arm, kebab ids), and registry.ts needs no change. ThreadMinimap reads the arm once per pane: full returns ScrollMap; shown now requires arm === "compact", so off renders null. The hooks still run in that case, and the scroll and placed handlers return early on !shown. The Previous/Next Turn and bookmarked-prompt listeners don't depend on the arm. Prompt bookmarks can still be set from the message actions (useRowMark in MessageActions.tsx), so the bookmark steps stay useful.
  • Stored arms: only globalThis.cmuxExperiments (gallery frame and tests) and the cmux.experiments debug key choose an arm. Nothing in Swift, Settings or the presets sets thread-minimap-layout. A dogfooder who stored full or compact keeps it; everyone else gets off.
  • The new test really asserts absence. It uses the same jsdom harness as thread-scroll-map-rail.test.tsx (600 px viewport, 1040 px pane), where the full map renders. With the old full default, the 30-prompt mount would therefore have drawn .acpmux-scrollmap, and the toBeNull checks would fail. The Previous Turn assertion covers the off arm. Every pinned rail suite (compact or full) and the new file passed in CI on 0518bf1, across react-apps-check shards 2, 3 and 4.
  • CSS: the compact list moves from overflow-y: auto with overscroll-behavior: contain to overflow: hidden. Script scrolling, focus scroll-into-view and the fade attributes still work. The only behavior change is that a wheel over the compact rail now scrolls the transcript instead of the list, and that arm is dogfood-only. No accent or Highlight colors in threadMinimap.css. The transcript's own scroller is unchanged.
  • CI on 0518bf1: fast gates failed only on the oxfmt formatting of the PR's two files, which 6334120b fixes. react-apps-check (1) failed in scripts/tests/test_pr_scoped_webviews_checks.py with a tempdir cleanup OSError: Directory not empty: 'objects', a flake unrelated to this diff. Its webview tests were 190 files, 0 failed.

Non-blocking nits (fine as follow-ups):

  • In the gallery entry's compare script, step "hover prompt 8" (ctx.hover('[data-tick="8"]')) throws in the new off cell. The default "rest" variant also now shows an empty left edge. Consider guarding that step for off, or noting it.
  • The ScrollMap.tsx header comment still calls the full map "the experiment's default arm".
  • expect(inset * 2 + width).toBe(PANE) is true by construction of transcriptColumn, so it asserts nothing about the DOM.
  • The solid blue pane focus ring isn't touched here. The PR body says it's a separate finding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 21, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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). git diff 0518bf11 0af436b8 only formats two files with oxfmt: it wraps the experiment's description string in layout.experiment.ts and switches one quote style in thread-minimap-default-off.test.tsx. Its tree is identical to 6334120b, the commit my earlier receipt named, so that review stands: the off default arm is wired correctly, the absence test would fail under the old full default, and the compact list draws no scroller. The nits from that comment are still optional follow-ups.

@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)/
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)
View Logs

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

@teamleaderleo
teamleaderleo merged commit e0bca24 into feat-cmux-next Oct 21, 2026
@teamleaderleo
teamleaderleo deleted the glass-regressions/focus-ring-scroller branch October 21, 2026 21:55
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant