🐛 Keep per-side token attributes when remapping a token (#12091)

Renaming a token with "Remap tokens" grouped the references by container
and wrote the union of their attributes onto every shape in it. A shape
with a stroke width token on one side got the token on all four sides
when another shape in the same container had it on every side.

Group the references by shape, so each shape is only renamed on the
attributes it already had the token on.

Closes #12075

AI-assisted-by: claude-opus-5-5
This commit is contained in:
Eva Marco 2026-10-06 13:49:16 +02:00 committed by GitHub
parent 582f943228
commit ff74c38bbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 44 additions and 7 deletions

View File

@ -115,11 +115,14 @@
shape-changes
(reduce-kv
(fn [changes container refs]
(let [shape-ids (map :shape-id refs)
(let [;; Each shape keeps only the attributes it already had the
;; token on, so the rename never applies the token to new
;; attributes (e.g. a stroke-width token on a single side).
attributes-by-shape (update-vals (group-by :shape-id refs)
#(into #{} (map :attribute) %))
;; Find the correct token to apply (new or alias)
token (or (some #(when (= (:name (:token %)) new-token-name) %) tokens-with-sets)
(some #(when (= (:name (:token %)) old-token-name) %) tokens-with-sets))
attributes (set (map :attribute refs))]
token (or (some #(when (= (:name (:token %)) new-token-name) %) tokens-with-sets)
(some #(when (= (:name (:token %)) old-token-name) %) tokens-with-sets))]
(if token
;; Create a new independent changes so we can call `with-file-data` after `with-container`
;; otherwise it causes probelms looking up for objects
@ -127,10 +130,11 @@
(-> (pcb/empty-changes)
(pcb/with-container container)
(pcb/with-file-data file-data)
(pcb/update-shapes shape-ids
(pcb/update-shapes (keys attributes-by-shape)
(fn [shape]
(update shape :applied-tokens
#(merge % (cft/attributes-map attributes (:token token)))))
(let [attributes (get attributes-by-shape (:id shape))]
(update shape :applied-tokens
#(merge % (cft/attributes-map attributes (:token token))))))
{:ignore-touched true}))]
(pcb/concat-changes changes container-changes))
changes)))

View File

@ -120,6 +120,39 @@
(let [result (dwtr/validate-token-remapping "color.primary" "brand.primary")]
(t/is (true? (:valid? result)))))))
(t/deftest test-remap-keeps-applied-attributes-per-shape
(t/testing "renaming a token only renames the attributes each shape already had"
(let [stroke-token {:id (cthi/new-id! :sw-10)
:name "sw-10"
:value "10"
:type :stroke-width}
file (-> (ctht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "Set A"))
(ctob/add-token (cthi/id :set-a)
(ctob/make-token stroke-token))))
(ctho/add-rect :top-rect)
(ctho/add-rect :all-rect)
(ctht/apply-token-to-shape :top-rect "sw-10"
[:stroke-width-top] [] nil)
(ctht/apply-token-to-shape :all-rect "sw-10"
cto/stroke-width-keys [] nil)
;; The production flow renames the token first,
;; then remaps the shapes that use it.
(update-in [:data :tokens-lib]
ctob/update-token (cthi/id :set-a) (cthi/id :sw-10)
#(assoc % :name "sw-big")))
changes (dwtr/build-remap-changes (:data file) "sw-10" "sw-big")
file' (cthf/apply-changes file changes)
top-rect' (cths/get-shape file' :top-rect)
all-rect' (cths/get-shape file' :all-rect)]
(t/is (= {:stroke-width-top "sw-big"}
(:applied-tokens top-rect')))
(t/is (= (zipmap cto/stroke-width-keys (repeat "sw-big"))
(:applied-tokens all-rect'))))))
(defn- setup-file-with-component-copy-and-token
"Create a file containing a component, an instance copy of it, and a single
`color.primary` token already applied to both the main and the copy shape.