A failed MCP batch goes back to where it started however long the undo history is: rollback undid as many steps as the undo history had grown, but the history is capped at 200 steps, so once it was full a failed batch reported undone: 0 and left its sketches behind (an empty Sketch1, two sketches named TapHole). New commands edit.checkpoint and edit.restore_checkpoint (forget: true drops one) remember and restore the design, the active sketch and the undo history; batch takes a checkpoint before its first step, restores it on a failure and forgets it on success. Against an app without checkpoints batch falls back to counting undo steps

This commit is contained in:
CypherNaugh_0x authored and bherbruck committed 2026-10-10 17:21:25 -04:00
1 parent 54a86dc6f5
commit ab23fc9906
6 files changed
+195 -13

No files matched your search

+29
View File
@@ -855,6 +855,35 @@ fn deleting_a_path_sketch_deletes_its_pipe() {
assert!(s.model.results.iter().all(|r| r.error.is_none()));
}
/// Regression: batches were rolled back by undoing as many steps as the undo history grew,
/// which is none once the history is full, leaving their sketches behind.
#[test]
fn checkpoint_restores_with_a_full_undo_history() {
let mut s = Session::default();
for i in 0..MAX_UNDO + 5 {
run(&mut s, "parameters.add", json!({"name": format!("p{i}"), "expression": "1 mm"}));
}
assert_eq!(s.undo.len(), MAX_UNDO);
let labels: Vec<String> = s.undo.iter().map(|u| u.label.clone()).collect();
let doc = s.doc.clone();
let id = run(&mut s, "edit.checkpoint", json!({}))["checkpoint"].as_u64().unwrap();
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "TapHole"}));
run(&mut s, "sketch.circle.center", json!({"center": [0, 0], "radius": 2}));
assert!(s.active_sketch.is_some());
run(&mut s, "edit.restore_checkpoint", json!({"checkpoint": id}));
assert!(Arc::ptr_eq(&s.doc, &doc) && s.active_sketch.is_none());
assert!(s.doc.find_feature("TapHole").is_none());
assert_eq!(s.undo.iter().map(|u| u.label.clone()).collect::<Vec<_>>(), labels);
// Used up: a second restore is an error, not a jump somewhere else.
assert!(s.execute("edit.restore_checkpoint", &json!({"checkpoint": id})).is_err());
// Forgetting one leaves the design alone.
let id = run(&mut s, "edit.checkpoint", json!({}))["checkpoint"].as_u64().unwrap();
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "Kept"}));
run(&mut s, "edit.restore_checkpoint", json!({"checkpoint": id, "forget": true}));
assert!(s.doc.find_feature("Kept").is_some());
assert!(s.execute("edit.restore_checkpoint", &json!({"checkpoint": id})).is_err());
}
#[test]
fn components_occurrences_and_world_placement() {
let mut s = Session::default();