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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
Levi Neuwirth 2026-08-14 14:39:47 +02:00
parent 4833a73dfe
commit 7d6b6da366
No known key found for this signature in database
2 changed files with 118 additions and 30 deletions

View File

@ -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

View File

@ -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