From dfd28b1e57d87055a2c0dd2cd984a6b4e4e0ca8e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 25 Sep 2026 09:49:53 +0200 Subject: [PATCH] :bug: Move legacy background blur out of the layer blur attribute (#11908) Before background blur got its own shape attribute, the `:blur` attribute accepted both `:layer-blur` and `:background-blur` types, so the editor and the plugin API could save a background blur under `:blur`. The shape schema was later tightened to only allow `:layer-blur` on `:blur`, but no migration moved the existing values, so those files fail server schema validation. Add migration 0029: when a shape has a `:blur` map with `:type :background-blur`, move it to the `:background-blur` attribute. When the shape already has a `:background-blur`, keep it and drop the mis-typed `:blur`. The migration walks both pages and components. Closes #11904 AI-assisted-by: deepseek-v4.1-flash --- common/src/app/common/files/migrations.cljc | 26 +++- .../common_tests/files_migrations_test.cljc | 111 ++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 8542a559c4..46f24f6add 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -2109,6 +2109,29 @@ (update :pages-index d/update-vals repair-page) (d/update-when :components d/update-vals repair-container)))) +(defmethod migrate-data "0029-move-background-blur-out-of-blur" + ;; Before background blur got its own attribute, `:blur` accepted a + ;; `:background-blur` type, so the editor and the plugin API saved those maps + ;; under `:blur`. Once the shape schema was tightened, such files fail + ;; validation; move the map to the `:background-blur` attribute. When the + ;; shape already has a `:background-blur`, keep it and drop the mis-typed one. + [data _] + (letfn [(repair-shape [shape] + (if (= :background-blur (get-in shape [:blur :type])) + (if (contains? shape :background-blur) + (dissoc shape :blur) + (-> shape + (dissoc :blur) + (assoc :background-blur (:blur shape)))) + shape)) + + (repair-container [container] + (d/update-when container :objects d/update-vals repair-shape))] + + (-> data + (update :pages-index d/update-vals repair-container) + (d/update-when :components d/update-vals repair-container)))) + (def available-migrations (into (d/ordered-set) ["legacy-2" @@ -2194,4 +2217,5 @@ "0025-repair-empty-text-content" "0026-fix-svg-raw-shapes-uuids" "0027-separate-tokens-status" - "0028-normalize-constrained-values"])) + "0028-normalize-constrained-values" + "0029-move-background-blur-out-of-blur"])) diff --git a/common/test/common_tests/files_migrations_test.cljc b/common/test/common_tests/files_migrations_test.cljc index 2e8e423c07..b87d1b4d4c 100644 --- a/common/test/common_tests/files_migrations_test.cljc +++ b/common/test/common_tests/files_migrations_test.cljc @@ -206,3 +206,114 @@ (t/is (contains? (:migrations file') migration-id) "migration id persisted") (t/is (zero? (get-in file' [:data :pages-index page-id :objects shape-id :r1])) "migration repaired file data before schema validation"))) + +(t/deftest migration-0029-moves-background-blur-out-of-blur + (let [migration-id "0029-move-background-blur-out-of-blur" + file-id (uuid/next) + page-id (uuid/next) + shape-id (uuid/next) + blur-id (uuid/next) + blur {:id blur-id + :type :background-blur + :value 10 + :hidden false} + shape (-> (cts/setup-shape {:id shape-id :type :rect}) + (assoc :blur blur)) + data (-> (ctf/make-file-data file-id page-id) + (assoc-in [:pages-index page-id :objects shape-id] shape))] + + (t/is (thrown? #?(:clj Exception :cljs js/Error) + (ctf/check-file-data data)) + "new schema rejects a background blur stored in :blur") + + (let [data' (cfm/migrate-data data migration-id) + shape' (get-in data' [:pages-index page-id :objects shape-id])] + (t/is (nil? (:blur shape')) "mis-typed :blur removed") + (t/is (= blur (:background-blur shape')) + "background blur moved to its own attribute") + (t/is (= data' (ctf/check-file-data data')) + "migrated file data passes the schema") + (t/is (= data' (cfm/migrate-data data' migration-id)) + "migration is idempotent")))) + +(t/deftest migration-0029-keeps-existing-background-blur + (let [migration-id "0029-move-background-blur-out-of-blur" + page-id (uuid/next) + shape-id (uuid/next) + existing-bg {:id (uuid/next) + :type :background-blur + :value 5 + :hidden false} + shape (-> (cts/setup-shape {:id shape-id :type :rect}) + (assoc :blur {:id (uuid/next) + :type :background-blur + :value 10 + :hidden false} + :background-blur existing-bg)) + data {:pages-index {page-id {:objects {shape-id shape}}}} + data' (cfm/migrate-data data migration-id) + shape' (get-in data' [:pages-index page-id :objects shape-id])] + + (t/is (nil? (:blur shape')) "mis-typed :blur dropped") + (t/is (= existing-bg (:background-blur shape')) + "existing background blur preserved"))) + +(t/deftest migration-0029-leaves-layer-blur-untouched + (let [migration-id "0029-move-background-blur-out-of-blur" + page-id (uuid/next) + shape-id (uuid/next) + blur {:id (uuid/next) + :type :layer-blur + :value 8 + :hidden false} + shape (-> (cts/setup-shape {:id shape-id :type :rect}) + (assoc :blur blur)) + data {:pages-index {page-id {:objects {shape-id shape}}}} + data' (cfm/migrate-data data migration-id) + shape' (get-in data' [:pages-index page-id :objects shape-id])] + + (t/is (= blur (:blur shape')) "layer blur untouched") + (t/is (nil? (:background-blur shape')) "no background blur created"))) + +(t/deftest migration-0029-migrates-component-blurs + (let [migration-id "0029-move-background-blur-out-of-blur" + component-id (uuid/next) + shape-id (uuid/next) + blur {:id (uuid/next) + :type :background-blur + :value 12 + :hidden false} + shape (-> (cts/setup-shape {:id shape-id :type :rect}) + (assoc :blur blur)) + data {:components {component-id {:objects {shape-id shape}}}} + data' (cfm/migrate-data data migration-id) + shape' (get-in data' [:components component-id :objects shape-id])] + + (t/is (nil? (:blur shape')) "component mis-typed :blur removed") + (t/is (= blur (:background-blur shape')) + "component background blur moved to its own attribute"))) + +(t/deftest migration-0029-runs-through-file-migration + (let [migration-id "0029-move-background-blur-out-of-blur" + shape-id (uuid/next) + blur-id (uuid/next) + blur {:id blur-id + :type :background-blur + :value 10 + :hidden false} + file (ctf/make-file {:name "Legacy background blur"}) + page-id (first (get-in file [:data :pages])) + shape (-> (cts/setup-shape {:id shape-id :type :rect}) + (assoc :blur blur)) + file (-> file + (assoc :migrations (disj cfm/available-migrations migration-id)) + (assoc-in [:data :pages-index page-id :objects shape-id] shape)) + file' (cfm/migrate-file file {})] + + (t/is (cfm/need-migration? file) "new migration detected") + (t/is (not (cfm/need-migration? file')) "new migration recorded") + (t/is (contains? (:migrations file') migration-id) "migration id persisted") + (t/is (= blur (get-in file' [:data :pages-index page-id :objects shape-id :background-blur])) + "background blur moved before schema validation") + (t/is (nil? (get-in file' [:data :pages-index page-id :objects shape-id :blur])) + "mis-typed :blur removed")))