diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index a4ff1aed8..04e220200 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -24,6 +24,9 @@ - Fixed the multi-line prompt editor bypassing the keybindings registry for word/line delete and yank: `ctrl+backspace` (a declared default of `tui.editor.deleteWordBackward`) never fired and `keybindings.yml` remaps of `deleteWordBackward`, `deleteWordForward`, `deleteToLineStart`, `deleteToLineEnd`, `yank`, and `yankPop` were ignored, because those actions were matched with hardcoded chords instead of `keybindings.matches(...)` like cursor motion and the single-line `Input` already do ([#6782](https://github.com/can1357/oh-my-pi/issues/6782)). - Restored the Windows Terminal raw `0x08` → `ctrl+backspace` disambiguation (`WT_SESSION` set, `SSH_*` unset) by routing the exported `matchesRawBackspace` helper through the `matchesKey`/`parseKey` seam. Remote SSH/container sessions where terminal identity is unavailable can opt in with `PI_TUI_RAW_BACKSPACE_IS_CTRL=1` ([#6782](https://github.com/can1357/oh-my-pi/issues/6782)). - Fixed plain Backspace deleting a whole word inside tmux/GNU screen/Zellij panes launched from Windows Terminal: multiplexers inherit `WT_SESSION` but emit raw `0x08` for plain Backspace, so the automatic raw-backspace → `ctrl+backspace` heuristic misfired. The heuristic now skips multiplexer sessions (`TMUX`/`STY`/`ZELLIJ` or `TERM` starting with `tmux`/`screen`); `PI_TUI_RAW_BACKSPACE_IS_CTRL=1` remains the explicit opt-in everywhere ([#6784](https://github.com/can1357/oh-my-pi/pull/6784)). +### 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 diff --git a/packages/tui/src/terminal.ts b/packages/tui/src/terminal.ts index 093fe7d1d..c250aab1f 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[