@vestra-player/sdk 1.5.12 → 1.5.14

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,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.14
4
+
5
+ **The documentation site in seven groups, with generated reference tables.** A documentation release; `apiVersion` stays `vestra/1`, and plugins need no change. Everything a plugin author needs is now on the [documentation site](https://vestrasys.github.io/vestra-player-sdk/) once, in English.
6
+
7
+ - **Seven groups**: Tutorials, Concepts, How-to, Guidelines, Examples, Reference and API. `scripts/doc-structure.mjs` lists every page and why it is a page of its own; `npm run docs:check` fails on a page that is not listed, a page without a `description`, a placeholder ("coming soon"), a nested folder, a stale generated table or a page far over its size limit.
8
+ - **Reference** (new): Manifest (every field and the player version each newer part of the API needs), Capabilities, Limits, Commands, menus and bindings (built-in menus, submenu keys in use, the built-in commands and their default keys, binding syntax and names, core actions, the keys the system takes first), Platforms, Theme (tokens with their current `vestra` values, fonts, icons, widgets) and Command line. Their tables are generated from the SDK's code and Slint files (`npm run docs:tables`), so they cannot drift from it.
9
+ - **Guidelines** (new): interface and menu rules.
10
+ - **New pages**: Concepts *How a plugin runs*, *Commands, menus and key bindings*, *Playback state, controls and sources*, *Video and audio processing*; How-to *Test a plugin*, *Publish a plugin*; *Translate a plugin* gains the language list and the machine translation review. Getting started and the README link to the Reference instead of repeating its tables; the old compatibility tables are replaced by the generated player version table.
11
+ - **New exports** for the generated tables and for tests (`@vestra-player/sdk/testing`): `CAPABILITY_INFO` (per capability: the members it opens, the capabilities they also need, the store's wording, whether the store highlights it; `CAPABILITIES` is derived from it), `MESSAGE_LIMIT` (1 MiB per message), `OSD_LIMITS`, `SECRET_LIMITS`, `BINDING_INPUTS` (the key and mouse names a binding is made of), `BUILTIN_SUBMENUS` (the submenu keys the built-in plugins use), `BUILTIN_COMMANDS` (the built-in commands with their menu paths and default keys) and `THEME_PALETTES` (the colours of `vestra` and `vestra-light` by `Theme` token). The player's tests check them against the player.
12
+ - **`TestHost`** enforces the information block's limits like the player: `OsdPanel.set` with more than 32 lines or 4096 bytes (after `maxColumns` is applied) fails with `invalid`.
13
+ - `llms.txt` lists every capability and points at every page of the site in `llms-full.txt`, which now contains all of them.
14
+
15
+ ## 1.5.13
16
+
17
+ **`i18n extract` keeps the translator's comments and writes the right number of plural forms.** A fix; `apiVersion` stays `vestra/1`.
18
+
19
+ - Merging into a `locales/<lang>.po` kept only the header's text: comment lines before the header (`# Reviewed by an AI reviewer on 2026-10-25`, `# Translator: …`, `#, fuzzy`) and the translator's own `# ` comments on entries were dropped. Both are now kept as written (`parsePo` returns `headerComments`, entries carry `comments`; `writePo` writes them).
20
+ - A plural entry has exactly `nplurals` `msgstr[n]` lines, from the file's `Plural-Forms` (or the language: one for `ja`, `ko`, `zh-Hans`). Before, every plural entry got an empty `msgstr[1]`, also in one-form languages; a merge now removes such an extra empty line (a filled one stays).
21
+
3
22
  ## 1.5.12
4
23
 
5
24
  **Keys of disabled commands pass on; `i18n check` fails on stale catalogs; translator notes in tables; the Button follows its text.** A fix release; `apiVersion` stays `vestra/1`.
package/README.md CHANGED
@@ -72,31 +72,23 @@ Complete, tested plugins in [`examples/`](https://github.com/vestrasys/vestra-pl
72
72
  | [menus](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/menus) | Radio items, separator groups and a menu item whose title changes. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Menus.html) |
73
73
  | [theme-widgets](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/theme-widgets) | A tool window drawn with the player's theme and widgets. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Theme_widgets.html) |
74
74
  | [markdown](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/markdown) | A window that shows a Markdown document. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Example__Markdown.html) |
75
- | [i18n](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/i18n) | A plugin in the player's language: English source text, a translation. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Counter__i18n_.html) |
75
+ | [i18n](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples/i18n) | A plugin in the player's language: English source text, a translation. | [Step by step](https://vestrasys.github.io/vestra-player-sdk/documents/Counter_%28i18n%29.html) |
76
76
 
77
- ## Command line
77
+ ## Documentation
78
78
 
79
- | Command | Does |
80
- | ----------------------------------------------------------- | -------------------------------------------------------------------------- |
81
- | `vestra-plugin check [dir...]` | Checks the manifest, then a strict type check of the plugin and its tests. |
82
- | `vestra-plugin test [dir...]` | Runs `node --test` on `tests/*.test.{ts,mjs}`. |
83
- | `vestra-plugin build [dir...] [--version x.y.z]` | Writes `dist/`: types stripped, files copied, manifest versioned. |
84
- | `vestra-plugin pack [dir...] [--version x.y.z] [--out dir]` | Builds, then packs `dist/` as `<id>-<version>.vxp`. |
85
- | `vestra-plugin i18n extract [dir...] [--locale lang]` | Collects the texts to translate into `locales/`, keeping translations. |
86
- | `vestra-plugin i18n check [dir...]` | Translation coverage and untranslatable text; fails on stale catalogs. |
79
+ The [documentation site](https://vestrasys.github.io/vestra-player-sdk/) is the one place for everything a plugin author needs:
87
80
 
88
- ## Compatibility
81
+ - **[Tutorials](https://vestrasys.github.io/vestra-player-sdk/documents/Tutorials.html)**: [Getting started](https://vestrasys.github.io/vestra-player-sdk/documents/Getting_started.html), from an empty folder to a packed plugin.
82
+ - **[Concepts](https://vestrasys.github.io/vestra-player-sdk/documents/Concepts.html)**: how a plugin runs, commands and key bindings, playback, video and audio processing, the interface.
83
+ - **[How-to guides](https://vestrasys.github.io/vestra-player-sdk/documents/How-to_guides.html)**: testing, translating and publishing a plugin, responsive sliders, remembering window state.
84
+ - **[Guidelines](https://vestrasys.github.io/vestra-player-sdk/documents/Guidelines.html)**: rules for the interface and the menus.
85
+ - **[Examples](https://vestrasys.github.io/vestra-player-sdk/documents/Examples.html)**: the walkthroughs above.
86
+ - **[Reference](https://vestrasys.github.io/vestra-player-sdk/documents/Reference.html)**: capabilities, manifest fields, limits, menus and bindings, platforms, theme, and the [command line](https://vestrasys.github.io/vestra-player-sdk/documents/Command_line.html) (`vestra-plugin check`, `test`, `build`, `pack`, `i18n extract`, `i18n check`).
87
+ - **[API](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html)**: every type and member of `@vestra-player/sdk` and `@vestra-player/sdk/testing`.
89
88
 
90
- | SDK | Plugin API | Vestra Player | Node.js |
91
- | --- | ---------- | ----------------------------------------------------------------------------------------- | ---------------------------- |
92
- | 1.0 | `vestra/1` | 0.1 or newer | 22.18 or newer |
93
- | 1.1 | `vestra/1` | 0.2 or newer for the 1.1 additions | 22.18 or newer |
94
- | 1.2 | `vestra/1` | 0.2 or newer | 26 (shipped with the player) |
95
- | 1.3 | `vestra/1` | 0.2 or newer; 0.3 for the 1.3 additions (menu ids, `Theme.danger`, empty video chains) | 26 (shipped with the player) |
96
- | 1.4 | `vestra/1` | 0.2 or newer; 0.3 for the 1.3 additions. `ThemeId` is `"vestra"` only (see the CHANGELOG) | 26 (shipped with the player) |
97
- | 1.5 | `vestra/1` | 0.2 or newer; 0.3 for the 1.3 additions; 0.4 for the 1.5 additions (`vestra-light`, `@vestra/icons.slint`); 0.4.3 for translations (1.5.9) | 26 (shipped with the player) |
89
+ ## Compatibility
98
90
 
99
- Minor releases only add backwards-compatible API; a breaking change gets a new `apiVersion` and a new major version. `@vestra-player/sdk/runtime` is for the player only and not covered by semver.
91
+ Every 1.x release implements the plugin API `vestra/1`: minor releases only add backwards-compatible API, and a breaking change gets a new `apiVersion` and a new major version. Plugins run on the Node.js 26 that the player ships. API added after SDK 1.0 may need a newer player; [Player versions](https://vestrasys.github.io/vestra-player-sdk/documents/Manifest.html#player-versions) lists which API needs which player, for the manifest's `minPlayerVersion`. `@vestra-player/sdk/runtime` is for the player only and not covered by semver.
100
92
 
101
93
  ## Contributing · License
102
94
 
@@ -1,7 +1,79 @@
1
1
  /**
2
- * Manifest capabilities the player knows (`vestra/1`). The same list as the
2
+ * What one manifest capability is for: the members it opens, the
3
+ * capabilities they also need, and how the plugin store describes it. An
4
+ * entry of {@link CAPABILITY_INFO}.
5
+ *
6
+ * @since 1.5.14
7
+ */
8
+ export interface CapabilityInfo {
9
+ /**
10
+ * The members this capability opens, as `Interface.member` (for example
11
+ * `Commands.add` is `plugin.commands.add`). A call to one of them without
12
+ * the capability fails with code `capability`; `Plugin.video.filter` is
13
+ * `plugin.video.filter`. A member can be listed
14
+ * under more than one capability when it needs all of them
15
+ * (`Panel.onChange` needs `ui.mount` and `events`).
16
+ */
17
+ readonly apis: readonly string[];
18
+ /**
19
+ * Capabilities every member in {@link CapabilityInfo.apis} also needs:
20
+ * declare them as well. What only one member or one option needs
21
+ * (`Plugin.video.filter` also needs `events`) is documented at that member.
22
+ */
23
+ readonly requires: readonly string[];
24
+ /**
25
+ * How the plugin store and the player's install confirmation describe the
26
+ * capability to the user, in English (the player translates it). Two
27
+ * capabilities can share one description (`media.read` and
28
+ * `media.control`); the store then shows it once.
29
+ */
30
+ readonly label: string;
31
+ /**
32
+ * `true` when the store highlights the capability as one to think about
33
+ * before installing (it reaches outside the plugin's own features: the
34
+ * clipboard, every shortcut, the keychain, other plugins).
35
+ */
36
+ readonly risky: boolean;
37
+ }
38
+ /**
39
+ * Every manifest capability the player knows (`vestra/1`), in the player's
40
+ * order, with the members it opens, the capabilities they also need and the
41
+ * store's wording. The player's tests check that the names agree with the
42
+ * player's `host/src/plugins.rs` and the labels with its plugin store.
43
+ *
44
+ * A member not listed under any capability needs none (`plugin.tools`,
45
+ * `plugin.storage`, `plugin.system`, `plugin.app`, `plugin.ui`,
46
+ * `plugin.i18n`, reading `plugin.window.state`, `plugin.appearance.theme`
47
+ * and `plugin.packages.list`).
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * import { CAPABILITY_INFO } from "@vestra-player/sdk/testing";
52
+ *
53
+ * const manifest = JSON.parse(await readFile("manifest.json", "utf8"));
54
+ * for (const capability of manifest.capabilities)
55
+ * for (const needed of CAPABILITY_INFO[capability].requires)
56
+ * assert.ok(manifest.capabilities.includes(needed), needed);
57
+ * ```
58
+ *
59
+ * @since 1.5.14
60
+ */
61
+ export declare const CAPABILITY_INFO: Readonly<Record<string, CapabilityInfo>>;
62
+ /**
63
+ * Manifest capabilities the player knows (`vestra/1`): the keys of
64
+ * {@link CAPABILITY_INFO}, in the player's order. The same list as the
3
65
  * player's `host/src/plugins.rs`; the player's tests check that they agree.
4
66
  */
5
67
  export declare const CAPABILITIES: readonly string[];
6
68
  /** The plugin API version this SDK implements (`manifest.apiVersion`). */
7
69
  export declare const API_VERSION = "vestra/1";
70
+ /**
71
+ * The longest message a plugin may send the player, in UTF-8 bytes of one
72
+ * protocol line with its newline (1 MiB). The player stops a plugin that
73
+ * writes a longer one, so the plugin's side refuses such a request before
74
+ * sending it: it rejects with code `limit`. Property values meet
75
+ * {@link UI_VALUE_LIMITS} before this.
76
+ *
77
+ * @since 1.5.14
78
+ */
79
+ export declare const MESSAGE_LIMIT: number;
@@ -1,33 +1,268 @@
1
1
  /**
2
- * Manifest capabilities the player knows (`vestra/1`). The same list as the
2
+ * Every manifest capability the player knows (`vestra/1`), in the player's
3
+ * order, with the members it opens, the capabilities they also need and the
4
+ * store's wording. The player's tests check that the names agree with the
5
+ * player's `host/src/plugins.rs` and the labels with its plugin store.
6
+ *
7
+ * A member not listed under any capability needs none (`plugin.tools`,
8
+ * `plugin.storage`, `plugin.system`, `plugin.app`, `plugin.ui`,
9
+ * `plugin.i18n`, reading `plugin.window.state`, `plugin.appearance.theme`
10
+ * and `plugin.packages.list`).
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { CAPABILITY_INFO } from "@vestra-player/sdk/testing";
15
+ *
16
+ * const manifest = JSON.parse(await readFile("manifest.json", "utf8"));
17
+ * for (const capability of manifest.capabilities)
18
+ * for (const needed of CAPABILITY_INFO[capability].requires)
19
+ * assert.ok(manifest.capabilities.includes(needed), needed);
20
+ * ```
21
+ *
22
+ * @since 1.5.14
23
+ */
24
+ export const CAPABILITY_INFO = Object.freeze({
25
+ "producer.av": {
26
+ apis: ["Player.openStream"],
27
+ requires: ["playback.control"],
28
+ label: "Provide audio and video it generates itself",
29
+ risky: false,
30
+ },
31
+ "playback.read": {
32
+ apis: [
33
+ "Player.state",
34
+ "Player.refresh",
35
+ "Player.stats",
36
+ "Player.chapters",
37
+ "Player.audioDevices",
38
+ "Timeline.marks",
39
+ ],
40
+ requires: [],
41
+ label: "Read what is playing and the position",
42
+ risky: false,
43
+ },
44
+ "playback.control": {
45
+ apis: [
46
+ "Player.open",
47
+ "Player.setPaused",
48
+ "Player.seek",
49
+ "Player.setRate",
50
+ "Player.restart",
51
+ "Player.close",
52
+ "Player.scrub",
53
+ "Player.setOutput",
54
+ "Player.provideSequence",
55
+ "Player.next",
56
+ "Player.previous",
57
+ "Player.addAudioTrack",
58
+ "Player.selectAudioTrack",
59
+ "Player.step",
60
+ "Player.setStepSeek",
61
+ "Player.setLoop",
62
+ "Player.setAudioDevice",
63
+ "Power.preventSleep",
64
+ ],
65
+ requires: [],
66
+ label: "Open media, control playback, keep the computer awake",
67
+ risky: false,
68
+ },
69
+ "source.resolve": {
70
+ apis: ["Resolvers.add"],
71
+ requires: [],
72
+ label: "Resolve online video addresses",
73
+ risky: false,
74
+ },
75
+ events: {
76
+ apis: [
77
+ "Player.onChange",
78
+ "Subtitles.onChange",
79
+ "Timeline.onChange",
80
+ "MainWindow.onChange",
81
+ "Appearance.onChange",
82
+ "PanelSlot.onChange",
83
+ "Panel.onChange",
84
+ "BackdropSlot.onChange",
85
+ "Resolvers.onFileTypesChange",
86
+ ],
87
+ requires: [],
88
+ label: "Receive playback changes as they happen",
89
+ risky: false,
90
+ },
91
+ commands: {
92
+ apis: ["Commands.add", "Host.run"],
93
+ requires: [],
94
+ label: "Add menu commands and shortcuts",
95
+ risky: false,
96
+ },
97
+ "ui.window": {
98
+ apis: ["Windows.create"],
99
+ requires: [],
100
+ label: "Open tool windows",
101
+ risky: false,
102
+ },
103
+ "ui.mount": {
104
+ apis: [
105
+ "Panels.mount",
106
+ "Panels.bottom",
107
+ "Panels.sidebar",
108
+ "Panels.backdrop",
109
+ "PanelSlot.onChange",
110
+ "Panel.onChange",
111
+ "BackdropSlot.onChange",
112
+ ],
113
+ requires: [],
114
+ label: "Show panels in the bottom bar, the sidebar or behind the video",
115
+ risky: false,
116
+ },
117
+ "ui.dialog": {
118
+ apis: [
119
+ "Dialogs.confirm",
120
+ "Dialogs.openFile",
121
+ "Dialogs.openFiles",
122
+ "Dialogs.openDirectory",
123
+ "Dialogs.saveFile",
124
+ ],
125
+ requires: [],
126
+ label: "Show file pickers and confirmations",
127
+ risky: false,
128
+ },
129
+ "plugins.manage": {
130
+ apis: [
131
+ "Packages.validate",
132
+ "Packages.validateArchive",
133
+ "Packages.rollback",
134
+ "Packages.setEnabled",
135
+ "Packages.uninstall",
136
+ ],
137
+ requires: [],
138
+ label: "Install, uninstall and disable other plugins",
139
+ risky: true,
140
+ },
141
+ "processing.video": {
142
+ apis: ["Plugin.video.filter", "Plugin.video.view", "Plugin.video.render"],
143
+ requires: ["playback.read"],
144
+ label: "Process the picture (filters, shaders)",
145
+ risky: false,
146
+ },
147
+ "processing.audio": {
148
+ apis: ["Plugin.audio.filter"],
149
+ requires: ["playback.read", "events"],
150
+ label: "Process the sound",
151
+ risky: false,
152
+ },
153
+ "subtitles.read": {
154
+ apis: ["Subtitles.state", "Subtitles.onChange", "Subtitles.refresh"],
155
+ requires: [],
156
+ label: "Read subtitles",
157
+ risky: false,
158
+ },
159
+ "subtitles.control": {
160
+ apis: [
161
+ "Subtitles.select",
162
+ "Subtitles.load",
163
+ "Subtitles.remove",
164
+ "Subtitles.setStyle",
165
+ ],
166
+ requires: [],
167
+ label: "Load and change subtitles",
168
+ risky: false,
169
+ },
170
+ "media.read": {
171
+ apis: ["MediaCapture.capture", "Snapshot.export"],
172
+ requires: ["media.control"],
173
+ label: "Take snapshots and export clips",
174
+ risky: false,
175
+ },
176
+ "media.control": {
177
+ apis: ["MediaCapture.capture", "Snapshot.export"],
178
+ requires: ["media.read"],
179
+ label: "Take snapshots and export clips",
180
+ risky: false,
181
+ },
182
+ "ui.osd": {
183
+ apis: ["Osd.show", "Osd.panel"],
184
+ requires: [],
185
+ label: "Show messages on the video",
186
+ risky: false,
187
+ },
188
+ clipboard: {
189
+ apis: [
190
+ "Clipboard.readText",
191
+ "Clipboard.writeText",
192
+ "Clipboard.writeImage",
193
+ ],
194
+ requires: [],
195
+ label: "Read and write the clipboard",
196
+ risky: true,
197
+ },
198
+ "window.control": {
199
+ apis: [
200
+ "MainWindow.setVideoArea",
201
+ "MainWindow.setAlwaysOnTop",
202
+ "MainWindow.setCursorAutoHide",
203
+ "MainWindow.setPictureInPicture",
204
+ ],
205
+ requires: [],
206
+ label: "Adjust the main window (size, on top, picture-in-picture)",
207
+ risky: false,
208
+ },
209
+ "bindings.manage": {
210
+ apis: [
211
+ "Bindings.list",
212
+ "Bindings.set",
213
+ "Bindings.reset",
214
+ "Bindings.onChange",
215
+ ],
216
+ requires: [],
217
+ label: "Change all shortcuts",
218
+ risky: true,
219
+ },
220
+ "timeline.marks": {
221
+ apis: ["Timeline.layer"],
222
+ requires: [],
223
+ label: "Show marks on the progress bar",
224
+ risky: false,
225
+ },
226
+ "ui.drop": {
227
+ apis: ["Drops.provide", "Panel.onDrop"],
228
+ requires: [],
229
+ label: "Receive files dropped on the player",
230
+ risky: false,
231
+ },
232
+ "ui.settings": {
233
+ apis: ["Settings.page", "Settings.show"],
234
+ requires: [],
235
+ label: "Add pages to the Options window",
236
+ risky: false,
237
+ },
238
+ "ui.theme": {
239
+ apis: ["Appearance.set", "Appearance.setLanguage"],
240
+ requires: [],
241
+ label: "Change the player's palette",
242
+ risky: false,
243
+ },
244
+ secrets: {
245
+ apis: ["Secrets.get", "Secrets.set", "Secrets.delete"],
246
+ requires: [],
247
+ label: "Store sign-in details in the system keychain",
248
+ risky: true,
249
+ },
250
+ });
251
+ /**
252
+ * Manifest capabilities the player knows (`vestra/1`): the keys of
253
+ * {@link CAPABILITY_INFO}, in the player's order. The same list as the
3
254
  * player's `host/src/plugins.rs`; the player's tests check that they agree.
4
255
  */
5
- export const CAPABILITIES = [
6
- "producer.av",
7
- "playback.read",
8
- "playback.control",
9
- "source.resolve",
10
- "events",
11
- "commands",
12
- "ui.window",
13
- "ui.mount",
14
- "ui.dialog",
15
- "plugins.manage",
16
- "processing.video",
17
- "processing.audio",
18
- "subtitles.read",
19
- "subtitles.control",
20
- "media.read",
21
- "media.control",
22
- "ui.osd",
23
- "clipboard",
24
- "window.control",
25
- "bindings.manage",
26
- "timeline.marks",
27
- "ui.drop",
28
- "ui.settings",
29
- "ui.theme",
30
- "secrets",
31
- ];
256
+ export const CAPABILITIES = Object.freeze(Object.keys(CAPABILITY_INFO));
32
257
  /** The plugin API version this SDK implements (`manifest.apiVersion`). */
33
258
  export const API_VERSION = "vestra/1";
259
+ /**
260
+ * The longest message a plugin may send the player, in UTF-8 bytes of one
261
+ * protocol line with its newline (1 MiB). The player stops a plugin that
262
+ * writes a longer one, so the plugin's side refuses such a request before
263
+ * sending it: it rejects with code `limit`. Property values meet
264
+ * {@link UI_VALUE_LIMITS} before this.
265
+ *
266
+ * @since 1.5.14
267
+ */
268
+ export const MESSAGE_LIMIT = 1024 * 1024;
@@ -44,6 +44,95 @@ export declare const COMMAND_LIMITS: {
44
44
  * ```
45
45
  */
46
46
  export declare const MENU_IDS: readonly ["open", "playback", "subtitles", "video", "audio", "screen", "settings"];
47
+ /**
48
+ * A submenu that one of the player's built-in plugins declares: an entry of
49
+ * {@link BUILTIN_SUBMENUS}.
50
+ *
51
+ * @since 1.5.14
52
+ */
53
+ export interface BuiltinSubmenu {
54
+ /**
55
+ * The submenu's key path: the English texts paths merge by, the
56
+ * built-in menu first by its key (`"Playback"` for `@playback`). Write
57
+ * these segments, followed by your command's title, to put a command into
58
+ * this submenu.
59
+ */
60
+ readonly path: readonly string[];
61
+ /** The built-in plugins that declare it (`vestra.history`). */
62
+ readonly plugins: readonly string[];
63
+ }
64
+ /**
65
+ * The submenus the player's built-in plugins declare, by key path, in menu
66
+ * order (built-in menus as in {@link MENU_IDS}, then top-level submenus).
67
+ * Menu paths merge by their English key, so a command whose path starts
68
+ * with the same keys appears in the same submenu, whatever the interface
69
+ * language; the submenu's title is the declaring plugin's translation. The
70
+ * player's own submenus (`Playback ▸ Speed`, `Playback ▸ Seek`) are not
71
+ * listed. The player's tests check that this list agrees with what its
72
+ * built-in plugins declare.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * import { BUILTIN_SUBMENUS } from "@vestra-player/sdk/testing";
77
+ *
78
+ * const snapshot = BUILTIN_SUBMENUS.find((menu) => menu.path.at(-1) === "Snapshot")!;
79
+ * plugin.commands.add({
80
+ * id: "snapshot-to-clipboard",
81
+ * path: ["@video", ...snapshot.path.slice(1), "Copy Snapshot"],
82
+ * run: copySnapshot,
83
+ * });
84
+ * ```
85
+ *
86
+ * @since 1.5.14
87
+ */
88
+ export declare const BUILTIN_SUBMENUS: readonly BuiltinSubmenu[];
89
+ /**
90
+ * A command the player or one of its built-in plugins registers: an entry
91
+ * of {@link BUILTIN_COMMANDS}.
92
+ *
93
+ * @since 1.5.14
94
+ */
95
+ export interface BuiltinCommand {
96
+ /** `"host"` for the player's own commands, else the built-in plugin's id. */
97
+ readonly owner: string;
98
+ /** The command's id within its owner (`"play-pause"`, `"capture.save"`). */
99
+ readonly id: string;
100
+ /**
101
+ * The menu path by key (the English texts paths merge by), the leaf
102
+ * included; a built-in menu by its key (`"Video"`, not `"@video"`).
103
+ * Empty for a command that is not in the main menu (a panel shortcut).
104
+ */
105
+ readonly path: readonly string[];
106
+ /** The English title (the last segment of `path` when there is one). */
107
+ readonly title: string;
108
+ /** The default bindings, canonical, in the order the command declares them. */
109
+ readonly bindings: readonly string[];
110
+ /** `"panel"`: the bindings apply only while the owner's panel has focus. */
111
+ readonly focus: "panel" | null;
112
+ }
113
+ /**
114
+ * The commands the player and its built-in plugins register when the
115
+ * player starts with nothing open, in menu order (commands outside the main
116
+ * menu last): their key paths, default bindings and owners. Use it to pick
117
+ * a menu place and default keys that do not collide with the player's (the
118
+ * user's own changes come on top; see {@link @vestra-player/sdk!Bindings | Bindings}). Menu items built
119
+ * from content (tracks, recent files, output devices) are not listed; their
120
+ * placeholders (`No Audio Tracks`) are. The player's tests check that this
121
+ * list agrees with what the player and its built-in plugins register.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * import assert from "node:assert/strict";
126
+ * import { BUILTIN_COMMANDS } from "@vestra-player/sdk/testing";
127
+ *
128
+ * const taken = BUILTIN_COMMANDS.filter((command) => command.focus === null)
129
+ * .flatMap((command) => command.bindings);
130
+ * assert.ok(!taken.includes("Ctrl+K"));
131
+ * ```
132
+ *
133
+ * @since 1.5.14
134
+ */
135
+ export declare const BUILTIN_COMMANDS: readonly BuiltinCommand[];
47
136
  export type CommandState = {
48
137
  enabled: boolean;
49
138
  checked: boolean | null;