Save unverified allocation and repair work at user-requested pause

This commit is contained in:
2026-09-12 04:01:44 -07:00
parent bc8bc4f5a1
commit 6b9cc77ece
10 changed files with 558 additions and 29 deletions
@@ -1029,6 +1029,91 @@ fn target_presentation_must_name_the_destination_and_its_visible_frozen_header()
));
}
#[test]
fn frozen_performer_label_refuses_overlapping_work_header_and_allows_nearby_exact_move() {
for layer in [Layer::Oa, Layer::Sa] {
let mut f = fixture(layer);
f.p.elements.get_mut(&f.a).unwrap().name = "Receiver".into();
let plan = semantic(&f, Destination::Performer(f.a));
let mut context = presentation(&f.p, f.d, Some(f.a));
// The actual frozen renderer bounds for Receiver at (100, 100).
context.target.as_mut().unwrap().label = Rect {
position: xy(110., 131.),
size: Size { width: 88., height: 22. },
};
let label = context.target.unwrap().label;
let counterexample = Rect {
position: xy(120., 110.),
size: Size { width: 224., height: 62. },
};
assert!(context.viewport.contains(label));
assert!(context.viewport.contains(counterexample));
assert!(label.contains(Rect {
position: xy(134., 145.),
size: Size { width: 1., height: 1. },
}), "the formerly admitted work name begins underneath the label");
let before = plan.project().clone();
assert!(matches!(
place(&plan, xy(120., 105.), context),
Err(Refusal::LayoutRequired(message)) if message.contains("Aim below or beside")
));
assert_eq!(plan.project(), &before);
// A 50-unit lower pointer position clears the frozen label. The planner
// preserves the user's snapped position instead of packing either card.
let placed = place(&plan, xy(120., 155.), context).unwrap();
assert_eq!(placed.diagram.nodes[&f.n].position, xy(120., 160.));
let after = roundtrip(&f.p, finish(&plan, &f.p, &placed).unwrap().unwrap());
assert_eq!(validation::effective_performer(&after, f.w), Some(f.a));
assert_eq!(after.diagrams[&f.d].nodes[&f.n].position, xy(120., 160.));
assert_eq!(after.elements, f.p.elements);
assert_eq!(after.diagrams[&f.second], f.p.diagrams[&f.second]);
}
}
#[test]
fn frozen_label_readability_allows_touching_edges_but_refuses_positive_area_overlap() {
let f = fixture(Layer::Oa);
let plan = semantic(&f, Destination::Performer(f.a));
let mut context = presentation(&f.p, f.d, Some(f.a));
context.target.as_mut().unwrap().label = Rect {
position: xy(110., 131.),
size: Size { width: 90., height: 19. },
};
for (position, allowed) in [
(xy(190., 110.), false),
(xy(200., 110.), true),
(xy(210., 110.), true),
(xy(120., 140.), false),
(xy(120., 150.), true),
(xy(120., 160.), true),
] {
let result = place(&plan, position, context);
assert_eq!(result.is_ok(), allowed, "{position:?}: {result:?}");
if let Ok(placed) = result {
assert_eq!(placed.diagram.nodes[&f.n].position, position);
}
}
}
#[test]
fn same_performer_readability_preserves_snapped_noop_and_allocation_identity() {
for layer in [Layer::Oa, Layer::Sa] {
let mut f = fixture(layer);
let a = f.a;
allocation(&mut f, a);
f.p.diagrams.get_mut(&f.d).unwrap().nodes.get_mut(&f.n).unwrap().position = xy(150., 200.);
let plan = semantic(&f, Destination::Performer(f.a));
let context = presentation(&f.p, f.d, Some(f.a));
assert_eq!(plan.project(), &f.p);
let noop = place(&plan, xy(154., 200.), context).unwrap();
assert!(finish(&plan, &f.p, &noop).unwrap().is_none());
let moved = place(&plan, xy(160., 210.), context).unwrap();
let after = roundtrip(&f.p, finish(&plan, &f.p, &moved).unwrap().unwrap());
assert_eq!(after.relations, f.p.relations);
}
}
#[test]
fn geometry_refuses_new_overlap_bounds_and_invisible_headers_and_preserves_existing_overlap() {
let mut f = fixture(Layer::Oa);