Answers review of 14. Framing only. Four of the six reverse something
14 asserted, and a green protocol gate would not have caught any of
them.
**"BESIDE `Present`/`PanelPointer`" WAS POSITIONALLY DANGEROUS.**
"Beside" reads as adjacent, and adjacent insertion shifts every
discriminant below it --- the exact hazard the appended-only rule
exists for. Appended means LAST: `PresentMapped` after `Absent`,
`PanelPointerMapped` after `TextInput`, with a diagram so the next
reader cannot re-derive it wrongly. Field order is stated exactly,
`mapping_generation` is a `u64`, ZERO IS INVALID --- it is what a
default-constructed or half-initialised sender produces, so accepting
it would let a peer opt out of the check by sending nothing --- and the
gate reads `PANEL_MAPPING_MIN_VERSION = 25` rather than a literal.
**BLANKET REFUSAL STOPPED THE WHEEL AFTER ONE TICK.** The first
effective document wheel changes `view_top`, which advances the key, so
the next already-queued tick carries the old generation and is refused:
the panel scrolls once and goes dead until the frontend observes the
new frame. Local terminal scrollback has the same shape.
The discriminator is whether the gesture USES its coordinate.
Coordinate-free gestures --- the document wheel, non-reporting terminal
scrollback --- cannot be mis-aimed by a stale mapping and are EXEMPT.
A child-reported wheel is the opposite case: SGR carries row and
column, so a stale one aims an application action at a cell the user
never pointed at, and it keeps the check. Two-tick witnesses added,
because without them a blanket-refusal implementation passes every
single-event row in the matrix.
**CANCELLATION WAS REACTIVE AND LOSES A RACE.** If the replacement
mapped frame reaches the frontend before the physical `Up`, the
producer resets `pointer_held` and SUPPRESSES THE VERY EVENT that would
have cancelled --- so the daemon is never told, the selection stays
armed, and the child keeps holding its button. It is now PROACTIVE,
triggered by the authoritative key advancing while a gesture is
accepted, and the producer must emit a cancellation tail or retain the
latch rather than clearing first.
That needs state 14 assumed and never specified: an ACCEPTED-GESTURE
LATCH recording whether the `Down` was accepted, whether it reached the
child, and the coordinate, button and encoding a release must match.
Two rules fall out and are ruled here --- a stale `Up` with no accepted
`Down` is INERT, and cancellation NEVER reclaims a controller another
frontend has since taken, because a stale gesture must not steal a live
one's terminal.
**THE EXISTING SCREEN GENERATION CANNOT BE THE TERMINAL KEY.**
`Screen::changed()` bumps from 39 call sites including `SetStyle`,
`Bell`, the tab-stop operations, cursor-only motion and `SetTitle`.
None of those change what a coordinate denotes, so keying on it would
cancel a drag every time the child recoloured a character. A dedicated
terminal mapping revision is defined over projected cell identity,
retained-row identity and the per-view scroll anchor --- with those
five events as explicit STABLE CONTROLS, so a reader who later reaches
for the convenient counter fails a test instead of shipping a cancelled
drag.
**G5'S EFFECTS ARE NOT PROVABLE ON THIS BRANCH**, and 14 claimed them.
`gesture_last_content_cell` and the document/terminal replay exist only
on `panel-pointer-replay` (`pmacs-gpu/src/main.rs:2143` there); the
same struct here is at `:2124` with no such field. The obligations are
split in a table. G5a --- that the key advancing RAISES cancellation
--- stays here on purpose: the trigger is this slice's rule, and moving
the whole row out would leave the proactive ruling with no witness in
the slice that introduces it.
P2s: mutation legs split (wrap vs gutter, terminal content vs
scrollback, G5a-c, G8a/b, G9a/b); G7 given a positive-path mutation;
G11 expanded --- exhaustion must publish `Absent`, clear input
authority, cancel any accepted gesture and LATCH, or a stale panel
stays painted and permanently inert; the v26 correction finished at the
gate and old-peer cells (`:573`); and the SS20 impact statement added
--- hardens an existing panel island, no journey grade changes, no
config, no background work.
**And the pin correction is mine to make: it EXISTS**, at
`src/protocol.rs:1975`, in the ROOT crate's test module rather than
under `pmacs-protocol/` or `tests/` --- which is exactly where I
searched. `message.rs:524` was right and the doubt was wrong; the
contrary claim is removed from both records.
Gates: all eleven green under `env -u TMPDIR` with `--protocol`,
log 20260814T180105Z.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Own branch, own slice, protocol-bearing, runs alone. Framing only; no
implementation. Blocks `panel-pointer-replay`, which blocks GUI arc 1b.
Answers review of revision 13. Every item below reverses or completes
something 13 got wrong.
**GATING IS REFUSAL, NOT FALLBACK.** Revision 13 said a bare
`PanelPointer` from a new peer would be "handled under the old
semantics". That is a BYPASS: it leaves the exact hole this slice
exists to close, reachable by omitting a field. A >= v25 session
sending the legacy event is REFUSED before mutation, and a >= v25
frontend REJECTS a legacy `Present` rather than painting a band it
cannot safely hit-test. Only negotiated <= v24 keeps legacy semantics;
`Absent` stays common to both families.
**ONE AUTHORITATIVE PER-FRONTEND KEY**, used by projection AND inbound
validation, advanced after any mapping mutation and BEFORE the next
inbound pointer is handled --- whether or not anything has rendered.
Comparing against the last EMITTED frame recreates the hole, because a
mutation not yet painted has still changed the inverse mapping.
**STALE TAILS TERMINATE; THEY DO NOT VANISH.** A blanket drop breaks
liveness: a refused `Up` leaves an empty document selection armed with
a stale anchor, and leaves a reporting terminal child HOLDING A BUTTON
FOREVER. Cancellation is now a ruled outcome --- producer latch reset,
daemon selection and click-chain cleanup, and the child's release
delivered at the last coordinate known good. A cancelled gesture is
explicitly not a replayed one: the release is for liveness, and no
selection or scroll effect is applied from the stale event. Stale
BEGINNINGS may still simply drop.
**THE DOMAIN WAS INCOMPLETE.** `view_left` is added, because 1b makes
horizontal scrolling real. "Cursor movement is stable" is now
CONDITIONAL: a cursor move that triggers vertical or horizontal follow
changes `view_top` or `view_left` and therefore does change the
mapping. Terminal panels are ruled explicitly --- their coordinates are
decided by the SCREEN, so output and scrollback movement change the
generation while their buffer revision does not.
**SS5b HAD NO ACCEPTANCE MATRIX.** G1-G11 now cover the foreign edit
before render, every changing and stable domain entry ROW BY ROW, a
selection repaint that must preserve the generation and let a drag
continue, mid-gesture cancellation, v24 and v25 positive controls with
both wrong-family refusals, identical cells across a generation change
still emitting, atomic retention of frame and generation on an invalid
frame, and fail-closed exhaustion. The per-entry enumeration is
deliberate: one aggregate row cannot show WHICH input moved the key,
and a key ignoring `view_left` passes every vertical-only row.
**MAPPED MOTION KEEPS ITS COALESCING TAGS.** A new variant falling
through to the lossless default would put pixel-rate `Move`/`Drag` on a
bounded queue.
**PINS ACCUMULATE.** Revision 13 said the pin "moves", which would
delete coverage of the shape it protects. `PanelPointer` is retained;
exact `TextInput` bytes are added as the previous-final
`FrontendEvent`; the complete nested `PanelFrame(Absent)` bytes are
added as the previous-final `PanelFramePayload`. Recorded honestly: I
could find NO exact-bytes pin for `PanelPointer` anywhere in the tree,
though `pmacs-protocol/src/message.rs:524` says one is in the tests.
Either my search missed it or the doc overclaims; this slice resolves
it either way, since it must add exact pins regardless.
**AND THIS SLICE OWNS THE VERSION CORRECTION.**
`docs/gui-stage1-input-framing.md` now says 1e's `OpenTarget` is
**v26**, with the reason stated at the top. An expected rebase conflict
on `gui-stage1b-pointer-scroll` is not grounds for leaving the
canonical document false --- which is what I argued last round, and it
was wrong. `ADVERTISED_PROTOCOL_VERSION` stays pinned at 20.
Gates: all ELEVEN green under `env -u TMPDIR`, with `--protocol`
(`build-crdt`, `sweep-crdt`), log 20260814T162843Z.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Docs only, and the first commit on the 1a branch. **No ruling changes.**
**1a needs no framing of its own** ---
`docs/gui-stage1-input-framing.md` already governs every Stage 1 slice,
and A1-A9, the eight Q#S1-9 precedence rules, §8's wire contract and
§11's gates are ruled there. What 1a does need is §2 to be TRUE, and it
was measured at `a994f37`, before 1-pre moved almost every GPU-side
coordinate in it. A framing whose ground truth points at the wrong lines
is how an implementation ends up arguing with the tree.
Re-measured at `4f77491`, and split by kind rather than renumbered
wholesale, because the two are not the same claim:
STILL TRUE `FrontendEvent`'s sixteen variants, `PROTOCOL_VERSION =
23`, IME's zero occurrences, the TUI wheel arms (1-pre
touched only `pmacs-gpu`), the handshake preceding the
window, and `Outbox::enqueue`'s closed-return.
MOVED `window_event` `:2734`/655 lines -> `:4450`/FOUR lines;
`translate_key` `:10975` -> `:12053`; "eight arms, rest
fall to `_`" -> three family decision functions over nine
variants. **1a edits `apply_keyboard` and
`translate_key`, not `window_event`.**
**TWO CLAIMS WERE WRONG AT BOTH ANCHORS, so they are corrections rather
than drift.**
**"`KeyEvent.text` is never read" is false, and was false at `a994f37`
too** (`:2800` there, `main.rs:3251` now): the AltGr rule reads it via
`is_layout_text(key.text.as_deref(), pmods)`. The true claim is
narrower and is the one that matters --- **`text` is never read as the
text a keypress INSERTS**, only as a discriminator separating AltGr from
a command chord.
That is load-bearing for A5 rather than cosmetic. §5's rule 2 already
exempts "printable Ctrl+Alt recognized by the existing AltGr rule", so
the precedence table depends on code the section claimed did not exist.
**1a widens `text` from discriminator to payload, and that is the actual
change of kind** --- which "text is never read" hid, along with the one
place the new payload must leave undisturbed.
**A4's exit site moved without its behaviour changing.** 1-pre routed
the idle-Escape quit through `EventOutcome`, so **A4 edits a branch and
a return type in `apply_keyboard` (`main.rs:3219`) and does not touch
`window_event`**, which holds the crate's only executable
`event_loop.exit()` (`main.rs:4452`). And **`EventOutcome` survives
A4**: a native close still returns `Exit`.
The lane records the v24 serialization constraint and the bump's blast
radius up front --- eight version-sensitive failures across six suites
on the last bump, of which CI showed one, because cargo stops at the
first failing target --- together with the rule for sorting them and the
pin that must never fire, `ADVERTISED_PROTOCOL_VERSION == 20`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two review findings, one of them a real defect.
**THE SENTINEL READ COULD HANG FOREVER.** `read_until_sentinel` blocked
with no bound, so a writer or encoder that regressed after `enqueue`
would WEDGE THE GATE rather than redden it --- and a hang is the worst
failure shape there is, because it looks like slowness until the job is
killed. A 30 s `READ_CEILING` is armed on the daemon socket.
The distinction is kept explicit in the code, because collapsing it is
how this fix would undo the design it protects: **the sentinel remains
the success condition and the ceiling is only an error ceiling.**
Arrival is still decided by the sentinel, so the harness never infers
"nothing was sent" from a duration --- the core-count assumption behind
PR #235's CI red is not reintroduced. The ceiling sits far above any
plausible drain, so reaching it means broken, never busy.
M24 proves it fires rather than trusting it: drop the sentinel enqueue
entirely and the row fails in under a second with a diagnostic naming
both candidate causes and the partial transcript, instead of hanging.
**THE STAGE 1a CONSEQUENCE WAS WRONG IN FOUR PLACES.** Every record
claimed A4 would leave `EventOutcome` with one variant, so the type
should go with the Escape branch. It will not, and it should not.
`LifecycleRoute::Exit` --- a native window close --- returns
`EventOutcome::Exit` too. A4 removes the KEYBOARD producer only, leaving
one `Exit` producer.
And **one producer is not one variant**: the type survives because
`dispatch_window_event` must still distinguish `Continue` from `Exit` on
every event it handles --- nearly all must not exit, and the close must.
What A4 actually changes is `apply_keyboard`'s signature. Corrected in
the `EventOutcome` doc, the Escape-branch comment, the framing and the
ledger; the framing's superseded paragraph is deleted rather than
patched, since it also carried the stale "two `event_loop.exit()`
call sites" count. **There is exactly one executable
`event_loop.exit()`**, in `window_event`.
Also: the sentinel-tag comment claimed four modifier bits and used
three. It now says three, wrapping every eight steps, and why that
suffices --- each sentinel is read before the next is issued, so a tag
only has to differ from its immediate predecessor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Revision 10 argued a route-classification transcript satisfied P2
because a route 'names its local effect'. The wheel falsifies it: a
wheel route carries a delta, and whether that becomes a viewport
update, a panel event, a terminal event or nothing at all depends on
State. The route names the family; only running the body names the
effect. Retracted rather than quietly amended, since it was an argument
this document made and review overturned.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Three documents, one finding each.
**`docs/gui-stage1-input-framing.md` -> revision 10.** Revision 9 is the
approved design and revision 10 changes none of it. It records ONE scope
correction that could not be seen from the design: P1 has a second
structural exception, for the keyboard family alone, and it is winit's
rather than this seam's. `KeyEvent` carries a `pub(crate)
platform_specific` field, so no `WindowEvent::KeyboardInput` can be
constructed outside winit. Bounded three ways rather than accepted
whole --- it does not reach the pointer families (`DeviceId::dummy()`
exists for exactly this, checked BEFORE writing the exception down), the
family's only decision is factored into `route_key_action` and witnessed
directly, and what stays uncovered is one pattern arm with no logic.
Also records that P3 is now MEASURED: deleting the whole delegation
leaves all 256 `pmacs-gpu` tests green, not merely the 13 new rows.
**`docs/active-work.md`** --- the lane moves to IMPLEMENTED with the
four commits, the shape, the verbatim-move method, and the gate result.
**`docs/agent-handoff.md`** --- the stray-marker hazard gains what this
run earned: `scripts/gate` DOES NOT ISOLATE `TMPDIR`. It isolates the
target directory and five ambient roots, so `tempfile::tempdir()` still
lands under whatever `/tmp` happens to contain, and the hazard therefore
reproduces INSIDE a gate run --- which is how it surfaced here, on a
lane that touches only `pmacs-gpu/src/main.rs`. The bullet now carries
the discriminating command pair (`TMPDIR=/tmp` 0/2 versus a marker-free
root 2/2) rather than only the narrative, because a rerun establishes
nothing about this and the pair establishes everything. Isolating
`TMPDIR` is assigned to the gate lane, not to whichever feature PR trips
over it next.
One code change rides along: `EventOutcome`'s doc comment said
`event_loop.exit()` is called in "exactly one place", which is true of
the function and false of the call sites --- there are two, both inside
`window_event`. Stated precisely now, since the whole point of the
sentence is that a reader can check it by grep.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The framing and the lane are this branch's FIRST commit, which is the
arc's §5 rule for every PR in it and the reason this branch exists
before any code: revision 9 was approved as an untracked file, and an
approved framing that lives in one worktree is not portable.
Revision 9 landed after EIGHT rejected revisions. The through-line of
those rounds is worth carrying: each early draft turned a fresh source
audit into SCOPE LABELS --- "DroppedFile", "translate_key completion",
"no behaviour change" --- and review kept asking the same question,
which is what the label means when someone has to test it. Revision 9 is
a contract/witness/mutation matrix over every numbered clause, split
into CHANGE clauses that fail today and PRESERVATION clauses that pass
and must keep passing.
Several findings only existed because the contracts were written down.
"Send Detach before exit" was unexecutable against an outbox whose
`enqueue` rejects once closed. A one-slot terminal reservation
contradicted the FocusLost-before-Detach ordering two sections above it.
"Terminal result after the commit resolves" was not total over a
pipeline with three legitimate exits that never commit --- a claimed
listener, a cleared handler slot, and the default handler's `open_async`
returning immediately. None of those are visible from a scope label.
This branch is 1-pre ALONE: the input seam, no behaviour change. Its
evidence is a headless routing harness recording routing decisions,
outbound events AND local effects, with production `window_event` as a
thin call-through. P3 is recorded as an accepted structural exception:
a headless test cannot construct `ActiveEventLoop`, so the delegation
itself is a code-review invariant rather than a tested one, and saying
so is better than a mutation that cannot exist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai