From 6eeac4fa04c72c74836d27848f2836227c737597 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:14:35 +0200 Subject: [PATCH] Keep the redo steps when a failed command batch rolls back (#196) --- crates/engine/src/batch_rollback_tests.rs | 22 ++++++++++++++++++++++ crates/engine/src/lib.rs | 5 ++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/batch_rollback_tests.rs b/crates/engine/src/batch_rollback_tests.rs index db0b7962..12d958cd 100644 --- a/crates/engine/src/batch_rollback_tests.rs +++ b/crates/engine/src/batch_rollback_tests.rs @@ -61,3 +61,25 @@ fn failed_plugin_run_restores_each_document_without_restoring_unrelated_state() assert_eq!(s.journal, before_journal); assert_eq!(s.message, before_message); } + +#[test] +fn failed_group_keeps_the_redo_steps() { + let mut s = Session::new(); + s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap(); + assert!(s.undo().unwrap()); + assert_eq!(s.undo_depth().1, 1); + + let result = s.execute( + "command.batch", + &json!({ + "commands": [ + {"command": "shape.rectangle", "params": {"x": 20, "y": 20, "width": 10, "height": 10}}, + {"command": "node.set", "params": {"id": 999999, "props": {"name": "must fail"}}} + ] + }), + ); + + assert!(result.is_err()); + assert_eq!(s.undo_depth().1, 1, "the redo step survives the failed batch"); + assert!(s.redo().unwrap()); +} diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 3bc49a90..0d9eddf4 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -1082,6 +1082,8 @@ impl Session { self.ds()?; let before: Vec<_> = self.docs.iter().map(|ds| (ds.uid, ds.doc.clone(), ds.selection.clone(), ds.page, ds.history.undo.len(), ds.history.limit)).collect(); + // An edit inside clears the redo steps; a failed group has to bring them back. + let redo_before: Vec<_> = self.docs.iter().map(|ds| ds.history.redo.clone()).collect(); let active = self.active; let journal_len = self.journal.len(); @@ -1099,8 +1101,9 @@ impl Session { if result.is_err() { self.docs.retain(|ds| before.iter().any(|(uid, _, _, _, _, _)| *uid == ds.uid)); - for (uid, doc, selection, page, undo_len, limit) in &before { + for ((uid, doc, selection, page, undo_len, limit), redo) in before.iter().zip(redo_before) { if let Some(ds) = self.docs.iter_mut().find(|ds| ds.uid == *uid) { + ds.history.redo = redo; ds.doc = doc.clone(); ds.selection = selection.clone(); ds.page = *page;