From ec127fc8fac95a1825c1e160e9f780b558b06a78 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Fri, 14 Aug 2026 16:28:18 +0200 Subject: [PATCH] docs(framing): bottom-panel revision 10 --- the ruling was on the wrong side of the seam Answers review of revision 9. Still framing only; no implementation. Q#BP-R2's outcome is unchanged; its ENFORCEMENT POINT moves. **THE GPU CANNOT KNOW WHETHER A PANEL HOLDS A TERMINAL.** `PanelFrame` carries `buffer_id`, both epochs, `size`, `cells`, `cursor` and `focused` --- and no target-kind discriminator (`pmacs-protocol/src/panel.rs:73`). The panel is an OPAQUE CELL PROJECTION to the frontend. `state.terminal` (`pmacs-gpu/src/main.rs:1964`) cannot answer either: it describes the primary full-window terminal, not the side-window. So revision 9's producer rule --- "terminal chrome wheel: do not send" --- was unimplementable without a new wire field, and this lane is explicitly non-protocol-bearing. I wrote a rule the frontend has no information to obey. The correction is structural, not cosmetic. **Every producer rule is now target-blind**, and the table is split by SEAM SIDE rather than by target: producer claims the chrome wheel and sends `PanelPointer` with its valid frame coordinate, for EVERY panel daemon after validation and resolving the side window --- document panel -> `scroll_window`; terminal panel -> CONSUME, producing no child bytes, no local scrollback, and no document fallthrough The other producer rules collapse safely because both targets want the same behaviour there: a chrome press is reserved by documents and dropped by terminals --- both drop --- and a crossing `Up` must arrive for either, to terminate a selection or deliver a release. The wheel was the sole divergence, which is exactly why it was the rule that could not survive on the producer side. The chrome coordinate does cross the wire now, and that is fine: it is a valid frame cell, coord validation accepts it, `scroll_window` is a window-level move that never reads it, and the terminal branch never forwards it to a child. Nothing is fabricated and nothing reaches an application --- which was the whole objection to clamping. **Witness: ONE frontend across a document->terminal replacement.** Document chrome wheel scrolls the panel; after replacement the terminal chrome wheel changes nothing. Doing it in one frontend across a replacement is what makes it a control rather than two unrelated observations --- geometry, pointer position and producer path are identical, and only the target differs. Mutation: let the terminal branch call `apply_terminal_gesture`, and the chrome coordinate fails its reporting bounds check, falls into the local branch, and the row catches the accidental local scrollback that revision 8's clamp would have shipped on purpose. Gates: all nine green under `env -u TMPDIR`, log 20260814T142336Z. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai --- docs/active-work.md | 12 +++- docs/bottom-panel-framing.md | 108 ++++++++++++++++++++++++++++++----- 2 files changed, 106 insertions(+), 14 deletions(-) diff --git a/docs/active-work.md b/docs/active-work.md index 3cb9d0b..fc95bb4 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -281,7 +281,7 @@ 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 9 (§5a) AWAITING APPROVAL; +- **No PR yet. Checkpoint: framing revision 10 (§5a) AWAITING APPROVAL; NO IMPLEMENTATION WRITTEN.** 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 @@ -300,6 +300,16 @@ from #171 and #215 — the correction the 1b lane missed, honoured here. same-cell `Drag` reaches the daemon (`pmacs-gpu/src/main.rs:19841`). **`Up` is the only crossing event promised unconditionally**; a crossing `Drag` is normalized and then deduped. + **Revision 10** keeps Q#BP-R2's outcome and moves its **enforcement + point**: the GPU **cannot know a panel holds a terminal** — + `PanelFrame` has no target-kind field + (`pmacs-protocol/src/panel.rs:73`) and `state.terminal` is the + primary full-window terminal — so a producer-side rule needed a new + wire field this lane must not add. **The producer is target-blind** + and sends the chrome wheel for every panel; **the daemon** resolves + the side window and decides: document → `scroll_window`, terminal → + consume. Witness: one frontend across a document→terminal + replacement. - **Why this lane exists.** `PanelPointer` **replays nothing**: `dispatch_semantic_panel_pointer` (`src/editor.rs:2674`) validates, focuses, returns. A panel wheel is dead on both axes and so is every diff --git a/docs/bottom-panel-framing.md b/docs/bottom-panel-framing.md index 765cffa..3c45220 100644 --- a/docs/bottom-panel-framing.md +++ b/docs/bottom-panel-framing.md @@ -7,8 +7,29 @@ protocol v20, 2026-07-24. Amended by the pre-implementation dependency verification in §0.6: the folding dependency is cleared, and one geometry caller-census error is corrected.** -**Revision 9 — 2026-08-14, AWAITING APPROVAL.** Answers review of 8. -Both items reverse a decision revision 8 made: +**Revision 10 — 2026-08-14, AWAITING APPROVAL.** Answers review of 9, +which put a correct ruling **on the wrong side of the seam**. + +**The GPU cannot know whether a panel holds a terminal.** `PanelFrame` +carries `buffer_id`, both epochs, `size`, `cells`, `cursor` and +`focused` and **no target-kind discriminator** +(`pmacs-protocol/src/panel.rs:73`); `state.terminal` +(`pmacs-gpu/src/main.rs:1964`) is the **primary full-window** terminal, +not the side-window projection. So revision 9's producer rule +*"terminal chrome wheel: do not send"* was unimplementable without a +new wire field, which this lane must not add. + +**Q#BP-R2's OUTCOME is unchanged; its ENFORCEMENT POINT moves.** The +producer is now target-blind throughout — it claims the chrome wheel +and sends it for every panel — and the daemon, which resolves the side +window and knows the buffer kind, decides: document → `scroll_window`; +terminal → **consume**, with no child bytes, no local scrollback and no +document fallthrough. The producer/receiver tables are split by seam +side accordingly, and the witness is **one frontend across a +document→terminal replacement**, so only the target differs. + +**Previously, revision 9 — SUPERSEDED.** Both items reversed a decision +revision 8 made: - **Q#BP-R2 is OVERRULED — a terminal-chrome wheel is CONSUMED, not clamped.** SGR wheel input is **coordinate-bearing**: `encode_mouse` @@ -2133,17 +2154,53 @@ branch**. Two concrete wrongnesses follow: a reporting child gets a `finish_selection`), and a chrome wheel becomes **local scrollback** instead of behaving as a content wheel does. -| target | kind | producer | receiver | -|---|---|---|---| -| **document** | `Down(Left)`, `Down(Right)` | do not arm, do not send | reserved — drop | -| **document** | `Drag(Left)`, chrome-originated | not sent (never armed) | reserved — drop | -| **document** | `Drag(Left)`, **content-originated** | normalized to the last valid CONTENT cell, then **subject to the ordinary dedupe** | process when it arrives | -| **document** | `Up(Left)` | **send** | **process** — terminates the gesture | -| **document** | wheel | **send** | **process** — scrolls the panel | -| **terminal** | any kind, chrome-**originated** | do not arm, do not send | drop, matching the TUI | -| **terminal** | `Drag`, content-originated, now over chrome | normalized, then **subject to the ordinary dedupe** | process when it arrives | -| **terminal** | `Up`, content-originated, now over chrome | **always sent** | **replay at the last valid CONTENT coordinate** — never the chrome row | -| **terminal** | wheel over chrome | **do not send — consume** | n/a; never falls through to the document (Q#BP-R2) | +##### The producer is TARGET-BLIND, and the table must respect that + +**Revision 9 split the producer column by target. The GPU cannot make +that split.** `PanelFrame` carries `buffer_id`, both epochs, `size`, +`cells`, `cursor` and `focused` — **and no target-kind discriminator** +(`pmacs-protocol/src/panel.rs:73` onward). The panel is an **opaque +cell projection** to the frontend. `state.terminal` +(`pmacs-gpu/src/main.rs:1964`) describes the **primary full-window** +terminal, not the side-window, so it cannot answer the question either. + +A producer rule reading "terminal chrome wheel: do not send" is +therefore **unimplementable without a new wire field**, and this lane +is explicitly non-protocol-bearing. **Every producer rule must be +target-blind; every target-dependent decision belongs to the daemon**, +which resolves the side window and knows the buffer kind +(`is_terminal(buffer_id)`). + +**Producer — by kind and origin only:** + +| kind | origin | producer | +|---|---|---| +| `Down(Left)`, `Down(Right)` | chrome | **do not arm, do not send** | +| `Drag(Left)` | chrome | not sent — never armed | +| `Drag(Left)` | content, now over chrome | normalize to the last valid CONTENT cell, then **ordinary dedupe** | +| `Up(Left)` | content, now over chrome | **always send**, normalized | +| wheel | chrome | **claim it and send**, carrying its valid frame coordinate | + +These collapse safely because the two targets want the **same producer +behaviour** everywhere except the wheel: a chrome press is reserved by +documents and dropped by terminals — both drop; a crossing `Up` must +arrive for both, to terminate a selection or to deliver a release. The +wheel is the sole divergence, and it is now decided receiver-side. + +**Receiver — after validation and resolving the side window:** + +| kind | document panel | terminal panel | +|---|---|---| +| chrome `Down`/`Drag` | reserved — drop | drop, matching the TUI | +| crossing `Drag` (normalized) | process when it arrives | process at the content coordinate | +| crossing `Up` (normalized) | terminate the gesture | **replay at the last valid CONTENT coordinate** — never the chrome row | +| chrome wheel | **process through `scroll_window`** | **CONSUME** — no child bytes, no local scrollback, no document fallthrough (Q#BP-R2) | + +**The chrome wheel carries a chrome coordinate over the wire, and that +is fine**: it is a valid frame cell, the daemon's coord validation +accepts it, `scroll_window` is a window-level move that does not read +it, and the terminal branch never forwards it to a child. **The +coordinate is never fabricated and never reaches an application.** **`Up` is the load-bearing crossing event, and the only one promised unconditionally.** A content-originated gesture terminates at its last @@ -2181,6 +2238,31 @@ parity, which revision 8 traded away for the weaker argument. Document chrome is unchanged: a wheel there still scrolls the panel. +**Enforced in the DAEMON, not the frontend.** Revision 9 wrote this as +a producer rule, which the GPU cannot implement — it has no way to know +the panel holds a terminal (see the seam note above). The producer +claims the chrome wheel and sends it for **every** panel; the daemon +resolves the side window and consumes it when the buffer is a terminal. + +**Witness — one frontend across a document→terminal replacement**, so +the two outcomes are separated by nothing but the buffer kind: + +1. panel shows a **document**; wheel over chrome → **the panel + scrolls**. +2. the panel is replaced by a **terminal**; wheel over chrome → + **nothing changes** — no child bytes, no scrollback movement, no + document scroll. + +*Mutation: let the terminal branch call `apply_terminal_gesture` — the +chrome coordinate fails its reporting bounds check, falls into the +local branch, and the row catches the **accidental local scrollback** +that revision 8's clamp would have shipped deliberately.* + +Doing it in one frontend across a replacement is what makes it a +control rather than two unrelated observations: the geometry, the +pointer position and the producer path are identical, and only the +target differs. + **Both crossings need witnesses, and they fail in opposite directions:**