Files
orca/tests
Jinwoo Hong f559c0588a fix(terminal): ground a program that dies with input modes armed on the normal screen (#22739)
* fix(daemon): rebase durable checkpoints on the live terminal

A durable checkpoint was folded from the previous checkpoint plus recorded
output, so it inherited that checkpoint's modes forever. After a daemon
restart killed a full-screen TUI and a new process started inline, the chain
kept the dead TUI's alt screen and mouse tracking (?1049h ?1003h ?1006h)
while the live emulator was clean. Every reattach and getBufferSnapshot
served the stale chain, the renderer re-armed mouse tracking, and wheel
scrolling went to a program that never asked for it: scrolling froze.

Each full checkpoint is now the live snapshot verbatim (screen, layout,
alt frame, modes, owner) with only the normal-buffer rows live has evicted
taken from the durable replay. A checkpoint can no longer carry a dead
process's modes, and checkpoints already poisoned on disk heal on the next
compaction.

- The first fold after a cold restore replays the same seed segments live
  was given, so rows line up even over a dead TUI's alt screen.
- Idle zero-record folds keep the disk copy when it already agrees with
  live, so quit and relaunch bursts don't replay every session.
- Held teardown bytes are already in the drained records and the live
  snapshot, so they are no longer replayed twice or appended as a tail.
- The bounded getBufferSnapshot path honors the requested depth even when
  the live window is deeper, without phantom link rows.
- The fold's ownership scanner and frame merge are removed; owner and
  frame come from live.

* fix(terminal): one process-boundary ground for every known or proven boundary

Three copies of the "the process that armed these modes is gone" reset had
drifted: the cold-restore seed cleared only pen and mouse, the recovery
barrier used the renderer's dead-TUI profile, and the cold-restore payload
had none. A cold restore therefore left the dead process's focus reporting,
bracketed paste, application cursor and keypad modes armed in the live
emulator, the first checkpoint, and main's mirror. And the seed wrote the
dead process's torn escape after the reset, so the new shell's first bytes
could complete it (for example retitling the pane).

PROCESS_BOUNDARY_GROUND replaces them: CAN, leave the alt screen without
moving the normal-buffer cursor, every mouse protocol and encoding off,
focus/paste/app-cursor/keypad off, cursor shown and style reset, kitty
popped, SGR reset, grounded DECSC. It stays inert for the lifecycle
scanner. The seed, the recovery barrier, and the cold-restore payload all
use it, and the seed no longer carries the torn tail.

The first fold after a cold restore now always rebases on live, because
focus and keypad are not in TerminalModes and the zero-record shortcut
could not see them differ.

* fix(terminal): ground a program that dies with input modes armed on the normal screen

The daemon's in-stream crash detector only fired when a program died with
the alternate screen up. A normal-buffer program that armed mouse tracking,
focus reporting, keypad or kitty keyboard flags and exited without
disabling them was cleaned up only in the renderer, so the daemon kept the
modes and re-armed them on the next reattach, mobile included (#13077's
garbage-at-the-prompt family).

The lifecycle scanner now tracks armed input modes (mouse protocols and
encodings, ?1004, ?66, and kitty flags as per-screen stacks that mirror
xterm's main/alt swap and its 16-entry cap). ?2004 and ?1 are excluded:
shells arm them at their own prompts. Modes armed when a command starts
(OSC 133;C) count as the shell's, so a prompt that leaves modes on never
triggers. At OSC 133;D the trigger is now "alt screen or a program-armed
input mode", still one-shot and still gated by the shell proof, and the
existing PROCESS_BOUNDARY_GROUND is recorded through the stream so live
and durable history change together.

WSL panes spawn wsl.exe, which the shell proof does not recognise, so the
detector never grounds them; a test pins that and the renderer keeps
covering them. The mouse-leak e2e now keeps its arming process alive until
the live pane is checked, because the daemon grounds a proven exit.

* fix(terminal): keep shell- and host-armed input modes through the process-boundary ground

ConPTY arms focus reporting (?1004h) before the first prompt, and the live
recovery ground cleared it for the rest of the pane. The barrier now re-arms
the modes that were on at OSC 133;C right after the ground, so only the dead
program's modes are reset.

* fix(terminal): re-assert only modes the shell or host armed outside a command

A mode a program leaked past a refuted proof was still on at the next OSC
133;C, so the baseline snapshot re-armed it after a later ground. Record who
armed each mode instead: only enables outside a command (before any marker,
or between 133;A/D and C) form the baseline.

* fix(daemon): keep OSC links and kitty flags through durable checkpoint folds and trims

Stop seeding persisted OSC link ranges into the fold replay: they index the
base buffer, so rows evicted by pending output left a link on the wrong text.
The serializer already writes OSC 8 into the ANSI the fold replays.

Re-apply kitty keyboard flags when replaying a snapshot for trimming, since
rehydrateSequences omits them.

Bound a smaller restore request by trimming the committed checkpoint instead
of re-reading disk and rebasing the live window at a smaller depth.

* test(daemon): follow the isFirstTake rename in the process-boundary ground suite

* refactor(daemon): drop the unreachable deep-live branch from the durable fold

The live window's override cap now derives from the restore depth, so live can
never be deeper than the fold. pendingRecords and isFirstTake are required.

* test(daemon): pass pendingRecords to the process-boundary ground fold

* fix(terminal): reset alt-screen kitty flags in the process boundary ground

Kitty keyboard stacks are per screen, so resetting only after ?1049l left
a dead TUI's alt-screen flags for the next alt-screen app. Also drop the
inert CAN from the ground (every site grounds after complete bytes) and
correct two stale comments.

* fix(terminal): track input-mode ownership in one map

Each armed mode now has one owner: host (before any marker, or a prompt a
133;C proved), prompt (unproven until C), command, or stale (left past a D).
Host arming is sticky, 133;D demotes command modes (the one-shot), and the
ground re-asserts only host modes. Fixes a D without C triggering on host
modes, an ESC c mid-command turning later enables into host modes, and a
program's repeated host enable dropping host ownership. The reattach e2e now
keeps the arming program alive so only the reattach reset can disarm it.

* refactor(terminal): stop treating kitty flags as host state

fish, the one shell that pushes kitty flags at its prompt, pops them before
running a command and re-pushes at the next prompt, so the ground never needs
to restore them. Only host private modes are re-asserted now.

* fix(terminal): keep host input-mode ownership across RIS

ConPTY answers a mid-command ESC c by re-sending ?1004h, which reset() had
recorded as the command's, so the ground turned host focus reporting off for
the rest of the pane. RIS now drops only non-host ownership.

* fix(terminal): let only the host own focus reporting and leave it in the ground

Host ownership covered every mode armed before the first marker, so a tmux
that died with mouse on had it re-armed by the ground. And the re-assert's
?1004h enable made the runtime's ownership mirror revoke, so remote owners
never settled on Windows. Only ?1004 can be host-owned now, and the ground
skips its ?1004l instead of turning it off and back on, so injected bytes
carry no enables.
2026-09-25 00:50:04 -04:00
..