🐛 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
"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."
[tokens]
(reduce
(fn [acc [_ token]]
(let [temp-id (random-uuid)
token (assoc token :temp/id temp-id)
path (get-token-path token)]
(let [path (get-token-path token)]
(-> acc
(assoc-in (concat [:tokens-tree] path) token)
(assoc-in [:ids temp-id] token))))
(assoc-in [:ids (:id token)] token))))
{:tokens-tree {} :ids {}}
tokens))

View File

@ -551,7 +551,7 @@
(.. sd-token -original -name))
(defn sd-token-uuid [^js sd-token]
(uuid (.-uuid (.-id ^js sd-token))))
(uuid (.-uuid (.. sd-token -original -id))))
(defn resolve-tokens
[tokens]
@ -560,15 +560,23 @@
(defn resolve-tokens-interactive
"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
so we know if references are missing / to resolve them and possibly show interactive previews (in the tokens form) to the user.
We have to pass in all tokens from all sets in the entire library to
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,
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."
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, 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]
(let [{:keys [tokens-tree ids]} (ctob/backtrace-tokens-tree tokens)]
(resolve-tokens-tree tokens-tree #(get ids (sd-token-uuid %)))))
@ -584,10 +592,11 @@
(defonce !tokens-cache (atom nil))
(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,
then the state will be updated with the resolved tokens."
This hook will return the unresolved tokens as state until they are
processed, then the state will be updated with the resolved tokens."
[tokens & {:keys [cache-atom interactive?]
:or {cache-atom !tokens-cache}
:as config}]

View File

@ -57,3 +57,29 @@
(t/is (= :error.token/number-too-large
(get-in resolved-tokens ["borderRadius.largeFn" :errors 0 :error/code])))
(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))))))))