fix(agent-chat): keep mouse wheel travel monotonic in WebKit
A macOS WebKit mouse-wheel notch could overshoot by one pixel and rebound on its final frame. Quantize cumulative mouse travel at every frame and add only its incremental delta to the live offset. The transcript's existing row-id anchor remains the sole layout compensation, pending notches still accumulate, and an intentional ride to the bottom still tracks the exact end.
Follow-up to #26114 and Leo's ask: "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". The existing Claude/Codex-inspired surface is unchanged; the Claude reference is composer-fixed-no-scroll-zone in cmux-design-references, with the fixed composer retained.
The new regression fails on the landed wheel code and on a final-tail-only fix at high refresh rates. Hosted verification 40488708004 at a38d2a5a62c7513f88fee35cb8d9f83d45daca73 passes 25 clock/input tests, 12 Chromium/WebKit browser cases, the full 2,862-file typecheck, and base-compared web lint (39.3 s). The trace covers 20 ms, 8 ms and a late 176.57 ms frame. The prior code fails all three new cadence cases. Native trackpad/motion-off input, asynchronous Send interruption and the existing wheel pass-through cases pass.
Same harness, headless Linux: before is base aa82735368f69b31aafcd6666304502a7716bc62 in run 40485963038; after is this PR's exact head in run 40488708004. Chromium uses a real browser wheel; WebKit uses the macOS non-precise notch event (100.006 px), since the Linux engine's injected wheel is precise. Linux WebKit also eases native wheels itself, so that baseline is not a macOS hardware measurement.
| 100 px notch | Before | After |
|---|---|---|
| Chromium maximum step / moving frames | 100 px / 1 | 34 px / 8 |
| WebKit maximum step / moving frames | 41 px / 6 | 29 px / 8 |
Both final traces move monotonically and land at exactly 100 px, with no rebound.
| Browser | Before | After |
|---|---|---|
| Chromium | ![]() | ![]() |
| WebKit | ![]() | ![]() |
Full recordings: , , , . These are browser fixture captures, not Leo's screenshots. SHA-256 and capture provenance were verified before upload. The publisher defers image OCR to the repository's privacy workflow on Linux.
This repair adds no loop, timer, listener, follow-state write or row scan. Existing wheel motion stops after its duration or on other input, explicit placement, hide, chat switch or disposal; the shared runner cancels its frame and removes transient input/visibility listeners. The existing installer listener is removed on unmount. Shared userScrollIntent integration remains with rail-opus: ordinary jump/settle rides get its intent gate; explicitly initiated wheel rides keep their own wheel arbitration so later notches can accumulate.
Review (gpt-6-astra, xhigh): SHIP at a38d2a5a62c7513f88fee35cb8d9f83d45daca73. Independent read-only reviewer inspected the complete final diff; runtime receipts are separate.
Agent session: agent session



