🐛 Fix TypeError in sd-token-uuid when resolving tokens interactively (#8929)

The backtrace-tokens-tree function used a namespaced keyword :temp/id
which clj->js converted to the JS property "temp/id". The sd-token-uuid
function then tried to access .id on the sd-token top-level object,
which was undefined, causing "Cannot read properties of undefined
(reading uuid)".

Fix by using the existing token :id instead of generating a temporary
one, and read it from sd-token.original (matching sd-token-name pattern).
This commit is contained in:
Andrey Antukh 2026-04-13 11:34:15 +02:00 committed by GitHub
parent ef6eeb5693
commit a403175d5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 48 additions and 15 deletions

View File

@ -485,17 +485,15 @@
(defn backtrace-tokens-tree (defn backtrace-tokens-tree
"Convert tokens into a nested tree with their name as the path. "Convert tokens into a nested tree with their name as the path.
Generates a uuid per token to backtrace a token from an external source (StyleDictionary). Uses the existing token :id to backtrace a token from an external source (StyleDictionary).
The backtrace can't be the name as the name might not exist when the user is creating a token." The backtrace can't be the name as the name might not exist when the user is creating a token."
[tokens] [tokens]
(reduce (reduce
(fn [acc [_ token]] (fn [acc [_ token]]
(let [temp-id (random-uuid) (let [path (get-token-path token)]
token (assoc token :temp/id temp-id)
path (get-token-path token)]
(-> acc (-> acc
(assoc-in (concat [:tokens-tree] path) token) (assoc-in (concat [:tokens-tree] path) token)
(assoc-in [:ids temp-id] token)))) (assoc-in [:ids (:id token)] token))))
{:tokens-tree {} :ids {}} {:tokens-tree {} :ids {}}
tokens)) tokens))

View File

@ -551,7 +551,7 @@
(.. sd-token -original -name)) (.. sd-token -original -name))
(defn sd-token-uuid [^js sd-token] (defn sd-token-uuid [^js sd-token]
(uuid (.-uuid (.-id ^js sd-token)))) (uuid (.-uuid (.. sd-token -original -id))))
(defn resolve-tokens (defn resolve-tokens
[tokens] [tokens]
@ -560,15 +560,23 @@
(defn resolve-tokens-interactive (defn resolve-tokens-interactive
"Interactive check of resolving tokens. "Interactive check of resolving tokens.
Uses a ids map to backtrace the original token from the resolved StyleDictionary token. Uses a ids map to backtrace the original token from the resolved
StyleDictionary token.
We have to pass in all tokens from all sets in the entire library to style dictionary We have to pass in all tokens from all sets in the entire library to
so we know if references are missing / to resolve them and possibly show interactive previews (in the tokens form) to the user. style dictionary so we know if references are missing / to resolve
them and possibly show interactive previews (in the tokens form) to
the user.
Since we're using the :name path as the identifier we might be throwing away or overriding tokens in the tree that we pass to StyleDictionary. Since we're using the :name path as the identifier we might be
throwing away or overriding tokens in the tree that we pass to
StyleDictionary.
So to get back the original token from the resolved sd-token (see my updates for what an sd-token is) we include a temporary :id for the token that we pass to StyleDictionary, So to get back the original token from the resolved sd-token (see my
this way after the resolving computation we can restore any token, even clashing ones with the same :name path by just looking up that :id in the ids map." updates for what an sd-token is) we include a temporary :id for the
token that we pass to StyleDictionary, this way after the resolving
computation we can restore any token, even clashing ones with the
same :name path by just looking up that :id in the ids map."
[tokens] [tokens]
(let [{:keys [tokens-tree ids]} (ctob/backtrace-tokens-tree tokens)] (let [{:keys [tokens-tree ids]} (ctob/backtrace-tokens-tree tokens)]
(resolve-tokens-tree tokens-tree #(get ids (sd-token-uuid %))))) (resolve-tokens-tree tokens-tree #(get ids (sd-token-uuid %)))))
@ -584,10 +592,11 @@
(defonce !tokens-cache (atom nil)) (defonce !tokens-cache (atom nil))
(defn use-resolved-tokens (defn use-resolved-tokens
"The StyleDictionary process function is async, so we can't use resolved values directly. "The StyleDictionary process function is async, so we can't use
resolved values directly.
This hook will return the unresolved tokens as state until they are processed, This hook will return the unresolved tokens as state until they are
then the state will be updated with the resolved tokens." processed, then the state will be updated with the resolved tokens."
[tokens & {:keys [cache-atom interactive?] [tokens & {:keys [cache-atom interactive?]
:or {cache-atom !tokens-cache} :or {cache-atom !tokens-cache}
:as config}] :as config}]

View File

@ -57,3 +57,29 @@
(t/is (= :error.token/number-too-large (t/is (= :error.token/number-too-large
(get-in resolved-tokens ["borderRadius.largeFn" :errors 0 :error/code]))) (get-in resolved-tokens ["borderRadius.largeFn" :errors 0 :error/code])))
(done)))))))) (done))))))))
(t/deftest resolve-tokens-interactive-test
(t/async
done
(t/testing "resolves tokens interactively using backtrace ids map"
(let [tokens (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :core-set)
:name "core"))
(ctob/add-token (cthi/id :core-set)
(ctob/make-token {:name "borderRadius.sm"
:value "12px"
:type :border-radius}))
(ctob/add-token (cthi/id :core-set)
(ctob/make-token {:value "{borderRadius.sm} * 2"
:name "borderRadius.md"
:type :border-radius}))
(ctob/get-all-tokens-map))]
(-> (sd/resolve-tokens-interactive tokens)
(rx/sub!
(fn [resolved-tokens]
(t/is (= 12 (get-in resolved-tokens ["borderRadius.sm" :resolved-value])))
(t/is (= "px" (get-in resolved-tokens ["borderRadius.sm" :unit])))
(t/is (= 24 (get-in resolved-tokens ["borderRadius.md" :resolved-value])))
(t/is (= "px" (get-in resolved-tokens ["borderRadius.md" :unit])))
(done))))))))