docs(framing): acceptance 48 re-measured --- it is HALF implemented

Ground truth before code, and the lane entry in the same commit, per
the standing correction from #171 and #215 --- the one the 1b lane
missed. Base `githubsucks/main` @ `72da24a`. No implementation yet.

**AC48 IS NOT UNIMPLEMENTED. IT IS HALF IMPLEMENTED**, and nothing had
written the halves down separately, which is how "PanelPointer drives
listview rows, selection, terminal reporting and focus" could sit next
to a dispatcher that does none of the first three.

Landed already: click-to-focus and the terminal activation rule
(`src/editor.rs:2701`); the focused-only auto-scroll clamp with a
passive panel's `view_top` preserved (`:2569`, which has cited parent
48 all along); and the coalescing rules --- `Move`/`Drag` tails
coalesce while press/release/context/wheel stay lossless and ordered
(`pmacs-gpu/src/attach.rs:374`).

Missing: listview row selection, panel selection, terminal mouse
reporting, wheel replay.

**AND THE REPLAY IS MOSTLY WIRING.** `apply_terminal_gesture`
(`src/editor.rs:3525`) is already "the one terminal pointer path,
shared by both frontend kinds" and already drives child mouse
reporting, selection and scrollback; a panel terminal needs the SAME
CALL, with `side_window_for` + `TerminalViewKey` + the
`panel_grid_size` the dispatcher already fetches. `scroll_window`
(`:3845`) is window-scoped including its cursor carry, and documents
that a wheel "names the pane under the pointer and does NOT activate
it". This is why a wheel-only bridge would be the wrong shape: the
shared path takes every kind at once, and the wheel-only version would
have to grow a second one later.

**The scoping hazard, and why no ruling is needed.** `set_cursor_byte`,
`begin_selection` and `clear_selection` are ACTIVE-WINDOW scoped
(`src/editor_core.rs:1216`, `:4691`), so replaying naively from the
panel path would move the DOCUMENT's point --- exactly what AC48's
"without disturbing the document mirror" forbids. The existing
activation rule already resolves it: activation runs before replay in
the same dispatch, and the gestures that need the active-window API are
precisely the ones that activate. The single gesture that does not --- a
document panel's wheel --- needs only the window-scoped
`scroll_window`.

**One genuine question, opened not answered: Q#BP-R1.** Does a listview
row VISIT on click, or only select? `listview.lua` binds visiting to
RET/SPC (`:610`) and there is no pointer precedent anywhere in the
file. Single-click-visits matches a file tree; click-selects,
double-click-visits matches an editor list; AC48 says "row selection"
and nothing more. This lane implements SELECTION and does not invent
activation, because the difference is whether a click navigates the
user's document.

Gates: all nine green under `env -u TMPDIR`, log 20260814T113155Z.

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 13:40:24 +02:00
parent 72da24adea
commit d97dcdfc46
No known key found for this signature in database
2 changed files with 161 additions and 0 deletions

View File

