From bc4edefd8174e57dbee97c49515398362580a2c6 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 23 Oct 2026 20:59:56 +0200 Subject: [PATCH] :bug: Remove the empty override field after clearing a swap When removing the last swap group, drop touched instead of retaining an empty set. A main clone then uses the same representation as set-touched; a later move and undo can restore its exact prior state (F156). The unit and move regressions fail before the change. The swap suite and original shrunk program pass 48 tests and 184 assertions. Common lint and format pass. Benchmark ratios: mains 0.49/0.56/0.79; levels 0.66; chain 0.82; library 0.78; many shapes 0.92; switch 1.02, no regression flags. AI-assisted-by: GPT-6 --- .serena/memories/common/component-data-model.md | 5 +++++ common/src/app/common/types/component.cljc | 9 ++++++--- .../test/common_tests/logic/swap_group_test.cljc | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.serena/memories/common/component-data-model.md b/.serena/memories/common/component-data-model.md index 2ee13c9176..2734207f9e 100644 --- a/.serena/memories/common/component-data-model.md +++ b/.serena/memories/common/component-data-model.md @@ -61,3 +61,8 @@ A variant container is a frame with `:is-variant-container true`. Its children a Predicates are broad: `ctk/is-variant?` checks `:variant-id` and applies to both variant master shapes and component rows; `ctk/is-variant-container?` checks the container shape flag. Moving/dropping a shape into a variant container through the move-to-frame path can auto-convert it into a variant via `generate-make-shapes-variant`, which may duplicate the underlying component. Treat drag/drop into variant containers as a component/variant operation, not a plain reparent. + +- `ctk/remove-swap-group` removes `:touched` when it removes the final group. + 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). diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index b99966435b..05b9f0599b 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -274,9 +274,12 @@ (defn remove-swap-group "Make the shape not swapped." [shape] - (cond-> shape - (swapped? shape) - (update :touched disj :swap-group))) + (if (swapped? shape) + (let [touched (disj (:touched shape) :swap-group)] + (if (seq touched) + (assoc shape :touched touched) + (dissoc shape :touched))) + shape)) (defn get-deleted-component-root [component] diff --git a/common/test/common_tests/logic/swap_group_test.cljc b/common/test/common_tests/logic/swap_group_test.cljc index 66b0557dc8..93766cce1b 100644 --- a/common/test/common_tests/logic/swap_group_test.cljc +++ b/common/test/common_tests/logic/swap_group_test.cljc @@ -805,3 +805,19 @@ head (tm/resolve-shape swapped :copy-star)] (t/is (mth/close? 45 (:rotation head) 0.001)) (t/is (= #{:swap-group} (:touched head))))) + +(t/deftest removing-the-last-swap-group-leaves-no-empty-override-field + (let [shape {:touched #{:swap-group}}] + (t/is (not (contains? (ctk/remove-swap-group shape) :touched))) + (t/is (= #{:geometry-group} + (:touched (ctk/remove-swap-group (assoc shape :touched #{:swap-group :geometry-group}))))))) + +(t/deftest moving-a-swapped-subtree-pushed-into-a-main-undoes-exactly + (let [updated (-> (tm/make-situation (setup)) + (r/step (n/update-main :copy)) r/settle) + file (tm/file updated) + head (tm/shape-by-id updated (:shape-ref (tm/resolve-shape updated :copy-star))) + moved (-> updated (r/step (n/nest-copy (constantly (:id head)) :star-main)) r/settle) + undone (r/step moved (n/undo))] + (t/is (nil? (:touched head))) + (t/is (= (thn/normalize-file file) (thn/normalize-file (tm/file undone))))))