Skip to content

fix(tui): reserve the bottom row on legacy Win10 conhost - #291

Merged
plombeer31 merged 1 commit into
mainfrom
fix/discord-conhost-render-guard
Aug 31, 2026
Merged

fix(tui): reserve the bottom row on legacy Win10 conhost#291
plombeer31 merged 1 commit into
mainfrom
fix/discord-conhost-render-guard

Conversation

@plombeer31

@plombeer31 plombeer31 commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

What

Win10 reports against v0.4.1/v0.4.2 describe two symptoms the #243 synchronized-update bracketing did not close: residual "shaking" on legacy conhost, and a duplicated last row under PowerShell. Both fit one mechanism bracketing cannot address:

  • the TUI pins its root box to height={rows}, so every frame is exactly terminal-height;
  • the frozen inbox conhost on Win10 scrolls when a full-height frame writes into its bottom row (it ignores DEC 2026, and a scroll is real buffer movement, not tearing);
  • once the viewport slides one line, Ink's repaint cursor math is off by one — the UI shakes, and the row that scrolled away leaves the last row painted twice.

The fix is a conservative render guard, active only where the problem lives:

  • Detection (src/tui/legacy-conhost.ts): win32 and neither WT_SESSION (Windows Terminal) nor TERM_PROGRAM (VS Code, mintty, …) set — i.e. a plain cmd/PowerShell conhost window.
  • Guard: useTerminalSize reports one row fewer on such consoles (real TTY only), so no frame ever touches the bottom terminal row and there is nothing left to scroll. All modern hosts keep the full height; non-TTY streams (tests, pipes, CI) are untouched.
  • Hint: a one-time startup transcript line recommends Windows Terminal and names the escape hatch.
  • Escape hatch: ATOMIC_AGENT_CONHOST_GUARD=0 disables the guard where the detection misfires; =1 forces it on anywhere (useful for QA on any OS).

No behaviour changes on macOS/Linux/Windows Terminal/VS Code: with the variable unset off-conhost, the rendered frame is unchanged from main.

Test evidence

  • New unit coverage: src/tui/legacy-conhost.test.ts (detection truth table, clamp floor, hint gating) and src/tui/hooks/use-terminal-size.test.ts (TTY-only clamping, defaults). The clamp test was verified to fail with the guard neutered.
  • Full src/tui suite: 238 files / 2551 tests pass (the single unhandled-error notice is the known llama-server spawn EACCES noise from local-models-orchestrator-auto-update.test.ts, present on main).
  • npm run lint (tsc) clean.
  • Live PTY verification (pyte, 80×24, TUI run from source): guard off — bottom row carries the hint strip as on main; ATOMIC_AGENT_CONHOST_GUARD=1 — frame is 23 rows, the bottom PTY row is never written during the whole run, and the startup hint renders in the transcript.

Windows QA note

No Windows repro was available here, so this needs a pass on a real Win10 box — cmd/conhost and PowerShell/conhost should show no duplicated last row and no shaking with one blank bottom row + the hint; Windows Terminal must be pixel-identical to main with no hint. ATOMIC_AGENT_CONHOST_GUARD=0 on conhost reproduces main's behaviour for comparison.

Reported on Discord: https://discord.com/channels/1515649306781155428/1515650562430079048/1541830173358301205 and https://discord.com/channels/1515649306781155428/1515651297691308183/1542897974445744159

The v0.4.2 synchronized-update bracketing (eb690e7) stopped the frame
tearing but not the Win10 conhost reports: residual shaking, and a
duplicated last row under PowerShell. Both fit one mechanism the
bracketing cannot address — the TUI pins its root to `height={rows}`,
and the frozen inbox conhost scrolls when a full-height frame writes
into its bottom row; DEC 2026 is ignored there, and a scroll is buffer
movement, not tearing. Once the viewport slides one line, the repaint
cursor math is off by one: the UI shakes and the row that scrolled away
leaves the last row painted twice.

No Windows machine was available to reproduce, so the change is the
conservative guard: when the host looks like a legacy conhost (win32,
and neither WT_SESSION nor TERM_PROGRAM set), `useTerminalSize` reports
one row fewer, so no frame ever touches the bottom terminal row and
there is nothing left to scroll. Every modern host — Windows Terminal,
VS Code, anything setting those variables — keeps the full height, and
non-TTY streams (tests, pipes, CI) are untouched.

A one-time transcript hint on such consoles recommends Windows Terminal
and names the escape hatch: ATOMIC_AGENT_CONHOST_GUARD=0 disables the
guard, =1 forces it on anywhere — which is also how it was verified: a
PTY+pyte run at 80x24 shows a 23-row frame, a never-written bottom row,
and the hint; with the variable unset the frame is unchanged from main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@plombeer31
plombeer31 merged commit f93f255 into main Aug 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant