🐛 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
This commit is contained in:
alonso.torres 2026-10-24 22:08:55 +02:00
parent cfb800620c
commit e0188af747
4 changed files with 43 additions and 1 deletions

View File

@ -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.

View File

@ -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)))

View File

@ -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))))

View File

@ -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