fix(gpu): positive family gates, a probe that reads accepted state, and a third split doc
**`Unsupported` WAS ACCEPTING LEGACY FRAMES.** I gated the legacy arm on `!= Mapped`, and `Unsupported` is neither --- so a session below `PANEL_MIN_VERSION` accepted a band it never negotiated. The `carries_panel()` check I had in mind guards `next_geometry_declaration`, a different seam entirely. Both present arms gate on their POSITIVE family now, which is the shape that cannot grow this hole again when a fourth family appears. **AND THE PROBE MEASURED THE PAYLOAD, NOT THE BAND.** It recorded panel facts before `apply_attach_message` ruled on the message, so once one valid frame had landed, a REJECTED frame --- wrong family, invalid, stale generation --- still supplied the expected text while the retained old frame supplied the rendering. The probe would report the band showing something it does not show, which is a false positive in the one place that exists to tell us the band is real. (The false NEGATIVE, observing only the legacy family, was the previous commit.) Facts now come from `state.panel.presented()` after the apply, and `panel_frames` counts only when the retained frame's identity actually moved: a duplicate or a refusal leaves the band exactly as it was, and counting either would say the daemon is painting when it is not. **Third rustdoc split in this slice**, same mechanism each time --- `screen_size`, `peer_may_send_panel_events`, now `send_panel_pointer`. I insert a function at what reads as a gap between declarations, when the lines above it are the NEXT function's documentation. The check is to look UP from the insertion point, not just down, and I will apply it rather than keep reporting the same correction. Verified by exit status: clippy 0, `-p pmacs-gpu` 0 (271 passed). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
parent
7e28eb8936
commit
4ecf09bf64
|
|
@ -1098,14 +1098,6 @@ impl AttachClient {
|
|||
})
|
||||
}
|
||||
|
||||
/// Send a `FrontendEvent::PanelPointer` (Q#BP16): a gesture
|
||||
/// hit-tested locally to a panel CELL. Callers gate on
|
||||
/// [`Self::session_protocol_version`] `>= 21`.
|
||||
///
|
||||
/// `buffer_id` and `panel_epoch` close different holes and neither
|
||||
/// subsumes the other — the first catches an A→B buffer replacement,
|
||||
/// the second a close/hide/reopen of the *same* buffer — so both are
|
||||
/// carried rather than one being derived from the other.
|
||||
/// §5b — the MAPPED gesture, echoing the generation of the frame
|
||||
/// this frontend is displaying.
|
||||
///
|
||||
|
|
@ -1136,6 +1128,14 @@ impl AttachClient {
|
|||
})
|
||||
}
|
||||
|
||||
/// Send a `FrontendEvent::PanelPointer` (Q#BP16): a gesture
|
||||
/// hit-tested locally to a panel CELL. Callers gate on
|
||||
/// [`Self::session_protocol_version`] `>= 21`.
|
||||
///
|
||||
/// `buffer_id` and `panel_epoch` close different holes and neither
|
||||
/// subsumes the other — the first catches an A→B buffer replacement,
|
||||
/// the second a close/hide/reopen of the *same* buffer — so both are
|
||||
/// carried rather than one being derived from the other.
|
||||
pub fn send_panel_pointer(
|
||||
&self,
|
||||
geometry_epoch: u64,
|
||||
|
|
|
|||
|
|
@ -849,33 +849,50 @@ fn run_headless_probe(socket: &Path, report: &Path) -> i32 {
|
|||
facts.last_frame_text = frame_probe_text(frame);
|
||||
facts.last_title.clone_from(&frame.title);
|
||||
}
|
||||
match msg.as_ref() {
|
||||
// §5b — BOTH families. Observing only the legacy
|
||||
// one would let mapped production work perfectly
|
||||
// while the live probe reported no panel at all,
|
||||
// which is a false negative in the one place that
|
||||
// exists to tell us the band is real.
|
||||
InstanceMessage::PanelFrame(
|
||||
pmacs_protocol::panel::PanelFramePayload::Present(frame)
|
||||
| pmacs_protocol::panel::PanelFramePayload::PresentMapped { frame, .. },
|
||||
) => {
|
||||
facts.panel_frames += 1;
|
||||
facts.panel_rows = frame.size.rows;
|
||||
facts.panel_cols = frame.size.cols;
|
||||
facts.panel_focused = frame.focused;
|
||||
facts.panel_frame_text = grid_probe_text(&frame.cells);
|
||||
if let Some(expected) = expected_panel_text.as_deref()
|
||||
&& facts.panel_frame_text.contains(expected)
|
||||
{
|
||||
facts.panel_text_observed = true;
|
||||
}
|
||||
}
|
||||
InstanceMessage::PanelFrame(
|
||||
pmacs_protocol::panel::PanelFramePayload::Absent,
|
||||
) => facts.panel_absent_observed = true,
|
||||
_ => {}
|
||||
}
|
||||
// §5b — a panel message is only NOTED here; its facts
|
||||
// are read from the retained state AFTER
|
||||
// `apply_attach_message` has ruled on it.
|
||||
//
|
||||
// Reading the raw payload was a false-positive
|
||||
// generator: once one valid frame had landed, a
|
||||
// REJECTED frame — wrong family, invalid, stale
|
||||
// generation — still supplied the expected text while
|
||||
// the retained old frame supplied the rendering. The
|
||||
// probe would report the band showing something it does
|
||||
// not show.
|
||||
let panel_message = matches!(msg.as_ref(), InstanceMessage::PanelFrame(_));
|
||||
let panel_before = state
|
||||
.panel
|
||||
.presented()
|
||||
.map(|frame| (frame.panel_epoch, frame.geometry_epoch, frame.size));
|
||||
|
||||
state.apply_attach_message(*msg);
|
||||
|
||||
if panel_message {
|
||||
match state.panel.presented() {
|
||||
Some(frame) => {
|
||||
let identity = (frame.panel_epoch, frame.geometry_epoch, frame.size);
|
||||
// Counted only when the retained frame
|
||||
// actually moved: a duplicate or a refusal
|
||||
// leaves the band exactly as it was, and
|
||||
// counting either would say the daemon is
|
||||
// painting when it is not.
|
||||
if panel_before != Some(identity) {
|
||||
facts.panel_frames += 1;
|
||||
}
|
||||
facts.panel_rows = frame.size.rows;
|
||||
facts.panel_cols = frame.size.cols;
|
||||
facts.panel_focused = frame.focused;
|
||||
facts.panel_frame_text = grid_probe_text(&frame.cells);
|
||||
if let Some(expected) = expected_panel_text.as_deref()
|
||||
&& facts.panel_frame_text.contains(expected)
|
||||
{
|
||||
facts.panel_text_observed = true;
|
||||
}
|
||||
}
|
||||
None => facts.panel_absent_observed = true,
|
||||
}
|
||||
}
|
||||
if is_snapshot {
|
||||
// The dual declaration: a byte viewport for a
|
||||
// document, a cell size for a terminal. The daemon
|
||||
|
|
@ -7015,7 +7032,12 @@ impl State {
|
|||
// frame with no mapping identity is one whose cells cannot
|
||||
// be inverted safely, so accepting it would reintroduce the
|
||||
// hole from the receiving side.
|
||||
PanelFramePayload::Present(_) if self.panel_family == PanelFamily::Mapped => false,
|
||||
// Gated on the POSITIVE family, not on "not mapped".
|
||||
// `Unsupported` is neither, and a `!= Mapped` test let it
|
||||
// through — a session below `PANEL_MIN_VERSION` accepting a
|
||||
// band it never negotiated. `carries_panel()` guards the
|
||||
// geometry declaration, not this seam.
|
||||
PanelFramePayload::Present(_) if self.panel_family != PanelFamily::Legacy => false,
|
||||
PanelFramePayload::PresentMapped {
|
||||
frame,
|
||||
mapping_generation,
|
||||
|
|
|
|||
Loading…
Reference in New Issue