Compare commits

...
15 changed files with 3927 additions and 8 deletions
@@ -0,0 +1,283 @@
//! Native UI wiring fixture/oracle. Expected output comes from candidate Layout,
//! not from an independent geometry algorithm. Run only after parent source freeze.
use oa_core::*;
use serde_json::{Value, json};
use std::{collections::BTreeMap, error::Error, fs, path::PathBuf};
fn id(n: u128) -> Id {
Id::from_u128(700_000 + n)
}
fn at(x: f32, y: f32) -> Position {
Position { x, y }
}
fn function(p: &mut Project, n: u128, name: &str, parent: Option<Id>) -> Id {
let old = p
.add_element(Kind::Function, Layer::Sa, name, parent)
.unwrap();
reidentify(p, old, id(n)).unwrap();
id(n)
}
fn place(p: &mut Project, d: Id, n: u128, element: Id, position: Position, size: Size) {
let old = p.place(d, element, position);
reidentify(p, old, id(n)).unwrap();
let node = p
.diagrams
.get_mut(&d)
.unwrap()
.nodes
.get_mut(&id(n))
.unwrap();
node.alias = format!("placement_{n}");
node.size = size;
}
fn exchange(p: &mut Project, n: u128, name: &str, source: Id, target: Id) -> Id {
let request = FunctionalExchangeRequest {
id: id(n),
alias: format!("exchange_{n}"),
name: name.into(),
description: String::new(),
source,
target,
source_port: PortChoice::Create,
target_port: PortChoice::Create,
items: vec![],
};
let commands = ports::plan_functional_exchange(p, &request).unwrap();
*p = execute_batch(p, &commands).unwrap();
id(n)
}
fn route(edge: Id, waypoints: Vec<Position>, fraction: f32, offset: Position) -> EdgeRoute {
EdgeRoute {
edge,
source_attachment: None,
target_attachment: None,
waypoints,
label: Some(EdgeLabel { fraction, offset }),
}
}
fn original() -> (Project, Id, Id, Value) {
let mut p = Project::empty();
p.id = id(0);
p.alias = "arrange_native_fixture".into();
p.name = "Arrange route reset trial".into();
let old = p.ensure_diagram(Layer::Sa);
let main = id(1);
reidentify(&mut p, old, main).unwrap();
{
let d = p.diagrams.get_mut(&main).unwrap();
d.alias = "main_dispatch".into();
d.name = "Main dispatch".into();
d.projection = ProjectionKind::FunctionalDataflow;
}
let group = function(&mut p, 10, "Prepare report", None);
let send = function(&mut p, 11, "Send report", Some(group));
let check = function(&mut p, 12, "Check report", Some(group));
let monitor = function(&mut p, 13, "Monitor report", None);
let archive = function(&mut p, 14, "Archive report", None);
let card = Size {
width: 224.,
height: 94.,
};
place(
&mut p,
main,
20,
group,
at(40., 80.),
Size {
width: 400.,
height: 420.,
},
);
for (n, element, x, y) in [
(21, send, 80., 160.),
(22, check, 80., 330.),
(23, monitor, 740., 80.),
(24, archive, 740., 380.),
] {
place(&mut p, main, n, element, at(x, y), card);
}
let authored = exchange(&mut p, 100, "Verified report", send, monitor);
exchange(&mut p, 101, "Report for checking", send, check);
exchange(&mut p, 102, "Checked report archive", check, archive);
let hidden = exchange(
&mut p,
103,
"Filtered archive acknowledgement",
monitor,
archive,
);
let RelationBody::FunctionalExchange {
source_port,
target_port,
..
} = p.relations[&authored].body
else {
unreachable!()
};
for (node, port, side, offset) in [
(id(21), source_port, PortAnchorSide::Right, 0.25),
(id(23), target_port, PortAnchorSide::Left, 0.75),
] {
p = prepare_command(
&p,
&Command::SetPortAnchor {
diagram: main,
node,
anchor: PortAnchor { port, side, offset },
},
)
.unwrap()
.project;
}
let mut visible = route(
authored,
vec![at(610., 210.), at(610., 420.)],
0.5,
at(0., -32.),
);
// Real semantic ports remain authoritative; these stored preferences are dormant.
visible.source_attachment = Some(EdgeAttachment {
side: PortAnchorSide::Bottom,
offset: 0.5,
});
visible.target_attachment = Some(EdgeAttachment {
side: PortAnchorSide::Top,
offset: 0.25,
});
let filtered = route(hidden, vec![at(1020., 270.)], 0.75, at(16., -16.));
p.diagrams.get_mut(&main).unwrap().routes = vec![visible, filtered];
p.diagrams.get_mut(&main).unwrap().view_options.hidden_edges = vec![hidden];
let second = id(2);
p.diagrams.insert(
second,
Diagram {
id: second,
alias: "review_sentinel".into(),
name: "Review sentinel".into(),
description:
"Independent other-view route; Arrange Main dispatch must retain this entire view"
.into(),
layer: Layer::Sa,
projection: ProjectionKind::FunctionalDataflow,
view_options: Default::default(),
nodes: BTreeMap::new(),
layout: None,
routes: vec![route(authored, vec![at(420., 40.)], 0.25, at(8., -8.))],
},
);
place(&mut p, second, 30, send, at(40., 120.), card);
place(&mut p, second, 31, monitor, at(660., 120.), card);
validate_candidate(&p).unwrap();
(
p,
main,
second,
json!({"authored_edge":authored,"authored_edge_name":"Verified report",
"filtered_edge":hidden,"container_placement":id(20),"source_port":source_port,"target_port":target_port,
"main_diagram":main,"second_diagram":second,"authored_pin_count":2,
"source_anchor":{"side":"Right","offset":0.25},"target_anchor":{"side":"Left","offset":0.75},
"dormant_source_attachment":{"side":"Bottom","offset":0.5},
"dormant_target_attachment":{"side":"Top","offset":0.25}}),
)
}
fn json_bytes(p: &Project) -> Vec<u8> {
let mut bytes = serde_json::to_vec_pretty(p).unwrap();
if !bytes.ends_with(b"\n") {
bytes.push(b'\n');
}
assert_eq!(persistence::decode(&bytes).unwrap(), *p);
bytes
}
fn source_bytes(p: &Project) -> Vec<u8> {
let source = language::format(p);
assert_eq!(language::parse(&source).unwrap(), *p);
let mut bytes = source.into_bytes();
if !bytes.ends_with(b"\n") {
bytes.push(b'\n');
}
bytes
}
fn main() -> Result<(), Box<dyn Error>> {
let mut args = std::env::args_os().skip(1);
let first = args
.next()
.ok_or("Usage: arrange_reset_native_probe NEW_DIRECTORY | --compare ACTUAL EXPECTED")?;
if first == "--compare" {
let actual_path = args.next().ok_or("Missing ACTUAL path")?;
let expected_path = args.next().ok_or("Missing EXPECTED path")?;
if args.next().is_some() {
return Err("Unexpected comparison argument".into());
}
let actual = persistence::decode(&fs::read(actual_path)?)?;
let expected = persistence::decode(&fs::read(expected_path)?)?;
let equal = actual == expected;
println!(
"{}",
json!({"complete_project_equal": equal,
"actual_diagrams": actual.diagrams.len(),
"expected_diagrams": expected.diagrams.len()})
);
if !equal {
return Err("Complete typed Projects differ".into());
}
return Ok(());
}
let output = PathBuf::from(first);
if args.next().is_some() || output.exists() {
return Err("Require exactly one output directory that does not already exist".into());
}
let (before, main, second, identities) = original();
let mut folded = before.clone();
folded
.diagrams
.get_mut(&main)
.unwrap()
.nodes
.get_mut(&id(20))
.unwrap()
.collapsed = true;
let mut files = Vec::new();
let mut cases = Vec::new();
for (name, project) in [("expanded", before), ("folded", folded)] {
validate_candidate(&project)?;
// Explicitly candidate-derived UI oracle: this is not an independent layout proof.
let expected = execute_batch(&project, &[Command::Layout(main)])?;
assert_eq!(project.elements, expected.elements);
assert_eq!(project.relations, expected.relations);
assert_eq!(project.diagrams[&second], expected.diagrams[&second]);
for (stage, p) in [("original", &project), ("expected-after", &expected)] {
files.push((format!("{name}-{stage}.json"), json_bytes(p)));
files.push((format!("{name}-{stage}.arcadia"), source_bytes(p)));
}
cases.push(json!({"name":name,"diagram":main,
"original_json":format!("{name}-original.json"),
"expected_json":format!("{name}-expected-after.json"),
"original_source":format!("{name}-original.arcadia"),
"expected_source":format!("{name}-expected-after.arcadia"),
"original_routes":project.diagrams[&main].routes,"expected_routes":expected.diagrams[&main].routes,
"expected_origin":"Candidate Command::Layout; UI-wiring oracle only"}));
}
let manifest = json!({"schema":1,"cases":cases,"identities":identities,
"definition":"Source-authored five-Function/two-view fixture; original geometry never generated by Layout.",
"comparison":"Decode saved JSON with production persistence and compare complete Project to immutable original/expected file. Prefer direct serializer bytes for byte checks; do not reserialize Project through generic JSON Value as a float oracle.",
"performed":"Generation and core checks only when this program is actually run; no native UI results recorded here."});
files.push((
"manifest.json".into(),
serde_json::to_vec_pretty(&manifest)?,
));
fs::create_dir(&output)?;
for (name, bytes) in files {
fs::write(output.join(name), bytes)?;
}
println!("{}", output.join("manifest.json").display());
Ok(())
}
+22 -1
View File
@@ -952,6 +952,21 @@ fn layout(p: &mut Project, diagram: Id) -> Result<(), ModelError> {
"Scenario layout requires the Scenario geometry operation.".into(),
));
}
// Freeze reset eligibility before this operation changes geometry. In a
// Batch this is the staged input, not an independently validated prefix.
let view = crate::projection::project_view(p, diagram);
let visible_nodes: BTreeSet<_> = view.nodes.iter().map(|node| node.id).collect();
let reset_routes: BTreeSet<_> = view
.edges
.iter()
.filter_map(|edge| {
let owners = crate::projection::edge_endpoint_placements(p, d, edge.id)?;
(owners == (edge.source, edge.target)
&& visible_nodes.contains(&edge.source)
&& visible_nodes.contains(&edge.target))
.then_some(edge.id)
})
.collect();
let parents = crate::projection::parents(p, d);
let mut children: BTreeMap<Option<Id>, Vec<Id>> = BTreeMap::new();
for (id, parent) in &parents {
@@ -1044,7 +1059,7 @@ fn layout(p: &mut Project, diagram: Id) -> Result<(), ModelError> {
}
node
};
let edges: BTreeSet<_> = crate::projection::project_view(p, diagram)
let edges: BTreeSet<_> = view
.edges
.iter()
.filter_map(|edge| {
@@ -1138,5 +1153,11 @@ fn layout(p: &mut Project, diagram: Id) -> Result<(), ModelError> {
*existing = route;
}
}
for route in &mut d.routes {
if reset_routes.contains(&route.edge) {
route.waypoints.clear();
route.label = None;
}
}
Ok(())
}
+451 -5
View File
@@ -649,7 +649,7 @@ fn group_translation_beyond_coordinate_limit_rolls_back_nodes_and_routes() {
}
#[test]
fn layout_preserves_authored_route_count_order_labels_and_undo() {
fn layout_resets_visible_pins_labels_preserving_route_count_order_and_undo() {
let f = routed(fixture());
let mut session = Session::new(f.project.clone());
session.execute(Command::Layout(f.diagram)).unwrap();
@@ -657,10 +657,10 @@ fn layout_preserves_authored_route_count_order_labels_and_undo() {
assert_eq!(routes.len(), 2);
assert_eq!(routes[0].edge, f.edge);
assert_eq!(routes[1].edge, f.external);
assert_eq!(routes[0].waypoints.len(), 2);
assert_eq!(routes[1].waypoints.len(), 2);
assert_eq!(routes[0].label, route(f.edge).label);
assert_eq!(routes[1].label, route(f.external).label);
assert!(routes[0].waypoints.is_empty());
assert!(routes[1].waypoints.is_empty());
assert_eq!(routes[0].label, None);
assert_eq!(routes[1].label, None);
assert_eq!(
session.project().diagrams[&f.second],
f.project.diagrams[&f.second]
@@ -669,6 +669,452 @@ fn layout_preserves_authored_route_count_order_labels_and_undo() {
assert_eq!(session.project(), &f.project);
}
#[test]
fn layout_keeps_attachments_port_anchors_empty_entries_and_exact_history() {
let mut f = routed(fixture());
let extra = f.project.relate(
RelationKind::FunctionalExchange,
"Attachment only",
f.a,
f.b,
);
let empty = f
.project
.relate(RelationKind::FunctionalExchange, "Explicit empty", f.a, f.b);
let attachment = EdgeAttachment {
side: PortAnchorSide::Bottom,
offset: 0.23,
};
let opposite = EdgeAttachment {
side: PortAnchorSide::Top,
offset: 0.81,
};
let mut fraction_only = route(f.edge);
fraction_only.waypoints.clear();
fraction_only.label = Some(EdgeLabel {
fraction: 0.73,
offset: Position { x: 0., y: 0. },
});
fraction_only.source_attachment = Some(attachment);
fraction_only.target_attachment = Some(opposite);
let attachment_only = EdgeRoute {
edge: extra,
source_attachment: Some(attachment),
target_attachment: None,
waypoints: vec![],
label: None,
};
let empty_route = EdgeRoute {
edge: empty,
source_attachment: None,
target_attachment: None,
waypoints: vec![],
label: None,
};
f.project.diagrams.get_mut(&f.diagram).unwrap().routes = vec![
route(f.external),
fraction_only.clone(),
attachment_only.clone(),
empty_route.clone(),
];
let RelationBody::FunctionalExchange { source_port, .. } = f.project.relations[&f.edge].body
else {
unreachable!()
};
let owner = node(&f.project, f.diagram, f.a);
let anchor = PortAnchor {
port: source_port,
side: PortAnchorSide::Left,
offset: 0.37,
};
f.project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.nodes
.get_mut(&owner)
.unwrap()
.port_anchors
.push(anchor.clone());
validate_candidate(&f.project).unwrap();
let original_source = language::format(&f.project);
let mut session = Session::new(f.project.clone());
session.execute(Command::Layout(f.diagram)).unwrap();
let after = session.project().clone();
let mut expected_external = route(f.external);
expected_external.waypoints.clear();
expected_external.label = None;
fraction_only.label = None;
assert_eq!(
after.diagrams[&f.diagram].routes,
vec![
expected_external,
fraction_only,
attachment_only,
empty_route
]
);
assert_eq!(
after.diagrams[&f.diagram].nodes[&owner].port_anchors,
vec![anchor]
);
assert_eq!(after.elements, f.project.elements);
assert_eq!(after.relations, f.project.relations);
assert_eq!(after.diagrams[&f.second], f.project.diagrams[&f.second]);
assert_eq!(language::parse(&language::format(&after)).unwrap(), after);
let command: Command =
serde_json::from_value(serde_json::json!({"Layout": f.diagram})).unwrap();
assert_eq!(execute_batch(&f.project, &[command]).unwrap(), after);
session.undo();
assert_eq!(session.project(), &f.project);
assert_eq!(language::format(session.project()), original_source);
assert!(!session.can_undo());
session.redo();
assert_eq!(session.project(), &after);
}
fn shift_all(p: &mut Project, d: Id, x: f32, y: f32) {
for placement in p.diagrams.get_mut(&d).unwrap().nodes.values_mut() {
placement.position.x += x;
placement.position.y += y;
}
}
fn translated_expected(mut route: EdgeRoute, x: f32, y: f32) -> EdgeRoute {
for point in &mut route.waypoints {
point.x += x;
point.y += y;
}
route
}
#[test]
fn layout_resets_only_visible_actual_owners_and_retains_filtered_and_missing_routes() {
let mut f = fixture();
let outside = f.project.relations[&f.external].target();
let visible = f.project.relate(
RelationKind::FunctionalExchange,
"Visible overview",
f.group,
outside,
);
let absent = f
.project
.add_element(Kind::Function, Layer::Sa, "Not placed", None)
.unwrap();
let missing = f.project.relate(
RelationKind::FunctionalExchange,
"Dormant missing owner",
f.b,
absent,
);
f.project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.view_options
.hidden_edges = vec![f.edge, f.external];
// Freeze a geometry-only starting point before adding sentinels. The oracle
// below names the eligible edge and exact authored deltas independently.
let base = execute_batch(&f.project, &[Command::Layout(f.diagram)]).unwrap();
f.project = base.clone();
shift_all(&mut f.project, f.diagram, 40., 60.);
let outside_node = node(&f.project, f.diagram, outside);
f.project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.nodes
.get_mut(&outside_node)
.unwrap()
.position
.x += 7.;
f.project.diagrams.get_mut(&f.diagram).unwrap().routes = vec![
route(visible),
route(f.edge),
route(f.external),
route(missing),
];
let view = projection::project_view(&f.project, f.diagram);
assert_eq!(
view.edges.iter().map(|edge| edge.id).collect::<Vec<_>>(),
vec![visible]
);
let after = execute_batch(&f.project, &[Command::Layout(f.diagram)]).unwrap();
assert_eq!(
after.diagrams[&f.diagram].nodes,
base.diagrams[&f.diagram].nodes
);
let mut reset = route(visible);
reset.waypoints.clear();
reset.label = None;
assert_eq!(
after.diagrams[&f.diagram].routes,
vec![
reset,
translated_expected(route(f.edge), -40., -60.),
route(f.external),
route(missing)
]
);
assert_eq!(after.diagrams[&f.second], f.project.diagrams[&f.second]);
}
#[test]
fn layout_folded_routes_use_actual_owner_deltas_without_resetting_promoted_edges() {
let mut f = routed(fixture());
let group = node(&f.project, f.diagram, f.group);
let outside = f.project.relations[&f.external].target();
let outside_node = node(&f.project, f.diagram, outside);
f.project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.nodes
.get_mut(&group)
.unwrap()
.collapsed = true;
f.project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.routes
.clear();
let base = execute_batch(&f.project, &[Command::Layout(f.diagram)]).unwrap();
f.project = base.clone();
shift_all(&mut f.project, f.diagram, 40., 60.);
// The visible container's displacement differs from its hidden children.
f.project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.nodes
.get_mut(&group)
.unwrap()
.position
.x += 13.;
f.project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.nodes
.get_mut(&outside_node)
.unwrap()
.position
.x += 7.;
f.project.diagrams.get_mut(&f.diagram).unwrap().routes = vec![route(f.edge), route(f.external)];
let view = projection::project_view(&f.project, f.diagram);
assert!(!view.edges.iter().any(|edge| edge.id == f.edge));
let promoted = view
.edges
.iter()
.find(|edge| edge.id == f.external)
.unwrap();
assert_eq!(promoted.source, group);
let after = execute_batch(&f.project, &[Command::Layout(f.diagram)]).unwrap();
assert_eq!(
after.diagrams[&f.diagram].nodes,
base.diagrams[&f.diagram].nodes
);
assert_eq!(
after.diagrams[&f.diagram].routes,
vec![
translated_expected(route(f.edge), -40., -60.),
route(f.external)
]
);
assert_eq!(after.diagrams[&f.second], f.project.diagrams[&f.second]);
}
#[test]
fn layout_batch_uses_staged_visibility_and_one_final_validation_history_boundary() {
let f = routed(fixture());
let mut hidden = f.project.diagrams[&f.diagram].clone();
hidden.view_options.hidden_edges = vec![f.edge, f.external];
let mut invalid = f.project.elements[&f.a].clone();
invalid.alias = f.project.elements[&f.b].alias.clone();
// Duplicate alias is repaired later in the same transaction. Layout must
// not add prefix validation, nor reset routes hidden by the preceding edit.
let mut session = Session::new(f.project.clone());
session
.execute(Command::Batch(vec![
Command::EditElement(f.a, invalid),
Command::EditDiagram(f.diagram, hidden),
Command::Layout(f.diagram),
Command::EditElement(f.a, f.project.elements[&f.a].clone()),
]))
.unwrap();
let after = session.project().clone();
assert_eq!(
after.diagrams[&f.diagram]
.routes
.iter()
.map(|r| (r.edge, r.waypoints.len(), r.label.clone()))
.collect::<Vec<_>>(),
vec![
(f.edge, 2, route(f.edge).label),
(f.external, 2, route(f.external).label)
]
);
session.undo();
assert_eq!(session.project(), &f.project);
assert!(!session.can_undo());
session.redo();
assert_eq!(session.project(), &after);
let mut bad = after.elements[&f.a].clone();
bad.alias = after.elements[&f.b].alias.clone();
let revision = session.revision();
assert!(
session
.execute(Command::Batch(vec![
Command::Layout(f.diagram),
Command::EditElement(f.a, bad)
]))
.is_err()
);
assert_eq!(session.project(), &after);
assert_eq!(session.revision(), revision);
}
#[test]
fn layout_outside_projection_routes_keep_overrides_and_inactive_owner_positions() {
let mut f = fixture();
// Decomposition admits ordinary relations, including FunctionalExchange.
// Capabilities explicitly excludes those edges and their Function owners;
// retain their authored placements while arranging a real visible node.
let capability = f
.project
.add_element(
Kind::SystemCapability,
Layer::Sa,
"Visible capability",
None,
)
.unwrap();
let capability_node = f
.project
.place(f.diagram, capability, Position { x: 100., y: 100. });
f.project.diagrams.get_mut(&f.diagram).unwrap().projection = ProjectionKind::Capabilities;
let base = execute_batch(&f.project, &[Command::Layout(f.diagram)]).unwrap();
f.project = base.clone();
shift_all(&mut f.project, f.diagram, 40., 60.);
f.project.diagrams.get_mut(&f.diagram).unwrap().routes = vec![route(f.edge), route(f.external)];
validate_candidate(&f.project).unwrap();
let projected = projection::project_view(&f.project, f.diagram);
assert_eq!(projected.nodes.len(), 1);
assert_eq!(projected.nodes[0].id, capability_node);
assert!(
projected
.edges
.iter()
.all(|edge| edge.id != f.edge && edge.id != f.external)
);
let mut expected = f.project.clone();
expected.diagrams.get_mut(&f.diagram).unwrap().nodes.insert(
capability_node,
base.diagrams[&f.diagram].nodes[&capability_node].clone(),
);
let after = execute_batch(&f.project, &[Command::Layout(f.diagram)]).unwrap();
assert_ne!(
after.diagrams[&f.diagram].nodes[&capability_node].position,
f.project.diagrams[&f.diagram].nodes[&capability_node].position
);
assert_eq!(
after.diagrams[&f.diagram].routes,
vec![route(f.edge), route(f.external)]
);
assert_eq!(
after, expected,
"Owners excluded by the projection receive no Layout delta; their pins stay fixed"
);
}
#[test]
fn identical_layout_transaction_keeps_redo() {
let mut p = Project::empty();
let element = p
.add_element(Kind::Function, Layer::Sa, "Unplaced", None)
.unwrap();
let diagram = p.ensure_diagram(Layer::Sa);
let mut changed = p.elements[&element].clone();
changed.name = "Renamed unplaced".into();
let mut session = Session::new(p.clone());
session
.execute(Command::EditElement(element, changed))
.unwrap();
session.undo();
let revision = session.revision();
session
.execute(Command::Batch(vec![
Command::Layout(diagram),
Command::Layout(diagram),
]))
.unwrap();
assert_eq!(session.project(), &p);
assert_eq!(session.revision(), revision);
assert!(session.can_redo());
}
#[test]
fn layout_route_reset_alone_is_one_edit_and_repeated_layout_keeps_redo() {
let f = fixture();
let mut before = execute_batch(&f.project, &[Command::Layout(f.diagram)]).unwrap();
assert!(!before.diagrams[&f.diagram].nodes.is_empty());
before.diagrams.get_mut(&f.diagram).unwrap().routes = vec![route(f.edge), route(f.external)];
// Node geometry is already arranged. The complete expected Project differs
// only in the two independently named eligible route overrides.
let mut expected = before.clone();
for route in &mut expected.diagrams.get_mut(&f.diagram).unwrap().routes {
route.waypoints.clear();
route.label = None;
}
let mut session = Session::new(before.clone());
session.execute(Command::Layout(f.diagram)).unwrap();
assert_eq!(
session.project().diagrams[&f.diagram].nodes,
before.diagrams[&f.diagram].nodes
);
assert_eq!(session.project(), &expected);
assert_eq!(
session.revision(),
1,
"Clearing overrides is a real edit without any node movement"
);
session.execute(Command::Layout(f.diagram)).unwrap();
assert_eq!(session.project(), &expected);
assert_eq!(
session.revision(),
1,
"A repeated identical Layout adds no history"
);
session.undo();
assert_eq!(session.project(), &before);
assert!(
!session.can_undo(),
"The route-only reset was exactly one history entry"
);
session.redo();
assert_eq!(session.project(), &expected);
let mut renamed = expected.elements[&f.a].clone();
renamed.name = "Pending redo of rename".into();
session.execute(Command::EditElement(f.a, renamed)).unwrap();
let after_rename = session.project().clone();
session.undo();
assert_eq!(session.project(), &expected);
assert!(session.can_redo());
let revision = session.revision();
session.execute(Command::Layout(f.diagram)).unwrap();
assert_eq!(session.project(), &expected);
assert_eq!(session.revision(), revision);
assert!(
session.can_redo(),
"No-op Layout retains an unrelated undone edit"
);
session.redo();
assert_eq!(session.project(), &after_rename);
}
#[test]
fn old_models_get_empty_routes_and_route_json_is_strict() {
for bytes in [
+92
View File
@@ -704,6 +704,98 @@ fn moving_a_region_does_not_translate_pins_attached_to_a_stationary_state_bounda
assert_eq!(after.diagrams[&f.d].routes[0].waypoints, pins);
}
#[test]
fn layout_retains_hidden_state_boundary_pins_and_internal_routes() {
let mut f = fixture();
f.p.diagrams
.get_mut(&f.d)
.unwrap()
.view_options
.hidden_edges = vec![f.local];
let baseline = execute_batch(&f.p, &[Command::Layout(f.d)]).unwrap();
f.p = baseline.clone();
let entry = node(&f.p, f.d, f.entry);
let leaf = node(&f.p, f.d, f.leaf);
let boundary = node(&f.p, f.d, f.a);
for id in [entry, leaf] {
let n =
f.p.diagrams
.get_mut(&f.d)
.unwrap()
.nodes
.get_mut(&id)
.unwrap();
n.position.x += 10.;
n.position.y += 10.;
}
let hidden = EdgeRoute {
edge: f.local,
source_attachment: None,
target_attachment: None,
waypoints: vec![point(280., 390.), point(280., 470.)],
label: Some(EdgeLabel {
fraction: 0.4,
offset: point(3., -7.),
}),
};
let internal = EdgeRoute {
edge: f.internal,
source_attachment: None,
target_attachment: None,
waypoints: vec![point(320., 440.)],
label: Some(EdgeLabel {
fraction: 0.7,
offset: point(0., 0.),
}),
};
let visible = EdgeRoute {
edge: f.self_edge,
source_attachment: Some(EdgeAttachment {
side: PortAnchorSide::Left,
offset: 0.25,
}),
target_attachment: Some(EdgeAttachment {
side: PortAnchorSide::Right,
offset: 0.75,
}),
waypoints: vec![point(700., 100.)],
label: Some(EdgeLabel {
fraction: 0.6,
offset: point(0., 0.),
}),
};
f.p.diagrams.get_mut(&f.d).unwrap().routes =
vec![hidden.clone(), internal.clone(), visible.clone()];
let projected = project_view(&f.p, f.d);
assert!(
!projected
.edges
.iter()
.any(|e| e.id == f.local || e.id == f.internal)
);
assert!(projected.edges.iter().any(|e| e.id == f.self_edge));
let after = execute_batch(&f.p, &[Command::Layout(f.d)]).unwrap();
for id in [entry, leaf, boundary] {
assert_eq!(
after.diagrams[&f.d].nodes[&id].position,
baseline.diagrams[&f.d].nodes[&id].position
);
}
assert_eq!(
after.diagrams[&f.d].nodes[&boundary].position,
f.p.diagrams[&f.d].nodes[&boundary].position,
"The endpoint's owning State boundary stayed stationary"
);
let mut reset = visible;
reset.waypoints.clear();
reset.label = None;
assert_eq!(
after.diagrams[&f.d].routes,
vec![hidden, internal, reset],
"Equal entry/leaf deltas must not translate a pin on their stationary State boundary"
);
}
#[test]
fn initial_transition_attaches_to_the_compact_symbol_and_preview_keeps_settled_geometry() {
let mut f = fixture();
+4 -2
View File
@@ -310,7 +310,7 @@ fn article(topic: Topic) -> Article {
"Use the diagram selector or + View to work with a focused diagram. View properties edits its name, perspective, projection and Saved view visibility. Apply changes commits the view settings.",
"Choose Containment for semantic children, Architecture · allocated work for responsibilities, Functional dataflow for work and exchanges, or Missions & capabilities for outcomes. All relationships explores one layer; OA ↔ SA traceability can show placed elements from both layers and their correspondence lines alongside ordinary relationships.",
"Select an element and edit Semantic parent to change ownership. Use the Allocation palette tool to connect a performer to existing unallocated leaf work in Containment. The relationship can appear as a line there; Architecture · allocated work shows responsibility through nesting instead. Saved visibility filters can hide a line without removing the relationship.",
"Scroll to zoom and use Fit to frame the current view. In allocated-work views, dragging work changes its assignment and placement together. Choose Arrange next drag or press Alt+A to move work without changing responsibility for that one drag; Escape cancels the armed action. Auto-layout arranges the whole view. Export SVG produces a shareable view.",
"Scroll to zoom and use Fit to frame the current view. In allocated-work views, dragging work changes its assignment and placement together. Choose Arrange next drag or press Alt+A to move work without changing responsibility for that one drag; Escape cancels the armed action. Auto-layout arranges the view; see Route connections around the model for its bend and label reset rules. Export SVG produces a shareable view.",
"Select a diagram node to access Placement, Fold / unfold and Hide. With the canvas focused, Delete hides its placement; Shift+Delete asks to remove the model element. Other views retain a hidden element. Undo restores either change.",
],
),
@@ -357,7 +357,9 @@ fn article(topic: Topic) -> Article {
&[
"Select an architecture connection and drag a segment to add a bend. Right-click a line for Add bend here; right-click a circle for Remove bend. With the canvas focused, Insert adds a bend and Delete removes the selected bend. Drag the label independently. Escape cancels a gesture; Undo reverses one completed drag. Shift+F10 opens the selected diagram item's context menu; use arrow keys and Enter to choose an action.",
"Automatic orthogonal routing avoids cards and container headers where space permits. Amber connections need attention: move an obstructing element or a bend. In Select / move, drag a port to any border of its owner. Manual positions are per diagram; right-click Reset automatic position restores connection-aware placement. Ports remain attached to their actual owners. Manual routes are saved separately for each diagram and appear in source, API and SVG export.",
"Select a connection and drag a square endpoint handle along its element boundary to move its attachment in this view without changing what it connects. If handles overlap, right-click the selected connection or use its Actions menu and choose Move source attachment or Move target attachment, then drag the handle. Connections to semantic ports retain their port attachment; move the port instead. Reset source attachment or Reset target attachment restores that end's automatic placement while preserving the other end, pins and label. Reset routing clears all these route overrides in this view. Auto-layout preserves pins. Moving one endpoint keeps pins fixed; moving a container carrying both endpoints translates their pins. When folding promotes an endpoint, expand it again to resume and edit its saved route.",
"Select a connection and drag a square endpoint handle along its element boundary to move its attachment in this view without changing what it connects. If handles overlap, right-click the selected connection or use its Actions menu and choose Move source attachment or Move target attachment, then drag the handle. Connections to semantic ports retain their port attachment; move the port instead. Reset source attachment or Reset target attachment restores that end's automatic placement while preserving the other end, pins and label. Reset routing clears all these route overrides, including both attachments, in this view.",
"Auto-layout (Arrange elements) replaces saved bends and label positions on visible connections with their actual endpoints shown; attachment preferences and port anchors stay saved. Hidden or folded routes keep their overrides, though pins can move with their endpoint owners during arrangement. Undo restores the prior arrangement.",
"During ordinary movement, moving one endpoint keeps pins fixed; moving a container carrying both endpoints translates their pins. When folding promotes an endpoint, expand it again to resume and edit its saved route.",
],
),
explain(
+158
View File
@@ -1692,3 +1692,161 @@ fn adding_bend_between_close_ports_stays_outside_owner_clearance() {
}]
);
}
fn arrange_reset_fixture() -> (Project, Id, Id) {
let mut project = Project::empty();
let source = project.add(Kind::Activity, "Send report", 20., 70.);
let target = project.add(Kind::Activity, "Receive report", 650., 200.);
let edge = project.relate(RelationKind::Interaction, "Report", source, target);
let diagram = project.ensure_diagram(Layer::Oa);
project
.diagrams
.get_mut(&diagram)
.unwrap()
.routes
.push(EdgeRoute {
edge,
source_attachment: Some(EdgeAttachment {
side: PortAnchorSide::Bottom,
offset: 0.25,
}),
target_attachment: Some(EdgeAttachment {
side: PortAnchorSide::Top,
offset: 0.75,
}),
waypoints: vec![Position { x: 400., y: 500. }],
label: Some(EdgeLabel {
fraction: 0.25,
offset: Position { x: 11., y: 7. },
}),
});
(project, diagram, source)
}
#[test]
fn arrange_reset_document_preserves_multifile_comments_and_exact_undo_redo_reopen() {
use crate::document::Document;
use oa_core::source_project::{self, ProjectLimits};
let (project, diagram, _) = arrange_reset_fixture();
let mut bundle = source_project::format_bundle(&project);
assert!(
bundle.files.len() > 1,
"This must exercise a real include closure"
);
for (path, text) in &mut bundle.files {
*text = format!("// Keep exact authored module: {path}\r\n{text}");
}
let mut document = Document::from_bundle(bundle, ProjectLimits::default()).unwrap();
document.mark_saved(document.snapshot());
let before = document.snapshot();
let original_route = before.decoded.project.diagrams[&diagram].routes[0].clone();
document.execute(Command::Layout(diagram)).unwrap();
let after = document.snapshot();
let routes = &after.decoded.project.diagrams[&diagram].routes;
let mut expected_route = original_route;
expected_route.waypoints.clear();
expected_route.label = None;
assert_eq!(routes, &vec![expected_route]);
let mut outside = after.decoded.project.clone();
outside
.diagrams
.insert(diagram, before.decoded.project.diagrams[&diagram].clone());
assert_eq!(outside, before.decoded.project);
let affected: std::collections::BTreeSet<_> = std::iter::once(diagram)
.chain(
before.decoded.project.diagrams[&diagram]
.nodes
.keys()
.copied(),
)
.map(|id| document.compiled().index.declarations[&id].path.clone())
.collect();
for (path, text) in &before.bundle.files {
assert!(
after.bundle.files[path].contains(&format!("// Keep exact authored module: {path}"))
);
if !affected.contains(path) {
assert_eq!(
&after.bundle.files[path], text,
"Unrelated accepted bytes: {path}"
);
}
}
assert!(document.undo());
assert_eq!(document.bundle(), &before.bundle);
assert_eq!(document.project(), &before.decoded.project);
assert!(!document.dirty());
assert!(document.redo());
assert_eq!(document.bundle(), &after.bundle);
assert_eq!(document.project(), &after.decoded.project);
let reopened = Document::from_bundle(after.bundle.clone(), ProjectLimits::default()).unwrap();
assert_eq!(reopened.bundle(), &after.bundle);
assert_eq!(reopened.project(), &after.decoded.project);
}
#[test]
fn arrange_reset_pending_valid_property_has_separate_apply_and_layout_history() {
let (project, diagram, source) = arrange_reset_fixture();
let mut control = App::with_project(project.clone());
send(&mut control, Message::Select(source));
send(
&mut control,
Message::Field("/name".into(), "Revised sender".into()),
);
send(&mut control, Message::Apply);
let applied_project = control.project().clone();
let applied_bundle = control.document.bundle().clone();
assert_eq!(applied_project.elements[&source].name, "Revised sender");
assert_eq!(
applied_project.diagrams[&diagram].routes,
project.diagrams[&diagram].routes
);
let mut app = App::with_project(project);
let before_project = app.project().clone();
let before_bundle = app.document.bundle().clone();
send(&mut app, Message::Select(source));
send(
&mut app,
Message::Field("/name".into(), "Revised sender".into()),
);
send(&mut app, Message::Command(Command::Layout(diagram)));
assert_eq!(app.project().elements[&source].name, "Revised sender");
assert!(
app.project().diagrams[&diagram].routes[0]
.waypoints
.is_empty()
);
assert!(app.project().diagrams[&diagram].routes[0].label.is_none());
let arranged_project = app.project().clone();
let arranged_bundle = app.document.bundle().clone();
send(&mut app, Message::Undo);
assert_eq!(app.project(), &applied_project);
assert_eq!(app.document.bundle(), &applied_bundle);
send(&mut app, Message::Undo);
assert_eq!(app.project(), &before_project);
assert_eq!(app.document.bundle(), &before_bundle);
send(&mut app, Message::Redo);
assert_eq!(app.project(), &applied_project);
assert_eq!(app.document.bundle(), &applied_bundle);
send(&mut app, Message::Redo);
assert_eq!(app.project(), &arranged_project);
assert_eq!(app.document.bundle(), &arranged_bundle);
}
#[test]
fn arrange_reset_invalid_property_blocks_layout_without_losing_draft_or_accepted_bytes() {
let (project, diagram, _) = arrange_reset_fixture();
let node = *project.diagrams[&diagram].nodes.keys().next().unwrap();
let mut app = App::with_project(project);
send(&mut app, Message::Select(node));
let before_project = app.project().clone();
let before_bundle = app.document.bundle().clone();
let revision = app.document.revision();
send(&mut app, Message::Field("/position/x".into(), "-".into()));
send(&mut app, Message::Command(Command::Layout(diagram)));
assert_eq!(app.project(), &before_project);
assert_eq!(app.document.bundle(), &before_bundle);
assert_eq!(app.document.revision(), revision);
assert!(!app.document.can_undo());
assert_eq!(app.draft.as_ref().unwrap().inputs["/position/x"].raw, "-");
}
+49
View File
@@ -1255,6 +1255,55 @@ translates pins when both actual endpoint owners move by the same delta; label
offsets remain relative. Raw ``EditNode`` and source position edits are exact
record edits and do not implicitly translate pins.
Explicit ``Layout(diagram)`` (Auto-layout / Arrange elements) is an exception to
route-override preservation. Immediately before this operation changes geometry,
it MUST determine eligibility from the staged Project's ordinary filtered
projection. An edge is eligible only if it is visible, both actual endpoint-owner
placements exist, both projected owner nodes exist, and its projected source and
target equal those actual owners in stored orientation. A semantic Port belongs
to its actual owning placement; hiding its marker does not make the edge
ineligible. An endpoint promoted to a folded ancestor does. Missing owners MUST
NOT be inferred. In a Batch, this input includes preceding staged operations;
determining eligibility MUST NOT introduce separate validation of a batch prefix.
For each existing eligible route entry, Layout MUST clear ``waypoints`` and the
entire ``label`` override (both fraction and offset). It MUST retain the entry,
its list position, edge identity, and both attachment values, including dormant
attachments at semantic ports. Empty and attachments-only entries MUST NOT be
removed. Layout MUST NOT create route entries merely to record generated elbows
or automatic label placement. Node port-anchor records remain exact, although
moving or resizing their owners can change their physical positions.
Ineligible routes, including hidden, filtered and promoted edges, MUST NOT be
reset. Their labels, attachments, identities and list order remain exact. Their
waypoints follow the existing movement rule: translate only when both actual
endpoint-owner placements receive the same nonzero original-to-final delta,
subject also to the existing State boundary movement rule. For a State Entry/Exit
endpoint whose owning State boundary has a placement, that boundary placement
must move with the same delta before the stored pins translate. Otherwise retain
the waypoint values.
Promotion to a visible ancestor alone does not authorize translation. Eligibility
limits route reset, not placement scope: ordinary Layout may still reposition or
resize hidden and folded descendant placements. Other diagrams remain unchanged.
Placement changes and eligible resets form one atomic Layout mutation. A changed
standalone Layout creates one history entry; Layout within Batch or execute_many
shares the enclosing transaction's entry, even when that transaction contains
several Layout operations. Validate the complete candidate through the ordinary
transaction path. Rejection leaves the accepted Project and history unchanged;
an identical enclosing transaction creates no entry and preserves Redo. Undo
restores the pre-transaction placement and route records. A separately applied
desktop property draft retains its own history entry; Layout does not merge it.
This exception does not change ordinary dragging, group movement, reconnect,
folding, filtering, raw ``EditNode`` or source position edits. Reset routing
remains the distinct whole-entry removal operation, including both attachments.
Internal State transitions have no external edge eligible for reset. Process
generated topology is unchanged; saved Scenario layout retains its dedicated
operation and rejects this Layout command. Automatic elbows remain derived;
resetting constraints does not guarantee an intersection-free or collision-free
arrangement.
This is an additive schema-3 field. Earlier schema-3 JSON/source that omits
``routes`` loads as an empty list. Frozen schema-1 and schema-2 readers do not
accept this new field; migration creates empty route lists. Source projects keep
+23
View File
@@ -498,3 +498,26 @@ editing remains separate from semantic reconnect. The [decision and evidence
map](edge-attachments-20260922.md) records alternatives, lifecycle rules,
independent falsifiers, evaluators and the Process/Scenario applicability limits.
This is branch-specific work; it does not establish an integrated release.
## Explicit Arrange resets visible authored geometry — 2026-09-25
Branch-specific candidate for issue #32, based on released715036e; not an integrated
release checkpoint. Explicit Layout clears pins and the complete label override
for existing projected routes whose displayed owners equal their actual placed
owners. Freeze that eligibility before geometry changes, including at the operation's
position in a Batch. Preserve route entries, order, attachments and semantic/manual
port choices. Preserve existing equal-actual-owner translation for ineligible
routes and retain ordinary movement behavior. Keep whole Reset routing distinct.
This narrowly amends the former pin-preserving Layout contract so an explicit
arrangement can recover automatic route/label placement without deleting endpoint
choices. Clearing the whole label record avoids retaining a stale authored fraction;
it does not guarantee better geometry. The routing and node-placement algorithms
remain unchanged. More aggressive experimental routing candidates were not adopted
because independent scenes exposed tradeoffs and regressions.
[Results and evidence](experiments/arrange-reset-20260925/results.md) bind the
contract, source/history tests, strict Clippy and expanded/folded native save/reopen
acceptance to their actual code pins. Residual crossing/layout quality and viewport/
guide discoverability remain outside this bounded acceptance. Gitea owns live
publication and integration status; this record does not close issue #32.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,15 @@
# Supplemental export checkpoint: source review
Prepared `.local/arrange-reset-export-checkpoint-20260925.py`, SHA256 `5bcd8a568d5318dd62217c1e0b35538b22e34403c9727eee8814a5d0ec5a899f`. AST parsing passed; script/oracle not executed. This is an author source check, not independent runtime acceptance.
Example after the parent observes JSON export to a new destination:
```text
python .local/arrange-reset-export-checkpoint-20260925.py --root TRIAL --actual TRIAL/working/expanded-after.json --name expanded-after-export --case expanded --expected after
```
Use fresh names/files for Undo (`--expected original`) and Redo (`--expected after`). The script requires an existing JSON directly inside the admitted working directory and refuses the two original imported filenames. It does not create an export or establish when/how that file was created; the parent's GUI observation supplies that evidence.
It loads only the exact reviewed staged common helper, then reuses admitted stage/source/package/baseline checks. The supplemental script is explicitly **not stage-admitted**: its own exact file identity appears in the checkpoint receipt and oracle invocation bindings. It snapshots the export without rewriting it, invokes the preserved same-candidate `--compare` oracle, and checks the export, entire working inventory, supplemental script and admitted inputs afterward. Fresh checkpoint directories and receipts preserve attempts; failure quarantines the trial through the existing sentinel convention. No staged tool, manifest, original trial file or source checkout is changed.
An accepted receipt proves complete typed Project equality for the captured JSON. It does not establish native canonical-project Save, clean-document state, UI action cause, source-bundle byte equality or fresh-process reopening. JSON Save As is export-only here; parent must separately observe canonical Save and reopening. The earlier refused overwrite remains a recorded UI observation, not a successful save.
@@ -0,0 +1,34 @@
# Arrange reset native outcome independent review
**Accept the bounded expanded/folded Arrange-reset workflow demonstrated here. No material blocker found in the reviewed outcome.** This is Windows native wiring, preservation, history, source-save and reopen evidence at `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed`, not full issue32 layout-quality acceptance or J/L router adoption.
Reviewed all18 observation JSON notes and independently viewed all18 corresponding full-window PNGs with view_image. Read all eight checkpoint receipts/oracle outputs and both final close receipts. No GUI action, compiler, model helper or checkpoint rerun by reviewer.
## Model and lifecycle evidence
Each case has after/Undo/Redo/reopened exports: eight checkpoints accepted, failure null, immutable inputs unchanged, oracle exit0 and complete_project_equal=true. Independently read the captured exported JSON and expected baseline files: all eight pairs are byte-identical, stronger than the reported typed-model equality for these particular artifacts. Verified snapshot/expected/supplemental-script/comparison-receipt hashes. Expected after-Projects remain candidate-derived UI-wiring oracles; independent core tests establish the reset contract separately.
Additional independent model inspection confirms expanded Verified report resets its two pins `(610,210),(610,420)` and full label override; folded Verified report retains both pins and label fraction0.5/offset(0,-32). Both cases retain route entry order, attachments, semantic port anchor records, semantic elements/relations and the entire Review sentinel diagram. Filtered edge's pin `(1020,270)` remains exact in this fixture; this is not a promise that all filtered routes remain at fixed world coordinates. Original working JSON imports remain byte-identical to immutable originals.
Run01 PID7812 and run02 PID4328 both have final close receipts: exit0, failure null, observed exit, immutable inputs unchanged and empty stdout/stderr. Parent observations establish normal AltF4 causes; receipts establish actual exits. Run02 close is now complete, not merely window disappearance. Both saved source projects were reopened in fresh process run02 after run01 closure, then exported and compared against the same frozen after-models.
## Native observations
Expanded before/Undo images show two authored circular handles and manual label; arranged/Redo images show the selected route without those handles while automatic elbows remain. Folded images preserve collapsed presentation and visible promoted exchanges. Source-save images report all source files saved; reopen images report source project opened and retain expanded/folded state. Both sentinel-view images show retained route/label geometry, including the selected pin in expanded sentinel. Whole-model comparison verifies hidden fields that screenshots alone cannot.
The arranged view required Fit and displays71% zoom; Undo retained viewport zoom, so folded Archive partly leaves the viewport until refit. These are recorded viewing limitations, not model-history failures. Guide card13 is readable and correctly distinguishes explicit Layout's visible actual-endpoint pin/label reset from full Reset routing, retained port/attachment choices, dormant hidden/folded overrides and Undo. Search filters topics but required six scrolls to the relevant card; retain this existing discoverability limitation.
## Protocol deviation and adapter incidents
The original plan assumed overwriteable JSON checkpoints. Observation03 visibly reports that the JSON destination already exists and asks for a new file. The original remained intact. The actual workflow instead used fresh JSON exports for model checkpoints, then saved expanded and folded multi-file source projects in separate locations and reopened those sources. Export did not mark source saved or consume model history; source Save supplied the persistence/reopen evidence. This is a justified workflow adjustment, not a falsely reported successful overwrite.
The supplemental export checker is explicitly marked not stage-admitted in its receipts, with its own hash and narrow equality scope; original staged tools remained the existing set. Preserve that provenance rather than describing it as original staging. A rejected click targeted a16×16 icon screenshot; full-window screenshot1 corrected targeting. Short filename typing/set_value failures and successful absolute-path typing are adapter interaction observations, not demonstrated application defects. None authorizes editing expected model bytes; no such repair is evidenced.
## Binding and limits
Native manifest SHA256 `eb208a3a1fa0843be4087e1797d109dc0e3dcddb3255f3ad9f536f3d98292b65`.
Recorded GUI SHA256 `133375d57bdecf3f3effe5ecc13ec121e1257b90838f2cdb64fb91aa9a530d1b`; oracle `0792aff4cf8460c58fa2bf2f5ddbfa478523b7c9fe9b45e19ec93090191c37c1`.
Supplemental checker `5bcd8a568d5318dd62217c1e0b35538b22e34403c9727eee8814a5d0ec5a899f`.
Run01 close SHA256 `ffe8a3cab307ecc4ad45caf027787efdb6e41449fb304fc2cadd242a7fb5daf8`; run02 `6abce58644e8bf2c85e133aa479f44d66eeeb0a6d59361afb9db1d996b4ed745`.
No claim of general crossing/label-aware arrangement quality, arbitrary State-boundary native behavior, Process/Scenario changes, performance, Linux, full workspace/regression-suite completion, or remote publication. This trial saves newly generated multi-file sources; original authored multi-file comment/draft preservation is separately covered by desktop tests, not inferred from this UI sequence. Remote disk-full blockage still separates local acceptance from delivery.
@@ -0,0 +1,61 @@
# Explicit Arrange route reset — branch evidence
**Bounded reset candidate verified on Windows; local-only pending publication.** Branch `codex/arrange-reset-ytr83r-20260925` is based on released `715036e132677dd2bc2fb37ed316d93dc9987aec`. Final native/build/Clippy source is `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed`. This result covers the reset capability within issue #32, not a replacement node-layout algorithm or completion of intersection/label-aware automatic arrangement. No routing algorithm change is included.
Publication remains blocked by the [recorded remote disk-space failure](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/32#issuecomment-1330). This branch/evidence is local-only; no successful push, PR, merge or release is claimed. Existing published prereleases are unaffected.
## Behavior
Explicit `Command::Layout` freezes reset eligibility from the staged pre-operation filtered projection. It clears existing pins and the complete label override only for projected edges whose displayed endpoint owners equal both actual placed owners and whose projected owner nodes exist. It retains route entries/order, edge identities, both endpoint attachments, semantic ports and manual port anchors. Ordinary placement movement and the routing engine are unchanged.
Filtered, promoted, missing-owner and outside-projection routes are not reset. Retained pins follow the existing actual-owner equal-delta translation rule, including the State boundary exception; a retained record is not a promise of fixed world coordinates. Projection-excluded owners receive no movement when outside the existing Layout placement scope. Other views remain exact. Whole **Reset routing** remains a distinct operation that removes all overrides. Documentation now describes this deliberate behavior amendment instead of promising that Auto-layout always preserves pins.
Standalone changes form one history entry, including a route-only reset when nodes are already arranged. Nested Layout participates in its enclosing Batch's final validation and history. No-op Layout preserves Redo. A valid pending property draft remains a separate Apply before Layout; an invalid draft blocks Layout without losing its input.
## Recorded checks
All successful receipts below record exit code 0, unchanged pinned source, and no runner failure. Environment: local Windows `x86_64-pc-windows-gnullvm`, frozen offline toolchain, one Cargo build job.
| Source | Check | Actual result |
| --- | --- | --- |
| `6c44a2d6a16d8c80320d35a9c8d2eee3b77e8047` | Core library + `release_routing`, `state_diagrams`, `routing_geometry`, `routing_labels`, `route_source`, `group_movement` | **195 passed**, 0 failed/ignored across seven harnesses |
| `6c44a2d6a16d8c80320d35a9c8d2eee3b77e8047` | `cargo clippy --locked --offline -p oa-core -p oa-desktop --all-targets -- -D warnings` | Passed |
| `9eb4642e50564165aa0db38d8e91f4c14b818bdc` | `cargo test --locked --offline -p oa-desktop arrange_reset_` | **3 passed**, 1535 filtered out; not a full desktop-suite run |
| `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed` | `cargo build --locked --offline -p oa-core --example arrange_reset_native_probe` | Passed |
| `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed` | `cargo build --locked --offline -p oa-desktop --bin arcadia-studio` | Passed |
| `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed` | Strict core/desktop all-target Clippy command above | Passed |
| `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed` | Native expanded/folded Arrange, Undo, Redo, source-save and fresh-process reopen | Eight complete-Project comparisons passed; two normal closes, exit 0, empty stdout/stderr |
The three desktop tests cover exact multi-file comment/source preservation through Undo/Redo/reopen, an invalid property draft blocking Layout, and separate valid-property Apply/Layout history. Core cases include explicit reset-only change history, preserved attachments/anchors and empty entries, filtered equal/different-owner deltas, folded actual-owner versus displayed-owner movement, missing owners, excluded projections, State boundary retention, temporary invalid Batch prefix repaired before final validation, and no-op Redo.
The difference from the core-test source `6c44a2d6` to `d6f45a13` is the added native oracle example and desktop history tests. The production reset implementation, production routing and node arrangement did not change in that interval. Each gate is reported at its actual source pin; the 195-test run was not repeated against `d6f45a13`.
Receipts, raw logs and before/after source inventories for all eight gates, including both failures, are preserved in the accompanying evidence archive under `gates/`. Commands run from the identified commit's repository root. Environment was Rust 1.98.1, Windows gnullvm, LLVM-MinGW 20260908 UCRT, dev builds and one Cargo job. `--offline` requires dependencies already cached. The core command is:
```text
cargo test --locked --offline -p oa-core --lib --test release_routing --test state_diagrams --test routing_geometry --test routing_labels --test route_source --test group_movement
```
Preserved failures: `01-core-contracts` at `f0d64b531addcea584083e35296d1e5a4cb1fa8d` failed compilation with E0507 because a test moved `Option<EdgeLabel>` through a shared reference; the assertion now clones it. `02-core-contracts-fixed` at `3dfbe1959d8b5717bf8b8c1a76f31afc5ca7072a` compiled, then stopped with 19/20 routing tests passing: the new fixture incorrectly assumed Decomposition excludes FunctionalExchanges. It now uses Capabilities, includes one genuinely visible capability and retains inactive Function placements, with full independently cloned expected Project equality. No production behavior was weakened to accommodate that fixture. The corrected complete selected run is the 195-pass result above.
## Native acceptance
The native fixture supplies expanded/folded originals, two authored visible pins and a label override, fixed semantic anchors, dormant attachments, a filtered-route sentinel and an independent second view. Its expected after-Projects are produced by the candidate `Command::Layout`: they verify UI wiring/save/history against the candidate, not independently prove geometric quality. Independent contract tests and source review supply separate checks. Generate a fresh fixture directory with `arrange_reset_native_probe NEW_DIRECTORY`; compare saved JSON using `arrange_reset_native_probe --compare ACTUAL EXPECTED`. Build commands are above. Run the desktop executable with its matching LLVM-MinGW `libunwind.dll`, open the generated original JSON, and use fresh destinations for every JSON export.
Executed for expanded and folded cases: Open → Arrange → export/compare expected → Undo/export/compare original → Redo/export/compare expected → save source project. After normal process closure, a fresh process reopened each saved source project, inspected its main and sentinel views, and exported another exact comparison. All eight typed-Project comparisons passed; an independent reviewer also confirmed byte equality for these particular JSON artifact pairs. Original imports remained intact. Saved source projects are multi-file bundles, not byte-identical to the generator's single canonical source file; their fresh-process model comparisons establish semantic persistence. Desktop tests separately cover authored multi-file comments and exact source history.
Protocol correction: the initial plan assumed JSON Save overwrites the opened file. The app instead imports JSON without adopting its path; JSON Save As exports to a new destination and refuses an existing destination. The first overwrite attempt was visibly refused and preserved. Subsequent checkpoints used new JSON exports, then actual source Save for clean closure and reopen. The original staged checkpoint helper was not used. A reviewed supplemental checker, explicitly recorded as not stage-admitted, snapshots exports and invokes exact production decoding/equality without mutation. Its SHA256 is `5bcd8a568d5318dd62217c1e0b35538b22e34403c9727eee8814a5d0ec5a899f`.
The launcher admission review found and corrected a package-inventory gap before staging. Captured GUI SHA256 is `133375d57bdecf3f3effe5ecc13ec121e1257b90838f2cdb64fb91aa9a530d1b`; oracle SHA256 is `0792aff4cf8460c58fa2bf2f5ddbfa478523b7c9fe9b45e19ec93090191c37c1`. Both came from reviewed same-pin builds under exclusive local compiler/target ownership and immediate stage capture. Ordinary Cargo receipts do not hash executables at compiler exit; the capture attribution depends on that ownership observation. The evidence archive excludes executables/private profiles but retains their manifests and inventories.
All 18 full-window native screenshots and eight comparisons received independent review. Both GUI processes closed normally, with exit 0 and empty logs. At maximized 1605×872 capture size, selected pins disappeared/reappeared with Arrange/Undo, folded metadata survived, and the guide correctly explained the narrower reset versus whole Reset routing. An icon-sized screenshot was initially selected by the automation helper; its rejected click was corrected using the full-window screenshot. Short filename/set-value helper failures were resolved by absolute-path typing. These observations do not establish application input defects or resolution of the separate restored-window issue.
Material existing usability limits: Arrange requires Fit to bring the wider arrangement into view; Undo retains the viewport and can clip earlier placements until refit. Guide search filters a topic but required six scroll actions to reach the routing card. Automatic shared contacts/crossings remain. These are follow-up work, not evidence of complete issue #32 acceptance.
No full-workspace regression, performance, Linux, general layout-quality, arbitrary State-boundary native or Process/Scenario acceptance is claimed. No source reset rule promises fewer automatic elbows, universal nonintersection or readable placement by itself. Linux remains excluded by the user. Live ownership and delivery status remain in Gitea; this document records branch-specific evidence, not an integrated-state checkpoint.
## Evidence navigation
[Source review](source-review.md), [native acceptance review](native-review.md), [staging review](tooling-review.md), [supplemental checker review](export-checker-review.md). Historical plans and rejected attempts are retained as historical inputs in the archive; the normative implemented contract is in `docs/language.rst` at the tested pin.
[Archive](evidence/arrange-reset-d6f45a13.zip) and [member manifest](evidence/manifest.json): 221 members, 6529778 bytes, SHA256 `219c6dde234f1f30c4af191a538c21eacf874f234af0b8506bb66251db412650`. Every member was reread and verified by length, SHA256 and CRC. Raw evidence retains original machine paths for provenance; peers should use the commit, repository-relative paths and commands above. Native binaries/private profiles are excluded.
@@ -0,0 +1,52 @@
# Arrange reset candidate source review
Reviewed exact commit `3923860ad2923ad2bcae93b2f0c3ccbcf02d7d75` in `.local/arrange-reset-20260925` against released `715036e132677dd2bc2fb37ed316d93dc9987aec`. Diff is confined to session.rs, release_routing.rs and state_diagrams.rs. No compiler/test execution, router adoption or source changes by reviewer.
No material implementation blocker found in this bounded core reset candidate. Approval here means ready for parent compilation/verification, not accepted product behavior or successful tests.
## Implementation
session::layout checks diagram existence and Scenario rejection before freezing the ordinary projected view. Eligibility requires actual endpoint-placement IDs to equal projected source/target and both projected owner nodes present. This excludes hidden, missing-owner, outside-projection and promoted edges without routing to infer suspension. The same input view is reused for root topology ordering; no post-layout eligibility recomputation occurs.
The existing node sizing/placement and translated_routes calls remain intact. Translation uses original stored positions and actual-owner deltas with State boundary_moves_with unchanged. Only afterward, existing eligible entries have waypoints cleared and label=None. No entry insertion/removal/reordering, attachment mutation, semantic edit, other-view mutation, new routing call or generated elbow persistence is introduced. Temporarily translating an eligible pin before clearing is redundant work, but leaves the specified final state and is not a correctness defect.
Batch execution still determines staged input and final validation/history through the existing session path; no new prefix validation or nested history entry. Borrow lifetimes are coherent: immutable Project/Diagram/view work precedes the existing final mutable diagram borrow. Added owner lookup is fallible and skips absent owners. No new indexing/unwrap panic path found in the added code. Existing layout assumptions about malformed transient candidates are unchanged, not newly certified as universally panic-free.
## Test-oracle assessment
The modified preservation test narrows the intentionally changed expectation to visible pins/labels, while still checking route count/order, other view and Undo. Additional tests cover full exact route vectors including attachments-only/empty entries, fraction-only label reset, port anchors, semantic records, canonical source roundtrip, JSON Layout command parity, Undo/Redo and one-entry history.
Filtered/missing-owner test explicitly identifies one eligible edge and uses authored40/60 offsets plus a7-unit unequal owner offset; folded test adds a13-unit visible-container offset distinct from hidden children. Their expected translations are computed arithmetically by a local helper, not by the production reset/translation helper. Baseline Layout supplies stable node geometry, but named eligibility and sentinel-route expectations remain independent and nonvacuous.
The staged Batch test introduces a duplicate alias, hides routes, runs Layout and repairs the alias afterward. This meaningfully detects accidental prefix validation and stale pre-batch visibility. Its failure branch checks model/revision rollback. The no-op test confirms retained Redo for an empty-view enclosing Batch. State test exercises equal entry/leaf displacement with a stationary owning boundary and retains internal-route metadata; visible external self-route clears overrides while preserving authored attachments.
Existing State `layout_reserves_internal_rows_and_state_routes_retain_hard_pins` checks router preservation BEFORE Layout and internal-row sizing afterward. It does not contradict the new reset policy, despite its broad name; no need to erase ordinary router pin-preservation coverage.
## Limits for parent verification/integration
- No new explicit hidden semantic-port-marker case; anchor preservation is covered, marker-hidden eligibility remains source reasoning pending focused verification.
- No nonempty already-arranged diagram proving route-reset-only change/history, or a second Layout no-op on that same diagram. Current empty-view no-op test does not establish that particular scenario. A focused regression is useful if the parent expands verification, but the present reset loop visibly handles it.
- Canonical language parse/format and JSON command parity are core evidence only. They do not prove HTTP transaction handling, desktop pending-draft history, multi-file comments/raw-byte Undo/Redo or native workflows. Those remain separate integration gates rather than grounds for claiming this candidate complete.
- Eligibility tests use explicit hidden_edges and Decomposition/folding; not every saved category/coverage/filter combination is individually exercised. The implementation correctly reuses the ordinary projection, so no separate filter policy is introduced.
Keep ordinary movement, raw source edits, folded suspension and RemoveEdgeRoute tests unchanged except where the deliberate explicit Layout amendment applies. Documentation must carry the reviewed staged-Batch, enclosing-history and hidden-placement movement qualifications. Actual build/test outcomes remain pending and must be recorded independently.
## Desktop tests and native fixture follow-up at9eb4642e
Reviewed exact `9eb4642e50564165aa0db38d8e91f4c14b818bdc` above `6c44a2d6`, plus the native plan. No new source blocker found. Core production changes since3923860 are formatting only. No builds/tests/native actions executed by reviewer; parent-reported core195 pass and Clippy results remain separate receipts, and desktop tests were in progress at assignment.
Three desktop tests are meaningful at their actual layers:
- Document test creates a real include closure, inserts authored CRLF comment lines, independently clears the expected route's pins/label while retaining attachments, compares all non-target-diagram model state, preserves unrelated module bytes, and checks exact accepted bundle/model Undo/Redo plus Document reconstruction. This closes much of the earlier multi-file source evidence gap. Reconstructing Document from the bundle is not native disk-save/fresh-process reopening.
- Valid pending-property test uses ordinary Select/Field/Apply as its control, explicitly checks the revised name and original routes, then proves Layout's separate history entry with complete Project and bundle comparisons through two Undo/Redo steps. This is an App message-wiring/history test, not mounted pointer/keyboard UI evidence or an independent layout-geometry oracle.
- Invalid property test feeds incomplete numeric input, verifies accepted Project/bundle/revision/history unchanged after Layout, and retains exact raw draft. It does not simulate every invalid source draft, which is correctly outside this narrow scope.
The added nonempty already-arranged route-only core test independently names the two routes, asserts unchanged nodes and complete expected Project, one real edit, repeated no-op, Undo/Redo and retained unrelated Redo. It addresses the prior route-only history coverage gap.
Correction to earlier fixture reasoning: Decomposition admits FunctionalExchange, so it was not a valid excluded-projection sentinel. The corrected Capabilities test includes one visible capability, asserts real movement there, and exact retained inactive Function placements/routes. `projection::parents` filters out placements whose kinds are excluded by the projection; folded/filtered descendants within the admitted placement domain may still move, but arbitrary outside-projection owners receive no delta. Earlier broad wording about all stored d.nodes must be read with this admitted-domain qualification. The corrected test has a concrete oracle rather than weakening assertions to accommodate observed output.
Native fixture SHA256 `2781d2915d7fd4e428be119c36b8ffde5105e48e76f6e8f7843069f69e5899cf`; desktop tests file `935d09da577b5e2c7e4b65329e0d89010bde23b0199ad3072f82f99b5e060d26`. Original five-Function geometry is hand-authored; IDs and placement aliases are made explicit, port IDs are deterministic, and fractional witnesses are binary-exact. Both expanded/folded originals contain a filtered route and an entire other-view route sentinel. Expected after-Projects are intentionally generated by candidate Layout and asserted to preserve semantic records/other view. This is a full-model UI wiring/history oracle, not independent algorithm correctness; the source/plan label that limitation correctly. Full JSON and canonical source forms roundtrip through production parsers before writing a fresh directory.
The native plan separates immutable fixtures from writable originals, compares complete saved models, includes Undo/save/Redo/save and fresh-process reopening, and preserves normal close/log evidence. It correctly distinguishes generated elbows from authored pins and does not infer reset from fewer corners. Hidden/promoted route translation is checked against the frozen candidate expected model; independent core tests supply the separate translation contract oracle. Any inspection-only expansion must be a recorded edit outside the accepted checkpoint. No native outcome has yet been reviewed or asserted. GUI artifact/source hashes and fixture hashes must bind the eventual run; source-generated expected files alone are not acceptance.
These changes are local-only while remote publication is blocked; no pushed-code or peer-availability claim is made in this review.
@@ -0,0 +1,24 @@
# Arrange reset native tooling review
Source reviewed at `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed`; evaluator SHA256 `6a691d6605dd1da0d862ffa3da0cfca2f83547e47b26f9cb648b83061f8f7a39`. Static review plus AST parsing and isolated inventory dictionary checks only. No build, fixture/oracle execution, GUI, source edit or native acceptance.
**Found and corrected one prelaunch admission gap.** The copied launcher originally captured a new package inventory at launch and compared only against that baseline. It would accept an extra file added after stage, although a later checkpoint would reject it. With parent authorization, the draft launcher now converts the admitted stage inventory to its local row shape and requires exact equality before any process launch. Existing close verification preserves that same set. `common.py::LAUNCHER_SHA` and README are updated. Original `.local/state-label-native-20260924/launch.py` remains hash-identical. AST parsing of all four scripts and isolated equal/extra-file comparisons passed; no filesystem trial was performed.
No remaining blocker found within the declared evidence contract:
- Stage binds matching successful dev build/source/environment receipts, preserves exact GUI/oracle/runtime bytes, checks source before/after generation, and freezes package/baseline/tool inventories. It expressly depends on exclusive compiler/target ownership between build completion and capture: these Cargo receipts do not themselves hash emitted binaries. Parent must retain that ownership; this review does not upgrade that attribution to CargoJSON artifact provenance.
- `--compare` decodes actual and expected via production persistence and compares complete typed Projects. Inequality/decode failure returns nonzero; it does not call Layout, routing or write input files. This avoids the generic-JSON float representation problem. Generation labels its expected Project as candidate-Layout-derived, so it tests GUI/save wiring, not independent arrangement quality.
- Checkpoint snapshots one explicitly named working JSON, verifies exact captured input and expected/oracle/runtime bindings before/after comparison, and requires unchanged current working bytes, full source and immutable inventory. Its accepted flag asserts saved Project equality only. Source-bundle/comment restoration remains covered by separate desktop tests, not this single-JSON checkpoint.
- Launcher records PID, argv, environment, private profile/cwd, working/package inventories and observed owned-process exit. Empty logs/exit0 cannot prove how it closed or what the user clicked. Parent observations must separately bind actual Open, Arrange, Save, Undo/Redo, reopening and normal-close actions to the correct process/case/checkpoint.
Failure evidence remains in fresh directories; stage/checkpoint failures quarantine the trial. Launch observer failures preserve their receipts and do not become a successful close. Parent must reject failed/missing process receipts rather than infer acceptance from a later model checkpoint. External supervision remains responsible for a stuck GUI/oracle; the scripts intentionally do not implement automated UI or automatic GUI termination.
Reviewed draft identities after correction:
| File | SHA256 |
| --- | --- |
| common.py | `1da64294c8b2380861a4fe2d715f18563a1a0bba1317572e4c3d0a516f04132d` |
| stage.py | `2185bc1b20eee1eea3e1e4a7527318be14b76d81c3c16e67eff7104083c06d39` |
| checkpoint.py | `d8b21c75c2c8f1d29f2a730f3125992575038a1353b87d967d53edd1d5d9caf0` |
| launch.py | `9f25579451895334ca4c3c80137c4a47cd4256c720740fea70b6c6dc664255a7` |
| README.md | `920c674e65ea6f1533e7f15fd9b7ec7ad1b501e7561561a7df93b92e91022409` |