From 384394eea0429b352aebb8dfae9c7b53b2460dcc Mon Sep 17 00:00:00 2001 From: roboomp Date: Mon, 27 Jul 2026 14:23:27 +0000 Subject: [PATCH] fix(tui): balanced keyboard frames on emergency exit - Popped the alternate-screen keyboard frame before restoring the main screen. - Reset the main-screen keyboard state in live and blind emergency cleanup. - Added regression coverage for both restore paths. Fixes #6810 --- packages/tui/CHANGELOG.md | 4 ++++ packages/tui/src/terminal.ts | 16 +++++++------- .../test/emergency-restore-altscreen.test.ts | 21 +++++++++++++++++++ 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index c42a9fdf5..6acd499ce 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed emergency exits from fullscreen overlays leaving omp's main-screen Kitty keyboard frame active, which could corrupt Arrow Up input in the terminal pane after omp exited ([#6810](https://github.com/can1357/oh-my-pi/issues/6810)). + ## [17.1.4] - 2026-07-26 ### Fixed diff --git a/packages/tui/src/terminal.ts b/packages/tui/src/terminal.ts index 00ea59ace..c907cc1c7 100644 --- a/packages/tui/src/terminal.ts +++ b/packages/tui/src/terminal.ts @@ -275,17 +275,15 @@ export function emergencyTerminalRestore(): void { restoreTerminalStderr(); const terminal = activeTerminal; if (terminal) { - terminal.stop(); - // stop() never touches the alternate screen — the TUI owns that - // state and exits it on the normal shutdown path. Only crash paths - // with a fullscreen overlay still hold the alt buffer here. The - // leave sequence is gated on the tracked state because it is NOT a - // universally safe no-op: Windows' VT dispatcher homes the cursor - // on DECRST 1049 even when the alt buffer is inactive. + // Keyboard enhancement state is screen-local: pop the alt-screen + // frame before leaving it, then let stop() pop omp's main-screen frame. if (altScreenActive) { - terminal.write("\x1b[?1049l"); + const keyboardExit = + terminal.keyboardEnhancementExitSequence ?? (terminal.kittyEnableSequence ? "\x1b[\x1b[ { emergencyTerminalRestore(); const firstRestore = writes.join(""); expect(firstRestore).toContain("\x1b[?1049l"); + const altExit = firstRestore.indexOf("\x1b[?1049l"); + expect(firstRestore.indexOf("\x1b[ { expect(activeRestore).toContain("\x1b[?1003l"); expect(activeRestore).toContain("\x1b[?1000l"); }); + 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"); + + terminal.write(`\x1b[?1049h${terminal.kittyEnableSequence}`); + setAltScreenActive(true); + writes.length = 0; + + emergencyTerminalRestore(); + + const restored = writes.join(""); + const altPop = restored.indexOf("\x1b[