@ -270,6 +270,68 @@ hazard in a shape that looks committed. **A documented error message
that never appears is worse than no documentation**, because the reader that never appears is worse than no documentation**, because the reader
waits for a signal that is not coming. waits for a signal that is not coming.
## Panel-pointer replay (parent acceptance 48) — ACTIVE, 1b's prerequisite
**Written with the branch's FIRST commit**, per the standing correction
from #171 and #215 — the correction the 1b lane missed, honoured here.
- **Branch `panel-pointer-replay`**, base `githubsucks/main` @
**`72da24a`** exactly, in worktree
`/home/jeans/Repos/personal/pmacs-panel-replay`.
**`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 5 (§5a) AWAITING APPROVAL;
NO IMPLEMENTATION WRITTEN.** The first commit is the ground-truth
re-measurement, per the standing method.
- **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
gesture past focus. **GUI arc 1b is BLOCKED on this lane and rebases
onto its merge commit.**
- **No new framing document.** Acceptance 48 is already ruled in
`docs/bottom-panel-framing.md`; §5a adds ground truth to it.
- **The measurement's headline: AC48 is HALF implemented**, and nothing
had written the halves down separately.
- **DONE:** click-to-focus and the terminal activation rule
(`src/editor.rs:2701` and its `activates`); the focused-only
auto-scroll clamp with passive `view_top` preserved (`:2569`, which
already cites parent 48); the coalescing rules — `Move`/`Drag`
tails coalesce, press/release/context/wheel lossless
(`pmacs-gpu/src/attach.rs:374`).
- **MISSING:** listview row selection, panel selection, terminal
mouse reporting, wheel replay.
- **The replay is mostly WIRING; both mechanisms exist.**
`apply_terminal_gesture` (`src/editor.rs:3525`) is *"the one terminal
pointer path, shared by both frontend kinds"* and already drives
child reporting, selection and scrollback — a panel terminal needs
the same call, with `side_window_for` + `TerminalViewKey` +
`panel_grid_size` (which the dispatcher already fetches).
`scroll_window` (`:3845`) is window-scoped, cursor carry included.
**A wheel-only bridge is the wrong shape** — the shared path takes
every kind at once.
- **The scoping hazard, and why it needs no ruling.** `set_cursor_byte`
(`src/editor_core.rs:1216`), `begin_selection` (`:4691`) and
`clear_selection` are **active-window scoped**; used naively they
would move the DOCUMENT's point, which AC48 forbids. **Activation
runs before replay in the same dispatch**, and the gestures needing
that API are exactly the ones that activate. The only gesture that
does not activate — a document panel's wheel — needs only the
window-scoped `scroll_window`.
- **OPEN: Q#BP-R1** — does a listview row **visit** on click, or only
select? `listview.lua` binds visiting to RET/SPC (`:610`) and there
is no pointer precedent. **This lane implements selection and does
not invent activation.**
- **Gates:** the four `bottom_panel_*` acceptance suites plus
`PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu`. **No `--protocol`** —
`PanelPointer` and every `MouseKind` it carries already exist on the
wire.
- **Expected rebase conflict, flagged deliberately:** the `gui-stage1b`
branch inserts its own lane at this same position and corrects three
stale headers below (#239/#240 still marked OPEN, "1a is next").
Those corrections are **left to that branch**; this lane does not
duplicate them. The conflict is a normal insertion collision.
## `scripts/gate` TMPDIR isolation — PR #240 OPEN ## `scripts/gate` TMPDIR isolation — PR #240 OPEN
**Written with the branch's first commit**, per the standing correction **Written with the branch's first commit**, per the standing correction

View File

@ -7,6 +7,16 @@ protocol v20, 2026-07-24. Amended by the pre-implementation dependency
verification in §0.6: the folding dependency is cleared, and one geometry verification in §0.6: the folding dependency is cleared, and one geometry
caller-census error is corrected.** caller-census error is corrected.**
**Revision 5 — 2026-08-14, AWAITING APPROVAL. Adds §5a: acceptance 48
re-measured against production at `72da24a`.** The finding is that AC48
is **half implemented** — focus, activation, the focused-only clamp and
the coalescing rules all landed; **listview row selection, panel
selection, terminal mouse reporting and wheel replay did not**, so
`PanelPointer` reaches `dispatch_semantic_panel_pointer` and stops at
focus. No ruling in §3 or §5 changes. **One question is opened,
Q#BP-R1**, on whether a listview row visits on click; this lane
implements selection and does not answer it.
Give pmacs a **bottom panel**: a buffer displayed in a fixed-height window Give pmacs a **bottom panel**: a buffer displayed in a fixed-height window
pinned to the bottom of the frame, resizable by dragging its divider, which pinned to the bottom of the frame, resizable by dragging its divider, which
feature code targets **by policy** instead of by stealing the selected window. feature code targets **by policy** instead of by stealing the selected window.
@ -1770,6 +1780,95 @@ cannot preserve the old panel-focused attach leak.
preserving the Stage 1 unknown-value rollback assertions; the Stage 3 PR preserving the Stage 1 unknown-value rollback assertions; the Stage 3 PR
then runs the full gate suite. then runs the full gate suite.
## 5a. Acceptance 48 — ground truth, MEASURED at `72da24a` (2026-08-14)
**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.
**Acceptance 48 is not unimplemented — it is HALF implemented**, and
the halves were never separated in writing. Measured clause by clause:
| clause | status | production anchor |
|---|---|---|
| click-to-focus | **DONE** | `dispatch_semantic_panel_pointer`'s `activates` → `focus_window` (`src/editor.rs:2701`) |
| terminal panel: non-`Move` activates, hover does neither | **DONE** | the same `activates` computation, split by `is_terminal` |
| focused-only auto-scroll clamp; passive preserves `view_top` | **DONE** | `src/editor.rs:2569`, which already cites "A2A-3 / parent 48" |
| move/drag tails coalesce; press/release/context/wheel lossless and ordered | **DONE** | `pmacs-gpu/src/attach.rs:374`–`:381` — `Move` key 6, `Drag` key 7, everything else unkeyed |
| **listview row selection** | **MISSING** | — |
| **panel selection** | **MISSING** | — |
| **terminal mouse reporting** | **MISSING for panels** | the path exists: `apply_terminal_gesture` |
| **wheel moves the panel's viewport** | **MISSING** | the mechanism exists: `scroll_window` |
| without disturbing the document mirror | **the constraint on all of the above** | — |
### The replay is mostly WIRING, and both mechanisms already exist
**Terminals.** `apply_terminal_gesture` (`src/editor.rs:3525`) is
documented as *"The one terminal pointer path, shared by both frontend
kinds"* — TUI via crossterm, semantic frontend via
`FrontendEvent::TerminalPointer` — and it already drives child mouse
reporting, selection and scrollback. A panel terminal needs the **same
call**, not a second implementation: `side_window_for`
(`src/editor_core.rs:3146`) gives the window, `TerminalViewKey::new`
the key, and `panel_grid_size` — which the dispatcher **already
fetches** — the viewport size. **A wheel-only bridge here would be the
wrong shape**: the shared path handles every kind at once.
**Documents.** `scroll_window` (`src/editor.rs:3845`) is window-scoped
throughout, cursor carry included, and says so: *"a wheel event names
the pane under the pointer and does NOT activate it, so the map must
come from `win_id`"*. Its cursor carry moves **that window's** point —
the panel's own, not the document mirror.
### The scoping hazard, and why the activation rule already answers it
**The selection and cursor API is ACTIVE-WINDOW scoped.**
`set_cursor_byte` (`src/editor_core.rs:1216`), `begin_selection`
(`:4691`) and `clear_selection` all write `active_window_mut()`. Used
naively from the panel path they would move the **document's** point —
precisely what "without disturbing the document mirror" forbids.
**No new ruling is needed, because the ordering already rules it.**
Activation runs *before* replay in the same dispatch, and the gestures
that need the active-window API are exactly the ones that activate:
- **Document panel** — `activates` on `Down(_)`. A drag-select is
Down → Drag → Up, and the Down focused the panel, so the later
gestures act on an active window legitimately.
- **Terminal panel** — `activates` on every non-`Move`, so reporting,
selection and wheel all run focused. This is AC48's own
controller-ownership clause.
- **The one gesture that does NOT activate** is a document panel's
wheel — and it needs only `scroll_window`, which is window-scoped.
So the rule is: **replay after activation, and anything reachable
without activation must use a window-scoped mechanism.** Both halves
are satisfiable with what exists.
### Q#BP-R1 — is a listview row's activation a click or a double-click? **OPEN**
The one question the measurement could not answer from the tree.
`builtin/runtime/listview.lua` binds visiting to **RET/SPC**
(`:610`–`:611`), reading `pmacs.editor.cursor_line()` and calling
`on_visit`. There is no pointer precedent: nothing in the file mentions
a mouse.
So a pointer gesture can *move* to a row unambiguously, but **whether a
single click also VISITS is a product decision with no existing
answer** — single-click-visits matches a file tree, click-selects /
double-click-visits matches an editor list, and AC48's wording
("drives listview row selection") names selection only.
**This lane implements row SELECTION and does not invent activation.**
If visiting on click is wanted it should be ruled explicitly rather
than arriving as a side effect of wiring, because it is the difference
between a click that navigates the user's document and one that does
not.
## 6. Deferred (named) ## 6. Deferred (named)
Left / right / top side windows; multiple slots per side; **rehoming a leaf Left / right / top side windows; multiple slots per side; **rehoming a leaf