diff --git a/.serena/memories/common/component-sync-contract.md b/.serena/memories/common/component-sync-contract.md index 27feb413fc..ad554a1a75 100644 --- a/.serena/memories/common/component-sync-contract.md +++ b/.serena/memories/common/component-sync-contract.md @@ -25,7 +25,7 @@ Intended semantics of copies, `:touched` and component sync. This is the spec: w - Sync main→copy: every untouched group gets the near main's value (geometry: expected geometry); touched groups keep their values; never sets or clears `:touched`. Main child added → added clean to copies. Main child deleted → copy child removed, overrides or not. Main reorder → copies follow. A swapped sub-head pairs with the main child it replaces (its `:shape-ref`) and takes only that child's placement (position, rotation, flips, constraints, `layout-item-*`); everything else syncs from its own component's main. A main child deleted → its swapped copies go with it. A swap made in the main reaches copies without `:swap-group` and spares those with it. - Sync cascade is eager: one sync runs to a fixpoint in memory (copies of A, including the A copy in B's main → copies of B → …) and commits once in the triggering op's undo group. The file is never left partly synced. Syncing only the components reported as changed equals a full-file sync. Code: `cll/generate-sync-file-cascade` (first pass as `generate-sync-file-changes`, then `generate-sync-components-cascade` over `cll/changed-components` of each pass); the frontend `sync-file` commits it to the synced file, and a library file's sync then syncs the current file's copies of the library components its cascade changed. - Reset: the selected head's subtree becomes equal to its near main (expected geometry); all its touched groups cleared, `:swap-group` included: a swapped head turns back into the near main's component in place, keeping its id. Nothing outside that subtree changes. -- Update main (from a top copy head): the copy's differing values go to the main; the copy's touched cleared; other copies then sync and keep their own overrides. A swapped child replaces the main child it stands for, and neither keeps `:swap-group`. +- Update main (from a top copy head): the copy's differing values go to the main; the copy's touched cleared; other copies then sync and keep their own overrides. A swapped child replaces the main child it stands for, and neither keeps `:swap-group`. The main's authored groups are unioned with the copy's even on a repeated update; `:remote-synced` records an earlier update and must never clear those groups (F114). - Detach: the subtree loses component links, `:touched`, `:remote-synced`. First-level nested heads stay copies, move one nesting level up and inherit the near main's touched groups, so values their old near main overrode become overrides. A swapped head left with no enclosing copy becomes a plain copy of its own component (`:shape-ref` = its main root, no `:swap-group`). - Instantiate / nest a copy: the new subtree has no touched groups. - 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. diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 3bdc5a2622..159dcd78df 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -471,9 +471,10 @@ (update-attrs shape-main shape-inst root-main root-inst main-container false) ;; the copy keeps its swap, and the main takes the copy's overrides (set-touched shape-inst container (when (ctk/swapped? shape-inst) #{:swap-group})) + ;; A previous update is not a reset of the main's authored + ;; overrides. Repeating it must retain the groups it pushed. (set-touched shape-main main-container - (when-not (:remote-synced shape-inst) - (set/union (:touched shape-main) (:touched shape-inst))))) + (set/union (:touched shape-main) (:touched shape-inst)))) (ctsl/any-layout? shape-main) (update-layout-children main-container shape-main shape-inst #(ctf/get-shape-in-copy container % shape-inst) false) diff --git a/common/test/common_tests/logic/comp_remote_synced_test.cljc b/common/test/common_tests/logic/comp_remote_synced_test.cljc new file mode 100644 index 0000000000..b4dac4c336 --- /dev/null +++ b/common/test/common_tests/logic/comp_remote_synced_test.cljc @@ -0,0 +1,38 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns common-tests.logic.comp-remote-synced-test + (:require + [app.common.test-helpers.composable.comp.nodes :as n] + [app.common.test-helpers.composable.comp.runner :as r] + [app.common.test-helpers.composable.comp.setups :as setup] + [app.common.test-helpers.composable.core :as tm] + [app.common.test-helpers.ids-map :as thi] + [clojure.test :as t])) + +(t/use-fixtures :each thi/test-fixture) + +(t/deftest updating-main-keeps-overrides-authored-since-the-previous-update + (let [middle-copy (fn [s] + (:parent-id (tm/shape-by-id s ((n/nested-head-of "a" 1) s)))) + nested-main-rect (fn [s] (n/level-rect s "a" 0)) + situation + (r/run-variant + {:setup setup/empty-situation + :operation + (tm/in-sequence + [(n/create-component "a" "#abcdef" :size 100) + (n/make-nested-component "a" 2) + (n/change-property middle-copy :fills "#ff0000") + (n/update-main middle-copy) + ;; Push a nested override, then update again for another edit. + (n/change-property (n/main-rect-of "a") :name "Authored name") + (n/update-main middle-copy) + (n/change-property middle-copy :opacity 0.4) + (n/update-main middle-copy)])}) + main (tm/shape-by-id situation (nested-main-rect situation))] + (t/is (= "Authored name" (:name main))) + (t/is (contains? (:touched main) :name-group)))) diff --git a/common/test/common_tests/runner.cljc b/common/test/common_tests/runner.cljc index c0a7ddb71c..35bd9033aa 100644 --- a/common/test/common_tests/runner.cljc +++ b/common/test/common_tests/runner.cljc @@ -66,6 +66,7 @@ [common-tests.logic.comp-flex-interactions-test] [common-tests.logic.comp-main-edit-breaks-copy-slots-test] [common-tests.logic.comp-move-swapped-copies-test] + [common-tests.logic.comp-remote-synced-test] [common-tests.logic.comp-reset-test] [common-tests.logic.comp-sync-test] [common-tests.logic.comp-touched-test]