Keep the v21 panel wire dark for v20 clients

Reserve the additive v21 panel schema without advertising it in the
server-first production handshake. Pin a real shipped-v20 client attach,
make the two aggregate-budget ratchets exactly one byte over, and update
the framing, coherence audit, handoff, and volatile lane record.
This commit is contained in:
Levi Neuwirth 2026-07-28 14:08:17 -04:00
parent f82d91ed3a
commit ab7c207904
16 changed files with 325 additions and 131 deletions

View File

@ -109,7 +109,7 @@ remain open to them.
| 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 shared primitive; bottom panel landed (#155) |
| 15 | Contextual affordances | **Weak** | Right-click menu only; code actions apply first-blindly; no git integration at all |
| 16 | Semantic frontend | **Strong** | v6..=v20 negotiated protocol; degradation practiced; TUI/GPU share the model |
| 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 |
| 18 | Onboarding | **Missing** | No welcome, no tutorial; `C-h` deletes a word; `M-x` is the only door in |
| 19 | Coherence acceptance tests | **Started** | `tests/journey_acceptance.rs` exists (steps 2, 3, 5); the other five scenarios are still unwritten |
@ -1340,9 +1340,13 @@ facto privileged implementation.
**Grade: strong — the healthiest concern in this document, and most of
its asks are already practiced.**
- Versioned, negotiated protocol `SUPPORTED=[6..=20]` with deliberate
- 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).
and byte-pin discipline for appended variants (handoff §4). The v21
bottom-panel family is reserved but dark in Stage 2B-1: 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.
- 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**:

View File

@ -305,13 +305,15 @@ 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-1 GATED; PR #184 OPEN FOR REVIEW
## Bottom-panel lane (Arc 7) — 2B-1 REVIEW FIX IN PROGRESS; PR #184 OPEN
Stage 1, the Stage 2 framing, and Stage 2A are on `main`. Framing
revision 5's three-way split of 2B was explicitly approved on
2026-07-27. **Stage 2B-1 is implemented, integrated with canonical
`main` @ `7fd646d`, and fully gated at `c8895a8`; it has no remaining
dependency. PR #184 is open and must not merge before user review.**
2026-07-27; revision 6 records PR #184's review correction. **Stage
2B-1 is implemented and integrated with canonical `main` @ `7fd646d`.
The previous head was fully gated at `c8895a8`, but review round 2 found
four issues and the corrected head must run the full gate again. PR
#184 is open and must not merge before user review.**
- **Stage 2B-1 branch:** `bottom-panel-stage2b`, based on
`githubsucks/main` @ `7fd646d` by merge because review had begun.
@ -320,14 +322,27 @@ dependency. PR #184 is open and must not merge before user review.**
and gate checkpoint is committed and pushed; nothing depends on a
worktree or `/tmp`. PR #184:
<https://github.com/levineuwirth/pmacs/pull/184>.
- **Ships only the v21 wire layer:** the four wire shapes, version bump,
shared cell-grid validator, and version-ladder move. It has no
producer, consumer, or capability change; `panel_capable` stays
`false`, so this slice changes no user-visible journey grade.
- **Ships only the reserved v21 wire layer:** the four wire shapes,
schema version, shared cell-grid validator, and accepted-version
ladder move. The production daemon continues advertising v20 because
its `Hello` is server-first; v21 activation belongs to 2B-3. This
slice has no producer, consumer, or capability change;
`panel_capable` stays `false`, so it changes no user-visible journey
grade and existing v20 clients remain attachable.
- **Review round 1 closed:** two P1s and one P2, all corrected at
`9b364ad`: `PanelFrame` now identifies its buffer, the transport
ratchet covers the actual attach path rather than a detached codec
assertion, and shared grid bounds have one validator.
- **Review round 2 found four issues; fixes are in progress:** the
server-first `Hello` made the advertised v20↔v21 compatibility
one-way; `COHERENCE.md` and `docs/agent-handoff.md` still named only
v20 schema support; framing §9 named a nonexistent aggregate 2B
suite instead of the three exact 2B slice suites; and the panel plus
copied terminal "one byte over" fixtures were actually two bytes
over. The correction keeps production advertisement at v20, adds a
real-daemon existing-v20-client acceptance, updates all three durable
records, names the exact slice suites, and asserts both rejecting
fixtures are exactly `limit + 1`.
- **The full gate found and corrected two 2B-1 omissions:** the
statusline version ladder still pinned v20/rejected v21, and Vterm
Stage 3 pinned v20 both structurally and in its real headless probe.
@ -350,7 +365,7 @@ dependency. PR #184 is open and must not merge before user review.**
`pmacs --gpu .` path, consumed the asynchronous dired snapshot, and
retained the managed daemon before the wait so failure cleanup remains
effective. The code integration auto-composed.
- **The complete post-integration gate is green at `c8895a8`:**
- **The previous complete post-integration gate was green at `c8895a8`:**
formatting; strict workspace Clippy; library **1,849 passed + 3
ignored default** and **2,034 passed + 4 ignored CRDT**; bottom-panel
Stage 1 / 2A / 2B-1 **46 / 17 / 15**; folding Stage 2 **48**; GPU font
@ -438,8 +453,9 @@ dependency. PR #184 is open and must not merge before user review.**
`docs/agent-handoff.md` §1; the two round lessons are in §5.
- Landed-docs follow-up merged as **#156** (`main` @ `d152120`,
2026-07-25).
- **Stage 2 framing: `docs/bottom-panel-stage2-framing.md` revision 5**
is commit `56301ed` on branch `githubsucks/bottom-panel-stage2b`,
- **Stage 2 framing: `docs/bottom-panel-stage2-framing.md` revision 6**
is on branch `githubsucks/bottom-panel-stage2b` (revision 5 is commit
`56301ed` there),
worktree `../pmacs-bp-stage2b`. Revisions 14 remain on
`githubsucks/bottom-panel-stage2-framing` (head `4fbd47f`, four
framing commits, revision 4 at `49757e5`). Round 1 closed 2 blocking +
@ -447,7 +463,9 @@ dependency. PR #184 is open and must not merge before user review.**
round 2 closed 1 blocking + 2 high + 1 medium and decided both open
items; round 3 closed 1 blocking + 1 high + 1 medium. No open items
remain. Revision 5 adds no decision; it records the approved
2B-1/2B-2/2B-3 implementation split. The
2B-1/2B-2/2B-3 implementation split. Revision 6 corrects the
server-first compatibility contract, durable protocol claims, exact
acceptance-suite names, and `limit + 1` fixture. The
parent framing `docs/bottom-panel-framing.md` (rev 4) remains
authoritative, **including its acceptance criteria 3755**.
- Retained, carrying nothing unmerged: branch `bottom-panel` and worktree
@ -456,12 +474,14 @@ dependency. PR #184 is open and must not merge before user review.**
before the next branches:
**2A** = classified §1.3 census routing + `paint_frame` per-window
painter extraction (with the active-window auto-scroll preparation), no
protocol change; **2B-1** = protocol **v21**
protocol change; **2B-1** = reserved protocol schema **v21**, with
production advertisement held at v20,
(`InstanceMessage::PanelFrame` plus
`FrontendEvent::{FrontendCellGeometry, PanelResizeRows, PanelPointer}`,
gated both directions, each extended enum byte-pinned on its own
previous final variant); **2B-2** = daemon panel projection and epoch
machine; **2B-3** = the GPU band and negotiated `panel_capable` flip.
machine; **2B-3** = compatible v21 activation, the GPU band, and the
negotiated `panel_capable` flip.
Stage 3 is the adopter default flip.
- **Correction — this entry previously mis-stated the census contract.**
It is **not** "route every consumer through `primary_document_window`".

View File

@ -1,7 +1,9 @@
# Agent handoff — cross-machine continuity
**Last updated: 2026-07-28, after the Journey/GPU directory-target
ratchet (#183), following Journey Stage 1a (#182), which made directory
**Last updated: 2026-07-28, during bottom-panel Stage 2B-1 PR #184
review; the canonical landed base remains the Journey/GPU
directory-target ratchet (#183), following Journey Stage 1a (#182),
which made directory
startup one coherent local/daemon/GPU path and incorporated the terminal
configuration + copy mode landed-doc work (#180); following terminal
copy mode (#178) — `C-c C-t`
@ -58,9 +60,11 @@ 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 unchanged
at **v20** — bottom-panel Stage 2A deliberately carries no wire change;
v21 arrives with Stage 2B. The bullets below describe the arcs in their
own terms; this line is the head-of-`main` anchor.
at **v20** — bottom-panel Stage 2A deliberately carries no wire change.
The in-review Stage 2B-1 reserves the v21 schema but keeps the
server-first production `Hello` at v20; compatible activation belongs
to Stage 2B-3. 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
@ -395,9 +399,14 @@ 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.
- Protocol **v20** (`SUPPORTED=[6..=20]`; v16 = `ThemeFacts`, v17 =
`FontFacts`, v18 = `StatuslineSegments`, v19 = terminal frames/events, v20 =
the GPU initial-target semantic bootstrap family).
- Canonical `main` is protocol **v20** (`SUPPORTED=[6..=20]`; 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.
- **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
@ -463,14 +472,18 @@ commands, read `docs/active-work.md` immediately after this file.
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**
`docs/bottom-panel-stage2-framing.md` rev 5, four review rounds, no
open items; the rev-5 implementation split was explicitly approved
2026-07-27. It takes protocol **v21** and ships as four serial
`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
implementation slices: **2A** classified census routing +
per-window painter extraction (no wire change), **2B-1** the wire,
**2B-2** the daemon projection and epoch machine, then **2B-3** the
GPU band and negotiated `panel_capable` flip. Parent acceptance
3755 remains authoritative. Stage 3 is the adopter default flip.
GPU band, compatible v21 activation, and negotiated
`panel_capable` flip. Production attachment remains v20 through
2B-1 and 2B-2. Parent acceptance 3755 remains authoritative.
Stage 3 is the adopter default flip.
- **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
@ -1201,12 +1214,17 @@ buffer owns a path's recovery slot; only recover/discard release
unclaimed crash data; adopt clears the old owner's skip cache.
**Protocol** — encoding-breaking bumps are deliberate and versioned. Canonical
`main` is `[6..=20]`. v15 = `CompletionPopup` + `StatusFacts.message`; v16 =
`main` is `[6..=20]`. The in-review bottom-panel 2B-1 schema extends support
to `[6..=21]`, while `ADVERTISED_PROTOCOL_VERSION` stays 20 until 2B-3
provides compatibility-preserving activation; the server-first `Hello`
cannot advertise 21 without stranding existing v20 clients before
`AttachRequest`. v15 = `CompletionPopup` + `StatusFacts.message`; v16 =
`ThemeFacts`; v17 = `FontFacts`; v18 = `StatuslineSegments`; v19 = the vterm
terminal family; v20 = semantic `SessionBootstrapRequest` plus appended
`InitialTargetResult`. New wire surface ⇒ bump + both-frontends support +
acceptance. An APPENDED variant must be guarded by a byte pin on the PREVIOUS
final variant — its own round-trip cannot detect a discriminant shift.
`InitialTargetResult`; v21 reserves the panel frame/event family. New wire
surface ⇒ bump + both-frontends support + acceptance. An APPENDED variant
must be guarded by a byte pin on the PREVIOUS final variant — its own
round-trip cannot detect a discriminant shift.
**Fake LSP** (`src/bin/pmacs_fake_lsp.rs`) modes: `fullonly`,
`rangeonly`, `rangeonly16` (UTF-16 + fail-closed bounds validation),

View File

@ -1,10 +1,13 @@
# Bottom panel Stage 2 — the GPU panel band (framing)
**Revision 5 — APPROVED 2026-07-27; 2A merged, 2B-1 in progress.
Ground truth: canonical `main` @ `c2d56ff`, protocol v20 on `main` and
v21 on `bottom-panel-stage2b`.** Revisions 14 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.
**Revision 6 — PR #184 review correction; the underlying Stage 2
framing remains APPROVED 2026-07-27. 2A is merged and 2B-1 is under
review. Ground truth: canonical `main` @ `7fd646d`, protocol v20 on
`main`; `bottom-panel-stage2b` reserves the v21 schema while its
server-first production handshake continues to advertise v20.**
Revisions 14 were pre-implementation; rev 5 recorded the three-way
slice of Stage 2B after its first slice was already built; rev 6
corrects that slice's mixed-version and gate contracts.
Stage 1 (#155, merge `e745068`) gave pmacs window placement, window
parameters, TUI side windows, the divider, and the adopter `display`
@ -29,7 +32,33 @@ geometries), Q#BP16 (pointer transport), Q#BP17 (fold projection), and
## 0. Revision history
### 0.0 Rev 4 → rev 5 — the three-way slice of 2B (not a review round)
### 0.0 Rev 5 → rev 6 — PR #184 review round 2, four findings closed
- **R6-1 (P1) — v21 is reserved, not advertised, in 2B-1.** The
protocol's handshake is server-first. An existing v20 TUI or GPU
frontend rejects a `Hello { protocol_version: 21 }` before it can
send an `AttachRequest`, so rev 5's claim that a v21 daemon and v20
peer "still negotiate 20" was impossible. 2B-1 therefore extends the
schema and accepted-version ladder to v21 while the production daemon
continues advertising v20. A real-daemon acceptance emulates the
shipped v20 rejection point and then requires the attachment to reach
its initial grid. **2B-3 owns both a compatibility-preserving
activation mechanism and the production move to v21; it may not
simply change the unsolicited `Hello` to 21.**
- **R6-2 (P2) — durable protocol claims move with the wire.**
`COHERENCE.md` and `docs/agent-handoff.md` now distinguish v21 schema
support from the still-v20 production handshake.
- **R6-3 (P2) — the gate contract names the actual decomposition.**
§9 now names 2B-1's
`bottom_panel_stage2b_protocol_acceptance` suite and the exact planned
daemon/GPU suite names for 2B-2 and 2B-3 instead of the nonexistent
`bottom_panel_stage2b_acceptance`.
- **R6-4 (P2) — "one byte over" means exactly one.** The panel and
copied terminal boundary fixtures replace a one-byte cluster with a
two-byte cluster, and each independently asserts a total of
`limit + 1`.
### 0.1 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
@ -68,18 +97,17 @@ criteria across them.
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.
`false` for every negotiated semantic session until 2B-3. Rev 5
incorrectly described that posture as a production v21 negotiation
that remained compatible with v20 clients; rev 6 R6-1 supersedes
that claim. The actual dark posture keeps the server-first
production handshake on v20 while the v21 schema is reserved. This
is the same posture 2A took ("seam adoption that becomes
load-bearing in 2B"), and 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
### 0.2 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
@ -107,7 +135,7 @@ criteria across them.
`cell.attachment.is_some()` rejection. It is now classified — and
**shared**, with the reasoning pinned.
### 0.2 Round 2 (rev 2 → rev 3) — 1 blocking, 2 high, 1 medium, all closed
### 0.3 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
@ -134,7 +162,7 @@ criteria across them.
- Both §8 open items are decided (§5.3): `BASE_DIVIDER_HEIGHT = 4.0` at
scale 1.0, and `TEXT_TOP` stays unscaled.
### 0.3 Round 1 (rev 1 → rev 2) — 2 blocking, 3 high, 3 revision points, all closed
### 0.4 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
@ -181,9 +209,10 @@ criteria across them.
| Byte pin `InstanceMessage::InitialTargetResult` | `pmacs-protocol/src/message.rs:1145` | Holds — still the enum's final variant |
| Byte pin `FrontendEvent::TerminalPointer` | final variant of its enum | Holds |
**Protocol is still v20** (`pmacs-protocol/src/message.rs:1568`); no
intervening PR bumped it. Q#BP9's conditional resolves: **Stage 2 is
v21**, no reservation was taken and none was needed.
**Protocol was still v20 at this re-scout**; no intervening PR had
bumped it. Q#BP9's conditional resolved: **Stage 2 reserves v21**.
Rev 6 R6-1 adds the server-first compatibility constraint discovered
during 2B-1 review.
Fifteen PRs merged between the parent's last re-scout (`47581f4`) and
this one: #149, #150, #152#155, #158#166. Nothing in the parent's
@ -375,13 +404,18 @@ undedicated (Q#BP2c). "It receives no new events" is insufficient — if
the daemon nevertheless places that frontend's window in a side panel
it cannot render, the window becomes invisible. The gate is on
placement, not only on transport. Parent acceptance 51 pins the mixed
session.
session. **The production daemon does not advertise v21 in 2B-1 or
2B-2.** Because `Hello` is server-first, 2B-3 must add or prove a
compatibility-preserving way to activate v21 before applying this rule;
merely advertising 21 would strand already-shipped v20 clients before
they can identify themselves.
## 4. Revisions to the parent framing
Only these; everything else stands.
- **Q#BP9 resolves to v21.**
- **Q#BP9 resolves to the v21 schema, with production advertisement
held at v20 until 2B-3 supplies compatible activation.**
- **Q#BP15a's epoch ownership is specified** by §3.1's table and API
split, replacing the parent's one-line "frontend-owned" statement.
- **Q#BP8's statusline criterion splits** per §3.3: one read reroutes,
@ -741,7 +775,10 @@ consumer, no capability change.
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.
version. **`ADVERTISED_PROTOCOL_VERSION` remains 20 in 2B-1 and
2B-2** because the unsolicited `Hello` precedes any client version
signal. A real daemon must remain attachable by a client whose
supported range ends at 20.
- **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
@ -790,9 +827,11 @@ production negotiation until 2B-3.
#### 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.
**Authority: `pmacs-gpu`, plus the compatibility-preserving negotiation
activation.** This is the only slice a user can observe, and the only
one that closes the journey divergence in §6. It must not advertise
v21 in the server-first `Hello` until an existing v20 client can still
attach.
- **46** (band + divider shrink the document text area by exactly their
pixel height; carets, hits, and scroll geometry respect the reduced
@ -838,7 +877,10 @@ divergence in §6.
its chrome.
- **A2B-5.** `panel_capable` is true only for a v21+ negotiated
authenticated semantic session; a v20 semantic session is never
**placed** in a side window, not merely denied the events.
**placed** in a side window, not merely denied the events. The same
acceptance must attach an actual v20 client to the production daemon
after v21 activation, so the new path cannot pass by breaking the old
handshake before placement is evaluated.
## 8. Open items
@ -866,17 +908,20 @@ stacked, and each is cut from `main`.
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`.
validator, and the version-ladder move. The v21 schema is reserved
while the production daemon continues advertising v20. **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
divider, pointer routing, the compatibility-preserving v21
activation, 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
@ -889,9 +934,16 @@ Gates for each slice: 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:
- `bottom_panel_stage1_acceptance` — the substrate both slices build on.
- `bottom_panel_stage2a_acceptance` / `bottom_panel_stage2b_acceptance`
— new, one per slice.
- `bottom_panel_stage1_acceptance` — the substrate all four Stage 2
slices build on.
- `bottom_panel_stage2a_acceptance` — Stage 2A's classified census and
painter extraction.
- `bottom_panel_stage2b_protocol_acceptance` — Stage 2B-1's v21 schema,
server-first v20 compatibility, byte pins, and shared validation.
- `bottom_panel_stage2b_daemon_acceptance` — the exact suite name
reserved for Stage 2B-2's projection and epoch machine.
- `bottom_panel_stage2b_gpu_acceptance` — the exact suite name reserved
for Stage 2B-3's band, compatible activation, and capability flip.
- `statusline_segments_acceptance` — the fan-out target change (§3.3).
- `m11_5_semantic_acceptance` — the semantic census (§3.2).
- `gpu_initial_target_acceptance` — parent criterion 55.

View File

@ -57,17 +57,18 @@ pub use cell::{
pub use crdt::CrdtOp;
pub use ids::{BufferId, ByteRange, FrontendId, Position};
pub use message::{
AdornmentContent, AdornmentPlacement, AttachRequest, BUILTIN_PAIR_CHARS, BlockAdornment,
CompletionPopupRow, CursorState, Decoration, DecorationKind, DecorationSegment,
FrontendCapabilities, FrontendEvent, GoodbyeReason, Hello, InitialTarget, InitialTargetResult,
InlineAdornment, InstanceCapabilities, InstanceIdentity, InstanceMessage, InstanceSignal, Key,
KeyEvent, LineNumberMode, MAX_INITIAL_TARGET_ERROR_BYTES, MAX_INITIAL_TARGET_PATH_BYTES,
MAX_STATUSLINE_FACE_BYTES, MAX_STATUSLINE_PROVIDER_NAME_BYTES, MAX_STATUSLINE_PROVIDERS,
MAX_STATUSLINE_SEGMENT_BYTES, MAX_STATUSLINE_TOTAL_TEXT_BYTES, MenuPromptRow, Modifiers,
MouseButton, MouseEvent, MouseKind, NegotiatedCapabilities, PROTOCOL_VERSION, PointerKind,
ResourceBody, SUPPORTED_PROTOCOL_VERSIONS, SelectionSnapshot, SessionBootstrapRequest,
StatuslineSegment, StyleSegment, StyleSpan, ThemeFace, is_builtin_pair_char,
is_modeline_face_name, is_supported_protocol_version, is_ui_face_name, negotiate_capabilities,
ADVERTISED_PROTOCOL_VERSION, AdornmentContent, AdornmentPlacement, AttachRequest,
BUILTIN_PAIR_CHARS, BlockAdornment, CompletionPopupRow, CursorState, Decoration,
DecorationKind, DecorationSegment, FrontendCapabilities, FrontendEvent, GoodbyeReason, Hello,
InitialTarget, InitialTargetResult, InlineAdornment, InstanceCapabilities, InstanceIdentity,
InstanceMessage, InstanceSignal, Key, KeyEvent, LineNumberMode, MAX_INITIAL_TARGET_ERROR_BYTES,
MAX_INITIAL_TARGET_PATH_BYTES, MAX_STATUSLINE_FACE_BYTES, MAX_STATUSLINE_PROVIDER_NAME_BYTES,
MAX_STATUSLINE_PROVIDERS, MAX_STATUSLINE_SEGMENT_BYTES, MAX_STATUSLINE_TOTAL_TEXT_BYTES,
MenuPromptRow, Modifiers, MouseButton, MouseEvent, MouseKind, NegotiatedCapabilities,
PROTOCOL_VERSION, PointerKind, ResourceBody, SUPPORTED_PROTOCOL_VERSIONS, SelectionSnapshot,
SessionBootstrapRequest, StatuslineSegment, StyleSegment, StyleSpan, ThemeFace,
is_builtin_pair_char, is_modeline_face_name, is_supported_protocol_version, is_ui_face_name,
negotiate_capabilities,
};
pub use panel::{MAX_PANEL_VISIBLE_CELLS, PanelFrame, PanelFrameError, PanelFramePayload};
pub use terminal::{

View File

@ -1665,6 +1665,16 @@ pub enum ResourceBody {
/// window invisible.
pub const PROTOCOL_VERSION: u32 = 21;
/// Protocol version placed in the daemon's server-first [`Hello`].
///
/// Bottom-panel Stage 2B-1 reserves the additive v21 wire family, but
/// production attachment remains on v20 until the Stage 2B-3 capability
/// activation can preserve compatibility with existing v20 frontends.
/// Those frontends reject an unknown server-first version before they can
/// send [`AttachRequest`], so advertising [`PROTOCOL_VERSION`] here would
/// make the otherwise-dark protocol slice user-visible.
pub const ADVERTISED_PROTOCOL_VERSION: u32 = 20;
/// T M10.5: the set of protocol versions a v1.0 binary accepts on
/// the wire. v0.1 binaries only accepted `[1]`; v1.0 binaries accept
/// `[1, 2]` so the version asymmetry the §sec:m10-backward-compat
@ -1742,9 +1752,12 @@ pub const PROTOCOL_VERSION: u32 = 21;
/// sessions send a bounded bootstrap envelope after `AttachRequest`; legacy
/// and non-semantic sessions retain their existing handshake shape.
///
/// Bottom panel Stage 2 (Q#BP9): extended to `[6, ..., 21]`. v21 peers
/// may exchange panel traffic; v20 peers interoperate with it simply
/// absent, and are never placed in a side window.
/// Bottom panel Stage 2 (Q#BP9): extended to `[6, ..., 21]`. Stage 2B-1
/// reserves and validates the v21 wire while production daemons continue
/// to send [`ADVERTISED_PROTOCOL_VERSION`] in their server-first
/// [`Hello`]. The later capability-activation slice owns moving production
/// negotiation to v21 without making existing v20 frontends reject the
/// handshake.
pub const SUPPORTED_PROTOCOL_VERSIONS: &[u32] =
&[6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
@ -2126,7 +2139,10 @@ pub fn negotiate_capabilities(
/// frontend will use as the `FrontendId` on every event it sends.
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Hello {
/// The instance's `PROTOCOL_VERSION`.
/// The protocol version this attachment should use.
///
/// This can deliberately trail [`PROTOCOL_VERSION`] while an additive
/// wire family is reserved but not yet activated in production.
pub protocol_version: u32,
/// `FrontendId` assigned to this attachment by the instance. The
/// frontend stamps this onto subsequent events. v0.1 daemons start

View File

@ -843,14 +843,14 @@ mod tests {
let mut over = exact.clone();
// One more byte of glyph, nothing else changed.
let last = over.cells.len() - 1;
over.cells[last] = cell_with(Glyph::Cluster(cluster_of_len(3).into_boxed_slice()));
over.cells[last] = cell_with(Glyph::Cluster(cluster_of_len(2).into_boxed_slice()));
(exact, over)
}
#[test]
fn maximum_legal_terminal_frame_encodes_below_the_transport_cap() {
let (exact, _) = budget_boundary_frames();
let (exact, over) = budget_boundary_frames();
assert_eq!(exact.validate(), Ok(()));
let mut glyph_bytes = 0usize;
@ -865,6 +865,20 @@ mod tests {
glyph_bytes, MAX_TERMINAL_FRAME_GLYPH_BYTES,
"the measured fixture must spend the whole aggregate budget"
);
let over_glyph_bytes = over
.cells
.iter()
.map(|cell| match &cell.glyph {
Glyph::Char(ch) => ch.len_utf8(),
Glyph::Cluster(bytes) => bytes.len(),
Glyph::Continuation => 0,
})
.sum::<usize>();
assert_eq!(
over_glyph_bytes,
MAX_TERMINAL_FRAME_GLYPH_BYTES + 1,
"the rejecting twin must be exactly one byte over the aggregate budget"
);
let msg = InstanceMessage::TerminalFrame(exact);
let bytes = postcard::to_allocvec(&msg).expect("encode");

View File

@ -67,9 +67,9 @@ use crate::lockfile::{self, LockError, LockHandle};
use crate::presence::{PresenceSnapshot, SessionRegistry};
use crate::protocol::crossterm_translate::{key_to_crossterm, mouse_to_crossterm};
use crate::protocol::{
AttachRequest, FrontendEvent, FrontendId, GoodbyeReason, Hello, InitialTarget,
InitialTargetResult, InstanceCapabilities, InstanceIdentity, InstanceMessage, InstanceSignal,
MAX_INITIAL_TARGET_ERROR_BYTES, MAX_INITIAL_TARGET_PATH_BYTES, PROTOCOL_VERSION, PointerKind,
ADVERTISED_PROTOCOL_VERSION, AttachRequest, FrontendEvent, FrontendId, GoodbyeReason, Hello,
InitialTarget, InitialTargetResult, InstanceCapabilities, InstanceIdentity, InstanceMessage,
InstanceSignal, MAX_INITIAL_TARGET_ERROR_BYTES, MAX_INITIAL_TARGET_PATH_BYTES, PointerKind,
SelectionSnapshot, SessionBootstrapRequest,
};
use crate::socket_path::{SocketPathError, ensure_runtime_subdir};
@ -712,7 +712,7 @@ fn per_attach_thread(
// mismatch path without changing the default.
let instance_caps_for_hello = instance_capabilities_with_env_override();
let hello = Hello {
protocol_version: PROTOCOL_VERSION,
protocol_version: ADVERTISED_PROTOCOL_VERSION,
assigned_frontend_id: frontend_id,
instance_identity: daemon_state.build_identity(),
instance_capabilities: instance_caps_for_hello.clone(),
@ -739,7 +739,7 @@ fn per_attach_thread(
let _ = write_message(
&mut stream,
&InstanceMessage::Goodbye(GoodbyeReason::VersionMismatch {
server: PROTOCOL_VERSION,
server: ADVERTISED_PROTOCOL_VERSION,
client: req.protocol_version,
}),
);
@ -3412,6 +3412,7 @@ fn apply_event(
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::PROTOCOL_VERSION;
#[test]
fn daemon_state_starts_frontend_id_at_two() {

View File

@ -5,17 +5,28 @@
//! projection, the epoch state machine, and the GPU band are later
//! slices of this stage and are not exercised here.
mod common;
use std::time::Duration;
use pmacs_protocol::cell::{Cell, CellCoord, CellSize, Color, Glyph, Style, UnderlineStyle};
use pmacs_protocol::message::{FrontendEvent, InstanceMessage, Modifiers, MouseButton, MouseKind};
use pmacs_protocol::message::{
AttachRequest, FrontendEvent, Hello, InstanceMessage, Modifiers, MouseButton, MouseKind,
};
use pmacs_protocol::panel::{
MAX_PANEL_VISIBLE_CELLS, PanelFrame, PanelFrameError, PanelFramePayload,
};
use pmacs_protocol::terminal::{
MAX_TERMINAL_COLS, TerminalFrame, TerminalFrameError, TerminalProcessState,
};
use pmacs_protocol::transport::MAX_FRAME_BYTES;
use pmacs_protocol::transport::{MAX_FRAME_BYTES, read_message, write_message};
use pmacs_protocol::wire_grid::{MAX_WIRE_GRID_GLYPH_BYTES, MAX_WIRE_GRID_GRAPHEME_BYTES};
use pmacs_protocol::{BufferId, FrontendId, PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS};
use pmacs_protocol::{
ADVERTISED_PROTOCOL_VERSION, BufferId, FrontendId, PROTOCOL_VERSION,
SUPPORTED_PROTOCOL_VERSIONS,
};
use common::daemon::{TestDaemon, build_default_caps};
fn cell(ch: char) -> Cell {
Cell {
@ -98,11 +109,54 @@ fn terminal_frame(rows: u32, cols: u32) -> TerminalFrame {
fn the_panel_stage_takes_protocol_v21() {
assert_eq!(PROTOCOL_VERSION, 21);
assert!(SUPPORTED_PROTOCOL_VERSIONS.contains(&21));
// v20 stays supported: a v20 peer interoperates with panel traffic
// simply absent rather than being refused the handshake.
// The wire family is reserved before it is activated: the production
// server-first Hello must remain acceptable to already-shipped v20
// clients throughout the dark protocol and daemon slices.
assert_eq!(ADVERTISED_PROTOCOL_VERSION, 20);
assert!(SUPPORTED_PROTOCOL_VERSIONS.contains(&20));
}
#[test]
fn a_new_daemon_keeps_an_existing_v20_client_attachable() {
let daemon = TestDaemon::spawn();
let mut stream = daemon.connect();
stream
.set_read_timeout(Some(Duration::from_secs(5)))
.expect("set handshake timeout");
// This is the rejection point in an already-shipped client: it reads the
// daemon's unsolicited Hello before it is able to identify its own
// supported range or send AttachRequest.
let hello: Hello = read_message(&mut stream).expect("read daemon Hello");
let v20_client_supported_versions = 6..=20;
assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION);
assert!(
v20_client_supported_versions.contains(&hello.protocol_version),
"an existing v20 client would reject the server-first Hello"
);
write_message(
&mut stream,
&AttachRequest {
protocol_version: hello.protocol_version,
frontend_capabilities: build_default_caps(),
initial_size: CellSize::new(24, 80),
},
)
.expect("write v20 AttachRequest");
assert!(
matches!(
read_message::<InstanceMessage>(&mut stream).expect("read initial grid"),
InstanceMessage::CellDelta {
full_grid: true,
..
}
),
"the daemon must establish the v20 session, not merely send an acceptable Hello"
);
}
#[test]
fn a_present_panel_frame_round_trips_with_both_epochs() {
let frame = panel_frame(2, 3);
@ -483,7 +537,7 @@ fn panel_budget_boundary_frames() -> (PanelFrame, PanelFrame) {
let mut over = exact.clone();
// One more byte of glyph, nothing else changed.
let last = over.cells.len() - 1;
over.cells[last] = maximal_cell(Glyph::Cluster(cluster_of_len(3).into_boxed_slice()));
over.cells[last] = maximal_cell(Glyph::Cluster(cluster_of_len(2).into_boxed_slice()));
(exact, over)
}
@ -508,6 +562,20 @@ fn maximum_legal_panel_frame_encodes_below_the_transport_cap() {
glyph_bytes, MAX_WIRE_GRID_GLYPH_BYTES,
"the measured fixture must spend the whole aggregate budget"
);
let over_glyph_bytes = over
.cells
.iter()
.map(|cell| match &cell.glyph {
Glyph::Char(ch) => ch.len_utf8(),
Glyph::Cluster(bytes) => bytes.len(),
Glyph::Continuation => 0,
})
.sum::<usize>();
assert_eq!(
over_glyph_bytes,
MAX_WIRE_GRID_GLYPH_BYTES + 1,
"the rejecting twin must be exactly one byte over the aggregate budget"
);
// One byte over is rejected, which is what makes `exact` maximal.
assert!(matches!(

View File

@ -26,7 +26,7 @@ use tempfile::TempDir;
#[cfg(feature = "crdt")]
use pmacs::cell::CellSize;
#[cfg(feature = "crdt")]
use pmacs::protocol::{AttachRequest, PROTOCOL_VERSION};
use pmacs::protocol::AttachRequest;
use pmacs::protocol::{FrontendCapabilities, Hello};
use pmacs::transport::read_message;
#[cfg(feature = "crdt")]
@ -294,7 +294,7 @@ pub fn attach_multi(daemon: &TestDaemon) -> (Hello, UnixStream) {
.unwrap();
let hello: Hello = read_message(&mut stream).expect("read Hello");
let req = AttachRequest {
protocol_version: PROTOCOL_VERSION,
protocol_version: hello.protocol_version,
frontend_capabilities: multi_frontend_caps(),
initial_size: CellSize::new(24, 80),
};

View File

@ -93,9 +93,9 @@ mod crdt {
use pmacs::cell::CellSize;
use pmacs::crdt::CrdtState;
use pmacs::protocol::{
AttachRequest, FrontendCapabilities, FrontendEvent, FrontendId, Hello, InitialTarget,
InitialTargetResult, InstanceCapabilities, InstanceIdentity, InstanceMessage,
PROTOCOL_VERSION, SessionBootstrapRequest,
ADVERTISED_PROTOCOL_VERSION, AttachRequest, FrontendCapabilities, FrontendEvent,
FrontendId, Hello, InitialTarget, InitialTargetResult, InstanceCapabilities,
InstanceIdentity, InstanceMessage, PROTOCOL_VERSION, SessionBootstrapRequest,
};
use pmacs::transport::{read_message, write_message};
@ -244,11 +244,11 @@ mod crdt {
.set_read_timeout(Some(Duration::from_secs(5)))
.expect("set target frontend timeout");
let hello: Hello = read_message(&mut stream).expect("target frontend Hello");
assert_eq!(hello.protocol_version, PROTOCOL_VERSION);
assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION);
write_message(
&mut stream,
&AttachRequest {
protocol_version: PROTOCOL_VERSION,
protocol_version: hello.protocol_version,
frontend_capabilities: FrontendCapabilities {
multi_frontend: true,
crdt_replica: true,
@ -583,7 +583,7 @@ mod crdt {
facts
.get("server_protocol_version")
.and_then(|value| value.parse::<u32>().ok()),
Some(PROTOCOL_VERSION)
Some(ADVERTISED_PROTOCOL_VERSION)
);
assert_eq!(
facts.get("spawned_daemon").map(String::as_str),

View File

@ -37,8 +37,8 @@ use pmacs::cell::Color;
#[cfg(feature = "crdt")]
use pmacs::overlay_color::color_for_slot;
use pmacs::protocol::{
AttachRequest, FrontendCapabilities, FrontendEvent, GoodbyeReason, Hello, InstanceMessage, Key,
KeyEvent, Modifiers, PROTOCOL_VERSION,
ADVERTISED_PROTOCOL_VERSION, AttachRequest, FrontendCapabilities, FrontendEvent, GoodbyeReason,
Hello, InstanceMessage, Key, KeyEvent, Modifiers, PROTOCOL_VERSION,
};
use pmacs::transport::{read_message, write_message};
@ -52,9 +52,9 @@ use common::daemon::{
/// Read the daemon's `Hello`, send our `AttachRequest`, return the Hello.
fn do_handshake(stream: &mut UnixStream) -> Hello {
let hello: Hello = read_message(stream).expect("read Hello");
assert_eq!(hello.protocol_version, PROTOCOL_VERSION);
assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION);
let req = AttachRequest {
protocol_version: PROTOCOL_VERSION,
protocol_version: hello.protocol_version,
frontend_capabilities: build_default_caps(),
initial_size: CellSize::new(24, 80),
};
@ -418,7 +418,7 @@ fn version_mismatch_clean_disconnect() {
// Read Hello.
let hello: Hello = read_message(&mut stream).expect("Hello");
assert_eq!(hello.protocol_version, PROTOCOL_VERSION);
assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION);
// Send AttachRequest with wrong protocol version.
let req = AttachRequest {
@ -431,7 +431,7 @@ fn version_mismatch_clean_disconnect() {
// Expect Goodbye(VersionMismatch).
match read_message::<InstanceMessage>(&mut stream) {
Ok(InstanceMessage::Goodbye(GoodbyeReason::VersionMismatch { server, client })) => {
assert_eq!(server, PROTOCOL_VERSION);
assert_eq!(server, ADVERTISED_PROTOCOL_VERSION);
assert_eq!(client, 999);
}
other => panic!("expected VersionMismatch Goodbye, got {other:?}"),
@ -1145,9 +1145,9 @@ fn m10_10_production_attach_negotiates_crdt_replica() {
// Production handshake — NOT the test `attach_multi()` path.
let hello: Hello = read_message(&mut stream).expect("read Hello");
assert_eq!(hello.protocol_version, PROTOCOL_VERSION);
assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION);
let req = AttachRequest {
protocol_version: PROTOCOL_VERSION,
protocol_version: hello.protocol_version,
frontend_capabilities: pmacs::attach::build_capabilities(),
initial_size: CellSize::new(24, 80),
};
@ -1183,9 +1183,9 @@ fn m10_10_production_attach_non_crdt_build_does_not_negotiate_crdt_replica() {
stream
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
let _hello: Hello = read_message(&mut stream).expect("read Hello");
let hello: Hello = read_message(&mut stream).expect("read Hello");
let req = AttachRequest {
protocol_version: PROTOCOL_VERSION,
protocol_version: hello.protocol_version,
frontend_capabilities: pmacs::attach::build_capabilities(),
initial_size: CellSize::new(24, 80),
};
@ -2171,7 +2171,7 @@ fn m10_10_f14_production_path_keystroke_flows_to_broadcast() {
.unwrap();
let hello_a: Hello = read_message(&mut stream_a).expect("A Hello");
let req_a = AttachRequest {
protocol_version: PROTOCOL_VERSION,
protocol_version: hello_a.protocol_version,
frontend_capabilities: pmacs::attach::build_capabilities(),
initial_size: CellSize::new(24, 80),
};

View File

@ -77,7 +77,7 @@ use nix::unistd::Pid;
use tempfile::TempDir;
use pmacs::attach::PMACS_TEST_SSH_BIN;
use pmacs::protocol::{Hello, PROTOCOL_VERSION};
use pmacs::protocol::{ADVERTISED_PROTOCOL_VERSION, Hello};
use pmacs::transport::read_message;
// ---------------------------------------------------------------------------
@ -385,7 +385,7 @@ fn daemon_attach_bridges_hello_from_existing_daemon() {
// verbatim. (No AttachRequest sent — the daemon will hold the
// attach slot until the bridge stdin closes below.)
let hello: Hello = read_message(&mut bridge_stdout).expect("read Hello via bridge");
assert_eq!(hello.protocol_version, PROTOCOL_VERSION);
assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION);
// Tear down: drop bridge stdin → bridge's stdin→socket copy sees
// EOF, shuts down the socket write half, the daemon notices and
@ -424,7 +424,7 @@ fn daemon_attach_auto_starts_missing_daemon() {
// bound the socket and the bridge connected.
let hello: Hello =
read_message(&mut bridge_stdout).expect("read Hello via auto-started daemon");
assert_eq!(hello.protocol_version, PROTOCOL_VERSION);
assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION);
// The lockfile must exist now: `acquire_lock` writes it on
// daemon startup. (Existence of the lockfile is what proves

View File

@ -72,8 +72,8 @@ use tempfile::TempDir;
use pmacs::cell::CellSize;
use pmacs::protocol::{
AttachRequest, FrontendCapabilities, FrontendEvent, Hello, InstanceMessage, Key, KeyEvent,
Modifiers, PROTOCOL_VERSION,
ADVERTISED_PROTOCOL_VERSION, AttachRequest, FrontendCapabilities, FrontendEvent, Hello,
InstanceMessage, Key, KeyEvent, Modifiers,
};
use pmacs::transport::{TransportError, read_message, write_message};
@ -158,9 +158,9 @@ fn build_default_caps() -> FrontendCapabilities {
fn do_handshake(stream: &mut UnixStream) -> Hello {
let hello: Hello = read_message(stream).expect("read Hello");
assert_eq!(hello.protocol_version, PROTOCOL_VERSION);
assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION);
let req = AttachRequest {
protocol_version: PROTOCOL_VERSION,
protocol_version: hello.protocol_version,
frontend_capabilities: build_default_caps(),
initial_size: CellSize::new(24, 80),
};

View File

@ -11,8 +11,8 @@ use std::time::{Duration, Instant};
use pmacs::cell::{Cell, CellSize, Glyph};
use pmacs::protocol::{
AttachRequest, FrontendEvent, FrontendId, Hello, InstanceMessage, Key, KeyEvent, Modifiers,
PROTOCOL_VERSION,
ADVERTISED_PROTOCOL_VERSION, AttachRequest, FrontendEvent, FrontendId, Hello, InstanceMessage,
Key, KeyEvent, Modifiers,
};
use pmacs::transport::{read_message, write_message};
@ -75,11 +75,11 @@ fn attach(daemon: &TestDaemon) -> (Client, Grid) {
.set_read_timeout(Some(Duration::from_secs(5)))
.expect("set daemon handshake timeout");
let hello: Hello = read_message(&mut stream).expect("read daemon Hello");
assert_eq!(hello.protocol_version, PROTOCOL_VERSION);
assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION);
write_message(
&mut stream,
&AttachRequest {
protocol_version: PROTOCOL_VERSION,
protocol_version: hello.protocol_version,
frontend_capabilities: build_default_caps(),
initial_size: CellSize::new(ROWS, COLS),
},

View File

@ -716,8 +716,8 @@ fn a37_real_daemon_real_pty_and_headless_gpu_render_one_terminal_session() {
assert_eq!(
facts.get("server_protocol_version").copied(),
Some("21"),
"the real daemon negotiated v21 with the real client: {text}"
Some("20"),
"the dark v21 wire slice must keep the real client on v20: {text}"
);
assert_eq!(
facts.get("entered_terminal_mode").copied(),