From cc36771713344dbfd91ce7290b274ec4e3b80a77 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Tue, 10 Nov 2026 03:06:32 -0500 Subject: [PATCH] M8: absoluteRotation turns a selected parent before the layers in it (#2196) A page angle depends on the parents', so a frame and a layer inside it set together both land on the typed page angle whatever the order of the ids (the layer went to 80 for 45 when it came first). Co-Authored-By: Claude Opus 5.5 Model-Effort: Opus 5.5 drove and wrote (review fix, no brief) --- crates/engine/src/cmd/edit/props.rs | 5 +++++ crates/engine/src/transform_behaviour_tests.rs | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/crates/engine/src/cmd/edit/props.rs b/crates/engine/src/cmd/edit/props.rs index 819632a82..b2c9791dc 100644 --- a/crates/engine/src/cmd/edit/props.rs +++ b/crates/engine/src/cmd/edit/props.rs @@ -165,6 +165,11 @@ pub(super) fn set(s: &mut Session, p: &Value) -> Result { } let doc = s.doc_mut()?; let before = crate::cmd::styles::bound_before(doc, &ids); + let mut ids = ids; + if props.contains_key("absoluteRotation") { + // A page angle depends on the parents': turn a selected parent before the layers in it. + ids.sort_by_key(|i| doc.ancestors(*i).len()); + } for i in ids { crate::cmd::props::apply("node.set", doc, i, skipped.get(&i).unwrap_or(&props))?; } diff --git a/crates/engine/src/transform_behaviour_tests.rs b/crates/engine/src/transform_behaviour_tests.rs index 96b753d6d..8d7c44657 100644 --- a/crates/engine/src/transform_behaviour_tests.rs +++ b/crates/engine/src/transform_behaviour_tests.rs @@ -508,6 +508,19 @@ fn absolute_rotation_sets_the_rotation_on_the_page_keeping_the_centre() { assert!(s.execute("node.set", &json!({"ids": [r], "props": {"rotation": 1, "absoluteRotation": 2}})).is_err()); } +#[test] +fn absolute_rotation_on_a_frame_and_a_layer_inside_it_lands_both_on_the_page_angle() { + let mut s = session(); + let page = |s: &Session, i: NodeId| rotation_degrees(s.doc().unwrap().world_transform(i)); + let f = id(&ex(&mut s, "frame.create", json!({"x": 0, "y": 100, "width": 400, "height": 400}))); + set(&mut s, &[f], json!({"rotation": 10})); + let r = rect(&mut s, 100.0, 100.0, 120.0, 80.0, Some(f)); + // The layer inside comes first in the ids: the frame's turn must not carry it past 45. + set(&mut s, &[r, f], json!({"absoluteRotation": 45})); + assert!(near(page(&s, f), 45.0), "{}", page(&s, f)); + assert!(near(page(&s, r), 45.0), "{}", page(&s, r)); +} + #[test] fn constrain_proportions_keeps_the_other_side_in_ratio() { let mut s = session();