From 2306f821132b9f0c91b0fd0fe302f2860dfd1887 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 12 Jul 2026 15:09:20 -0700 Subject: [PATCH] fix(terminal): gate Shift+Enter CSI-u on active protocol (#8427) --- .../terminal-shortcut-policy.test.ts | 13 ++++--- .../terminal-pane/terminal-shortcut-policy.ts | 6 ++- tests/e2e/terminal-shortcuts.spec.ts | 39 ++++++++++--------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts index a99299514..927f57eff 100644 --- a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts @@ -66,12 +66,12 @@ describe('resolveTerminalShortcutAction', () => { ).toEqual({ type: 'focusPane', direction: 'next' }) }) - it('keeps shift-enter and delete helpers explicit', () => { + it('keeps inactive shift-enter and delete helpers explicit', () => { expect( resolveTerminalShortcutAction(event({ key: 'Enter', code: 'Enter', shiftKey: true }), true) ).toEqual({ type: 'sendInput', - data: '\x1b[13;2u' + data: '\x1b\r' }) expect(resolveTerminalShortcutAction(event({ key: 'Backspace', ctrlKey: true }), true)).toEqual( { type: 'sendInput', data: '\x17' } @@ -164,9 +164,9 @@ describe('resolveTerminalShortcutAction', () => { expect(getWindowsShiftEnterEncoding).not.toHaveBeenCalled() }) - it('always uses CSI-u Shift+Enter off Windows regardless of Windows encoding', () => { + it('uses CSI-u Shift+Enter off Windows only while Kitty keyboard is active', () => { for (const encoding of [() => 'csi-u' as const, () => 'alt-enter' as const, undefined]) { - expect( + const resolve = (kittyActive: boolean) => resolveTerminalShortcutAction( event({ key: 'Enter', code: 'Enter', shiftKey: true }), false, @@ -175,11 +175,12 @@ describe('resolveTerminalShortcutAction', () => { false, undefined, undefined, - undefined, + () => kittyActive, undefined, encoding ) - ).toEqual({ type: 'sendInput', data: '\x1b[13;2u' }) + expect(resolve(true)).toEqual({ type: 'sendInput', data: '\x1b[13;2u' }) + expect(resolve(false)).toEqual({ type: 'sendInput', data: '\x1b\r' }) } }) diff --git a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts index f0f284d16..15a1ec07d 100644 --- a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts +++ b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts @@ -158,7 +158,11 @@ export function resolveTerminalShortcutAction( : isWindows ? 'alt-enter' : 'csi-u' - return { type: 'sendInput', data: encoding === 'csi-u' ? '\x1b[13;2u' : '\x1b\r' } + // Why: CSI-u is application input, not a universal terminal sequence. Off + // Windows, only send it while the pane's application has KKP active. + const canSendCsiU = + encoding === 'csi-u' && (useLocalWindowsCapability || isKittyKeyboardActivePane?.() === true) + return { type: 'sendInput', data: canSendCsiU ? '\x1b[13;2u' : '\x1b\r' } } if ( diff --git a/tests/e2e/terminal-shortcuts.spec.ts b/tests/e2e/terminal-shortcuts.spec.ts index c6c40afe6..17d837aa1 100644 --- a/tests/e2e/terminal-shortcuts.spec.ts +++ b/tests/e2e/terminal-shortcuts.spec.ts @@ -518,19 +518,26 @@ test.describe('Terminal Shortcuts', () => { await waitForPaneCount(orcaPage, 1, 30_000) }) - test('Shift+Enter writes the platform newline chord for terminal TUIs', async ({ - orcaPage, - electronApp - }) => { + test('Shift+Enter follows the pane Kitty keyboard state', async ({ orcaPage, electronApp }) => { await installMainProcessPtyWriteSpy(electronApp) - await waitForActivePanePtyId(orcaPage) + const ptyId = await waitForActivePanePtyId(orcaPage) - await pressAndExpectWrite( - orcaPage, - electronApp, - 'Shift+Enter', - process.platform === 'win32' ? '\x1b\r' : '\x1b[13;2u' - ) + await pressAndExpectWrite(orcaPage, electronApp, 'Shift+Enter', '\x1b\r') + if (process.platform === 'win32') { + return + } + + // Why: exercise the production PTY-output tracker, not xterm's renderer- + // local flag state, so the test covers the bytes the shortcut policy sees. + await execInTerminal(orcaPage, ptyId, "printf '\\033[>1u'") + await expect.poll(() => getKittyKeyboardFlags(orcaPage)).toBe(1) + await pressAndExpectWrite(orcaPage, electronApp, 'Shift+Enter', '\x1b[13;2u') + + // The shell is only standing in for a KKP-aware TUI and does not consume + // the CSI-u input above, so cancel that synthetic line before its reset. + await execInTerminal(orcaPage, ptyId, "\x03printf '\\033[ getKittyKeyboardFlags(orcaPage)).toBe(0) + await pressAndExpectWrite(orcaPage, electronApp, 'Shift+Enter', '\x1b\r') }) test('Droid gets CSI-u Shift+Enter on Windows without changing Antigravity', async ({ @@ -769,14 +776,8 @@ test.describe('Terminal Shortcuts', () => { // Ctrl+Backspace → \x17 (unix-word-rubout). await pressAndExpectWrite(orcaPage, electronApp, 'Control+Backspace', '\x17') - // Shift+Enter stays distinct; Windows keeps Esc+CR unless the active agent - // explicitly requires CSI-u (currently Droid, #7620). - await pressAndExpectWrite( - orcaPage, - electronApp, - 'Shift+Enter', - process.platform === 'win32' ? '\x1b\r' : '\x1b[13;2u' - ) + // The shell has not enabled KKP, so Shift+Enter must not leak CSI-u text. + await pressAndExpectWrite(orcaPage, electronApp, 'Shift+Enter', '\x1b\r') // --- send-input chords (macOS-only) ---