🐛 Place children after carrying variant geometry overrides

A switch can retain the copy root's resized geometry while leaving its
untouched children in the old frame. After geometry carry, sync the new
subtree from the updated builder objects so constraints place its children
without adding overrides. Reuse the direct component sync entry point.

The portable rotated-root regression fails before the fix with a 30px
child offset. Swap and variant suites pass 108 tests / 539 assertions;
the pure golden master matches 1149 assertions unchanged. Full frontend:
1517 tests / 8436 assertions. Live ws1 WASM retains the resized root and
child placement, with no exception or pending reflow.

Benchmark ratios: main 10/100/1000 copies 0.48/0.56/0.57;
three levels 0.49; chain 0.48; library 0.50; many shapes 0.45;
variant switch 0.61. No slowdown flags.

AI-assisted-by: GPT-6
This commit is contained in:
alonso.torres 2026-10-24 01:36:47 +02:00
parent 8a2c2b7246
commit 955650c618
4 changed files with 42 additions and 2 deletions

View File

@ -48,3 +48,8 @@ The clean target already has the copy's rotation, which can turn 100 into
99.99999999999994. Exact dimension equality drops valid resize overrides
(F160). Fixed layout sizes and composite geometry use the same tolerance;
nongeometry scalar attributes still compare exactly.
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.

View File

@ -285,7 +285,7 @@
(reduce #(update %1 %2 turn) objects
(cfh/get-children-ids-with-self objects (:id shape-main)))))))))
(defn- generate-sync-shape-direct
(defn generate-sync-shape-direct
"Generate changes to synchronize one shape that is the root of a component
instance, and all its children, from the given component."
[changes file libraries container shape-id reset?]

View File

@ -430,7 +430,14 @@
(ctk/touched-group? % :geometry-group)) orig-touched)
(keep-orientation-override original-shape new-shape page
(or (get-in libraries [(:component-file new-shape) :data]) ldata)
(:component-id new-shape) nil))]
(:component-id new-shape) nil))
;; A carried size changes the frame its untouched children follow.
;; Place them through the same constraints as an ordinary copy sync.
changes (cond-> changes
(some #(ctk/touched-group? % :geometry-group) orig-touched)
(cll/generate-sync-shape-direct
nil libraries (assoc container :objects (pcb/get-objects changes))
(:id new-shape) false))]
[changes (mapv (comp :parent-id :new) pairs)]))
(defn- shape-index

View File

@ -49,6 +49,34 @@
(t/is (mth/close? 80 (get-in copy [:selrect :height]) 1e-10))
(t/is (contains? (:touched copy) :geometry-group)))))
(t/deftest switch-places-children-through-a-carried-root-resize
(doseq [angle [0 45]]
(let [root-params {:width 100 :height 100}
child-params {:x 10 :y 10 :width 20 :height 20
:constraints-h :left :constraints-v :top}
file (-> (thf/sample-file :file1)
(thv/add-variant-with-child
:v01 :c01 :m01 :c02 :m02 :r01 :r02
{:variant1-params root-params :variant2-params root-params
:child1-params child-params :child2-params child-params})
(thc/instantiate-component :c01 :copy01))
file (reduce (fn [file modifiers]
(let [shape (ths/get-shape file :copy01)]
(thf/apply-changes file
(n/transform-changes (thf/current-page file) shape
(modifiers shape)))))
file [#(ctm/rotation-modifiers % (gsh/shape->center %) angle)
#(ctm/change-size % nil 80)])
switched (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})
relative (fn [file]
(let [root (ths/get-shape file :copy01)
child (get-in (thf/current-page file) [:objects (first (:shapes root))])]
(gpt/subtract (first (:points child)) (first (:points root)))))
child (get-in (thf/current-page switched)
[:objects (first (:shapes (ths/get-shape switched :copy01)))])]
(t/is (< (gpt/distance (relative file) (relative switched)) 0.01))
(t/is (empty? (:touched child))))))
;; ============================================================
;; BASIC SWITCH TESTS (no overrides)
;; ============================================================