Session rail: the card opens on hover, a click lands at once, and wheeling restyles no tick
Leo, 2026-10-29:
"but how about fixing up the left side scroll rail thing (moving it to the left, giving it faster scrttoll/hover over) and being able to scroll it? and the various other noticeable ui or perf things"
Relayed later: "on the left, faster scroll and hover, scrollable". From the rulebook: "when it comes to the left scroll rail thing, i already said we want it ultra fast ultra responsive". Spec: leo-workspace design/session-rail.md. It settles the card's "instant-open hover behavior" and the left pin, and this PR changes neither.
This PR supersedes #27858's card-dwell change and carries its click change too. #27858 is "Agent chat rail: the preview card opens at once, and a click lands at once". Its two product commits are cherry-picked here with their authorship kept (2a085961e28 and 269e4db52da, -x). It composes with #26635 (merged): ticks still get data-entering, and this PR only changes where the entrance stagger reads its row place from.
What was checked on feat-cmux-next
- On the left: yes, in every placement. A right-side option exists only in the open #24131.
- Scrollable: yes, since #26520. An overflowing list scrolls itself, its scrollbar is hidden, it doesn't chain to the transcript, and its wheel listener is passive and keeps the wheel on the rail.
- Scroll: the listeners are passive, and the band re-renders only when it moves. Shortfall: the list carried
--acpmux-minimap-first, its first row in view, and every tick's entrance stagger read it. A wheel that moved the list by a row changed that variable, so every drawn tick's style was recomputed on every scrolled frame. - Hover and click: the tick magnifies at once (a 120 ms spring on transform, as the spec says). Shortfalls: the preview card waited 250 ms; a click glided the transcript for 200-400 ms before landing; and the card built a new
Intl.DateTimeFormaton every render, which happens once for every row the pointer passes.
Changes
- Hover: the card opens in the same commit as the hover (
CARD_DELAY = 0) and still follows the pointer from row to row. From #27858. - Click: a click on a tick lands the prompt within the click's own task, with no glide. The full map's mark and track clicks seek at once too. Bookmark jumps and Previous/Next Turn keep their glide. From #27858.
- Scroll: each tick keeps its own row place from when it mounted (
--acpmux-minimap-place). The list's rows inherit nothing that changes with scroll.Tickignoresplaceandenteringafter mount, so a scroll renders no tick and restyles none. - The card uses one shared time formatter.
- Latency harness: the preview server colours its port in the startup banner, and the plain string match never found it. Every harness run on CI failed with "server startup timed out" and then hung until the step's 30-minute limit. The banner is now matched with its colour escapes removed, and the process exits when the run ends.
No timer, observer or animation loop is added. The change does no new O(rows) work per scroll or hover.
Performance
| metric | before | after | delta | harness or run link |
|---|---|---|---|---|
| rail hover → card committed (dwell) | 250 ms | 0 ms | −250 ms | model.ts CARD_DELAY; fake-clock tests hover-intent-cards, thread-minimap-rail (red on base, green here) |
| rail click → prompt landed (glide) | 200–400 ms (jumpDuration) | 0 ms | −200 to −400 ms | thread-minimap-motion (WebKit): every frame after the click already shows the landing |
| ticks restyled per wheel step that moves the list a row | every drawn tick (rows in view + 24 overscan) | 0 | −100% | code count: --acpmux-minimap-first on the list → per-tick --acpmux-minimap-place set at mount |
| latency harness start on CI | timed out every run, then hung 1800 s | starts and runs its cases | fixed | runs 41117089640 (hung), 41129655222 (runs) |
The latency harness does not have a working rail hover or wheel case yet. The three cases I drafted timed out on the flows fixture's rail in both arms, so they are left out. The rows above come from code constants, fake-clock tests and a count, not from the latency harness.
Stills
Gallery agent-pane.thread-minimap / card-markdown, Chromium, the hover card open. This is a capture via cmux-ci at the merge-base product source and at the head, in one job (run 41126799888). The rail and card look identical; what changes is when the card appears.
| Before | After | |
|---|---|---|
| Dark | ![]() | ![]() |
| Light | ![]() | ![]() |
Viewed after image: yes
Visual verdict: SHIP (ux-owner; checked again by claude-sr2-coord on the after stills). Dark and light: equal short rail ticks at the pane's left edge, the hovered tick grown, and the preview card flush right of the rail, readable, with nothing clipped or overlapping. Same look as before; only the timing changes.
Testing
- Red on base, green here:
bun test test/hover-intent-cards.test.tsx test/thread-minimap-rail.test.tsx. Base: 2 fail (the card is still closed when the hover commits). Head: pass. - All rail jsdom suites after merging #26635 (
test/*minimap*.test.tsx,hover-intent-cards,minimap-card-*,thread-scroll-map*): 107 pass, 0 fail. - Browser suites at f176bb2 (
thread-minimap-feelChromium,thread-minimap-motionWebKit,thread-minimap-tick-states,thread-rail-scroll-entrancefrom #26635): 16 pass, 0 fail (run 41130984757). scripts/web-lint.sh: passes.
Observed / Not run
- Ran: the tests above, web-lint, and the gallery capture.
- Not run: WebKit gallery stills, and a live tagged build.
Changelog
Changed: The chat rail's preview card opens as soon as you hover a tick, a click on a tick lands at once, and scrolling the rail does less work per frame.
Agent session: agent session
🤖 Generated with Claude Code



