From 7e2f8b8781dc510b81b4ba7f310592c2f972edb5 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 27 Oct 2026 09:40:28 +0100 Subject: [PATCH] :bug: Predict typography ownership across variant switches Correct the independent switch model when only some typography styles carry into the new variant. Mixed ownership drops the reference, while fully carried styles retain it. Predict this from the two mains and prior authored groups, including when the result is corrupt. F221's regression and ownership controls failed before the correction; they pass 2 tests / 6 assertions afterward. The original program and shrink pass. Async frontend replay passes 19 assertions with undo/redo. Common/frontend lint passes; changed files were formatted. No production behavior, golden data or benchmark baseline changes. Fresh 5,000-program gates remain required. AI-assisted-by: gpt-6 --- .../test_helpers/composable/shadow.cljc | 16 +++++++- .../logic/composable_gen_test.cljc | 39 +++++++++++++++++++ .../composable_tests/generated_test.cljs | 6 +++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/common/src/app/common/test_helpers/composable/shadow.cljc b/common/src/app/common/test_helpers/composable/shadow.cljc index 5b8058b8d8..0c008e23a1 100644 --- a/common/src/app/common/test_helpers/composable/shadow.cljc +++ b/common/src/app/common/test_helpers/composable/shadow.cljc @@ -397,12 +397,24 @@ (if-let [[main _] (source after new-paths new-main-id shape)] (let [path (get new-paths (:id main)) [old old-main groups] (get old-copies path) - old-main (or old-main (get old-by-path path))] + old-main (or old-main (get old-by-path path)) + text-differences (when (= :text (:type main)) + (thtx/override-units (:content old-main) (:content main))) + carried-text-units (into #{} + (keep (fn [[group unit]] + (when (and (contains? groups group) + (not (contains? text-differences unit))) + unit))) + text-groups) + mixed-typography? (some #(not= (contains? carried-text-units :typography) + (contains? carried-text-units %)) + [:family :weight :size :line-height :spacing :case])] (reduce-kv (fn [result group _] (if-let [unit (get text-groups group)] (cond-> result - (and (not (contains? (thtx/override-units (:content old-main) (:content main)) unit)) + (and (not (and (= unit :typography) mixed-typography?)) + (not (contains? text-differences unit)) (not (same-value? main group {:content (:content old)}))) (-> (assoc-in [(:id shape) group] {:content (:content old)}) (assoc-in [(:id shape) :content-group] {}))) diff --git a/common/test/common_tests/logic/composable_gen_test.cljc b/common/test/common_tests/logic/composable_gen_test.cljc index 93376114d6..25a3d0529a 100644 --- a/common/test/common_tests/logic/composable_gen_test.cljc +++ b/common/test/common_tests/logic/composable_gen_test.cljc @@ -388,6 +388,45 @@ (t/is (= {} (get-in result [:result-text :content-group]))) (t/is (false? (shadow/same-value? (:result-text after) :text-content-size expected))))))) +(t/deftest switch-shadow-keeps-a-typography-only-with-its-style-ownership + (doseq [[new-size keep-reference?] [[14 true] [22 false]]] + (let [content #(thtx/content [[["Same text" {:size %}]]]) + applied (thtx/content + (thtx/apply-typography [[["Same text"]]] :heading + {:family "Roboto" :weight 700 :italic true :size 22 + :line-height 1.6 :spacing 2 :case "uppercase"})) + root {:type :frame :name "Board" :width 100 :height 100 :rotation 0} + text {:type :text :name "Caption" :width 70 :height 25 :rotation 0} + before {:old (assoc root :id :old :shapes [:old-text]) + :old-text (assoc text :id :old-text :parent-id :old :content (content 14)) + :copy (assoc root :id :copy :shape-ref :old :shapes [:copy-text]) + :copy-text (assoc text :id :copy-text :shape-ref :old-text :parent-id :copy :content applied)} + after {:new (assoc root :id :new :shapes [:new-text]) + :new-text (assoc text :id :new-text :parent-id :new :content (content new-size)) + :copy (assoc root :id :copy :shape-ref :new :shapes [:result-text]) + ;; A corrupt result must not supply the model's expected values. + :result-text (assoc text :id :result-text :shape-ref :new-text :parent-id :copy + :content (content 99))} + groups #{:content-group :text-content-typography :text-content-family + :text-content-weight :text-content-size :text-content-line-height + :text-content-spacing :text-content-case} + model {:copy-text (zipmap groups (repeat {:content applied}))} + predicted (get (shadow/swap model before after :copy :old :new true) :result-text) + expected-groups (cond-> groups + (not keep-reference?) (disj :text-content-size :text-content-typography))] + (t/is (= expected-groups (set (keys predicted)))) + (t/is (false? (shadow/same-value? (:result-text after) :text-content-family + (:text-content-family predicted)))) + (when keep-reference? + (t/is (false? (shadow/same-value? (:result-text after) :text-content-typography + (:text-content-typography predicted)))))))) + +(t/deftest typography-ownership-stays-consistent-when-a-variant-changes-size + (t/is (map? (cg/run {:world [2 0 {:library true :variants true :rich-variants true + :text true :text-styles true :layout nil :layout-copies true}] + :ops [[:text-style 4 8 1] [:switch 1 0 0 0] + [:undo] [:undo] [:redo] [:redo]]})))) + (t/deftest source-library-edits-wait-for-explicit-consumer-acceptance (let [initial (cg/initial-state [2 1 {:library true :stale-libraries true}]) situation (:situation initial) diff --git a/frontend/test/frontend_tests/composable_tests/generated_test.cljs b/frontend/test/frontend_tests/composable_tests/generated_test.cljs index a65ee91408..9d0c671ae8 100644 --- a/frontend/test/frontend_tests/composable_tests/generated_test.cljs +++ b/frontend/test/frontend_tests/composable_tests/generated_test.cljs @@ -337,6 +337,12 @@ :ops [[:swap 0 0 0 0] [:text 23 3 1 0] [:text-style 22 8 1] [:undo] [:undo] [:undo] [:redo] [:redo] [:redo]]}))) +(t/deftest ^:async typography-ownership-stays-consistent-when-a-variant-changes-size + (await (replay {:world [2 0 {:library true :variants true :rich-variants true + :text true :text-styles true :layout nil :layout-copies true}] + :ops [[:text-style 4 8 1] [:switch 1 0 0 0] + [:undo] [:undo] [:redo] [:redo]]}))) + (t/deftest ^:async detaching-after-a-nested-switch-keeps-placement-stable-through-sync (await (replay {:world [2 2 {:library true :variants true :rich-variants true :text true :text-styles true :layout :flex :layout-copies true}]