From a568b1d6cf72dbe84f0768af35cc0a2574fa3c35 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Sun, 25 Oct 2026 10:54:21 +0100 Subject: [PATCH] :bug: Place reset replacement children in the main frame Reset clears the parent's geometry override, but replacing a swapped child cloned its new children through the parent's old rotation. Drop that override from the whole sync context so existing and new children use the main's orientation in the same undo group. F185: portable checks at 45 and 90 degrees fail six assertions before the fix and pass all fourteen after it. Original and shrunk programs pass. The focused suite passes 139 tests / 2590 assertions with unchanged goldens. Live WASM checks reset placement, exact undo/redo, later sync and no phantom overrides at both angles. Frontend passes 1538 tests / 9253 assertions; plugin 68 cases, lint/format and isolated Stage 3 checks pass. Common JVM/JS, backend and exporter suites pass. Phase 1e benchmark ratios: mains 10/100/1000 .50/.55/.57, levels .50, chain .50, library .52, many shapes .42, variant switch .60. AI-assisted-by: GPT-6 --- .../common/component-sync-contract.md | 6 ++++ common/src/app/common/logic/libraries.cljc | 8 ++--- .../logic/comp_expected_geometry_test.cljc | 34 +++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.serena/memories/common/component-sync-contract.md b/.serena/memories/common/component-sync-contract.md index 10dafa2b85..fed4207439 100644 --- a/.serena/memories/common/component-sync-contract.md +++ b/.serena/memories/common/component-sync-contract.md @@ -72,3 +72,9 @@ change builder (F182). Its content and position reference can have different sizes, so rotating the content rigidly can leave its untransformed anchor different from full sync. Keep the swapped head/children untouched and put the derived correction in the same undo group. Authored geometry stays. + +Reset drops the root's geometry override from the whole sync context +(F185), including the branch that clones replacement children. Clearing it +only while updating existing shapes can turn a newly added child by the +old override after its parent has already reset. The reset and replacement +children share one undo group and stay stable through later sync. diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 264d7644cd..70bab2ae62 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -321,7 +321,8 @@ :main-container main-container :component component :library library - :root-inst shape-inst + ;; Reset also places new children in the main's frame. + :root-inst (cond-> shape-inst reset? (dissoc :touched)) :root-main shape-main :reset? reset?} shape-inst @@ -366,10 +367,7 @@ as it follows its near main." [changes {:keys [container main-container root-inst root-main reset?]} shape-inst shape-main] - (let [omit-touched? (not reset?) - ;; Reset takes the main's orientation too. Its old geometry override - ;; must not turn the main into the copy's frame before overwriting it. - root-inst (cond-> root-inst reset? (dissoc :touched))] + (let [omit-touched? (not reset?)] (cond-> (update-attrs changes shape-inst shape-main root-inst root-main container omit-touched?) ;; the children refer to the children of `shape-main`, in the ;; container of the walk's main shapes diff --git a/common/test/common_tests/logic/comp_expected_geometry_test.cljc b/common/test/common_tests/logic/comp_expected_geometry_test.cljc index 2b5dddc511..97b4e269f8 100644 --- a/common/test/common_tests/logic/comp_expected_geometry_test.cljc +++ b/common/test/common_tests/logic/comp_expected_geometry_test.cljc @@ -36,6 +36,40 @@ (t/use-fixtures :each thi/test-fixture) +(t/deftest resetting-a-turned-copy-places-replacement-children-in-the-main-frame + (doseq [angle [45 90]] + (let [file (-> (thf/sample-file :file) + (tho/add-simple-component :a :main-a :rect-a + :root-params {:width 100 :height 100} + :child-params {:width 20 :height 20 :x 10 :y 10}) + (tho/add-simple-component :other :main-other :rect-other + :root-params {:width 100 :height 100} + :child-params {:width 20 :height 20 :x 10 :y 10}) + (tho/add-frame :main-b {:width 200 :height 200}) + (thc/instantiate-component :a :b-a :parent-label :main-b) + (thc/make-component :b :main-b) + (thc/instantiate-component :b :copy-b :children-labels [:copy-a])) + turned (-> (tm/make-situation file) + (r/step (n/swap-shape :copy-a :other)) r/settle + (r/step (n/rotate :copy-b angle)) r/settle) + reset (-> turned (r/step (n/reset :copy-b)) r/settle) + head (tm/resolve-shape reset :copy-a) + child (tm/shape-by-id reset (first (:shapes head))) + expected (gsh/move (ths/get-shape file :rect-a) + (gpt/subtract (gsh/shape->center head) + (gsh/shape->center (ths/get-shape file :main-a)))) + file (tm/file reset) + full (:file (r/sync-cascade file {} (keys (get-in file [:data :components]))))] + (t/is (mth/close? 0 (:rotation head) 0.001)) + (t/is (mth/close? 0 (:rotation child) 0.001)) + (t/is (empty? (:touched head))) + (t/is (empty? (:touched child))) + (t/is (gsh/close-attrs? :points (:points expected) (:points child) 0.001)) + (t/is (gsh/close-attrs? :points (:points child) + (:points (ths/get-shape-by-id full (:id child))) 0.001)) + (t/is (= (thn/normalize-file (tm/file turned)) + (thn/normalize-file (tm/file (r/step reset (n/undo))))))))) + (t/deftest turning-a-parent-settles-a-swapped-head-with-a-different-position-size (doseq [angle [45 90]] (let [file (-> (thf/sample-file :file)