From 67550ebb5cc335bc662a5fa28605730f5d76c7c6 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 23 Oct 2026 14:59:14 +0200 Subject: [PATCH] :bug: Record user undo changes before starting derived component sync Hand applied transitions to the component watcher only after the workspace records their undo entry. Keep the handoff synchronous with the captured prior file data. This preserves transaction order when nesting triggers several component syncs. Fix F137. The minimal replay failed both undo and redo before the fix; its 4 checks now pass. Existing sync scenarios pass their behavior and undo checks; 60 golden digests need review for the accumulated Phase 4 fixes. Common benchmark ratios against Phase 1e: main 10/100/1000 0.46/0.52/0.53; levels 0.47; chain 0.51; library 0.51; many 0.49; switch 0.61. This handoff changes no common benchmark code. AI-assisted-by: GPT-6 --- .../workspace-state-persistence-subtleties.md | 13 ++++++---- frontend/src/app/main/data/changes.cljs | 9 +++++++ frontend/src/app/main/data/workspace.cljs | 24 +++++++++++-------- .../app/main/data/workspace/libraries.cljs | 2 +- .../composable_tests/generated_test.cljs | 3 +++ 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/.serena/memories/frontend/workspace-state-persistence-subtleties.md b/.serena/memories/frontend/workspace-state-persistence-subtleties.md index 201c16c624..373f28aa26 100644 --- a/.serena/memories/frontend/workspace-state-persistence-subtleties.md +++ b/.serena/memories/frontend/workspace-state-persistence-subtleties.md @@ -19,11 +19,16 @@ - `commit-changes` refuses to create commits unless `:permissions :can-edit` is true. It captures file revn/vern, selected-before, features, tags, undo group, and translation flag into a `::commit` event. A refusal answers with an empty stream, never nil, so callers subscribe uniformly instead of branching on nil. - Applying a remote commit first rolls back pending local commits, applies the remote changes, then replays pending local redo changes. Index updates are emitted for undo, remote redo, and replayed redo paths. -- Local commits are independently consumed by undo, persistence, WASM model updates, thumbnail/library watchers, and text position-data recalculation. +- Persistence, WASM model updates, thumbnails and text position-data watchers + consume local commits independently. Undo records applied transitions first. - `dch/changes-applied?` follows local application and carries the commit - with its prior file data. The component watcher syncs each transition - before the next user commit, keeping undo groups adjacent. Its barrier - remains open through the event turn. Foreign file cascades stay in common. + with its prior file data. The workspace records the undo entry, then emits + `dch/changes-recorded?` synchronously with that same transition. Component + sync starts from this handoff, after history records the original edit. + Syncing from `changes-applied?` can record derived commits before the user + commit and reverse the undo/redo order inside a transaction (F137). + The watcher syncs before the next user commit; its barrier remains open + through the event turn. Foreign file cascades stay in common. - Undo changes carry `:app.main.data.workspace.undo/file-id`, separate from change payload fields named `:file-id` (e.g. `:set-tokens-source`). Undo and redo replay consecutive runs per destination in order; old entries diff --git a/frontend/src/app/main/data/changes.cljs b/frontend/src/app/main/data/changes.cljs index 5e9f35be91..b0db0c8856 100644 --- a/frontend/src/app/main/data/changes.cljs +++ b/frontend/src/app/main/data/changes.cljs @@ -38,6 +38,15 @@ (def changes-applied? (ptk/type? ::changes-applied)) +(def changes-recorded? + (ptk/type? ::changes-recorded)) + +(defn changes-recorded + "The commit has applied and its undo entry has been recorded. Derived + edits can now follow it without reversing transaction history." + [transition] + (ptk/data-event ::changes-recorded transition)) + (defn- fix-page-id "For events that modifies the page, page-id does not comes as a property so we assign it from the `id` property." diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index ea18725eb7..3f4ba69dc7 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -494,18 +494,22 @@ (rx/take-until stopper-s))) (->> stream - (rx/filter dch/commit?) + (rx/filter dch/changes-applied?) (rx/map deref) (rx/mapcat - (fn [{:keys [file-id save-undo? undo-changes redo-changes undo-group tags stack-undo? selected-before]}] - (if (and save-undo? (seq undo-changes)) - (let [entry {:undo-changes (mapv #(assoc % ::dwu/file-id file-id) undo-changes) - :redo-changes (mapv #(assoc % ::dwu/file-id file-id) redo-changes) - :undo-group undo-group - :tags tags - :selected-before selected-before}] - (rx/of (dwu/append-undo entry stack-undo?))) - (rx/empty)))))) + (fn [{:keys [commit] :as transition}] + (let [{:keys [file-id save-undo? undo-changes redo-changes undo-group + tags stack-undo? selected-before]} commit] + (rx/concat + (if (and save-undo? (seq undo-changes)) + (let [entry {:undo-changes (mapv #(assoc % ::dwu/file-id file-id) undo-changes) + :redo-changes (mapv #(assoc % ::dwu/file-id file-id) redo-changes) + :undo-group undo-group + :tags tags + :selected-before selected-before}] + (rx/of (dwu/append-undo entry stack-undo?))) + (rx/empty)) + (rx/of (dch/changes-recorded transition)))))))) (rx/take-until stopper-s))))))) (defn initialize-workspace diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 0b973ccaa3..28c37118b6 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -1420,7 +1420,7 @@ local-commits-s (->> stream - (rx/filter dch/changes-applied?) + (rx/filter dch/changes-recorded?) (rx/map deref) (rx/filter #(= :local (get-in % [:commit :source]))) ;; Keep waits pending while component changes are checked. diff --git a/frontend/test/frontend_tests/composable_tests/generated_test.cljs b/frontend/test/frontend_tests/composable_tests/generated_test.cljs index d21402a152..a64bca9ee6 100644 --- a/frontend/test/frontend_tests/composable_tests/generated_test.cljs +++ b/frontend/test/frontend_tests/composable_tests/generated_test.cljs @@ -86,3 +86,6 @@ (t/deftest ^:async nested-text-rotation-shrunk-regression (await (replay {:world [2 0 {:text true}] :ops [[:nest 0] [:detach 0] [:rotate 0]]}))) + +(t/deftest ^:async nesting-restores-default-constraints-on-undo + (await (replay {:world [2 0] :ops [[:nest 0]]})))