Skip to content

Prompt rail: a hover sweep forces fewer layouts; the card's blur follows a move in one measure - #34711

Merged
teamleaderleo merged 13 commits into
feat-cmux-nextfrom
lane/rail-hover-lag
Nov 4, 2026
Merged

teamleaderleo merged 13 commits into
feat-cmux-nextfrom
lane/rail-hover-lag

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Leo, 2026-11-04:

hovering across the session rail gets a bit laggy and the missing chunks of the wave bother me :( it needs to get faster

likewise hover previews kinda lag across me hovering over the sidebar stuff :( idk if more things need to be at higher priority or whatnot

Leo-ask: ask-0eea9d95e5

The rail's height and the wave's gaps are fixed in #34666. This PR cuts the work done each time the hovered row changes during a sweep, when the row changes about every 40 ms:

  • Preview card (PreviewCard.tsx): it reads before it writes, and it remembers each row's fit. Placing and fitting the card used to alternate reads and writes, which forced 4 layouts per row. Now there is one read pass and then one write. A row seen before writes its remembered lines and line height and reads once. That fit is reused only at the same card width and the same type (the prompt's and the reply's computed line heights). So a text size or density change, even while the card showed another row, measures fresh, as do a resize the card's observer catches (a font, an image) and a width change.
  • See-through blur (seeThroughBlur.ts): the card moves through its inline transform. Each move used to restart the blur's follow loop, which ran at least 4 frames of re-measuring every covered element and rewriting the SVG hole filter on the whole transcript. A change to the surface's own style now measures once on the next frame. Transitions, animations and resizes still run the full loop. The Summary card and Session Info share this path.
  • Tick labels (ThreadMinimap.tsx TickList): each drawn row keeps its spoken label while its position, row count and bookmark state don't change. A hover no longer rebuilds about 115 labels to change about 8.

Performance

metric before after delta harness
forced layouts per new hovered row 4 3 -1 test/thread-rail-hover-reads.test.tsx (jsdom dirty/read model)
forced layouts per revisited row 4–5 1 -3 same
blur measures per card transform move 4 1 -3 test/see-through-blur-follow.test.ts
main-thread busy ms per frame during a sweep (p50/p95) pending pending bench/thread-rail-hover.bench.ts (#34658); cmux-ci is down until #34673 lands, numbers follow then

No new timers or observers run on their own. The blur's new style observer is the existing one, split out: it disconnects in the same cleanup, and its single measure is one rAF that is cancelled on teardown. The card's fit cache is a module-level WeakMap keyed by each turn's reply blocks, then by the prompt text: an entry goes when its turn's blocks do, and it is shared by every card that shows that turn.

Tests

Red first, each before its fix:

  • bun test test/thread-rail-hover-reads.test.tsx: fails on base (4 forced layouts per row), passes on head (3 new, 1 revisited).
  • bun test test/see-through-blur-follow.test.ts: fails on base ([4,4,4,4] measures per move), passes on head ([1,1,1,1]). The hole still follows the card, and a transition still follows for 3 frames.
  • bun test test/thread-rail-hover-reads.test.tsx ("measures again once the text size changed"): fails at 449d336 (the old 20px budget kept), passes on head (31px).
  • test/thread-minimap-rail.test.tsx: the label follows a bookmark on and off.
  • Every jsdom suite in thread-minimap*, thread-rail*, minimap*, chat-minimap*, thread-scroll-map*, glass, Summary and hover-cards-glass passes.

Observed / Not run

  • Observed: the jsdom suites above; vp fmt, vp lint and tsc are clean on the changed files.
  • Not run locally: the browser suites (minimap-card-layout, see-through-blur, thread-minimap-motion, session-info-glass); this PR's webview CI runs them. The frame bench is pending as above.
  • Skipped on purpose: caching the list's rect for pointer moves. A test showed those reads hit a clean layout, and a cache could go stale.

Follow-ups

Review follow-ups: #35239.

Changelog

  • Changed: Hovering across the agent chat's prompt rail does less work per row, so the preview card keeps up with the pointer.

risk:normal

🤖 Generated with Claude Code

Agent session: agent session

teamleaderleo and others added 5 commits November 4, 2026 03:16
…over sweep (red)

jsdom has no layout, so the test models it: a write dirties the page and
the first rail or card layout read after it is one forced layout. On base
each row a sweep reaches forces 4 layouts, and a row it comes back to as
many. The test asks for at most 3 on a new row and 1 on a row seen before.

Leo-ask: ask-0eea9d95e5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-6db3c1673d18051baad7
…ach row's fit

Leo (2026-11-04): "hovering across the session rail gets a bit laggy ... it
needs to get faster". Each row a sweep reaches placed the card after the
commit (a layout), then fitted it: a computed style and the prompt's rect
after the place's write, the reply's rect after lifting its clamp, and the
place again after the budget, four forced layouts per row.

Now a content change fits once and the fit places the card (no place
before it), the place's own reads join the fit's last pass, and the fit
remembers its budget by the reply's blocks and the prompt: a row seen
before at the same card width writes that budget and reads once. A
resize the card's observer sees measures again and replaces what the row
remembers. thread-rail-hover-reads: 4 -> 3 forced layouts on a new row,
4 -> 1 on a row seen before.

Leo-ask: ask-0eea9d95e5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-6db3c1673d18051baad7
…move costs (red)

The rail's preview card moves to the next row by its inline transform.
On base each move restarts the follow loop: 4 measures (the move and 3
unchanged frames) per row, so a sweep at a row every 40 ms never lets the
loop stop. The test asks for one measure per move, with the hole moved.

Leo-ask: ask-0eea9d95e5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-6db3c1673d18051baad7
… follow loop

Leo (2026-11-04): "hover previews kinda lag across me hovering over the
sidebar stuff :(". The rail's card moving to the next row by its inline
transform restarted the loop that follows fades and resizes until three
unchanged frames; a sweep crosses a row every 40 ms, so through it every
frame re-read the card's rect and style and the transcript's rect. A
style change alone now measures on the next frame, once: a transition is
still followed through transitionrun and a resize through the
ResizeObserver, which keep the loop. see-through-blur-follow: 4 -> 1
measures per move.

Leo-ask: ask-0eea9d95e5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-6db3c1673d18051baad7
Every row the pointer crosses renders the tick list, and each drawn row
(about 115 on a tall rail) built its aria label again through t(), for
the few rows the hover changes. A row whose place, count and mark are
unchanged now keeps its label; a language change starts over. The rail
test checks the label follows a bookmark on and off.

Leo-ask: ask-0eea9d95e5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-6db3c1673d18051baad7
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Nov 4, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at 586ef38

Visual verdict: unchanged by this diff (no rendering change; work per row only).
Checked the fit cache against streaming replies, pending prompts, bookmarks, same-text chats, resizes; the single blur measure against transitions, animations, opacity/visibility, moves mid-loop and cleanup; the label cache against index shifts, folds and language changes. Tests pass.

Follow-ups:

  • [follow-up] PreviewCard fit cache hit checks only width: a text-size change leaves a max-width card on a revisited row with the old line budget (add line height to the hit check or clear on text-size change)

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 449d336: 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=pr34711 -f ref=lane/rail-hover-lag -f platform=macos -f backend_mode=local

Gallery before/after for 449d336: 16 states rendered, 7 images differ, 539 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41788253642 -R manaflow-ai/cmux -n gallery-advisory-34711-449d336f821829bc258b1920324e27c3c2f1acd7 (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 unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-failure Apple System Colors unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-metadata Apple System Colors Light unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-metadata Apple System Colors unchanged 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 changed 0.0002% own PreviewCard.tsx
agent-pane.thread-minimap/card-prompt-fixed-3 Apple System Colors Light unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-prompt-growing Apple System Colors Light unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap/card-prompt-growing Apple System Colors unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap/first-paint Apple System Colors Light unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap/first-paint Apple System Colors unchanged 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-foot/resting Apple System Colors Light unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap-foot/resting Apple System Colors unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap-foot/bottom-hovered Apple System Colors Light changed 0% own PreviewCard.tsx
agent-pane.thread-minimap-foot/bottom-hovered Apple System Colors unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap-foot/tall-draft Apple System Colors Light unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap-foot/tall-draft Apple System Colors unchanged 0% own PreviewCard.tsx
agent-pane.thread-minimap-height/eighteen-prompts Apple System Colors Light changed 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-height/eighteen-prompts Apple System Colors unchanged 0% 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-incoming/states Apple System Colors Light unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-incoming/states Apple System Colors unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-incoming/incoming-hovered Apple System Colors Light unchanged 0% own ThreadMinimap.tsx
agent-pane.thread-minimap-incoming/incoming-hovered Apple System Colors unchanged 0% own ThreadMinimap.tsx

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

Passes: CI passes on 59088681ac.

CI passes on 59088681ac (run 41805332941 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

Shipped in:

teamleaderleo and others added 4 commits November 4, 2026 06:44
…over-lag

# Conflicts:
#	webviews/src/agent-session/acpmux/threadMinimap/PreviewCard.tsx
…ommit in a row's turn

The scroll pacer runs on the page's clock and its frame sometimes lands
inside a row's turn, rendering the card again with the same props and
one more place() read. The assertion now takes the median per sweep
(1 on a revisited row; base: 4 on every row) and allows one extra read
per row, so it no longer flakes on a loaded host.

Leo-ask: ask-0eea9d95e5

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

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at 449d336

Visual verdict: unchanged by this diff.
Delta review of the base merge: the PreviewCard conflict keeps #34293 exactly (reply line height measured and budgeted from the reply, cached the same), and the hover-reads test still fails on base (4 per row).

Follow-ups:

  • [follow-up] make the transcript scroll pacer deterministic in thread-rail-hover-reads.test.tsx and restore the strict per-row maximums

@blacksmith-sh

blacksmith-sh Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Found 2 test failures on Blacksmith runners:

Failures

Test View Logs
nightly cmux-tui resolve/nightly cmux-tui resolve View Logs
the short nightly decide gate must use the pooled Linux route with a Blacksmith fallbac
k/
the short nightly decide gate must use the pooled Linux route with a Blacksmith fallbac
k
View Logs

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

teamleaderleo and others added 4 commits November 4, 2026 09:07
The fit cache was keyed by content and width. A text size or density change
while the card showed another row kept the old line budget. The reuse path now
reads the prompt's and reply's line heights in its single read pass.
Closes #34718.

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

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at 5908868

Delta since the SHIP at 449d336: a remembered card fit is reused only at the same prompt and reply line heights (closes #34718), and feat-cmux-next is merged in. The reuse path still does one read pass after its budget write. bun test test/thread-rail-hover-reads.test.tsx test/thread-minimap test/thread-rail passes; the new same-width text-size test fails at 449d336 (20px kept) and passes here.

Follow-ups:

  • F1 [follow-up] pass the reuse path's typeOf result to the fresh path, so a mismatch reads styles once instead of twice.

@teamleaderleo teamleaderleo added the leo-named-fix An obvious fix to something Leo named: lands on push, review runs in parallel label Nov 4, 2026
@teamleaderleo
teamleaderleo merged commit 756c019 into feat-cmux-next Nov 4, 2026
178 of 184 checks passed
@teamleaderleo
teamleaderleo deleted the lane/rail-hover-lag branch November 4, 2026 09:19
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Finding outcome: comment-6627112810/F1 filed-follow-up #35239

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 59088681 Deployed Nov 4, 2026 by teamleaderleo via wait for same-key tree publisher #110872
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

leo-named-fix An obvious fix to something Leo named: lands on push, review runs in parallel risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant