Skip to content

Agent chat: a mouse wheel's notch glides the transcript instead of jumping - #24327

Closed
teamleaderleo wants to merge 5 commits into
feat-cmux-nextfrom
leo/transcript-mouse-wheel-glide
Closed

teamleaderleo wants to merge 5 commits into
feat-cmux-nextfrom
leo/transcript-mouse-wheel-glide

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Leo-ask: 102e9a090f
Leo-ask: 6e88488b1c (build half)

Leo: "smoother scrolling in webkit: i mean with a mouse instead of just trackpad. i have mos and lawrence doesn't, tho we'd presumably be doing a bit of our handrolled mos-ish thing"

Context, from his message just before it: "...the former [chromium] has smoother scroll btu the latter [webkit] has glass... but we should be able to do smoother scroll on webview too right?"

What changed

In the agent chat transcript, a notched mouse wheel now glides instead of jumping. Each notch eases the view toward an accumulated target over the motion scale's standard step, 180 ms (MOTION_MS.standard, ease-out cubic). A notch that comes in mid-glide adds its distance to the target. Trackpad and momentum scrolling are untouched, so the engine still handles them.

Timers and cleanup

One requestAnimationFrame loop per transcript, running only while a glide is in flight.

  • Stop conditions:
    • the glide covers its distance (180 ms after the last notch);
    • the view reaches the top or bottom end;
    • any wheel event the glide doesn't take arrives: a trackpad's or momentum's, a modified (shift, alt, meta, ctrl) or sideways one, one over a part that scrolls on its own (code, a long tool output), or one while Reduce Motion or the Debug toggle is off;
    • the reader presses, touches or types anywhere in the page (a drag on the scrollbar, a keyboard scroll, a click on Jump to latest or the minimap; a window capture listener does this, added only while a glide runs);
    • Jump to latest is used;
    • the transcript switches to another chat (VirtualTranscript is kept across chats, so the switch stops the glide);
    • the pane is hidden mid-glide (the scroller has no height).
  • Cleanup: unmount calls remove(), which cancels the frame, removes the window listeners and removes the scroller's wheel and scroll listeners.
  • No new work per token or per row.

Tests

  • webviews/test/agent-pane-wheel-glide.test.ts, run in Chromium and Playwright WebKit. Playwright's WebKit wheel is always a precise (trackpad) scroll, so the WebKit notch is the event macOS WebKit dispatches for a mouse wheel, built in the page: 100.006103515625 px, wheelDeltaY 300. It checks:
    • a notch, and Chromium's own wheel tick, are taken by the page;
    • the view covers the whole distance through at least two positions on the way, taking 100 ms or more from its first move to rest (time-based, so a loaded runner's skipped frames don't matter);
    • notches during a glide add up;
    • a trackpad scroll is not taken (defaultPrevented false) and Chromium applies it in one frame;
    • Reduce Motion and data-wheel-glide="off" leave the notch to the engine;
    • a notch over Jump to latest reaches the transcript with its shift, alt and meta keys.
  • transcript-wheel-glide.test.ts (Chromium and WebKit, the transcript harness), for the Agent chat: a mouse wheel's notch glides the transcript instead of jumping #24327 review:
    • a switch to another chat mid-glide leaves the new chat at its end on every frame;
    • a notch over a part that scrolls on its own ends the glide where it is: the content holds from the next frame. Measured by where the content is, not scrollTop, since rows above that measure move the offset without moving anything on screen.
  • wheelGlide.test.ts also drives installWheelGlide on a stand-in scroller and frame clock. It checks each stop path frame by frame (a nested part, a trackpad, modified and sideways wheels, stop(), a hidden pane), and that remove() with a frame pending cancels it and takes every listener off.
  • wheelGlide.test.ts: unit cases for isWheelNotch, using the values each engine dispatches.
  • agent-pane-wheel-pass-through.test.ts (existing) runs alongside it, unchanged.

Before and after (same harness, huge-blue, Linux headless; one notch of 100 px)

Input Base: max jump in one frame Base: frames moving Head: max jump in one frame Head: frames moving Head: first move to rest
Chromium wheel tick (page.mouse.wheel(0, -100)) 100 px 1 23 px 8-10 150 ms
WebKit macOS notch event (100.006 px) 90-95 px 3 33-58 px 4-6 185-204 ms
Chromium trackpad (40 px, precise) 40 px 1 40 px 1 unchanged
WebKit trackpad (120 px, precise) engine's own 3 engine's own 4 unchanged, not taken

Linux WPE animates a wheel step over 2-3 frames by itself (see the #23062 note). macOS WebKit applies it in one frame, which is the jump Leo sees. On WPE the base red is therefore that the page doesn't take the notch (defaultPrevented false); the max jump of 95 of 100 px also shows it. WebKit's head frame counts vary because the loaded host skips frames; the glide is time-based, so it still takes its 180 ms (200 ms when these numbers were taken, before 4c55c45).

Same file on huge-blue: base (test-only commit) fails the 3 Chromium glide cases and the 2 WebKit glide cases; trackpad and toggle cases pass. Head passes all 9.

Review fixes (head 75ab7f8; test-only commit 9c1cdd7):

  • huge-blue, old code with the new tests: red. Chromium: the chat switch and the nested part. WebKit: the chat switch. Unit: the nested part, the trackpad, modified and sideways wheels, and the hidden pane. Both engines: the modifiers on the Jump to latest copy.
  • huge-blue, head: wheelGlide.test.ts 12/12, transcript-wheel-glide 4/4 (3 runs in a row), agent-pane-wheel-glide 11/11, agent-pane-wheel-pass-through 6/6, thread-minimap-feel 4/4.
  • WebKit's nested case passes on the old code too: WebKit scrolls for a dispatched wheel event, so the test scrolls the part partway first and holds the content still either way. The Chromium case and the unit test cover that path.
  • Fleet red at the test-only commit 9c1cdd7: step 2e4ac98a251ada15c8e99e0c, exit 1. Every new review case fails in both engines, and so do the new unit cases; the earlier cases pass.
  • Fleet green at 75ab7f8: step e0c7ae052a63269942457d4d, 4 files, 0 failed. Compile-check --app passed (job 2ff932550df981ca3a084859).

Current head dc000a7 (75ab7f8 plus a merge of feat-cmux-next, which brings in #24377's fix for the PaneMotionBench compile red on base):

  • Fleet green: step 8d9135ebafd227de8f754428 (aws-m4pro-6). It ran wheelGlide.test.ts, agent-pane-wheel-glide, transcript-wheel-glide and thread-minimap-feel: 4 files, 0 failed, exit 0.
  • Compile: cmux-ci compile-check --ref dc000a7549b10202dc44442f1ab0cbe8b4fb065e --app passed (job e0b527f05b68a4d4f87ed8df).
  • Swift: AgentPaneThoughtTunablesTests run alone on the fleet, step 17934d5ed345480051f901f1 (aws-m4pro-2): 9/9 passed, including wheelGlideIsOnByDefaultAndOffSetsItsAttribute().

Review round 2 (head 683d6d8; test-only commit faca193):

  • 4c55c45 ran the glide for MOTION_MS.standard (180 ms) instead of a literal 200 ms. motion-scale.test refused the 200.
  • A page hidden mid-glide ends it. WebKit holds a hidden page's frames, so the old glide finished in one jump when the page came back. A visibilitychange listener now stops the glide when document.hidden. It is added only while a glide runs and removed in stop(), so remove() takes it off too. The frame step also checks document.hidden. It is allowlisted in check-switch-fetches.py because it reads no data.
  • A notch that moves as much sideways as down goes to the engine, the same deltaY <= deltaX rule wheelPassThrough uses.
  • Fleet red at faca193: step e6fd68b2d23055e80f95cbee, exit 1. These fail: the Chromium hidden-page case, the unit hidden-page case, the diagonal case and the sideways list. Earlier cases pass.
  • Fleet green at 683d6d8: step cb8ebc356ad2ed14dc9e9d0a. It ran wheelGlide.test, motion-scale, agent-pane-wheel-glide, thread-minimap-feel and transcript-wheel-glide: 5 files, 0 failed.
  • The fleet mini skipped WebKit in transcript-wheel-glide ("skipping webkit"), so there Chromium and the unit test cover the hidden-page case.
  • At 4c55c45 (the last Swift change): compile-check --app passed (job e6b8c0e48d6d06f70d8a1350), and AgentPaneThoughtTunablesTests passed 9/9 (step 9163689c79abb133800284c7). Round 2 touches no Swift.
  • Base reds: agent-pane-older-rows-fade fails the same way at the base merge a687a49 (step 15ced7124fd88e55d6dc53dd). agent-pane-wheel-pass-through times out with no output at both base a687a49 (step 1e1b85dd1c7bc4da2824ac59) and head 4c55c45 (step 5eface195dd067e8a0e74f76).

Review round 3 (head 3a632eb; test-only commit d8c9bdc; base merged at 53173b2):

  • A glide to the bottom now follows a streaming reply. Before, a notch that sent the view to the bottom while a reply streamed was clamped once to the bottom of that moment. If the reply grew more than the 4 px follow deadband during the glide, it landed short, follow turned off and Jump to latest showed. A glide whose wheel asked for the bottom now extends to the current bottom each frame. That is one scrollHeight read the step already made, so no O(rows) work per frame. A glide short of the bottom keeps its own distance.
  • A modified wheel (shift, option, command) over Jump to latest or a stack gutter scrolls nothing now. Its copy is cancelled before dispatch, so the transcript's listeners still hear it (a glide in flight ends) but no scroll happens. Cancelling matters because WebKit scrolls for a dispatched wheel event: without it the copy still moved the transcript 100 px.
  • The glide check in agent-pane-wheel-glide now takes 2 or more frames with no frame covering more than 80% of the distance. A loaded mini painted only 2 frames of the 180 ms glide; the engine's own step is still one frame for all of it.
  • vp lint warning in the unit stand-in (no-useless-spread) fixed. That warning reddened fast gates at 683d6d8.
  • Fleet red at d8c9bdc on cmux10 (WebKit launches there): step 4708f1ec69802612a6389718, exit 1. These fail:
    • WebKit: the streaming-reply case and the modified-notch case;
    • Chromium: the modified-notch case;
    • unit: the bottom-growth case.
      The Chromium streaming case passes on the old code too, so in Chromium only the unit case shows this bug.
  • Fleet green at 3a632eb:
    • cmux10: step e0da532abb83ec0e14b31fcf, 3 files, 0 failed. WebKit cases ran by name on this worker at d8c9bdc and a4630d6; passing files print no per-test lines.
    • aws: step cb64cff8fdc861b3c98a6edb, motion-scale, wheelGlide.test, transcript-wheel-glide, agent-pane-wheel-glide, thread-minimap-feel, transcript-follow: 6 files, 0 failed.
  • Compile-check --app at a4630d6 (after the base merge) passed, job 602a4b41be4b577636c519e8. Round 3 touches no Swift.

Earlier head b6ae0c2:

  • huge-blue: agent-pane-wheel-glide.test.ts 9/9, agent-pane-wheel-pass-through.test.ts 6/6, thread-minimap-feel.test.ts 4/4, wheelGlide.test.ts 6/6. These ran with each file's browsers launched once; huge-blue hangs on the files' launch-then-relaunch probe even on base.
  • Fleet green: step 1d1d6dbb4af1f938768d6383 (aws-m4pro-9), running agent-pane-wheel-glide, wheelGlide.test.ts and thread-minimap-feel. 3 files, 0 failed, exit 0.
  • Fleet red: step cc051e132c65db3d275a5f35 (aws-m4pro-5) at the test-only commit bb3f503. The Chromium notch, notches-add-up and wheel-tick cases fail (prevented false, one 100 px jump); the trackpad and toggle cases pass.
  • Compile: cmux-ci compile-check --ref b6ae0c2b12d5cf847a4c580b3c5535930d995950 --app passed (fleet job 700e8b30aef37319af11a2db).

The last commit fixes a red this PR caused in thread-minimap-feel ("the band of lit rows follows the scroll on the scroll's own frame": 29-34 wrong frames). A scrollTop write in a frame callback fires its scroll event only in the next frame, so the minimap's lit band and the mounted rows painted one frame behind each glide step. Each step now dispatches its scroll event at once, and the browser's later event for the same offset is dropped. Checked on huge-blue: without the fix 34 wrong frames, with it 4/4 pass.

No capture: the only native change is one Debug Settings toggle row generated from its Tunable descriptor. What changes for the user is wheel motion in the webview, which the Chromium and WebKit frame test above measures.

Changelog

  • Changed: In the agent chat, a mouse wheel now scrolls the transcript smoothly instead of in steps.

Agent session: Air-Blue

🤖 Generated with Claude Code

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

teamleaderleo commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

Owned by nx-chat on big-red · session

@cursor

cursor Bot commented Oct 22, 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
teamleaderleo force-pushed the leo/transcript-mouse-wheel-glide branch from 1bbf717 to a0730f8 Compare October 22, 2026 08:24
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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

@teamleaderleo
teamleaderleo force-pushed the leo/transcript-mouse-wheel-glide branch from a0730f8 to 8a7b078 Compare October 22, 2026 09:13
@teamleaderleo
teamleaderleo force-pushed the leo/transcript-mouse-wheel-glide branch from 8a7b078 to 1d1ed86 Compare October 22, 2026 09:25
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

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

Review d17bbcd: 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=pr24327 -f ref=leo/transcript-mouse-wheel-glide -f platform=macos -f backend_mode=local

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

gh run download 40339191530 -R manaflow-ai/cmux -n gallery-advisory-24327-d17bbcdf8e7100afacf91060b21fd6d8f295d343 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.chat-width/standard Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/standard Apple System Colors unchanged 0% own App.tsx
agent-pane.chat-width/wide Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/wide Apple System Colors unchanged 0% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-floats/scrolled-to-end Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-floats/scrolled-to-end Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-floats/composer-grown Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-floats/composer-grown Apple System Colors unchanged 0% own App.tsx
agent-pane.docked-chat/new-chat Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/new-chat Apple System Colors unchanged 0% own App.tsx
agent-pane.docked-chat/after-turn Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/after-turn Apple System Colors unchanged 0% own App.tsx
agent-pane.docked-chat/working Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/working Apple System Colors unchanged 0% own App.tsx
agent-pane.docked-chat/typing-in-dock Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/typing-in-dock Apple System Colors unchanged 0% own App.tsx
agent-pane.docked-chat/long-title Apple System Colors Light unchanged 0% own App.tsx
agent-pane.docked-chat/long-title Apple System Colors unchanged 0% own App.tsx
agent-pane.gutter-select/from-left-margin Apple System Colors Light unchanged 0% own App.tsx
agent-pane.gutter-select/from-left-margin Apple System Colors unchanged 0% own App.tsx
agent-pane.gutter-select/from-right-margin Apple System Colors Light unchanged 0% own App.tsx
agent-pane.gutter-select/from-right-margin Apple System Colors unchanged 0% own App.tsx
agent-pane.orchestration-lab/local-queue Apple System Colors Light unchanged 0% own App.tsx
agent-pane.orchestration-lab/local-queue Apple System Colors unchanged 0% own App.tsx
agent-pane.orchestration-lab/build-box Apple System Colors Light unchanged 0% own App.tsx
agent-pane.orchestration-lab/build-box Apple System Colors unchanged 0% own App.tsx
agent-pane.orchestration-lab/cloud-fair-share Apple System Colors Light unchanged 0% own App.tsx
agent-pane.orchestration-lab/cloud-fair-share Apple System Colors unchanged 0% own App.tsx
agent-pane.streaming-follow-motion/stream Apple System Colors Light unchanged 0% own App.tsx
agent-pane.streaming-follow-motion/stream Apple System Colors unchanged 0% own App.tsx

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

Probably yours: accent-glass-invariants.test.ts, agent-pane-agents-pill.test.ts, agent-pane-background-tasks.test.ts fails here but not on main's latest full suite. Seen on other PRs too (likely flaky): claimPaint.test.tsx, claimPaint.test.tsx, agent-pane-hairline-focus.test.ts and 2 more.

CI failed on 227a8143f5 (run 40245632739 attempt 1): 1 code, 1 unknown.

Job Verdict Why
web / react-apps-check (1) code a web test failed
web / react-apps-check unknown no known signature; failed step: Check webview test shards

Failures:

  • seen on other PRs claimPaint.test.tsx a pooled Settings page claimed by the host reports its paint once its first screen is in: failed (job)
  • seen on other PRs claimPaint.test.tsx a Settings page loaded fresh reports its first paint once its first screen is in: failed (job)
  • new in this PR accent-glass-invariants.test.ts no orange or system accent and no opaque glass on 2101 gallery stages: failed (job)
  • new in this PR agent-pane-agents-pill.test.ts by default the running agents sit in the tray above the composer, not in the transcript: failed (job)
  • new in this PR agent-pane-background-tasks.test.ts the summary card lists the chat's background tasks: running ones first, the finished folded: failed (job)
  • seen on other PRs agent-pane-hairline-focus.test.ts composer at 1x: focus is the resting hairline a step brighter, no ring: failed (job)
  • seen on other PRs agent-pane-hairline-focus.test.ts newtab at 1x: focus is the resting hairline a step brighter, no ring: failed (job)
  • seen on other PRs agent-pane-hairline-focus.test.ts composer at 2x: focus is the resting hairline a step brighter, no ring: failed (job)
Matched log lines
web / react-apps-check (1): (fail) a pooled Settings page claimed by the host reports its paint once its first screen is in [1366.27ms]

Not re-run automatically: web / react-apps-check (1), web / react-apps-check are not machine failures.

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

@blacksmith-sh

blacksmith-sh Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Found 45 test failures on Blacksmith runners:

Failures

Test View Logs
---/--- View Logs
---/--- View Logs
---/--- View Logs
---/--- View Logs
---/--- View Logs
---/--- View Logs
---/--- View Logs
---/--- View Logs
---/--- View Logs
---/--- View Logs

...and 35 more test failures. View all on Blacksmith

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next crash-class rules / cmux-next crash-class rules is red on 8 of 8 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / nightly-guards / workflow-guard-tests / release-notary is red on 13 of 13 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / tooling-tests is red on 11 of 18 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next / cmux-next checks (god files, concurrency, crash safety, l10n) › Checks job structure is red on 7 of 11 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol): BLOCK at b6ae0c2

Blocking findings

  • webviews/src/agent-session/acpmux/App.tsx:3148-3158: Start a mouse-wheel glide, then switch agent sessions before the 200 ms glide ends. VirtualTranscript is deliberately kept mounted across a session switch, but this new effect has [], so its cleanup does not run. The same scroller and rAF state then serve the new session; wheelGlide.ts:94-100 applies the old session's remaining distance to the new transcript. The new chat can jump or lose its intended follow position. Stop the glide on sessionId changes (and cover the switch case).

  • webviews/src/agent-session/acpmux/wheelGlide.ts:119-126: Start a transcript glide, then turn the wheel over a nested scroller such as a Monaco editor, diff code row, long tool output, or textarea while the glide is still active. A notched event is classified first, then wheelTarget(event) !== scroller returns without calling stop(). Because the reader-input capture list has no wheel listener, the transcript keeps moving for the rest of the glide while the nested view is being scrolled. Stop an active glide whenever wheel ownership is elsewhere.

Non-blocking findings

  • webviews/src/agent-session/acpmux/wheelPassThrough.ts:33-45: the synthetic event sent from Jump to latest and floating-stack gutters carries wheelDeltaY but drops metaKey, shiftKey, and altKey; forwardWheel only filters ctrlKey, while the direct listener filters both control and command zoom. A command/shift wheel on a pass-through surface is therefore classified differently from the same event over the transcript. Preserve the modifier fields and add coverage.
  • The new browser tests do not cover session switches, nested scrollers, pass-through modifiers, hidden panes, or cleanup while a frame is pending. The existing unit cases cover only notch classification.

Known red checks are unrelated to this diff: Guard status, release-notary, the HoverCard objc-selectors crash-class rule, the cmux-next fleet/gallery/job/No-new-SwiftUI/namespace-type reds, the base react-apps-check webview reds, gallery health composer#steer-read-hover, the unconfirmed Swift failures in AgentCursorNameTag/BrowserProfileLinkMenu/NowPlayingCardDismiss/NativeTooltipGuard, and tooling-tests (Developer ID profile, .tbd stub, submodule-reset). The changed paths contain none of those components; the head's web validation and app compile checks are green.

Checked the merge-base 54ec1aceb1d2f5aff009e437d9c8af0dbf886af7 to head diff and whole changed files, session lifecycle, wheel ownership/modifier paths, Swift tunable catalog/attributes, and one GraphQL head/check snapshot; no app builds or Swift/browser suites were run.

teamleaderleo added a commit that referenced this pull request Oct 23, 2026
#24327 review: a switch to another chat mid-glide, a notch over a part that scrolls on its own,
a modified or sideways wheel, a pane hidden mid-glide, and remove() with a frame pending. A
notch over Jump to latest reaches the transcript with its modifiers.

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
teamleaderleo added a commit that referenced this pull request Oct 23, 2026
…ther part takes the wheel

#24327 review (Codex BLOCK):
- VirtualTranscript is kept across chats, so a glide in flight ran its old distance on the new
  chat. The session switch now stops it.
- A notch over a part that scrolls on its own (code, a long tool output) returned without stopping
  a glide in flight, so the transcript kept moving while the part scrolled. Any wheel the glide
  does not take now ends it where it is: another part's, a trackpad's, a modified or sideways one,
  or one under Reduce Motion or the Debug toggle off.
- A pane hidden mid-glide ends it.
- The copy of a wheel forwarded from Jump to latest, the stack gutters or the minimap card keeps
  the wheel's shift, alt and meta keys.

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
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol): BLOCK at 683d6d8

blocking findings

  • webviews/src/agent-session/acpmux/wheelGlide.ts:126-130,159-169 with App.tsx:3123-3148: while a reply is streaming, put the reader above the bottom and wheel down far enough to reach the current bottom. The target is clamped once to the old scrollHeight - clientHeight; later frames keep that fixed distance even when new reply text grows the bottom. If the reply grows more than the 4 px follow deadband during the 180 ms glide, the glide finishes at the old bottom and onScroll calls follow(false). The chat stops following the stream and shows Jump to latest. Retarget a downward glide or preserve the intended follow once its target was the bottom.

non-blocking findings

  • webviews/src/agent-session/acpmux/wheelPassThrough.ts:31-57: the forwarded path only rejects ctrl and sideways wheels. A shift, alt, or meta notch over Jump to latest or a floating gutter is left to the direct listener, then the fallback still does scroller.scrollTop += wheelPixels(...); it is consumed as vertical transcript scrolling instead of remaining with the engine. The direct transcript path checks all modifiers at wheelGlide.ts:135, so the pass-through path should match it.
  • wheelGlide.ts:15 uses the motion scale's 180 ms standard step while the PR description and several comments say 200 ms. This is a documentation/expectation mismatch; the motion-scale choice itself is consistent with the repository.

Known-red audit: Guard status, release-notary, crash-class HoverCard objc-selectors, cmux-next fleet-host/gallery fixtures, job-structure, No-new-SwiftUI, namespace-type (AppsPageNativeOps/ChiefReplyWatchdog), react-apps-check base webview reds, gallery health composer#steer-read-hover, Swift failures in AgentCursorNameTag/BrowserProfileLinkMenu/NowPlayingCardDismiss/NativeTooltipGuard, and tooling-tests (Developer ID profile, .tbd stub, submodule-reset) touch no changed path and cannot be caused by this PR. The only check-script edit is the scoped visibility listener allowlist entry.

checked: full diff from merge-base a687a49e03f457f0750ac49d8c10b57b5d18e32c, notch classification and horizontal/shift/ctrl/cmd paths, nested scrollers and non-transcript panes, follow/jump/stream interactions, rAF/listener cleanup for unmount/hidden/stop/restart/session switch, O(1) frame work, tunable defaults/attributes/catalog/debug export row, and test receipts (faca193 red at e6fd68b2d23055e80f95cbee; 683d6d8 green at cb8ebc356ad2ed14dc9e9d0a; the transcript run skipped WebKit).

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next / cmux-next checks (god files, concurrency, crash safety, l10n) › No new SwiftUI (macOS UI is AppKit) is red on 13 of 14 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

teamleaderleo added a commit that referenced this pull request Oct 23, 2026
…g reply grew to

The cases for #24327 review round 3, ahead of the fix. A notch that sends
the view to the bottom while a reply streams, with the reply growing more
than the follow deadband mid-glide, lands at the new bottom and the chat
follows the reply again (unit and Chromium/WebKit transcript harness). A
glide short of the bottom keeps its own distance. A modified notch over
Jump to latest scrolls nothing.

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
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

rust workspaces / plan (changed Cargo workspaces) is red on 19 of 19 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

rust workspaces / rust workspaces is red on 19 of 19 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol): BLOCK at 3a632eb

Blocking findings:

  • webviews/src/agent-session/acpmux/wheelGlide.ts:175 — Trigger: send a down notch and then an equal up notch before the next animation frame, which can happen when wheel events arrive in one input batch. Result: target === from returns without preventing the up event or stopping the existing glide. The browser applies the up notch immediately, then the pending down glide continues on its next frame and moves back, so the reverse notch is undone and the accumulated target is wrong. This branch must consume the cancellation or stop the in-flight glide.

Non-blocking findings:

  • The new tests do not cover reverse-notch cancellation, reply shrink while a glide runs, or forwarded trackpad/ctrl-wheel/minimap listener behavior. The relaxed frame assertion also permits an 80% first-frame jump. I found no additional runtime defect in those paths.

Checked the exact head against merge-base ea5f81f037b3c1753cd1891f4a087a373e323a6b: the wheel glide and pass-through implementation, React scroll and history handlers, all changed tests, listener/rAF cleanup, per-frame work, the Swift toggle wiring, and the 53173b229a8 base merge.

teamleaderleo added a commit that referenced this pull request Oct 23, 2026
…shrinking cases

The cases for #24327 review round 4, ahead of the fix:
- A notch and its reverse before the next frame cancel out (unit and
  Chromium/WebKit): both are taken and nothing moves.
