Skip to content

Session rail: the preview card opens in one commit, and the rail's hover, wheel and click are measured at 3,000 prompts - #28540

Merged
teamleaderleo merged 16 commits into
feat-cmux-nextfrom
lane/ux-rail-3000
Oct 31, 2026
Merged

teamleaderleo merged 16 commits into
feat-cmux-nextfrom
lane/ux-rail-3000

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Leo (rulebook): "when it comes to the left scroll rail thing, i already said we want it ultra fast ultra responsive". Leo, 2026-10-29: "giving it faster scrttoll/hover over ... and being able to scroll it".

Stacked on #28494 (head ca1f3e2) until it lands. Until then the diff against base includes #28494's commits. Rebased onto #28494's merge with #24131 (the right-edge rail): the card places itself on either edge (left: card.left == rail.right; right: card.right == rail.left, in the layer by its own width or pinned to the right edge without one).

Takes back #26134: the card now measures and places itself in one commit (fit() plus a ResizeObserver in PreviewCard) instead of a setCardHeight round-trip; #26134's wrapped-prompt behavior is kept and minimap-card-layout.test.ts still passes.

Root cause: why the rail harness cases timed out at 3,000 prompts

The cause was the harness, not the rail. Three harness faults hit both arms:

  1. The preview server colours its port in its startup banner, so the harness never saw it start and then hung until the 1800 s CI limit. Fixed in Session rail: the card opens on hover, a click lands at once, and wheeling restyles no tick #28494.
  2. A hover never started the probe's clock, because the probe's default inputs are presses, keys and wheels. The hover cases now arm on pointerover.
  3. The flows fixture opens Session Info, which hid the rail on base. The cases now close Session Info first.

