From 2f7fe087a2a0f474ba68267ef2b07ea88010fd04 Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Sat, 6 Jun 2026 00:16:40 -0400 Subject: [PATCH] Fix Codex held-key input batching (#4732) --- .../pane-terminal-output-scheduler.test.ts | 49 ++++++++++++++++++- .../pane-terminal-output-scheduler.ts | 4 +- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.test.ts b/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.test.ts index 07acbb59541..63c205ff3e4 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.test.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.test.ts @@ -298,7 +298,7 @@ describe('pane terminal output scheduler', () => { stripTransientCursorShows: true }) - vi.advanceTimersByTime(31) + vi.advanceTimersByTime(15) expect(terminal.write).not.toHaveBeenCalled() vi.advanceTimersByTime(1) @@ -328,7 +328,7 @@ describe('pane terminal output scheduler', () => { coalesceForeground: true }) - vi.advanceTimersByTime(31) + vi.advanceTimersByTime(15) expect(terminal.write).not.toHaveBeenCalled() vi.advanceTimersByTime(1) @@ -340,6 +340,51 @@ describe('pane terminal output scheduler', () => { ) }) + it('does not batch repeated latency-sensitive synchronized frames across key-repeat ticks', async () => { + vi.useFakeTimers() + const { writeTerminalOutput } = await loadScheduler() + const terminal = createTerminal() + + writeTerminalOutput(terminal, '\x1b[?2026h\x1b[0 q\x1b[?25l\x1b[19;3Hx\x1b[?25h', { + foreground: true, + latencySensitive: true, + stripTransientCursorShows: true, + holdForeground: true + }) + writeTerminalOutput(terminal, '\x1b[?2026l', { + foreground: true, + latencySensitive: true, + stripTransientCursorShows: true, + coalesceForeground: true + }) + + vi.advanceTimersByTime(16) + vi.runOnlyPendingTimers() + expect(terminal.write).toHaveBeenCalledTimes(1) + + writeTerminalOutput(terminal, '\x1b[?2026h\x1b[0 q\x1b[?25l\x1b[19;4Hx\x1b[?25h', { + foreground: true, + latencySensitive: true, + stripTransientCursorShows: true, + holdForeground: true + }) + writeTerminalOutput(terminal, '\x1b[?2026l', { + foreground: true, + latencySensitive: true, + stripTransientCursorShows: true, + coalesceForeground: true + }) + + vi.advanceTimersByTime(16) + vi.runOnlyPendingTimers() + + expect(terminal.write).toHaveBeenCalledTimes(2) + expect(terminal.write.mock.calls.map(([data]) => data)).toEqual([ + '\x1b[?2026h\x1b[0 q\x1b[?25l\x1b[19;3Hx\x1b[?25h\x1b[?2026l', + '\x1b[?2026h\x1b[0 q\x1b[?25l\x1b[19;4Hx\x1b[?25h\x1b[?2026l' + ]) + }) + it('keeps transient cursor shows unless the caller opts into stripping', async () => { vi.useFakeTimers() const { writeTerminalOutput } = await loadScheduler() diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.ts b/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.ts index 3778c6dd440..40d6e45e358 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.ts @@ -71,7 +71,9 @@ const MAX_BACKGROUND_QUEUE_CHUNKS = 4096 const PARSE_SETTLE_TIMEOUT_MS = 250 const FOREGROUND_COALESCE_DELAY_MS = 1000 const FOREGROUND_HOLD_SAFETY_DELAY_MS = 250 -const LATENCY_SENSITIVE_FOREGROUND_COALESCE_DELAY_MS = 32 +// Why: key repeat can tick every 30-50ms; one frame catches split restores +// without batching multiple typed-character redraws behind the fallback. +const LATENCY_SENSITIVE_FOREGROUND_COALESCE_DELAY_MS = 16 const LATENCY_SENSITIVE_FOREGROUND_HOLD_SAFETY_DELAY_MS = 32 const CURSOR_SHOW_SEQUENCE = '\x1b[?25h' const CURSOR_HIDE_SEQUENCE = '\x1b[?25l'