🐛 Keep pending library edits out of transform settlement

Use the component sync result only as a geometry proposal when settling
reflections and swapped placement. Correct existing shapes without pulling
pending library content, styles or structure into a transformed copy.
Keep path geometry and the primary action's exact undo/redo group.

Generate source transforms, letters and all twelve text style units with
separate source histories and explicit consumer acceptance. Add dropped
source-edit controls and portable/frontend regressions for the accidental
pull. Register the new portable namespace in the JavaScript runner.
The complete original program and both reductions pass unchanged.

Common JVM passes 1,919 tests/31,313 assertions. Full JS passes
1,845/29,770; the newly registered source namespace adds 5/135 passing
checks. Frontend passes 1,566/11,119, backend 1,610/8,104 and exporter
14/32. Lint and formatting pass; browser ShapeProxy replay passes.
No golden data changes.

Benchmark ratios against the unchanged baseline: mains 10/100/1000
0.57/0.65/0.61, levels 0.52, chain 0.51, library 0.52, many shapes 0.47,
variant switch 0.60. No regression flag.

AI-assisted-by: gpt-6
This commit is contained in:
alonso.torres 2026-10-27 14:21:46 +01:00
parent 188dac5ad3
commit 2e542dc8cf
7 changed files with 274 additions and 40 deletions

View File

@ -62,6 +62,9 @@ Intended semantics of copies, `:touched` and component sync. This is the spec: w
- Children of a resized or transformed shape: derived (expected geometry), untouched, and still follow later main edits. Code: a transform touches only the shapes the user transformed (`clp/calculate-ignore-tree` takes their ids; the frontend keeps them in `:workspace-modifiers-ids`); sync places the main subtree of a copy shape with its own geometry like that shape before walking its children.
- Layout-owned (never touched; sync writes, then reflow): x/y of non-absolute children of flex/grid frames; width/height on axes sized fill, or auto on a layout frame.
- Grow-owned (never touched; sync writes, then re-measure on SVG and WASM): width+height of auto-width text, height of auto-height text. Every editor and resize path writes the measured size as derived; on WASM `sync-file` re-measures the auto-grow texts of the current page it wrote (texts on other pages keep the synced size until measured).
- Transform settlement corrects geometry on the existing copy subtree
(F223). Its sync proposal must not pull pending library content, styles
or structure. Path content can carry geometry; text content cannot.
- `set-shape-attr`'s `ignore-geometry` does not cover `:width`/`:height`; a derived size write needs `ignore-touched` (transforms get it from the ignore tree).
- Repairs are not overrides: `files/repair` writes with `ignore-touched`; only the text repairs set groups, explicitly.

View File

@ -120,6 +120,15 @@ Settlement also checks partial/full sync equivalence, idempotence and exact
undo/redo snapshots. Library routing in `comp/files` applies both the working
file and auxiliary file changes, then cascades both in the same undo group.
Stale-library and dependency generation includes source transforms on main
roots/children, letters and all twelve text style units. `:library-text true`
adds a separate source text component without reusing consumer shape IDs.
Source edits keep consumer history separate and copies stale until an
acceptance or command that explicitly refreshes that subtree. Requested
source geometry/text checks use the source editor's before-state and intent;
dropped source edits must fail I9. `common-tests.logic.source-gen-test`
covers the action matrix and independent source/consumer undo histories.
Backend parity uses `oracle/canonical` and `oracle/equivalent?`: IDs become
file/page/tree paths, flip
nil/false values become booleans, and derived layout/text dimensions and text

View File

@ -333,11 +333,17 @@
changes)
changes)))
(def ^:private placement-geometry-attrs
#{:x :y :width :height :selrect :points :transform :transform-inverse
:rotation :flip-x :flip-y :content-scale})
(defn generate-sync-transformed-placement
"Settle copy descendants after a reflection and untouched swapped heads
after a parent turn, in the transform's change group. Reflection can
leave descendant flip flags in another decomposition; swapped content
can have a different size from its position frame."
can have a different size from its position frame. Keep this correction
within existing geometry: transforming a stale library copy must not
accept pending content, style or structural changes."
[changes file libraries page before ids]
(let [objects (pcb/get-objects changes)
orientation (fn [shape]
@ -362,11 +368,21 @@
(let [objects (pcb/get-objects changes)
page (assoc page :objects objects)
file (assoc-in file [:data :pages-index (:id page) :objects] objects)
container (ctn/make-container page :page)]
(-> changes
(pcb/with-container container)
(generate-sync-shape-direct file (assoc libraries (:id file) file)
container id false))))
container (ctn/make-container page :page)
changes (pcb/with-container changes container)
proposal (generate-sync-shape-direct changes file (assoc libraries (:id file) file)
container id false)
placed (pcb/get-objects proposal)]
(pcb/update-shapes
changes (cfh/get-children-ids-with-self objects id)
(fn [shape]
(let [expected (get placed (:id shape))
attrs (cond-> placement-geometry-attrs
(= :path (:type shape)) (conj :content))]
(if (= (:type shape) (:type expected))
(merge shape (select-keys expected attrs))
shape)))
{:ignore-touched true})))
changes roots)))
(defn- sync-copy-shape

View File

@ -75,6 +75,20 @@
[files]
(sort-by (fn [[id file]] [(:name file) (str (thi/label id))]) files))
(defn- with-source-text
[file]
(-> file
(tho/add-frame-with-text :library-text-root :library-text-child "Library text"
:child-params {:grow-type :auto-height})
(thc/make-component :library-text-component :library-text-root)
(thc/instantiate-component :library-text-component :library-text-copy)
(ths/add-sample-typography :generated-typography
:font-id "gfont-roboto" :font-family "Roboto"
:font-variant-id "700italic" :font-weight "700"
:font-style "italic" :font-size "22"
:line-height "1.6" :letter-spacing "2"
:text-transform "uppercase")))
(defn- with-library
[situation opts]
(let [library (-> (tm/make-situation (thf/sample-file :random-library :name "random-library" :is-shared true))
@ -87,7 +101,8 @@
s))
(as-> s (if (and (:library-variants opts) (:rich-variants opts))
(variants/diversify s "library-variants") s))
tm/file)
tm/file
(cond-> (:library-text opts) with-source-text))
dependent (when (:library-dependency opts)
(-> (tm/make-situation (thf/sample-file :dependent-library :name "dependent-library" :is-shared true))
(as-> s (tm/apply-to (n/create-component "library-dependent" setup/labeled-fill :size 100) s))
@ -269,10 +284,10 @@
(def library-source-kinds
#{:library-edit :library-add-child :library-remove-child :library-delete :library-restore
:library-undo :library-redo :library-accept})
:library-undo :library-redo :library-accept :library-transform :library-text :library-text-style})
(defn- library-candidates
[situation kind]
[situation kind scope]
(vec
(for [[library-index [library-id library]]
(map-indexed vector (ordered-files (tm/aux-files situation)))
@ -299,6 +314,15 @@
(case kind
(:library-edit :library-remove-child)
(and (= :rect (:type shape)) (nil? (:shape-ref shape)))
:library-transform
(and (not= :text (:type shape)) (nil? (:shape-ref shape))
(if (even? (or scope 0))
(ctk/main-instance? shape)
(and (not (ctk/main-instance? shape))
(some ctk/main-instance?
(cfh/get-parents (:objects (thf/current-page library)) (:id shape))))))
(:library-text :library-text-style)
(and (= :text (:type shape)) (nil? (:shape-ref shape)))
(:library-add-child :library-delete) (ctk/main-instance? shape)))
(tree (tm/with-file situation library))))]
(-> entry
@ -360,7 +384,7 @@
(let [index (or index 0)
[style-property style-values] (nth style-edits (mod (or property 0) (count style-edits)))
items (cond->> (if (contains? library-source-kinds kind)
(library-candidates situation kind)
(library-candidates situation kind scope)
(candidates situation model (if (= kind :style) :edit kind) scope))
(= kind :style)
(filter (fn [{:keys [shape]}]
@ -399,15 +423,15 @@
(when target
(case kind
(:edit :style) (n/change-property target property value)
:transform (let [action (nth [:move :width :height :rotate :flip-x :flip-y]
(mod (or (nth tuple 2 nil) 0) 6))
value (or (nth tuple 3 nil) 0)]
(n/transform-shape target action
(case action
:move [(* 5 (inc (mod value 4))) (* -3 (inc (mod value 3)))]
(:width :height) (+ 80 (* 20 (mod value 6)))
:rotate (* 45 (inc (mod value 4)))
nil)))
(:transform :library-transform) (let [action (nth [:move :width :height :rotate :flip-x :flip-y]
(mod (or (nth tuple 2 nil) 0) 6))
value (or (nth tuple 3 nil) 0)]
(n/transform-shape target action
(case action
:move [(* 5 (inc (mod value 4))) (* -3 (inc (mod value 3)))]
(:width :height) (+ 80 (* 20 (mod value 6)))
:rotate (* 45 (inc (mod value 4)))
nil)))
:library-edit (n/change-property target property value)
:library-add-child (n/add-child target (keyword (str "library-generated-" (count (:applied situation))))
:width 20 :height 20 :x 10 :y 10)
@ -419,17 +443,21 @@
:detach (n/detach target)
:resize (n/change-height target (+ 80 (* 20 (mod (or (nth tuple 3 nil) 0) 6))))
:rotate (n/rotate target (* 45 (inc (mod (or (nth tuple 3 nil) 0) 4))))
:text (let [text (thtx/view (:content (:shape selected)))
text (if (even? (or (nth tuple 2 nil) 0))
(thtx/set-letters text (nth ["Hello world" "Hello there" "Hello\nworld"] (mod (or (nth tuple 3 nil) 0) 3)))
(thtx/restyle text :size (nth [14 18 24] (mod (or (nth tuple 3 nil) 0) 3))))]
(n/edit-text target :content text))
:text-style (n/edit-text target :content
(edit-text-style (:shape selected) (nth tuple 2 nil) (nth tuple 3 nil))
(when (and (= 8 (mod (or (nth tuple 2 nil) 0) (count text-style-edits)))
(odd? (or (nth tuple 3 nil) 0)))
{:typography {:id (thi/id :generated-typography)
:file-id (:id (tm/file situation))}}))
(:text :library-text) (let [text (thtx/view (:content (:shape selected)))
text (if (even? (or (nth tuple 2 nil) 0))
(thtx/set-letters text (nth ["Hello world" "Hello there" "Hello\nworld"] (mod (or (nth tuple 3 nil) 0) 3)))
(thtx/restyle text :size (nth [14 18 24] (mod (or (nth tuple 3 nil) 0) 3))))]
(n/edit-text target :content text))
(:text-style :library-text-style)
(let [typography? (and (= 8 (mod (or (nth tuple 2 nil) 0) (count text-style-edits)))
(odd? (or (nth tuple 3 nil) 0)))
file-id (or (:library-id selected) (:id (tm/file situation)))
content (cond-> (edit-text-style (:shape selected) (nth tuple 2 nil) (nth tuple 3 nil))
(and typography? (:library-id selected))
(thtx/restyle :typography-ref-file file-id))]
(n/edit-text target :content content
(when typography?
{:typography {:id (thi/id :generated-typography) :file-id file-id}})))
:add-child (n/add-child target (keyword (str "generated-" (count (:applied situation))))
:width 20 :height 20 :x 10 :y 10)
:remove-child (n/remove-child target)
@ -589,7 +617,7 @@
modifiers (case kind
:resize (ctm/change-size shape nil (:value op))
:rotate (n/geometry-modifiers shape :rotate (:angle op))
:transform (n/geometry-modifiers shape (:action op) (:value op)))
(:transform :library-transform) (n/geometry-modifiers shape (:action op) (:value op)))
tree (gm/set-objects-modifiers {(:id shape) {:modifiers modifiers}} objects)]
(gsh/transform-shape shape (get-in tree [(:id shape) :modifiers]))))
@ -688,16 +716,19 @@
(> (r/undo-depth after) (r/undo-depth situation)))
(update-in (conj coverage-path :undo-only) (fnil inc 0)))
(update-in [:executed kind] (fnil inc 0)))]
(when (#{:resize :rotate :transform} kind)
(oracle/check-requested-geometry (:objects (thf/current-page (tm/file after)))
(tm/shape-by-id after (get-in resolved [:shape :id]))
(requested-geometry situation resolved kind)))
(when (#{:resize :rotate :transform :library-transform} kind)
(let [before (if library-id (source-context situation library-id) situation)
after (if library-id (source-context after library-id) after)]
(oracle/check-requested-geometry (:objects (thf/current-page (tm/file after)))
(tm/shape-by-id after (get-in resolved [:shape :id]))
(requested-geometry before resolved kind))))
(when (#{:swap :switch} kind)
(let [head (tm/shape-by-id after (get-in resolved [:shape :id]))]
(when-not (= (get-in resolved [:destination :component-id]) (:component-id head))
(oracle/fail :I9 {:phase :requested-component :kind kind}))))
(when (#{:text :text-style} kind)
(let [actual (tm/shape-by-id after (get-in resolved [:shape :id]))
(when (#{:text :text-style :library-text :library-text-style} kind)
(let [after (if library-id (source-context after library-id) after)
actual (tm/shape-by-id after (get-in resolved [:shape :id]))
expected (thtx/normalize (:value op))]
(when-not (= expected (thtx/view (:content actual)))
(oracle/fail :I9 {:phase :requested-text :expected expected
@ -845,6 +876,14 @@
(contains? (set kinds) :text-style)
(conj [2 (gen/fmap (fn [[index unit]] [[:text-style index unit 1]])
(gen/tuple gen/nat (gen/choose 0 (dec (count text-style-edits)))))])
(contains? (set kinds) :library-transform)
(conj [2 (gen/fmap (fn [[index action scope]] [[:library-transform index action 4 scope]])
(gen/tuple gen/nat (gen/choose 0 5) (gen/choose 0 1)))])
(contains? (set kinds) :library-text-style)
(conj [2 (gen/fmap (fn [[index unit]] [[:library-text-style index unit 1]])
(gen/tuple gen/nat (gen/choose 0 (dec (count text-style-edits)))))])
(contains? (set kinds) :library-text)
(conj [2 (gen/fmap (fn [index] [[:library-text index 0 1]]) gen/nat)])
(every? (set kinds) [:undo :redo])
(conj [1 (gen/return [[:undo] [:redo]])])
(contains? (set kinds) :text)
@ -928,17 +967,18 @@
library-depth (gen/choose 1 2)
layout (gen/elements [nil :flex :grid])
ops (operation-gen stale-library-kinds)]
{:world [2 depth {:library true :stale-libraries true :library-variants true
{:world [2 depth {:library true :stale-libraries true :library-variants true :library-text true
:library-depth library-depth :variants true :rich-variants true :text true :text-styles true
:layout layout :layout-copies true}]
:ops ops}))
(def dependency-kinds
[:edit :reset :swap :sync :undo :redo :library-sync :library-edit :library-accept
:library-add-child :library-remove-child :library-delete :library-restore :library-undo :library-redo])
:library-add-child :library-remove-child :library-delete :library-restore :library-undo :library-redo
:library-transform :library-text :library-text-style])
(def dependency-program-gen
(gen/let [depth (gen/choose 0 1)
ops (operation-gen dependency-kinds)]
{:world [2 depth {:library true :stale-libraries true :library-dependency true}]
{:world [2 depth {:library true :stale-libraries true :library-dependency true :library-text true}]
:ops ops}))

View File

@ -0,0 +1,93 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns common-tests.logic.source-gen-test
(:require
[app.common.test-helpers.composable.comp.runner :as r]
[app.common.test-helpers.composable.core :as tm]
[app.common.test-helpers.composable.gen :as cg]
[app.common.test-helpers.composable.oracle :as oracle]
[app.common.test-helpers.ids-map :as thi]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(def source-world
[2 1 {:library true :stale-libraries true :library-text true :text true :text-styles true}])
(t/deftest a-transform-does-not-accept-pending-library-content-or-structure
(doseq [[edit index] [[[:library-text 0 1 1] 8]
[[:library-text 0 0 1] 8]
[[:library-edit 0 0 1] 4]
[[:library-add-child 0] 4]]]
(t/is (map? (cg/run {:world [2 0 {:library true :stale-libraries true :library-text true}]
:ops [edit [:transform index 3 3 4] [:undo] [:redo]
[:library-sync]]}))
(str edit))))
(t/deftest source-transforms-keep-consumers-stale-until-acceptance
(doseq [action (range 6) scope [0 1]]
(let [initial (cg/initial-state source-world)
situation (:situation initial)
tuple [:library-transform 0 action 4 scope]
resolved (cg/resolve-op situation {} tuple)
owner (:library-id resolved)]
(t/is (some? (:op resolved)) (str "source transform " action ", scope " scope))
(when (:op resolved)
(let [edited (-> initial (cg/step tuple) cg/settle-state)
source (get (oracle/snapshot (:situation edited)) owner)]
(t/is (not= (get (oracle/snapshot situation) owner) source))
(t/is (= (dissoc (oracle/snapshot situation) owner)
(dissoc (oracle/snapshot (:situation edited)) owner)))
(t/is (zero? (r/undo-depth (:situation edited))))
(t/is (map? (cg/run {:world source-world
:ops [tuple [:library-sync] [:undo] [:redo]
[:library-undo] [:library-redo] [:library-sync]]}))))))))
(t/deftest source-text-edits-cover-letters-and-every-style-unit
(doseq [tuple (into [[:library-text 0 0 1]]
(map #(vector :library-text-style 0 % 1) (range 12)))]
(let [initial (cg/initial-state source-world)
situation (:situation initial)
resolved (cg/resolve-op situation {} tuple)
owner (:library-id resolved)]
(t/is (some? (:op resolved)) (str tuple))
(when (:op resolved)
(let [edited (-> initial (cg/step tuple) cg/settle-state)]
(t/is (not= (get (oracle/snapshot situation) owner)
(get (oracle/snapshot (:situation edited)) owner)))
(t/is (= (dissoc (oracle/snapshot situation) owner)
(dissoc (oracle/snapshot (:situation edited)) owner)))
(t/is (zero? (r/undo-depth (:situation edited))))
(t/is (map? (cg/run {:world source-world
:ops [tuple [:library-sync] [:undo] [:redo]
[:library-undo] [:library-redo] [:library-sync]]}))))))))
(t/deftest source-intent-checks-reject-a-dropped-primary-edit
(doseq [tuple [[:library-transform 0 1 4 0]
[:library-transform 0 3 1 1]
[:library-text 0 0 1]
[:library-text-style 0 2 1]]]
(let [initial (cg/initial-state source-world)]
(t/is (= :I9
(try
(with-redefs [r/step (fn [situation _] situation)]
(cg/step initial tuple))
nil
(catch #?(:clj Exception :cljs js/Error) error
(:invariant (ex-data error)))))
(str "dropped source intent " tuple)))))
(t/deftest source-text-fixture-does-not-reuse-consumer-shape-ids
(let [situation (cg/world source-world)
entries (cg/forest situation)
ids (map (comp :id :shape) entries)
source-texts (for [[_ library] (tm/aux-files situation)
entry (cg/tree (tm/with-file situation library))
:when (= :text (get-in entry [:shape :type]))]
entry)]
(t/is (= (count ids) (count (set ids))))
(t/is (seq source-texts))))

View File

@ -83,6 +83,7 @@
[common-tests.logic.multiple-nesting-levels-test]
[common-tests.logic.override-scan-test]
[common-tests.logic.placement-test]
[common-tests.logic.source-gen-test]
[common-tests.logic.swap-and-reset-test]
[common-tests.logic.swap-as-override-test]
[common-tests.logic.swap-group-test]
@ -215,6 +216,7 @@
'common-tests.logic.multiple-nesting-levels-test
'common-tests.logic.override-scan-test
'common-tests.logic.placement-test
'common-tests.logic.source-gen-test
'common-tests.logic.swap-and-reset-test
'common-tests.logic.swap-as-override-test
'common-tests.logic.swap-group-test

View File

@ -25,6 +25,7 @@
[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.test-helpers.texts :as thtx]
[app.common.time :as ct]
@ -35,6 +36,7 @@
[app.main.data.workspace.thumbnails-wasm :as dwthw]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.wasm-text :as dwwt]
[app.main.repo :as rp]
[app.main.store :as st]
[app.render-wasm.api :as wasm]
[app.render-wasm.api.fonts :as wasm.fonts]
@ -395,6 +397,75 @@
(t/is (= red (fill (:id first-copy))))
(t/is (= setup/labeled-fill (fill (:id other-copy))))))
(defn- ^:async source-acceptance-history
[tuple transform-tuple]
(let [initial (cg/initial-state [2 1 {:library true :stale-libraries true :library-text true}])
situation (:situation initial)
owner (:library-id (cg/resolve-op situation {} tuple))
transform-tuple (when transform-tuple
(assoc transform-tuple 1
(first (keep-indexed
(fn [index entry]
(when (= (thi/id :library-text-component)
(get-in entry [:shape :component-id])) index))
(cg/candidates situation {} :transform 4)))))
edited (-> initial (cg/step tuple) cg/settle-state)
group (peek (get-in edited [:situation :library-histories owner :runner ::r/undo-stack]))
changes (mapcat :redo-changes group)
;; The notification envelope advances the loaded library revision.
edited (update edited :situation
#(tm/with-aux-files % (update (tm/aux-files %) owner assoc :revn 1)))
_ (ftm/start-generated situation)
current-id (:id (tm/file situation))
before (get-in (ftm/snapshot) [:files current-id])]
(t/is (seq changes) (str "source edit changes " tuple))
(t/is (= :settled (await (ftm/await-step [(dwl/ext-library-changed owner (ct/now) 1 changes)]))))
(t/is (= before (get-in (ftm/snapshot) [:files current-id])) "notification keeps consumer copies stale")
(t/is (empty? (get-in @st/state [:workspace-undo :items])) "notification creates no consumer history")
(when transform-tuple
(let [before-transform (ftm/snapshot)
resolved (cg/resolve-op situation {} transform-tuple)
content (fn []
(into {} (map (fn [{:keys [shape]}]
[(:id shape) (select-keys shape [:content :name :fills :strokes :shapes :opacity])]))
(cg/tree (ftm/refresh-generated situation))))
before-content (content)]
(t/is (= :settled (await (ftm/await-step (ftm/op->events (:op resolved) situation)))))
(t/is (= before-content (content)) "a transform keeps pending source content and structure stale")
(let [after-transform (ftm/snapshot)]
(t/is (= :settled (await (ftm/await-step [dwu/undo]))))
(t/is (uc/same-state? before-transform (ftm/snapshot)))
(t/is (= :settled (await (ftm/await-step [dwu/redo]))))
(t/is (uc/same-state? after-transform (ftm/snapshot))))))
(let [edited (cond-> edited transform-tuple (cg/step transform-tuple))
before-accept (ftm/snapshot)
expected (-> edited (cg/step [:library-sync]) cg/settle-state)]
(t/is (= :settled (await (ftm/await-step [(dwl/sync-file current-id owner)]))))
(let [actual (ftm/refresh-generated (:situation expected))
after-accept (ftm/snapshot)
expected-design (oracle/canonical (:situation expected))
actual-design (oracle/canonical actual)]
(t/is (true? (oracle/equivalent? expected-design actual-design))
(str "consumer acceptance " tuple ": " (pr-str (thn/diff expected-design actual-design))))
(t/is (nil? (oracle/check actual (:model expected))))
(t/is (= :settled (await (ftm/await-step [dwu/undo]))))
(t/is (uc/same-state? before-accept (ftm/snapshot)) "undo restores the stale consumer only")
(t/is (= :settled (await (ftm/await-step [dwu/redo]))))
(t/is (uc/same-state? after-accept (ftm/snapshot)) "redo restores accepted geometry and text")))))
(t/deftest ^:async source-geometry-and-text-wait-for-consumer-acceptance
(await
(mock/with-mocks* {rp/cmd! mock/rpc-cmd-mock rx/timer mock/timer-mock}
(doseq [tuple (concat (map #(vector :library-transform 0 % 4 1) (range 6))
[[:library-text 0 0 1]]
(map #(vector :library-text-style 0 % 1) (range 12)))]
(await (source-acceptance-history tuple nil))))))
(t/deftest ^:async a-transform-keeps-library-text-stale-until-acceptance
(await
(mock/with-mocks* {rp/cmd! mock/rpc-cmd-mock rx/timer mock/timer-mock}
(await (source-acceptance-history [:library-text 0 1 1] [:transform 8 3 3 4])))))
(t/deftest ^:async edit-step-waits-for-slow-sync
(let [sync-file dwl/sync-file]
(await