diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index b16750245..7d8dc7709 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -6,6 +6,7 @@ - Fixed animated Loader paints saturating a CPU core on slow WSL/ConPTY terminals by applying cost-aware cadence backpressure while preserving 30fps on cheap frames ([#7290](https://github.com/can1357/oh-my-pi/issues/7290)). - Fixed interactive terminals suppressing all output and input when the host project sets `NODE_ENV=test` or `BUN_ENV=test` ([#7261](https://github.com/can1357/oh-my-pi/issues/7261)). +- Fixed Kitty/Ghostty shortcuts on non-Latin keyboard layouts by requesting base-layout key reporting from the terminal ([#7320](https://github.com/can1357/oh-my-pi/issues/7320)). ## [17.2.2] - 2026-07-31 diff --git a/packages/tui/src/terminal.ts b/packages/tui/src/terminal.ts index 5a46eded6..7e1d2368c 100644 --- a/packages/tui/src/terminal.ts +++ b/packages/tui/src/terminal.ts @@ -404,7 +404,7 @@ export interface Terminal { // Whether Kitty keyboard protocol is active get kittyProtocolActive(): boolean; - // The exact kitty keyboard push sequence in effect ("\x1b[>1u" or "\x1b[>7u"), + // The exact kitty keyboard push sequence in effect ("\x1b[>5u" or "\x1b[>7u"), // or null when the protocol is not active. Kitty keyboard flags are per-screen, // so the TUI re-pushes this after entering the alternate screen. get kittyEnableSequence(): string | null; @@ -1106,9 +1106,9 @@ export class ProcessTerminal implements Terminal { this.#kittyEnableSeq = "\x1b[>7u"; this.#safeWrite(this.#kittyEnableSeq); } else { - // Level 1 (disambiguate escape codes) — enough for Shift+Enter - // without the modifyOtherKeys fallback that caused regression #3259. - this.#kittyEnableSeq = "\x1b[>1u"; + // Disambiguate escape codes and report base-layout keys for physical + // shortcut matching, without event reporting that caused regression #3259. + this.#kittyEnableSeq = "\x1b[>5u"; this.#safeWrite(this.#kittyEnableSeq); } return; diff --git a/packages/tui/test/emergency-restore-altscreen.test.ts b/packages/tui/test/emergency-restore-altscreen.test.ts index fc9b26ea8..537289a44 100644 --- a/packages/tui/test/emergency-restore-altscreen.test.ts +++ b/packages/tui/test/emergency-restore-altscreen.test.ts @@ -127,7 +127,7 @@ describe("emergencyTerminalRestore alt-screen gating", () => { it("pops keyboard enhancement frames on both screens when crashing from a fullscreen overlay", () => { const { terminal, writes } = startCapturedTerminal(); process.stdin.emit("data", "\x1b[?0u"); - expect(terminal.kittyEnableSequence).toBe("\x1b[>1u"); + expect(terminal.kittyEnableSequence).toBe("\x1b[>5u"); terminal.write(`\x1b[?1049h${terminal.kittyEnableSequence}`); setAltScreenActive(true); diff --git a/packages/tui/test/keys.test.ts b/packages/tui/test/keys.test.ts index 3878d2a3b..b7f9a6ef8 100644 --- a/packages/tui/test/keys.test.ts +++ b/packages/tui/test/keys.test.ts @@ -56,6 +56,17 @@ describe("matchesKey", () => { expect(matchesKey(dvorakCtrlSlash, "ctrl+[")).toBe(false); setKittyProtocolActive(false); }); + + it("matches non-Latin shortcuts by their base-layout key", () => { + setKittyProtocolActive(true); + try { + expect(matchesKey("\x1b[1089::99;5u", "ctrl+c")).toBe(true); + expect(matchesKey("\x1b[1079::112;5u", "ctrl+p")).toBe(true); + expect(matchesKey("\x1b[1057::99;6u", "ctrl+shift+c")).toBe(true); + } finally { + setKittyProtocolActive(false); + } + }); it("ignores Kitty release events while still matching repeats", () => { setKittyProtocolActive(true); expect(matchesKey("\x1b[127u", "backspace")).toBe(true); diff --git a/packages/tui/test/kitty-keyboard-da1-ordering.test.ts b/packages/tui/test/kitty-keyboard-da1-ordering.test.ts index 42662f4eb..c3a654a8b 100644 --- a/packages/tui/test/kitty-keyboard-da1-ordering.test.ts +++ b/packages/tui/test/kitty-keyboard-da1-ordering.test.ts @@ -46,7 +46,7 @@ describe("ProcessTerminal kitty keyboard progressive-enhancement ordering", () = Object.defineProperty(TERMINAL, "id", { value: originalTerminalId, configurable: true }); }); - it("enables kitty when the kitty reply arrives before the DA1 sentinel", async () => { + it("requests alternate keys when the kitty reply arrives before the DA1 sentinel", async () => { harness = createProcessTerminalRenderHarness(100, 30); await harness.settle(); expect(harness.writes.join("")).toContain("\x1b[?u\x1b[c"); @@ -56,7 +56,7 @@ describe("ProcessTerminal kitty keyboard progressive-enhancement ordering", () = const out = harness.writes.join(""); expect(harness.terminal.kittyProtocolActive).toBe(true); - expect(out).toContain("\x1b[>1u"); + expect(out).toContain("\x1b[>5u"); expect(out).not.toContain("\x1b[>4;2m"); }); @@ -71,10 +71,10 @@ describe("ProcessTerminal kitty keyboard progressive-enhancement ordering", () = const out = harness.writes.join(""); expect(harness.terminal.kittyProtocolActive).toBe(true); - expect(out).toContain("\x1b[>1u"); + expect(out).toContain("\x1b[>5u"); const enableIdx = out.indexOf("\x1b[>4;2m"); const disableIdx = out.indexOf("\x1b[>4;0m"); - const kittyIdx = out.indexOf("\x1b[>1u"); + const kittyIdx = out.indexOf("\x1b[>5u"); expect(enableIdx).toBeGreaterThanOrEqual(0); expect(disableIdx).toBeGreaterThan(enableIdx); expect(kittyIdx).toBeGreaterThan(enableIdx); @@ -92,7 +92,7 @@ describe("ProcessTerminal kitty keyboard progressive-enhancement ordering", () = const out = harness.writes.join(""); expect(harness.terminal.kittyProtocolActive).toBe(false); expect(out).toContain("\x1b[>4;2m"); - expect(out).not.toContain("\x1b[>1u"); + expect(out).not.toContain("\x1b[>5u"); }); it("skips modifyOtherKeys fallback for SSH_CONNECTION-only unknown terminals", async () => { @@ -131,7 +131,7 @@ describe("ProcessTerminal kitty keyboard progressive-enhancement ordering", () = const out = harness.writes.join(""); expect(harness.terminal.kittyProtocolActive).toBe(false); expect(out).toContain("\x1b[>4;2m"); - expect(out).not.toContain("\x1b[>1u"); + expect(out).not.toContain("\x1b[>5u"); expect(harness.terminal.keyboardEnhancementEnterSequence).toBe("\x1b[>4;2m"); }); @@ -182,7 +182,7 @@ describe("ProcessTerminal kitty keyboard progressive-enhancement ordering", () = margin: 0, }); await harness.settle(); - expect(harness.writes.join("")).toContain("\x1b[?1049h\x1b[>1u"); + expect(harness.writes.join("")).toContain("\x1b[?1049h\x1b[>5u"); harness.writes.length = 0; overlay.hide(); diff --git a/packages/tui/test/terminal-appearance.test.ts b/packages/tui/test/terminal-appearance.test.ts index f222c6ee7..39c27f263 100644 --- a/packages/tui/test/terminal-appearance.test.ts +++ b/packages/tui/test/terminal-appearance.test.ts @@ -601,7 +601,7 @@ describe("ProcessTerminal OSC 11 appearance detection", () => { // Simulate kitty-capable terminal reply (level >=1). process.stdin.emit("data", "\x1b[?1u"); - const pushes = writes.filter(w => w === "\x1b[>1u" || w === "\x1b[>7u" || w === "\x1b[>31u").length; + const pushes = writes.filter(w => w === "\x1b[>5u" || w === "\x1b[>7u" || w === "\x1b[>31u").length; expect(pushes).toBe(1); terminal.stop();