- A reverse notch mid-glide heads back; a reply that shrinks mid-glide
  ends the glide at the new bottom (the stand-in now keeps scrollTop
  inside its content, as a browser does).
- forwardWheel, as Jump to latest, the stack gutters and the minimap's
  card call it: a notch glides, a trackpad moves at once and ends a glide,
  a pinch is left alone, a modified notch is heard and scrolls nothing.
  Over Jump to latest in the browsers: a trackpad moves the transcript by
  its pixels once, a pinch not at all, a notch with the glide off once.
- The glide check now reads the ease at each painted frame's time from
  the wheel (two frames' grace) instead of a fixed share per frame.

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
@cursor

cursor Bot commented Oct 23, 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
teamleaderleo force-pushed the leo/transcript-mouse-wheel-glide branch from fd69d01 to 66bae5f Compare October 25, 2026 23:26
@teamleaderleo
teamleaderleo force-pushed the leo/transcript-mouse-wheel-glide branch from 66bae5f to 227a814 Compare October 25, 2026 23:37
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next fast gates / post cmux-next/fast-gates is red on 18 of 21 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Orphaned: no activity for 14 h, and no lane holds a lease on it. Adopted by the orphan sweep: merged feat-cmux-next in at d17bbcd. A later pass queues it for the merge hub if it is reviewed, small and red only on base reds.

teamleaderleo added a commit that referenced this pull request Oct 27, 2026
…#26114)

* test(agent-chat): reproduce jumping mouse wheel notches

Agent-Session: https://cmux.com/deeplink/agent?id=aid-8d9d6ada62087fc93733

* fix(agent-chat): ease mouse notches on the shared scroll clock

Recuts #24327 and retains its notch classification and default-on Debug setting. Incremental wheel steps share the jump runner without a second scroll anchor or animation loop.

Agent-Session: https://cmux.com/deeplink/agent?id=aid-8d9d6ada62087fc93733

* fix(agent-chat): honor wheel speed and explicit view placements

Agent-Session: https://cmux.com/deeplink/agent?id=aid-8d9d6ada62087fc93733
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Superseded by #26114, landed on feat-cmux-next at d76bfca. The fresh replacement uses the existing shared scroll-animation clock and retains the original notch/trackpad distinction and default-on Debug setting. The WebKit subpixel-tail refinement is tracked separately in #26135.

teamleaderleo added a commit that referenced this pull request Oct 29, 2026
…so action.run's deadline test no longer times out behind it (#24456)

* test(cmux-next control): the watchdog CPU test spins its own thread, not the main actor

ActionRunCreatedTests.aCreatedTabThatNeverShowsStillAnswersRanBeforeTheDeadline
timed out at 1999 ms with its stage log stuck at daemon@43 (base run
39882563236, PR #24327 run 39798893065). In both runs
MainThreadWatchdogPolicyTests.mainThreadCPUIsReadableFromAnotherThreadAndMovesWithWork
started within 4 ms of it and spun the main actor until it had burned
80 ms of CPU, which took 3.5-4.1 s of wall time on the loaded host. The
action.run test needs the main actor (the work queue's run-loop block,
awaitIdle's MainActor.run) inside its 2 s deadline, so it timed out.

BusyWatchdog.watchCurrentThread() watches whichever thread calls it, so
the CPU test now watches and spins a thread of its own and reads it from
the test's task: the same contract, and the main actor stays free.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c6d-a1d2-7fc2-9458-92c6bdcb7b4d&host=huge-blue

* test: the watchdog CPU test joins its spinning thread before it ends

Review (Codex): the watched thread was signalled but never awaited, so
it could outlive the test. The thread now finishes the stream after its
last wait; the test reads both CPU times, releases the thread, drains the
stream to its end, and only then checks the values, so no path returns
before the thread exits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c6d-a1d2-7fc2-9458-92c6bdcb7b4d&host=huge-blue

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>

This branch was previously deployed

1 inactive deployment
ci-routing-agent — d17bbcdf Deployed Oct 26, 2026 by teamleaderleo via namespace autofix #3427
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