🐛 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
This commit is contained in:
Andrey Antukh 2026-09-25 09:49:53 +02:00 committed by GitHub
parent 5ef70c7284
commit dfd28b1e57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 136 additions and 1 deletions

View File

@ -2109,6 +2109,29 @@
(update :pages-index d/update-vals repair-page) (update :pages-index d/update-vals repair-page)
(d/update-when :components d/update-vals repair-container)))) (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 (def available-migrations
(into (d/ordered-set) (into (d/ordered-set)
["legacy-2" ["legacy-2"
@ -2194,4 +2217,5 @@
"0025-repair-empty-text-content" "0025-repair-empty-text-content"
"0026-fix-svg-raw-shapes-uuids" "0026-fix-svg-raw-shapes-uuids"
"0027-separate-tokens-status" "0027-separate-tokens-status"
"0028-normalize-constrained-values"])) "0028-normalize-constrained-values"
"0029-move-background-blur-out-of-blur"]))

View File

@ -206,3 +206,114 @@
(t/is (contains? (:migrations file') migration-id) "migration id persisted") (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])) (t/is (zero? (get-in file' [:data :pages-index page-id :objects shape-id :r1]))
"migration repaired file data before schema validation"))) "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")))