With the harness fixed, the rail does no work that grows with the number of prompts. The 3,000 ticks are already virtualized to about 86 drawn. A Chromium trace at 300 and at 3,000 prompts gave the same per-step cost: wheel style time 233 vs 239 ms per 10 steps, and hover 19–36 ms to commit. The remaining cost is per pane DOM, not per prompt:

  • Hover (fixed here): opening the card took 3 React commits and 6 forced layouts, because the card's origin, its height and its reply line budget each went through state.
  • Wheel (not fixed here): any node inserted under the agent pane restyles the whole pane in Chromium, about 1,100–1,165 elements at 4.6 ms. Six pane :has() rules cause this (the composer field menu, the PR dock's :has(> .acpmux-pr-dock) > :not(...), and two label:has(...) rules). With every :has() rule turned off, the same wheel step restyles 14–22 elements. A wheel step over the rail inserts nodes in two places: ticks entering the drawn window, and the open card's reply when the hovered row changes. Those rules are being handled in a separate PR, since they also affect transcript streaming.
  • I also tried snapping the tick window to 32-row chunks, so the mounted ticks change less often. It made no measurable difference (wheel p95 31.4 vs 30.5 ms on base, 12 vs 12 dropped frames), so it is not in this PR.

Change

  • The preview card now positions itself in its layout effect. It reads the rail's origin and its own height and writes its transform before paint. It writes the reply line budget straight onto the reply. Its owner passes the row's center and the view height, not a top it had to measure for. The card now opens in one commit. The existing ResizeObserver keeps the card placed when its content grows; it is disconnected when the content changes or the card goes. This adds no timer or observer, and no work per prompt.
  • The harness gains four cases at 3,000 prompts: chat.rail.hover.tick, chat.rail.hover.card, chat.rail.wheel and chat.rail.click.

Performance

Same harness and same command for every arm: bun scripts/latency/run.ts --engine chromium --runs N --page "chat rail pointer", on the 3,000-prompt resident flows page, Chromium. Within a run, the arms differ only in webviews/src: base is 094dd560 (feat-cmux-next before #28494), "#28494" is f176bb2, "this PR" is the card change on top of it. The card change was measured before the rebase onto #24131; the rebase adds only the right-edge branch of its placement.

metric before after delta harness or run link
hover → card painted p50 / p95, vs feat-cmux-next 289.6 / 292.8 ms 41.3 / 42.7 ms −248 ms (−86%); the 250 ms dwell is #28494's, so −3 ms here latency chat.rail.hover.card, runs=10, run 41136916156
hover → card painted p50 / p95, vs #28494 44.1 / 46.0 ms 41.3 / 42.7 ms −2.8 ms (−6%) same case; #28494 run 41139037067, this PR run 41136916156
React commits, 6 hover cases, vs #28494 18 7 −61% chat.rail.hover.tick, runs=6, run 41139037067
hover → tick lit p50 / p95, vs #28494 45.3 / 46.5 ms 43.6 / 45.6 ms −1.7 ms same run
wheel frame time p50 / p95 / dropped (10 × 100 px, 6 runs), vs feat-cmux-next 16.7 / 30.5 ms / 12 16.7 / 28.8 ms / 14 within noise chat.rail.wheel, base run 41138308077, this PR run 41139037067
wheel React commits (6 runs), vs feat-cmux-next 120 60 −50% same runs
click → prompt landed p50 / p95 84.1 / 115.9 ms 93.4 / 127.2 ms within run-to-run noise (#28494's head arm: 83.6 / 115.2) chat.rail.click, runs 41138308077 / 41139037067

Harness note: when the cases share one page, a case that runs after another sometimes finds no rail ("rail not ready ... rail:false"), so some arms lack a case in a given run. The table uses only complete cases and links each to its run. The wheel and click costs are the pane :has() restyle described above, which is being fixed separately.

Stills

The card's placement code changed, so here are gallery stills with the hover card open on both edges. They were captured via cmux-ci in one job (run 41144770735), Chromium. Before is #28494's head ca1f3e26; after is this PR's head 444a364e. Left edge: agent-pane.thread-minimap / card-markdown. Right edge (#24131): agent-pane.thread-minimap-narrow / right-hovered. All eight before/after pairs are byte-identical (same PNG hash): the card lands on the same pixels on both edges, with card.left == rail.right on the left and card.right == rail.left on the right.

Before After
Left, dark left before dark left after dark
Left, light left before light left after light
Right, dark right before dark right after dark
Right, light right before light right after light

Viewed after image: yes

Testing

  • Rail jsdom suites (*minimap*, hover-intent-cards, minimap-*, thread-scroll-map*, HarnessStatusCard.dom): 134 pass, 0 fail.
  • Card browser suites after the rebase (thread-minimap-feel, thread-minimap-motion, thread-minimap-side (right edge), thread-minimap-left-rail-stays, minimap-card-layout, thread-minimap-card-glass, thread-minimap-tick-states), Chromium and WebKit: 54 pass, 0 fail (run 41142877669).
  • scripts/web-lint.sh: passes.

Changelog

Changed: The chat rail's preview card opens with less work per hover.

Agent session: agent session

🤖 Generated with Claude Code

teamleaderleo and others added 9 commits October 30, 2026 19:35
Three cases on the 3,000-prompt resident chat the rail drag uses: the hover
lighting a tick, the hover opening its preview card, and a wheel over the
rail scrolling its own list with the pointer resting on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
The compact rail's card waited 250 ms (Codex's delayOpen) before it opened.
design/session-rail.md keeps the card's instant-open hover behavior, and Leo
asked for faster hover (2026-10-29: "faster scrttoll/hover over"). The card
now opens in the hover's own commit (RAIL_CARD_DELAY = 0), then follows the
pointer from row to row as before. The opt-in full-height map keeps its
250 ms (CARD_DELAY).

Red on base, green here: bun test test/hover-intent-cards.test.tsx
test/thread-minimap-rail.test.tsx (2 fail on base, 20 pass here).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
… costs no formatter

The list carried --acpmux-minimap-first (its first row in view), which every
tick's entrance stagger read. A wheel that moved the list a row changed it, and
with it the computed style of every drawn tick, on every scrolled frame. Each
tick now keeps its own place in the view as it mounted (--acpmux-minimap-place),
which is what its once-per-mount entrance needs; the rows inherit nothing that
moves with the scroll. Tick ignores that prop after mount, so a scroll renders
no tick either.

The preview card built an Intl.DateTimeFormat on every render, and a hover that
follows the pointer (or the wheel under it) renders it once per row. One
formatter now serves every card.

No timer, observer or animation loop is added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…once

Leo, 2026-10-29: "giving it faster scrttoll/hover over". The rail's
preview card no longer waits 250 ms: it opens on the pointer's commit and
follows it row to row. A click on a tick (or on a mark or the track of the
full map) lands the prompt in the frame of the click instead of gliding
for 200-400 ms; bookmark jumps and Previous/Next Turn steps keep their glide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2a08596)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 269e4db)
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…the run ends

