From 4c011cf42e985155f1f45f6d82e884f7499eaa1d Mon Sep 17 00:00:00 2001 From: Nigel Ihza Firdausy <106657905+Ni-zav@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:36:51 +0700 Subject: [PATCH 1/2] fix(paint): preserve new-art defaults when paint edits are rejected --- crates/engine/src/cmd/paint.rs | 24 +++++++++++++----------- crates/engine/src/tests.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/crates/engine/src/cmd/paint.rs b/crates/engine/src/cmd/paint.rs index b98c6c82..08f0cb8d 100644 --- a/crates/engine/src/cmd/paint.rs +++ b/crates/engine/src/cmd/paint.rs @@ -375,15 +375,7 @@ fn apply_to_proxy(s: &mut Session, p: &Value, paint: Paint) -> Result { /// remember the paint. fn apply_paint(s: &mut Session, p: &Value, paint: Paint, fill: bool) -> Result { let cmd = if fill { "paint.setFill" } else { "paint.setStroke" }; - // New art gets the paint fitted to itself, not placed where this one is. - if fill { - s.paint.fill = unplaced(&paint); - } else { - s.paint.stroke = unplaced(&paint); - } - if bool_or(p, "focus", true) { - s.fill_active = fill; - } + // Resolve the requested targets before mutating defaults or the active proxy. let item = item_target(s, p, cmd)?.of_kind(s, fill); let selected = item.targets(s, p)?; let ids = super::graph::paint_targets(&s.doc()?.doc, &selected); @@ -423,6 +415,16 @@ fn apply_paint(s: &mut Session, p: &Value, paint: Paint, fill: bool) -> Result Result { - std::mem::swap(&mut s.paint.fill, &mut s.paint.stroke); let ids = paint_targets(s, p)?; if !ids.is_empty() { s.edit("Swap Fill and Stroke", |d, _| { @@ -458,11 +459,11 @@ fn swap(s: &mut Session, p: &Value) -> Result { Ok(()) })?; } + std::mem::swap(&mut s.paint.fill, &mut s.paint.stroke); ok() } fn default_paint(s: &mut Session, p: &Value) -> Result { - super::newart::reset(s); let ids = paint_targets(s, p)?; if !ids.is_empty() { s.edit("Default Fill and Stroke", |d, _| { @@ -481,6 +482,7 @@ fn default_paint(s: &mut Session, p: &Value) -> Result { Ok(()) })?; } + super::newart::reset(s); ok() } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index ad04e948..13da65c1 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -134,6 +134,40 @@ fn fill_and_stroke_commands() { s.execute("paint.setFill", &json!({"gradient": {"kind": "radial"}})).unwrap(); } + +#[test] +fn rejected_paint_edits_leave_new_art_defaults_and_focus_unchanged() { + let mut s = session(); + let id = rect(&mut s, 0.0, 0.0, 10.0, 10.0); + s.execute("paint.setFill", &json!({"color": "#00ff00"})).unwrap(); + s.execute("paint.setStroke", &json!({"color": "#ff0000"})).unwrap(); + let defaults = s.paint.clone(); + let focused = s.fill_active; + let original_fill = s.doc().unwrap().doc.node(id).unwrap().appearance.fill_paint(); + let original_stroke = s.doc().unwrap().doc.node(id).unwrap().appearance.stroke_paint(); + + // Both shortcut commands used to change defaults before checking invalid ids. + for (command, params) in [ + ("paint.swap", json!({"ids": "not an array"})), + ("paint.default", json!({"ids": ["not an object id"]})), + ("paint.setFill", json!({"color": "#123456", "ids": [false]})), + ("paint.setStroke", json!({"color": "#123456", "ids": "not an array"})), + ("paint.setFill", json!({"color": "#123456", "item": "invalid"})), + ] { + assert!(s.execute(command, ¶ms).is_err(), "{command} should reject {params}"); + assert_eq!(s.paint, defaults, "{command} changed new-art defaults on failure"); + assert_eq!(s.fill_active, focused, "{command} changed the active proxy on failure"); + let appearance = &s.doc().unwrap().doc.node(id).unwrap().appearance; + assert_eq!(appearance.fill_paint(), original_fill); + assert_eq!(appearance.stroke_paint(), original_stroke); + } + + // Successful operations still update both the selected object and new-art defaults. + s.execute("paint.swap", &json!({})).unwrap(); + assert_eq!(s.paint.fill, defaults.stroke); + assert_eq!(s.paint.stroke, defaults.fill); +} + #[test] fn select_same_fill() { let mut s = session(); From e860b01ed83ae23f6d55be12a65689e3a481de01 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 11 Oct 2026 01:36:54 +0700 Subject: [PATCH 2/2] Review #984: rustfmt Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 13da65c1..69c4ce75 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -134,7 +134,6 @@ fn fill_and_stroke_commands() { s.execute("paint.setFill", &json!({"gradient": {"kind": "radial"}})).unwrap(); } - #[test] fn rejected_paint_edits_leave_new_art_defaults_and_focus_unchanged() { let mut s = session();