docs(bottom-panel): record Stage 2B-3 and close Stage 2

Handoff section 1 gains the 2B-3 bullet and the protocol anchor moves to
v21 with the rule that matters stated once: advertise the baseline,
negotiate up from the frontend's AttachRequest, and reserve moving the
advertised version for a change that cannot be expressed additively at
all. 2B-1's forward-looking constraint is marked discharged rather than
deleted, because its acceptance still passes unchanged and that is the
evidence.

COHERENCE section 14 grades the bottom/side panel primitive as complete on
BOTH frontends rather than 'Stage 2 pending its own framing', section 20
P5 follows it, and the section-19 protocol bullet records that the v21
family is live in production without an incompatible handshake change.

The active-work lane is rewritten to the shipped slice, including the
rejected activation alternatives and why the server-first shape forces
each one out, and the one-way compatibility window it leaves open.

The 2B-2 acceptance suite's header said production keeps panel_capable
false for every semantic session. That is no longer true, and its
assertions did not change — which is the point, so the header now says so
rather than being quietly left stale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
This commit is contained in:
Levi Neuwirth 2026-07-29 19:12:36 -04:00
parent 71cc31bfbb
commit 8ee08d93e5
4 changed files with 234 additions and 66 deletions

View File

@ -107,7 +107,7 @@ remain open to them.
| 11 | Config layering + provenance | **Partial (foundation only)** | Typed registry is right; 5 settings live in it; no value provenance |
| 12 | Profiles | **Missing** | One hardcoded default keymap; not a named concept |
| 13 | Package lifecycle UX | **Resolution without lifecycle** | Mature resolver/lockfile; init-only install; no uninstall/disable/search |
| 14 | Workbench primitives | **Partial (best trajectory)** | Listview is a real primitive but only 3 call sites, all LSP panels; buffer-list and search re-implement it; bottom panel landed (#155) |
| 14 | Workbench primitives | **Partial (best trajectory)** | Listview is a real primitive but only 3 call sites, all LSP panels; buffer-list and search re-implement it; bottom panel complete on BOTH frontends (#155 + Stage 2) |
| 15 | Contextual affordances | **Weak** | Right-click menu only; code actions apply first-blindly; no git integration at all |
| 16 | Semantic frontend | **Strong** | v6..=v21 schema support; production attach remains v20 during the dark panel slice; degradation practiced |
| 17 | Distribution | **Missing** | CI is test-only; no binaries, channels, checksums, or update path |
@ -1280,10 +1280,17 @@ Primitive-by-primitive against the list above:
vocabulary is fixed Rust-side).
- **Contextual popup** ✓ — completion popup, context menu (each a
shadow, §6).
- **Bottom/side panel** ✓ — landed as bottom-panel Stage 1 (#155):
- **Bottom/side panel** ✓ — Stage 1 (#155) gave the substrate:
`WindowParams` side/fixed_rows/dedicated, `display = "current" |
"panel"` adopted by listview/compile/terminal, quit-action, divider
drag. Stage 2 (GPU band) pending its own framing.
drag. **Stage 2 completed it on the second frontend** (2A #177, 2B-1
#184, 2B-2 #187, 2B-3): the GPU frontend now renders a real panel
band with its own divider, pointer routing, and resize drag, and a
semantic session that negotiates the panel wire is panel-capable.
Both frontends therefore share one placement primitive rather than
the GPU silently taking the Stage 1 non-side fallback. Stage 3 — the
adopter default flip, so omitting `display` resolves to the panel
policy — is the remaining step.
- **Task/progress view** △ — `*workers*` exists but joins nothing
(§9).
- **Help view** △ — exists twice (§5); needs unification, not
@ -1366,10 +1373,15 @@ its asks are already practiced.**
- Versioned protocol schema `SUPPORTED=[6..=21]` with deliberate
encoding-breaking bumps, both-frontends support required per bump,
and byte-pin discipline for appended variants (handoff §4). The v21
bottom-panel family landed with Stage 2B-1 (#184) and is reserved but
dark: because `Hello` is server-first, the production daemon still
advertises v20 so shipped v20 clients remain attachable; compatible
v21 activation belongs to Stage 2B-3.
bottom-panel family landed with Stage 2B-1 (#184) and is **live in
production since Stage 2B-3** — activated without an incompatible
handshake change. Because `Hello` is server-first, the daemon's
advertised version is now a permanent compatibility **baseline**
(still 20) and the session's real version is settled one message
later by the frontend's `AttachRequest` counter-offer. That split —
advertise the floor, negotiate up — is the reusable pattern for every
future additive family, and it means bumping the advertised version is
reserved for a change that cannot be expressed additively at all.
- Two genuine frontends share the conceptual model; CRDT concurrent
editing with presence across them; remote attach + reconnect.
- **Graceful per-frontend degradation is practiced, not aspirational**:
@ -1569,10 +1581,12 @@ and §18's floor ride on this.
### Priority 5: Finish the workbench convergence
**State: partial and moving (§14) — bottom panel Stage 1 landed, GPU
band pending; listview proven.** Remaining: the tree primitive (build
it before dired and the worker tree invent two), table/inspector/diff,
help unification. Wiring plus one modest model piece (the tree model).
**State: partial and moving (§14) — the bottom panel is now complete on
both frontends (Stage 1 #155 through Stage 2B-3), and listview is
proven.** Only the adopter default flip (Stage 3) remains on the panel
itself. Remaining elsewhere: the tree primitive (build it before dired
and the worker tree invent two), table/inspector/diff, help unification.
Wiring plus one modest model piece (the tree model).
### Priority 6: Productize configuration

View File

@ -1,6 +1,6 @@
# Active work — cross-machine resume ledger
**Snapshot: 2026-07-28.** This file records volatile work that has not
**Snapshot: 2026-07-29.** This file records volatile work that has not
landed on `main`. Read it after `docs/agent-handoff.md`. Remove completed
entries when their PR merges; do not let this become a second permanent
backlog.
@ -245,24 +245,84 @@ If it does not, stop and repair the remote/fetch configuration.
never been enforced. Any CI job that compiles the `crdt` targets has to
fix them first or it will be red on arrival.
## Bottom-panel lane (Arc 7) — 2B-2 MERGED; 2B-3 IS NEXT
## Bottom-panel lane (Arc 7) — 2B-3 OPEN; Stage 2 is COMPLETE with it
Stage 1, the Stage 2 framing, Stage 2A, Stage 2B-1, and **Stage 2B-2 are
all on `main`**. Framing revision 5's three-way split of 2B was
explicitly approved on 2026-07-27; revision 6 records PR #184's review
correction. **2B-2 — the daemon panel projection and epoch machine —
landed as [PR #187](https://github.com/levineuwirth/pmacs/pull/187)**,
one review round of five findings on top of the implementation, 12/12
green, 22/22 mutations biting. Its durable lessons are in
`docs/agent-handoff.md` §1; what remains below is the 2B-3 plan.
Stage 1, the Stage 2 framing, Stage 2A, Stage 2B-1, and Stage 2B-2 are
all on `main`. **Stage 2B-3 — the GPU panel band, compatible protocol-v21
activation, and the negotiated `panel_capable` flip — is this lane's open
work**, and it completes Stage 2. Framing revision 5's three-way split of
2B was explicitly approved on 2026-07-27; revision 6 records PR #184's
review correction.
**2B-2's boundaries, restated because they are easy to overrun:** the
production `Hello` stays at v20 and `panel_capable` stays `false`. The
slice is dark/test-only capability exactly as 2B-1 was. Compatible v21
activation, the GPU band, and the negotiated capability flip are all
2B-3's, and 2B-3 may **not** simply change the unsolicited `Hello` to
21.
- **Branch `bottom-panel-stage2b3`, worktree `../pmacs-bp-2b3`, cut fresh
from landed `main` @ `e003b81`** (`Merge pull request #190 from
levineuwirth/resource-op-delete-guard-impl`) — measured with
`git log --oneline -1 githubsucks/main`, not carried over from a
briefing. Not stacked on `../pmacs-bp-stage2b2`: each slice starts from
landed main, which is the house pattern and what 2B-2 did.
- **The activation mechanism, and why the alternatives fail.** The
constraint inherited from 2B-1's review is that the unsolicited `Hello`
may not become 21: it is server-first, so a shipped v20 frontend rejects
an unknown version *before* it can send `AttachRequest`. What 2B-3 ships
instead is a **frontend counter-offer**:
`ADVERTISED_PROTOCOL_VERSION` becomes a permanent compatibility
*baseline* (20), the frontend answers
`requested_protocol_version(baseline)` — its own `PROTOCOL_VERSION` when
the baseline is current, a verbatim echo of anything older — and the
daemon records `negotiated_session_version(offer)`. The daemon needed
**no change** to accept it; it already recorded `req.protocol_version`.
Rejected alternatives, each for a reason the server-first shape forces:
growing `Hello`/`InstanceCapabilities` (postcard structs are positional,
so it breaks the *other* direction symmetrically and cannot be verified
against shipped binaries); a client-first hint (an old client writes
nothing until it has read `Hello`, so any probe is a timeout);
sniffing `instance_identity.pmacs_version` (version-string branching on
a field documented as display-only); and a second post-`Hello` daemon
message (an old client reads it as an `InstanceMessage` and dies on the
unknown discriminant).
- **The window this leaves open is named, not hidden.** A daemon whose own
`PROTOCOL_VERSION` equals the baseline rejects an offer above its
supported range. A single `AttachRequest` cannot mean both "I want 21"
and "≤ 20", so compatibility is preserved for old *frontends* — the
direction that matters, since the daemon is what a user leaves running —
and the window closes on the next daemon restart. It surfaces as an
explicit `GoodbyeReason::VersionMismatch` naming both versions, pinned by
`an_unsupported_offer_is_refused_by_name`.
- **Five commits:** the activation mechanism, the three-boundary split plus
the band's machinery, the paint/input wiring, the acceptance suites, and
the bite fixes plus these docs.
- **Verification on the reviewed head** (all redirected to files and
checked by their own exit codes, never through a pipe): recorded in the
PR body and reproduced by the commands in `CLAUDE.md`.
- **23 falsifying mutations, all executed, all biting**, using an in-place
mutation runner rather than `scripts/bite` because several claims live in
the *same file* as their test and a whole-file swap would not compile.
**Three assertions were VACUOUS on the first pass and the mutation runs
are what found them** — a fixed-point contrast check, a pixel test that
passed with the band painting nothing, and a monospace fixture that could
not tell two glyph-advance derivations apart. A fourth finding was about
the code, not the tests: "no panel frame reaches a v20 session" is
defence in depth, not the placement gate, and passes with the capability
gate removed entirely.
- **Two real defects the new tests caught in the implementation**, both
recorded in `docs/agent-handoff.md` §1: `edge_scroll_direction` has no
upper bound, so reclassifying its boundary was necessary but not
sufficient; and `apply_panel_payload` ignored the exhaustion latch.
- **Cross-machine recovery (fresh clone):**
```sh
git fetch githubsucks --prune
git switch --track -c bottom-panel-stage2b3 githubsucks/bottom-panel-stage2b3
git rev-parse HEAD
```
- **Stage 3 (the adopter default flip) is the arc's last step** and is not
started. This lane is not removed at 2B-3's merge: Stage 3 remains ahead
of it.
- **Stage 2B-2 MERGED as #187.** One review round of five findings on top
of the implementation, 12/12 green, 22/22 mutations biting. Its durable
lessons are in `docs/agent-handoff.md` §1.
- **What PR #187 shipped, dark by construction:** the semantic daemon's
`FrontendCellGeometry` epoch machine; one reconciled panel grid
derivation; `PanelFrame::{Present, Absent}` projection on both document
@ -308,7 +368,7 @@ activation, the GPU band, and the negotiated capability flip are all
```
#187 has landed, so `githubsucks/main` already contains this work and
the branch is retained only for provenance. Start 2B-3 from `main`.
the branch is retained only for provenance. 2B-3 was cut from `main`.
- **Stage 2B-1 MERGED as #184** (`main` @ `6bee09d`, 2026-07-28; all
twelve checks green on the reviewed head `5539b6e`; two review rounds
plus a gate-found follow-up). Branch
@ -322,11 +382,11 @@ activation, the GPU band, and the negotiated capability flip are all
`panel_capable` is still `false` for every semantic session, so the
journey grade is unchanged and every shipped v20 client remains
attachable.
- **2B-3 inherits a hard constraint from 2B-1's review**: it owns a
*compatibility-preserving* v21 activation mechanism and may **not**
simply change the unsolicited `Hello` to 21. The handshake is
server-first, so that one-line change locks out every shipped v20
frontend before it can even send an `AttachRequest`.
- **2B-3 discharged the hard constraint 2B-1's review set**: the
*compatibility-preserving* activation is the frontend counter-offer
described at the top of this lane, and the unsolicited `Hello` is
unchanged. 2B-1's real-daemon acceptance for the v20 rejection point
still passes untouched.
- **Two review rounds, and what each cost.** Round 1: `PanelFrame`
needed an explicit `buffer_id`, the transport ratchet had to drive the
real attach path rather than a detached codec assertion, and shared

View File

@ -1,6 +1,9 @@
# Agent handoff — cross-machine continuity
**Last updated: 2026-07-28, as bottom-panel Stage 2B-2 (#187) — the
**Last updated: 2026-07-29, as bottom-panel Stage 2B-3 — the GPU panel
band, compatible protocol-v21 activation, and the negotiated
`panel_capable` flip, completing Arc 7 Stage 2 — opens atop `e003b81`.
Beneath it, bottom-panel Stage 2B-2 (#187) — the
daemon panel projection and the epoch machine — lands on `6c9e765`,
which is the dired Stage 2 framing (#171) atop the resource-op delete
guard framing (#186). Those two are framing-only: both are approved
@ -77,14 +80,13 @@ commands, read `docs/active-work.md` immediately after this file.
#165, the GPU terminal input fix #166, Lean 4 Stage 2 #161, the dired
framing #164, COHERENCE.md #163, find-file #162, Lean 4 Stage 1 #160,
minimap blank-slab #159, bottom-panel Stage 1 #155).
**Protocol schema support is now `v6..=v21`, while the production
server-first `Hello` still advertises v20.** Those are two different
facts and #184 landed only the first: the v21 bottom-panel wire family
exists, is gated in both directions, and has no producer, no consumer,
and no capability behind it. Compatible v21 activation and the
production advertisement move belong to Stage 2B-3. The bullets below
describe the arcs in their own terms; this line is the head-of-`main`
anchor.
**Protocol schema support is `v6..=v21`, the production server-first
`Hello` advertises v20, and a current session nevertheless negotiates
v21.** All three are true at once, and Stage 2B-3 is what made them
compatible: the advertised version is a permanent **baseline** and the
session's real version is settled one message later by the frontend's
`AttachRequest` counter-offer. The bullets below describe the arcs in
their own terms; this line is the head-of-`main` anchor.
- **`COHERENCE.md` is now required reading and a required framing input
#163.** It carries the product-coherence thesis, an audited
scorecard, per-concern gaps, and §20's priority order, and it is the
@ -457,14 +459,17 @@ commands, read `docs/active-work.md` immediately after this file.
unchanged, which is the additivity gate for the `read_dir` change; M4
121; required GPU 155; isolated-`XDG_CONFIG_HOME` workspace sweep
3,205 across 93 suites. 15 claims bite-verified.
- Canonical `main` is protocol **v20** (`SUPPORTED=[6..=20]`; v16 =
- Canonical `main` is protocol **v21** (`SUPPORTED=[6..=21]`; v16 =
`ThemeFacts`, v17 = `FontFacts`, v18 = `StatuslineSegments`, v19 =
terminal frames/events, v20 = the GPU initial-target semantic
bootstrap family). Bottom-panel Stage 2B-1's in-review schema is v21
(`SUPPORTED=[6..=21]`), but its production daemon deliberately
advertises v20: the handshake is server-first, so advertising 21
would make shipped v20 GPU/TUI clients reject before
`AttachRequest`. Stage 2B-3 owns compatible production activation.
bootstrap family, v21 = the bottom-panel band family).
**`ADVERTISED_PROTOCOL_VERSION` stays 20, permanently, and that is the
activation mechanism rather than a hedge** — see the Stage 2B-3 bullet.
The rule for every future additive family: advertise the baseline,
negotiate up from the frontend's `AttachRequest`. Moving the advertised
version is reserved for a change that cannot be expressed additively at
all, because a server-first `Hello` reaches a shipped frontend before it
can identify itself.
- **Bottom panel Stage 1 (window placement + TUI side windows) LANDED —
#155** (`docs/bottom-panel-framing.md` rev 4; merge `e745068`; two review
rounds). **No protocol change (still v20).** Arc 7's substrate: pmacs now
@ -529,23 +534,21 @@ commands, read `docs/active-work.md` immediately after this file.
`bottom_panel_stage1_acceptance` 46; kill ring 30; compile 67; M4 121;
required GPU 152; initial-target 14 CRDT; all three vterm suites; folding
Stage 2 48. All 12 CI checks green at merge.
- **Stage 2 (the GPU panel band) is FRAMED; its first two slices have
LANDED and its third is open as PR #187** —
- **Stage 2 (the GPU panel band) is COMPLETE: 2A #177, 2B-1 #184,
2B-2 #187, and 2B-3 (this lane)** —
`docs/bottom-panel-stage2-framing.md` rev 6, four
framing review rounds, no open framing items; the rev-5
implementation split was explicitly approved 2026-07-27 and rev 6
records PR #184's server-first compatibility and gate correction.
It reserves protocol **v21** and ships as four serial
It reserved protocol **v21** and shipped as four serial
implementation slices: **2A** classified census routing +
per-window painter extraction (no wire change, #177), **2B-1** the
wire (#184), **2B-2** the daemon projection and epoch machine
(implemented but not landed in PR #187), then **2B-3** the GPU band,
compatible v21 activation, and the
negotiated `panel_capable` flip. Production attachment remains v20
through 2B-2. Parent acceptance 3755 remains authoritative.
Stage 3 is the adopter default flip. **Do not start 2B-3 until PR
#187 lands.** Its branch, checkpoints, two review rounds, verification,
and exact recovery commands live in `docs/active-work.md`.
(#187), then **2B-3** the GPU band, compatible v21 activation, and
the negotiated `panel_capable` flip. Production attachment stayed
v20 through 2B-2 and negotiates v21 from 2B-3 on. Parent acceptance
3755 remains authoritative. **Stage 3, the adopter default flip, is
the arc's remaining step.**
- **The §1.3 census is CLASSIFIED, not uniformly redirected.** Only the
Projection class (#1#12, #21#22) routes through
`primary_document_window`; focus/input (#13#15, #23), focus chrome
@ -567,9 +570,10 @@ commands, read `docs/active-work.md` immediately after this file.
version is therefore an incompatible act on its own, independent of
whether any new message is ever sent. A real-daemon acceptance
emulates that exact rejection point and then requires the
attachment to reach its initial grid. **2B-3 must ship a
compatibility-preserving activation mechanism; it may not simply
change the unsolicited `Hello` to 21.**
attachment to reach its initial grid. **Stage 2B-3 discharged this
without touching the unsolicited `Hello`** — the advertisement is a
permanent baseline and the frontend counter-offers; see its bullet
below. That acceptance still passes unchanged, which is the point.
- **One shared grid validator, split along a stated boundary**
(`pmacs-protocol/src/wire_grid.rs`). Shared: checked area, the
visible-cell bound (262,144), cell count, cursor bounds, glyph
@ -650,7 +654,91 @@ commands, read `docs/active-work.md` immediately after this file.
stays a per-frame `Absent`, flagged rather than hidden, because
making it durable needs a new "presentation permanently
unavailable" reason in `FrontendView` for a state requiring 2^64
presentations in one session.
presentations in one session. **Stage 2B-3 did not make it cheap**
the frontend half latches instead, which is a different remedy, so
the daemon-side hole stays open as recorded.
- **Bottom panel Stage 2B-3 (the GPU band, compatible v21 activation,
and the negotiated capability flip) — the arc's Stage 2 is COMPLETE.**
This is the slice a user can see: a semantic GPU session now renders a
real panel band instead of taking the Stage 1 non-side fallback, which
is what closes the journey-steps-710 divergence §6 of the Stage 2
framing names. Durable facts:
- **A server-first handshake is negotiated from the CLIENT side, not by
moving the advertisement.** `ADVERTISED_PROTOCOL_VERSION` is now a
permanent compatibility *baseline*; the frontend answers
`requested_protocol_version(baseline)` — its own `PROTOCOL_VERSION`
when the baseline is the current one, a verbatim echo of anything
older — and the daemon records
`negotiated_session_version(offer)`. A shipped baseline frontend
echoes and gets a baseline session, byte-for-byte as before; a
current frontend offers up and gets the current wire. The `Hello`
encoding and value never change, which is why the old frontend never
sees a version it must reject. **The daemon needed no change to
accept the offer** — it already recorded `req.protocol_version` as
the negotiated version, so the whole mechanism is one value the
frontend chooses plus a documented clamp.
- **The window this leaves open, named rather than hidden:** a daemon
whose own `PROTOCOL_VERSION` equals the baseline rejects an offer
above its supported range. Compatibility can be preserved for old
*frontends* or old *daemons* — a single `AttachRequest` cannot mean
both "I want 21" and "≤ 20" — and the frontend direction is the one
that matters, because the daemon is what a user leaves running. It
closes on the next daemon restart and surfaces as an explicit
`GoodbyeReason::VersionMismatch` naming both versions.
- **`server_protocol_version` split into two facts on the GPU client**:
`session_protocol_version` (what the session speaks — every wire gate
keys on this) and `baseline_protocol_version` (what `Hello` said).
They now DIFFER in the normal case, and that difference *is* the
compatibility property, so both headless probe reports emit both and
the two ratchets that read them assert both directions. Asserting
only the session version would pass if the baseline had been bumped
too, which is the exact incompatible change the mechanism avoids.
- **The GPU document bottom is three boundaries and each call site was
classified individually** — 20 production sites (8 status-owned, 12
document-owned), 1 definition, 8 test sites, 29 matches, arithmetic
stated. `geometry_capacity_bottom` reserves the divider *while the
panel is absent* (that asymmetry is what breaks the first-open cycle)
while `document_text_bottom` costs the document nothing until a
`Present` frame paints.
- **Moving a boundary is not always sufficient.** `edge_scroll_direction`
has no upper bound, so a pixel *inside* the band still read as
"further down the document" and armed the document's auto-scroll —
the exact named symptom, surviving a correct reclassification. Any
consumer that treats "past the bottom" as unbounded needs the band as
an explicit exclusion, not just a moved boundary.
- **Three of the first-pass assertions were VACUOUS, and the mutation
runs are what found them.** (a) The contrast assertion compared
`status_band_top` before and after — a *fixed point* — so the blanket
rewrite it exists to prevent moved both readings together and passed;
it is now anchored to an independent formula. (b) The criterion-46
pixel test passed with the band painting *nothing*, because
installing a panel reshapes the document and that produced the whole
diff; it now counts differing pixels in the divider and cell rows.
(c) The probe-versus-document-advance fixture compared two ASCII
documents, which in a monospace family have identical advances.
- **"No panel frame reaches a v20 session" is defence in depth, not the
placement gate.** The producer's peer flag and the write-loop filter
both suppress `PanelFrame` below the panel version independently of
`panel_capable`, so that assertion passes with the capability gate
removed entirely. The load-bearing claim is *placement*: the adopter's
buffer must land in the pre-panel session's own document window,
because a side window it cannot render is simply invisible.
- `PanelBand::presented()` is the ONE frontend-side derivation of "is a
band on screen" — retained valid frame, matching `geometry_epoch`,
latch clear — behind the band inset, the painter, the hit-tester, and
the drag. A latched frontend also stops absorbing payloads, so
`presented()` is not the only thing between a disowned declaration and
a painted band.
- Panel columns come from the **stable normal-face probe**, never
`mono_advance`'s document-glyph fallback, and `panel_cell_capacity`
carries the daemon's **virtual status row** (`frontend_area_rows` is
`total.rows - 1`) with **no per-axis cap**, because a panel may
legitimately be wider than a PTY.
- `PANEL_MIN_VERSION` moved into `pmacs-protocol` so the GPU frontend
aliases one definition instead of restating 21.
- **Stage 3 (the adopter default flip) is the arc's last step**, and it
is the only thing between today's state and omitting `display`
resolving to the panel policy.
- **dired Stage 2 framing LANDED (document only) — #171**
(`docs/dired-stage2-framing.md`, revision 9; seven review rounds).
**Approved as a framing; no runtime code and no implementation

View File

@ -4,10 +4,16 @@
//! The daemon panel projection and the epoch machine.
//!
//! Everything here runs through a **test-only** panel-capable semantic
//! view: production negotiation still sets `panel_capable = false` for
//! every semantic session, and the compatibility-preserving v21
//! activation is Stage 2B-3's. Nothing in this slice is user-reachable.
//! Everything here runs through an explicitly panel-capable semantic
//! view constructed by the fixture. When this suite was written that was
//! a *test-only* configuration — production negotiation set
//! `panel_capable = false` for every semantic session. **Stage 2B-3 made
//! it production-reachable**: a semantic session that negotiates
//! `PANEL_MIN_VERSION` is panel-capable, activated by the frontend's
//! `AttachRequest` counter-offer rather than by moving the advertised
//! `Hello` version. These assertions did not change, and that is the
//! point — the projection they pin is the one production now drives.
//! `bottom_panel_stage2b_gpu_acceptance` covers the negotiation itself.
//!
//! Two disciplines the framing is explicit about:
//!