🐛 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
This commit is contained in:
alonso.torres 2026-10-22 20:11:08 +02:00
parent 9c40c84b3b
commit 0575e0059f
2 changed files with 25 additions and 3 deletions

View File

@ -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)})

View File

@ -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)))))
(t/is (not (ctk/subcopy-head? copy-nested2-h-ellipse)))))