Three of the four terminal gaps. The fourth is recorded as owed rather
than faked; see below.
**THE STABLE CLASSIFICATION WAS INCOMPLETE.** Cursor motion still
advanced the mapping revision: `restore_cursor`, `horizontal_tab`,
`move_vertical`, `move_horizontal`, `set_col` and `set_row` all called
`changed()`. Moving the caret denotes nothing new, and a child that
merely repositions its cursor would have cancelled a drag. All six take
the display-only path now.
Worse, **rewriting the same glyph under another style advanced it**,
which is precisely the control SS5b requires to hold. `write_character`
now compares the glyph before writing --- sampled BEFORE
`clear_wide_at`, which blanks a cell that is part of a wide pair and
would otherwise make every rewrite look like a change. That ordering
was found by instrumenting the failing row, not by reading the code.
**THE SNAPSHOT CARRIED DOCUMENT-ONLY STATE FOR TERMINALS.**
`view_top`, `view_left`, wrap, content columns, fold policy and folds
describe a document projection and take no part in a terminal's, where
the child's screen decides the mapping. They live inside the `Document`
arm now; only common geometry --- buffer identity, rows, columns ---
stays outside.
**AND THE REVISION WAS NOT PUBLICATION-CONSISTENT.**
`view_mapping_identity` read the LIVE screen revision while
`projection_ref` returns the last PUBLISHED cells, so buffered output
under synchronized-output would stamp displayed cells with authority
they were never painted under --- a frontend echoing a generation
matching nothing it can see. `ScreenProjection` carries
`mapping_revision` now and the published value is what is read.
**The witnesses were separated across the seam**, which review named
exactly: `screen.rs` proved the counter, the daemon proved enum
selection, and a `view_mapping_identity` returning a constant would
have left both green. A daemon-level row now drives real events through
a panel terminal and asserts the daemon's generation moves on a new
glyph and holds across a style-only rewrite and across cursor motion.
**OWED, NOT DONE: the scroll-anchor row.** The anchor is in the key,
but three attempts failed to drive a scroll from this fixture ---
`scroll_lines` wants a viewport the projection registers on its own
schedule, and `scroll_view` with an explicit size reports no movement
after forty line feeds. Recorded in the ledger rather than faked or
quietly dropped: without it, a constant ANCHOR alongside a live
revision still passes every terminal row that exists.
The two test hooks are `#[doc(hidden)] pub`, not `#[cfg(test)]`,
because the rows needing them are integration tests and those link the
library without `cfg(test)`.
Verified: focused suite 37/37, `cargo test --lib` 1945 green, clippy
clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
All four closure gaps in one correction. Three of them are defects in
what I committed as G1-G4; the fourth overturns a claim I made about
what could not be witnessed.
**THE KEY IS NOW EXACT, NOT PROBABILISTIC.** It was a `DefaultHasher`
digest, so authoritative equality rested on the absence of collisions
--- and a collision silently ACCEPTS a stale gesture, which is precisely
the failure the key exists to prevent. It is a `PanelMappingSnapshot`
struct compared structurally now. The emitted `mapping_generation`
stays a `u64` on the wire; only the daemon's own comparison changed.
**THE TERMINAL DOMAIN WAS ABSENT, AND THE BUFFER REVISION WAS WRONG.**
The key hashed the panel buffer's content revision for every target
kind. For a terminal that is doubly wrong: SS5b says the buffer revision
does not decide the mapping, and what does --- the screen --- was not
consulted at all. `PanelMappingContent` now splits by kind, and
terminals carry the screen's mapping revision plus the view's scroll
anchor.
That revision had to be built. `Screen::generation` cannot serve:
it advances from 39 sites including style, title, bell, tab stops and
cursor motion, none of which changes what a coordinate denotes.
`Screen` now carries `mapping_revision`, and the classification FAILS
SAFE --- `changed()` bumps both by default, and only the eleven
explicitly display-only arms call `display_only_changed()`. Anything
unclassified is treated as content, because over-cancelling a gesture
is a nuisance while under-cancelling one lets a stale coordinate reach
a child.
**"NO PRODUCTION PATH REACHES A TRANSPOSITION" WAS WRONG.** I recorded
the rows/cols product mutation as unwitnessable and kept the separate
hashing on principle. Resize plus redeclare reaches it: 4x80 -> 8x40
holds the area at 320 while swapping the dimensions, and
`last_content_cols` is not refreshed until the next render, so the two
grid fields are isolated. The row exists and the product mutation now
fails.
**G3 WAS INCOMPLETE.** It covered idle and cursor only. Focus is added
at the daemon level --- the tempting error is folding the whole frame,
which carries a `focused` flag, into the key. Styling is pinned
structurally instead: the snapshot has no style field, so there is
nothing a recolour could touch. The terminal controls live in
`screen.rs`, at the level the classification lives, with a positive
half so a revision that never advanced at all cannot pass them.
**AND MUTATION TESTING FOUND ANOTHER UNWITNESSED BRANCH.** Routing
terminal panels through the DOCUMENT arm left all thirty-five rows
green --- the `is_terminal` branch had no daemon-level witness at all.
A row now pins that the snapshot picks its domain by target kind, and
that mutation fails.
Mutations: display-only events bumping the mapping revision (the
screen-level control fails); terminal panels keyed on the buffer
revision (the domain row fails); rows*cols as an area (the
transposition row fails); plus G1-G4's original five, still biting.
Verified: focused suite 36/36, `cargo test --lib` green, clippy clean.
Full `--protocol` gate reserved for the checkpoint after bilateral
gating, per the standing procedure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Require the fixed C-c escape before editor-local terminal bindings, reject context-implicit Lua operations from document windows, and make controller replacement atomic per frontend. Borrow screen rows during view projection instead of deep-cloning retained history, preserve view anchors through zero-area layouts, and remove redundant detach paths.
Add focused child-input coverage for unescaped bound keys and C-c C-c, plus controller, zero-area, context-error, and clone-free projection assertions.
Co-authored-by: OpenAI Codex <codex@openai.com>
Publish retained row metadata, alternate-screen identity, cursor, title, and
cells through the same synchronized-output generation gate. This gives each
Stage 2 view one coherent projection without borrowing or mirroring mutable
screen state.
Co-Authored-By: Claude <noreply@anthropic.com>
Reject C0/C1 controls before terminal text reaches screen cells, and preserve
the released button code in SGR mouse reports.
Remove dead screen branches, keep logical-line allocation saturating, and clear
round-trip input state when pruning externally removed terminal buffers.
Dispatch ESC D, ESC E, and ESC M as typed full-screen IND, NEL, and RI
operations while preserving the line-oriented parser contract.
Extend both the every-split parser matrix and parser-to-screen integration test
so missing dispatch fails behaviorally rather than only at compile time.
Add typed IND, NEL, and RI operations with exact screen semantics, preserve
application tab stops across resize, and default terminal children to the
supported xterm-256color capability set.
Make shutdown liveness acceptance portable with kill(pid, 0), and document the
public TerminalScreen methods consumed by later stages.
Add compatibility-preserving full-screen ANSI operations, the bounded terminal
screen and input encoders, and a transactional TerminalManager owning one
read-only identity buffer, PTY process, and screen per session.
Drain terminal-owned process events before process.after-tick, retain exact
final output and PID/outcome annotations, reap killed buffers and shutdown
children safely, and enforce buffer-owned read-only checks across ordinary,
host, undo/redo, and CRDT mutation paths.
Cover split parser and grapheme boundaries, screen/reflow/history invariants,
device responses, lifecycle cleanup, and a real adversarial alternate-screen
PTY. Record the fully gated Stage 1 delivery and downstream TUI/GPU contracts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>