From 86e7879f5eadce0907c7b173d0c8f304468e6a7e Mon Sep 17 00:00:00 2001 From: Zetenypapp Date: Sun, 18 Oct 2026 19:52:41 +0200 Subject: [PATCH] Duplicate: Undo and Redo forget the last Duplicate's move A shape added after an Undo can get an undone copy's id; duplicating it then used the move measured from the old original. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/edit.rs | 2 ++ crates/engine/src/tests.rs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index f00660f1..139fa30e 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -51,6 +51,8 @@ fn restore(s: &mut Session, redo: bool) -> Result { st.doc = e.doc; st.selection = e.selection; st.interaction = None; + // A later shape can get an undone copy's id: Duplicate starts over with the usual offset. + st.last_duplicate = None; st.revision += 1; Ok(json!({"label": e.label})) } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index fee47368..c5cc3cd0 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -4098,6 +4098,13 @@ fn duplicating_a_moved_copy_repeats_the_move() { s.doc_mut().unwrap().selection.shapes = vec![ShapeId(a as u32)]; let e = dup(&mut s); assert_eq!(boxed(&mut s, e), (100.0 + off, 100.0 + off)); + // Undone, its id can go to a new shape: duplicating that one uses the usual offset too. + s.execute("edit.undo", &json!({})).unwrap(); + let r = s.execute("shape.insert", &json!({"preset": "ellipse", "rect": [400, 300, 40, 40]})).unwrap(); + let g = r["id"].as_u64().unwrap(); + assert_eq!(g, e, "the new shape reuses the undone copy's id"); + let h = dup(&mut s); + assert_eq!(boxed(&mut s, h), (400.0 + off, 300.0 + off)); // After Undo removed that copy, duplicating the last moved copy gets the usual offset too. s.execute("edit.undo", &json!({})).unwrap(); s.doc_mut().unwrap().selection.shapes = vec![ShapeId(d as u32)];