mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 14:09:42 +02:00
fix(terminal): take the wheel in capture phase; xterm's scroller is deaf after reset
Measured on the live instance: xterm's vscode-style viewport scroller consumes wheel events itself whenever it believes a scrollbar exists (preventDefault + stopPropagation, attachCustomWheelEventHandler is not consulted), so Codeman's bubble-phase handler never fired once local scrollback existed. Forwarding, the deltaMode conversion and the top-of-buffer history re-pull were all silently dead exactly on the sessions that had history, which is the 'input box scrolls up then it fights and hangs' report. Worse, that scroller's dimensions go stale after terminal.reset(): following a tab switch or full-history replay it neither scrolls nor propagates, which is the 'works at first, breaks after reload and tab switch' report. The container wheel listener now runs in capture phase, stops propagation, and scrolls locally through buffer-level scrollLines(), which keeps working after resets. Mouse-tracking sessions and the alternate buffer (direct-PTY vim/less) are passed through untouched so xterm's encoder and alt-scroll arrow conversion keep owning those. Verified end to end against the beta: 9/9 matrix checks including the exact reported flows (claude wheel with scrollback present stays pinned and forwards, shell reaches full history by wheel alone, reload then tab switch then back still works, SSE reconnect survives, Shift+wheel stays local), plus the two prior E2E suites re-passing 10/10 and 6/6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -418,10 +418,39 @@ Object.assign(CodemanApp.prototype, {
|
||||
// history lives there); the plain wheel stays on the CLI's transcript for
|
||||
// those modes regardless of scroll position, so the CLI's input box never
|
||||
// slides off the screen (see _shouldForwardWheelToApp).
|
||||
//
|
||||
// CAPTURE phase, deliberately, and Codeman owns the scroll. xterm's
|
||||
// viewport is a vscode-style ScrollableElement that consumes wheel events
|
||||
// itself (preventDefault + stopPropagation) whenever it believes a
|
||||
// scrollbar exists, does NOT consult attachCustomWheelEventHandler, and —
|
||||
// measured on the live instance — goes DEAF after terminal.reset(): a tab
|
||||
// switch or full-history replay leaves its scroll dimensions stale, after
|
||||
// which wheel events neither scroll nor propagate reliably. A bubble-phase
|
||||
// listener here therefore never fired once local scrollback existed
|
||||
// (measured: _shouldForwardWheelToApp call count stayed 0 while xterm
|
||||
// scrolled), and after a tab switch NOTHING scrolled at all — the "input
|
||||
// box scrolls up then it fights", "works at first, breaks after a tab
|
||||
// switch" reports on #205.
|
||||
//
|
||||
// So: capture runs ancestors-first; this handler sees every wheel first
|
||||
// and stops propagation, keeping xterm's scroller out of it entirely.
|
||||
// Local scrolling goes through terminal.scrollLines() — buffer-level, so
|
||||
// it keeps working after resets — with our own deltaMode normalization
|
||||
// (_wheelScrollLines) covering Firefox's line-unit wheels. Two cases still
|
||||
// belong to xterm and are passed through untouched:
|
||||
// - mouseTrackingMode active: xterm's own encoder forwards the wheel to
|
||||
// the PTY (htop/vim with mouse on in a shell pane);
|
||||
// - alternate buffer (direct-PTY fallback running vim/less): xterm's
|
||||
// alt-scroll handling converts the wheel to cursor keys, which is what
|
||||
// those apps expect.
|
||||
container.addEventListener(
|
||||
'wheel',
|
||||
(ev) => {
|
||||
const trackingMode = this.terminal?.modes?.mouseTrackingMode;
|
||||
if (trackingMode && trackingMode !== 'none') return;
|
||||
if (this.terminal?.buffer?.active?.type === 'alternate') return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const lines = this._wheelScrollLines(ev);
|
||||
if (this._shouldForwardWheelToApp(ev)) {
|
||||
this._forwardScrollToApp(ev.clientX, ev.clientY, lines);
|
||||
@@ -431,7 +460,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
this.terminal.scrollLines(lines);
|
||||
this._maybeLoadMoreHistoryOnScroll(lines);
|
||||
},
|
||||
{ passive: false }
|
||||
{ passive: false, capture: true }
|
||||
);
|
||||
|
||||
// Touch scrolling — use terminal.scrollLines() for all devices.
|
||||
|
||||
Reference in New Issue
Block a user