mux: read-screen reports the rendered viewport (fixes the macOS smoke-attach banner divergence)
Fixes main's persistent test (macos) failure in scripts/smoke-attach.py (assert_client_matches_server), which reproduces deterministically with SHELL=/bin/bash scripts/smoke-attach.py (3/3 before, 5/5 pass after).
Root cause (verified in code by the /fable judge, not just observed): the attach mirror was correct; the comparison oracle was wrong. Terminal::plain_text() formats the active screen's full page list including scrollback (null-selection ScreenFormatter over the .screen point space), while clients render the RenderState viewport. When macOS bash's "default interactive shell is now zsh" banner scrolled into scrollback and \e[H\e[2J repainted the prompt, read-screen kept reporting the stale banner rows. The spec already promised read-screen output "does not include prior scrollback", so the old behavior violated its own contract.
Fix: read-screen uses a new Terminal::viewport_text() built on the terminal formatter's read-only selection path (GHOSTTY_POINT_TAG_VIEWPORT, (0,0)..(cols-1,rows-1), unwrap:false). A first-round RenderState::update approach was rejected in review because that snapshot consumes per-row dirty flags and would starve a concurrent TUI renderer's row refresh in non-headless sessions; viewport_text_does_not_clear_render_dirty_state locks in the read-only property.
Two-commit red/green: commit 1 adds the failing socket regression (scrollback + clear-screen → viewport reporting), commit 2 the fix. No ghostty submodule changes (the formatter options already existed in the C API).
Verified: cargo fmt/clippy/test workspace-clean (121 tests), smoke-tui, SHELL=/bin/bash smoke-attach.py 5/5. /fable judge: APPROVE after one REVISE round.
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.
[!NOTE]
Medium Risk
Changes the observable read-screen contract (intentionally aligning with spec); low blast radius but affects attach/smoke oracles and any client that assumed scrollback-inclusive text.
Overview
read-screen now returns the rendered viewport instead of plain_text(), which still dumps the active screen including scrollback. That mismatch caused attach/smoke comparisons to see stale rows (e.g. macOS bash banner) after content scrolled away and the screen was cleared.
Adds Terminal::viewport_text(), formatting the full viewport via the terminal formatter’s read-only selection path (GHOSTTY_POINT_TAG_VIEWPORT, unwrap: false) so inspection does not consume RenderState dirty flags. Selection helpers gain selection_text_with_tag_options for configurable unwrap/trim; plain_text() docs now point callers at viewport_text() for viewport-only dumps.
Regression coverage: viewport_text_does_not_clear_render_dirty_state and a control-socket test (scrollback + clear-screen → prompt on first line).
Reviewed by Cursor Bugbot for commit 40347a814de0fa7dc950dcccb0165cba52f35807. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
read-screen now returns the currently rendered viewport (not scrollback). This fixes the macOS smoke-attach mismatch and aligns with the command spec.
- Bug Fixes
mux-core:read-screennow callsTerminal::viewport_text()instead ofplain_text().ghostty-vt: Addedviewport_text()using the read-only formatter overGHOSTTY_POINT_TAG_VIEWPORT; it does not clear render dirty flags.- Tests: Added
viewport_text_does_not_clear_render_dirty_stateand a control socket test to verify viewport output after scrollback + clear-screen.
Summary by CodeRabbit
-
New Features
- Added a way to read only the currently visible terminal viewport as plain text.
- Screen-read responses now return the rendered viewport instead of the full scrollback history.
-
Bug Fixes
- Improved screen-reading behavior after clearing scrollback so the returned text matches what is actually on screen.
- Confirmed viewport reads do not affect the terminal’s render-dirty state.