fix(terminal): page a hollow Codex transcript with PageUp/PageDown from the first wheel event

Two changes to the last-resort page-key scroll (_maybePageCliTranscript), the
path a wheel or touch gesture takes when it is not forwarded to the CLI and
the local xterm buffer has no history (normal buffer, baseY 0).

Codex now takes that path too. Newer codex draws on tmux's alternate screen,
which the full strip hides from xterm, so a codex pane is as hollow as a
repaint-mode Claude pane and the wheel did nothing. Codex still ignores SGR
wheel reports and is still never forwarded the wheel; this sends plain
PageUp/PageDown keys, which codex does page its transcript on (measured on
0.157.1 locally and 0.160 over a remote session). Which modes may page is a
new registry capability, capabilities.transcriptPageKeys (claude and codex in
stock.ts), published to the page as window.__codemanTranscriptPageKeys, so
terminal-ui.js holds no CLI id for it.

The first event of a gesture now pages at once. The fallback used to send a
key only after half a screen of accumulated travel (19 rows, about 475px on a
38-row pane), so an ordinary trackpad flick sent nothing. The skipped travel is
owed back by the rest of the gesture, keeping one page per half screen; a pause
of more than 150 ms, a direction change or a tab switch starts a new gesture.
This commit is contained in:
Saqeb Akhter authored and Aamer Akhter committed 2026-10-09 20:19:46 -04:00
1 parent b59145effd
commit 2eff6dcbdd
10 files changed
+260 -45

No files matched your search

+4
View File
@@ -294,6 +294,10 @@ const capabilitiesSchema = z
wheelForward: z
.object({ mode: z.enum(['never', 'version-gated']), minVersion: z.string().max(20).optional() })
.strict(),
// Whether a hollow pane's wheel/touch scroll may be turned into PageUp/PageDown for
// the CLI to page its own transcript. Distinct from wheelForward (SGR reports).
// Optional: absent means no paging, the safe default for an unmeasured CLI.
transcriptPageKeys: z.boolean().optional(),
keyboardAccessory: z.enum(['agent', 'shell']),
privilegedCommandGate: z.boolean(),
startMode: z.enum(['interactive', 'shell']),
+9
View File
@@ -267,6 +267,10 @@ const CLAUDE: CliEntry = {
// Declared-for-later: the live rule (`_shouldForwardWheelToApp`, terminal-ui.js) is this version
// AND the server-published `cliMouseTracking` flag (#498), so wiring this field up needs both.
wheelForward: { mode: 'version-gated', minVersion: '2.1.187' },
// A repaint-mode Claude pane keeps no local history, so when the wheel is not
// forwarded (older CLI, inline renderer, the local-scrollback opt-out) a scroll is
// turned into PageUp/PageDown, which Claude pages its transcript on (#205).
transcriptPageKeys: true,
keyboardAccessory: 'agent',
privilegedCommandGate: false,
startMode: 'interactive',
@@ -703,6 +707,11 @@ const CODEX: CliEntry = {
altScreen: 'strip-full',
echo: { policy: 'predict', anchor: { kind: 'cursor' }, predictProfile: 'codex' },
wheelForward: { mode: 'never' }, // #227: codex ignores SGR wheel reports, never forward
// ...but it DOES page its transcript on PageUp/PageDown. Measured on 0.157.1 (local,
// alternate screen inside tmux, so xterm holds no history) and 0.160 (remote): PageUp
// moved the visible transcript while SGR wheel reports and local scrolling did nothing.
// This is the page-key fallback for a hollow pane, not wheel forwarding.
transcriptPageKeys: true,
maxFrameBytes: 32 * 1024,
// codex's own bare-spawn default (no config sent) is already safe (no bypass flag), so
// the multi-user clamp only needs to force an EXPLICITLY-SENT bypass back off.
+14
View File
@@ -490,6 +490,20 @@ export interface CliCapabilities {
};
/** Forwarding the wheel to the CLI's own transcript. 'never' keeps local scrollback. */
wheelForward: { mode: 'never' | 'version-gated'; minVersion?: string };
/**
* The CLI pages its OWN transcript on PageUp/PageDown, so a wheel or touch scroll over
* a pane with no local history can be turned into those keys instead of doing nothing
* (`_maybePageCliTranscript` in terminal-ui.js). Read by the frontend through
* `window.__codemanTranscriptPageKeys`, which the server builds from this field.
*
* ⚠ NOT wheel forwarding. `wheelForward` above sends SGR mouse wheel REPORTS; this sends
* plain keys, and only when the local buffer is hollow (normal buffer, `baseY === 0`).
* A CLI can ignore one and honour the other: codex ignores SGR wheel reports (so its
* `wheelForward` stays 'never') yet pages on PageUp/PageDown.
*
* Absent means no paging, so a CLI nobody has measured keeps plain local scrolling.
*/
transcriptPageKeys?: boolean;
keyboardAccessory: 'agent' | 'shell';
/** Multi-user: this CLI is a raw shell, so its commands need the privileged gate. */
privilegedCommandGate: boolean;