From cda078fa9195b9783d189f2d3304ed2430d2af7b Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Thu, 20 Aug 2026 20:25:22 +0200 Subject: [PATCH] docs(framing): SS5a revision 15 --- answers review of 14, mostly by correction Four blocking gaps, all of them cases where revision 14 asserted a guarantee its own mechanism did not provide. 1. Q#BP-R4's release half was bookkeeping in an effect's clothes. It ruled that a chrome Up "consumes" the latch and claimed this stops a child being left button-down. It stops nothing: update_accepted_gesture does `let _ = state.consume_accepted_gesture()` at daemon.rs:1067, and terminal chrome returns at editor.rs:2862-2870, BEFORE apply_terminal_gesture at :2899. P3/P4 asserted only that the latch empties, so they would have passed while the child heard nothing --- the failure looking fixed because the bookkeeping looked right. Now ruled as TERMINATE, with a per-target completion effect (child release, or local terminal-selection completion, or the document completion SS5b already assigned here), a no-duplicate leg, and P3 written explicitly against the latch-only assertion. 2. The pending-release slot had an invariant and no ordering. The seam replays before it arms (daemon.rs:2608-2616), so a check inside arm_accepted_gesture could only fire after the new press had already reached the child. Drain order is now part of the ruling: before any subsequent panel-pointer effect, before detach teardown, before the next frame --- with an old-release-before-new-press row that reads the child's byte order, and the invariant demoted to a backstop. 3. The ground truth was knowingly false, and the workflow requires scouting before framing. RE-MEASURED at 2c0d3ff instead of deferred. Two clauses the old table called MISSING are DONE on this branch --- panel selection and terminal mouse reporting --- the vertical wheel works, and the horizontal wheel turns out to be an UNRULED gap sitting in a catch-all arm with no comment. Revision 15 names it rather than ruling it, because inventing a ruling inside a re-measurement is how an unexamined default becomes a decision. The 72da24a table is kept as history. 4. "This lane MOVES the grade" was wrong against the authoritative scorecard. Step 5/GPU is floored Partial by 5(a) IME, so completing 5(c) cannot lift it, and step 8 is already Works on all three columns. The draft had read this section's MISSING column as if it were the scorecard; COHERENCE.md is the only thing that decides grades. Also drops a false TUI claim --- dispatch_semantic_panel_pointer has exactly two callers, both FrontendEvent arms, so only semantic legacy and mapped peers reach it --- and clears two merged-state contradictions: SS5b no longer says "Nothing implemented", and the lane no longer says the v26 correction is owed by 1b while the block below records SS5b making it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai --- docs/active-work.md | 17 ++- docs/bottom-panel-framing.md | 206 ++++++++++++++++++++++++++++------- 2 files changed, 179 insertions(+), 44 deletions(-) diff --git a/docs/active-work.md b/docs/active-work.md index 11b6ba5..5cc2ffd 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -281,7 +281,8 @@ from #171 and #215 — the correction the 1b lane missed, honoured here. **`githubsucks/panel-pointer-replay` is the authoritative tip** (the ref, not a SHA). Recover with `git fetch githubsucks && git checkout panel-pointer-replay`. -- **No PR yet. Checkpoint: framing revision 14 AWAITING APPROVAL; +- **No PR yet. Checkpoint: framing revision 15 AWAITING APPROVAL + (revision 14 was reviewed and had four blocking gaps, all answered); IMPLEMENTATION STILL PAUSED, now on approval rather than on a blocker.** §5a's replay contract is approved at revision 12; revision 13 ruled Q#BP-R3 and blocked the lane on a protocol-bearing mapping @@ -332,11 +333,15 @@ from #171 and #215 — the correction the 1b lane missed, honoured here. inverse mapping, stable across focus/styling/cursor/selection-only repaints so drags survive — as **appended** wire variants with bilateral gating. -- **Chain: §5b (protocol) → panel replay → GUI arc 1b.** §5b takes the - next protocol version, so **1e's `OpenTarget` moves to the one - after**. **That edit is OWED BY THE 1b BRANCH**, which already - revises `docs/gui-stage1-input-framing.md` (`:1011`, `:1221` still - say v25); making it here would collide at 1b's scheduled rebase. Commit one was the ground-truth +- **Chain: ~~§5b (protocol) →~~ panel replay → GUI arc 1b.** §5b took + **v25**, so 1e's `OpenTarget` is **v26**. **That edit is NOT owed by + the 1b branch** — this bullet said it was, and said making it here + would collide at 1b's rebase. **§5b made it, in + `docs/gui-stage1-input-framing.md`, and merged as #242**: a canonical + document saying v25 is false the moment v25 is taken, and an expected + rebase conflict was not grounds for leaving it wrong. Corrected here + because this bullet and the merged-#242 block below it were saying + opposite things. Commit one was the ground-truth re-measurement; 6 added the four replay edges; **7 answers review of 6; **8 answers review of 7** — R-c is target × gesture-ORIGIN (terminals reject all chrome kinds and raw chrome coords fail the diff --git a/docs/bottom-panel-framing.md b/docs/bottom-panel-framing.md index 44fc20d..8424413 100644 --- a/docs/bottom-panel-framing.md +++ b/docs/bottom-panel-framing.md @@ -1968,30 +1968,84 @@ cannot preserve the old panel-focused attach leak. preserving the Stage 1 unknown-value rollback assertions; the Stage 3 PR then runs the full gate suite. -## 5a. Acceptance 48 — ground truth, MEASURED at `72da24a` (2026-08-14) +## 5a. Acceptance 48 — ground truth, RE-MEASURED at `2c0d3ff` (2026-08-20) -**Status: revision 14 — AWAITING APPROVAL.** Revision 13 ruled Q#BP-R3 +**Status: revision 15 — AWAITING APPROVAL.** Revision 13 ruled Q#BP-R3 and **blocked** this lane on a protocol-bearing mapping generation. That slice was framed as §5b, approved at revision 16, and **merged as #242 (`47b5463`)**, so **revision 13's block is DISCHARGED** and its -ruling stands as history rather than as a gate. Revision 14 answers -what the lane inherits from a substrate that changed underneath it: -the meaning of the dispatcher's answer (**Q#BP-R4**, new), the rows -§5b's split table assigned here, the cancellation record's missing -resting place, four transitions that strand a live gesture, and a -ground truth whose every anchor has moved. **The measurement date in -this heading is now false and revision 14 owes its replacement.** +ruling stands as history rather than as a gate. Revision 14 answered +what the lane inherits from a substrate that changed underneath it. + +**Revision 15 answers review of 14, and four of its five changes are +corrections rather than additions:** + +- **Q#BP-R4's release half was bookkeeping wearing an effect's + clothes.** It said a chrome `Up` should "consume" the latch and + claimed that prevents a child left button-down. It prevents nothing: + the daemon drops the record, and terminal chrome returns before + replay. Now ruled as **TERMINATE**, with the row asserting the + child's release or the local selection's completion — and a + no-duplicate leg. +- **The pending-release slot had an invariant but no ordering.** The + seam replays before it arms, so the invariant could only fire after + the damage. **Drain order is now part of the ruling** — before any + subsequent panel-pointer effect, before detach teardown, before the + next frame — with an old-release-before-new-press row. +- **The ground truth was knowingly false and is now RE-MEASURED**, at + `2c0d3ff`, rather than deferred to implementation. Two clauses the + old table called MISSING are DONE, and a third — the horizontal + wheel — turns out to be an unruled gap. +- **"This lane MOVES the grade" was wrong.** Step 5/GPU is floored + `Partial` by 5(a) IME and step 8 is already `Works`; no journey cell + moves. The draft had read this section's own MISSING column as if it + were the scorecard. +- Q#BP-R4 also **drops a false TUI claim**: only semantic legacy and + mapped peers reach this dispatcher. **Why this section exists.** GUI arc Stage 1b's ground-truth pass found -that `PanelPointer` **replays nothing**: `dispatch_semantic_panel_pointer` -(`src/editor.rs:2674`) validates the coord, resolves the side window, -focuses when the gesture activates, and returns `true`. Its own doc -defers replay to *"parent acceptance 48… Stage 2B-3"*. **A panel wheel -is dead today on both axes**, and so is every other panel gesture past -focus. 1b is blocked on this lane and rebases onto its merge. +that `PanelPointer` replayed nothing past focus. **That is no longer +the state of this branch**, and the paragraph that said so — with the +wheel "dead on both axes" — is superseded below. This lane has since +landed the document and terminal replay, and §5b merged underneath it. +1b still rebases onto this lane's merge. -**Acceptance 48 is not unimplemented — it is HALF implemented**, and -the halves were never separated in writing. Measured clause by clause: +**RE-MEASURED at `2c0d3ff`, the tip this revision was written against.** +The 2026-08-14 measurement was taken at `72da24a`, **before this +branch's own implementation commits and before §5b**, so it described +neither the branch nor main. Every anchor in it had also moved. It is +kept below as history; **this table is the current one**: + +| clause | status at `2c0d3ff` | production anchor | +|---|---|---| +| click-to-focus | **DONE** | `activates` → `focus_window` (`src/editor.rs:2888`) | +| terminal panel: non-`Move` activates, hover does neither | **DONE** | the same `activates`, split by `is_terminal` | +| focused-only auto-scroll clamp; passive preserves `view_top` | **DONE** | `src/editor.rs:2698`, which cites "A2A-3 / parent 48" | +| move/drag tails coalesce; press/release/context/wheel lossless and ordered | **DONE** | `coalesce_kind` (`pmacs-gpu/src/attach.rs:338`) | +| **panel selection** | **DONE — was MISSING** | `replay_panel_document_gesture` (`src/editor.rs:2914`), called at `:2903`: `Down`/`Drag`/`Up(Left)`, shift-extend, double-click word select, right-press | +| **terminal mouse reporting** | **DONE — was MISSING** | `apply_terminal_gesture` called at `src/editor.rs:2899`, viewport `content_rows` | +| **wheel moves the panel's viewport** | **VERTICAL DONE — was MISSING** | `ScrollUp`/`ScrollDown` → `scroll_window` (`src/editor.rs:2925`) | +| **horizontal wheel** | **INERT, AND UNRULED** | `ScrollLeft`/`ScrollRight` fall into the catch-all no-op arm (`src/editor.rs:3004`) **with no comment and no ruling** — the old table's "dead on both axes" became half-true without anyone deciding the other half | +| **listview row selection** | **MISSING** | — no listview handling on the replay path; Q#BP-R1 ruled the semantics (single click selects only), and nothing implements them | +| without disturbing the document mirror | **the constraint on all of the above** | — | + +**Two findings the re-measurement produced**, neither of which a +line-number patch would have surfaced: + +- **The horizontal wheel is an unruled gap, not a deferral.** It sits + in a catch-all arm beside `Move` and the non-left buttons, so it + reads as deliberate and is not. **Revision 14 does not rule it** — + it names it, because inventing a ruling inside a re-measurement is + how an unexamined default becomes a decision. +- **Two of the four "MISSING" effects are DONE on this branch.** The + section had been telling every reader that replay does nothing while + the branch it describes had implemented most of it. + +### Superseded — the 2026-08-14 measurement at `72da24a` + +Kept because it is what the lane was scoped against, and because two of +its verdicts are now wrong rather than merely stale. Measured clause by +clause: | clause | status | production anchor | |---|---|---| @@ -2687,14 +2741,50 @@ the pointer happens to be: - **ARM** only on `Accepted` **and** `Down(Left)`. A chrome press begins nothing. -- **CONSUME** on any `Up(Left)` that was **not `Refused`** — so - `Accepted` **or** `Consumed`. A button-up over the mode line still - ends the gesture; declining to consume it would strand the gesture - and leave the child holding the button down, which is the same - failure §5b's round 4 named. A **`Refused`** release is different in +- **TERMINATE** on any `Up(Left)` that was **not `Refused`** — so + `Accepted` **or** `Consumed`. A **`Refused`** release is different in kind: out-of-grid or wrong-buffer means the daemon cannot tell the - event is even about this gesture, and §5b already pinned that it - must not consume. + event is even about this gesture, and §5b already pinned that it must + not consume. + +**"TERMINATE", not "consume", and the distinction is the whole of +review finding 1.** An earlier draft of this ruling said consume, and +justified it by claiming it prevents a child left button-down. **It +prevents no such thing.** Emptying the latch is bookkeeping; +`update_accepted_gesture` currently does `let _ = +state.consume_accepted_gesture()` (`src/daemon.rs:1067`) — it **takes +the record and drops it**. And on the path that matters, terminal +chrome returns at `src/editor.rs:2862`–`:2870`, **before** +`apply_terminal_gesture` at `:2899`, so the child is never told +anything. A latch that empties while the child stays pressed is +*exactly* the failure the draft claimed to prevent, now invisible +because the bookkeeping looks right. + +**RULED: a non-`Refused` `Up(Left)` must produce the target's +completion effect, and the row asserts the EFFECT, not the latch.** + +| target | required completion effect | +|---|---| +| terminal panel, child reporting on | the **release is reported to the child** — the same report an in-content `Up` produces, at the gesture's last content cell per R-c2 | +| terminal panel, child reporting off | the **local terminal selection completes** — the selection the drag built is finalised, not abandoned | +| document panel | the document gesture completes at the last content cell; an empty selection is cleared **without moving point** (the effect §5b's split table already assigns here) | + +**And exactly once.** A completion delivered here must not be delivered +again by a later cancellation of the same gesture, which is reachable +because the four stranding transitions below can fire afterwards. + +| # | mutation | must bite | +|---|---|---| +| P1 | chrome press returns `Accepted` | a chrome `Down(Left)` does not arm | +| P2 | `Refused` treated as `Accepted` | §5b's four `g5_substrate_a_refused_*` rows | +| P3 | chrome `Up` empties the latch but delivers **no** completion | the child-release / selection-completion row — **the latch-only assertion must NOT satisfy this** | +| P4 | `Refused` release delivers a completion | a refused release leaves both the latch and the child untouched | +| P5 | completion delivered, then a later cancellation delivers another | the no-duplicate-release row | + +**P3 is written against the earlier draft's own weakness.** A row that +asserts only `has_accepted_gesture() == false` passes while the child +receives nothing, so the row must read the child's reported bytes or +the terminal's selection state. **That asymmetry is what earns the third state.** Under it, all three outcomes are behaviourally distinct at the latch, so each is @@ -2710,12 +2800,18 @@ falsifiable: **R-c2 does not discharge P3.** The producer normalizes a release that lands on chrome back to the last content cell, so a conforming frontend should not send one — but the daemon's contract cannot rest -on the producer's good behaviour, and a TUI or legacy peer reaches the -same path. Producer-side normalization and daemon-side consumption are -**both** required, and the existing GPU row +on the producer's good behaviour, and **a legacy `PanelPointer` peer +predates that normalization entirely**. Producer-side normalization and +daemon-side termination are **both** required, and the existing GPU row (`a_press_on_the_bands_mode_line_neither_arms_nor_reports_content`) covers only the producer half. +**No TUI claim is made here.** An earlier draft said a TUI reaches the +same path; it does not. `dispatch_semantic_panel_pointer` has exactly +two callers, both `FrontendEvent` arms in `src/daemon.rs` (`:2609`, +`:2680`), so **only semantic legacy and mapped peers reach it** — the +TUI goes through `dispatch_mouse`. + #### The rows §5b's split table assigned here §5b states its own split rather than leaving it to whoever runs the @@ -2752,17 +2848,38 @@ dropped. **RULED: one pending-release SLOT per frontend, not a queue.** The latch holds at most one gesture per frontend, so at most one release can be owed at a time, and the slot is bounded by construction rather -than by a cap someone has to choose. **The invariant is load-bearing -and therefore owed a witness**: arming while a release is still -pending must be impossible. If that witness fails, the slot is the -wrong shape and this ruling is wrong — which is the point of asserting -it rather than assuming it. +than by a cap someone has to choose. + +**AND THE DRAIN ORDER IS PART OF THE RULING, because an invariant alone +is a detector, not a guard.** An earlier draft asserted only that +arming over a pending release is impossible. That check would sit +inside `arm_accepted_gesture` — and the seam replays **before** it: +`dispatch_semantic_panel_pointer` runs its effects and only then does +`update_accepted_gesture` arm (`src/daemon.rs:2608`–`:2616`). So the +invariant can fail **only after the new press has already reached the +child or moved the selection**. It would report the collision one +effect too late. + +**A pending termination DRAINS FIRST:** + +1. **before any subsequent panel-pointer effect** for that frontend — + the drain happens ahead of `dispatch_semantic_panel_pointer`, not + after it, so the old gesture's release reaches the child before the + new gesture's press does; +2. **before detach teardown**, so a frontend that goes away does not + take an owed release with it — detach is one of the four stranding + transitions below, and it is the one with no later opportunity; +3. **before the frontend's next frame is produced**, so a cancellation + raised during projection cannot be overtaken by the frame that + caused it. | # | mutation | must bite | |---|---|---| | Q1 | drop the record instead of parking it | the cancelled-gesture release row | | Q2 | park it but never drain | the same row, from the delivery side | -| Q3 | allow arming over a pending release | the invariant witness above | +| Q3 | drain **after** `dispatch_semantic_panel_pointer` instead of before | the **old-release-before-new-press ordering row** — assert the child's byte stream carries the release ahead of the press, not merely that both arrive | +| Q4 | skip the drain on detach | the detach row: an owed release is delivered before teardown | +| Q5 | allow arming over a pending release | the invariant witness — kept, now as a **backstop** behind the ordering rows rather than as the guarantee | #### Four transitions strand a live gesture, and they become defects HERE @@ -2804,10 +2921,19 @@ patch. A table that is right about *what* is missing and wrong about - **Journey steps touched: 5** — Edit, clause (c) selection, kill and yank — **and 8** — Open terminal, clause (b) input and output - round-trip. **Unlike §5b, this lane MOVES the grade.** §5b hardened - steps that already worked; §5a's own table lists panel selection, - listview row selection, terminal mouse reporting and the wheel as - **MISSING**, and this lane is what supplies them. + round-trip. **NO JOURNEY-CELL GRADE MOVES**, and an earlier draft + claimed one. Checked against the authoritative scorecard rather than + inferred from this section's own MISSING column: + - **Step 5 / GPU is `Partial`, and stays `Partial`.** It is floored + by **5(a)** — no IME, no `set_ime_allowed`, so composed and CJK + input is impossible (`COHERENCE.md:462`). Completing 5(c) inside a + panel cannot lift a cell held down by a different clause. That is + §2a's aggregation rule working as designed. + - **Step 8 is already `Works` on all three columns.** There is no + grade left to move; 8(b) is hardened, not opened. + - The draft's error was reading §5a's MISSING column as if it were + the scorecard. **A section-local gap list is not a journey grade**, + and the one place that decides grades is `COHERENCE.md`. - **Interaction islands: none added.** It **completes** the existing panel island rather than opening a new one — the same gestures the band already advertises, finally reaching their target. @@ -2825,7 +2951,11 @@ which changes their return value and not their behaviour. ## 5b. The cell-mapping generation — a protocol slice (Q#BP-R3) -**Status: revision 16 — APPROVED 2026-08-15. Nothing implemented.** +**Status: revision 16 — APPROVED 2026-08-15. IMPLEMENTED AND MERGED as +#242 (`47b5463`), 2026-08-20.** The "Nothing implemented" this line +carried until then was left behind by the merge; the slice's own +"What the slice actually landed, row by row" subsection below had been +contradicting it. Revision 16 answers review of 15; three of its items reverse a rule 15 introduced: