Merge #771 fix(pi): keep the mouse wheel working after a tab switch or reload

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-11-06 05:00:46 +01:00
commit 4d20b9c898
17 files changed
+172 -49

No files matched your search

+2 -2
View File
@@ -1149,8 +1149,8 @@
// A plain left-click reported to the CLI, the primary pane's desktop click
// (terminal-ui.js _handleDesktopTerminalClick) aimed at this pane. The
// server strips the mouse DECSETs of some modes (opencode's since #555, so a
// drag selects text), which leaves this xterm's own mouse encoder idle for
// server strips the mouse DECSETs of some modes (so a drag selects text),
// which leaves this xterm's own mouse encoder idle for
// them; without this a click in such a pane never reached the CLI. Only
// while this pane's session has tracking on (cliMouseTracking), through the
// same skips as the primary pane. Bubble phase, as there. The target is
+16 -13
View File
@@ -1572,8 +1572,8 @@ Object.assign(CodemanApp.prototype, {
// A real mouse click normally reaches the PTY through xterm's own mouse
// encoder, but that encoder only runs while mouseTrackingMode is ON — and
// the server strips the enabling DECSETs from claude/codex/gemini output
// (isAltScreenStripMode, session.ts) and from opencode's (isMuxMouseStripMode,
// so a drag selects text) so the wheel keeps scrolling
// (isAltScreenStripMode, session.ts) and from the mouse-strip modes'
// (isMuxMouseStripMode, so a drag selects text) so the wheel keeps scrolling
// scrollback. Desktop clicks therefore stopped reporting entirely (the
// same breakage the mobile touchend tap branch above works around).
// Hand-encode the SGR report for plain left-clicks on those sessions.
@@ -5942,8 +5942,8 @@ Object.assign(CodemanApp.prototype, {
* literal text (`[<0;88;20M`) and they garble the next line typed.
*
* Fails toward silence: an unknown or stale flag reports nothing rather than
* injecting bytes. After a server restart the flag is false until the CLI
* re-emits its DECSET, which closing and reopening a dialog does.
* injecting bytes. After a server restart the flag is false until the DECSET
* passes again, which tmux does itself when the server re-attaches to the pane.
*
* `sessionId` defaults to the primary pane's session; a TerminalTile passes
* its own (through _handleDesktopTerminalClick's target), never the active one.
@@ -6059,18 +6059,21 @@ Object.assign(CodemanApp.prototype, {
// option, but it would override an explicit user choice; instead the caller
// falls through to _maybePageCliTranscript, so the gesture still pages the
// CLI's transcript (or loads unloaded tmux scrollback, see there) and the
// setting keeps meaning exactly what it says.
// setting keeps meaning exactly what it says. Pi is the accepted exception:
// it declares no `transcriptPageKeys`, so with this on its wheel stays dead.
if (this.loadAppSettingsFromStorage?.()?.terminalWheelLocalScrollback) return false;
const mode = (target.terminal || this.terminal)?.modes?.mouseTrackingMode;
if (mode && mode !== 'none') return false;
const session = this.sessions?.get(target.sessionId || this.activeSessionId);
const sessionMode = session?.mode || 'claude';
// GitHub Copilot CLI scrolls its own transcript on SGR wheel reports (checked with xterm
// encoding them natively). Its mouse DECSETs are stripped server-side so a drag selects
// locally (strip-mux-and-mouse), which also means xterm no longer encodes the wheel: send the
// same reports by hand, but only while the CLI is actually tracking the mouse. Not gated on a
// version; a stale-false flag falls through to the PageUp/PageDown fallback like claude's.
if (sessionMode === 'copilot') return session?.cliMouseTracking === true;
// GitHub Copilot CLI (and pi, fullscreen by default) scrolls its own transcript on SGR wheel
// reports (checked with xterm encoding them natively). Their mouse DECSETs are stripped
// server-side so a drag selects locally (strip-mux-and-mouse), which also means xterm no longer
// encodes the wheel: send the same reports by hand, but only while the CLI is actually tracking
// the mouse. Not gated on a version. On Copilot a stale-false flag falls through to the
// PageUp/PageDown fallback like claude's; pi has none on purpose (the `transcriptPageKeys` note
// in stock.ts), so there the wheel does nothing until the flag is back.
if (sessionMode === 'copilot' || sessionMode === 'pi') return session?.cliMouseTracking === true;
if (sessionMode !== 'claude' && sessionMode !== 'codex') return false;
if (sessionMode === 'claude' && !this._cliVersionAtLeast(session?.cliVersion, '2.1.187')) return false;
// Only while Claude or Codex is actually listening for the mouse. Claude's
@@ -6408,8 +6411,8 @@ Object.assign(CodemanApp.prototype, {
* The reason is that the habit and xterm's Shift mean different things once
* the DECSETs are stripped. xterm reads Shift as "force selection" ONLY while
* the app actually has mouse tracking on; the server strips those DECSETs for
* claude/codex/gemini (isAltScreenStripMode) and opencode (isMuxMouseStripMode),
* so xterm's mouseTrackingMode is
* claude/codex/gemini (isAltScreenStripMode) and the mouse-strip modes
* (isMuxMouseStripMode), so xterm's mouseTrackingMode is
* permanently `none`, that branch is unreachable, and Shift instead falls into
* `_onIncrementalClick` — EXTEND an existing selection. Extending is a no-op
* when `selectionStart` is null, so the drag never anchors and no selection is
+1 -1
View File
@@ -332,7 +332,7 @@ const MOUSE_TRACKING_PATTERN = /\x1b\[\?(?:1000|1001|1002|1003|1005|1006|1007)[h
* - `strip-full` (claude/codex/gemini): alt-screen toggles, `3J` and mouse DECSETs.
* xterm obeys the toggles by switching to its scrollback-less alt buffer and wiping
* saved lines, so history disappeared on tab switch.
* - `strip-mux-and-mouse` (opencode, tmux-backed): alt-screen toggles and mouse DECSETs.
* - `strip-mux-and-mouse` (tmux-backed): alt-screen toggles and mouse DECSETs.
* A replayed tracking enable parks xterm in report mode, where a drag goes to the CLI
* instead of selecting text (and Ctrl+C without a selection is opencode's app_exit).
* `3J` stays: a TUI is not a `clear` consumer.