From 78ef2dffe65a31b766e881d51832ee49da683dd9 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 13 Oct 2026 13:56:29 +0200 Subject: [PATCH] :sparkles: Pin that a deleted component keeps passing changes on When a component nested in others is deleted (inner in mid in outer, mid deleted), a change to inner still reaches mid's stored shapes, mid's copies, outer and outer's copies, and restoring mid brings back an up-to-date main. Keep this behavior: outer is a live component, and its users cannot see a deleted mid between it and inner. Extend the deleted-component propagation test with a copy of the deleted component and its restore, and reword the contract, which said copies of a deleted component do not sync. AI-assisted-by: claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../common/component-sync-contract.md | 2 +- .../logic/chained_propagation_test.cljc | 36 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/.serena/memories/common/component-sync-contract.md b/.serena/memories/common/component-sync-contract.md index da69b6b1db..63c5691ef5 100644 --- a/.serena/memories/common/component-sync-contract.md +++ b/.serena/memories/common/component-sync-contract.md @@ -31,7 +31,7 @@ Intended semantics of copies, `:touched` and component sync. This is the spec: w - Duplicate a copy: the new subtree keeps the source's values and touched; if its nesting level changes, touched is inherited as in detach. Paste and move out of a copy follow the same rule. - Swap without keep-touched: the new subtree is clean and keeps `swap-keep-attrs` values; the new head keeps the old shape's id. Inside a copy it keeps the old `:shape-ref` and gets `:swap-group` (none when the new component is the near main's); inside a main it is a plain nested copy. - Variant switch with keep-touched: an attr is carried from the old copy only when its group is touched and the old and new mains hold the same value for it. Resulting touched = groups of carried attrs (+ `:swap-group` when the head is inside a copy). A value equal to the new main is not carried and not touched. -- Delete component: copies keep their data and do not sync. Restore brings sync back. +- Delete component: copies keep their data. The deleted main cannot be edited, so it starts no sync, but changes from components nested in it still pass through: sync updates its stored shapes (`:objects` of the deleted component), its copies, and the components that nest it. Restore brings back an up-to-date main. - Undo/redo: restores the exact prior state, `:touched` and `:remote-synced` included, of the op and the sync it caused together (same undo group, one step). Undo never needs a re-sync. ## Geometry ownership diff --git a/common/test/common_tests/logic/chained_propagation_test.cljc b/common/test/common_tests/logic/chained_propagation_test.cljc index 32a98e68dc..a7c0f538f3 100644 --- a/common/test/common_tests/logic/chained_propagation_test.cljc +++ b/common/test/common_tests/logic/chained_propagation_test.cljc @@ -89,17 +89,37 @@ (tho/add-frame :frame-comp-6) (thc/instantiate-component :comp-5 :copy-comp-5 :parent-label :frame-comp-6 :children-labels [:comp-4-comp-5]) - (thc/make-component :comp-6 :frame-comp-6))) + (thc/make-component :comp-6 :frame-comp-6) + + (thc/instantiate-component :comp-5 :loose-copy-comp-5))) (propagate-all-component-changes [file] (-> file (tho/propagate-component-changes :comp-4) (tho/propagate-component-changes :comp-5))) + (stored-fill-color [file] + (let [component (thc/get-component file :comp-5 :include-deleted? true)] + (when (:deleted component) + (->> (vals (:objects component)) + (some #(when (= :rect (:type %)) + (-> % :fills first :fill-color))))))) + (fill-colors [file] [(tho/bottom-fill-color file :frame-comp-4) (tho/bottom-fill-color file :frame-comp-5) - (tho/bottom-fill-color file :frame-comp-6)]) + (tho/bottom-fill-color file :frame-comp-6) + (tho/bottom-fill-color file :loose-copy-comp-5)]) + + (restore-comp-5 [file] + (let [page (thf/current-page file)] + (thf/apply-changes file + (cll/generate-restore-component (pcb/empty-changes) + (:data file) + (thi/id :comp-5) + (:id file) + page + (:objects page))))) (validate [file validator] (validator file) @@ -108,9 +128,15 @@ (-> (setup) ;; Delete Comp5. (tho/delete-shape :frame-comp-5) - (validate #(t/is (= (fill-colors %) ["#b1b2b5" nil "#b1b2b5"]))) + (validate #(t/is (= (fill-colors %) ["#b1b2b5" nil "#b1b2b5" "#b1b2b5"]))) ;; Change the color of Comp4 - ;; Desired result: Comp6 change color + ;; Desired result: the deleted Comp5 keeps passing the change on, to + ;; its stored main, its copy and Comp6 (tho/update-bottom-color :frame-comp-4 "#FF0000" :propagate-fn propagate-all-component-changes) - (validate #(t/is (= (fill-colors %) ["#FF0000" nil "#FF0000"])))))) + (validate #(t/is (= (fill-colors %) ["#FF0000" nil "#FF0000" "#FF0000"]))) + (validate #(t/is (= (stored-fill-color %) "#FF0000"))) + + ;; Restore Comp5: its main comes back up to date + (restore-comp-5) + (validate #(t/is (= (fill-colors %) ["#FF0000" "#FF0000" "#FF0000" "#FF0000"]))))))