From 407e14682f3dc45a28bc5deabe2d01dff55b82ae Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 23 Oct 2026 19:34:54 +0200 Subject: [PATCH] :bug: Preserve swapped placement when its content main rotates Prepare the content subtree in the swapped head's orientation before syncing it. A content main rotation must not replace the orientation of the position this head occupies in its enclosing component. The focused regression fails two assertions before the fix. Geometry, reset, swap and oracle suites pass 77 tests / 312 assertions. The original generated failure also passes after the fix. Benchmark vs Phase 1e: main 10/100/1000 0.51/0.57/0.57; levels 0.49; chain 0.49; library 0.54; many shapes 0.72; switch 0.64. No flags. AI-assisted-by: GPT-6 --- common/src/app/common/logic/libraries.cljc | 16 ++++++++++------ .../test/common_tests/logic/swap_group_test.cljc | 13 +++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 8c48954c97..b74aae398b 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -261,17 +261,21 @@ (recur shape-inst' next-data component'))))))) (defn- main-in-parent-orientation - "Place a main in the orientation of an ordinary instance parent. Such a - parent can survive detach, while the nested copy stays untouched." + "Place a content main in the orientation of a swapped head, or compose + the placement of an ordinary parent that survived detach." [main-container shape-main container shape-inst reset?] (let [parent (ctn/get-shape container (:parent-id shape-inst)) main-parent (get-in main-container [:objects (:parent-id shape-main)]) - relative (gmt/multiply (or (:transform parent) (gmt/matrix)) - (or (:transform-inverse main-parent) (gmt/matrix)))] - (if (or (nil? parent) (and (:shape-ref parent) (not reset?)) (gmt/unit? relative)) + swapped? (and (not reset?) (ctk/swapped? shape-inst)) + origin (if swapped? shape-main main-parent) + target (if swapped? shape-inst parent) + relative (gmt/multiply (or (:transform target) (gmt/matrix)) + (or (:transform-inverse origin) (gmt/matrix)))] + (if (or (nil? target) (and (:shape-ref parent) (not reset?) (not swapped?)) + (gmt/unit? relative)) main-container (let [transform (gmt/transform-in (gsh/shape->center shape-main) relative) - angle (- (or (:rotation parent) 0) (or (:rotation main-parent) 0)) + angle (- (or (:rotation target) 0) (or (:rotation origin) 0)) turn #(-> % (assoc-in [:modifiers :rotation] angle) (gsh/apply-transform transform) diff --git a/common/test/common_tests/logic/swap_group_test.cljc b/common/test/common_tests/logic/swap_group_test.cljc index 2f1a5942b4..25debb2faf 100644 --- a/common/test/common_tests/logic/swap_group_test.cljc +++ b/common/test/common_tests/logic/swap_group_test.cljc @@ -721,3 +721,16 @@ head (tm/resolve-shape situation :plain-star)] (t/is (== 40 (:height head))) (t/is (not (ctk/touched-group? head :geometry-group))))) + +(t/deftest swapped-copy-keeps-position-orientation-when-its-content-main-turns + (let [initial (setup-sized) + file (-> initial + (tho/swap-component-in-shape :copy-star :heart) + (transform-subtree :heart-main #(ctm/rotation-modifiers %1 %2 90)) + (propagate :heart)) + head (ths/get-shape file :copy-star) + child (ths/get-shape-by-id file (first (:shapes head)))] + (t/is (mth/close? 0 (:rotation head) 0.001)) + (t/is (mth/close? 0 (:rotation child) 0.001)) + (t/is (= #{:swap-group} (:touched head))) + (t/is (nil? (:touched child)))))