🐛 Plugin API theme.addSet/removeSet accept proxy or set ID

Signed-off-by: RenzoMXD <170978465+RenzoMXD@users.noreply.github.com>

🐛 Preserve validation errors for theme set APIs

Signed-off-by: RenzoMXD <170978465+RenzoMXD@users.noreply.github.com>

Co-authored-by: Alonso Torres <alonso.torres@kaleidos.net>
This commit is contained in:
RenzoMXD 2026-05-11 08:39:51 +02:00 committed by Alonso Torres
parent 5b7447fbe4
commit 1c9ab691e6
4 changed files with 96 additions and 17 deletions

View File

@ -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

View File

@ -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))))))

View File

@ -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

View File

@ -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.