From 12b37742a74d27f3558fa14b46c0810367a9a0fc Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Mon, 31 Aug 2026 21:40:14 +0200 Subject: [PATCH] =?UTF-8?q?docs(framing):=20revision=2020=20---=20=C2=A72a?= =?UTF-8?q?=20re-measured=20at=200ec13b3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revision 18's §2a was measured at `72da24a`, now 167 commits back, with #242 and #243 landing on exactly the pointer paths 1b builds on. Every 1b anchor is stale again, which §2a's own history predicted. All four load-bearing claims were re-verified and all four SURVIVE. Quantization still happens BEFORE routing (:3254 quantize, :3262 return on zero, :3270 panel routing) --- checked first, because an inversion would have changed B1's design. `Elsewhere` still collapses four wheel targets. The panel branch still tests `PanelCell` only, so B6's premise holds. `code_scroll_residual` is still the caret-follow residual, not a wheel accumulator. Three base facts were wrong: main.rs is 22,502 lines, not 21,435 PROTOCOL_VERSION is 25, not 24 --- #242 took v25 the panel-replay premise is stale That third one matters most. §2a said a panel wheel "does nothing today, on either axis". #243 landed the replay and it is in this base: the dispatcher is split into `classify_panel_pointer` and `apply_panel_pointer`, and the vertical axis replays for real. THE PREREQUISITE IS DISCHARGED. What does not lapse is the witness --- 1b still owes an end-to-end panel-wheel EFFECT witness on both axes, and #243 discharges only the vertical half. Citation policy: 45 live citations were re-measured and renumbered; a citation that records what an EARLIER revision cited is left exactly as written, and its block is labelled "citations are HISTORICAL (72da24a)". Renumbering those would falsify sentences whose whole point is that an earlier citation was wrong --- revision 15's test-fixture finding is the clearest case. Verified after the sweep: 79 §2a citations, zero out of range, and the seventeen load-bearing anchors each resolve to the construct they name. No ruling changes. Re-measurement, three corrections, one discharge. --- docs/gui-stage1-input-framing.md | 227 +++++++++++++++++++++---------- 1 file changed, 152 insertions(+), 75 deletions(-) diff --git a/docs/gui-stage1-input-framing.md b/docs/gui-stage1-input-framing.md index 355c722..b9f9985 100644 --- a/docs/gui-stage1-input-framing.md +++ b/docs/gui-stage1-input-framing.md @@ -1,6 +1,52 @@ # GUI arc, Stage 1 — input foundation (framing) -**Status: revision 19 — RECONCILES TWO LINEAGES THAT BOTH NUMBERED +**Status: revision 20 — §2a RE-MEASURED at `0ec13b3`, and three base +facts corrected.** Revision 18's §2a was measured at `72da24a`. That +base is now **167 commits back**, and #242 and #243 landed on exactly +the pointer paths 1b builds on, so every 1b anchor is stale again — +which §2a's own history predicted would happen. + +**All four load-bearing claims were re-verified and all four SURVIVE:** + +1. **Quantization still happens BEFORE routing.** `:3254` quantizes, + `:3262` returns on zero, `:3270` routes to the panel. CORRECTION 5's + central point is intact — this was checked first, because an + inversion would have changed B1's design; +2. **`Elsewhere` still collapses four wheel targets** — its doc at + `:2108` still reads *"Not the band: the document, the terminal, the + minimap, or the chrome."*; +3. **the wheel's panel branch still tests `PanelCell` only** (`:3270`), + so minimap pixels still fall through to `scroll_by_lines`. **B6's + premise survives**; +4. **`code_scroll_residual` is still not a wheel accumulator** — it is + the caret-follow pixel residual (`:1664`), cleared at `:6218` and + `:8433`. Reusing it would still be a defect. + +**Three base facts were wrong, and are corrected in place:** + +- `pmacs-gpu/src/main.rs` is **22,502 lines**, not 21,435; +- **`PROTOCOL_VERSION = 25`**, not 24 — v25 was taken by #242's + cell-mapping generation; +- **the panel-replay premise is stale.** §2a said a panel wheel "does + nothing today, on either axis". **#243 landed the replay and it is in + this base**: the dispatcher is split into `classify_panel_pointer` + (`src/editor.rs:2985`) and `apply_panel_pointer` (`:3088`), and the + vertical axis replays for real at `:3277`–`:3278`. **The prerequisite + is DISCHARGED.** What does not lapse is the witness: 1b still owes an + end-to-end panel-wheel **effect** witness on **both** axes, of which + #243 discharges only the vertical half. + +**Citation policy, applied throughout:** a citation that makes a claim +about the tree **as it is** was re-measured and renumbered — 45 of +them. A citation that records **what an earlier revision cited** is +left exactly as written and its block is labelled *"citations are +HISTORICAL (`72da24a`)"*. Renumbering those would have falsified +sentences whose whole point is that an earlier citation was wrong. + +**No ruling changes in revision 20.** It is a re-measurement, three +corrections, and a discharge. + +**Previously, revision 19 — RECONCILES TWO LINEAGES THAT BOTH NUMBERED THEMSELVES 13.** This document was advanced independently by two branches, and both called their change "revision 13" — the second block at `revision 13` below is the other lineage's, left exactly as it @@ -35,7 +81,7 @@ window-targeted `view_left` path and B3's wrap-pins-to-zero. **Nothing in revision 19 changes a ruling.** It is a reconciliation and a record. -**Previously, revision 18 — APPROVED (2026-08-14).** §2a is the 1b ground +**Previously, revision 18 — APPROVED (2026-08-14); its citations are HISTORICAL (`72da24a`).** §2a is the 1b ground truth and its rulings are settled: **Q#S1-11 (B)**, the five-clause lifetime contract with witnesses L1–L8, B1's six-target enumeration with discriminators **R1–R5**, and B3's exact saturated bound. Revision @@ -70,7 +116,7 @@ survived from 14 and 15 untouched. That is the failure mode §6 names, arriving once per round, and it is why the R- and L-rows specify their *drivers* and *setups* rather than only their assertions. -**Previously, revision 17 — SUPERSEDED.** Two additions and one +**Previously, revision 17 — SUPERSEDED; its citations are HISTORICAL (`72da24a`).** Two additions and one correction: - **B1 gains the two separations its crossing witness could not @@ -94,7 +140,7 @@ correction: binaries **sequentially**, so the two steps never differed in concurrency and no experiment reversed anything. -**Previously, revision 16 — SUPERSEDED.** Four changes, three of them +**Previously, revision 16 — SUPERSEDED; its citations are HISTORICAL (`72da24a`).** Four changes, three of them corrections to 15's own reasoning: - **The panel-replay lane becomes a HARD PREREQUISITE in §3's @@ -119,7 +165,11 @@ corrections to 15's own reasoning: already says is false and unachievable; mutations now name the rows they must **bite** and the **legitimate dependents**. -**Previously, revision 15 — SUPERSEDED.** Answered review of 14 and +**Previously, revision 15 — SUPERSEDED. Every citation and every +present-tense claim in this block is HISTORICAL, measured at +`72da24a`**; see revision 20 for the live sites, and note in particular +that "replays nothing" was true then and is **not** true on `0ec13b3`. +Answered review of 14 and **changed two B-row contracts**, which 14 wrongly denied. - **B1 is now fully ruled**, not half-ruled. Revision 14 left two @@ -149,11 +199,11 @@ B1 gains a per-target enumeration it did not have, and B3 gains B7's exact upper bound in place of "content bounds". B2, B4, B5, B6 and B7 are unchanged. -**Previously, revision 14 — SUPERSEDED.** Ruled Q#S1-11 (B) with the +**Previously, revision 14 — SUPERSEDED; its citations are HISTORICAL (`72da24a`).** Ruled Q#S1-11 (B) with the five-clause lifetime contract, and gave B3 B7's exact saturated bound. Both stand. -**Previously, revision 13 — SUPERSEDED.** §2a's ground-truth +**Previously, revision 13 — SUPERSEDED; its citations are HISTORICAL (`72da24a`).** §2a's ground-truth re-measurement for Stage 1b at the post-#240 tip. Every 1b anchor was stale, as expected; what was not was that **three B-rows describe a field as empty when it is occupied**, and that **B7 re-opens a question @@ -290,27 +340,32 @@ produces nothing. §2 above was measured for **1a**, at `4f77491`. 1a (#239) has since merged and #240 landed on top, so **every coordinate the 1b table cites -is stale** — `pmacs-gpu/src/main.rs` is now **21,435 lines** and 1-pre's +is stale** — `pmacs-gpu/src/main.rs` is now **22,502 lines** and 1-pre's router extraction moved the wheel path wholesale. Renumbering alone would have been routine. It is not what the measurement found. -**`PROTOCOL_VERSION = 24`** now (1a's `TextInput`). 1b remains -non-protocol-bearing. +**`PROTOCOL_VERSION = 25`** now (`pmacs-protocol/src/message.rs:2002`) +— **not 24**, which is what this line said until revision 20: v25 was +taken by the panel cell-mapping generation (#242). 1b remains +non-protocol-bearing either way. ### The anchors, re-measured -| the table cites | holds what now | the real site | +**Left column: HISTORICAL — what §1b's table cited, as measured at +`72da24a`. Right column: LIVE, re-measured at `0ec13b3`.** + +| the table cited (historical, `72da24a`) | held what then | the real site NOW (`0ec13b3`) | |---|---|---| -| `main.rs:2061` "minimap is `Elsewhere`" | `PanelCell(CellCoord)` | `enum PointerSurface` `:2057`; **`Elsewhere` `:2067`** | -| `main.rs:3337` wheel reads `pointer_pos` | an `attach_client` line | `apply_wheel` `:3090` and `:3113` | -| `main.rs:3373` falls to `scroll_by_lines` | a bare `}` | `apply_wheel` `:3126`; `scroll_by_lines` `:8002` | -| `editor.rs:3052` `dispatch_mouse` | — | **`:3207`** | -| `editor.rs:3203` scroll arms | a doc-comment line | `ScrollUp` `:3358`, `ScrollDown` `:3362` | +| `main.rs:2061` "minimap is `Elsewhere`" | `PanelCell(CellCoord)` | `enum PointerSurface` `:2099`; **`Elsewhere` `:2109`** | +| `main.rs:3337` wheel reads `pointer_pos` | an `attach_client` line | `apply_wheel` `:3270` and `:3293` | +| `main.rs:3373` falls to `scroll_by_lines` | a bare `}` | `apply_wheel` `:3306`; `scroll_by_lines` `:8417` | +| `editor.rs:3052` `dispatch_mouse` | — | **`:3920`** | +| `editor.rs:3203` scroll arms | a doc-comment line | `ScrollUp` `:4296`, `ScrollDown` `:4299` | **B6's premise survives the move intact**, and is now stated by the code itself: `Elsewhere`'s doc comment reads *"Not the band: the document, the terminal, the minimap, or the chrome."* The wheel's panel branch -(`:3090`) tests `PanelCell` only, so minimap pixels still fall through +(`:3270`) tests `PanelCell` only, so minimap pixels still fall through to `scroll_by_lines`. **B1's defect is visible in four lines.** `apply_wheel` rounds to whole @@ -321,11 +376,11 @@ let lines = match delta { LineDelta(_, y) => (-y * WHEEL_LINES_PER_TICK).round() if lines == 0 { return; } ``` -`:3075`–`:3084`. The `_` is the **x** delta, discarded at the same site +`:3254`–`:3262`. The `_` is the **x** delta, discarded at the same site — so **B1 and B2's witnesses are the same four lines**, and a residual accumulator is what both need. **There is no wheel accumulator today.** -`code_scroll_residual` (`:1622`) is *not* it: that is the caret-follow -pixel residual, buffer-scoped, cleared at `:5928`. **Reusing it would +`code_scroll_residual` (`:1664`) is *not* it: that is the caret-follow +pixel residual, buffer-scoped, cleared at `:6218`. **Reusing it would be a defect**, not a shortcut. ### CORRECTION 5 — B1's "surface" is not enumerated, and the classifier cannot enumerate it @@ -334,20 +389,20 @@ B1 says "residual per **axis and surface**" and the table leaves "surface" undefined. Two facts make that a hole rather than a detail. **Quantization happens BEFORE routing.** The rounding and the -`lines == 0` return are at `:3074`–`:3084`; the panel branch is `:3090` -and the terminal branch `:3112`. So a sub-tick delta bound for the +`lines == 0` return are at `:3254`–`:3262`; the panel branch is `:3270` +and the terminal branch `:3292`. So a sub-tick delta bound for the panel or the terminal is **discarded before anything knows where it was going**. An accumulator added after the routing decision would fix the document and leave the wire targets exactly as broken as they are now. **And `PointerSurface` cannot name the surfaces B1 needs.** -`classify_pointer_surface` (`:7192`) resolves panel geometry only; -`Elsewhere` (`:2067`) is *"the document, the terminal, the minimap, or +`classify_pointer_surface` (`:7577`) resolves panel geometry only; +`Elsewhere` (`:2109`) is *"the document, the terminal, the minimap, or the chrome"* — four wheel targets under one name, three of which B1 and B6 must distinguish. **B1 needs a wheel-target enumeration; it does not get one for free from the existing classifier.** -Note also a live consequence of `:3090` matching `PanelCell(_)` alone: +Note also a live consequence of `:3270` matching `PanelCell(_)` alone: **a wheel over the panel DIVIDER or the band's BACKGROUND scrolls the document today**, though the enum's own doc says the band "still owns the pixel." @@ -358,11 +413,11 @@ they differ 1b closes the gap: | wheel target | classified | vertical today | vertical RULED | residual owner | horizontal RULED | |---|---|---|---|---|---| -| Panel cell | `PanelCell` | emits (`:3102`), **replay missing** | emit **both axes**; replay is a **prerequisite lane** | per panel | emit `ScrollLeft`/`Right` | +| Panel cell | `PanelCell` | emits (`:3282`); **vertical replay LANDED in #243** (`src/editor.rs:3277`) | emit **both axes**; the horizontal leg is B1–B3's | per panel | emit `ScrollLeft`/`Right` | | Panel divider / background | `PanelDivider` / `PanelBackground` | **scrolls the document** | **consume both axes** — the band owns the pixel | **none — discards** | consumed, no emit | -| Terminal | `Elsewhere` + `terminal.is_some()` (`:3112`) | emits (`:3122`) | emit **both axes** | per terminal | emit `ScrollLeft`/`Right`; **inert when not reporting** | +| Terminal | `Elsewhere` + `terminal.is_some()` (`:3292`) | emits (`:3302`) | emit **both axes** | per terminal | emit `ScrollLeft`/`Right`; **inert when not reporting** | | Minimap | `Elsewhere` | document `scroll_by_lines` | document viewport, **own residual** (B6) | **its own** | inert | -| Document | `Elsewhere` | `scroll_by_lines` (`:3126`) | unchanged | document | `code_scroll_left` (B3) | +| Document | `Elsewhere` | `scroll_by_lines` (`:3306`) | unchanged | document | `code_scroll_left` (B3) | | Chrome | `Elsewhere` | document | **shares the document's**, deliberately | **the document's** | shares the document's | **Divider and background consume both axes, and BANK NOTHING.** Falling @@ -416,7 +471,7 @@ minimap do not get replaced — their state OUTLIVES the buffer.** A panel or terminal residual can be keyed to a surface that goes away; the document's and the minimap's live in the long-lived GPU `State`, which survives a buffer switch. **Their reset is manual, and there is a list -to be forgotten from.** Buffer replacement (`pmacs-gpu/src/main.rs:5922` +to be forgotten from.** Buffer replacement (`pmacs-gpu/src/main.rs:6218` onward) zeroes `scroll_top`, `code_scroll_residual` and `code_scroll_left` **one explicit line at a time**, each with its own comment — including `code_scroll_left`, added later precisely because a @@ -463,8 +518,8 @@ needs no protocol bump** — 1b stays non-protocol-bearing. **The terminal answer follows from the encoder.** `sgr_mouse_report` (`src/terminal/input.rs`) already encodes `ScrollLeft` as **66** and -`ScrollRight` as **67** (`:126`–`:127`), so the terminal handles both -axes the moment they are emitted. Its guard (`:111`) returns `None` +`ScrollRight` as **67** (`:130`–`:131`), so the terminal handles both +axes the moment they are emitted. Its guard (`:120`) returns `None` unless `mouse_sgr` is on and tracking is not `Off`, so a **non-reporting terminal is horizontally inert** — which matches the TUI, where nothing consumes a horizontal tick either. No new arm is @@ -474,22 +529,33 @@ needed. **Revision 14 cited `src/daemon.rs:6683` as the panel's `ScrollUp` handler. That was wrong: it is a test fixture**, inside `#[cfg(test)] -mod tests` (opened at `:3740`). Citing a fixture as production is +mod tests` (opened at `:6813`). Citing a fixture as production is precisely the error B1's enumeration exists to prevent, and it hid a real defect. -The production path is `dispatch_semantic_panel_pointer` -(`src/editor.rs:2674`). It validates the coord against the panel grid, -resolves the side window, focuses when the gesture activates — and -**returns `true` without replaying anything.** Its own doc says so: -*"**Replay is out of scope in Stage 2B-2.** Driving selection, listview -rows, or child SGR reporting is **parent acceptance 48**, which needs -the GPU band and lands in **Stage 2B-3**."* +**As measured at `72da24a`**, the production path was +`dispatch_semantic_panel_pointer` (`src/editor.rs:2674` *at that base*). +It validated the coord against the panel grid, resolved the side +window, focused when the gesture activated — and **returned `true` +without replaying anything.** Its own doc said so: *"**Replay is out of +scope in Stage 2B-2.** Driving selection, listview rows, or child SGR +reporting is **parent acceptance 48**, which needs the GPU band and +lands in **Stage 2B-3**."* -So **a panel wheel does nothing today, on either axis** — the frontend -emits, the daemon validates and drops. That is a **pre-existing -violation of an already-ruled contract**, uncovered by this -measurement, not created by 1b. +**THAT IS NO LONGER THE BASE. #243 landed the replay, and revision 20 +re-measures it.** On `0ec13b3` the dispatcher is split into +`classify_panel_pointer` (`src/editor.rs:2985`) and +`apply_panel_pointer` (`:3088`), and the effect path replays a **panel +wheel's vertical axis** for real: `PKind::ScrollUp => +self.scroll_window(side, -SCROLL_LINES)` and its `ScrollDown` twin +(`:3277`–`:3278`). + +**So "a panel wheel does nothing today, on either axis" is FALSE on this +base, and the sentence is corrected rather than deleted** — it was true +when written and is the reason the prerequisite lane existed. What +remains is narrower and was already assigned here: **the horizontal +document-panel leg is B1–B3's**, per the "Panel-replay consequence" +paragraph in §1b. **Ruling: 1b does not absorb it, and 1b does not ship before it.** The replay is repaired in a **prerequisite lane** carrying parent @@ -548,13 +614,13 @@ Each of these reads as an empty field in the table and is not one. The contracts are unaffected; the **implementation shape** is. - **B3 — "no horizontal scroll to clamp".** The GPU has a horizontal - origin: **`code_scroll_left`** (`:1639`), a pixel offset *snapped to - the column grid*, moved by its own `horizontal_follow` (`:7702`). + origin: **`code_scroll_left`** (`:1681`), a pixel offset *snapped to + the column grid*, moved by its own `horizontal_follow` (`:8117`). What is missing is a **wheel-driven** horizontal scroll. B3 adds a second writer to an existing field, which is a different job from introducing one. - **B5 — "no I-beam".** True as stated, but the cursor already has an - owner: **`apply_panel_cursor_icon`** (`:7328`) sets `RowResize` over + owner: **`apply_panel_cursor_icon`** (`:7743`) sets `RowResize` over the divider and **`CursorIcon::Default` everywhere else**. An I-beam written as a separate site would be **clobbered by that else branch**. B5 must extend this function, not join it. @@ -570,7 +636,7 @@ contracts are unaffected; the **implementation shape** is. This is the finding that needs a ruling rather than a renumber. The TUI horizontal origin is **`window.view_left`** (`src/window.rs:386`), -and `horizontal_follow` (`src/editor.rs:4495`) already **pins it to 0 +and `horizontal_follow` (`src/editor.rs:5233`) already **pins it to 0 under wrap** — so *B7's wrap clause is implemented today*, for the caret-follow path. Both frontends share the arithmetic (`pmacs_protocol::scroll::follow_left`, `scroll.rs:134`), deliberately. @@ -591,18 +657,18 @@ scroll for exactly this reason: > would otherwise snap `view_top` straight back … so the user's > mouse-wheel scroll would feel stuck after one notch. -(That citation is itself stale: `scroll_window` is **`src/editor.rs:3845`**, +(That citation is itself stale: `scroll_window` is **`src/editor.rs:4583`**, not `:3628`.) So a wheel-driven `view_left` that does **not** carry the cursor is snapped back, and horizontal wheel scrolling "feels stuck after one notch" — the identical bug, one axis over, on **both** frontends, since -the GPU's `horizontal_follow` (`:7702`) has the same shape. +the GPU's `horizontal_follow` (`:8117`) has the same shape. **And it happens on the next PAINT, not the next caret event.** -`prepare_window_cursor_visible` (`src/editor.rs:4518`) calls -`horizontal_follow` unconditionally as its **first** act (`:4539`), and -`paint_frame` (`:4747`) calls it every frame (`:4852`, and `:2572` for +`prepare_window_cursor_visible` (`src/editor.rs:5256`) calls +`horizontal_follow` unconditionally as its **first** act (`:5277`), and +`paint_frame` (`:5485`) calls it every frame (`:5590`, and `:2572` for the panel). Revision 13 said "the next caret event"; that was wrong and understated the exposure — the origin is overwritten by a redraw with no input at all. @@ -616,17 +682,17 @@ viewport that is overwritten on the next frame. **(A) is not viable in 1b, and the vertical precedent does not reach it.** `scroll_window` carries point because it is **TUI-side**, where the editor owns the cursor directly. The GPU has no such power: -`OwnCursor` (`pmacs-gpu/src/main.rs:2337`) is *"pmacs-gpu's own cursor +`OwnCursor` (`pmacs-gpu/src/main.rs:1774`) is *"pmacs-gpu's own cursor position, **mirrored** from `CursorByte`"* — a read-only reflection of daemon state. The only wire operation that positions it is `Pointer`, -and `dispatch_pointer` (`src/editor.rs:3638`) sets `active_frontend`, +and `dispatch_pointer` (`src/editor.rs:4376`) sets `active_frontend`, calls `break_command_chain`, and — by its own comment — *"Every `PointerKind` moves point or changes the selection."* Carrying point from a wheel would mean **a new wire operation**, which contradicts 1b's non-protocol scope outright. Note also that **GPU vertical scrolling already does not carry point**: -`apply_wheel` ends at `send_viewport` (`:3129`). (A) would therefore not +`apply_wheel` ends at `send_viewport` (`:3309`). (A) would therefore not even be internally consistent — it would make the horizontal axis carry point on a frontend where the vertical axis does not. @@ -655,7 +721,7 @@ undefined, which is the part that decides whether the feature works. *changing*, never by elapsed time or by the follow running. 5. **Wrap and buffer replacement clear it and pin the origin to zero.** This is the existing rule (`horizontal_follow`'s wrap branch, and - the GPU's at `:7703`); authority must not survive either. + the GPU's at `:8089`); authority must not survive either. ##### What B7 and B3 must witness @@ -664,12 +730,12 @@ a follow that runs inside a frame. **The two frontends need DIFFERENT preservation drivers, and this is where revision 14 was wrong.** It said "the next paint" without -qualification. **That is TUI-only.** The GPU's `render()` (`:9881`) goes +qualification. **That is TUI-only.** The GPU's `render()` (`:10296`) goes straight to `render_to_view` and **never calls `horizontal_follow`**; -the follow reaches it only through `ensure_caret_painted` (`:7674`), -whose callers are cursor paths (`:5607`, `:5659`, `:5776`, `:6320`) and -geometry paths — `resize` (`:9851`), `apply_font_facts` (`:9729`), -`reflow_dynamic_code_geometry` (`:9672`). **A GPU wheel-then-paint row +the follow reaches it only through `ensure_caret_painted` (`:8071`), +whose callers are cursor paths (`:5897`, `:5949`, `:6066`, `:6610`) and +geometry paths — `resize` (`:10266`), `apply_font_facts` (`:10144`), +`reflow_dynamic_code_geometry` (`:10087`). **A GPU wheel-then-paint row would stay green with the overwrite mutation restored**, which is a vacuous witness of exactly the kind this framing keeps producing. @@ -764,7 +830,7 @@ the same bound on the same rule, and B7's exactness exists because the loose version *blanks the viewport*. **B3 takes B7's rule verbatim, in the GPU's column-grid units.** The -GPU already reckons in that grid — `horizontal_follow` (`:7702`) +GPU already reckons in that grid — `horizontal_follow` (`:8117`) derives `cols = (width / advance).floor()` and `left_col = (code_scroll_left / advance).round()`, then re-multiplies to snap the offset back onto the grid, deliberately, so both frontends put the same @@ -797,20 +863,31 @@ B7's revision found and B3 currently has no row to catch. **1c is NOT protocol-bearing** under Q#S1-8's ruling. Protocol slices are serialized. -**The panel-replay lane is a HARD PREREQUISITE of 1b**, not a parallel -track. §2a found that `dispatch_semantic_panel_pointer` validates and -focuses but **replays nothing**, so a panel wheel is dead on both axes -today — a pre-existing gap in **parent acceptance 48** -(`docs/bottom-panel-framing.md:1719`), which owns it. 1b's B1 makes the -panel a first-class wheel target with its own residual, and **the -defect is exactly "frontend emits, receiver discards"**, so a 1b that -witnessed emission alone would repeat the blind spot that hid it. +**The panel-replay lane WAS a hard prerequisite of 1b. It is +DISCHARGED.** §2a found, at `72da24a`, that +`dispatch_semantic_panel_pointer` validated and focused but **replayed +nothing**, so a panel wheel was dead on both axes — a pre-existing gap +in **parent acceptance 48** (`docs/bottom-panel-framing.md:1719`), which +owned it. -**Order: the replay lane merges, 1b rebases onto that merge commit, -then 1b lands an end-to-end panel-wheel EFFECT witness on both axes.** -1b's base -moves off `72da24a` accordingly; the rest of §2a is measured on the GPU -and TUI sides and is unaffected. +**#243 repaired it, and it is in this base.** `0ec13b3` contains the +split dispatcher (`src/editor.rs:2985`, `:3088`) and the vertical +panel-wheel replay (`:3277`–`:3278`). **1b has no outstanding +prerequisite**, and the rebase this revision was written on is the one +the order below called for. + +**What does NOT lapse is the witness.** The reason for demanding it +stands unchanged: 1b's B1 makes the panel a first-class wheel target +with its own residual, and **the original defect was exactly "frontend +emits, receiver discards"** — so a 1b that witnessed emission alone +would repeat the blind spot that hid it. **1b still owes an end-to-end +panel-wheel EFFECT witness on BOTH axes**, and #243 discharges only the +vertical half of what that witness must show. + +**Order, as it now stands: the prerequisite is met; 1b implements +B1–B7 on this base and lands the both-axis effect witness.** 1b's base +is **`0ec13b3`**, off `72da24a` as the earlier order required; the rest +of §2a is measured on the GPU and TUI sides and is unaffected. ## 4. Q#S1-8 — RULED: (A), preserve pre-window readiness