fix(daemon): the accepted-gesture latch must follow the dispatch
Both inbound panel-pointer arms discarded the bool from
dispatch_semantic_panel_pointer and called update_accepted_gesture
unconditionally. The ladder authenticates the SENDER; only the
dispatcher re-derives the TARGET, so an event can clear every rung and
still be refused --- for an out-of-grid coordinate, an absent side
window, or a buffer that is no longer the one in the side window.
A rejected Down therefore ARMED the latch, so a later authority loss
counted a cancellation for a gesture that never began, and once replay
attaches effects it would deliver a release to a child that was never
pressed. A rejected Up CONSUMED a real armed gesture, so the authority
loss that should have ended it found nothing armed and that child holds
the button down for good. A rejected Down on top of a live gesture was
worse again: arm_accepted_gesture ends what it overwrites, so it also
counted a spurious cancellation.
Both arms now gate on the return. The dispatcher is #[must_use], so the
class cannot recur silently --- clippy runs with -D warnings, making a
future discarded answer a build failure rather than a review finding.
Four rows, g5_substrate_a_refused_{press_never_arms,
release_never_consumes}_on_the_{legacy,mapped}_arm. Each drives the
refusal from a coordinate one past the last grid row and ends in a
positive control differing only in that coordinate; without the control
a row would pass just as well if an unrelated rung had dropped the
event. Mapped rows read the generation through the validator's own
accessor so a mapping-rung refusal cannot masquerade as a dispatcher
refusal. Three mutations, each biting its named rows: ungating the
legacy arm fails exactly the two legacy rows, ungating the mapped arm
exactly the two mapped rows, and relaxing the dispatcher's >= to > fails
all four.
Also removes a committed diff3 ancestor marker this file carried since
8c9afde --- the only one in the branch's 32 commits, and invisible to a
clean-worktree `git diff --check`.
Also withdraws the claim that the local CRDT sweep could not go green.
The full 16-stage gate is green, sweep-crdt included. The two m4_24
failures came from running that sweep outside scripts/gate, where it
inherited TMPDIR=/tmp; /tmp/.git exists on this machine and project
detection walks upward, so both base-resolution rows resolved against
the wrong root. That is the exact hazard #240's isolation exists for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
parent
e06fb83850
commit
026bb4955f
|
|
@ -270,35 +270,103 @@ hazard in a shape that looks committed. **A documented error message
|
|||
that never appears is worse than no documentation**, because the reader
|
||||
waits for a signal that is not coming.
|
||||
|
||||
## Panel cell-mapping generation (v25) — REBASED, GATE GREEN except two pre-existing rows
|
||||
## Panel cell-mapping generation (v25) — REBASED, FULL GATE GREEN, review round 4 fixed
|
||||
|
||||
- **REBASED onto `f13506c`** (post-#241 main) at `3e68b76`, 31 ahead, 0
|
||||
behind, signed, clean. One conflict, in this file: main's merged-#241
|
||||
block and this lane's own first-commit block landed at the same
|
||||
position; both kept, active lane above merged. The other 30 commits
|
||||
applied cleanly, and no code file overlapped — #241 touched
|
||||
`scripts/`, `tests/common/` and two test suites; this lane touches
|
||||
the protocol, daemon and GPU sources.
|
||||
- **REBASED onto `f13506c`** (post-#241 main); the rebase itself landed
|
||||
at `3e68b76`, and everything after it is review-round work. One
|
||||
conflict, in this file: main's merged-#241 block and this lane's own
|
||||
first-commit block landed at the same position; both kept, active
|
||||
lane above merged. No code file overlapped — #241 touched `scripts/`,
|
||||
`tests/common/` and two test suites; this lane touches the protocol,
|
||||
daemon and GPU sources.
|
||||
- **NO AHEAD-COUNT OR TIP SHA IS RECORDED HERE, deliberately.** Both
|
||||
were wrong within one commit the last two times they were written,
|
||||
because the commit that updates this file is itself the commit that
|
||||
invalidates them. Read them instead:
|
||||
`git log --oneline githubsucks/main..HEAD | wc -l` and
|
||||
`git rev-parse HEAD`. Immutable EVENT shas — the rebase, a fix, a
|
||||
merge — are recorded; the moving tip is not.
|
||||
- **REVIEW ROUND 4 (2026-08-20) — three blockers, all fixed.**
|
||||
- **FUNCTIONAL: rejected gestures mutated the accepted-gesture
|
||||
latch.** Both inbound arms discarded the bool from
|
||||
`dispatch_semantic_panel_pointer` and called
|
||||
`update_accepted_gesture` unconditionally. The ladder authenticates
|
||||
the SENDER; only the dispatcher re-derives the TARGET, so an event
|
||||
can clear every rung and still be refused for an out-of-grid
|
||||
coordinate, an absent side window, or a buffer that is no longer
|
||||
the one in the side window. **A rejected `Down` therefore ARMED** —
|
||||
so a later authority loss counted a cancellation for a gesture that
|
||||
never began, and once replay attaches effects it would release a
|
||||
child that was never pressed. **A rejected `Up` CONSUMED a real
|
||||
armed gesture** — so the authority loss that should have ended it
|
||||
found nothing, and that child holds the button down for good. A
|
||||
rejected `Down` on top of a live gesture was worse still:
|
||||
`arm_accepted_gesture` ends what it overwrites, so it also counted
|
||||
a spurious cancellation.
|
||||
- **Fixed** by gating both arms on the return, and by marking the
|
||||
dispatcher `#[must_use]` so the class cannot recur silently —
|
||||
clippy runs with `-D warnings`, so a future discarded answer is a
|
||||
build failure rather than a review finding.
|
||||
- **Four new rows**, `g5_substrate_a_refused_{press_never_arms,
|
||||
release_never_consumes}_on_the_{legacy,mapped}_arm`. Each drives
|
||||
the refusal from a coordinate ONE PAST the last grid row and **ends
|
||||
in a positive control differing only in that coordinate** —
|
||||
without it a row would pass just as well if an unrelated rung had
|
||||
dropped the event, which is how a negative test of this shape rots.
|
||||
Mapped rows read the generation through the validator's own
|
||||
accessor, so a mapping-rung refusal cannot masquerade as a
|
||||
dispatcher refusal. **Three mutations, each biting its named rows:**
|
||||
ungating the legacy arm fails exactly the two legacy rows, ungating
|
||||
the mapped arm exactly the two mapped rows, and relaxing the
|
||||
dispatcher's `>=` to `>` fails all four.
|
||||
- **DOCS: committed conflict residue** — an orphaned diff3
|
||||
`|||||||` ancestor line survived an earlier resolution in this
|
||||
file, with the other three markers removed and both sides' content
|
||||
kept. Deleted; the section below it was the only copy.
|
||||
- **THE GATE CANNOT SEE THAT CLASS, and this is not fixed here.**
|
||||
`git diff --check` compares the WORKTREE against the index, so a
|
||||
marker that is already committed passes a clean-worktree gate
|
||||
forever. `git diff --check githubsucks/main...HEAD` exits 2 on it.
|
||||
Widening the gate's check to the branch range is a **candidate
|
||||
lane** — a gate change owes a framing doc, and it should not ride
|
||||
in on a §5b review round.
|
||||
- **The blocker is gone.** #241 fixed the `sweep-crdt` defect this lane
|
||||
was held behind, and the SIGINT guard now refuses a gate run started
|
||||
with `SIGINT` ignored — which is what made stage 15 unreachable.
|
||||
- **GATE, run in two labelled pieces** because 16 stages exceed this
|
||||
session's 10-minute command cap and the guard rightly forbids the
|
||||
backgrounding that would evade it:
|
||||
- **14 stages green** — plain gate plus all six `--acceptance`
|
||||
suites, log `20260820T143050Z-3909596`, started at load 5.28.
|
||||
- **`build-crdt` green**; **`sweep-crdt` red on two rows only**:
|
||||
`m4_24_bare_string_glob_stays_relative` and
|
||||
`m4_24_d3_fallback_base_is_the_smallest_attachment_dir`.
|
||||
- **Those two rows are PRE-EXISTING and local-only.** They fail
|
||||
identically at **current main `f13506c`**, and they **passed in CI**
|
||||
on #241's `Test (crdt)` job. Not this lane's, and not a code defect —
|
||||
local-environment-specific.
|
||||
- **FULL 16-STAGE GATE GREEN**, log `20260820T153017Z-147411`, one
|
||||
unsplit `./scripts/gate --protocol` run with all six touched
|
||||
acceptance suites. `sweep-crdt` reports **zero** failures.
|
||||
- **THE TWO `m4_24_*` FAILURES WERE MY OWN MEASUREMENT ERROR, and the
|
||||
"CI is the arbiter" framing built on them is WITHDRAWN.** I had run
|
||||
the CRDT sweep OUTSIDE `scripts/gate`, so it inherited
|
||||
`TMPDIR=/tmp` instead of the gate's isolated one, and I then reported
|
||||
the local sweep as unable to go green for pre-existing reasons. It
|
||||
goes green. CI never needed to substitute for it.
|
||||
- **MECHANISM, and it is the exact hazard #240 fixed:** `/tmp/.git`
|
||||
exists on this machine (an empty directory, 2026-08-15). Project
|
||||
detection **walks upward**, so every `tempfile::tempdir()` under
|
||||
`/tmp` inherits `/tmp` as a project root. Both failing rows are
|
||||
base-resolution rows — `m4_24_bare_string_glob_stays_relative` and
|
||||
`m4_24_d3_fallback_base_is_the_smallest_attachment_dir` — so both
|
||||
resolve against the wrong base and fail. CI's `/tmp` carries no
|
||||
such marker, which is why they passed there.
|
||||
- **The gate's `TMPDIR` is disk-backed under `<gate-root>/tmp/` on
|
||||
btrfs, in a directory the gate owns**, with no stray ancestor.
|
||||
Running the sweep by hand defeats exactly the isolation #240 built
|
||||
for this signature.
|
||||
- **`/tmp/.git` IS STILL THERE and is not mine to delete** — it is
|
||||
outside the repo and I did not create it. Any hand-run test that
|
||||
resolves a project root from a temp dir will keep failing on this
|
||||
machine until it is removed.
|
||||
- **A prior gate attempt is NOT evidence**: I wrapped it in
|
||||
`timeout 580` to fit the command cap, which killed the sweep mid-run
|
||||
(`Terminated` in its log) and reported it as a stage failure. Its
|
||||
two perf-budget failures also ran at load 36.9 with a foreign
|
||||
workload present. Recorded so the log is not mistaken for a result.
|
||||
`timeout 580` to fit the session's command cap, which killed the
|
||||
sweep mid-run (`Terminated` in its log) and reported it as a stage
|
||||
failure. Its two perf-budget failures also ran at load 36.9 with a
|
||||
foreign workload present. Recorded so the log is not mistaken for a
|
||||
result. The supported way past the cap is the tool-level background
|
||||
launch, which measures `safe` under
|
||||
`scripts/check-sigint-deliverable`; `docs/agent-handoff.md` §3 now
|
||||
says so.
|
||||
|
||||
**Written with the branch's FIRST commit**, per the standing correction
|
||||
from #171 and #215.
|
||||
|
|
@ -1189,7 +1257,6 @@ from #171 and #215.
|
|||
bite, direct-test diagnosis, unaffected foreground success, mutation,
|
||||
an otherwise unchanged gate, a distinct `error` outcome, and a
|
||||
non-Linux-unix statement.
|
||||
||||||| parent of 8a4711e (docs(framing): the panel cell-mapping generation (v25) --- SS5b revision 14)
|
||||
|
||||
## `scripts/gate` TMPDIR isolation — PR #240 OPEN
|
||||
|
||||
|
|
|
|||
295
src/daemon.rs
295
src/daemon.rs
|
|
@ -2581,18 +2581,36 @@ fn handle_dispatcher_event(
|
|||
panel_epoch,
|
||||
)
|
||||
{
|
||||
editor.dispatch_semantic_panel_pointer(source, buffer_id, coord, kind);
|
||||
update_accepted_gesture(
|
||||
semantic_states,
|
||||
source,
|
||||
kind,
|
||||
coord,
|
||||
buffer_id,
|
||||
// Whether the press reached a child is
|
||||
// replay's to know; until replay exists no
|
||||
// press does.
|
||||
false,
|
||||
);
|
||||
// THE LATCH FOLLOWS THE DISPATCH. The ladder
|
||||
// above authenticates the SENDER; the dispatcher
|
||||
// re-derives the TARGET and refuses an
|
||||
// out-of-grid coordinate, an absent side window,
|
||||
// or a buffer that is no longer the one shown
|
||||
// there. Those refusals are not hypothetical --
|
||||
// a stale coordinate outlives the frame it was
|
||||
// hit-tested against.
|
||||
//
|
||||
// Arming on a refusal lets a rejected press
|
||||
// manufacture a cancellation, and -- once replay
|
||||
// attaches effects -- a release for a child that
|
||||
// was never pressed. Consuming on a refusal is
|
||||
// worse: a rejected release swallows a REAL
|
||||
// armed gesture, so the authority loss that
|
||||
// should have ended it finds nothing, and the
|
||||
// child holds the button down forever.
|
||||
if editor.dispatch_semantic_panel_pointer(source, buffer_id, coord, kind) {
|
||||
update_accepted_gesture(
|
||||
semantic_states,
|
||||
source,
|
||||
kind,
|
||||
coord,
|
||||
buffer_id,
|
||||
// Whether the press reached a child is
|
||||
// replay's to know; until replay exists
|
||||
// no press does.
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
FrontendEvent::PanelPointerMapped {
|
||||
|
|
@ -2637,15 +2655,22 @@ fn handle_dispatcher_event(
|
|||
mapping_generation,
|
||||
)
|
||||
{
|
||||
editor.dispatch_semantic_panel_pointer(source, buffer_id, coord, kind);
|
||||
update_accepted_gesture(
|
||||
semantic_states,
|
||||
source,
|
||||
kind,
|
||||
coord,
|
||||
buffer_id,
|
||||
false,
|
||||
);
|
||||
// The latch follows the dispatch, for the
|
||||
// reason spelled out on the legacy arm above.
|
||||
// The mapping rung narrows WHICH coordinates
|
||||
// survive the ladder; it does not make a
|
||||
// surviving one land, so this arm needs the same
|
||||
// gate.
|
||||
if editor.dispatch_semantic_panel_pointer(source, buffer_id, coord, kind) {
|
||||
update_accepted_gesture(
|
||||
semantic_states,
|
||||
source,
|
||||
kind,
|
||||
coord,
|
||||
buffer_id,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
FrontendEvent::Pointer {
|
||||
|
|
@ -7317,6 +7342,234 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// §5b review round 4 — THE LATCH FOLLOWS THE DISPATCH.
|
||||
//
|
||||
// Both inbound arms discarded the dispatcher's answer and updated
|
||||
// the accepted-gesture latch unconditionally. The ladder
|
||||
// authenticates the SENDER; only the dispatcher re-derives the
|
||||
// TARGET, so an event that clears every rung can still be refused —
|
||||
// for an out-of-grid coordinate, an absent side window, or a buffer
|
||||
// that is no longer the one shown there.
|
||||
//
|
||||
// Each row drives the refusal from a coordinate ONE PAST the last
|
||||
// row of the live grid, which also pins the dispatcher's `>=`
|
||||
// against a `>`. Each ends in a POSITIVE CONTROL differing from the
|
||||
// refused event ONLY in that coordinate: without one, a row would
|
||||
// pass just as well if some unrelated rung had dropped the event,
|
||||
// which is how a negative test of this shape usually rots.
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
/// Which inbound arm a latch row exercises.
|
||||
#[derive(Clone, Copy)]
|
||||
enum PanelArm {
|
||||
Legacy,
|
||||
Mapped,
|
||||
}
|
||||
|
||||
impl PanelArm {
|
||||
fn version(self) -> u32 {
|
||||
match self {
|
||||
PanelArm::Legacy => LEGACY_PANEL_VERSION,
|
||||
PanelArm::Mapped => PROTOCOL_VERSION,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The mapping key the validator will compare against, read through
|
||||
/// the SAME accessor it reads through.
|
||||
///
|
||||
/// Peeking the last STAMPED value instead would let a mapped row be
|
||||
/// refused at the mapping rung whenever the preceding dispatch moved
|
||||
/// the fingerprint — a ladder refusal wearing a dispatcher refusal's
|
||||
/// clothes. Legacy events carry no generation and never read this.
|
||||
fn live_generation(
|
||||
arm: PanelArm,
|
||||
editor: &crate::editor::EditorState,
|
||||
semantic_states: &mut HashMap<FrontendId, crate::semantic_render::SemanticRenderState>,
|
||||
fid: FrontendId,
|
||||
) -> u64 {
|
||||
match arm {
|
||||
PanelArm::Legacy => 0,
|
||||
PanelArm::Mapped => {
|
||||
let snapshot = editor.panel_mapping_snapshot(fid);
|
||||
semantic_states
|
||||
.get_mut(&fid)
|
||||
.expect("semantic projection")
|
||||
.panel_mapping_generation(snapshot)
|
||||
.expect("a stamped mapping generation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One pointer event on whichever arm the row is testing.
|
||||
fn arm_pointer(
|
||||
arm: PanelArm,
|
||||
fid: FrontendId,
|
||||
epochs: (u64, u64),
|
||||
buffer_id: crate::buffer::BufferId,
|
||||
mapping_generation: u64,
|
||||
coord: pmacs_protocol::CellCoord,
|
||||
kind: pmacs_protocol::MouseKind,
|
||||
) -> FrontendEvent {
|
||||
match arm {
|
||||
PanelArm::Legacy => FrontendEvent::PanelPointer {
|
||||
frontend_id: fid,
|
||||
geometry_epoch: epochs.0,
|
||||
panel_epoch: epochs.1,
|
||||
buffer_id,
|
||||
coord,
|
||||
kind,
|
||||
mods: pmacs_protocol::Modifiers::default(),
|
||||
},
|
||||
PanelArm::Mapped => FrontendEvent::PanelPointerMapped {
|
||||
frontend_id: fid,
|
||||
geometry_epoch: epochs.0,
|
||||
panel_epoch: epochs.1,
|
||||
buffer_id,
|
||||
coord,
|
||||
kind,
|
||||
mods: pmacs_protocol::Modifiers::default(),
|
||||
mapping_generation,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The panel's buffer, and a coordinate one row past its grid.
|
||||
fn panel_buffer_and_outside_coord(
|
||||
editor: &crate::editor::EditorState,
|
||||
fid: FrontendId,
|
||||
panel: crate::window::WindowId,
|
||||
) -> (crate::buffer::BufferId, pmacs_protocol::CellCoord) {
|
||||
let core = editor.core.borrow();
|
||||
let grid = core.panel_grid_size(fid).expect("a live panel grid");
|
||||
(
|
||||
core.windows[&panel].buffer_id,
|
||||
pmacs_protocol::CellCoord::new(grid.rows, 0),
|
||||
)
|
||||
}
|
||||
|
||||
/// A press the dispatcher refuses must leave the latch exactly as it
|
||||
/// found it — neither armed, nor displaced.
|
||||
fn a_refused_press_never_arms(arm: PanelArm, fid: FrontendId) {
|
||||
let (mut editor, mut states, mut render, _document, panel, epochs) =
|
||||
panel_session_at(arm.version(), fid);
|
||||
let (buffer_id, outside) = panel_buffer_and_outside_coord(&editor, fid, panel);
|
||||
let inside = pmacs_protocol::CellCoord::new(0, 0);
|
||||
let press = pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left);
|
||||
let mut send =
|
||||
|editor: &mut crate::editor::EditorState,
|
||||
states: &mut HashMap<FrontendId, crate::semantic_render::SemanticRenderState>,
|
||||
coord| {
|
||||
let generation = live_generation(arm, editor, states, fid);
|
||||
let event = arm_pointer(arm, fid, epochs, buffer_id, generation, coord, press);
|
||||
dispatch_panel_event(editor, fid, arm.version(), states, &mut render, event);
|
||||
};
|
||||
|
||||
send(&mut editor, &mut states, outside);
|
||||
assert!(
|
||||
!states[&fid].has_accepted_gesture(),
|
||||
"a refused press must not arm: an authority loss would then \
|
||||
count a cancellation for a gesture that never began, and \
|
||||
replay would release a child that was never pressed"
|
||||
);
|
||||
assert_eq!(
|
||||
states[&fid].panel_gesture_cancellations(),
|
||||
0,
|
||||
"and it must not count one on the way in either"
|
||||
);
|
||||
|
||||
send(&mut editor, &mut states, inside);
|
||||
assert!(
|
||||
states[&fid].has_accepted_gesture(),
|
||||
"control: the same event one row up clears the ladder, so the \
|
||||
refusal above came from the DISPATCHER and not from a rung"
|
||||
);
|
||||
|
||||
// `arm_accepted_gesture` ends whatever it overwrites, so an
|
||||
// unconditional update also destroys a live gesture.
|
||||
send(&mut editor, &mut states, outside);
|
||||
assert!(
|
||||
states[&fid].has_accepted_gesture(),
|
||||
"the live gesture survives a refused press"
|
||||
);
|
||||
assert_eq!(
|
||||
states[&fid].panel_gesture_cancellations(),
|
||||
0,
|
||||
"and the refused press ends nothing"
|
||||
);
|
||||
}
|
||||
|
||||
/// A release the dispatcher refuses must not consume a live gesture.
|
||||
fn a_refused_release_never_consumes(arm: PanelArm, fid: FrontendId) {
|
||||
let (mut editor, mut states, mut render, _document, panel, epochs) =
|
||||
panel_session_at(arm.version(), fid);
|
||||
let (buffer_id, outside) = panel_buffer_and_outside_coord(&editor, fid, panel);
|
||||
let inside = pmacs_protocol::CellCoord::new(0, 0);
|
||||
let mut send =
|
||||
|editor: &mut crate::editor::EditorState,
|
||||
states: &mut HashMap<FrontendId, crate::semantic_render::SemanticRenderState>,
|
||||
coord,
|
||||
kind| {
|
||||
let generation = live_generation(arm, editor, states, fid);
|
||||
let event = arm_pointer(arm, fid, epochs, buffer_id, generation, coord, kind);
|
||||
dispatch_panel_event(editor, fid, arm.version(), states, &mut render, event);
|
||||
};
|
||||
let press = pmacs_protocol::MouseKind::Down(pmacs_protocol::MouseButton::Left);
|
||||
let release = pmacs_protocol::MouseKind::Up(pmacs_protocol::MouseButton::Left);
|
||||
|
||||
send(&mut editor, &mut states, inside, press);
|
||||
assert!(
|
||||
states[&fid].has_accepted_gesture(),
|
||||
"fixture: a real gesture is live"
|
||||
);
|
||||
|
||||
send(&mut editor, &mut states, outside, release);
|
||||
assert!(
|
||||
states[&fid].has_accepted_gesture(),
|
||||
"a refused release must not consume the live gesture: the \
|
||||
authority loss that should have ended it would find nothing \
|
||||
armed, and the child would hold the button down for good"
|
||||
);
|
||||
assert_eq!(
|
||||
states[&fid].panel_gesture_cancellations(),
|
||||
0,
|
||||
"and a refused release ends nothing"
|
||||
);
|
||||
|
||||
send(&mut editor, &mut states, inside, release);
|
||||
assert!(
|
||||
!states[&fid].has_accepted_gesture(),
|
||||
"control: the same release one row up DOES consume it, so the \
|
||||
refusal above came from the DISPATCHER and not from a rung"
|
||||
);
|
||||
assert_eq!(
|
||||
states[&fid].panel_gesture_cancellations(),
|
||||
0,
|
||||
"an ordinary release is a consume, never a cancellation"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn g5_substrate_a_refused_press_never_arms_on_the_legacy_arm() {
|
||||
a_refused_press_never_arms(PanelArm::Legacy, FrontendId(778));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn g5_substrate_a_refused_press_never_arms_on_the_mapped_arm() {
|
||||
a_refused_press_never_arms(PanelArm::Mapped, FrontendId(779));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn g5_substrate_a_refused_release_never_consumes_on_the_legacy_arm() {
|
||||
a_refused_release_never_consumes(PanelArm::Legacy, FrontendId(780));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn g5_substrate_a_refused_release_never_consumes_on_the_mapped_arm() {
|
||||
a_refused_release_never_consumes(PanelArm::Mapped, FrontendId(781));
|
||||
}
|
||||
|
||||
/// Criterion 50: a gesture from a source whose latest declaration is
|
||||
/// not a visible `Present` is dropped.
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -2801,6 +2801,11 @@ impl EditorState {
|
|||
/// which needs the GPU band and lands in Stage 2B-3.
|
||||
///
|
||||
/// Returns whether the gesture was accepted.
|
||||
///
|
||||
/// `#[must_use]` because the accepted-gesture latch is driven off
|
||||
/// this answer, and discarding it silently arms on rejected presses
|
||||
/// and consumes on rejected releases.
|
||||
#[must_use]
|
||||
pub fn dispatch_semantic_panel_pointer(
|
||||
&self,
|
||||
frontend_id: FrontendId,
|
||||
|
|
|
|||
Loading…
Reference in New Issue