diff --git a/.serena/memories/common/component-data-model.md b/.serena/memories/common/component-data-model.md index e7f062df35..0a176a77c1 100644 --- a/.serena/memories/common/component-data-model.md +++ b/.serena/memories/common/component-data-model.md @@ -40,6 +40,9 @@ Masters are not normally touched through `set-shape-attr`, but touched flags can `make-component-instance` in `app.common.types.container` produces a clean component copy through `update-new-shape`, dissociating attrs such as `:touched`, `:remote-synced`, `:variant-id`, and `:variant-name` on cloned shapes. +With `:main-instance? true` (library absorption), nested copies keep their +`:touched` and `:remote-synced`; they remain copies inside the new main. + `duplicate-component` in `app.common.logic.duplicate` creates a new component master by cloning existing component shapes, setting component metadata, and applying a position delta. It does not have the same clean-copy semantics as `make-component-instance`, so inherited attrs on the source can matter. When a bug depends on touched state, identify which cloning path produced the shape before changing sync logic. diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 6d160cb25e..24e23cc28e 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -347,7 +347,10 @@ (cond-> new-shape :always (-> (gsh/move delta) - (dissoc :touched :remote-synced :variant-id :variant-name)) + (dissoc :variant-id :variant-name)) + + (not main-instance?) + (dissoc :touched :remote-synced) (and main-instance? root?) (assoc :main-instance true) diff --git a/common/test/common_tests/types/absorb_assets_test.cljc b/common/test/common_tests/types/absorb_assets_test.cljc index d053fa6735..f8b5bb560b 100644 --- a/common/test/common_tests/types/absorb_assets_test.cljc +++ b/common/test/common_tests/types/absorb_assets_test.cljc @@ -10,6 +10,7 @@ [app.common.files.changes-builder :as pcb] [app.common.files.validate :as cfv] [app.common.logic.shapes :as cls] + [app.common.logic.libraries :as cll] [app.common.test-helpers.components :as thc] [app.common.test-helpers.compositions :as tho] [app.common.test-helpers.files :as thf] @@ -106,22 +107,42 @@ (t/deftest absorb-nested-components ;; component2's main nests a copy of component1, and has an annotation; ;; the file has a copy of component2 - (let [library (-> (thf/sample-file :library :is-shared true) - (tho/add-nested-component :component1 :main1-root :main1-child - :component2 :main2-root :nested-head) - (thc/update-component :component2 :annotation "Note")) - file (-> (thf/sample-file :file) - (thc/instantiate-component :component2 :copy2-root :library library - :children-labels [:copy-nested-head])) - file' (ctf/update-file-data file #(ctf/absorb-assets % (:data library))) - data' (ctf/file-data file') - page' (second (ctpl/pages-seq data')) - nested' (get-in page' [:objects (thi/id :nested-head)])] + (let [library (-> (thf/sample-file :library :is-shared true) + (tho/add-nested-component :component1 :main1-root :main1-child + :component2 :main2-root :nested-head) + (ths/update-shape :nested-head :opacity 0.5) + (as-> library + (ths/update-shape-by-id + library (first (:shapes (ths/get-shape library :nested-head))) + :fills (ths/sample-fills-color :fill-color "#FF0000"))) + (thc/update-component :component2 :annotation "Note")) + file (-> (thf/sample-file :file) + (thc/instantiate-component :component2 :copy2-root :library library + :children-labels [:copy-nested-head])) + file' (ctf/update-file-data file #(ctf/absorb-assets % (:data library))) + data' (ctf/file-data file') + page' (second (ctpl/pages-seq data')) + nested' (get-in page' [:objects (thi/id :nested-head)]) + child-id (first (:shapes nested')) + synced (thf/apply-changes + file' + (:changes (cll/generate-sync-components-cascade + (pcb/empty-changes) (:id file') #{(thi/id :component1)} + {(:id file') file'} (:id file')))) + nested-synced (get-in synced [:data :pages-index (:id page') :objects (thi/id :nested-head)])] (thf/validate-file! file') (t/is (= 2 (count (ctkl/components-seq data')))) (t/testing "the nested copy in the absorbed main keeps its link" (t/is (= (thi/id :main1-root) (:shape-ref nested'))) (t/is (= (:id file') (:component-file nested')))) + (t/testing "nested overrides survive absorption and a later sync" + (t/is (= #{:layer-effects-group} (:touched nested'))) + (t/is (= 0.5 (:opacity nested-synced))) + (t/is (= #{:layer-effects-group} (:touched nested-synced)))) + (t/testing "the nested descendant keeps its fill override" + (let [child (get-in synced [:data :pages-index (:id page') :objects child-id])] + (t/is (= "#FF0000" (-> child :fills first :fill-color))) + (t/is (= #{:fill-group} (:touched child))))) (t/testing "the component keeps its annotation" (t/is (= "Note" (:annotation (ctkl/get-component data' (thi/id :component2))))))))