mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 09:19:42 +02:00
Merge #555: opencode drags select text and the wheel pages its transcript
This commit is contained in:
@@ -1273,7 +1273,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) so the wheel keeps scrolling
|
||||
// (isAltScreenStripMode, session.ts) and from opencode's (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.
|
||||
@@ -5369,36 +5370,32 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
},
|
||||
|
||||
// Mirror of the server's isAltScreenStripMode (session.ts): session modes whose
|
||||
// output stream has mouse-tracking DECSET sequences stripped before reaching the
|
||||
// browser. For these, xterm's live mouseTrackingMode is useless as a gate — the
|
||||
// PTY-side TUI keeps tracking enabled, we just never see the enable sequence.
|
||||
/**
|
||||
* True when the browser has to hand-encode a click report for the CLI.
|
||||
* True when the browser has to hand-encode a click report for the CLI: the
|
||||
* server stripped this session's mouse-tracking DECSETs out of the stream (so
|
||||
* xterm's own encoder is permanently idle here and something has to stand in
|
||||
* for it) AND the CLI has a tracking mode on right now.
|
||||
*
|
||||
* Two conditions, and dropping either one is a bug that has already happened:
|
||||
* One flag answers both. The server sets `cliMouseTracking` only as it strips a
|
||||
* tracking DECSET (`_recordStrippedMouseMode` in session.ts, called from the
|
||||
* mouse-strip branch of `_handleTerminalOutput` and nowhere else), so it can
|
||||
* only ever be true for a mode whose DECSETs are stripped: whichever modes the
|
||||
* registry decides to strip, the browser follows, with no mode list here to
|
||||
* keep in step. For a `preserve` / `strip-mux-only` mode the flag stays false
|
||||
* and xterm keeps encoding its own reports. That invariant is pinned server-side
|
||||
* in test/claude-scrollback-strip.test.ts.
|
||||
*
|
||||
* 1. The session's mode is one whose mouse DECSETs the server STRIPS out of
|
||||
* the stream (claude/codex/gemini, `isAltScreenStripMode`), which is why
|
||||
* xterm's own encoder is permanently idle here and something has to stand
|
||||
* in for it.
|
||||
* 2. The CLI actually has a mouse-tracking mode on right now. The server
|
||||
* records that as it strips (`_recordStrippedMouseMode` in session.ts) and
|
||||
* publishes it as `cliMouseTracking`. Without this half the browser
|
||||
* reported EVERY click, so a CLI sitting at its composer with no dialog
|
||||
* open, or a pane that has fallen back to a shell prompt, received mouse
|
||||
* reports it never asked for. A shell prints those as literal text
|
||||
* (`[<0;88;20M`) and they garble the next line typed.
|
||||
* Without the flag the browser reported EVERY click, so a CLI sitting at its
|
||||
* composer with no dialog open, or a pane that has fallen back to a shell
|
||||
* prompt, received mouse reports it never asked for. A shell prints those as
|
||||
* 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.
|
||||
*/
|
||||
_shouldReportMouseToCli() {
|
||||
const session = this.sessions?.get(this.activeSessionId);
|
||||
const mode = session?.mode || 'claude';
|
||||
if (mode !== 'claude' && mode !== 'codex' && mode !== 'gemini') return false;
|
||||
return session?.cliMouseTracking === true;
|
||||
return this.sessions?.get(this.activeSessionId)?.cliMouseTracking === true;
|
||||
},
|
||||
|
||||
// True when xterm's viewport shows the live PTY screen (not scrolled up into
|
||||
@@ -5583,15 +5580,29 @@ Object.assign(CodemanApp.prototype, {
|
||||
},
|
||||
|
||||
/**
|
||||
* True when this session's LOCAL scrollback is structurally empty: a Claude
|
||||
* pane in repaint mode, where tmux reports `history_size≈0` and every frame
|
||||
* overwrites the last, so xterm's normal buffer never grows past one screen
|
||||
* True when this session's LOCAL scrollback is structurally empty: a pane whose
|
||||
* TUI repaints one full screen in place, so tmux keeps no history for it
|
||||
* (`history_size≈0`) and xterm's normal buffer never grows past one screen
|
||||
* (`baseY === 0`). Scrolling that buffer is a no-op no matter how the gesture
|
||||
* is routed — the "wheel does nothing at all" half of the #205 retest.
|
||||
*
|
||||
* Two shapes, measured separately:
|
||||
* - `claude` in repaint mode (the original, #205 round 2), and
|
||||
* - `opencode`, whose TUI runs on the ALTERNATE SCREEN (opencode 1.18.31: tmux
|
||||
* `alternate_on=1`, `history_size=0`) and so pushes nothing into the
|
||||
* terminal's scrollback at all. It pages its own transcript with the same
|
||||
* PageUp/PageDown keys (`messages_page_up/down`) but IGNORES SGR wheel
|
||||
* reports — six `\x1b[<64;…M` reports against an idle pane left the capture
|
||||
* byte-identical — so paging is the only gesture that reaches it. Without
|
||||
* this the wheel was silently dead in every opencode tab.
|
||||
*
|
||||
* Every other mode is deliberately absent: shell/pi own real terminal
|
||||
* scrollback, and codex/gemini/antigravity/grok/deepseek/omp page-key behaviour
|
||||
* is unverified (docs/scrollback-fix-plan.md).
|
||||
*/
|
||||
_localScrollbackIsHollow() {
|
||||
const mode = this.sessions?.get(this.activeSessionId)?.mode || 'claude';
|
||||
if (mode !== 'claude') return false;
|
||||
if (mode !== 'claude' && mode !== 'opencode') return false;
|
||||
const buf = this.terminal?.buffer?.active;
|
||||
if (!buf || buf.type === 'alternate') return false;
|
||||
return (buf.baseY || 0) === 0;
|
||||
@@ -5602,18 +5613,19 @@ Object.assign(CodemanApp.prototype, {
|
||||
* coalesced PageUp/PageDown key sends so the CLI pages its OWN transcript.
|
||||
*
|
||||
* The rescue path for every way `_shouldForwardWheelToApp` can come back false
|
||||
* on a Claude session that has no local history to fall back on: the CLI
|
||||
* version probe failed or is genuinely older than 2.1.187, the CLI's mouse
|
||||
* tracking flag is unset (the inline renderer, or fullscreen right after a
|
||||
* server restart), or the user turned on "Wheel scrolls local history" (which
|
||||
* pins the wheel to a buffer that, for a repaint-mode CLI, is empty: the
|
||||
* setting's footgun). Before this, all of those produced a completely dead
|
||||
* gesture; the #205 reporter proved the keyboard route works by paging back
|
||||
* through intact text with Fn+Up.
|
||||
* on a session that has no local history to fall back on: the CLI version probe
|
||||
* failed or is genuinely older than 2.1.187, the CLI's mouse tracking flag is
|
||||
* unset (the inline renderer, or fullscreen right after a server restart), the
|
||||
* user turned on "Wheel scrolls local history" (which pins the wheel to a buffer
|
||||
* that, for a repaint-mode CLI, is empty: the setting's footgun), or the CLI is
|
||||
* opencode, which never fills the buffer and never accepts the wheel. Before
|
||||
* this, all of those produced a completely dead gesture; the #205 reporter
|
||||
* proved the keyboard route works by paging back through intact text with Fn+Up.
|
||||
*
|
||||
* Triple-guarded (claude mode + gate false + `baseY === 0`), so a session with
|
||||
* real local scrollback is never touched. Shift is excluded on purpose: it is
|
||||
* the explicit "give me local scrollback" gesture and must keep that meaning.
|
||||
* Guarded by `_localScrollbackIsHollow()` plus a false forwarding gate, so a
|
||||
* session with real local scrollback is never touched. Shift is excluded on
|
||||
* purpose: it is the explicit "give me local scrollback" gesture and must keep
|
||||
* that meaning.
|
||||
*
|
||||
* @returns true when the gesture was consumed here (the caller must not also
|
||||
* scroll locally).
|
||||
@@ -5714,7 +5726,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), so xterm's mouseTrackingMode is
|
||||
* claude/codex/gemini (isAltScreenStripMode) and opencode (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
|
||||
|
||||
Reference in New Issue
Block a user