From 0575e0059fc1ef945cf0ecc91eab170c75159d54 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 22 Oct 2026 20:11:08 +0200 Subject: [PATCH] :bug: Restore copy links before overrides when undoing a detach The Phase 4 random harness found F113: undo of detaching an overridden copy restored its touched groups before shape-ref. The copy-only setter therefore dropped those groups. Append the touched and remote-sync undo operations after restoring the shape's attributes. The focused regression failed first and now passes. Common JVM/JS and all 1499 frontend tests pass. Common lint and targeted format checks pass. The frontend test build used shadow-cljs directly because Cargo's cache permissions blocked the wrapper's unrelated WASM rebuild. Benchmark lower-quantile ratios against Phase 1e: main 10/100/1000 copies: 0.46/0.52/0.53; three levels: 0.54; chain: 0.47; library: 0.58; many shapes: 0.68; switch: 0.64. No regression flag. AI-assisted-by: GPT-6 --- .../src/app/common/files/changes_builder.cljc | 6 +++-- .../logic/comp_detach_with_nested_test.cljc | 22 ++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 5bbce80fba..16cdeeed84 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -647,8 +647,10 @@ attrs) [rops uops] (generate-operations attrs old-obj new-obj) - ;; a touch also clears :remote-synced - uops (cond-> uops + ;; Restore links before their copy-only state. Detach + ;; removes shape-ref, so set-touched must run last. + ;; A touch also clears :remote-synced. + uops (cond-> (vec uops) (seq rops) (conj {:type :set-touched :touched (:touched old-obj)}) diff --git a/common/test/common_tests/logic/comp_detach_with_nested_test.cljc b/common/test/common_tests/logic/comp_detach_with_nested_test.cljc index 74e76775c9..8184fa4141 100644 --- a/common/test/common_tests/logic/comp_detach_with_nested_test.cljc +++ b/common/test/common_tests/logic/comp_detach_with_nested_test.cljc @@ -12,12 +12,32 @@ [app.common.test-helpers.compositions :as tho] [app.common.test-helpers.files :as thf] [app.common.test-helpers.ids-map :as thi] + [app.common.test-helpers.normalize :as thn] [app.common.test-helpers.shapes :as ths] [app.common.types.component :as ctk] [clojure.test :as t])) (t/use-fixtures :each thi/test-fixture) +(t/deftest undo-of-detach-restores-overrides-and-remote-sync-state + (let [file (-> (thf/sample-file :file1) + (tho/add-simple-component-with-copy + :component1 :main-root :main-child :copy-root + :copy-root-params {:children-labels [:copy-child]}) + (ths/update-shape :copy-root :opacity 0.4) + (ths/update-shape :copy-child :fills + (ths/sample-fills-color :fill-color "#ff0000")) + (ths/update-shape :copy-child :remote-synced true)) + page (thf/current-page file) + changes (cli/generate-detach-component (pcb/empty-changes) + (thi/id :copy-root) (:data file) + (:id page) {(:id file) file}) + detached (thf/apply-changes file changes) + undone (thf/apply-changes detached {:redo-changes (:undo-changes changes)})] + (t/is (nil? (:shape-ref (ths/get-shape detached :copy-root)))) + (t/is (nil? (:touched (ths/get-shape detached :copy-child)))) + (t/is (= (thn/normalize-file file) (thn/normalize-file undone))))) + ;; Related .penpot file: common/test/cases/detach-with-nested.penpot (defn- setup-file [] @@ -477,4 +497,4 @@ ;; When the nested copy inside the main is detached, their copies are unheaded. (t/is (not (ctk/subcopy-head? nested2-h-ellipse))) - (t/is (not (ctk/subcopy-head? copy-nested2-h-ellipse))))) \ No newline at end of file + (t/is (not (ctk/subcopy-head? copy-nested2-h-ellipse)))))