test(panel): the authority-loss matrix, asserted as EFFECTS across every quadrant

Answers review of e2b080f. The transition detection was sound; the rows
proving it were not.

G5b(a)-(c) and G5m stopped at has_pending_release() or a cancellation
count. Both pass while delivery or recorded-domain routing is broken,
and the mutation that parks a release and never delivers it proves it:
it now fails all three rows and previously failed none. Every quadrant
drains explicitly and asserts the effect --- exact release bytes for a
reporting terminal, the cleared empty selection for a document, and an
empty slot afterwards.

The matrix is now the table the framing asks for: four transitions x two
families x two targets, sixteen quadrants. All four earlier rows used
the legacy terminal fixture, so the mapped legs and every document leg
were simply absent.

G5m takes both composites the framing names --- changed-size geometry,
which moves the epoch AND the mapping generation, and a buffer
replacement that also moves the mapping --- rather than the one
wrong-shaped combination it had.

One quadrant asserts less and says so: for window replacement on a
document the window the gesture belonged to is gone, so the completion
has nothing left to clear and the ending is the whole effect. Written
into the row, because a silently absent assertion is how a quadrant
stops testing anything.

Two fixture facts the failures taught: the document legs press at row 0
because foreign_edit replaces buffer contents without refreshing the
window's cached line index, so only the first display row resolves to a
byte and a press that anchors nothing does not arm; and cargo check
--lib does not compile #[cfg(test)] code, which is why a missing test
helper passed a check and failed the build.

Corrects two records. The ledger had the mutation labels reversed ---
dropping the BUFFER comparison misses the buffer transition, not the
window one. And semantic_render still told readers these transitions
were left armed on this branch, which was true of SS5b and false here
since the matrix landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
Levi Neuwirth 2026-08-21 11:43:27 +02:00
parent e2b080f6ae
commit c37c066786
No known key found for this signature in database
3 changed files with 327 additions and 264 deletions

View File

@ -366,19 +366,33 @@ from #171 and #215 — the correction the 1b lane missed, honoured here.
needed a retained `geometry_epoch` because nothing else the needed a retained `geometry_epoch` because nothing else the
producer holds moves with it); **detach** cancels in the dispatcher producer holds moves with it); **detach** cancels in the dispatcher
before any teardown. before any teardown.
- **Witnessed: G5b(a)–(d), G5m, G5j**, each reading the CHILD'S - **Witnessed by a TABLE-DRIVEN matrix**: four transitions × two
STREAM or the document's selection rather than the latch. The families × two targets, sixteen quadrants, each **draining
mutations discriminate exactly: dropping the window half of the explicitly** and asserting the effect — the exact release bytes
identity check fails only the buffer leg, dropping the buffer for a reporting terminal, the cleared empty selection for a
half fails only the window leg, dropping the geometry check fails document, and an empty slot afterwards. An earlier version
only that leg, and dropping detach's cancel fails only detach. stopped at `has_pending_release()` and would have passed while
- **G5m survives all four**, correctly: two coincident causes take delivery was broken; the mutation that parks a release and never
the same latch, so one release, and removing either cause still delivers it now fails every row.
leaves one. - **Mutation labels, stated correctly this time**: dropping the
BUFFER comparison misses the buffer transition; dropping the
WINDOW comparison misses the window transition; dropping the
geometry comparison misses that one; dropping detach's cancel
misses detach. Each fails the matrix.
- **G5m takes both composites the framing names** — changed-size
geometry (epoch **and** mapping generation) and a buffer
replacement that also moves the mapping — and reads the child's
stream rather than a cancellation count, because a count of one
proves the latch was taken once, not that one release went out.
- **G5j has two legs and they differ**: an empty selection is - **G5j has two legs and they differ**: an empty selection is
cleared without moving point, a REAL dragged region survives cleared without moving point, a REAL dragged region survives
anchor-and-cursor exact. Clearing every selection fails the anchor-and-cursor exact. Clearing every selection fails the
second. second.
- **One quadrant asserts less, and says so**: for window
replacement on a document, the window the gesture belonged to is
gone, so the completion has nothing left to clear and the
gesture ending is the whole effect. Written into the row rather
than left as a silently absent assertion.
- **REMAINING:** Q5's acceptance-shaped row, then the full head-exact - **REMAINING:** Q5's acceptance-shaped row, then the full head-exact
gate and the PR. gate and the PR.
- **Two test seams added for this:** an opt-in child-input tap - **Two test seams added for this:** an opt-in child-input tap

View File

@ -7948,10 +7948,23 @@ mod tests {
); );
fn terminal_panel_session(fid: FrontendId, reporting: bool) -> TerminalPanelFixture { fn terminal_panel_session(fid: FrontendId, reporting: bool) -> TerminalPanelFixture {
terminal_panel_session_at(fid, reporting, LEGACY_PANEL_VERSION)
}
/// The same fixture at a chosen negotiated version.
///
/// G5b's matrix runs every transition across BOTH families, so the
/// version has to be a parameter rather than the legacy default the
/// earlier rows could assume.
fn terminal_panel_session_at(
fid: FrontendId,
reporting: bool,
version: u32,
) -> TerminalPanelFixture {
use crate::terminal::TerminalSpec; use crate::terminal::TerminalSpec;
let (editor, mut states, render, _document, panel, _epochs) = let (editor, mut states, render, _document, panel, _epochs) =
panel_session_at(LEGACY_PANEL_VERSION, fid); panel_session_at(version, fid);
let mut spec = TerminalSpec::new("/bin/sh"); let mut spec = TerminalSpec::new("/bin/sh");
spec.args = vec!["-c".into(), "sleep 30".into()]; spec.args = vec!["-c".into(), "sleep 30".into()];
spec.rows = 4; spec.rows = 4;
@ -8801,61 +8814,109 @@ mod tests {
} }
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// G5b — the common authority-loss matrix. // G5b — the common authority-loss matrix, table-driven.
// //
// A live gesture belongs to the presentation it was pressed on. // Four transitions x two families x two targets. §5b wired `Absent`
// Five transitions end that presentation; §5b wired `Absent` and // and left these armed --- inert while nothing consumed the latch,
// left the other four armed, inert while nothing consumed the // defects the moment cancellation gained an effect.
// latch. They became defects the moment cancellation gained an
// effect, and each has its own cause even though they share one
// consequence.
// //
// Every row reads the CHILD'S STREAM: the release is the effect, // Every quadrant asserts the EFFECT, drained explicitly: the exact
// and a latch that empties without one is the exact failure §5b's // release bytes for a reporting terminal, the cleared empty
// round four named. // selection for a document, and an empty pending slot afterwards.
// Stopping at `has_pending_release()` would pass while delivery or
// recorded-domain routing was broken, which is what an earlier
// version of these rows did.
// ----------------------------------------------------------------- // -----------------------------------------------------------------
/// Render one frame, which is where the producer notices an /// Which authority loss a matrix row drives.
/// authority loss, and re-declare so a later gesture can be sent. #[derive(Clone, Copy, Debug)]
fn render_and_redeclare( enum LossCause {
editor: &crate::editor::EditorState, WindowReplaced,
states: &mut HashMap<FrontendId, crate::semantic_render::SemanticRenderState>, BufferReplaced,
fid: FrontendId, GeometrySameSize,
) { Detach,
let sem = states.get_mut(&fid).expect("projection");
let _ = sem.render_frame(editor);
} }
/// G5b(a) — the side WINDOW is replaced: a close/reopen of the same /// Which target the gesture was pressed on.
/// buffer takes a fresh identity, and the old gesture ends. #[derive(Clone, Copy, Debug)]
#[test] enum LossTarget {
fn g5b_a_a_window_replacement_cancels_and_pays() { Document,
let fid = FrontendId(820); ReportingTerminal,
let (mut editor, mut states, mut render, panel, buffer_id, epochs) = }
terminal_panel_session(fid, true);
let cell = pmacs_protocol::CellCoord::new(1, 2); /// A panel session of the requested target kind and family.
fn loss_fixture(fid: FrontendId, version: u32, target: LossTarget) -> TerminalPanelFixture {
match target {
LossTarget::ReportingTerminal => terminal_panel_session_at(fid, true, version),
LossTarget::Document => {
let (editor, mut states, render, _document, panel, _epochs) =
panel_session_at(version, fid);
let buffer_id = editor.core.borrow().windows[&panel].buffer_id;
// A press must ANCHOR, or the row never arms.
foreign_edit(&editor, buffer_id, b"alpha beta gamma\ndelta\n");
let epochs = shipped_declaration(&editor, fid, &mut states);
(editor, states, render, panel, buffer_id, epochs)
}
}
}
/// Send one press in whichever family `version` negotiated.
#[expect(
clippy::too_many_arguments,
reason = "one call shape for every matrix quadrant"
)]
fn send_press(
editor: &mut crate::editor::EditorState,
states: &mut HashMap<FrontendId, crate::semantic_render::SemanticRenderState>,
render: &mut HashMap<FrontendId, RenderState>,
fid: FrontendId,
version: u32,
epochs: (u64, u64),
buffer_id: crate::buffer::BufferId,
coord: pmacs_protocol::CellCoord,
) {
let press = pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left); let press = pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left);
let none = pmacs_protocol::Modifiers::default(); let none = pmacs_protocol::Modifiers::default();
if version >= pmacs_protocol::PANEL_MAPPING_MIN_VERSION {
send_panel( let generation = live_generation(PanelArm::Mapped, editor, states, fid);
&mut editor, let event = arm_pointer(
&mut states, PanelArm::Mapped,
&mut render,
fid, fid,
epochs, epochs,
buffer_id, buffer_id,
cell, generation,
coord,
press, press,
none,
); );
assert_eq!( dispatch_panel_event(editor, fid, version, states, render, event);
child_stream(&editor), } else {
vec![SGR_PRESS_1_2.to_vec()], send_panel(
"fixture: the press reached the child" editor, states, render, fid, epochs, buffer_id, coord, press, none,
); );
}
}
// Close and reopen the SAME buffer: a different window, so a /// Replace the side window's buffer, keeping the window.
// fresh panel identity. fn replace_panel_buffer(editor: &crate::editor::EditorState, panel: crate::window::WindowId) {
let mut core = editor.core.borrow_mut();
let replacement = core.registry.borrow_mut().create("*replacement*");
let view = {
let registry = core.registry.clone();
let registry = registry.borrow();
crate::text_view::TextView::new(registry.get(replacement).expect("replacement"))
};
let window = core.windows.get_mut(&panel).expect("panel window");
window.buffer_id = replacement;
window.text_view = view;
}
/// Close and reopen the panel on the same buffer: a new window.
fn replace_panel_window(
editor: &crate::editor::EditorState,
fid: FrontendId,
panel: crate::window::WindowId,
buffer_id: crate::buffer::BufferId,
) {
{ {
let mut core = editor.core.borrow_mut(); let mut core = editor.core.borrow_mut();
core.active_frontend = fid; core.active_frontend = fid;
@ -8877,211 +8938,194 @@ mod tests {
panel, panel,
"fixture: the successor really is a different window" "fixture: the successor really is a different window"
); );
render_and_redeclare(&editor, &mut states, fid);
assert!(
!states[&fid].has_accepted_gesture(),
"the gesture must END: it belongs to a presentation that no \
longer exists"
);
assert!(
states[&fid].has_pending_release()
|| child_stream(&editor) == vec![SGR_RELEASE_1_2.to_vec()],
"and its release must be owed or already paid, never dropped"
);
} }
/// G5b(b) — the side window's BUFFER is replaced. /// G5b — every transition, every family, every target.
/// #[expect(
/// The release still goes to the buffer the gesture was pressed on, clippy::too_many_lines,
/// because the domain is recorded --- delivering it to whatever is on reason = "one table whose quadrants read together; splitting it \
/// screen now would tell the wrong child to lift a button. hides which combinations are covered"
)]
#[test] #[test]
fn g5b_b_a_buffer_replacement_cancels_and_pays_the_old_child() { fn g5b_the_authority_loss_matrix() {
let fid = FrontendId(821); use LossCause::{BufferReplaced, Detach, GeometrySameSize, WindowReplaced};
use LossTarget::{Document, ReportingTerminal};
let mut next_fid = 830u64;
for cause in [WindowReplaced, BufferReplaced, GeometrySameSize, Detach] {
for target in [Document, ReportingTerminal] {
for version in [LEGACY_PANEL_VERSION, PROTOCOL_VERSION] {
let fid = FrontendId(next_fid);
next_fid += 1;
let label = format!("{cause:?}/{target:?}/v{version}");
let (mut editor, mut states, mut render, panel, buffer_id, epochs) = let (mut editor, mut states, mut render, panel, buffer_id, epochs) =
terminal_panel_session(fid, true); loss_fixture(fid, version, target);
let cell = pmacs_protocol::CellCoord::new(1, 2); // The terminal legs use (1, 2), whose exact SGR
let press = pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left); // bytes the constants above pin. A document panel
let none = pmacs_protocol::Modifiers::default(); // uses row 0: `foreign_edit` replaces the buffer's
// contents without refreshing the window's cached
send_panel( // line index, so only the first display row resolves
&mut editor, // to a byte --- and a press that anchors nothing does
&mut states, // not arm, which would make every document leg
&mut render, // vacuous.
fid, let cell = match target {
epochs, ReportingTerminal => pmacs_protocol::CellCoord::new(1, 2),
buffer_id, Document => pmacs_protocol::CellCoord::new(0, 2),
cell,
press,
none,
);
let _ = child_stream(&editor);
// Same window, different buffer.
{
let mut core = editor.core.borrow_mut();
let replacement = core.registry.borrow_mut().create("*replacement*");
let view = {
let registry = core.registry.clone();
let registry = registry.borrow();
crate::text_view::TextView::new(registry.get(replacement).expect("replacement"))
}; };
let window = core.windows.get_mut(&panel).expect("panel window");
window.buffer_id = replacement;
window.text_view = view;
}
render_and_redeclare(&editor, &mut states, fid); send_press(
assert!(!states[&fid].has_accepted_gesture(), "the gesture must END");
assert!(
states[&fid].has_pending_release(),
"with its release owed to the buffer it was PRESSED on, not \
to whatever occupies the panel now"
);
}
/// G5b(c) — a geometry change at an UNCHANGED cell size.
///
/// Nothing else the producer holds moves: not the panel epoch, not
/// the identity, and on a legacy peer not a mapping key either. The
/// retained geometry epoch is the only thing that can see it.
#[test]
fn g5b_c_a_same_size_geometry_change_cancels_and_pays() {
let fid = FrontendId(822);
let (mut editor, mut states, mut render, _panel, buffer_id, epochs) =
terminal_panel_session(fid, true);
let cell = pmacs_protocol::CellCoord::new(1, 2);
let press = pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left);
let none = pmacs_protocol::Modifiers::default();
let size_before = editor.core.borrow().panel_grid_size(fid).expect("grid");
send_panel(
&mut editor, &mut editor,
&mut states, &mut states,
&mut render, &mut render,
fid, fid,
version,
epochs, epochs,
buffer_id, buffer_id,
cell, cell,
press,
none,
);
let _ = child_stream(&editor);
// A new geometry epoch at the SAME total size --- a font or scale
// change that leaves `CellSize` identical.
editor.accept_semantic_frame_geometry(fid, 2, CellSize::new(24, 80));
render_and_redeclare(&editor, &mut states, fid);
assert_eq!(
editor.core.borrow().panel_grid_size(fid).expect("grid"),
size_before,
"fixture: the SIZE must be unchanged, or this row is testing \
a resize instead"
);
assert!(
!states[&fid].has_accepted_gesture(),
"the gesture must END: the same cells now sit on a different \
grid, and the epochs the frontend echoes have moved under it"
);
assert!(
states[&fid].has_pending_release(),
"and its release is owed"
);
}
/// G5b(d) — DETACH ends a live gesture and pays it before teardown.
///
/// Q4 proved detach pays a release that was ALREADY owed. This one
/// starts from a live gesture, which is the case with no later
/// opportunity of any kind.
#[test]
fn g5b_d_detach_cancels_a_live_gesture_and_pays_it() {
let fid = FrontendId(823);
let (mut editor, mut states, mut render, _panel, buffer_id, epochs) =
terminal_panel_session(fid, true);
let cell = pmacs_protocol::CellCoord::new(1, 2);
let press = pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left);
let none = pmacs_protocol::Modifiers::default();
send_panel(
&mut editor,
&mut states,
&mut render,
fid,
epochs,
buffer_id,
cell,
press,
none,
); );
assert!( assert!(
states[&fid].has_accepted_gesture(), states[&fid].has_accepted_gesture(),
"fixture: the gesture is LIVE, not merely owed" "{label}: fixture --- the press must arm, or the row \
proves nothing"
); );
let _ = child_stream(&editor); if matches!(target, ReportingTerminal) {
assert_eq!(
child_stream(&editor),
vec![SGR_PRESS_1_2.to_vec()],
"{label}: fixture --- the press reached the child"
);
}
let selection_before = editor.core.borrow().windows[&panel].selection;
match cause {
WindowReplaced => {
replace_panel_window(&editor, fid, panel, buffer_id);
render_and_redeclare(&editor, &mut states, fid);
}
BufferReplaced => {
replace_panel_buffer(&editor, panel);
render_and_redeclare(&editor, &mut states, fid);
}
GeometrySameSize => {
editor.accept_semantic_frame_geometry(fid, 2, CellSize::new(24, 80));
render_and_redeclare(&editor, &mut states, fid);
}
Detach => {
detach_session(&mut editor, &mut states, &mut render, fid); detach_session(&mut editor, &mut states, &mut render, fid);
}
}
// Detach drains inside its own teardown; the producer
// transitions park and are paid at the next drain.
if !matches!(cause, Detach) {
assert!(
!states[&fid].has_accepted_gesture(),
"{label}: the gesture must END --- it belongs to \
a presentation that no longer exists"
);
drain_pending_release(&mut editor, &mut states, fid);
assert!(
!states[&fid].has_pending_release(),
"{label}: and the slot must be empty afterwards"
);
}
match target {
ReportingTerminal => assert_eq!(
child_stream(&editor),
vec![SGR_RELEASE_1_2.to_vec()],
"{label}: EXACTLY the recorded release, to the \
child the gesture was pressed on --- routing it \
by what occupies the panel now would tell the \
wrong child to lift a button"
),
Document => {
assert!(
selection_before.is_some(),
"{label}: fixture --- the press anchored an \
empty selection for the cancellation to clear"
);
let core = editor.core.borrow();
match core.windows.get(&panel) {
Some(window) => assert!(
window.selection.is_none(),
"{label}: the empty selection must be \
cleared, or its stale anchor captures \
the next shift-motion"
),
// WINDOW REPLACED: the window the gesture
// belonged to is gone, so the completion
// has nothing left to clear and the
// gesture ENDING is the whole of the
// effect --- already asserted above. Said
// out loud rather than skipped, because a
// silently absent assertion is how a
// quadrant stops testing anything.
None => assert!(
matches!(cause, WindowReplaced),
"{label}: the panel window vanished for \
a cause that should not remove it"
),
}
}
}
}
}
}
}
/// G5m — coincident invalidations produce ONE completion effect.
///
/// Both composites the framing names, and each reads the effect
/// rather than the cancellation count: a count of one proves the
/// latch was taken once, not that exactly one release went out.
#[test]
fn g5m_coincident_invalidations_emit_one_release() {
// (a) changed-size geometry, which moves the geometry epoch AND
// the mapping generation; (b) buffer replacement, which
// moves the identity AND the mapping.
for (label, changed_size) in [("changed-size geometry", true), ("buffer replaced", false)] {
let fid = FrontendId(if changed_size { 850 } else { 851 });
let (mut editor, mut states, mut render, panel, buffer_id, epochs) =
terminal_panel_session_at(fid, true, PROTOCOL_VERSION);
let cell = pmacs_protocol::CellCoord::new(1, 2);
send_press(
&mut editor,
&mut states,
&mut render,
fid,
PROTOCOL_VERSION,
epochs,
buffer_id,
cell,
);
assert_eq!(
child_stream(&editor),
vec![SGR_PRESS_1_2.to_vec()],
"{label}: fixture --- the press reached the child"
);
if changed_size {
editor.accept_semantic_frame_geometry(fid, 2, CellSize::new(20, 60));
} else {
replace_panel_buffer(&editor, panel);
}
render_and_redeclare(&editor, &mut states, fid);
drain_pending_release(&mut editor, &mut states, fid);
assert_eq!( assert_eq!(
child_stream(&editor), child_stream(&editor),
vec![SGR_RELEASE_1_2.to_vec()], vec![SGR_RELEASE_1_2.to_vec()],
"detach must end the gesture AND pay it --- the frontend is \ "{label}: TWO causes, ONE release --- they take the same \
gone, so a release not sent here is never sent" latch, and a per-cause flag would send the child two \
releases for one press"
);
assert!(
!states[&fid].has_pending_release(),
"{label}: with nothing left owed"
); );
} }
/// G5m — coincident causes cancel ONCE.
///
/// Identity and geometry both change in one transition. Taking the
/// shared latch is what makes this one release rather than two.
#[test]
fn g5m_coincident_causes_emit_one_release() {
let fid = FrontendId(824);
let (mut editor, mut states, mut render, panel, buffer_id, epochs) =
terminal_panel_session(fid, true);
let cell = pmacs_protocol::CellCoord::new(1, 2);
let press = pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left);
let none = pmacs_protocol::Modifiers::default();
send_panel(
&mut editor,
&mut states,
&mut render,
fid,
epochs,
buffer_id,
cell,
press,
none,
);
let _ = child_stream(&editor);
// BOTH at once: a replaced buffer and a moved geometry epoch.
{
let mut core = editor.core.borrow_mut();
let replacement = core.registry.borrow_mut().create("*both*");
let view = {
let registry = core.registry.clone();
let registry = registry.borrow();
crate::text_view::TextView::new(registry.get(replacement).expect("replacement"))
};
let window = core.windows.get_mut(&panel).expect("panel window");
window.buffer_id = replacement;
window.text_view = view;
}
editor.accept_semantic_frame_geometry(fid, 2, CellSize::new(24, 80));
render_and_redeclare(&editor, &mut states, fid);
assert_eq!(
states[&fid].panel_gesture_cancellations(),
1,
"TWO causes, ONE cancellation: they take the same latch, and \
counting per cause would emit a duplicate release"
);
} }
/// G5j — document cancellation has two legs, and they differ. /// G5j — document cancellation has two legs, and they differ.
@ -9093,25 +9137,21 @@ mod tests {
#[test] #[test]
fn g5j_document_cancellation_clears_only_an_empty_selection() { fn g5j_document_cancellation_clears_only_an_empty_selection() {
for dragged in [false, true] { for dragged in [false, true] {
let fid = FrontendId(if dragged { 826 } else { 825 }); let fid = FrontendId(if dragged { 861 } else { 860 });
let (mut editor, mut states, mut render, _document, panel, epochs) = let (mut editor, mut states, mut render, panel, buffer_id, epochs) =
panel_session_at(LEGACY_PANEL_VERSION, fid); loss_fixture(fid, LEGACY_PANEL_VERSION, LossTarget::Document);
let buffer_id = editor.core.borrow().windows[&panel].buffer_id;
foreign_edit(&editor, buffer_id, b"alpha beta gamma\ndelta\n");
let press = pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left);
let drag = pmacs_protocol::MouseKind::Drag(pmacs_protocol::MouseButton::Left); let drag = pmacs_protocol::MouseKind::Drag(pmacs_protocol::MouseButton::Left);
let none = pmacs_protocol::Modifiers::default(); let none = pmacs_protocol::Modifiers::default();
send_panel( send_press(
&mut editor, &mut editor,
&mut states, &mut states,
&mut render, &mut render,
fid, fid,
LEGACY_PANEL_VERSION,
epochs, epochs,
buffer_id, buffer_id,
pmacs_protocol::CellCoord::new(0, 0), pmacs_protocol::CellCoord::new(0, 0),
press,
none,
); );
if dragged { if dragged {
send_panel( send_panel(
@ -9163,7 +9203,18 @@ mod tests {
} }
} }
/// P12 — a panel WIDER THAN 512 COLUMNS still routes pointer input. /// Render one frame, which is where the producer notices an
/// authority loss.
fn render_and_redeclare(
editor: &crate::editor::EditorState,
states: &mut HashMap<FrontendId, crate::semantic_render::SemanticRenderState>,
fid: FrontendId,
) {
let sem = states.get_mut(&fid).expect("projection");
let _ = sem.render_frame(editor);
}
/// P12 — a panel WIDER THAN 512 COLUMNS still routes pointer input. /// P12 — a panel WIDER THAN 512 COLUMNS still routes pointer input.
/// ///
/// A panel deliberately does not inherit the terminal's per-axis PTY /// A panel deliberately does not inherit the terminal's per-axis PTY
/// caps (Bet B5'): a 4K surface at a small font is legitimately /// caps (Bet B5'): a 4K surface at a small font is legitimately

View File

@ -1876,14 +1876,12 @@ impl SemanticRenderState {
// wire work but must still leave no live gesture behind — the // wire work but must still leave no live gesture behind — the
// clears below are idempotent for the same reason. // clears below are idempotent for the same reason.
// //
// Only the producer half lands here. The framing's G5b matrix — // `Absent` is one of G5b's five transitions. THE OTHER FOUR ARE
// the panel-epoch, buffer-replacement, same-size geometry and // NOW WIRED TOO, on this branch: window replacement, buffer
// detach transitions, each with its v24 and v25 legs and its // replacement and a geometry-epoch change are handled where the
// document and terminal effects — is `panel-pointer-replay`'s per // declaration is built above, and detach in the dispatcher's
// §5b's split table. Those transitions leave the latch armed on // teardown. This comment used to say they were left armed — true
// this branch, which is inert here (nothing consumes it) and // of §5b, false here since the authority-loss matrix landed.
// becomes a defect only once replay gives it effects, in the
// branch that owns the row.
self.cancel_accepted_gesture(); self.cancel_accepted_gesture();
self.panel_presentation = None; self.panel_presentation = None;
// `Absent` also clears the peer's retained mode line. A later // `Absent` also clears the peer's retained mode line. A later