@vestra-player/sdk 1.6.2 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.4
4
+
5
+ **Who must change: nobody, but check tests that compare whole `bindings.list()` entries.** Additions only: two optional `CommandOptions` fields, one optional `BindingInfo` field, a `COMMAND_LIMITS` entry and a new built-in command. `apiVersion` stays `vestra/1`, no new capability. A plugin that does not set the new fields sends exactly what it sent before; one that sets them needs player 0.7.6 (`"minPlayerVersion": "0.7.6"`), because older players refuse a declaration with fields they do not know (`invalid`).
6
+
7
+ - **`CommandOptions.category`** (player 0.7.6): what a command is about, in a word or two (`"Subtitle Sync"`), English source text translated like the title, or a `Text`. The player's new command palette (Ctrl+Shift+P, ⌘⇧P on macOS) and lists of commands name a command `Category: Title`, so a title such as "Earlier 0.5 s" or "Off" still says what it changes outside its menu. Without one the player uses the menu path above the item, then the plugin's name. 1–80 bytes (`COMMAND_LIMITS.categoryBytes`), checked like the other command texts. See [The command palette and categories](docs/explanation/commands.md#the-command-palette-and-categories).
8
+ - **`CommandOptions.home`** (player 0.7.6): the local id of the plugin's command that opens the window or panel where this command's control lives; the palette shows that place. Checked like a command id.
9
+ - **`BindingInfo.category`** (player 0.7.6): the shown category from `bindings.list`, `null` without one, for a keyboard shortcuts page that names commands like the palette.
10
+ - **`BUILTIN_COMMANDS`** lists the player's **Command Palette** (`host/palette`, `Ctrl+Shift+P`, outside the menu). From player 0.7.6 a plugin that declares `Ctrl+Shift+P` as a default key loses it to this command (the player's own commands win conflicts); no built-in plugin declares it.
11
+ - **`bindings.list()` entries now carry `category`** (`null` when the command declares none), in the player and in `TestHost`. A test that compares a whole entry with `deepEqual` now needs `category` in the expected value.
12
+ - **The palette lists a disabled command only when it gives a `reason`.** A command that is disabled without one is left out, whatever its keys (as VS Code hides a command whose `when` is false), so give a `reason` to a command that should stay findable while it is disabled.
13
+ - `TestHost` checks `category` and `home` as the player does, shows the translated category in `FakeCommand.display.category` (not enumerable, like `reason`) and in its `bindings.list` entries.
14
+
15
+ ## 1.6.3
16
+
17
+ **Who must change: nobody.** Documentation only: no change to the TypeScript API, the wire or `TestHost`, `apiVersion` stays `vestra/1`, no new capability, no player version needed.
18
+
19
+ - **Examples name window handles after the window they name (its Slint file where there is one).** The API examples, guides, tutorials and example plugins no longer call a window handle `window`: `readmeWindow` for `ui/readme.slint`, `presetsWindow`, `thumbnailWindow`, `bookmarkWindow`, `blurWindow` and so on, in `plugin.windows.create(…)` and `host.window(…)` alike.
20
+
3
21
  ## 1.6.2
4
22
 
5
23
  **Who must change: nobody, but check `TestHost` tests of a full sidebar or of `keepsRunning` with a background tab.** No change to the TypeScript API or the wire, `apiVersion` stays `vestra/1`, no new capability, no player version needed. `TestHost` follows player 0.7.3: it no longer refuses a ninth sidebar tab, and `keepsRunning` lists a sidebar panel only while its tab is the current tab, as `"its sidebar tab is the current tab"`. A test that expected `conflict` for a ninth tab, or `"a mounted panel"` for a sidebar panel, now fails.
