From 6002ca5274e80d0df58fea411cfc22d9e58e75b9 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 22 Oct 2026 14:36:01 +0200 Subject: [PATCH] :bug: Bound component sync retries per component Count repeated processing of each component instead of total graph width. A valid dependency graph with 102 outer components now settles and updates every copy (F108). The non-settling cascade guard remains bounded. The regression failed at 100 passes before the fix, then passed with 103 components. Common JVM/JS, frontend, backend and exporter suites pass. Benchmark vs Phase 1e (lower quantile, ms): world baseline now ratio main-10 1.790 0.993 0.55 main-100 15.415 9.038 0.59 main-1000 153.086 90.145 0.59 three-levels 35.215 18.989 0.54 chain 68.931 35.045 0.51 library-500 83.083 85.830 1.03 many-shapes 4.837 3.344 0.69 variant-100 32.052 33.586 1.05 All worlds pass the 10% regression threshold. AI-assisted-by: GPT-6 --- common/src/app/common/logic/libraries.cljc | 16 ++++++------ .../logic/chained_propagation_test.cljc | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 864de164c7..783333a15c 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -1431,7 +1431,7 @@ (maybe-sync sync-typographies? done? #(generate-sync-file % file-id :typographies asset-id library-id libraries current-file-id early-return?)))))) -;; A guard against a sync cascade that never settles. +;; Bound repeated processing of a component, not the width of the graph. (def ^:private max-sync-passes 100) (defn changed-components @@ -1449,16 +1449,17 @@ `{:changes :data :component-ids :passes}`, with `component-ids` the components synced." [changes data file-id pending libraries current-file-id passes] - (loop [changes changes - data data - pending (vec pending) - synced #{} - passes passes] + (loop [changes changes + data data + pending (vec pending) + synced #{} + attempts {} + passes passes] (cond (empty? pending) {:changes changes :data data :component-ids synced :passes passes} - (>= passes max-sync-passes) + (>= (get attempts (first pending) 0) max-sync-passes) (throw (ex-info "Component sync did not settle" {:type ::sync-not-settled :pending pending @@ -1482,6 +1483,7 @@ data' (into (subvec pending 1) more) (conj synced component-id) + (update attempts component-id (fnil inc 0)) (inc passes)))))) (defn generate-sync-components-cascade diff --git a/common/test/common_tests/logic/chained_propagation_test.cljc b/common/test/common_tests/logic/chained_propagation_test.cljc index 7be6a15cad..e1ce08fa36 100644 --- a/common/test/common_tests/logic/chained_propagation_test.cljc +++ b/common/test/common_tests/logic/chained_propagation_test.cljc @@ -8,6 +8,7 @@ (:require [app.common.files.changes-builder :as pcb] [app.common.logic.instances :as cli] + [app.common.logic.libraries :as cll] [app.common.logic.shapes :as cls] [app.common.test-helpers.components :as thc] [app.common.test-helpers.compositions :as tho] @@ -19,6 +20,30 @@ (t/use-fixtures :each thi/test-fixture) +(t/deftest a-wide-component-cascade-settles + (let [labels (mapv #(keyword (str "outer-" %)) (range 102)) + file (reduce (fn [file label] + (let [root (keyword (str (name label) "-main")) + head (keyword (str (name label) "-nested")) + copy (keyword (str (name label) "-copy"))] + (-> file + (tho/add-frame root) + (thc/instantiate-component :inner head :parent-label root) + (thc/make-component label root) + (thc/instantiate-component label copy)))) + (-> (thf/sample-file :file1) + (tho/add-simple-component :inner :inner-main :inner-child)) + labels) + file (ths/update-shape file :inner-child :fills + (ths/sample-fills-color :fill-color "#FF0000")) + result (cll/generate-sync-components-cascade + (pcb/empty-changes) (:id file) #{(thi/id :inner)} + {(:id file) file} (:id file)) + synced (thf/apply-changes file (:changes result))] + (t/is (= 103 (count (:component-ids result)))) + (doseq [label labels] + (t/is (= "#FF0000" (tho/bottom-fill-color synced (keyword (str (name label) "-copy")))))))) + (defn- first-fill-color [file tag] (-> (ths/get-shape file tag) (:fills)