From 7d6b6da366e65640ea0ed5aa0eb72faf7ea1df17 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Fri, 14 Aug 2026 14:39:47 +0200 Subject: [PATCH] docs(framing): bottom-panel revision 9 --- Q#BP-R2 overruled, and R-c2 stops fighting a tested contract Answers review of revision 8. Still framing only; no implementation. Both items reverse a decision revision 8 made. **Q#BP-R2 IS OVERRULED: a terminal-chrome wheel is CONSUMED, not clamped.** I argued clamping on consistency grounds and missed that SGR wheel input is COORDINATE-BEARING: `encode_mouse` takes a `coord` and writes `coord.col + 1` / `coord.row + 1` into the emitted sequence (`src/terminal/input.rs:102`, `:146`) --- which A4, added one revision earlier, pins exactly. Clamping therefore fabricates a hit on the terminal's final content row, and an application that routes wheel input by position acts on a cell the user never pointed at. The consistency I was buying is cosmetic; the price is a synthetic coordinate handed to a program as real. The asymmetry with `Up` is the part worth keeping in the record: `Up` must be normalized because an unterminated gesture HANGS --- the daemon holds a button down forever --- while a wheel tick is self-contained and dropping one strands nothing. Liveness is what earns normalization, and the wheel has no liveness obligation. Consumed, and never fallen through to the document; TUI parity restored. **R-c2 WOULD HAVE BROKEN A TESTED GUARANTEE.** Revision 8 said to retain the `Down` cell in `last_pointer_cell`. That field is cleared on press deliberately, and a live test says why: "the first drag after a press must reach the daemon even at the cell the press landed on" (`pmacs-gpu/src/main.rs:19841`). Storing `Down` there makes the press's own cell the dedupe baseline and suppresses exactly that `Drag`. Ruled: a separate `gesture_last_content_cell`. The two fields have different jobs and conflating them was the error --- `last_pointer_cell` answers "is this motion worth sending?", the new one answers "where did this gesture last legitimately point?" --- different lifetimes, different resets. Written on arm and on each accepted content motion; reset on release and on BOTH identity changes; never consulted by `panel_motion_is_new`. A second mutation guards the separation itself: point the dedupe at the new field and the existing first-`Drag`-after-press assertion must fail. The alternative --- ruling `Down` the dedupe baseline and retiring the guarantee --- is recorded as considered and rejected. It would need a mutation showing the first same-cell `Drag` is redundant, and it is not obviously so, since that event is what establishes the daemon's drag state. **And the crossing table follows the choice.** Revision 8 promised a content-originated `Drag` over chrome would be sent. It should not be: once normalized, its coordinate is usually the one already reported and the ordinary dedupe suppresses it, correctly --- promising delivery would oblige the producer to defeat its own dedupe for a daemon state that is identical either way. `Up` is the load-bearing crossing event and the only one promised unconditionally. Gates: all nine green under `env -u TMPDIR`, log 20260814T123502Z. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai --- docs/active-work.md | 13 +++- docs/bottom-panel-framing.md | 135 +++++++++++++++++++++++++++-------- 2 files changed, 118 insertions(+), 30 deletions(-) diff --git a/docs/active-work.md b/docs/active-work.md index 694c6a9..3cb9d0b 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 8 (§5a) AWAITING APPROVAL; +- **No PR yet. Checkpoint: framing revision 9 (§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 @@ -290,7 +290,16 @@ from #171 and #215 — the correction the 1b lane missed, honoured here. positive SGR controls, and four witness seams are tightened. **New ruling Q#BP-R2**: a chrome wheel over a terminal panel clamps into content rather than dropping — a deliberate divergence from the TUI, - flagged for overrule. + flagged for overrule — **and overruled in 9**. + **Revision 9** reverses two of 8's decisions: a terminal-chrome wheel + is **CONSUMED, not clamped** (SGR wheel input is coordinate-bearing, + so clamping fabricates a hit the user never made, and a wheel has no + liveness obligation), and the `Down`-cell fallback lives in a + **separate `gesture_last_content_cell`** rather than + `last_pointer_cell`, which is cleared on press precisely so the first + 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. - **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 007325f..765cffa 100644 --- a/docs/bottom-panel-framing.md +++ b/docs/bottom-panel-framing.md @@ -7,7 +7,31 @@ 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 8 — 2026-08-14, AWAITING APPROVAL.** Answers review of 7: +**Revision 9 — 2026-08-14, AWAITING APPROVAL.** Answers review of 8. +Both items reverse 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` + writes `coord.col + 1` / `coord.row + 1` into the sequence + (`src/terminal/input.rs:102`, `:146`), which A4 pins exactly. Clamping + **fabricates a hit on the final content row**, so a + position-routing application acts on a cell the user never pointed + at. And unlike `Up`, a wheel has **no liveness obligation** — + dropping one strands nothing. Consumed, never fallen through to the + document; TUI parity restored. +- **R-c2's field choice is corrected: a SEPARATE + `gesture_last_content_cell`.** Revision 8 said to retain the `Down` + cell in `last_pointer_cell`, which would break a **tested + guarantee** — that field is cleared on press precisely so the first + same-cell `Drag` reaches the daemon + (`pmacs-gpu/src/main.rs:19841`). Dedupe baseline and termination + fallback are different jobs with different lifetimes. +- **The crossing table follows:** a content-originated `Drag` over + chrome is **normalized and then subject to the ordinary dedupe**, not + promised as sent. **`Up` is the load-bearing crossing event** and the + only one promised unconditionally. + +**Previously, revision 8 — SUPERSEDED.** Answered review of 7: - **R-c is target × gesture-ORIGIN, not kind alone.** The TUI's per-kind rule is **document-only** — for terminals it rejects every @@ -2113,33 +2137,49 @@ instead of behaving as a content wheel does. |---|---|---|---| | **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** | **sent** — the gesture is live | **process at the last valid CONTENT coordinate** | +| **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`/`Up`, **content-originated**, now over chrome | **send** | **replay at the last valid CONTENT coordinate** — never the chrome row | -| **terminal** | wheel over chrome | **send, clamped to content** | as a content wheel: reported when eligible, else local scrollback | +| **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) | -**A content-originated gesture terminates normally, at its last valid -content coordinate.** That is the whole of the crossing rule, and it is -what keeps a reporting child from being told about a row it does not -own while still receiving its release. +**`Up` is the load-bearing crossing event, and the only one promised +unconditionally.** A content-originated gesture terminates at its last +valid content coordinate — that is what keeps a reporting child from +being told about a row it does not own while still receiving its +release. -#### Q#BP-R2 — a chrome wheel over a TERMINAL panel: clamp, or drop? **RULED: clamp** +**A crossing `Drag` is NOT promised as sent.** Once normalized, its +coordinate is frequently the one already reported, and the ordinary +motion dedupe suppresses it — correctly. Promising delivery would +oblige the producer to defeat its own dedupe for no gain: the daemon's +state after a suppressed same-cell `Drag` is identical. -The one product call inside R-c, flagged because it **diverges from the -TUI deliberately**. The TUI drops terminal mode-line wheels; this rules -that a panel clamps them into content instead. +#### Q#BP-R2 — a chrome wheel over a TERMINAL panel **RULED: CONSUME, do not clamp** -The reason is that the panel's mode line is not a window's mode line -among many — it is **the band's own chrome**, one row, and the document -panel already scrolls on a chrome wheel (the TUI does not guard the -wheel for documents). Dropping for terminals would make **the same -pixel behave differently depending on the buffer kind currently shown -there**, which is not a distinction a user is holding in mind while -scrolling. **Overrule this if you would rather have TUI parity**; it is -a one-line difference in the clamp and nothing else in R-c depends on -it. +Revision 8 ruled *clamp*, on a consistency argument. **That was wrong, +and the reason is that SGR wheel input is COORDINATE-BEARING.** +`encode_mouse` takes a `coord` and writes `coord.col + 1` and +`coord.row + 1` into the sequence (`src/terminal/input.rs:102`, +`:146`–`:147`) — which A4 now pins exactly. **Clamping would fabricate +a hit on the terminal's final content row**, so an application that +routes wheel input by position could act on a cell the user never +pointed at. The consistency gain is cosmetic; the cost is a synthetic +coordinate delivered to a program as though it were real. + +**And the wheel carries no liveness obligation.** `Up` must be +normalized because a gesture left un-terminated hangs — the daemon +holds a button down forever. A wheel tick is self-contained: dropping +one ends nothing and strands nothing. + +**Ruling: a wheel over a terminal panel's chrome is CONSUMED** — not +reported, not scrolled locally, and **not fallen through to the +document**. The band owns the pixel either way. This also restores TUI +parity, which revision 8 traded away for the weaker argument. + +Document chrome is unchanged: a wheel there still scrolls the panel. **Both crossings need witnesses, and they fail in opposite directions:** @@ -2170,13 +2210,52 @@ by a release over chrome or outside the band, with **no intervening motion**, has no coordinate to fall back to, so the `Up` is either dropped or sent with nothing. -**Retain the `Down` cell when arming.** Row: `Down` in content → -release over chrome, **no intervening motion** → the `Up` carries the -**`Down` cell's** coordinate. *Mutation: keep clearing the cell on arm -— the release has no coordinate.* **For a reporting terminal the row -asserts the exact child release BYTES**, not merely that the frontend -latch cleared: a latch that clears while the child never hears the -release is the failure this row exists for. +**Revision 8 said "retain the `Down` cell in that field". That would +break a tested guarantee.** `last_pointer_cell` is cleared on press +*deliberately*, and a live test says why: + +> *"A press or release re-arms it: the first drag after a press must +> reach the daemon even at the cell the press landed on."* +> (`pmacs-gpu/src/main.rs:19841`–`:19844`) + +Storing the `Down` cell there would make the press's own cell the +dedupe baseline and **suppress that first `Drag`**. + +**Ruled: a SEPARATE field, `gesture_last_content_cell`.** The two have +different jobs and conflating them was the error — +`last_pointer_cell` is a **wire dedupe baseline**, answering *"is this +motion worth sending?"*; the new field is a **termination fallback**, +answering *"where did this gesture last legitimately point?"*. They +have different lifetimes and different reset rules, so one field cannot +serve both without one job corrupting the other. + +- **Written** on arm (the `Down` cell) and on every accepted content + motion. +- **Reset** alongside the rest of the gesture state: on release, and on + **both** identity changes (R-d's D1 and D2). +- **Never consulted** by `panel_motion_is_new`, which keeps its own + baseline and its existing behaviour unchanged. + +*The alternative was considered and rejected:* ruling that `Down` +becomes the dedupe baseline, retiring the guarantee and its test. It +would need a justified mutation showing the first same-cell `Drag` is +redundant, and it is not obviously so — the daemon's drag state is +established by that event. **Preserving a tested contract beats +retiring one to save a field.** + +Row: `Down` in content → release over chrome, **no intervening +motion** → the `Up` carries the **`Down` cell's** coordinate. +*Mutation: reset `gesture_last_content_cell` on arm instead of writing +the `Down` cell — the release has no coordinate.* **For a reporting +terminal the row asserts the exact child release BYTES**, not merely +that the frontend latch cleared: a latch that clears while the child +never hears the release is the failure this row exists for. + +**A second mutation guards the separation itself:** make +`panel_motion_is_new` consult `gesture_last_content_cell`. The existing +first-`Drag`-after-press assertion (`:19841`) must fail — which is what +proves the new field did not quietly become the dedupe baseline after +all. #### R-d. Panel replacement leaves the frontend's gesture latch armed