The preview server colors its port in the banner ("127.0.0.1:" ESC[1m port
ESC[22m), so the plain match never saw it: every run failed with "server
startup timed out", and the process then stayed up on the server's
surviving child's pipes until the CI step's 30-minute limit. Match the
output so far with SGR escapes dropped (as #27858 does), and exit with the
run's code.

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?id=aid-63b9964940c20354b639
… sits beside the text

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…l-fast

# Conflicts:
#	webviews/src/agent-session/acpmux/threadMinimap/ThreadMinimap.tsx
#	webviews/src/agent-session/acpmux/threadMinimap/threadMinimap.css
They time out on the flows fixture's rail (the selector waits for a tick
the fixture does not leave hoverable), so they would only add red cases to
the scoreboard. The server banner fix stays: with it the harness starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 30, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
main.DeveloperIdAppTests.test_download_prints_the_active_profile_only/
test_download_prints_the_active_profile_only
View Logs
stubs: /System/Library/Frameworks/Security.framework/Versions/A/Security has no stub/
Security has no stub
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_CFRetain']/
CoreFoundation, missing 1: ['_CFRetain']
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_log2']/
CoreFoundation, missing 1: ['_log2']
View Logs
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)/
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)
View Logs
test_the_ten_first_party_gpl_crates_are_named_exactly (main.AppCrateLicenseGate.tes
t_the_ten_first_party_gpl_crates_are_named_exactly)/
test_the_ten_first_party_gpl_crates_are_named_exactly (main.AppCrateLicenseGate.tes
t_the_ten_first_party_gpl_crates_are_named_exactly)
View Logs

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

