From 357e8a7ff7133697d0c0814fc1e1ec8df378ae20 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Sat, 24 Oct 2026 09:17:12 +0200 Subject: [PATCH] :bug: Stop inherited content overrides at swapped references Walk content refs through intermediate swapped heads when collecting inherited touched groups. Following the replaced position instead crosses into another component and gives copies phantom geometry overrides (F165). Position advancement keeps its separate position-ref walk. A portable three-level regression fails both chain and touched assertions before the fix. Swap-group, file and variant suites pass 124 tests and 588 assertions; the shrunk library seed-712 program passes as well. Common lint and format pass. Benchmark lower-quantile ratios against Phase 1e: main 10/100/1000 copies 0.48/0.55/0.57, three levels 0.48, chain 0.47, library 0.49, many shapes 0.46, variant switch 0.57. No regression flags. AI-assisted-by: GPT-6 --- .../memories/common/component-data-model.md | 6 +++++ common/src/app/common/types/file.cljc | 8 +++---- .../common_tests/logic/swap_group_test.cljc | 22 +++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/.serena/memories/common/component-data-model.md b/.serena/memories/common/component-data-model.md index 2734207f9e..b8baf24fbd 100644 --- a/.serena/memories/common/component-data-model.md +++ b/.serena/memories/common/component-data-model.md @@ -66,3 +66,9 @@ Moving/dropping a shape into a variant container through the move-to-frame path New main clones must use the same empty-state representation as `process-operation :set-touched`; otherwise a later move+undo changes `:touched #{}` to a missing field and cannot restore the exact state (F156). + +Content ref chains (get-ref-chain-until-target-ref, including touched walks) +take find-content-ref-shape at every link. A swapped intermediate head +follows its own main; collecting its replaced position's content overrides +creates phantom groups in copies of that swap (F165). Position advancement +still uses advance-shape-ref and find-ref-shape. diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index bea1989384..bdab828fba 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -1257,16 +1257,16 @@ ;; Ref Chains (defn get-ref-chain-until-target-ref - "The shape `shape` of `container` and its ref chain (each shape's near - main, found in the container of the one before), up to the shape whose id - is `target-ref`, included, or to the end of the chain." + "The shape `shape` of `container` and its content ref chain, up to the + shape whose id is `target-ref`, included, or to the end of the chain. + A swapped head follows its own component, not the replaced position." [container libraries shape target-ref] (loop [chain [shape] current shape container container] (if (= (:id current) target-ref) chain - (if-let [ref (find-ref-shape nil container libraries current :with-context? true)] + (if-let [ref (find-content-ref-shape nil container libraries current :with-context? true)] (recur (conj chain ref) ref (:container (meta ref))) chain)))) diff --git a/common/test/common_tests/logic/swap_group_test.cljc b/common/test/common_tests/logic/swap_group_test.cljc index e7bc4676ec..45cc06ed89 100644 --- a/common/test/common_tests/logic/swap_group_test.cljc +++ b/common/test/common_tests/logic/swap_group_test.cljc @@ -34,6 +34,7 @@ [app.common.test-helpers.variants :as thv] [app.common.types.component :as ctk] [app.common.types.container :as ctn] + [app.common.types.file :as ctf] [app.common.types.modifiers :as ctm] [clojure.test :as t])) @@ -877,3 +878,24 @@ swapped (-> initial (r/step (n/swap-shape :card-star :heart)) r/settle) undone (r/step swapped (n/undo))] (t/is (= (thn/normalize-file file) (thn/normalize-file (tm/file undone)))))) + +(t/deftest inherited-content-overrides-stop-at-a-swapped-reference + (let [file (-> (thf/sample-file :file) + (tho/add-two-levels-nested-component-with-copy + :c1 :main1 :main1-child :c2 :main2 :nested-head1 + :c3 :main3 :nested-head2 :nested-subhead2 :copy3) + (tho/add-simple-component :other :other-main :other-child)) + file (-> (tm/make-situation file) + (r/step (n/change-property :nested-head1 :opacity 0.4)) + tm/file + (tho/swap-component-in-shape :nested-subhead2 :other + :propagate-fn #(propagate % :c3))) + page (thf/current-page file) + middle (ths/get-shape-by-id file (first (:shapes (ths/get-shape file :copy3)))) + child (ths/get-shape-by-id file (first (:shapes middle))) + libraries {(:id file) file} + chain (ctf/get-ref-chain-until-target-ref page libraries child nil)] + (t/is (= [(:id child) (thi/id :nested-subhead2) (thi/id :other-main)] + (mapv :id chain))) + (t/is (= #{:swap-group} + (ctf/get-touched-from-ref-chain-until-target-ref page libraries child nil)))))