Skip to content

Windows: TUI exit (Ctrl+C) leaves the terminal garbled -- alt-screen not restored cleanly on conpty #205

Description

@arelchan

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

  1. Windows, PowerShell (Windows Terminal or conhost).
  2. raven (enter TUI).
  3. Press Ctrl+C to exit.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions