From e0188af747f6bfda63f2541405f79b5e36966125 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Sat, 24 Oct 2026 22:08:55 +0200 Subject: [PATCH] :bug: Carry variant overrides through deleted near mains Copies still use stored main data after its component is deleted. Follow that data both when collecting inherited groups and when matching variant children, so a switch keeps inherited size, orientation and child fills. Portable live/deleted controls fail four assertions before the fix and pass all 16 afterward, including repeat sync and exact undo. Library seed 712 case 1462 and its 12-step shrink both pass. Focused regression and golden checks pass 148 tests, 2593 assertions; goldens are unchanged. Live WASM verifies height 80, rotation 45, child fill, groups, undo/redo and later main opacity propagation. Component benchmark ratios: mains 0.51/0.58/0.56; levels 0.50; chain 0.51; library 0.59; many shapes 0.48; variant switch 0.71. All worlds pass the unchanged baseline gate. AI-assisted-by: GPT-6 --- .../common/component-swap-pipeline.md | 5 +++ common/src/app/common/logic/variants.cljc | 1 + common/src/app/common/types/file.cljc | 3 +- .../logic/variants_switch_test.cljc | 35 +++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.serena/memories/common/component-swap-pipeline.md b/.serena/memories/common/component-swap-pipeline.md index 7494dc905b..30ebe26856 100644 --- a/.serena/memories/common/component-swap-pipeline.md +++ b/.serena/memories/common/component-swap-pipeline.md @@ -53,3 +53,8 @@ After carrying geometry, the switch syncs its new subtree once from the updated builder objects. Untouched children must follow a retained root size through constraints; copying only root attrs leaves them in the old frame (F162). The direct sync keeps carried groups and child swaps. + +Variant carry must collect groups and match children through deleted near +mains too (F180). Copies keep those values after deletion. Both the content +ref-chain walk and find-shape-ref-child-of include deleted component data; +otherwise a switch loses inherited size, rotation groups and child fills. diff --git a/common/src/app/common/logic/variants.cljc b/common/src/app/common/logic/variants.cljc index 8f76471bd2..ed055571a2 100644 --- a/common/src/app/common/logic/variants.cljc +++ b/common/src/app/common/logic/variants.cljc @@ -298,6 +298,7 @@ It will return the shape or nil if it doesn't found any" [container libraries shape parent-id] (let [ref-shape (ctf/find-ref-shape nil container libraries shape + :include-deleted? true :with-context? true) ref-shape-container (when ref-shape (:container (meta ref-shape))) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index bdab828fba..07873237a8 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -1266,7 +1266,8 @@ container container] (if (= (:id current) target-ref) chain - (if-let [ref (find-content-ref-shape nil container libraries current :with-context? true)] + (if-let [ref (find-content-ref-shape nil container libraries current + :include-deleted? true :with-context? true)] (recur (conj chain ref) ref (:container (meta ref))) chain)))) diff --git a/common/test/common_tests/logic/variants_switch_test.cljc b/common/test/common_tests/logic/variants_switch_test.cljc index 5150b9b9b1..bc989c31cc 100644 --- a/common/test/common_tests/logic/variants_switch_test.cljc +++ b/common/test/common_tests/logic/variants_switch_test.cljc @@ -31,6 +31,41 @@ (t/use-fixtures :each thi/test-fixture) +(t/deftest switch-keeps-overrides-inherited-through-a-deleted-near-main + (doseq [deleted? [false true]] + (let [file (-> (thf/sample-file :file) + (thv/add-variant :variants :a :main-a :b :main-b + :variant1-params {:width 100 :height 100} + :variant2-params {:width 100 :height 100}) + (ths/add-sample-shape :rect-a :parent-label :main-a :width 20 :height 20) + (ths/add-sample-shape :rect-b :parent-label :main-b :width 20 :height 20) + (tho/add-frame :outer {:width 200 :height 200}) + (thc/instantiate-component :a :near :parent-label :outer + :children-labels [:near-child]) + (thc/make-component :outer-component :outer) + (thc/instantiate-component :outer-component :copy + :children-labels [:copy-near :copy-child])) + edited (-> (tm/make-situation file) + (r/step (n/change-height :near 80)) r/settle + (r/step (n/rotate :near 45)) r/settle + (r/step (n/change-property :near-child :fills "#00ff00")) r/settle) + before (cond-> edited deleted? (r/step (n/delete-component :outer))) + switched (-> before (r/step (n/switch-variant :copy-near "Value2")) r/settle) + head (tm/resolve-shape switched :copy-near) + child (tm/shape-by-id switched (first (:shapes head))) + file (tm/file switched) + full (:file (r/sync-cascade file {} (keys (get-in file [:data :components]))))] + (t/is (empty? (:touched (tm/resolve-shape before :copy-near)))) + (t/is (mth/close? 80 (:height head) 0.001)) + (t/is (mth/close? 45 (:rotation head) 0.001)) + (t/is (= #{:swap-group :geometry-group} (:touched head))) + (t/is (= "#00ff00" (get-in child [:fills 0 :fill-color]))) + (t/is (= #{:fill-group} (:touched child))) + (t/is (gsh/close-attrs? :points (:points head) + (:points (ths/get-shape-by-id full (:id head))) 0.001)) + (t/is (= (:data (tm/file before)) + (:data (tm/file (r/step switched (n/undo))))))))) + (t/deftest switching-an-untouched-rotated-copy-authors-its-retained-orientation (let [file (-> (thf/sample-file :file) (thv/add-variant :variant :c01 :m01 :c02 :m02