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

+16
View File
@@ -159,6 +159,22 @@ describe('WebServer.renderIndexHtml', () => {
expect(map.shell).toBeUndefined();
});
it('injects the transcript page-key modes, in a /session/:id window too', async () => {
// Read off the transcriptPageKeys capability. A solo window scrolls a terminal
// like the main page does, so it needs the list as much as the gutter map.
for (const solo of [undefined, 'sess-123']) {
const { server } = makeServer();
const html = await render(server, solo);
const match = html.match(/window\.__codemanTranscriptPageKeys=(\[[^<]*\]);/);
expect(match).not.toBeNull();
const modes = JSON.parse(match![1]) as string[];
expect(modes).toContain('claude');
expect(modes).toContain('codex');
expect(modes).not.toContain('shell');
expect(modes).not.toContain('gemini');
}
});
it('escapes the solo id so it cannot break out of the inline <script>', async () => {
const { server } = makeServer({});
const html = await render(server, 'a</script><b>');