From ceb24fbf6d0b7a54063be9e481dc62a56101ca95 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 28 Oct 2026 11:38:32 +0100 Subject: [PATCH] :bug: Keep flex deletion and reflow in one undo group Use the deletion's undo group for its delayed layout update, reusing an enclosing transaction when present. Store that group on the primary changes so deleting a child and moving its siblings undo together. Add real-event regressions for standalone deletion, an explicit group and an outer transaction. Verify exact snapshots after one undo and redo. AI-assisted-by: GPT-6 --- .../src/app/main/data/workspace/shapes.cljs | 9 ++-- .../completion_contract_test.cljs | 41 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index b5e738d33c..5f23d84fd4 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -402,19 +402,22 @@ objects (:objects page) deleted-ids (into #{} (mapcat #(cfh/get-children-ids-with-self objects %)) ids) - undo-id (or (:undo-id options) (js/Symbol)) + undo-id (or (:undo-id options) (js/Symbol)) + undo-group (or (:undo-group options) + (get-in state [:workspace-undo :transaction :undo-group]) + (uuid/next)) [all-parents changes] (-> (pcb/empty-changes it (:id page)) + (pcb/set-undo-group undo-group) (cls/generate-delete-shapes fdata page objects ids {:ignore-touched (:allow-altering-copies options) - :undo-group (:undo-group options) :undo-id undo-id}))] (wrf/cancel-shapes! deleted-ids) (rx/of (dwu/start-undo-transaction undo-id) (dc/detach-comment-thread ids) (dch/commit-changes changes) - (ptk/data-event :layout/update {:ids all-parents :undo-group (:undo-group options)}) + (ptk/data-event :layout/update {:ids all-parents :undo-group undo-group}) (dwu/commit-undo-transaction undo-id))))))) (defn create-and-add-shape diff --git a/frontend/test/frontend_tests/composable_tests/completion_contract_test.cljs b/frontend/test/frontend_tests/composable_tests/completion_contract_test.cljs index 895bdc7792..4af9294de6 100644 --- a/frontend/test/frontend_tests/composable_tests/completion_contract_test.cljs +++ b/frontend/test/frontend_tests/composable_tests/completion_contract_test.cljs @@ -30,6 +30,7 @@ [app.common.test-helpers.shapes :as ths] [app.common.test-helpers.texts :as thtx] [app.common.time :as ct] + [app.common.uuid :as uuid] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.reflow :as wrf] [app.main.data.workspace.shapes :as dwsh] @@ -53,6 +54,46 @@ (t/use-fixtures :each ftm/fixture) +(t/deftest ^:async deleting-a-flex-child-undoes-with-its-delayed-reflow + (await + (mock/with-mocks* {rp/cmd! mock/rpc-cmd-mock rx/timer mock/timer-mock} + (doseq [mode [:standalone :explicit-group :outer-transaction]] + (let [file (-> (thf/sample-file :file) + (tho/add-frame :main-root (merge cls/initial-flex-layout + {:width 300 :height 100 + :layout-gap {:row-gap 5 :column-gap 9}})) + (tho/add-rect :child-a :parent-label :main-root :width 50 :height 30) + (tho/add-rect :child-b :parent-label :main-root :width 50 :height 30) + (tho/add-rect :child-c :parent-label :main-root :width 50 :height 30) + (thc/make-component :component :main-root) + (thc/instantiate-component :component :copy)) + _ (ftm/install! (tm/make-situation file)) + group (uuid/next) + outer-id (js/Symbol)] + (t/is (= :settled (await (ftm/await-step + [(ptk/data-event :layout/update {:ids [(thi/id :main-root) (thi/id :copy)]})])))) + (let [before (ftm/snapshot) + x (:x (ths/get-shape (ftm/current-file) :child-a)) + events (cond-> [] + (= mode :outer-transaction) + (into [(dwu/start-undo-transaction outer-id) + (dwsh/update-shapes #{(thi/id :main-root)} #(assoc % :opacity 0.4) + {:undo-group group})]) + true + (conj (dwsh/delete-shapes nil #{(thi/id :child-b)} + (if (= mode :explicit-group) {:undo-group group} {}))) + (= mode :outer-transaction) + (conj (dwu/commit-undo-transaction outer-id)))] + (t/is (= :settled (await (ftm/await-step events)))) + (let [after (ftm/snapshot)] + (t/is (nil? (ths/get-shape (ftm/current-file) :child-b))) + (t/is (= 2 (count (:shapes (ths/get-shape (ftm/current-file) :copy))))) + (t/is (not= x (:x (ths/get-shape (ftm/current-file) :child-a)))) + (t/is (= :settled (await (ftm/await-step [dwu/undo])))) + (t/is (uc/same-state? before (ftm/snapshot)) (str "one undo restores " mode)) + (t/is (= :settled (await (ftm/await-step [dwu/redo])))) + (t/is (uc/same-state? after (ftm/snapshot)) (str "one redo restores " mode))))))))) + (t/deftest ^:async swapping-growing-text-undoes-with-the-component-choice (let [features (:features @st/state)] (thw/setup-wasm-mocks!)