From 1c9ab691e6a7b0eeefe21f34189fd1da708a1735 Mon Sep 17 00:00:00 2001 From: RenzoMXD <170978465+RenzoMXD@users.noreply.github.com> Date: Mon, 11 May 2026 08:39:51 +0200 Subject: [PATCH] :bug: Plugin API theme.addSet/removeSet accept proxy or set ID Signed-off-by: RenzoMXD <170978465+RenzoMXD@users.noreply.github.com> :bug: Preserve validation errors for theme set APIs Signed-off-by: RenzoMXD <170978465+RenzoMXD@users.noreply.github.com> Co-authored-by: Alonso Torres --- frontend/src/app/plugins/tokens.cljs | 40 +++++++----- .../frontend_tests/plugins/tokens_test.cljs | 64 +++++++++++++++++++ plugins/CHANGELOG.md | 1 + plugins/libs/plugin-types/index.d.ts | 8 ++- 4 files changed, 96 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/plugins/tokens.cljs b/frontend/src/app/plugins/tokens.cljs index a9c27c9649..e832e7750a 100644 --- a/frontend/src/app/plugins/tokens.cljs +++ b/frontend/src/app/plugins/tokens.cljs @@ -453,6 +453,23 @@ (defn token-theme-proxy? [p] (obj/type-of? p "TokenThemeProxy")) +(defn- resolve-token-set + "Resolves an addSet/removeSet argument to a token set. A proxy is returned + as-is; an id is located in the file's token library." + [file-id set-arg] + (if (token-set-proxy? set-arg) + set-arg + (u/locate-token-set file-id set-arg))) + +(defn- token-set-name + "Reads the name from a resolved token set, supporting both proxies (whose + getter falls back to the freshly-created name) and located sets." + [set] + (when (some? set) + (if (token-set-proxy? set) + (obj/get set "name") + (ctob/get-name set)))) + (defn token-theme-proxy [plugin-id file-id id] (obj/reify {:name "TokenThemeProxy" @@ -535,27 +552,20 @@ :addSet {:enumerable false - :schema [:tuple [:fn token-set-proxy?]] - :fn (fn [token-set] - ;; Resolve the set name before the theme lookup. The proxy's :name - ;; getter now falls back to `initial-name` when state hasn't - ;; propagated, so this is safe even for freshly created sets. - ;; Guard against nil to prevent `enable-set` from conj'ing nil - ;; into the theme's :sets — which would send `:sets #{nil}` to the - ;; backend and crash the workspace. - (let [set-name (obj/get token-set "name") + :schema [:tuple [:or [:fn token-set-proxy?] ::sm/uuid]] + :fn (fn [set-arg] + (let [set-name (token-set-name (resolve-token-set file-id set-arg)) theme (u/locate-token-theme file-id id)] - (when (and (some? set-name) (some? theme)) + (when (and set-name theme) (st/emit! (dwtl/update-token-theme id (ctob/enable-set theme set-name))))))} :removeSet {:enumerable false - :schema [:tuple [:fn token-set-proxy?]] - :fn (fn [token-set] - ;; Same nil guard as addSet — see comment above. - (let [set-name (obj/get token-set "name") + :schema [:tuple [:or [:fn token-set-proxy?] ::sm/uuid]] + :fn (fn [set-arg] + (let [set-name (token-set-name (resolve-token-set file-id set-arg)) theme (u/locate-token-theme file-id id)] - (when (and (some? set-name) (some? theme)) + (when (and set-name theme) (st/emit! (dwtl/update-token-theme id (ctob/disable-set theme set-name))))))} :duplicate diff --git a/frontend/test/frontend_tests/plugins/tokens_test.cljs b/frontend/test/frontend_tests/plugins/tokens_test.cljs index f95ba1d811..fe04a6a5e3 100644 --- a/frontend/test/frontend_tests/plugins/tokens_test.cljs +++ b/frontend/test/frontend_tests/plugins/tokens_test.cljs @@ -11,6 +11,7 @@ [app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.tokens :as ctht] [app.common.types.tokens-lib :as ctob] + [app.common.uuid :as uuid] [app.main.data.tokenscript :as ts] [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.store :as st] @@ -338,3 +339,66 @@ result (get-resolved-value token {(:name token) token})] (t/is (array? result)) (t/is (= ["Inter" "Arial"] (vec result))))) + +(t/deftest token-theme-add-set-accepts-token-set-id + (let [plugin-id "plugin-id" + file-id (uuid/next) + theme-id (uuid/next) + set-id (uuid/next) + token-set (ctob/make-token-set :id set-id :name "Core") + theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light") + emitted (atom []) + invalid (atom [])] + (with-redefs [u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) + u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) + u/not-valid (fn [_ code value] (swap! invalid conj [code value])) + dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) + st/emit! (fn ([event] (swap! emitted conj event) nil) + ([event & _] (swap! emitted conj event) nil))] + (let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] + (.addSet theme-proxy (str set-id)) + (t/is (= #{"Core"} (-> @emitted first :theme :sets))) + (t/is (empty? @invalid)))))) + +(t/deftest token-theme-add-set-accepts-token-set-proxy + (let [plugin-id "plugin-id" + file-id (uuid/next) + theme-id (uuid/next) + set-id (uuid/next) + token-set (ctob/make-token-set :id set-id :name "Core") + theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light") + emitted (atom []) + invalid (atom [])] + (with-redefs [u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) + u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) + u/not-valid (fn [_ code value] (swap! invalid conj [code value])) + dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) + st/emit! (fn ([event] (swap! emitted conj event) nil) + ([event & _] (swap! emitted conj event) nil))] + (let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id) + set-proxy (ptok/token-set-proxy plugin-id file-id set-id "Core")] + (.addSet theme-proxy set-proxy) + (t/is (= #{"Core"} (-> @emitted first :theme :sets))) + (t/is (empty? @invalid)))))) + +(t/deftest token-theme-add-set-rejects-invalid-arguments + (let [plugin-id "plugin-id" + file-id (uuid/next) + theme-id (uuid/next) + theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light") + emitted (atom []) + invalid (atom [])] + (with-redefs [u/locate-token-set (constantly nil) + u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) + u/not-valid (fn [_ code value] (swap! invalid conj [code value])) + dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) + st/emit! (fn ([event] (swap! emitted conj event) nil) + ([event & _] (swap! emitted conj event) nil))] + (let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] + ;; Non-id, non-proxy arguments are rejected by the schema coercer. + (.addSet theme-proxy 42) + (.removeSet theme-proxy nil) + (t/is (empty? @emitted)) + (t/is (= 2 (count @invalid))) + (t/is (every? #(= :error (first %)) @invalid)))))) + diff --git a/plugins/CHANGELOG.md b/plugins/CHANGELOG.md index c3138b0659..5504c20dac 100644 --- a/plugins/CHANGELOG.md +++ b/plugins/CHANGELOG.md @@ -21,6 +21,7 @@ - **plugin-types**: Added `fixedWhenScrolling` property for shapes - **plugin-runtime:** `addToken` now resolves references against all token sets, allowing references to tokens in inactive sets - **plugin-types:** `TokenCatalog.addSet` now accepts an optional `active` flag to create an already-active set (sets are inactive by default) +- **plugin-types:** `TokenTheme.addSet` and `TokenTheme.removeSet` now accept a token set id (`string`) in addition to a `TokenSet` - **plugin-runtime:** A `fontFamilies` token's `resolvedValue` now returns the documented `string[]` (the resolved family list) instead of leaking the raw tokenscript list symbol ### 🩹 Fixes diff --git a/plugins/libs/plugin-types/index.d.ts b/plugins/libs/plugin-types/index.d.ts index b232a16e78..b95e78c9c3 100644 --- a/plugins/libs/plugin-types/index.d.ts +++ b/plugins/libs/plugin-types/index.d.ts @@ -5291,13 +5291,17 @@ export interface TokenTheme { /** * Adds a set to the list of the theme. + * + * @param tokenSet a `TokenSet` or the id of a token set. */ - addSet(tokenSet: TokenSet): void; + addSet(tokenSet: TokenSet | string): void; /** * Removes a set from the list of the theme. + * + * @param tokenSet a `TokenSet` or the id of a token set. */ - removeSet(tokenSet: TokenSet): void; + removeSet(tokenSet: TokenSet | string): void; /** * Adds to the catalog a new TokenTheme equal to this one but with a new id.