docs(panel): Stage 2 framing rev 5 — the three-way slice of 2B
Rev 4 §9 scoped Stage 2B as a single PR: v21 protocol, daemon panel projection, GPU band, and the negotiated `panel_capable` flip. Implementation showed that to be roughly four thousand lines across three crates with three different failure modes. Rev 5 splits it into 2B-1 (the wire layer), 2B-2 (the daemon projection and epoch machine), and 2B-3 (the GPU band and the flip), on the rule that a slice ends where the next thing to build has a different authority. No decision changes. What changes is the allocation: - §7.2 becomes three subsections, and criteria that span a boundary are named in every slice they touch with their half stated, rather than assigned wholesale to one. Parent 39 is the clearest case: its shared-validation and transport-budget halves are wire properties provable in 2B-1, while "the previous valid frame is retained" and "a duplicate does no work" need the epoch machine and are 2B-2. A2B-1 splits the same way — grid exhaustion in 2B-2, the frontend latch in 2B-3. - §9 lists four serial PRs instead of two, each cut from `main`, and states that every slice runs the full gate set rather than the subset its own crate suggests. - §6 records which slice pays the coherence debt. The journey claim belongs to 2B-3 alone: with `panel_capable = false`, a GPU user still gets the Stage 1 non-side fallback after 2A, 2B-1 and 2B-2 have all landed. Three quarters of this stage is preparation. Two things recorded because they are easy to inherit silently: - This revision is retroactive for slice 1. `bottom-panel-stage2b` already carried the v21 protocol layer, written before the revision existed, which inverts framing -> approval -> branch -> implement. The slicing was sound; taking it in code rather than in the document is how a stage's scope drifts without anyone deciding that it should. - 2B-1 and 2B-2 ship dark. The bump advertises a capability whose only distinguishing feature is unreachable until 2B-3, so the arc must not stall between them. Safe for compatibility — appended variants, extended ladder, a v20 peer still negotiates 20 — but a stall should be visible as a decision, not inherited as a default. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LFvC4FQtux4y32KuevZ7B
This commit is contained in:
parent
9b364adc26
commit
56301eda02
|
|
@ -1,7 +1,10 @@
|
|||
# Bottom panel Stage 2 — the GPU panel band (framing)
|
||||
|
||||
**Revision 4 — pre-implementation. Ground truth: canonical `main` @
|
||||
`ccf29e3`, protocol v20, 2026-07-25.**
|
||||
**Revision 5 — 2A merged, 2B in progress. Ground truth: canonical
|
||||
`main` @ `42025e4`, protocol v20 on `main` and v21 on
|
||||
`bottom-panel-stage2b`, 2026-07-26.** Revisions 1–4 were
|
||||
pre-implementation; rev 5 records the three-way slice of Stage 2B
|
||||
(§0.0, §7.2, §9) after its first slice was already built.
|
||||
|
||||
Stage 1 (#155, merge `e745068`) gave pmacs window placement, window
|
||||
parameters, TUI side windows, the divider, and the adopter `display`
|
||||
|
|
@ -26,7 +29,57 @@ geometries), Q#BP16 (pointer transport), Q#BP17 (fold projection), and
|
|||
|
||||
## 0. Revision history
|
||||
|
||||
### 0.0 Round 3 (rev 3 → rev 4) — 1 blocking, 1 high, 1 medium, all closed
|
||||
### 0.0 Rev 4 → rev 5 — the three-way slice of 2B (not a review round)
|
||||
|
||||
This revision changes no decision. It splits one approved
|
||||
implementation slice into three and reallocates the acceptance
|
||||
criteria across them.
|
||||
|
||||
- **R5-1 — why.** Rev 4 §9 scoped 2B as a single PR: v21 protocol,
|
||||
daemon panel projection, GPU band, and the negotiated
|
||||
`panel_capable` flip. Implementation showed that to be roughly four
|
||||
thousand lines spanning `pmacs-protocol`, `src/daemon.rs`, and
|
||||
`pmacs-gpu` — three review surfaces with different failure modes, in
|
||||
one diff. The same argument that produced 2A/2B applies again one
|
||||
level down, and it is the argument this arc has already accepted
|
||||
twice (Lean 4 stages 3a/3b and 4a/4b).
|
||||
- **R5-2 — the boundary rule.** A slice ends where the next thing to
|
||||
build has a different *authority*: the wire format, the daemon that
|
||||
produces frames, and the frontend that paints them. Each slice is
|
||||
independently reviewable against a subset of the parent criteria,
|
||||
and each is additive — no slice makes a previously-passing assertion
|
||||
fail.
|
||||
**Criteria that span a boundary are named in every slice they touch,
|
||||
with their half stated**, rather than assigned wholesale to one. The
|
||||
clearest case is parent 39: its shared-validation and
|
||||
transport-budget halves are wire properties provable in 2B-1, while
|
||||
"the previous valid frame is retained" and "a duplicate does no
|
||||
work" are receiver-state properties that need the epoch machine and
|
||||
land in 2B-2.
|
||||
- **R5-3 — this revision is retroactive for slice 1, and that is a
|
||||
process defect worth recording.** `bottom-panel-stage2b` already
|
||||
carries the v21 protocol layer (three commits, one review round
|
||||
closed) written before this revision existed. The workflow is
|
||||
framing → approval → branch → implement; slice 1 inverted it. The
|
||||
slicing decision was sound, but it was taken in code and discovered
|
||||
in the branch rather than proposed in the document, which is exactly
|
||||
how a stage's scope drifts without anyone deciding that it should.
|
||||
Rev 5 exists to put the decision back where it belongs before slices
|
||||
2 and 3 are written.
|
||||
- **R5-4 — two of the three slices ship dark, deliberately.** Nothing
|
||||
in 2B-1 or 2B-2 is reachable by a user: `panel_capable` stays
|
||||
`false` for every negotiated semantic session until 2B-3, so a v21
|
||||
daemon and a v21 GPU frontend negotiate 21 and behave exactly as
|
||||
they do at v20. This is the same posture 2A took ("seam adoption
|
||||
that becomes load-bearing in 2B") and it carries the same
|
||||
obligation: **the version bump advertises a capability whose only
|
||||
distinguishing feature is unreachable until 2B-3 lands.** That is
|
||||
safe for compatibility — the variants are appended, the ladder is
|
||||
extended, and a v20 peer still negotiates 20 — but it means the arc
|
||||
must not stall between 2B-1 and 2B-3. Recorded here so a stall is
|
||||
visible as a decision rather than inherited as a default.
|
||||
|
||||
### 0.1 Round 3 (rev 3 → rev 4) — 1 blocking, 1 high, 1 medium, all closed
|
||||
|
||||
- **R3-1 (blocker).** Rev 3's three-boundary model was right but its
|
||||
call-site table was wrong in five places, and each error was a real
|
||||
|
|
@ -54,7 +107,7 @@ geometries), Q#BP16 (pointer transport), Q#BP17 (fold projection), and
|
|||
`cell.attachment.is_some()` rejection. It is now classified — and
|
||||
**shared**, with the reasoning pinned.
|
||||
|
||||
### 0.1 Round 2 (rev 2 → rev 3) — 1 blocking, 2 high, 1 medium, all closed
|
||||
### 0.2 Round 2 (rev 2 → rev 3) — 1 blocking, 2 high, 1 medium, all closed
|
||||
|
||||
- **R2-1 (blocker).** Rev 2's "one document-bottom seam" conflated two
|
||||
boundaries that must **diverge** once a panel exists. Several sites it
|
||||
|
|
@ -81,7 +134,7 @@ geometries), Q#BP16 (pointer transport), Q#BP17 (fold projection), and
|
|||
- Both §8 open items are decided (§5.3): `BASE_DIVIDER_HEIGHT = 4.0` at
|
||||
scale 1.0, and `TEXT_TOP` stays unscaled.
|
||||
|
||||
### 0.2 Round 1 (rev 1 → rev 2) — 2 blocking, 3 high, 3 revision points, all closed
|
||||
### 0.3 Round 1 (rev 1 → rev 2) — 2 blocking, 3 high, 3 revision points, all closed
|
||||
|
||||
- **R1-1 (blocker).** Rev 1 said all 23 census reads route through
|
||||
`primary_document_window`. That contradicts Q#BP14, which routes only
|
||||
|
|
@ -589,11 +642,20 @@ exactly once.
|
|||
- **Section this serves:** `COHERENCE.md` §14, which records the panel
|
||||
primitive as landed for Stage 1 and names "Stage 2 (GPU band)
|
||||
pending its own framing" as the open item.
|
||||
- **Which slice pays the coherence debt (rev 5).** The journey claim
|
||||
above is Stage 2B-3's alone. 2A, 2B-1, and 2B-2 close **no** journey
|
||||
divergence: with `panel_capable = false`, a GPU user still gets the
|
||||
Stage 1 non-side fallback on steps 7–10 after all three land. Stated
|
||||
explicitly so no slice's PR can claim the arc's coherence benefit
|
||||
before the flip earns it — three quarters of this stage is
|
||||
preparation, and only the last quarter is the improvement.
|
||||
|
||||
## 7. Acceptance
|
||||
|
||||
**Parent criteria 37–55 remain authoritative and are not replaced.**
|
||||
This section maps them to the two slices and adds only refinements.
|
||||
This section maps them to the four slices — 2A, then 2B-1/2B-2/2B-3 —
|
||||
and adds only refinements. A criterion that spans a slice boundary is
|
||||
named in each slice it touches, with its half stated.
|
||||
|
||||
### 7.1 Stage 2A — classified census routing + painter extraction
|
||||
|
||||
|
|
@ -638,18 +700,80 @@ Refinements 2A adds:
|
|||
scroll state**. Byte-identical cells alone would not catch a clamp
|
||||
that silently moved to the wrong window.
|
||||
|
||||
### 7.2 Stage 2B — v21 protocol + daemon projection + GPU band
|
||||
### 7.2 Stage 2B — v21 protocol, daemon projection, GPU band
|
||||
|
||||
Parent criteria that apply in full: **37, 38, 39, 40, 41, 45, 46, 47,
|
||||
48, 49, 50, 51, 53, 54, 55**, plus re-assertion of **42, 43, 44, and
|
||||
52** **through the actual negotiated capability flip** rather than
|
||||
Stage 2B as a whole owns parent criteria **37, 38, 39, 40, 41, 45, 46,
|
||||
47, 48, 49, 50, 51, 53, 54, 55**, plus re-assertion of **42, 43, 44,
|
||||
and 52** **through the actual negotiated capability flip** rather than
|
||||
through a test-only panel-capable semantic view. 52's 2B form is the
|
||||
production one: a real semantic frontend with `fold_projection = false`
|
||||
displaying a folded buffer in a panel shows every source line, and the
|
||||
panel path never reaches `fold_map_for_window`.
|
||||
|
||||
Refinements 2B adds:
|
||||
Per §0.0 R5-1 those land across three slices. Each slice's own gate run
|
||||
is the standing suite plus §9's named acceptance suites; **only 2B-3
|
||||
changes what a user sees.**
|
||||
|
||||
#### 7.2.1 Slice 2B-1 — the v21 wire layer
|
||||
|
||||
**Authority: `pmacs-protocol`.** The four wire shapes Q#BP9 names, the
|
||||
version bump, and the shared cell-grid validator. No producer, no
|
||||
consumer, no capability change.
|
||||
|
||||
- **37, in full.** `PanelFrame` round-trips including `panel_epoch` and
|
||||
`geometry_epoch`, with independent byte pins on the previous final
|
||||
`InstanceMessage::InitialTargetResult` and
|
||||
`FrontendEvent::TerminalPointer` variants. **Both pins must be
|
||||
falsified by revert**, not merely observed passing: a byte pin that
|
||||
never saw the shift it exists to catch pins nothing.
|
||||
- **39, the wire half only.** Shared cell/topology/glyph/area
|
||||
validation; an area-bounded panel wider than 512 columns is accepted
|
||||
while a terminal frame retains its 512-column PTY cap; the maximum
|
||||
legal panel encoding stays below the transport limit. **The ratchet's
|
||||
fixture must be shown to spend the whole aggregate glyph budget** —
|
||||
otherwise it measures something smaller than the worst case and the
|
||||
bound it proves is not the bound that matters. The worst case is
|
||||
`1 × MAX_PANEL_VISIBLE_CELLS`, a legal panel geometry no terminal can
|
||||
express, so the terminal's own ratchet has never covered it.
|
||||
**39's receiver half — atomic rejection with retention of the
|
||||
previous valid frame, and a duplicate doing no work — is 2B-2.**
|
||||
- **The version ladder moves with the bump.** `PROTOCOL_VERSION`
|
||||
becomes 21, `SUPPORTED_PROTOCOL_VERSIONS` accepts `6..=21` and
|
||||
rejects 22, and any test whose *name* encodes the old number is
|
||||
renamed. A ladder pin that passes across a bump was not pinning the
|
||||
version.
|
||||
- **Shared bounds are aliased, not duplicated.** Every constant the
|
||||
terminal screen and the panel validator both enforce is one
|
||||
definition with the other as an alias, so truncation and validation
|
||||
cannot drift apart.
|
||||
- **Not in this slice:** the daemon arm that drops panel events from a
|
||||
grid session is exhaustiveness bookkeeping the bump forces, not
|
||||
projection. It asserts only that a grid session's panel declaration
|
||||
is dropped rather than trusted.
|
||||
|
||||
#### 7.2.2 Slice 2B-2 — the daemon panel projection and epoch machine
|
||||
|
||||
**Authority: `src/daemon.rs`.** Produces `PanelFrame`; derives the
|
||||
grid; owns stale-event rejection. Exercised through a **test-only**
|
||||
panel-capable semantic view — `panel_capable` stays `false` in
|
||||
production negotiation until 2B-3.
|
||||
|
||||
- **38** (open → replace buffer → hidden by a tiny frame → reappear →
|
||||
close, with authoritative `Absent` and a new epoch on
|
||||
replacement/reappearance), **40** (first open at a non-80×24 frame
|
||||
stays absent until real `FrontendCellGeometry` arrives, never
|
||||
consulting the 24×80 attach placeholder), **49**, **50**, **51**,
|
||||
**53**.
|
||||
- **39's receiver half**, per §7.2.1.
|
||||
- **41, the daemon half:** the daemon alone derives the grid; an older
|
||||
retained frame neither paints nor accepts input after a new
|
||||
`geometry_epoch` until a matching `Present` arrives; row-clamping
|
||||
preserves the stored request; zero, non-finite, and non-positive
|
||||
metric inputs fail closed to zero usable geometry. *The pixel→cell
|
||||
formula and its call sites are 2B-3.*
|
||||
- **42, 43, 44, 45, 52** in their projection form, through the
|
||||
test-only panel-capable view. Their production re-assertion through
|
||||
the real flip is 2B-3.
|
||||
- **A2B-1.** The epoch state machine of §3.1 is pinned row by row,
|
||||
including the lower-epoch-identical-data rejection and the
|
||||
same-epoch-different-total rejection, and each row's
|
||||
|
|
@ -660,7 +784,33 @@ Refinements 2B adds:
|
|||
hides (a subsequent real resize must not paint a stale-geometry
|
||||
panel), and a frontend that exhausts latches — a retained `Present`
|
||||
whose epoch still matches cannot make the band reappear, and only a
|
||||
fresh session clears the latch.
|
||||
fresh session clears the latch. **A2B-1's grid-exhaustion half is
|
||||
2B-2; its frontend-latch half needs a real frontend and is 2B-3.**
|
||||
Both halves are named here so neither is lost at the seam.
|
||||
|
||||
#### 7.2.3 Slice 2B-3 — the GPU band and the capability flip
|
||||
|
||||
**Authority: `pmacs-gpu`, plus the negotiation rule.** This is the only
|
||||
slice a user can observe, and the only one that closes the journey
|
||||
divergence in §6.
|
||||
|
||||
- **46** (band + divider shrink the document text area by exactly their
|
||||
pixel height; carets, hits, and scroll geometry respect the reduced
|
||||
area), **47** (divider drag, `window.min-height`, `RowResize` hover,
|
||||
and the stalled-writer tail-coalescing), **48** (`PanelPointer`
|
||||
driving selection, terminal mouse reporting, and click-to-focus
|
||||
without disturbing the document mirror), **54** (the
|
||||
`--headless-probe` run: one real daemon, real PTY, real wgpu, through
|
||||
a panel-hosted terminal), **55**.
|
||||
- **41, the GPU half:** the pixel→cell conversion pinned at fractional
|
||||
widths and heights, and geometry refresh on window resize, font
|
||||
change, and scale change.
|
||||
- **42, 43, 44, 45, 52 re-asserted through the production flip**, not
|
||||
the test-only view. This is the point of the re-assertion: a
|
||||
test-only panel-capable view can be constructed wrongly and agree
|
||||
with itself, so the production negotiation path must carry the same
|
||||
assertions.
|
||||
- **A2B-1's frontend-latch half**, per §7.2.2.
|
||||
- **A2B-2.** A font or scale change that leaves `CellSize` **identical**
|
||||
still produces a new `geometry_epoch`, and the older `PanelFrame`
|
||||
neither paints nor hit-tests until a matching `Present` arrives. This
|
||||
|
|
@ -704,19 +854,37 @@ fixes. It belongs to a spacing-system change of its own.
|
|||
|
||||
## 9. Slices, branches, and gates
|
||||
|
||||
Per review round 1: **two serial implementation PRs**, each a named
|
||||
slice under this framing so one-feature/one-branch/one-PR holds. **2A
|
||||
lands before 2B branches** — not stacked.
|
||||
Per review round 1 and §0.0 R5-1: **four serial implementation PRs**,
|
||||
each a named slice under this framing so one-feature/one-branch/one-PR
|
||||
holds. **Each slice lands before the next branches** — none are
|
||||
stacked, and each is cut from `main`.
|
||||
|
||||
- **Stage 2A** — classified census routing + per-window painter
|
||||
extraction. Branch `bottom-panel-stage2a`. No protocol change. The
|
||||
three-boundary GPU split is **2B**, not 2A: it is only observable
|
||||
once a band can be installed.
|
||||
- **Stage 2B** — v21 protocol, daemon panel projection, GPU band, and
|
||||
the negotiated `panel_capable` flip. Branch `bottom-panel-stage2b`,
|
||||
cut from `main` after 2A merges. Repeats 2A's relevant census
|
||||
- **Stage 2A — MERGED as #177** (`main` @ `0a3fcd1`). Classified census
|
||||
routing + per-window painter extraction. Branch
|
||||
`bottom-panel-stage2a`. No protocol change. The three-boundary GPU
|
||||
split is **2B-3**, not 2A: it is only observable once a band can be
|
||||
installed.
|
||||
- **Stage 2B-1 — the v21 wire layer.** Branch `bottom-panel-stage2b`.
|
||||
The four wire shapes, the version bump, the shared cell-grid
|
||||
validator, and the version-ladder move. **No producer, no consumer,
|
||||
no capability change** — `panel_capable` stays `false`.
|
||||
- **Stage 2B-2 — the daemon panel projection and epoch machine.** Cut
|
||||
from `main` after 2B-1 merges. Produces `PanelFrame` and owns
|
||||
stale-event rejection, exercised through a **test-only**
|
||||
panel-capable semantic view. Still no production flip.
|
||||
- **Stage 2B-3 — the GPU band and the negotiated flip.** Cut from
|
||||
`main` after 2B-2 merges. The three-boundary text-area split, the
|
||||
divider, pointer routing, and `panel_capable = true` for a v21+
|
||||
negotiated authenticated semantic session. **This is the slice that
|
||||
changes what a user sees**, and it repeats 2A's and 2B-2's relevant
|
||||
assertions through the real capability flip.
|
||||
|
||||
**Each slice runs the full gate set below, not a subset of it.** A
|
||||
slice that touches only `pmacs-protocol` still runs the GPU and vterm
|
||||
suites: the shared validator and the wire enums are exactly the kind of
|
||||
change whose breakage surfaces in a consumer rather than at its own
|
||||
definition.
|
||||
|
||||
Gates for both: the standing suite from `CLAUDE.md`, plus the **touched
|
||||
acceptance suites named explicitly** — the standing rule is to run the
|
||||
suites a change touches, and "standing suite" does not name them:
|
||||
|
|
|
|||
Loading…
Reference in New Issue