🐛 Keep colliding tokens visible as broken pills

This commit is contained in:
rene0422 2026-05-15 13:05:11 -07:00 committed by Elena Torró
parent 429103d076
commit 0efebc5e0e
5 changed files with 83 additions and 2 deletions

View File

@ -138,6 +138,7 @@
- Fix standalone tokens ordering separated from token groups [#9733](https://github.com/penpot/penpot/issues/9733) (PR: [#9736](https://github.com/penpot/penpot/pull/9736)) - Fix standalone tokens ordering separated from token groups [#9733](https://github.com/penpot/penpot/issues/9733) (PR: [#9736](https://github.com/penpot/penpot/pull/9736))
- Fix delete invitation modal readability in light theme [#9737](https://github.com/penpot/penpot/issues/9737) (PR: [#9747](https://github.com/penpot/penpot/pull/9747)) - Fix delete invitation modal readability in light theme [#9737](https://github.com/penpot/penpot/issues/9737) (PR: [#9747](https://github.com/penpot/penpot/pull/9747))
- Fix team invitation not automatically accepted after account validation [#9776](https://github.com/penpot/penpot/issues/9776) (PR: [#9782](https://github.com/penpot/penpot/pull/9782)) - Fix team invitation not automatically accepted after account validation [#9776](https://github.com/penpot/penpot/issues/9776) (PR: [#9782](https://github.com/penpot/penpot/pull/9782))
- Fix design tokens vanishing from the sidebar when a token name collides with a token-group prefix from another active set (e.g. `a` in one set and `a.b` in another); the colliding token is now kept and rendered as a broken pill [Github #9584](https://github.com/penpot/penpot/issues/9584)
## 2.15.4 ## 2.15.4

View File

@ -553,10 +553,36 @@
(defn sd-token-uuid [^js sd-token] (defn sd-token-uuid [^js sd-token]
(uuid (.-uuid (.. sd-token -original -id)))) (uuid (.-uuid (.. sd-token -original -id))))
(defn- merge-name-collisions
"Re-attach tokens that `ctob/tokens-tree` / `backtrace-tokens-tree`
dropped because one token's name is a strict prefix of another's
(e.g. `a` vs `a.b` coming from two active sets). `assoc-in` in the
tree builder collapses such pairs, so StyleDictionary only sees one
of them and the other vanishes from the resolved map — and from the
sidebar, even though it still lives in the library (#9584).
We tag each dropped token with `:error.token/name-collision` so the
existing token-pill error rendering picks them up as broken pills,
matching the expected behaviour in the issue. Resolved tokens are
left untouched."
[tokens resolved]
(let [resolved-names (set (keys resolved))
dropped (->> tokens
(remove (fn [[k _]] (contains? resolved-names k)))
(map (fn [[k token]]
[k (assoc token
:errors
[(wte/error-with-value
:error.token/name-collision
(:name token))])]))
(into {}))]
(merge resolved dropped)))
(defn resolve-tokens (defn resolve-tokens
[tokens] [tokens]
(let [tokens-tree (ctob/tokens-tree tokens)] (let [tokens-tree (ctob/tokens-tree tokens)]
(resolve-tokens-tree tokens-tree #(get tokens (sd-token-name %))))) (->> (resolve-tokens-tree tokens-tree #(get tokens (sd-token-name %)))
(rx/map #(merge-name-collisions tokens %)))))
(defn resolve-tokens-interactive (defn resolve-tokens-interactive
"Interactive check of resolving tokens. "Interactive check of resolving tokens.
@ -579,7 +605,8 @@
same :name path by just looking up that :id in the ids map." 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 %)))
(rx/map #(merge-name-collisions tokens %)))))
(defn resolve-tokens-with-verbose-errors [tokens] (defn resolve-tokens-with-verbose-errors [tokens]
(resolve-tokens-tree (resolve-tokens-tree

View File

@ -52,6 +52,16 @@
{:error/code :error.token/number-too-large {:error/code :error.token/number-too-large
:error/fn #(str (tr "errors.tokens.number-too-large" %))} :error/fn #(str (tr "errors.tokens.number-too-large" %))}
;; Surfaced when a token name conflicts with a token-group prefix
;; across the active sets (e.g. one set defines `a`, another set
;; defines `a.b`). DTCG/StyleDictionary cannot represent both as
;; resolvable leaves, so the colliding token is preserved with this
;; error rather than silently disappearing from the sidebar — see
;; #9584.
:error.token/name-collision
{:error/code :error.token/name-collision
:error/fn #(str (tr "errors.tokens.name-collision" %))}
:error.style-dictionary/missing-reference :error.style-dictionary/missing-reference
{:error/code :error.style-dictionary/missing-reference {:error/code :error.style-dictionary/missing-reference
:error/fn #(str (tr "errors.tokens.missing-references") (str/join " " %))} :error/fn #(str (tr "errors.tokens.missing-references") (str/join " " %))}

View File

@ -58,6 +58,45 @@
(get-in resolved-tokens ["borderRadius.largeFn" :errors 0 :error/code]))) (get-in resolved-tokens ["borderRadius.largeFn" :errors 0 :error/code])))
(done)))))))) (done))))))))
;; Regression for #9584 — when one active set defines a token named
;; "a" and another defines "a.b", the tokens-tree builder collapses
;; them via assoc-in, so StyleDictionary only sees one. Previously
;; the other vanished from the sidebar entirely; now `resolve-tokens`
;; tags the dropped token with `:error.token/name-collision` so the
;; existing broken-pill rendering picks it up.
(t/deftest resolve-tokens-name-collision-test
(t/async
done
(t/testing "tokens colliding with a token-group prefix survive resolution as broken pills"
(let [tokens (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-1)
:name "set-1"))
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-2)
:name "set-2"))
(ctob/add-token (cthi/id :set-1)
(ctob/make-token {:name "a"
:value "8px"
:type :border-radius}))
(ctob/add-token (cthi/id :set-2)
(ctob/make-token {:name "a.b"
:value "12px"
:type :border-radius}))
(ctob/get-all-tokens-map))]
(-> (sd/resolve-tokens tokens)
(rx/sub!
(fn [resolved-tokens]
(t/testing "both tokens are present in the resolved map"
(t/is (contains? resolved-tokens "a"))
(t/is (contains? resolved-tokens "a.b")))
(t/testing "the colliding token carries the name-collision error"
(let [errors (or (get-in resolved-tokens ["a" :errors])
(get-in resolved-tokens ["a.b" :errors]))]
(t/is (seq errors))
(t/is (= :error.token/name-collision
(-> errors first :error/code)))))
(done))))))))
(t/deftest resolve-tokens-interactive-test (t/deftest resolve-tokens-interactive-test
(t/async (t/async
done done

View File

@ -8608,6 +8608,10 @@ msgstr "%s active sets"
msgid "errors.tokens.number-too-large" msgid "errors.tokens.number-too-large"
msgstr "Invalid token value. The resolved value is too large: %s" msgstr "Invalid token value. The resolved value is too large: %s"
#: src/app/main/data/workspace/tokens/errors.cljs:60
msgid "errors.tokens.name-collision"
msgstr "Token name %s conflicts with a token group of the same name in another active set."
#: src/app/main/data/workspace/tokens/errors.cljs:73, src/app/main/data/workspace/tokens/warnings.cljs:15 #: src/app/main/data/workspace/tokens/errors.cljs:73, src/app/main/data/workspace/tokens/warnings.cljs:15
msgid "errors.tokens.opacity-range" msgid "errors.tokens.opacity-range"
msgstr "Opacity must be between 0 and 100% or 0 and 1 (e.g. 50% or 0.5)." msgstr "Opacity must be between 0 and 100% or 0 and 1 (e.g. 50% or 0.5)."