From 1d04ee0e846ee82d8577419f03b955cc9b201eb6 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Thu, 8 Oct 2026 06:54:49 +0700 Subject: [PATCH] Engine: a failed batch gives back the highlighted Layers panel rows `command.batch` rolls a failed batch back through each document's interaction, which restored the art, the selection, the current layer and isolation but not the highlighted Layers panel rows. A batch whose `layer.setCurrent` step ran before a later step failed left that layer's row highlighted, so a following Collect in New Layer collected the layer instead of the selected art. The failed batch isn't journaled, so replaying the journal gave a different document: the `journal_replay_reproduces_document` property test failed whenever it drew that sequence. The interaction now keeps the highlighted rows too, which also puts them back when a drag is cancelled. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/lib.rs | 6 +++++- crates/engine/src/tests_journal.rs | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 59da0fdea..69c3c77d4 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -79,8 +79,10 @@ pub struct Interaction { pub doc: Arc, pub selection: Selection, pub preview: Option<(String, Value)>, - /// Per-document state restored on cancel (current layer, isolation). + /// Per-document state restored on cancel (current layer, highlighted Layers panel rows, + /// isolation). pub active_layer: Option, + pub layer_rows: Vec, pub isolation: Option, /// The perspective transform the previews make (`perspective.transform` params): Transform /// Again repeats it once the drag is committed. @@ -206,6 +208,7 @@ impl DocState { self.doc = it.doc; self.selection = it.selection; self.active_layer = it.active_layer; + self.layer_rows = it.layer_rows; self.isolation = it.isolation; self.revision += 1; } @@ -1114,6 +1117,7 @@ impl Session { selection: st.selection.clone(), preview: None, active_layer: st.active_layer, + layer_rows: st.layer_rows.clone(), isolation: st.isolation, perspective_again: None, }); diff --git a/crates/engine/src/tests_journal.rs b/crates/engine/src/tests_journal.rs index 8bbaa6054..03b5cf8f7 100644 --- a/crates/engine/src/tests_journal.rs +++ b/crates/engine/src/tests_journal.rs @@ -231,3 +231,21 @@ fn a_failed_batch_brings_back_a_document_it_reverted() { assert!(st.is_dirty() && st.interaction.is_none()); assert!(st.revision > revision, "the restored document redraws"); } + +#[test] +fn a_failed_batch_gives_back_the_highlighted_rows_and_the_current_layer() { + let mut s = Session::new(); + s.execute("file.new", &json!({"created": null})).unwrap(); + let layer = s.doc().unwrap().doc.layers[0].id; + // A step that highlights the layer's row, then one that fails: the highlight goes back too. + let steps = json!([{"command": "layer.setCurrent", "params": {"id": layer.0}}, {"command": "no.such.command"}]); + assert!(s.execute("command.batch", &json!({ "commands": steps })).is_err()); + assert!(s.doc().unwrap().layer_rows.is_empty(), "no row stays highlighted"); + assert_eq!(s.doc().unwrap().active_layer, Some(layer)); + // So Collect in New Layer acts on the drawn rectangle, as it does when the journal is replayed + // (a failed batch isn't journaled). + s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 1, "height": 1})).unwrap(); + s.execute("layer.collectInNew", &json!({})).unwrap(); + let r = replayed(&s); + assert_eq!(documents(&r), documents(&s)); +}