diff --git a/docs/active-work.md b/docs/active-work.md index dbd2571..a0e5a9d 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -409,6 +409,34 @@ from #171 and #215. the canonical document false. - **Chain: this slice → `panel-pointer-replay` (rebases onto it) → GUI arc 1b.** +- **CI-red observations on this branch, NOT yet in the registry — and + deliberately so.** `docs/ci-red-signatures.md` here ends at **U9**; + the `panel-pointer-replay` branch already added a **U10** that has + not merged. Adding a row from this branch would either duplicate that + id or invent U11 against an unseen neighbour — and this file's own + history records exactly that going wrong once already, when two + branches' entries "merged **without a conflict**, producing duplicate + ids across four sites". **The rows below are owed to the registry by + whichever branch merges second**, numbered after the other's. + - **R7, two occurrences on this branch** (2026-08-15, `gpu` step, + logs `20260815T095532Z` and `20260815T100719Z`). Fragments verified + both times: `transient sequence must attach: Attach(Handshake(Io(Os + { code: 32, kind: BrokenPipe, message: "Broken pipe" })))` at + `pmacs-gpu/src/attach.rs:1728`. One machine, one day, one branch, + with a green full-gate run between them. Isolated reruns green. + - **`composition_overhead_under_ten_percent`, once** (log + `20260815T102527Z`, step **`04-lib-crdt`**). Fragment: + `composition machinery added more than 10% overhead: 1.146 + (single=227350 ns, dispatch=260497 ns)`. **`criterion_1` ran in the + same step and PASSED**, so per U6's own instruction this is one + selector redding without the other and is a different incident. + **1.146× is the smallest margin this budget has ever failed by** + (U6 1.297×, U10 1.343×, U9 1.613×), and the "realistic" figure was + **negative** (−25.8%) in the same run. Isolated rerun green. + - **Cost, stated plainly:** four `--protocol` gate runs on one commit, + three of them lost to these two signatures. U9's synthetic-load + control remains unrun and is the cheapest thing that would either + implicate load or clear it. - **Gates:** the four `bottom_panel_*` suites, the GUI 1a wire suite, `PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu`, and **`--protocol`**. diff --git a/pmacs-gpu/src/main.rs b/pmacs-gpu/src/main.rs index c42fd94..47d4664 100644 --- a/pmacs-gpu/src/main.rs +++ b/pmacs-gpu/src/main.rs @@ -6895,6 +6895,20 @@ impl State { return false; } match payload { + // §5b — the mapped family is REFUSED until its gate lands. + // + // Refusing is the correct default at every intermediate + // commit, not a placeholder: G8d requires a `<= v24` + // frontend to reject `PresentMapped` outright, and until + // this frontend can prove it negotiated `>= v25` it is, for + // gating purposes, exactly such a peer. Painting it first + // and gating later would mean shipping a window in which the + // band is hit-tested with no mapping identity at all. + // + // The retention is ATOMIC, as G8d and G10 require: nothing + // about the previous frame, its generation or the pointer + // state is touched on the way out. + PanelFramePayload::PresentMapped { .. } => false, PanelFramePayload::Absent => { // Authoritative removal, and always safe. Note this does // NOT clear the geometry declaration: the frontend's frame diff --git a/pmacs-protocol/src/lib.rs b/pmacs-protocol/src/lib.rs index 3a857b7..858359e 100644 --- a/pmacs-protocol/src/lib.rs +++ b/pmacs-protocol/src/lib.rs @@ -66,7 +66,7 @@ pub use message::{ 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, MinibufferRow, Modifiers, MouseButton, MouseEvent, MouseKind, - NegotiatedCapabilities, PROTOCOL_VERSION, PointerKind, ResourceBody, + NegotiatedCapabilities, PANEL_MAPPING_MIN_VERSION, PROTOCOL_VERSION, PointerKind, ResourceBody, SUPPORTED_PROTOCOL_VERSIONS, SelectionSnapshot, SessionBootstrapRequest, StatuslineSegment, StyleSegment, StyleSpan, TEXT_INPUT_MAX_BYTES, TEXT_INPUT_MIN_VERSION, ThemeFace, is_builtin_pair_char, is_modeline_face_name, is_supported_protocol_version, is_ui_face_name, diff --git a/pmacs-protocol/src/message.rs b/pmacs-protocol/src/message.rs index 75340d4..e7c3ea5 100644 --- a/pmacs-protocol/src/message.rs +++ b/pmacs-protocol/src/message.rs @@ -550,8 +550,75 @@ pub enum FrontendEvent { /// The committed text. Non-empty; see [`TEXT_INPUT_MAX_BYTES`]. text: String, }, + /// Bottom panel §5b (protocol v25): a panel-cell gesture that also + /// names the **inverse mapping** the frontend was looking at. + /// + /// **APPENDED LAST, after [`Self::TextInput`].** "Beside + /// `PanelPointer`" would be adjacent insertion, which shifts every + /// discriminant below it; postcard encodes variants positionally, so + /// only the end of the enum is safe. + /// + /// **Why the mapping and not the epochs.** A cell must be inverted + /// to a byte, and the existing ladder cannot tell whether the text + /// under that cell still means what it meant: `buffer_id` catches an + /// A→B replacement, `panel_epoch` catches close/reopen, + /// `geometry_epoch` catches a declaration race, and **nothing + /// catches a foreign edit** — which moves the mapping while every + /// epoch holds. `mapping_generation` closes exactly that. + /// + /// **Not a per-frame token.** A token moving with each frame would + /// invalidate a gesture on every repaint and break drags outright, + /// which is why [`crate::panel::PanelFrame::panel_epoch`] is stable + /// across ordinary frames. This identifies the *mapping*: it moves + /// with the viewport, folds, wrap and gutter geometry, buffer + /// content and terminal topology, and holds across focus, styling, + /// selection-only repaints and absorbed cursor motion. + /// + /// **Gated bilaterally on [`PANEL_MAPPING_MIN_VERSION`]**, and the + /// gate is a REFUSAL rather than a fallback: a `>= v25` session + /// sending the bare [`Self::PanelPointer`] is refused, and a + /// `<= v24` session sending this variant is refused too. Treating + /// either as "handled under the other family's semantics" would + /// leave the hole reachable by choosing a discriminant. + PanelPointerMapped { + /// Which frontend produced the gesture (untrusted, as above). + frontend_id: FrontendId, + /// Geometry declaration this gesture was hit-tested against. + geometry_epoch: u64, + /// Presentation identity this gesture addresses. + panel_epoch: u64, + /// Buffer the frontend believed the panel was displaying. + buffer_id: crate::BufferId, + /// Cell the pointer is over, within the declared panel grid. + coord: CellCoord, + /// Which gesture step this is. + kind: MouseKind, + /// Modifiers held during the gesture. + mods: Modifiers, + /// The inverse mapping the frontend was displaying. + /// + /// **Zero is invalid** and is refused like a mismatch: it is + /// what a default-constructed or half-initialised sender + /// produces, so accepting it would let a peer opt out of the + /// check by sending nothing. A live key starts at 1. + /// + /// Appended **last within the variant**, so this mirrors + /// [`Self::PanelPointer`]'s field order exactly and the two stay + /// diffable by eye. + mapping_generation: u64, + }, } +/// First protocol version carrying the **mapped panel family** — +/// [`FrontendEvent::PanelPointerMapped`] and +/// [`crate::panel::PanelFramePayload::PresentMapped`]. +/// +/// Read as a constant rather than compared against a literal, and +/// **not** derived from [`PROTOCOL_VERSION`]: expressing the boundary +/// against a moving ceiling would silently drag this feature forward +/// on the next bump. +pub const PANEL_MAPPING_MIN_VERSION: u32 = 25; + /// First protocol version carrying [`FrontendEvent::TextInput`]. /// /// A frontend older than this **retains its existing `Key` behaviour and @@ -617,7 +684,8 @@ impl FrontendEvent { | Self::FrontendCellGeometry { frontend_id, .. } | Self::PanelResizeRows { frontend_id, .. } | Self::PanelPointer { frontend_id, .. } - | Self::TextInput { frontend_id, .. } => *frontend_id, + | Self::TextInput { frontend_id, .. } + | Self::PanelPointerMapped { frontend_id, .. } => *frontend_id, } } } @@ -1920,7 +1988,18 @@ pub enum ResourceBody { /// negotiated version. What is unusual here is only that the extension /// is **inbound-only** — there is no outbound counterpart to withhold, /// so the receiver check is the whole of the daemon's half. -pub const PROTOCOL_VERSION: u32 = 24; +/// +/// Bottom panel §5b: bumped 24 → 25 for the mapped panel family, +/// [`FrontendEvent::PanelPointerMapped`] and +/// [`crate::panel::PanelFramePayload::PresentMapped`]. Both variants are +/// appended after their enum's v24 final variant. The family is gated in +/// both directions from the authenticated session's negotiated version: +/// v6–v24 sessions use only `PanelPointer`/`Present`, while v25 sessions +/// use only the mapped pair; choosing the other discriminant is refused, +/// not reinterpreted. [`crate::panel::PanelFramePayload::Absent`] remains +/// common because hiding a band carries no mapping. The advertised +/// baseline remains 20. +pub const PROTOCOL_VERSION: u32 = 25; /// Protocol version placed in the daemon's server-first [`Hello`]. /// @@ -2108,8 +2187,16 @@ pub fn negotiated_session_version(frontend_offer: u32) -> u32 { /// cannot rely on the producer withholding. [`ADVERTISED_PROTOCOL_VERSION`] does not move: a v23 /// frontend negotiates v23, never sends the variant, and keeps today's /// first-scalar behaviour. +/// +/// Bottom panel §5b: extended to `[6, ..., 25]` for +/// [`FrontendEvent::PanelPointerMapped`] and +/// [`crate::panel::PanelFramePayload::PresentMapped`]. The authenticated +/// negotiated version selects exactly one legacy/mapped family in each +/// direction; a peer compiled from this crate can encode either variant, +/// so receiver-side refusal is load-bearing. `Absent` is common, and +/// [`ADVERTISED_PROTOCOL_VERSION`] remains 20. pub const SUPPORTED_PROTOCOL_VERSIONS: &[u32] = &[ - 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, ]; /// T M10.5: predicate for the handshake check. Returns `true` if diff --git a/pmacs-protocol/src/panel.rs b/pmacs-protocol/src/panel.rs index 6e26fe2..f9f344a 100644 --- a/pmacs-protocol/src/panel.rs +++ b/pmacs-protocol/src/panel.rs @@ -103,6 +103,29 @@ pub enum PanelFramePayload { Present(PanelFrame), /// No panel is visible; clear any retained frame. Absent, + /// §5b (protocol v25): a visible panel **plus the identity of the + /// inverse mapping** it was painted from. + /// + /// **APPENDED AFTER `Absent`, not beside `Present`.** postcard + /// encodes variants positionally, so inserting adjacent to + /// `Present` would shift `Absent`'s discriminant and silently + /// re-interpret every older peer's "hide the band" as something + /// else. + /// + /// `Absent` is deliberately **shared by both families** — hiding a + /// band carries no mapping, so it needs no mapped twin. + PresentMapped { + /// The frame, reused whole and unchanged. + frame: PanelFrame, + /// Identity of the inverse mapping this frame was painted from. + /// + /// A `PanelPointerMapped` echoes it, and the daemon refuses a + /// gesture whose echo no longer matches. **Zero is invalid**; + /// a live key starts at 1 and is **nondecreasing**, including + /// across `Absent` — a delayed lower frame must not roll the + /// producer's authority backward. + mapping_generation: u64, + }, } /// Why a [`PanelFrame`] is not structurally valid. diff --git a/src/daemon.rs b/src/daemon.rs index c9d5973..3bba690 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -3607,6 +3607,13 @@ fn apply_semantic_input_event( let ct_mouse = mouse_to_crossterm(&pmacs_mouse); editor.dispatch_mouse(source, ct_mouse, term_size); } + // Everything else, including BOTH panel-pointer families. Panel + // gestures are dispatched from their own arm in + // `handle_dispatcher_event`, behind the epoch ladder; reaching + // them from here would route around it. §5b's mapped variant is + // dropped for the same reason, and specifically NOT unwrapped to + // its legacy meaning — that is the bypass the family gate exists + // to close. _ => {} } } @@ -3647,6 +3654,12 @@ fn apply_event( // client-supplied `frontend_id` this function would believe. FrontendEvent::Paste { .. } | FrontendEvent::TextInput { .. } + // §5b: listed here for the same reason as the two above — a + // mapped panel gesture must not reach a payload-trusting path. + // Its own dispatcher arm authenticates the source and checks + // the family gate; arriving here it is dropped, never unwrapped + // to the legacy family. + | FrontendEvent::PanelPointerMapped { .. } | FrontendEvent::FocusGained(_) | FrontendEvent::FocusLost(_) // T M11.1: the semantic-frontend viewport declaration. Its diff --git a/src/protocol.rs b/src/protocol.rs index 474473b..cf26cc6 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1683,7 +1683,7 @@ mod tests { // --- M5.5a handshake & postcard round-trips --- #[test] - fn protocol_version_is_twenty_four_for_text_input() { + fn protocol_version_is_twenty_five_for_the_mapped_panel_family() { // Pin the value: T M10.5 bumped 1→2 (v1.0 wire: CrdtOp / // PresenceUpdate). T M11.1 bumped 2→3 (v1.1 wire: the // SemanticFrame family + FrontendEvent::Viewport). T M11.6 @@ -1741,10 +1741,11 @@ mod tests { // minibuffer at all. `MinibufferPrompt` is therefore frozen and // pinned by literal bytes below. // - // v24 is `FrontendEvent::TextInput` (GUI arc Stage 1a) — an - // APPENDED variant, which is why the freeze above survives it + // v24 is `FrontendEvent::TextInput` (GUI arc Stage 1a) and v25 + // is the mapped panel family (bottom-panel §5b) — both APPENDED + // variants, which is why the freeze above survives them // untouched: nothing in `MinibufferPrompt`'s encoding moved. - assert_eq!(PROTOCOL_VERSION, 24); + assert_eq!(PROTOCOL_VERSION, 25); } #[test] @@ -1822,18 +1823,19 @@ mod tests { // v18 (`StatuslineSegments`), v19 (the vterm terminal family), // v20 (semantic initial-target bootstrap), v21 (the bottom // panel band), v22 (`LineWrapFacts`), v23 - // (`MinibufferPromptRows`), and v24 (`TextInput`, GUI arc Stage - // 1a) all interoperate. - for accepted in 6..=24 { + // (`MinibufferPromptRows`), v24 (`TextInput`, GUI arc Stage 1a) + // and v25 (`PanelPointerMapped` + `PresentMapped`, bottom-panel + // §5b) all interoperate. + for accepted in 6..=PROTOCOL_VERSION { assert!( is_supported_protocol_version(accepted), "v{accepted} must be accepted" ); } - for rejected in [0, 1, 2, 3, 4, 5, 25, u32::MAX] { + for rejected in [0, 1, 2, 3, 4, 5, PROTOCOL_VERSION + 1, u32::MAX] { assert!( !is_supported_protocol_version(rejected), - "v{rejected} must be rejected by a v24 binary" + "v{rejected} must be rejected by the current binary" ); } } @@ -2008,6 +2010,167 @@ mod tests { ); } + /// §5b G0a — the **previous-final `FrontendEvent`** is now + /// `TextInput`, and it gets its own exact pin. + /// + /// Pins ACCUMULATE rather than move. The `PanelPointer` pin above + /// still protects the v21–v23 population; this one protects v24's, + /// and appending `PanelPointerMapped` is exactly the edit that would + /// shift it if it were inserted rather than appended. + #[test] + fn text_input_encoding_is_unchanged_by_the_v25_build() { + let ev = FrontendEvent::TextInput { + frontend_id: FrontendId(2), + text: "hi".to_owned(), + }; + assert_eq!( + postcard::to_allocvec(&ev).expect("encode"), + [16, 2, 2, 104, 105], + "TextInput's v24 wire bytes changed — a variant was inserted \ + before it; append new FrontendEvent variants at the end" + ); + } + + /// §5b G0a — the **previous-final `PanelFramePayload`** is `Absent`, + /// pinned through its real nesting inside `InstanceMessage`. + /// + /// Nested deliberately: `PanelFramePayload` never travels alone, and + /// a pin on the bare enum would miss a shift in the message + /// discriminant that carries it. + #[test] + fn absent_panel_payload_encoding_is_unchanged_by_the_v25_build() { + let msg = InstanceMessage::PanelFrame(pmacs_protocol::panel::PanelFramePayload::Absent); + assert_eq!( + postcard::to_allocvec(&msg).expect("encode"), + [28, 1], + "Absent's wire bytes changed — a PanelFramePayload variant \ + was inserted before it, or InstanceMessage's discriminant \ + moved; append PresentMapped AFTER Absent" + ); + } + + /// §5b G0b — exact **encode and decode** for the mapped pointer. + /// + /// **Every adjacent same-typed field carries a different value**, so + /// a reordering is visible. `geometry_epoch`, `panel_epoch` and + /// `mapping_generation` are all `u64` and all distinct (3, 7, 11); + /// `coord.row`/`coord.col` likewise (5, 9). A round-trip alone would + /// stay green through any swap of those — it is self-consistent by + /// construction — which is why the exact bytes are asserted in both + /// directions. + #[test] + fn panel_pointer_mapped_encodes_and_decodes_exactly() { + const WIRE: [u8; 11] = [17, 2, 3, 7, 4, 5, 9, 0, 0, 0, 11]; + let ev = FrontendEvent::PanelPointerMapped { + frontend_id: FrontendId(2), + geometry_epoch: 3, + panel_epoch: 7, + buffer_id: pmacs_protocol::BufferId::from_raw(4), + coord: CellCoord { row: 5, col: 9 }, + kind: pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left), + mods: Modifiers::NONE, + mapping_generation: 11, + }; + + assert_eq!( + postcard::to_allocvec(&ev).expect("encode"), + WIRE, + "PanelPointerMapped's v25 wire bytes changed — it must be \ + APPENDED after TextInput, with mapping_generation last" + ); + let decoded: FrontendEvent = postcard::from_bytes(&WIRE).expect("decode"); + assert_eq!( + decoded, ev, + "the frozen historical bytes must decode back to the same \ + value — round-trip alone would only witness the current \ + encoder and decoder agreeing with each other" + ); + } + + /// §5b G0b — exact **encode and decode** for the mapped frame, + /// through its real nesting. + /// + /// Reordering named variant fields still compiles and changes + /// postcard's positional bytes, so the exact pin is load-bearing. + /// The frame's own `panel_epoch` (8) and `geometry_epoch` (12) are + /// adjacent `u64`s and are pinned distinct for the same reason as + /// above. + #[test] + fn present_mapped_encodes_and_decodes_exactly() { + const WIRE: [u8; 33] = [ + 28, 2, 6, 8, 12, 1, 2, 2, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 13, + ]; + let frame = pmacs_protocol::panel::PanelFrame { + buffer_id: pmacs_protocol::BufferId::from_raw(6), + panel_epoch: 8, + geometry_epoch: 12, + size: CellSize::new(1, 2), + cells: vec![Cell::default(); 2], + cursor: None, + focused: true, + }; + let msg = + InstanceMessage::PanelFrame(pmacs_protocol::panel::PanelFramePayload::PresentMapped { + frame, + mapping_generation: 13, + }); + + assert_eq!( + postcard::to_allocvec(&msg).expect("encode"), + WIRE, + "PresentMapped's v25 wire bytes changed — it must be APPENDED \ + after Absent, with mapping_generation last" + ); + let decoded: InstanceMessage = postcard::from_bytes(&WIRE).expect("decode"); + assert_eq!(decoded, msg, "the frozen bytes must decode back"); + } + + /// §5b G14a — the version constants move together, and the + /// advertised baseline does not move at all. + #[test] + fn the_mapping_slice_takes_v25_and_the_advertised_baseline_stays_pinned() { + assert_eq!(pmacs_protocol::PROTOCOL_VERSION, 25); + assert_eq!(pmacs_protocol::PANEL_MAPPING_MIN_VERSION, 25); + assert!( + pmacs_protocol::SUPPORTED_PROTOCOL_VERSIONS.contains(&pmacs_protocol::PROTOCOL_VERSION), + "a wire version the handshake does not support fails against \ + this daemon's own peers" + ); + assert_eq!( + pmacs_protocol::ADVERTISED_PROTOCOL_VERSION, + 20, + "the advertised baseline is PERMANENT — it is not bumped to \ + chase the wire version" + ); + } + + /// §5b G14b — the supported set's boundary, and the family boundary, + /// are both literal. + #[test] + fn the_supported_set_ends_at_the_current_wire_version() { + assert!(pmacs_protocol::is_supported_protocol_version(25)); + assert!( + !pmacs_protocol::is_supported_protocol_version(26), + "an unreleased version must not negotiate" + ); + // The family boundary is stated against a LITERAL, not against + // `PROTOCOL_VERSION`: expressing it arithmetically would drag + // this feature forward on the next bump, so v24 sessions would + // silently start being offered a family they never negotiated. + // + // Asserted as equality rather than `24 < MIN`, which clippy + // correctly calls a compile-time tautology — it holds for every + // value above 24 and so pins nothing. + assert_eq!(25, pmacs_protocol::PANEL_MAPPING_MIN_VERSION); + assert_ne!( + pmacs_protocol::PANEL_MAPPING_MIN_VERSION, + pmacs_protocol::TEXT_INPUT_MIN_VERSION, + "the mapped family must not share v24's gate — that would \ + admit it on sessions that negotiated only TextInput" + ); + } + #[test] fn terminal_family_round_trips_and_pins_its_discriminants() { let bid = pmacs_protocol::BufferId::from_raw(9); diff --git a/src/semantic_render.rs b/src/semantic_render.rs index 3359504..5e46628 100644 --- a/src/semantic_render.rs +++ b/src/semantic_render.rs @@ -592,7 +592,9 @@ impl SemanticRenderState { #[must_use] pub fn panel_declaration(&self) -> Option<&PanelFrame> { match self.last_panel_payload.as_ref()? { - PanelFramePayload::Present(frame) => Some(frame), + PanelFramePayload::Present(frame) | PanelFramePayload::PresentMapped { frame, .. } => { + Some(frame) + } PanelFramePayload::Absent => None, } } diff --git a/tests/bottom_panel_stage2b_gpu_acceptance.rs b/tests/bottom_panel_stage2b_gpu_acceptance.rs index 7da08ea..b24629c 100644 --- a/tests/bottom_panel_stage2b_gpu_acceptance.rs +++ b/tests/bottom_panel_stage2b_gpu_acceptance.rs @@ -337,10 +337,11 @@ fn one_daemon_serves_a_v21_panel_session_and_a_shipped_v20_client() { #[test] fn the_baseline_stays_and_the_counter_offer_activates() { // A deliberate tripwire: bumping the wire must be a conscious edit - // here, not a silent one. v24 is `TextInput` (GUI arc Stage 1a); - // v23 was `MinibufferPromptRows` (Discovery Stage 2); v22 was - // `LineWrapFacts` (long-lines Stage 3). - assert_eq!(PROTOCOL_VERSION, 24); + // here, not a silent one. v25 is the mapped panel family + // (`PanelPointerMapped` + `PresentMapped`, bottom-panel §5b); v24 is + // `TextInput` (GUI arc Stage 1a); v23 was `MinibufferPromptRows` + // (Discovery Stage 2); v22 was `LineWrapFacts` (long-lines Stage 3). + assert_eq!(PROTOCOL_VERSION, 25); assert_eq!( ADVERTISED_PROTOCOL_VERSION, 20, "moving this is the incompatible act the mechanism exists to avoid" diff --git a/tests/discovery_stage2_acceptance.rs b/tests/discovery_stage2_acceptance.rs index db5f8cc..36d50a0 100644 --- a/tests/discovery_stage2_acceptance.rs +++ b/tests/discovery_stage2_acceptance.rs @@ -67,10 +67,11 @@ use common::daemon::{TestDaemon, build_default_caps}; /// server-first, so moving it locks out every already-shipped frontend /// before it can counter-offer. An additive family never needs it. #[test] -fn the_wire_is_v24_and_the_advertised_baseline_is_unmoved() { +fn the_wire_is_v25_and_the_advertised_baseline_is_unmoved() { assert_eq!( - PROTOCOL_VERSION, 24, - "v24 is TextInput (GUI arc Stage 1a); v23 was MinibufferPromptRows" + PROTOCOL_VERSION, 25, + "v25 is the mapped panel family (bottom-panel §5b); v24 was \ + TextInput (GUI arc Stage 1a); v23 was MinibufferPromptRows" ); assert_eq!( ADVERTISED_PROTOCOL_VERSION, 20, @@ -618,7 +619,7 @@ fn one_daemon_serves_a_v23_rows_session_and_a_frozen_v22_session() { // rather than after the interesting half has already passed. let (mut legacy, _legacy_fid) = attach_semantic(&daemon, 22); let (mut current, current_fid) = attach_semantic(&daemon, PROTOCOL_VERSION); - assert_eq!(PROTOCOL_VERSION, 24); + assert_eq!(PROTOCOL_VERSION, 25); // Open the real `M-x` through the real key path, then narrow to the // probe command by typing it — the candidate window is ten rows out diff --git a/tests/statusline_segments_acceptance.rs b/tests/statusline_segments_acceptance.rs index c927956..448442f 100644 --- a/tests/statusline_segments_acceptance.rs +++ b/tests/statusline_segments_acceptance.rs @@ -804,8 +804,8 @@ fn a13_17_26_protocol_semantic_init_late_join_and_version_cost() { // three lines on purpose. The ceiling assertion is the load-bearing // one — it says the supported set ENDS here, which is what makes an // accidentally-widened set a failure rather than a silent pass. - assert_eq!(PROTOCOL_VERSION, 24); - for version in 6..=24 { + assert_eq!(PROTOCOL_VERSION, 25); + for version in 6..=PROTOCOL_VERSION { assert!(is_supported_protocol_version(version)); } assert!(!is_supported_protocol_version(PROTOCOL_VERSION + 1)); diff --git a/tests/vterm_stage3_acceptance.rs b/tests/vterm_stage3_acceptance.rs index 837d242..2163bfb 100644 --- a/tests/vterm_stage3_acceptance.rs +++ b/tests/vterm_stage3_acceptance.rs @@ -888,10 +888,11 @@ fn terminal_mode_keeps_reporting_presence_so_peers_drop_the_stale_caret() { panic!("timed out waiting for {what}"); } - // Tripwire: a wire bump must be a conscious edit here. v24 is - // `TextInput` (GUI arc Stage 1a); v23 was `MinibufferPromptRows` - // (Discovery Stage 2); v22 was `LineWrapFacts` (long-lines Stage 3). - assert_eq!(PROTOCOL_VERSION, 24); + // Tripwire: a wire bump must be a conscious edit here. v25 is the + // mapped panel family (bottom-panel §5b); v24 is `TextInput` (GUI + // arc Stage 1a); v23 was `MinibufferPromptRows` (Discovery Stage 2); + // v22 was `LineWrapFacts` (long-lines Stage 3). + assert_eq!(PROTOCOL_VERSION, 25); let daemon = common::daemon::TestDaemon::spawn_with_env_and_init( &[ ("PMACS_INSTANCE_SEMANTIC_RENDER", "1"),