Emit native terminal resize events and cache frame dimensions
Class
Native resize notifications were discarded, so GraphCoder queried dimensions every frame. A resize while it was suspended could remain invisible after returning to the foreground.
Why it cannot recur
The input stream now produces InputEvent::Resize. Unix registers SIGWINCH before taking initial dimensions and queries its owned terminal descriptor after a notification. Successful resume invalidates input under the existing lifecycle mutex: the typed Input::Resumed result reaches the stream before another read is admitted, forcing a size recheck even without SIGWINCH.
Windows surfaces WINDOW_BUFFER_SIZE_EVENT through ConsoleRead and the existing persistent waiter. A coalesced resize flag shares the handoff's real mutex and waker registration and takes priority over queued bytes. Events caches dimensions and suppresses duplicates. GraphCoder subscribes before reading the initial cache and updates frame dimensions from events; its per-frame size query is deleted.
Verification
- Real-lock models cover resize/finish/wakeup, full-queue priority/coalescing and exactly one resume observation before read admission. Broken controls remove registration atomicity, prioritize bytes or omit resume invalidation.
- A shrinking property checks arbitrary dimension sequences: exactly the changes emit events and the cache always matches the latest size.
- PTYs require SIGWINCH for an ordinary resize and suppress duplicates. A separate child stops, changes to 101 x 37 without SIGWINCH, continues and must report that resize before receiving any key.
- Real ConPTY changes to 101 x 37 without a key. Break, close and blocked-input restoration also pass.
Validation
| Check | Result |
|---|---|
| Linux terminal units, f39123374 | 15/15, 0.07 s; build 1.96 s |
| Linux PTY input and signal lifecycle, f39123374 | 7/7 in 0.89 s; 8/8 in 0.04 s; build 3.37 s |
| Real-lock models and negative controls, f39123374 | 44/44, 0.84 s; build 2.67 s |
| Windows GNU touched TUI libraries/tests, f39123374 | pass, 8.56 s; complete focused batch 31.7 s |
| Linux GraphCoder CLI, 0fdb08d61 | 5/5, 1.08 s; build 1m42s |
| Windows GNU Harness/TUI/native-runtime/GraphCoder, 0fdb08d61 | pass, 37.12 s |
| Real Windows input | 6/6, 0.22 s; build 10.87 s |
| Real ConPTY restoration, latency and resize | pass, 2.71 s |
| macOS terminal units, f39123374 | 15/15, 0.32 s |
| macOS PTY input and signal lifecycle, f39123374 | 9/9 in 0.90 s; 8/8 in 0.18 s |
| macOS terminal integration, f39123374 | 14/14, 0.60 s; combined native build 13.94 s |
| macOS production reader/model/property/native checks | 15/15, 0.19 s; direct target compile 1.09 s |
The final private lifecycle fix changes neither GraphCoder code nor the public TUI API. Its focused tests and GNU proof cover the changed crate; the earlier CLI/four-crate results remain applicable. Windows production input is unchanged by that Unix-only resume fix.
Measurements
Linux PTY input at f39123374: p50 21.282 µs, p99 37.838 µs, max 85.509 µs; zero idle wakeups and 349.621 µs runtime-thread CPU over 200 ms. macOS kqueue at f39123374: p50 1.458 µs, p99 6.000 µs, max 53.209 µs; zero idle wakeups. Windows waiter handoff: p50 2.4 µs, p99 31.4 µs; zero warm allocations and zero idle wakeups. End-to-end 200-key ConPTY latency: p50 179.4 µs, p99 1706.4 µs.
Commands: cargo test --locked -p acyclic-harness-tui --features tui --lib term::; cargo test --locked -p acyclic-harness-tui --features tui --test terminal_input --test terminal_signals; RUSTFLAGS='--cfg acyclic_harness_tui_loom' cargo test --locked -p acyclic-harness-tui --features tui --lib term::; cargo test --locked -p graphcoder --test cli. Linux and GNU checks run through bs.ps1; real Windows through lb.ps1 and scripts/test-terminal-signals.ps1; macOS over ssh ivar with CARGO_TARGET_DIR=~/mac-target/tui-async. macOS tests were compiled once and emitted binaries rerun directly.
Decisions
- Stacked on #869/#910/#874 until those dependencies merge; preserve published history with ordinary merges.
- Reuse native Tokio signals and the existing console waiter/handoff; no resize timer, additional thread or lock.
- Terminal dimensions are external OS notifications with a per-frame cache. Existing UI configuration remains filesystem driven.
Live steps
- The macOS CLI attempt at this PR head was stopped after global disk exhaustion. A later lane qualification on the updated input branch (916b074859, with filesystem and CLI test sources matching main 66ddfd542) completed its 13m50s build: 1/6 passed, and five failed before input with "source does not support original native work custody" in 1.32 s. This does not establish a passing full macOS CLI qualification for this PR. Portable CLI tests and every focused changed macOS native check above passed.
- Under the updated disk rule, after all lane jobs exited, ~/mac-target/tui-async and ~/tui-async-src were deleted (19 GiB free). No lane target, source copy or tarball remains. Future macOS builds use RUSTC_WRAPPER=/opt/homebrew/bin/sccache and delete their target and source after validation.