diff --git a/CHANGES.md b/CHANGES.md index 8df4c93f9c..1d13e74f47 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 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 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 diff --git a/frontend/src/app/main/data/style_dictionary.cljs b/frontend/src/app/main/data/style_dictionary.cljs index 48320fd9a0..4e0a1fc635 100644 --- a/frontend/src/app/main/data/style_dictionary.cljs +++ b/frontend/src/app/main/data/style_dictionary.cljs @@ -553,10 +553,36 @@ (defn sd-token-uuid [^js sd-token] (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 [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 "Interactive check of resolving tokens. @@ -579,7 +605,8 @@ 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 %))))) + (->> (resolve-tokens-tree tokens-tree #(get ids (sd-token-uuid %))) + (rx/map #(merge-name-collisions tokens %))))) (defn resolve-tokens-with-verbose-errors [tokens] (resolve-tokens-tree diff --git a/frontend/src/app/main/data/workspace/tokens/errors.cljs b/frontend/src/app/main/data/workspace/tokens/errors.cljs index 0e7a78de9b..4aaae14eba 100644 --- a/frontend/src/app/main/data/workspace/tokens/errors.cljs +++ b/frontend/src/app/main/data/workspace/tokens/errors.cljs @@ -52,6 +52,16 @@ {:error/code :error.token/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/code :error.style-dictionary/missing-reference :error/fn #(str (tr "errors.tokens.missing-references") (str/join " " %))} diff --git a/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs b/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs index 1f1609f344..3461298c4d 100644 --- a/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs +++ b/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs @@ -58,6 +58,45 @@ (get-in resolved-tokens ["borderRadius.largeFn" :errors 0 :error/code]))) (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/async done diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 4ac05d4419..fb4b609978 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -8608,6 +8608,10 @@ msgstr "%s active sets" msgid "errors.tokens.number-too-large" 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 msgid "errors.tokens.opacity-range" msgstr "Opacity must be between 0 and 100% or 0 and 1 (e.g. 50% or 0.5)."