Summary
On Windows (PowerShell / conpty), exiting the TUI with Ctrl+C leaves the terminal garbled -- the last TUI frame overlaps the shell prompt (stale content bleeding onto the primary screen). cls cleans it up, so it is a terminal-restore problem on exit, not lost data.
Filed from code analysis on macOS; I cannot reproduce Windows conpty locally, so the exact trigger needs a Windows repro to confirm. Everything below is from reading the exit path.
Why (from the code)
The TUI renders into the alternate screen (\x1b[?1049h). Clean exit must leave the alt screen and reset modes exactly once, ordered before the resume hint -- otherwise the final frame / a redundant alt-screen-leave lands on the primary buffer as overlapping output.
That teardown is carefully tuned, but for POSIX:
ui-tui/packages/hermes-ink/src/ink/ink.tsx -- gracefulShutdown / cleanupTerminalModes / detachForShutdown exist specifically to avoid "2-3 redundant EXIT_ALT_SCREEN sequences landing on the main screen AFTER printResumeHint()" (see the detachForShutdown docstring, ~L1275-1290). The ordering guarantee is validated against signal-exit's deferred-unmount behavior.
- The only
win32 branch (ink.tsx:2409) is an early return in the stdin-drain helper ("No /dev/tty on Windows; CONIN$ doesn't support O_NONBLOCK"), so Windows skips part of the POSIX cleanup path.
writeSync(1, EXIT_ALT_SCREEN) (ink.tsx:~2177) and the reset in ui-tui/src/lib/terminalModes.ts (TERMINAL_MODE_RESET, includes \x1b[?1049l) assume POSIX fd/ordering semantics.
- There are multiple exit paths that must not double-fire: the app's
die() (ui-tui/src/app/useMainApp.ts:~392 -- ink exit() + process.exit(0)), setupGracefulExit's SIGINT handler + resetTerminalModes() (ui-tui/src/entry.tsx:95-112), and signal-exit's onExit(this.unmount) (ink.tsx:344).
On Windows conpty these assumptions don't hold: no /dev/tty, Ctrl+C delivery in raw mode differs, and conpty's alternate-screen / cursor-restore semantics differ. The "exactly one EXIT_ALT_SCREEN, ordered before the resume hint" guarantee breaks, so a redundant/mis-ordered alt-screen-leave plus the final render bleeds onto the primary buffer -> overlapping output.
Steps to reproduce
- Windows, PowerShell (Windows Terminal or conhost).
raven (enter TUI).
- Press Ctrl+C to exit.
- Prompt returns with garbled / overlapping leftover TUI output.
Expected
Exiting the TUI (Ctrl+C or otherwise) restores the primary screen cleanly, same as POSIX.
Workaround
cls (Clear-Host) clears the overlap. If input is also affected (mouse modes left on), open a fresh tab.
Suggested direction
Make the alt-screen leave + terminal reset emit exactly once and ordered before the resume hint on Windows conpty too -- i.e. give the shutdown ordering guards (detachForShutdown / isUnmounted / cancel-pending-render) a Windows path instead of relying on POSIX signal-exit timing, and cover the cleanup the win32 early-return (ink.tsx:2409) skips.
Environment
Windows, PowerShell (conpty). Reproduced by a user on Ctrl+C exit.
Summary
On Windows (PowerShell / conpty), exiting the TUI with Ctrl+C leaves the terminal garbled -- the last TUI frame overlaps the shell prompt (stale content bleeding onto the primary screen).
clscleans it up, so it is a terminal-restore problem on exit, not lost data.Why (from the code)
The TUI renders into the alternate screen (
\x1b[?1049h). Clean exit must leave the alt screen and reset modes exactly once, ordered before the resume hint -- otherwise the final frame / a redundant alt-screen-leave lands on the primary buffer as overlapping output.That teardown is carefully tuned, but for POSIX:
ui-tui/packages/hermes-ink/src/ink/ink.tsx--gracefulShutdown/cleanupTerminalModes/detachForShutdownexist specifically to avoid "2-3 redundantEXIT_ALT_SCREENsequences landing on the main screen AFTERprintResumeHint()" (see thedetachForShutdowndocstring, ~L1275-1290). The ordering guarantee is validated againstsignal-exit's deferred-unmount behavior.win32branch (ink.tsx:2409) is an early return in the stdin-drain helper ("No /dev/tty on Windows; CONIN$ doesn't support O_NONBLOCK"), so Windows skips part of the POSIX cleanup path.writeSync(1, EXIT_ALT_SCREEN)(ink.tsx:~2177) and the reset inui-tui/src/lib/terminalModes.ts(TERMINAL_MODE_RESET, includes\x1b[?1049l) assume POSIX fd/ordering semantics.die()(ui-tui/src/app/useMainApp.ts:~392-- inkexit()+process.exit(0)),setupGracefulExit's SIGINT handler +resetTerminalModes()(ui-tui/src/entry.tsx:95-112), andsignal-exit'sonExit(this.unmount)(ink.tsx:344).On Windows conpty these assumptions don't hold: no
/dev/tty, Ctrl+C delivery in raw mode differs, and conpty's alternate-screen / cursor-restore semantics differ. The "exactly one EXIT_ALT_SCREEN, ordered before the resume hint" guarantee breaks, so a redundant/mis-ordered alt-screen-leave plus the final render bleeds onto the primary buffer -> overlapping output.Steps to reproduce
raven(enter TUI).Expected
Exiting the TUI (Ctrl+C or otherwise) restores the primary screen cleanly, same as POSIX.
Workaround
cls(Clear-Host) clears the overlap. If input is also affected (mouse modes left on), open a fresh tab.Suggested direction
Make the alt-screen leave + terminal reset emit exactly once and ordered before the resume hint on Windows conpty too -- i.e. give the shutdown ordering guards (
detachForShutdown/isUnmounted/ cancel-pending-render) a Windows path instead of relying on POSIXsignal-exittiming, and cover the cleanup thewin32early-return (ink.tsx:2409) skips.Environment
Windows, PowerShell (conpty). Reproduced by a user on Ctrl+C exit.