From 337b1230b6a020d14e9b041545cc8deb183a6a86 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Sat, 24 Oct 2026 14:22:09 +0200 Subject: [PATCH] :bug: Author pushed content against its new nested reference When a pushed swap takes a position with the same component, preserve content differences as overrides on the promoted head and descendants. The source copy clears its overrides and follows the promoted subtree. Portable regression: three failures before the fix; now passes, including full sync and exact undo. Focused JVM suites: 121 tests, 638 assertions. Component benchmark ratios: mains 0.61/0.78/0.69; levels 0.53; chain 0.50; library 0.56; many shapes 0.75; variant switch 0.61. All worlds pass the baseline performance gate. AI-assisted-by: GPT-6 --- common/src/app/common/logic/libraries.cljc | 38 ++++++++++++++++--- .../common_tests/logic/swap_group_test.cljc | 33 ++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 6f066313fd..b8fca8ae69 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -755,6 +755,30 @@ (and expected (orient-like shape expected (gsh/shape->center shape))) (update :touched ctk/set-touched-group :geometry-group)))) +(defn- author-pushed-content + "Taking a new content reference makes the pushed differences authored. + Placement still follows the position, and existing overrides survive." + [shape reference] + (if (or (nil? reference) (ctk/swapped? shape)) + shape + (let [text? (cfh/text-shape? shape) + groups (into #{} + (keep (fn [attr] + (let [group (ctk/resolve-sync-group (:type shape) attr)] + (when (and group + (not (contains? placement-groups group)) + (not= attr :position-data) + (not (and text? (= attr :content))) + (not= (get shape attr) (get reference attr))) + group)))) + updatable-attrs) + text-groups (when text? + (txt/override-groups (:content reference) (:content shape))) + groups (cond-> (into groups text-groups) + (seq text-groups) (conj :content-group))] + (cond-> shape + (seq groups) (update :touched (fnil into #{}) groups))))) + (defn- add-shape-to-main "Add to the main shape `parent-main`, at `index`, a copy of `child-inst`, a child of the copy shape that pairs with it; `child-inst` then refers to @@ -817,11 +841,15 @@ new-shapes (if swap-in-copy? - (let [objects (d/index-by :id new-shapes) - position (ctf/find-ref-shape nil main-container libraries replaced - :include-deleted? true - :with-context? true)] - (mapv (ctf/take-position nil libraries objects new-head (:shape-ref replaced) position) + (let [objects (d/index-by :id new-shapes) + position (ctf/find-ref-shape nil main-container libraries replaced + :include-deleted? true + :with-context? true) + take-position (ctf/take-position nil libraries objects new-head (:shape-ref replaced) position) + position-objects (get-in (meta position) [:container :objects])] + (mapv (fn [shape] + (let [shape (take-position shape)] + (author-pushed-content shape (get position-objects (:shape-ref shape))))) new-shapes)) new-shapes) diff --git a/common/test/common_tests/logic/swap_group_test.cljc b/common/test/common_tests/logic/swap_group_test.cljc index 7a5b46a607..e31be06860 100644 --- a/common/test/common_tests/logic/swap_group_test.cljc +++ b/common/test/common_tests/logic/swap_group_test.cljc @@ -933,3 +933,36 @@ (:points (ths/get-shape-by-id full (:id head))) 0.001)) (t/is (= (thn/normalize-file (tm/file swapped)) (thn/normalize-file (tm/file (r/step updated (n/undo)))))))) + +(t/deftest pushing-a-swap-authors-content-that-differs-from-its-new-position + (let [file (-> (setup) + (tho/add-frame :page-main) + (thc/instantiate-component :card :inner-copy + :parent-label :page-main + :children-labels [:inner-star]) + (thc/make-component :page :page-main) + (thc/instantiate-component :page :outer-copy + :children-labels [:outer-inner :outer-star])) + swapped (-> (tm/make-situation file) + (r/step (n/swap-shape :outer-star :heart)) r/settle + (r/step (n/swap-shape :card-star :heart)) r/settle) + child-id (first (:shapes (tm/resolve-shape swapped :card-star))) + edited (-> swapped + (r/step (n/change-property :card-star :fills "#333333")) r/settle + (r/step (n/change-property (constantly child-id) :fills "#444444")) r/settle) + updated (-> edited (r/step (n/update-main :outer-copy)) r/settle) + head (tm/resolve-shape updated :outer-star) + main (tm/shape-by-id updated (:shape-ref head)) + main-child (tm/shape-by-id updated (first (:shapes main))) + file (tm/file updated) + full (:file (r/sync-cascade file {} (keys (get-in file [:data :components]))))] + (t/is (ctk/swapped? (tm/resolve-shape edited :outer-star))) + (t/is (= (thi/id :card-star) (:shape-ref main))) + (t/is (= #{:fill-group} (:touched main) (:touched main-child))) + (t/is (= (:fills (tm/resolve-shape edited :outer-star)) (:fills main))) + (t/is (= "#222222" (get-in main-child [:fills 0 :fill-color]))) + (t/is (empty? (:touched head))) + (t/is (= (:fills main) (:fills (ths/get-shape-by-id full (:id main))))) + (t/is (= (:fills main-child) (:fills (ths/get-shape-by-id full (:id main-child))))) + (t/is (= (thn/normalize-file (tm/file edited)) + (thn/normalize-file (tm/file (r/step updated (n/undo))))))))