@@ -78,6 +78,13 @@ export declare const COMMAND_LIMITS: {
78
78
  * @since 1.5.48
79
79
  */
80
80
  readonly groupBytes: 80;
81
+ /**
82
+ * UTF-8 bytes of a category (`CommandOptions.category`), a text value
83
+ * counted by its English rendering.
84
+ *
85
+ * @since 1.6.4
86
+ */
87
+ readonly categoryBytes: 80;
81
88
  /**
82
89
  * UTF-8 bytes of a state's reason, as shown in the player's
83
90
  * language.
@@ -327,6 +334,16 @@ export type CommandDeclaration = {
327
334
  icon: string | Json;
328
335
  order: number;
329
336
  };
337
+ /**
338
+ * The command's category, source text or a text value (player 0.7.6).
339
+ * Sent only when set: older players refuse unknown declaration fields.
340
+ */
341
+ category?: string | TextJson;
342
+ /**
343
+ * The local id of the command that opens where this one lives (player
344
+ * 0.7.6). Sent only when set, like `category`.
345
+ */
346
+ home?: string;
330
347
  };
331
348
  export declare function commandClient(transport: Transport): {
332
349
  register: (id: string, revision: number, declaration: CommandDeclaration, state: CommandState, when?: CommandWhen) => Promise<{
package/dist/commands.js CHANGED
@@ -74,6 +74,13 @@ export const COMMAND_LIMITS = {
74
74
  * @since 1.5.48
75
75
  */
76
76
  groupBytes: 80,
77
+ /**
78
+ * UTF-8 bytes of a category (`CommandOptions.category`), a text value
79
+ * counted by its English rendering.
80
+ *
81
+ * @since 1.6.4
82
+ */
83
+ categoryBytes: 80,
77
84
  /**
78
85
  * UTF-8 bytes of a state's reason, as shown in the player's
79
86
  * language.
@@ -594,6 +601,7 @@ host | quit | Quit | Cmd+Q
594
601
  host | enter-fullscreen | (Enter Fullscreen)
595
602
  host | exit-fullscreen | (Leave Fullscreen)
596
603
  host | sidebar | (Toggle Sidebar)
604
+ host | palette | (Command Palette) | Ctrl+Shift+P
597
605
  vestra.info | info.brief | (Brief Info) | Shift+Tab
598
606
  vestra.playlist | playlist.play | (Play) | Enter, P | panel
599
607
  vestra.playlist | playlist.list-add | (Add Files…) | Ctrl+I | panel
Binary file
@@ -189,6 +189,7 @@ export const bindingsApi = (scope) => ({
189
189
  title: info.title,
190
190
  path: info.path,
191
191
  key: info.key ?? null,
192
+ category: info.category ?? null,
192
193
  scope: info.scope,
193
194
  defaults: info.defaults,
194
195
  current: info.current,
@@ -26,13 +26,18 @@ function declare(options) {
26
26
  throw failure("invalid", `command ${options.id}: title or path required`);
27
27
  if (path.length > 8)
28
28
  throw failure("invalid", `command ${options.id}: path has 1–8 segments`);
29
- for (const segment of [title, ...path])
29
+ const category = options.category;
30
+ for (const segment of [
31
+ title,
32
+ ...path,
33
+ ...(category === undefined ? [] : [category]),
34
+ ])
30
35
  if (typeof segment !== "string") {
31
36
  const problem = isTextValue(segment)
32
37
  ? "$vestraList" in segment
33
38
  ? "a menu text is a string or a text, not a list"
34
39
  : textProblem(segment)
35
- : "title and path segments are strings or texts";
40
+ : "title, path segments and category are strings or texts";
36
41
  if (problem)
37
42
  throw failure("invalid", `command ${options.id}: ${problem}`);
38
43
  }
@@ -59,6 +64,14 @@ function declare(options) {
59
64
  // Only when set: players before 0.5.10 refuse the field.
60
65
  if (options.button !== undefined)
61
66
  declaration.button = button(options.id, options.button, path.length > 0);
67
+ // Only when set: players before 0.7.6 refuse the fields.
68
+ if (category !== undefined)
69
+ declaration.category = category;
70
+ if (options.home !== undefined) {
71
+ if (typeof options.home !== "string")
72
+ throw failure("invalid", `command ${options.id}: home is a command id`);
73
+ declaration.home = options.home;
74
+ }
62
75
  return declaration;
63
76
  }
64
77
  /** `CommandOptions.button`, checked as the player checks it, in wire form. */
@@ -115,7 +115,7 @@ type Call = {
115
115
  * import assert from "node:assert/strict";
116
116
  * declare const host: TestHost;
117
117
  * // ---cut---
118
- * await host.command("open-picture").run(); // the plugin calls window.show()
118
+ * await host.command("open-picture").run(); // the plugin calls pictureWindow.show()
119
119
  * const picture = host.window("picture");
120
120
  * assert.equal(picture.visible, true);
121
121
  * picture.emit("adjust", "brightness", 40); // fire a Slint callback
@@ -270,6 +270,14 @@ export interface FakeCommand {
270
270
  * @since 1.5.11
271
271
  */
272
272
  reason?: string | null;
273
+ /**
274
+ * The category as shown (`CommandOptions.category`, translated with
275
+ * the plugin's catalog), `null` without one. Not enumerable, like
276
+ * `reason`.
277
+ *
278
+ * @since 1.6.4
279
+ */
280
+ category?: string | null;
273
281
  };
274
282
  /**
275
283
  * The conditions the plugin sent for `enabled` and `checked`
@@ -2746,11 +2754,11 @@ export declare class TestHost implements Transport {
2746
2754
  * declare const host: TestHost;
2747
2755
  * declare const expected: string;
2748
2756
  * // ---cut---
2749
- * const window = host.window("import");
2750
- * window.emit("load-file"); // the plugin reads and parses the file itself
2751
- * for (let i = 0; i < 100 && window.props["status-text"] !== expected; i++)
2757
+ * const importWindow = host.window("import");
2758
+ * importWindow.emit("load-file"); // the plugin reads and parses the file itself
2759
+ * for (let i = 0; i < 100 && importWindow.props["status-text"] !== expected; i++)
2752
2760
  * await host.settle(20);
2753
- * assert.equal(window.props["status-text"], expected);
2761
+ * assert.equal(importWindow.props["status-text"], expected);
2754
2762
  * ```
2755
2763
  *
2756
2764
  * @capability None.
@@ -354,7 +354,7 @@ function menuTextKey(value) {
354
354
  * The declaration checks of the player's `Registry::register`, in its
355
355
  * order, on the keyed title and path (`@<id>` already replaced).
356
356
  */
357
- function checkDeclaration(id, title, path, declaration) {
357
+ function checkDeclaration(id, title, path, declaration, category = null) {
358
358
  commandText(id, "localId", COMMAND_LIMITS.idBytes);
359
359
  if (!/^[A-Za-z0-9._-]+$/.test(id))
360
360
  throw commandInvalid("localId permits ASCII letters, digits, '.', '_' and '-'");
@@ -367,6 +367,17 @@ function checkDeclaration(id, title, path, declaration) {
367
367
  if (typeof group !== "string")
368
368
  throw new HostError("invalid", "group is a string");
369
369
  commandText(group, "group", COMMAND_LIMITS.groupBytes);
370
+ // Player 0.7.6: the category and the home command's local id.
371
+ if (category !== null)
372
+ commandText(category, "category", COMMAND_LIMITS.categoryBytes);
373
+ const home = declaration.home;
374
+ if (home !== undefined && home !== null) {
375
+ if (typeof home !== "string")
376
+ throw new HostError("invalid", "home is a command id");
377
+ commandText(home, "home", COMMAND_LIMITS.idBytes);
378
+ if (!/^[A-Za-z0-9._-]+$/.test(home))
379
+ throw commandInvalid("home permits ASCII letters, digits, '.', '_' and '-'");
380
+ }
370
381
  const order = declaration.order ?? 0;
371
382
  if (!Number.isInteger(order) ||
372
383
  Math.abs(order) > COMMAND_LIMITS.order)
@@ -669,7 +680,7 @@ export class TestHost {
669
680
  : formatTemplate(shown, args);
670
681
  }
671
682
  /** A declared menu path and title as the player shows them. */
672
- #display(path, title, reason = null) {
683
+ #display(path, title, reason = null, category = null) {
673
684
  const keys = menuKeys(path.map((segment) => typeof segment === "string" ? segment : textKey(segment)));
674
685
  const display = {
675
686
  title: this.translate(title),
@@ -684,6 +695,10 @@ export class TestHost {
684
695
  value: reason === null ? null : this.translate(reason),
685
696
  enumerable: false,
686
697
  });
698
+ Object.defineProperty(display, "category", {
699
+ value: category === null ? null : this.translate(category),
700
+ enumerable: false,
701
+ });
687
702
  return display;
688
703
  }
689
704
  /**
@@ -3230,6 +3245,8 @@ export class TestHost {
3230
3245
  key: declaration.path?.length
3231
3246
  ? menuKeys(declaration.path.map((segment) => typeof segment === "string" ? segment : textKey(segment)))
3232
3247
  : null,
3248
+ // Player 0.7.6: the category as shown, `null` without one.
3249
+ category: shown?.display.category ?? null,
3233
3250
  scope: scope,
3234
3251
  defaults: declaration.bindings,
3235
3252
  current,
@@ -3479,11 +3496,11 @@ export class TestHost {
3479
3496
  * declare const host: TestHost;
3480
3497
  * declare const expected: string;
3481
3498
  * // ---cut---
3482
- * const window = host.window("import");
3483
- * window.emit("load-file"); // the plugin reads and parses the file itself
3484
- * for (let i = 0; i < 100 && window.props["status-text"] !== expected; i++)
3499
+ * const importWindow = host.window("import");
3500
+ * importWindow.emit("load-file"); // the plugin reads and parses the file itself
3501
+ * for (let i = 0; i < 100 && importWindow.props["status-text"] !== expected; i++)
3485
3502
  * await host.settle(20);
3486
- * assert.equal(window.props["status-text"], expected);
3503
+ * assert.equal(importWindow.props["status-text"], expected);
3487
3504
  * ```
3488
3505
  *
3489
3506
  * @capability None.
@@ -4086,7 +4103,11 @@ export class TestHost {
4086
4103
  if (menu !== null && keys.length < 2)
4087
4104
  throw commandInvalid(`menu id @${menu} needs an item after it`);
4088
4105
  const path = menuKeys(keys);
4089
- checkDeclaration(id, title, path, declaration);
4106
+ // Player 0.7.6: the category, keyed like a menu text.
4107
+ const category = declaration.category === undefined || declaration.category === null
4108
+ ? null
4109
+ : menuTextKey(declaration.category);
4110
+ checkDeclaration(id, title, path, declaration, category);
4090
4111
  if (button !== undefined && button !== null) {
4091
4112
  if (path.length === 0)
4092
4113
  throw commandInvalid("button needs a menu path: a bar button is also a menu item");
@@ -4144,7 +4165,7 @@ export class TestHost {
4144
4165
  this.commands.set(id, {
4145
4166
  declaration,
4146
4167
  get display() {
4147
- return host.#display(declaration.path ?? [], declaration.title, this.state.reason);
4168
+ return host.#display(declaration.path ?? [], declaration.title, this.state.reason, declaration.category ?? null);
4148
4169
  },
4149
4170
  ...(p.when ? { when: p.when } : {}),
4150
4171
  get state() {
@@ -367,10 +367,10 @@ export interface Scope extends Disposable {
367
367
  * return;
368
368
  * }
369
369
  * live = plugin.scope();
370
- * const window = live.windows.create("ui/stats.slint");
370
+ * const statsWindow = live.windows.create("ui/stats.slint");
371
371
  * const timer = setInterval(() => live?.update(), 1000);
372
372
  * live.onDispose(() => clearInterval(timer));
373
- * await window.show();
373
+ * await statsWindow.show();
374
374
  * },
375
375
  * });
376
376
  * ```
@@ -487,12 +487,12 @@ export interface Scope extends Disposable {
487
487
  * promise settles; state changed between the awaits of a long handler
488
488
  * shows only after an explicit `update()`.
489
489
  * ```ts
490
- * declare const window: Window;
490
+ * declare const downloadWindow: Window;
491
491
  * declare function downloadPart(part: number): Promise<void>;
492
492
  * // ---cut---
493
493
  * let done = 0;
494
- * window.bind(() => ({ progress: done }));
495
- * window.on("start", async () => {
494
+ * downloadWindow.bind(() => ({ progress: done }));
495
+ * downloadWindow.on("start", async () => {
496
496
  * for (let part = 0; part < 10; part++) {
497
497
  * await downloadPart(part);
498
498
  * done = part + 1;
@@ -1112,6 +1112,87 @@ export interface CommandOptions {
1112
1112
  * defaults to the last path segment.
1113
1113
  */
1114
1114
  title?: string | Text;
1115
+ /**
1116
+ * What the command is about, in a word or two (`"Subtitle Sync"`,
1117
+ * `"Picture"`): English source text, shown translated like `title`, or a
1118
+ * {@link Text}. The player's command palette (Ctrl+Shift+P, ⌘⇧P on macOS)
1119
+ * and lists of commands name a command `Category: Title`, so a short title
1120
+ * such as "Earlier 0.5 s", "Off" or "90°" still says what it changes once
1121
+ * it is out of the menu. Without it the player uses the menu path above
1122
+ * the item, then the plugin's name.
1123
+ *
1124
+ * 1–80 UTF-8 bytes (a text value counted by its English rendering), no
1125
+ * control characters, no whitespace at either end
1126
+ * (`COMMAND_LIMITS.categoryBytes`); the player refuses anything else with
1127
+ * `invalid`. Sent only when set: players before 0.7.6 refuse the field
1128
+ * with `invalid`, so declare `"minPlayerVersion": "0.7.6"` when you set
1129
+ * it.
1130
+ *
1131
+ * @example Name commands that have no menu path
1132
+ * The palette lists "Subtitle Sync: Earlier 0.5 s" and "Subtitle Sync:
1133
+ * Later 0.5 s"; without a category both would read as a bare "Earlier
1134
+ * 0.5 s", indistinguishable from an audio delay command of the same name.
1135
+ * ```ts
1136
+ * declare function shift(seconds: number): void;
1137
+ * // ---cut---
1138
+ * for (const [id, title, seconds] of [
1139
+ * ["sync-earlier", "Earlier 0.5 s", -0.5],
1140
+ * ["sync-later", "Later 0.5 s", 0.5],
1141
+ * ] as const)
1142
+ * plugin.commands.add({
1143
+ * id,
1144
+ * title,
1145
+ * category: "Subtitle Sync", // the palette reads "Subtitle Sync: Earlier 0.5 s"
1146
+ * bindings: [id === "sync-earlier" ? "." : ","],
1147
+ * run: () => shift(seconds),
1148
+ * });
1149
+ * ```
1150
+ *
1151
+ * @player 0.7.6 (declare `"minPlayerVersion": "0.7.6"`; older players
1152
+ * refuse the declaration with `invalid`).
1153
+ * @since 1.6.4
1154
+ */
1155
+ category?: string | Text;
1156
+ /**
1157
+ * The id of this plugin's command that opens the window or panel where
1158
+ * this command's control lives (`"tune"` for a Subtitle Tuning… command
1159
+ * whose window has the sync slider). The player's command palette shows
1160
+ * that place next to the command. A local id, like `id`: ASCII letters,
1161
+ * digits, `.`, `_` and `-`, 1–128 bytes; the player refuses anything else
1162
+ * with `invalid`. The command it names need not be registered yet; while
1163
+ * it is not, the palette shows the place it would show without `home`:
1164
+ * the menu path above the item, "Menu" for a top-level item, nothing for
1165
+ * a command outside the menu. Sent only when set: players
1166
+ * before 0.7.6 refuse the field with `invalid`, so declare
1167
+ * `"minPlayerVersion": "0.7.6"` when you set it.
1168
+ *
1169
+ * @example Point a key command at its window
1170
+ * The palette row for "Subtitle Sync: Earlier 0.5 s" also says where the
1171
+ * same setting can be changed by hand: the Subtitle Tuning window that the
1172
+ * `tune` command opens.
1173
+ * ```ts
1174
+ * declare const tuning: { show(): void };
1175
+ * declare function shift(seconds: number): void;
1176
+ * // ---cut---
1177
+ * plugin.commands.add({
1178
+ * id: "tune",
1179
+ * path: ["@subtitles", "Subtitle Tuning…"],
1180
+ * run: () => tuning.show(),
1181
+ * });
1182
+ * plugin.commands.add({
1183
+ * id: "sync-earlier",
1184
+ * title: "Earlier 0.5 s",
1185
+ * category: "Subtitle Sync",
1186
+ * home: "tune", // the palette shows "Subtitle Tuning" as its place
1187
+ * run: () => shift(-0.5),
1188
+ * });
1189
+ * ```
1190
+ *
1191
+ * @player 0.7.6 (declare `"minPlayerVersion": "0.7.6"`; older players
1192
+ * refuse the declaration with `invalid`).
1193
+ * @since 1.6.4
1194
+ */
1195
+ home?: string;
1115
1196
  /**
1116
1197
  * Sibling order in the menu, ascending (integer, default 0). A submenu sits
1117
1198
  * where its first command sits, except a built-in menu, which keeps its
@@ -1682,11 +1763,11 @@ export interface Ui {
1682
1763
  * calls `image()` again after rewriting it, because the SDK does not send
1683
1764
  * a value that did not change.
1684
1765
  * ```ts
1685
- * const window = plugin.windows.create("ui/thumbnail.slint");
1766
+ * const thumbnailWindow = plugin.windows.create("ui/thumbnail.slint");
1686
1767
  * const file = `${plugin.dataDirectory}/thumbnail.png`;
1687
1768
  * // … write the PNG to `file` (for example with plugin.tools.run("ffmpeg", …)) …
1688
- * window.set({ thumbnail: plugin.ui.image(file) });
1689
- * await window.show();
1769
+ * thumbnailWindow.set({ thumbnail: plugin.ui.image(file) });
1770
+ * await thumbnailWindow.show();
1690
1771
  * ```
1691
1772
  *
1692
1773
  * @since 1.0.0
@@ -1850,11 +1931,11 @@ export interface Ui {
1850
1931
  * const response = await fetch(readmeUrl);
1851
1932
  * if (!response.ok) throw new Error(`README: HTTP ${response.status}`);
1852
1933
  * const text = await response.text();
1853
- * const window = plugin.windows.create("ui/readme.slint");
1934
+ * const readmeWindow = plugin.windows.create("ui/readme.slint");
1854
1935
  * // Bound before the first `show`, as a tool window requires.
1855
- * window.on("openLink", (url: string) => plugin.system.openUrl(url));
1856
- * window.set({ doc: await plugin.ui.markdown(text, { base: readmeUrl }) });
1857
- * await window.show();
1936
+ * readmeWindow.on("openLink", (url: string) => plugin.system.openUrl(url));
1937
+ * readmeWindow.set({ doc: await plugin.ui.markdown(text, { base: readmeUrl }) });
1938
+ * await readmeWindow.show();
1858
1939
  * ```
1859
1940
  *
1860
1941
  * @since 1.1.0
@@ -1999,10 +2080,10 @@ export interface View {
1999
2080
  * sends the value even though it equals the one sent before, which is
2000
2081
  * what pulls a control the user moved back to the plugin's value.
2001
2082
  * ```ts
2002
- * const window = plugin.windows.create("ui/presets.slint");
2083
+ * const presetsWindow = plugin.windows.create("ui/presets.slint");
2003
2084
  * let presetIndex = 0;
2004
- * window.on("presetChosen", (index: number) => {
2005
- * if (index > 2) window.set({ presetIndex }); // "Custom" is not a preset
2085
+ * presetsWindow.on("presetChosen", (index: number) => {
2086
+ * if (index > 2) presetsWindow.set({ presetIndex }); // "Custom" is not a preset
2006
2087
  * else presetIndex = index;
2007
2088
  * });
2008
2089
  * ```
@@ -2165,9 +2246,9 @@ export interface Window extends View, Disposable {
2165
2246
  * window; it rejects with a code, such as `host` for a Slint file that
2166
2247
  * does not compile or `unavailable` without a user interface.
2167
2248
  * ```ts
2168
- * const window = plugin.windows.create("ui/inspector.slint");
2249
+ * const inspectorWindow = plugin.windows.create("ui/inspector.slint");
2169
2250
  * try {
2170
- * await window.show();
2251
+ * await inspectorWindow.show();
2171
2252
  * } catch (error) {
2172
2253
  * // The code is data; `message` has the details for the log.
2173
2254
  * await plugin.osd.show("Cannot open the inspector ({})", [(error as PluginError).code]);
@@ -2276,8 +2357,8 @@ export interface Window extends View, Disposable {
2276
2357
  * store writes the change to `window.json` shortly after.
2277
2358
  * ```ts
2278
2359
  * const store = plugin.storage.json("window", { defaults: { open: false } });
2279
- * const window = plugin.windows.create("ui/notes.slint");
2280
- * window.onClose(() => store.save({ open: false }));
2360
+ * const notesWindow = plugin.windows.create("ui/notes.slint");
2361
+ * notesWindow.onClose(() => store.save({ open: false }));
2281
2362
  * ```
2282
2363
  *
2283
2364
  * @see [Remember a window's state between runs](../../docs/how-to/window-state.md)
@@ -5390,9 +5471,9 @@ export interface Stream extends Disposable {
5390
5471
  * connection; it resolves once both are closed.
5391
5472
  * ```ts
5392
5473
  * declare const stream: Stream;
5393
- * declare const window: Window;
5474
+ * declare const generatorWindow: Window;
5394
5475
  * // ---cut---
5395
- * window.onClose(() => stream.stop());
5476
+ * generatorWindow.onClose(() => stream.stop());
5396
5477
  * ```
5397
5478
  *
5398
5479
  * @since 1.0.0
@@ -9201,6 +9282,16 @@ export interface BindingInfo extends BindingRef {
9201
9282
  * @since 1.5.11
9202
9283
  */
9203
9284
  key?: string[] | null;
9285
+ /**
9286
+ * The command's category as shown ({@link CommandOptions.category},
9287
+ * translated), `null` when it declares none (and always from players
9288
+ * before 0.7.6). Always set by `list()`; optional so that test fakes
9289
+ * written for older SDKs still type-check. A list of commands names one `Category: Title`; without a
9290
+ * category, use the menu path (`path`) as before.
9291
+ *
9292
+ * @since 1.6.4
9293
+ */
9294
+ category?: string | null;
9204
9295
  /** Global, or only while the plugin's panel has focus. */
9205
9296
  scope: "global" | "panel";
9206
9297
  /** The command's own bindings. */
@@ -9660,10 +9751,10 @@ export interface JsonStore<T> extends Disposable {
9660
9751
  * writes the file only once the user stops for 300 ms. Each `save`
9661
9752
  * replaces the value and restarts the debounce timer.
9662
9753
  * ```ts
9663
- * declare const window: Window;
9754
+ * declare const volumeWindow: Window;
9664
9755
  * // ---cut---
9665
9756
  * const store = plugin.storage.json("prefs", { defaults: { volume: 100 } });
9666
- * window.on("volumeChanged", (volume: number) => store.save({ ...store.value, volume }));
9757
+ * volumeWindow.on("volumeChanged", (volume: number) => store.save({ ...store.value, volume }));
9667
9758
  * ```
9668
9759
  *
9669
9760
  * @since 1.0.0
package/dist/wire.d.ts CHANGED
@@ -209,6 +209,8 @@ export interface BindingTable {
209
209
  path: string[] | null;
210
210
  /** The path's keys (player 0.4.3). */
211
211
  key?: string[] | null;
212
+ /** The shown category (player 0.7.6). */
213
+ category?: string | null;
212
214
  scope: "global" | "panel";
213
215
  defaults: string[];
214
216
  current: string[];
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.6.2: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls. The documentation is a tree of short pages read by path, like `go doc`: the same pages and API as https://vestrasys.github.io/vestra-player-sdk/.
3
+ > @vestra-player/sdk 1.6.4: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls. The documentation is a tree of short pages read by path, like `go doc`: the same pages and API as https://vestrasys.github.io/vestra-player-sdk/.
4
4
 
5
5
  ## Read the documentation
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "Plugin API types, test host and plugin CLI for Vestra Player (apiVersion vestra/1)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {