Compare commits

..
9 changed files with 745 additions and 18 deletions
+21
View File
@@ -1351,6 +1351,27 @@ impl App {
let preview_read_only = self.process_preview_primary().is_some();
let selection_boundary = self.multiselect_boundary();
// Reject delayed gestures before they can retire newer input ownership.
if let Message::Connection(
crate::connection_edit::Event::Endpoint(captured, _)
| crate::connection_edit::Event::Restart(captured),
) = &message
&& (self.route_context() != Some(*captured)
|| self
.connection
.as_ref()
.is_none_or(|session| session.context != *captured))
{
return Task::none();
}
// Selection can change without advancing the captured canvas context.
if let Message::Route(
crate::route_edit::Event::Commit(captured, changed, ..)
| crate::route_edit::Event::CommitAttachment(captured, changed, ..),
) = &message
&& (self.route_context() != Some(*captured) || changed.edge != self.selected)
{
return Task::none();
}
if let Message::DiagramSelection(event)
| Message::StateDiagram(crate::behavior::state_edit::Event::Selection(event)) = &message
&& !self.multiselect_envelope(event)
@@ -1,6 +1,12 @@
use super::*;
use crate::app::multiselect::{self, Choice};
use crate::diagram_usage::Target as SelectionTarget;
use iced::widget::text_editor;
#[path = "group/connection_lifetime.rs"]
mod connection_lifetime;
#[path = "group/route_lifetime.rs"]
mod route_lifetime;
struct Group {
f: Fixture,
@@ -0,0 +1,292 @@
//! Delayed captured drawing packets must not retire a newer Allocation owner.
use super::*;
use crate::connection_edit::Event as ConnectionEvent;
use crate::connection_tool::Tool;
fn drawing_tool(layer: Layer) -> Tool {
match layer {
Layer::Oa => Tool::Interaction,
Layer::Sa => Tool::FunctionalExchange,
}
}
fn drawing_source(g: &mut Group, layer: Layer) -> crate::route_edit::Context {
let _ =
g.f.app
.update(Message::Connection(ConnectionEvent::Choose(Some(
drawing_tool(layer),
))));
let context = g.f.app.route_context().unwrap();
let _ =
g.f.app
.update(Message::Connection(ConnectionEvent::Endpoint(
context, g.f.work,
)));
let session = g.f.app.connection.as_ref().unwrap();
assert_eq!(session.context, context);
assert_eq!(session.source, Some(g.f.work));
assert!(session.candidates.contains(&g.y));
assert_eq!(g.f.app.route_context(), Some(context));
context
}
fn assert_captured_noop(app: &mut App, captured: crate::route_edit::Context, target: Id) {
for event in [
ConnectionEvent::Endpoint(captured, target),
ConnectionEvent::Restart(captured),
] {
assert_connection_noop(app, event);
}
}
fn assert_connection_noop(app: &mut App, event: ConnectionEvent) {
let document = app.document.snapshot();
let selected = selection(app);
let context = app.route_context();
let camera = (app.pan, app.zoom, app.canvas_epoch, app.camera_epoch);
let ownership = (app.allocation_drag.epoch, app.allocation_drag.arrange_next);
let active = app
.allocation_drag
.active
.as_ref()
.map(|a| (a.press.clone(), a.crossed, a.cache.len()));
let preview = app.allocation_drag.preview.as_ref().map(|p| {
(
p.token,
p.placement.diagram.clone(),
p.placement.delta,
p.project().clone(),
p.routed.clone(),
)
});
let feedback = app.allocation_drag.feedback.as_ref().map(|f| {
(
f.press.token,
f.text.clone(),
f.refused,
f.targets.clone(),
f.inspection_targets.clone(),
)
});
let connection = |app: &App| {
app.connection
.as_ref()
.map(|s| (s.context, s.tool, s.source, s.candidates.clone()))
};
let session = connection(app);
let interaction = app.source_symbols.interaction;
let status = app.status.clone();
let _ = app.update(Message::Connection(event));
assert_eq!(selection(app), selected);
assert_eq!(app.route_context(), context);
assert_eq!(
(app.pan, app.zoom, app.canvas_epoch, app.camera_epoch),
camera
);
assert_eq!(
(app.allocation_drag.epoch, app.allocation_drag.arrange_next),
ownership,
"rejected drawing packet must not retire the newer input owner"
);
match (active, app.allocation_drag.active.as_ref()) {
(Some((press, crossed, cached)), Some(current)) => {
assert!(Arc::ptr_eq(&press, &current.press));
assert_eq!((current.crossed, current.cache.len()), (crossed, cached));
}
(None, None) => {}
_ => panic!("rejected drawing packet changed the active Allocation token"),
}
match (preview, app.allocation_drag.preview.as_ref()) {
(Some((token, diagram, delta, project, routed)), Some(current)) => {
assert_eq!(current.token, token);
assert_eq!(current.placement.diagram, diagram);
assert_eq!(current.placement.delta, delta);
assert_eq!(current.project(), &project);
assert!(Arc::ptr_eq(&current.routed, &routed));
}
(None, None) => {}
_ => panic!("rejected drawing packet changed the Allocation preview"),
}
assert_eq!(
app.allocation_drag.feedback.as_ref().map(|f| (
f.press.token,
f.text.clone(),
f.refused,
f.targets.clone(),
f.inspection_targets.clone(),
)),
feedback
);
assert_eq!(connection(app), session);
assert_eq!(app.source_symbols.interaction, interaction);
assert_eq!(app.status, status);
assert!(Arc::ptr_eq(&document, &app.document.snapshot()));
assert_eq!(app.source_workspace.bundle(), document.bundle);
}
#[test]
fn captured_connection_after_cancel_preserves_arrange_arm_and_live_group() {
for layer in [Layer::Oa, Layer::Sa] {
let mut g = group(layer);
let old = drawing_source(&mut g, layer);
let _ = g.f.app.update(Message::Escape);
assert!(g.f.app.connection.is_none());
select(&mut g.f.app, &[g.an, g.yn]);
let _ = g.f.app.update(Message::WorkDrag(Event::ArrangeNext));
assert!(g.f.app.allocation_drag.arrange_next);
let current = g.f.app.route_context().unwrap();
assert_captured_noop(&mut g.f.app, old, g.y);
// Matching context with no session isolates the missing-owner guard.
assert_captured_noop(&mut g.f.app, current, g.y);
let before = g.f.app.project().clone();
let source = g.f.app.document.bundle().clone();
let held = selection(&g.f.app);
let from = node_point(&g.f.app, g.an);
let to = from + Vector::new(400., 0.);
let mut state = CanvasState::default();
let press = begin(&mut g.f.app, &mut state, from);
assert_eq!(press.mode, Mode::Arrange);
assert!(!g.f.app.allocation_drag.arrange_next);
move_pointer(&mut g.f.app, &mut state, to);
assert!(matches!(
g.f.app
.allocation_drag
.preview
.as_ref()
.map(|p| &p.candidate),
Some(Candidate::Arrange(_))
));
let current = g.f.app.route_context().unwrap();
assert_captured_noop(&mut g.f.app, old, g.y);
assert_captured_noop(&mut g.f.app, current, g.y);
release(&mut g.f.app, &mut state, to);
let mut expected = before.clone();
let diagram = expected.diagrams.get_mut(&g.f.diagram).unwrap();
diagram.nodes.get_mut(&g.an).unwrap().position = xy(500., 100.);
diagram.nodes.get_mut(&g.f.node).unwrap().position = xy(540., 200.);
diagram.nodes.get_mut(&g.yn).unwrap().position = xy(540., 600.);
assert_eq!(selection(&g.f.app), held);
exact_history(&mut g.f.app, &before, &source, &expected);
}
}
#[test]
fn captured_connection_context_mismatches_preserve_the_current_session() {
let mut g = group(Layer::Oa);
let current = drawing_source(&mut g, Layer::Oa);
// Session and current context agree: each old captured field is the sole
// invalid relationship, not hidden behind a missing session.
for captured in [
crate::route_edit::Context {
document: Id::new_v4(),
..current
},
crate::route_edit::Context {
diagram: Id::new_v4(),
..current
},
crate::route_edit::Context {
revision: current.revision + 1,
..current
},
crate::route_edit::Context {
epoch: current.epoch + 1,
..current
},
] {
assert_eq!(g.f.app.connection.as_ref().unwrap().context, current);
assert_captured_noop(&mut g.f.app, captured, g.y);
}
// Captured/session contexts agree, but the rendered view has advanced.
// This isolates the live-route predicate from session ownership.
g.f.app.canvas_epoch += 1;
assert_ne!(g.f.app.route_context(), Some(current));
assert_eq!(g.f.app.connection.as_ref().unwrap().context, current);
assert_captured_noop(&mut g.f.app, current, g.y);
g.f.app.canvas_epoch = current.epoch;
// Conversely, incoming/current contexts agree. Retain obsolete session
// metadata explicitly to isolate the session-context admission branch.
g.f.app.connection.as_mut().unwrap().context.epoch += 1;
assert_eq!(g.f.app.route_context(), Some(current));
assert_captured_noop(&mut g.f.app, current, g.y);
}
#[test]
fn captured_connection_current_restart_and_pair_commit_after_tool_boundary() {
for begin_tool in [false, true] {
let mut g = group(Layer::Oa);
select(&mut g.f.app, &[g.an, g.yn]);
let _ = g.f.app.update(Message::WorkDrag(Event::ArrangeNext));
let from = node_point(&g.f.app, g.an);
let mut state = CanvasState::default();
begin(&mut g.f.app, &mut state, from);
move_pointer(&mut g.f.app, &mut state, from + Vector::new(400., 0.));
assert!(g.f.app.allocation_drag.preview.is_some());
let before = g.f.app.project().clone();
let source = g.f.app.document.bundle().clone();
let epoch = g.f.app.allocation_drag.epoch;
let event = if begin_tool {
ConnectionEvent::Begin(Tool::Interaction, g.f.work)
} else {
ConnectionEvent::Choose(Some(Tool::Interaction))
};
let _ = g.f.app.update(Message::Connection(event));
assert!(g.f.app.allocation_drag.epoch > epoch);
assert!(g.f.app.allocation_drag.active.is_none());
assert!(g.f.app.allocation_drag.preview.is_none());
assert!(!g.f.app.allocation_drag.arrange_next);
let context = g.f.app.route_context().unwrap();
if !begin_tool {
let _ =
g.f.app
.update(Message::Connection(ConnectionEvent::Endpoint(
context, g.f.work,
)));
}
assert_eq!(g.f.app.connection.as_ref().unwrap().source, Some(g.f.work));
assert!(
g.f.app
.connection
.as_ref()
.unwrap()
.candidates
.contains(&g.y)
);
let document = g.f.app.document.snapshot();
let _ =
g.f.app
.update(Message::Connection(ConnectionEvent::Restart(context)));
let context = g.f.app.route_context().unwrap();
let restarted = g.f.app.connection.as_ref().unwrap();
assert_eq!(restarted.context, context);
assert_eq!(restarted.tool, Tool::Interaction);
assert_eq!(restarted.source, None);
assert!(restarted.candidates.contains(&g.f.work));
assert!(Arc::ptr_eq(&document, &g.f.app.document.snapshot()));
let _ =
g.f.app
.update(Message::Connection(ConnectionEvent::Endpoint(
context, g.f.work,
)));
assert_eq!(g.f.app.connection.as_ref().unwrap().source, Some(g.f.work));
let revision = g.f.app.document.revision();
let _ =
g.f.app
.update(Message::Connection(ConnectionEvent::Endpoint(context, g.y)));
assert!(g.f.app.connection.is_none());
assert_eq!(g.f.app.document.revision(), revision + 1);
let relation = &g.f.app.project().relations[&g.f.app.selected];
assert_eq!(relation.kind(), RelationKind::Interaction);
assert_eq!((relation.source(), relation.target()), (g.f.work, g.y));
let mut expected = before.clone();
assert!(
expected
.relations
.insert(relation.id, relation.clone())
.is_none()
);
exact_history(&mut g.f.app, &before, &source, &expected);
}
}
@@ -0,0 +1,274 @@
//! Captured route commits cannot retire a newer Allocation input owner.
use super::*;
use crate::route_edit::{Context, Event as RouteEvent};
fn routed_group() -> (Group, Id) {
let mut g = group(Layer::Oa);
let mut project = g.f.app.project().clone();
let edge = project.relate(
RelationKind::Interaction,
"Reviewed exchange",
g.f.work,
g.y,
);
project
.diagrams
.get_mut(&g.f.diagram)
.unwrap()
.routes
.push(EdgeRoute {
edge,
waypoints: vec![xy(700., 400.)],
label: Some(EdgeLabel {
fraction: 0.4,
offset: xy(5., 10.),
}),
source_attachment: None,
target_attachment: Some(EdgeAttachment {
side: PortAnchorSide::Left,
offset: 0.6,
}),
});
validate_candidate(&project).unwrap();
g.f.app = App::with_project(project);
g.f.app.active_diagram = Some(g.f.diagram);
g.f.app.zoom = 1.;
g.f.app.pan = Vector::ZERO;
g.f.app.fit_to_view = false;
choose(&mut g.f.app, SelectionTarget::Edge(edge), Choice::Replace);
assert_eq!(g.f.app.selected, edge);
let view = projection::project_view(g.f.app.project(), g.f.diagram);
assert!(view.edges.iter().any(|e| e.id == edge));
assert_eq!(
crate::route_edit::attachment_node(
g.f.app.project(),
&g.f.app.project().diagrams[&g.f.diagram],
&view,
edge,
EdgeEnd::Source,
)
.unwrap()
.id,
g.f.node
);
(g, edge)
}
fn packet(g: &Group, edge: Id, captured: Context, attachment: bool) -> RouteEvent {
let mut route =
crate::route_edit::stored_route(&g.f.app.project().diagrams[&g.f.diagram], edge);
if attachment {
route.source_attachment = Some(EdgeAttachment {
side: PortAnchorSide::Top,
offset: 0.25,
});
RouteEvent::CommitAttachment(captured, route, EdgeEnd::Source, g.f.node)
} else {
route.waypoints[0] = xy(750., 450.);
RouteEvent::Commit(captured, route, Some(0))
}
}
fn assert_route_noop(app: &mut App, event: RouteEvent) {
let document = app.document.snapshot();
let selected = selection(app);
let context = app.route_context();
let camera = (app.pan, app.zoom, app.canvas_epoch, app.camera_epoch);
let ownership = (app.allocation_drag.epoch, app.allocation_drag.arrange_next);
let press = app.allocation_drag.active.as_ref().map(|a| a.press.clone());
let preview = app.allocation_drag.preview.clone();
let feedback = app.allocation_drag.feedback.as_ref().map(|f| {
(
f.press.token,
f.text.clone(),
f.refused,
f.targets.clone(),
f.inspection_targets.clone(),
)
});
let handles = (app.selected_bend, app.selected_attachment);
let interaction = app.source_symbols.interaction;
let status = app.status.clone();
let _ = app.update(Message::Route(event));
assert_eq!(selection(app), selected);
assert_eq!(app.route_context(), context);
assert_eq!(
(app.pan, app.zoom, app.canvas_epoch, app.camera_epoch),
camera
);
assert_eq!(
(app.allocation_drag.epoch, app.allocation_drag.arrange_next),
ownership
);
match (press, app.allocation_drag.active.as_ref()) {
(Some(before), Some(after)) => assert!(Arc::ptr_eq(&before, &after.press)),
(None, None) => {}
_ => panic!("stale route packet changed the active Allocation token"),
}
match (preview, app.allocation_drag.preview.as_ref()) {
(Some(before), Some(after)) => assert!(Arc::ptr_eq(&before, after)),
(None, None) => {}
_ => panic!("stale route packet changed the Allocation preview"),
}
assert_eq!(
app.allocation_drag.feedback.as_ref().map(|f| (
f.press.token,
f.text.clone(),
f.refused,
f.targets.clone(),
f.inspection_targets.clone(),
)),
feedback
);
assert_eq!((app.selected_bend, app.selected_attachment), handles);
assert_eq!(app.source_symbols.interaction, interaction);
assert_eq!(app.status, status);
assert!(Arc::ptr_eq(&document, &app.document.snapshot()));
assert_eq!(app.source_workspace.bundle(), document.bundle);
}
fn stale_route_preserves_new_group(attachment: bool, escape: bool) {
let (mut g, edge) = routed_group();
let old = g.f.app.route_context().unwrap();
let delayed = packet(&g, edge, old, attachment);
if escape {
let _ = g.f.app.update(Message::Escape);
assert_ne!(g.f.app.route_context(), Some(old));
}
select(&mut g.f.app, &[g.an, g.yn]);
let _ = g.f.app.update(Message::WorkDrag(Event::ArrangeNext));
assert!(g.f.app.allocation_drag.arrange_next);
if !escape {
assert_eq!(g.f.app.route_context(), Some(old));
assert_ne!(g.f.app.selected, edge);
}
assert_route_noop(&mut g.f.app, delayed.clone());
let before = g.f.app.project().clone();
let source = g.f.app.document.bundle().clone();
let selected = selection(&g.f.app);
let from = node_point(&g.f.app, g.an);
let to = from + Vector::new(400., 0.);
let mut state = CanvasState::default();
let press = begin(&mut g.f.app, &mut state, from);
assert_eq!(press.mode, Mode::Arrange);
move_pointer(&mut g.f.app, &mut state, to);
assert!(g.f.app.allocation_drag.preview.is_some());
if !escape {
assert_eq!(g.f.app.route_context(), Some(old));
assert_ne!(g.f.app.selected, edge);
}
assert_route_noop(&mut g.f.app, delayed);
release(&mut g.f.app, &mut state, to);
let mut expected = before.clone();
let diagram = expected.diagrams.get_mut(&g.f.diagram).unwrap();
diagram.nodes.get_mut(&g.an).unwrap().position = xy(500., 100.);
diagram.nodes.get_mut(&g.f.node).unwrap().position = xy(540., 200.);
diagram.nodes.get_mut(&g.yn).unwrap().position = xy(540., 600.);
// Both endpoint placements move by (400, 0); the saved pin follows once,
// while label offset and endpoint attachment settings stay unchanged.
diagram
.routes
.iter_mut()
.find(|r| r.edge == edge)
.unwrap()
.waypoints = vec![xy(1100., 400.)];
assert_eq!(selection(&g.f.app), selected);
exact_history(&mut g.f.app, &before, &source, &expected);
}
#[test]
fn stale_route_commit_preserves_arrange_arm_and_live_group() {
stale_route_preserves_new_group(false, true);
}
#[test]
fn stale_route_attachment_preserves_arrange_arm_and_live_group() {
stale_route_preserves_new_group(true, true);
}
#[test]
fn same_context_route_commit_after_selection_preserves_arrange_arm_and_live_group() {
stale_route_preserves_new_group(false, false);
}
#[test]
fn same_context_route_attachment_after_selection_preserves_arrange_arm_and_live_group() {
stale_route_preserves_new_group(true, false);
}
#[test]
fn captured_route_context_fields_independently_reject_before_retirement() {
for attachment in [false, true] {
let (mut g, edge) = routed_group();
let current = g.f.app.route_context().unwrap();
// The selected edge and route payload are valid. Each capture mismatch
// is isolated; no nonexistent route session can mask admission.
for captured in [
Context {
document: Id::new_v4(),
..current
},
Context {
diagram: Id::new_v4(),
..current
},
Context {
revision: current.revision + 1,
..current
},
Context {
epoch: current.epoch + 1,
..current
},
] {
let event = packet(&g, edge, captured, attachment);
assert_route_noop(&mut g.f.app, event);
}
// Hold the captured packet fixed and advance the live context instead.
let event = packet(&g, edge, current, attachment);
g.f.app.canvas_epoch += 1;
assert_route_noop(&mut g.f.app, event.clone());
g.f.app.canvas_epoch = current.epoch;
g.f.app.active_diagram = None;
assert_route_noop(&mut g.f.app, event);
}
}
#[test]
fn current_route_commits_without_session_preserve_exact_route_history() {
for attachment in [false, true] {
let (mut g, edge) = routed_group();
let current = g.f.app.route_context().unwrap();
let event = packet(&g, edge, current, attachment);
let before = g.f.app.project().clone();
let source = g.f.app.document.bundle().clone();
let revision = g.f.app.document.revision();
assert!(g.f.app.connection.is_none());
assert!(g.f.app.selected_attachment.is_none());
let _ = g.f.app.update(Message::Route(event));
assert_eq!(g.f.app.document.revision(), revision + 1);
assert_eq!(g.f.app.selected, edge);
assert_eq!(
g.f.app.selected_bend,
if attachment { None } else { Some((edge, 0)) }
);
let mut expected = before.clone();
let route = expected
.diagrams
.get_mut(&g.f.diagram)
.unwrap()
.routes
.iter_mut()
.find(|r| r.edge == edge)
.unwrap();
if attachment {
route.source_attachment = Some(EdgeAttachment {
side: PortAnchorSide::Top,
offset: 0.25,
});
} else {
route.waypoints = vec![xy(750., 450.)];
}
exact_history(&mut g.f.app, &before, &source, &expected);
}
}
@@ -10,6 +10,11 @@ async fn press_escape(mounted: &mut Mounted, app: &mut App) -> Vec<Message> {
std::slice::from_ref(&input),
mouse::Cursor::Unavailable,
);
assert_eq!(
statuses,
[iced::event::Status::Captured],
"the mounted picker must capture Escape before App subscriptions"
);
crate::app::escape::tests::subscriptions(app, input, statuses[0], &mut messages);
for message in messages.iter().cloned() {
let task = app.update(message);
@@ -41,17 +46,11 @@ async fn mounted_state_missing_picker_escape_preserves_exact_document_then_reope
send(&mut app, Message::BehaviorDiagram(choice));
let mut mounted = Mounted::new(&mut app).await;
assert_eq!(state_edit::unplaced(app.project(), diagram), vec![f.b]);
let focus_id = iced::advanced::widget::Id::from("diagram-keyboard-owner");
let (_, canvas, _) = mounted
.geometry(&app)
.focused
.into_iter()
.find(|(id, _, _)| id.as_ref() == Some(&focus_id))
.unwrap();
// The existing picker is immediately above the measured canvas, separated
// by 8px. Its 12px text and 8px padding give a 31.6px row.
let picker = iced::Point::new(canvas.center_x(), canvas.y - 24.);
let option = iced::Point::new(picker.x, canvas.y + 8.);
// Locate the stable picker slot itself: selection controls may sit between
// it and the canvas. The single menu option opens below this slot.
let bounds = mounted.geometry(&app).bounds("state-missing-picker");
let picker = bounds.center();
let option = iced::Point::new(picker.x, bounds.y + bounds.height * 1.5);
let before = app.document.snapshot();
let original = app.project().clone();
let source = app.document.bundle().clone();
+1 -1
View File
@@ -187,7 +187,7 @@ pub fn view<'a>(app: &'a App, machine: Id, mut out: Column<'a, Message>) -> Elem
.into();
out = out.push(
column![
missing_controls,
container(missing_controls).id("state-missing-picker"),
selection_controls(app),
crate::diagram::focus::wrap(
canvas.map(event),
+39 -6
View File
@@ -1,14 +1,47 @@
# Verified branch checkpoint — 2026-09-23 UTC
# Integrated prerelease source checkpoint — 2026-09-24 UTC
This describes Codex-956atf's contribution, not a combined release.
This branch assembles peer contributions for the Windows prerelease in issue37.
[PROJECT_GOAL.md](../PROJECT_GOAL.md) remains authoritative. The complete OA/SA
application is unfinished; published previews and finite prototypes are intermediate.
**Linux/WSL builds and checks remain deferred until the user explicitly reports
networking resolved.** No guest setup, diagnosis, restart or cache transfer is
active. The VPN remains untouched.
**The current prerelease is Windows-only. Linux/WSL builds and checks remain
excluded by the user's explicit request, including after networking recovers.**
## Current integrated candidate and evidence
## Assembled source and acceptance boundary
Combined code through **4deced53b98cd4d7649ba7aa60fa4b9b30d2f3b8**, plus the
test-only import correction from **7c2b06b63f8a0fe14b3f001996bdf965b36a4e85**, adds the reviewed
Connection/Route early-admission correction to the assembled Scenario/Text,
ordinary/State/Process/Allocation selection, direct drawing, Traceability and
picker changes. The [source handoff](prerelease-integrated-source-20260924.md)
identifies the incorporated commits, exact new test paths and required gates.
The nine event regressions and existing completed-Text-Apply cases are retained.
Direct formatting and whitespace checks pass; this source checkpoint does not
claim combined Cargo, native, packaged or release acceptance. The independent
[event-fix review](reviews/prerelease37-event-admission-20260924.md) found no
further material source defect in its stated scope. A subsequent peer compile
found a missing `projection::` qualification in the new Route test helper;
the current code includes that one-line correction. The original compile failed
before running tests. The first combined compile at `897d9f04` subsequently
found a missing `text_editor` import in the Allocation Text Apply regression;
the current source includes that one-line correction too. Both failures remain
recorded in the source handoff. The subsequent full test run at `8ddfadf4`
failed the mounted State picker fixture: added selection controls invalidated
its canvas-relative clicks. Source successor `a9cd364eacbbffa0693bab05b5a2b65f9d105535`
measures an identified fixed-height picker slot and checks Escape capture,
retaining the exact preservation/reopen/Place/Undo assertions. The source
handoff links the preserved failed and interrupted attempts. Corrected combined
Windows execution and package acceptance remain pending.
Live build, final-source and publication results belong in
[issue37](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/37).
Human workflows are described in [the preview checklist](../docs/preview-human-checks.md).
Private mixed-parser source is excluded from this application candidate.
## Contribution-specific verification
The following records retain their original source and runtime scope; they are
not relabeled as acceptance of the combined source above.
Branch `codex/behavior-authoring-20260910`, published as `codex/release1-956atf`.
Integrated code checkpoint **4ffff0d9d94bd1875e6b316b84ac11e5c5c9657c** combines
@@ -43,8 +43,87 @@ candidate: Allocation group `57aec979f640ba1b8aa6fcbb7d9e26b29cf51596`, drawing
ancestry and is linked rather than merged for source. Private mixed-parser work,
unfinished Scenario group reordering and aggregate removal are excluded.
## Captured event admission successor
Combined code `a6b9f6029aad618a84d136c5293353688d36809f` cherry-picks the narrow
peer code commits `44217307`, `72b1bb7`, and `6f247af795b7bec1818c588b2dabf203f8f68d8a`.
The peer's exact source and initial evidence are published at
[`c655c5e`](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/src/commit/c655c5eeaaa32c1c21074a7543844ae99a1d6fd5/harness/prerelease37-connection-admission-20260924.md).
Only `app.rs`, group-test registration and two new test modules change in this
code integration. Historical evidence archives are linked rather than merged.
Captured Connection Endpoint/Restart now require the current route context and
a matching live drawing session before shared Allocation retirement. Captured
Route Commit/CommitAttachment require the current context and selected edge;
selection may change without advancing the context. Stale packets preserve
newer Arrange/drag state and feedback. Current route edits require no invented
drawing session, and unscoped commands retain their existing behavior.
The three Connection and six Route regressions are in:
- `crates/oa-desktop/src/app/allocation_drag_tests/group/connection_lifetime.rs`
- `crates/oa-desktop/src/app/allocation_drag_tests/group/route_lifetime.rs`
The real Text Apply cases added at `542b10ac` remain in:
- `crates/oa-desktop/src/app/process_multiselect/tests.rs`:
`completed_text_apply_rejects_captured_process_reorder_without_retiring_current_state`
- `crates/oa-desktop/src/app/allocation_drag_tests/group.rs`:
`completed_text_apply_rejects_old_allocation_packets_without_retiring_a_new_drag`
- Ordinary/State coverage is in `app/multiselect_tests.rs`; earlier navigation
tests in `navigation_union.rs` are a separate scope.
Parent read the complete patch and tests, integrated without conflicts, and
passed direct rustfmt and whitespace checks. A fresh independent
[source review](reviews/prerelease37-event-admission-20260924.md) found no
actionable defect in the stated boundary. These do not substitute for the final
combined execution. The vendor patch-description correction is incorporated at
`17eaa03fd69741dff746f21d150dc04c341411b3`.
## Mounted State picker correction
The combined Windows test run at `8ddfadf440b5c208b1adb6b211a97ba0c99cff04`
failed the mounted State missing-picker Escape regression. The test located the
picker relative to the canvas, but the integrated selection toolbar adds 56px
plus an 8px gap between them. Both the opening and option clicks were stale.
Parent and independent source inspection agreed on this fixture defect; they
found no reason to change the production Escape policy from this failure.
Successor `a9cd364eacbbffa0693bab05b5a2b65f9d105535` adds a zero-padding
identified container around the existing fixed-height picker slot. The fixture
measures that slot, checks Escape capture before App subscriptions, and retains
zero messages, exact document/source/selection/camera/revision/history,
reopen-and-Place, and exact source Undo checks. The option coordinate assumes
this fixture's single downward-opening menu at 1800 by 1100; it is not a general
overlay geometry evaluator. No production Escape handler changes.
The peer records 2,199 passes, one failure and eight ignores for that failed
full run, with lint and product builds not run. The earlier final-02 attempt
was interrupted during harness listing before test bodies; neither attempt is
a passed gate. [Preserved attempts and v2 recipe](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/src/commit/184c1307d1ae3d49007480020fa1c080c94ececa/harness/prerelease37-windows-gates-20260924/attempts-8dd)
retain their distinct boundaries. The corrected source requires current
combined Windows gates and packaged native acceptance.
## Combined gates and package handoff
The first peer compilation exposed E0425 in the new Route fixture: its
`project_view` call lacked the `projection::` qualification used by the parent
helpers. Combined successor `4deced53b98cd4d7649ba7aa60fa4b9b30d2f3b8`
cherry-picks peer `e68a83959900e556768d8470640e28a7df7d8cb8` to correct that
single test line. Production code is unchanged. This concrete finding supersedes
the earlier `9e99618c` build handoff; the failed compile ran no tests and remains
part of the peer's evidence. Source review and formatting did not catch it.
The first combined Windows attempt on `897d9f04` then found E0433 in the
Allocation Text Apply test added at `542b10ac`: the test's module lacked
`iced::widget::text_editor`. The current successor incorporates peer
`7c2b06b63f8a0fe14b3f001996bdf965b36a4e85`, adding that import only. Production
code remains unchanged. The failed combined compile ran no tests, lint or
product build; its 3,709 source inputs stayed unchanged and formatting passed.
[Preserved failure evidence](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/src/commit/2bd0e615204be728b5c2b77cc7e28504c18a2277/harness/prerelease37-windows-gates-20260924/README.md)
records that distinct failure. The corrected successor requires fresh combined
execution; no earlier pass is substituted.
Codex-ytr83r owns the exact-candidate Windows build/package on its separate
machine. Codex-9e8r6t owns independent source/seam review and a downloaded-package
check. Codex-956atf remains the sole integration, tag and release publication owner.
@@ -0,0 +1,23 @@
# Prerelease 37 event guard integration review
2026-09-24. Independent source review; no confirmed actionable finding in the bounded change.
Parent follow-up: the later peer compile found E0425 at
`route_lifetime.rs:40`, which this source review missed. Peer correction
`e68a83959900e556768d8470640e28a7df7d8cb8` qualifies the call as
`projection::project_view`; combined code `4deced53b98cd4d7649ba7aa60fa4b9b30d2f3b8`
incorporates it. This is test-only and does not change the guard analysis below.
The failed compile ran no tests; passing corrected execution must be recorded
separately. The original review is retained below with its original scope.
Reviewed exact delta `340a5c56b0783e22d7336a07ce96ddb7c2b79b0d..6f247af795b7bec1818c588b2dabf203f8f68d8a` (code commits `44217307`, `72b1bb7`, `6f247af`) against combined candidate `17eaa03fd69741dff746f21d150dc04c341411b3`. This is compatibility review before the parent's integration, not verification of an already integrated commit. Read AGENTS.md, App update/connection/route handlers, Allocation retirement adapter, both new regression modules, group fixtures/history evaluator, and existing combined Allocation connection regressions. Formed independent judgment before reading peer reviews at `c655c5eeaaa32c1c21074a7543844ae99a1d6fd5`.
The 21-line early guards precede pending-reveal reconciliation, allocation_before, inner dispatch and post-dispatch reconciliation. The preceding preview/selection-boundary computations are reads. Connection Endpoint/Restart require both current route Context and extant matching session; Choose/Begin retain their intentional new-owner behavior. Current Restart remains admitted and creates a fresh Choose session. Route Commit/CommitAttachment require current Context and payload edge equal to current selected edge, without incorrectly demanding a Connection session or armed attachment.
The combined candidate does not change allocation_before from the reviewed base; its default retirement of Connection/Route makes early rejection necessary. The combined Allocation drawing adapter retains captured context through first endpoint selection, including mixed-layer Traceability, and changes model revision only after completed pair execution. Nothing in the new guard rejects those valid paths. Combined saved-Scenario route_context returns None, so old captured architecture events are rejected consistently.
The connection regressions independently isolate missing session, live route mismatch with session equal to captured context, and session mismatch with live route equal to captured context. Both Endpoint and Restart traverse these cases. The field matrix checks document, diagram, revision and epoch. Current Choose/Begin retire a live Arrange preview, then current Restart and an endpoint pair commit with exact history.
The route regressions independently isolate context mismatch while edge selection remains valid and same-context changed selection for both commit variants. Both armed Arrange and active preview survive rejection and then complete. Literal expected placement and pin coordinates protect against merely retaining an unusable token; both moved route endpoints translate the pin once to (1100, 400). Positive commits independently specify bend/source-attachment changes and preserve other model fields. Whole-Project/source reopen and exact one-step Undo/Redo checks are meaningful oracles rather than implementation mirrors.
Coverage limits and remaining checks: this reviewer ran no compiler, tests, formatter, GUI, fetch or Gitea mutation. The parent must run the new regression modules and existing connection_edit Allocation/mixed-layer tests against the actual cherry-picked Windows candidate, plus required Windows build/package and native smoke gates. New retained-owner cases exercise Arrange; they do not independently exercise every reassignment drag mode, every connection tool, or broader same-edge selection-away-and-back lifetime semantics. Existing combined tests cover positive OA/SA Allocation pairs and mixed-layer views, but their passing execution after integration is not established by source review. Linux is excluded from this acceptance claim.