@teamleaderleo teamleaderleo changed the title Session rail at 3,000 prompts: measure hover, wheel and click (stacked on #28494) Session rail at 3,000 prompts: the card opens in one commit, and a scroll remounts ticks once per 32 rows Oct 30, 2026
teamleaderleo and others added 4 commits October 30, 2026 22:56
Keeps #24131's rail side (left or right edge, the mirrored rail and card) with
this branch's changes: the card opens with the hover (CARD_DELAY 0), a click
lands at once (ScrollMap's seek takes no smooth flag), each tick keeps its own
mounted place beside #26635's data-entering, and the harness banner fix. Adds
a WebKit test that Next Turn still glides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
At 3,000 prompts (and at 300: it does not grow with the chat) a hover took three
React commits and six forced layouts to show its card: the card measured the
rail's origin into state, its owner measured the card's height into state, and
the reply's line budget went through state too, each a commit of its own.

The card now places itself in its layout effect: it reads the rail's origin
and its own height and writes its transform before paint, and writes the
reply's line budget straight onto the reply. Its owner hands it the row's
center and the view's height instead of a top it had to measure for. The
ResizeObserver it already had keeps the place right when the content grows;
it stops with the card's content or the card going (cleanup disconnects it).

No new timer or observer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…rompts

A hover arms the probe on pointerover (its default inputs are presses, keys
and wheels, so a hover never started the clock and every run timed out at
5 s). A case that cannot start names what the rail showed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…m measures the rail beside the text

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
@teamleaderleo teamleaderleo changed the title Session rail at 3,000 prompts: the card opens in one commit, and a scroll remounts ticks once per 32 rows Session rail: the preview card opens in one commit, and the rail's hover, wheel and click are measured at 3,000 prompts Oct 30, 2026
@teamleaderleo
teamleaderleo marked this pull request as ready for review October 30, 2026 23:23
@cursor

cursor Bot commented Oct 30, 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.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review e09e164: 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=pr28540 -f ref=lane/ux-rail-3000 -f platform=macos -f backend_mode=local

Stale: these images are of 444a364; the head is now e09e164 and a new capture is running.

Gallery before/after for 444a364: 16 states rendered, 20 images differ, 1337 more touched states not rendered. Images are in the run's artifact, not on this public PR.

gh run download 41144144432 -R manaflow-ai/cmux -n gallery-advisory-28540-444a364e838d634cc909015d4da6d8bf6d07553d (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.thread-minimap/card-markdown Apple System Colors Light changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-markdown Apple System Colors changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-failure Apple System Colors Light changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-failure Apple System Colors changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-metadata Apple System Colors Light changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-metadata Apple System Colors changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-prompt-fixed-2 Apple System Colors Light changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-prompt-fixed-2 Apple System Colors changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-prompt-fixed-3 Apple System Colors Light changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-prompt-fixed-3 Apple System Colors changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-prompt-growing Apple System Colors Light changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-prompt-growing Apple System Colors changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/rest Apple System Colors Light unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap/rest Apple System Colors unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap/scrubbing Apple System Colors Light changed 0% own PreviewCard.tsx
agent-pane.thread-minimap/scrubbing Apple System Colors changed 0% own PreviewCard.tsx
agent-pane.thread-minimap-height/eighteen-prompts Apple System Colors Light changed 1.67% own ThreadMinimap.tsx
agent-pane.thread-minimap-height/eighteen-prompts Apple System Colors changed 0.678% own ThreadMinimap.tsx
agent-pane.thread-minimap-height/seventy-prompts Apple System Colors Light unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-height/seventy-prompts Apple System Colors unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-height/tall-pane-caps Apple System Colors Light unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-height/tall-pane-caps Apple System Colors unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-lit/mid-thread Apple System Colors Light unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-lit/mid-thread Apple System Colors unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-lit/at-the-end Apple System Colors Light unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-lit/at-the-end Apple System Colors unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-lit/hovered Apple System Colors Light changed 0.869% own ThreadMinimap.tsx
agent-pane.thread-minimap-lit/hovered Apple System Colors changed 0.67% own ThreadMinimap.tsx
agent-pane.thread-minimap-mark/states Apple System Colors Light unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-mark/states Apple System Colors unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-mark/bookmark-hovered Apple System Colors Light changed 2.51% own ThreadMinimap.tsx
agent-pane.thread-minimap-mark/bookmark-hovered Apple System Colors changed 1.13% own ThreadMinimap.tsx

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

Shipped in:

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, router-review): BLOCK at e09e164

BLOCK

Visual verdict: PASS — all four after screenshots were inspected; card placement looks correct on both edges and themes, and hashes match the before images.

  1. webviews/src/agent-session/acpmux/threadMinimap/PreviewCard.tsx:174-179 — Reply-only cards (used by ScrollMap for assistant marks) return after removing the prompt card’s inline reply clamp, without calling place() or installing a ResizeObserver. When hover swaps from a prompt card to a reply card, the component is reused: placement measures the stale clamped height, then cleanup changes the height while leaving the old transform. The card can therefore be vertically misplaced, and later width/content-driven size changes are not repositioned.

@teamleaderleo teamleaderleo removed the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
teamleaderleo and others added 2 commits October 31, 2026 00:34
…, reused or resized, on both edges (red)

A prompt's card then a reply's in the same card component (the full map's
assistant marks), and a reply's card whose content grows without a render,
in Chromium and WebKit, on the left and the right rail edge. Red at this
commit: the reply-only path returned before placing the card or observing
its size (#28540 review).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
A reply's card (no prompt) returned from the budget effect after clearing a
prompt card's clamp, without placing the card or observing its size. A card
reused from a prompt's to a reply's was then placed from its old height by
the every-commit effect, which runs first, and a later resize never moved it.

One fit path now serves every kind: it sets or clears the reply's line
budget, then places the card from its height after that change. The layout
effect runs it at open and on every content or kind change, and the
ResizeObserver runs it on every resize; the observer is disconnected in the
cleanup when the content or kind changes or the card goes. Still one commit
to open; no new timer or observer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
@cursor

cursor Bot commented Oct 31, 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 (codex gpt-6-astra, router-review): SHIP at 1f32556

Delta review from e09e164 (prompt and reply cards share one fit() path; ResizeObserver cleanup). No findings. Visual verdict not part of this range review.

@cmux-bot cmux-bot Bot removed the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@teamleaderleo
teamleaderleo merged commit ba52d0c into feat-cmux-next Oct 31, 2026
179 of 188 checks passed
@teamleaderleo
teamleaderleo deleted the lane/ux-rail-3000 branch October 31, 2026 01:48

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 1f325569 Deployed Oct 31, 2026 by teamleaderleo via cmux-tui tree preflight #86063
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant