From ff74c38bbf0b01dc540be64747dec91b98e35ef6 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 6 Oct 2026 13:49:16 +0200 Subject: [PATCH] :bug: 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 --- .../main/data/workspace/tokens/remapping.cljs | 18 ++++++---- .../tokens/logic/token_remapping_test.cljs | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/main/data/workspace/tokens/remapping.cljs b/frontend/src/app/main/data/workspace/tokens/remapping.cljs index e36b9d1b1d..7ff4a8be4b 100644 --- a/frontend/src/app/main/data/workspace/tokens/remapping.cljs +++ b/frontend/src/app/main/data/workspace/tokens/remapping.cljs @@ -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))) diff --git a/frontend/test/frontend_tests/tokens/logic/token_remapping_test.cljs b/frontend/test/frontend_tests/tokens/logic/token_remapping_test.cljs index acad25311c..fc47db9132 100644 --- a/frontend/test/frontend_tests/tokens/logic/token_remapping_test.cljs +++ b/frontend/test/frontend_tests/tokens/logic/token_remapping_test.cljs @@ -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.