@vestra-player/sdk 1.5.24 → 1.5.26

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/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @vestra-player/sdk 1.5.24: full reference
1
+ # @vestra-player/sdk 1.5.26: full reference
2
2
 
3
3
  > Everything needed to write a Vestra Player plugin (plugin API `vestra/1`): the guides, every public declaration of `@vestra-player/sdk` and `@vestra-player/sdk/testing` with its documentation, and the complete source of the example plugins. Generated from the SDK's sources by scripts/llms.mjs; the short index is llms.txt.
4
4
 
@@ -65,6 +65,29 @@ my-plugin/
65
65
  }
66
66
  ```
67
67
 
68
+ Add a `tsconfig.json` for your editor. The `plugins` entry turns on the SDK's TypeScript plugin, which marks a call whose capability the manifest does not declare as you type (Capabilities):
69
+
70
+ ```json
71
+ {
72
+ "compilerOptions": {
73
+ "target": "ES2023",
74
+ "module": "NodeNext",
75
+ "moduleResolution": "NodeNext",
76
+ "strict": true,
77
+ "noEmit": true,
78
+ "allowImportingTsExtensions": true,
79
+ "verbatimModuleSyntax": true,
80
+ "erasableSyntaxOnly": true,
81
+ "skipLibCheck": true,
82
+ "types": ["node"],
83
+ "plugins": [{ "name": "@vestra-player/sdk/ts-plugin" }]
84
+ },
85
+ "include": ["*.ts", "tests/*.ts"]
86
+ }
87
+ ```
88
+
89
+ In VS Code, open a `.ts` file, run **TypeScript: Select TypeScript Version** and pick **Use Workspace Version** (the TypeScript the SDK installed): editors load a project's language-service plugins only with the project's own TypeScript.
90
+
68
91
  #### The manifest
69
92
 
70
93
  ```json
@@ -81,7 +104,7 @@ my-plugin/
81
104
  - `version` is `x.y.z`; `vestra-plugin build` writes it into the package.
82
105
  - `apiVersion` is always `"vestra/1"` for this major version of the SDK.
83
106
  - `entry` is the entry module: `.ts` while you develop, `.js` after building.
84
- - `capabilities` lists what the plugin may do. A call to a member whose capability is missing fails with `code: "capability"`.
107
+ - `capabilities` lists what the plugin may do. A call to a member whose capability is missing fails with `code: "capability"`; the editor (with the `tsconfig.json` above) and `vestra-plugin check` point such calls out before the plugin runs.
85
108
 
86
109
  Optional display fields (`name`, `author`, `punchline`, `description`, `tags`, `icon`, `readme`, `changelog`, `sourceUrl`, `license`) are shown by the plugin store and the plugin manager, and `minPlayerVersion` names the oldest player that can load the plugin. Every field with its exact rules is in Manifest.
87
110
 
@@ -221,7 +244,7 @@ It assumes you have written a first plugin (Getting started). For each type's me
221
244
  #### The plugin process
222
245
 
223
246
  - **One Node.js process per plugin.** The player starts each plugin in a Node.js 26 process of its own (the installed player ships that Node.js). The plugin can use the Node standard library, the file system, the network and npm packages. A crash or a busy loop in one plugin does not stop the player or other plugins.
224
- - **No web storage.** The player starts Node with `--no-experimental-webstorage`, so `localStorage` and `sessionStorage` do not exist. Keep settings with plugin.storage or in your own files in the data directory.
247
+ - **No web storage.** The player starts Node with `--no-experimental-webstorage`, so `localStorage` and `sessionStorage` do not exist. Keep settings with plugin.storage or in your own files in the data directory, and state that must outlive a restart of the plugin's process, but not the player, in plugin.storage.session (Restarts).
225
248
  - **Trusted code.** Manifest capabilities are a contract the player checks on every call (a missing one fails with `code: "capability"`), not a sandbox: the process has the user's file and network access. The full list of capabilities and the API each one opens is in Reference: capabilities.
226
249
  - **One API on every system.** The same plugin runs on macOS and Windows. Something a system cannot do fails with `unsupported`; the differences are collected in Reference: platforms.
227
250
  - **Types only.** The plugin imports `@vestra-player/sdk` with `import type`. The `plugin` object it receives is built by the player's copy of the SDK, so a built plugin contains only its own code and keeps working with the SDK version the player ships.
@@ -314,6 +337,27 @@ The player stops a plugin when the user disables or uninstalls it, when it is re
314
337
 
315
338
  So `onDispose` cleanup must be quick and must not rely on the player. Media the plugin opened with player.open **keeps playing** after it stops.
316
339
 
340
+ #### Restarts: activate can run more than once
341
+
342
+ A plugin's process does not live as long as the player. Within one player session the player stops a plugin and starts it again, and `activate` runs again in a new process, when:
343
+
344
+ - the plugin is **reloaded** (from the plugin manager, or a development load with `--plugin`) or **upgraded**;
345
+ - it **crashed**, or failed to start, and is started again.
346
+
347
+ A later player may restart a plugin in more cases: restarting plugins that share one process together when one of them faults or hangs, or stopping a plugin nobody uses and starting it on its next use. Write the plugin for those too.
348
+
349
+ Everything in the process is gone after such a restart: variables, timers, caches, open sockets. The menus, windows and panels come back because `activate` creates them again. **Keep state that must survive a restart in session storage (or persistent storage):**
350
+
351
+ | Where | Survives a restart of the plugin | Survives the player quitting | Cleared when |
352
+ | --- | --- | --- | --- |
353
+ | Variables in the process | no | no | the process stops |
354
+ | plugin.storage.session (player 0.5.5) | yes | no | the user disables or uninstalls the plugin; the player quits |
355
+ | plugin.storage.json, files in the data directory | yes | yes | uninstalling with `removeData` |
356
+
357
+ Session storage is for state that belongs to this run of the player: a queue the plugin is working through, the item a panel was showing, a token that expires anyway. Settings and history the user expects to find tomorrow go in `storage.json`. Session storage keeps JSON values in the player's memory, at most 1 MiB per plugin (one value a little less, since it travels in one 1 MiB message; split large state over several keys), and does not keep the order of an object's properties. Each `set` is kept as soon as its promise resolves, so a crash right after loses nothing. Disabling a plugin is the user switching it off, so its session values go; a reload, a crash or a failed start keeps them.
358
+
359
+ Write `activate` so that running it twice in one session is normal: read what you kept, then build the interface from it.
360
+
317
361
  #### The data directory
318
362
 
319
363
  plugin.dataDirectory is a writable directory for the plugin's own files (settings, caches, remembered folders). It needs no capability; the player creates it before it starts the plugin.
@@ -1660,7 +1704,7 @@ Every test follows the same five steps:
1660
1704
  4. **Assert** on what the host recorded.
1661
1705
  5. **Stop** with `await stop()`: the plugin is disposed as when the player stops it (scopes released, stores flushed) and the host's temporary files are removed. Each test creates its own host.
1662
1706
 
1663
- `startPlugin` also returns `plugin`, the object `activate` received, to call the API directly (`plugin.player.state`). Its options: `host` (a host you prepared before activation, as above), `dataDirectory` (an existing folder as `plugin.dataDirectory`, to test a restart), `locale` (play another interface language, see Translate a plugin), and `id` with `capabilities` to start without a `manifest.json`.
1707
+ `startPlugin` also returns `plugin`, the object `activate` received, to call the API directly (`plugin.player.state`). Its options: `host` (a host you prepared before activation, as above), `dataDirectory` (an existing folder as `plugin.dataDirectory`, to test a restart), `sessionStorage` (the values of `plugin.storage.session` an earlier run left, to test a restart within one player session), `locale` (play another interface language, see Translate a plugin), and `id` with `capabilities` to start without a `manifest.json`.
1664
1708
 
1665
1709
  #### Act like the user
1666
1710
 
@@ -1775,6 +1819,29 @@ The host checks what the player checks: the command limits, the property value l
1775
1819
 
1776
1820
  Copy `host.dataDirectory` after the first run and pass it as `dataDirectory` to the second, to check what the plugin stored and restores; the example is on startPlugin, and Remember a window's state between runs is a plugin that needs it.
1777
1821
 
1822
+ A restart within one player session (a reload, an upgrade, a crash) keeps plugin.storage.session too. Pass the first run's `host.sessionStorage` as `sessionStorage` to the second run; leave it out to test a fresh player session, where those values are gone:
1823
+
1824
+ ```ts
1825
+ import assert from "node:assert/strict";
1826
+ import { test } from "node:test";
1827
+ import { startPlugin } from "@vestra-player/sdk/testing";
1828
+ import activate from "../main.ts";
1829
+
1830
+ test("goes on with the queue after a restart", async () => {
1831
+ const first = await startPlugin(activate);
1832
+ await first.plugin.storage.session.set("queue", ["a.mkv", "b.mkv"]);
1833
+ const kept = first.host.sessionStorage;
1834
+ await first.stop();
1835
+
1836
+ const second = await startPlugin(activate, { sessionStorage: kept });
1837
+ assert.deepEqual(await second.plugin.storage.session.get("queue"), [
1838
+ "a.mkv",
1839
+ "b.mkv",
1840
+ ]);
1841
+ await second.stop();
1842
+ });
1843
+ ```
1844
+
1778
1845
  Every member of `TestHost` is in the @vestra-player/sdk/testing reference.
1779
1846
 
1780
1847
  ## How-to: Publish a plugin
@@ -2186,6 +2253,7 @@ The table lists the API whose documentation names a player version, newest last:
2186
2253
  | 0.5.1 | ImageRef, ImageOptions.maxSide, PropValue, Ui.image, Ui.remoteImage, SlotState.reason, Panel.setInteraction, PlaybackStats.presentedFps, Player.setStepSeek, Host.run, Packages.reload |
2187
2254
  | 0.5.2 | HostAction |
2188
2255
  | 0.5.3 | Scrub.seek |
2256
+ | 0.5.5 | SessionStorage |
2189
2257
 
2190
2258
  The Slint library carries no version tags: `Theme.danger` needs player 0.3.0; `@vestra/icons.slint` and the palette `vestra-light` need 0.4.0 (Theme).
2191
2259
 
@@ -2226,6 +2294,7 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
2226
2294
  **Also needs** applies to every member of the row: declare those capabilities as well. A few members or options need one more, documented at the member:
2227
2295
 
2228
2296
  - video.filter also needs `events`.
2297
+ - The changes of Subtitles (`select`, `load`, `remove`, `setStyle`) also need `subtitles.read`: they read the current selection first.
2229
2298
  - A `processed` snapshot or export (MediaCapture) also needs `playback.read` and `subtitles.read`.
2230
2299
  - A file resolver (Resolvers.add with file types) also needs `producer.av`.
2231
2300
  - Without `events`, Player.state is read at start and after the plugin's own operations, and `onChange` listeners do not fire for changes made elsewhere.
@@ -2234,6 +2303,21 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
2234
2303
 
2235
2304
  A member not in the table needs no capability: `plugin.tools`, `plugin.storage`, `plugin.system`, `plugin.app`, `plugin.ui` (images and Markdown), `plugin.i18n`, reading window.state, appearance.theme and packages.list. The table is `CAPABILITY_INFO`, which tests can import from `@vestra-player/sdk/testing` to check a manifest (CAPABILITY_INFO).
2236
2305
 
2306
+ #### Catching a missing capability
2307
+
2308
+ The player refuses a request whose capability the manifest does not declare, at run time. The SDK finds such calls earlier, with one rule: the player's own table of which request needs which capability (copied from the player), and the requests each member above sends. Nothing has to be remembered:
2309
+
2310
+ | Where | How |
2311
+ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2312
+ | The editor | An error at the member, "`Player.seek` needs the capability "playback.control", which manifest.json does not declare", with the quick fix **Add 'playback.control' to manifest.json**. It comes from the TypeScript plugin `@vestra-player/sdk/ts-plugin`, listed in the project's `tsconfig.json` (`"plugins": [{ "name": "@vestra-player/sdk/ts-plugin" }]`, as in Getting started). In VS Code, run **TypeScript: Select TypeScript Version** → **Use Workspace Version**: editors load a project's plugins only with the project's own TypeScript. `tsc` ignores language-service plugins. |
2313
+ | `vestra-plugin check`, `build` and `pack` | `check` reports each use as `file:line:column` with the member, the capability and the request, and fails; `build` and `pack` run `check` first, so a package cannot be made with a missing capability. `check --json` prints the findings as JSON (Command line). |
2314
+
2315
+ The TypeScript plugin runs inside the editor's TypeScript server, on the editor's own Node.js, and needs that Node.js to be version 22.12 or later (the plugin is an ES module loaded with `require()`; the SDK itself needs 26, but the editor brings its own); current VS Code qualifies. With an older one the server logs that it could not load `@vestra-player/sdk/ts-plugin` and the editor shows no capability errors; `vestra-plugin check` still reports them. The quick fix edits the saved `manifest.json` (editors do not share unsaved JSON buffers with the TypeScript server) and keeps its layout and line endings.
2316
+
2317
+ The manifest is the nearest `manifest.json` above the file. Only the plugin's own code is checked: `tests/` (tests choose their capabilities in `startPlugin`), `node_modules/`, `dist/` and `vendor/` are not. A member needs the capabilities its requests need and those capabilities' **Also needs**, so a `video.filter` without `playback.read` is reported too.
2318
+
2319
+ The check follows types, not names: `plugin.player.seek`, `const { player } = plugin; player.seek(…)`, a function taking a `Player`, `plugin.clipboard["readText"]` and members of objects other members return (`panel.onChange` on a mounted panel) are all found. Not found: access with a computed name (`player[name]`), values typed `any`, plain JavaScript without types, and options that need more than the member (a `processed` capture, a file resolver). The player's check at run time stays: it is the boundary, since a plugin can bypass the SDK.
2320
+
2237
2321
  ## Reference: Limits
2238
2322
 
2239
2323
  The player bounds what one request may cost it. The limits are guards against runaway plugins, not sizes to aim for: a plugin that needs to come close to one usually needs a different design (send the rows near the view, not the whole list). The SDK checks the same limits before it sends a request, so a plugin's own tests hit them too (TestHost enforces them). A name in code style is a constant you can import from `@vestra-player/sdk/testing`.
@@ -2307,6 +2391,16 @@ plugin.secrets. A name or value out of its rule fails with code `invalid`; a new
2307
2391
  | `SECRET_LIMITS.valueBytes` | 1024 (1 KiB) | UTF-8 bytes of a value. |
2308
2392
  | `SECRET_LIMITS.names` | 256 | Different names one plugin may have stored at the same time. |
2309
2393
 
2394
+ #### Session storage
2395
+
2396
+ plugin.storage.session (player 0.5.5). A key out of its rule, or a value that is not JSON (or has a string with a lone surrogate), fails with code `invalid`; a `set` that would take the plugin over `bytes` fails with `limit` and changes nothing. Each key counts its UTF-8 bytes, its value as compact JSON and `entryBytes`. One `set` is also one message, so a single value must fit in `MESSAGE_LIMIT` with its key and the request around it.
2397
+
2398
+ | Limit | Value | Meaning |
2399
+ | --- | --- | --- |
2400
+ | `SESSION_STORAGE_LIMITS.keyBytes` | 1024 (1 KiB) | UTF-8 bytes of a key. |
2401
+ | `SESSION_STORAGE_LIMITS.bytes` | 1048576 (1 MiB) | Bytes one plugin may hold at the same time: for every key, its UTF-8 bytes, its value written as compact JSON (`JSON.stringify(value)`) and `entryBytes`. |
2402
+ | `SESSION_STORAGE_LIMITS.entryBytes` | 128 | What each key costs on top of its text and value (128): the player's own bookkeeping, so that many tiny entries stay within `bytes` of memory. |
2403
+
2310
2404
  #### Messages
2311
2405
 
2312
2406
  Every request a plugin sends travels as one protocol line. The plugin's side refuses a longer one with code `limit` before sending it; property values meet their own limits first.
@@ -3040,13 +3134,14 @@ Struct **`MarkdownBlock`**: One displayed block. Plugins only pass these through
3040
3134
 
3041
3135
  ```text
3042
3136
  usage: vestra-plugin <check|build|test|pack> [plugin-dir...] [--version x.y.z] [--out dir]
3137
+ vestra-plugin check [plugin-dir...] --json
3043
3138
  vestra-plugin i18n extract [plugin-dir...] [--locale <lang>]...
3044
3139
  vestra-plugin i18n check [plugin-dir...]
3045
3140
  ```
3046
3141
 
3047
3142
  | Command | Does |
3048
3143
  | --- | --- |
3049
- | `vestra-plugin check` | strict type check of the plugin's .ts files and tests/*.ts |
3144
+ | `vestra-plugin check` | strict type check of the plugin's .ts files and tests/*.ts (then every use of an API member whose capability manifest.json does not declare: file:line, member, capability; fails on any; --json prints the findings as JSON) |
3050
3145
  | `vestra-plugin build` | check, then write dist/: .ts → .js with types stripped, other package files copied, dist/manifest.json with `version` |
3051
3146
  | `vestra-plugin test` | node --test on tests/*.test.{ts,mjs} |
3052
3147
  | `vestra-plugin pack` | build, then dist/ → <out>/<id>-<version>.vxp (a zip; default out: the current directory) with its sha256 and size |
@@ -3059,6 +3154,7 @@ Exit codes: 0 on success, 1 when a command failed for any of the directories (ea
3059
3154
 
3060
3155
  - **The manifest first.** Every command checks `manifest.json` against the player's own rules (Manifest) before anything else. `check` also prints hints that do not fail, such as an id without the `<owner>.` prefix the official repository needs.
3061
3156
  - **Type checking** extends the nearest `tsconfig.json` at or above the plugin directory (up to the repository root), else built-in strict settings (`strict`, `NodeNext`, `verbatimModuleSyntax`, `erasableSyntaxOnly`). Only the plugin's own files and `tests/` are checked, with the SDK's own TypeScript 5.x.
3157
+ - **Capabilities.** After the type check, `check` reports every use of an API member whose capability `manifest.json` does not declare (`file:line:column`, the member, the capability, the request) and fails; `build` and `pack` run `check` first. Only the plugin's own code is checked, not `tests/`. The same check runs in the editor (Capabilities). `check --json` prints, instead of the text, one JSON array with an entry per plugin directory: `directory`, `plugin`, `findings` (each with `file` (absolute), `line` and `column` (1-based), `start` and `length` (the member's name as a UTF-16 offset and length in the file, as TypeScript counts), `api`, `capability`, `method`, `message`, and `requiredBy` when the capability is another one's **Also needs**) and `error` when the directory failed.
3062
3158
  - **`import type` only.** A plugin never imports SDK code at run time; `build` fails when the output still refers to `@vestra-player/sdk`. Tests import `@vestra-player/sdk/testing` freely.
3063
3159
  - **Translations.** With a `locales/` folder, `check` prints the `i18n check` problems as warnings and `build` compiles each `locales/<lang>.po` to `dist/locales/<lang>.json` (Translate a plugin).
3064
3160
 
@@ -5675,7 +5771,8 @@ interface SubtitleConditional {
5675
5771
  ### Subtitles
5676
5772
 
5677
5773
  `plugin.subtitles`: tracks, uploads and style. Reading needs
5678
- `subtitles.read`, changes need `subtitles.control`.
5774
+ `subtitles.read`; changes need `subtitles.control` and also
5775
+ `subtitles.read`, since they read the current selection first.
5679
5776
 
5680
5777
  ```ts
5681
5778
  interface Subtitles {
@@ -6427,6 +6524,70 @@ interface Storage {
6427
6524
  validate?: (value: unknown) => value is T;
6428
6525
  debounceMs?: number;
6429
6526
  }): JsonStore<T>;
6527
+ /**
6528
+ * Values the player keeps in its memory for this plugin until the player quits: they survive a restart of the plugin's process (reload, upgrade, crash, failed start) but not the player. See SessionStorage.
6529
+ */
6530
+ readonly session: SessionStorage;
6531
+ }
6532
+ ```
6533
+
6534
+ ### SessionStorage
6535
+
6536
+ `plugin.storage.session`: JSON values that live as long as the **player**,
6537
+ not as long as the plugin's process (like `chrome.storage.session`). No
6538
+ capability; needs player 0.5.5 (older players reject the calls with
6539
+ `unsupported`).
6540
+
6541
+ `activate` can run more than once in one player session: the player stops
6542
+ and starts a plugin's process again when the plugin is reloaded (also a
6543
+ development load with `--plugin`) or upgraded, and after a crash or a
6544
+ failed start. A later player may also restart plugins that share a
6545
+ process, or stop an idle plugin and start it on its next use. Variables do
6546
+ not survive that; keep state that must survive a restart here (or in
6547
+ storage.json when it must also survive the player
6548
+ quitting).
6549
+
6550
+ | | Values |
6551
+ | --- | --- |
6552
+ | The plugin's process restarts: reload, upgrade, crash | kept |
6553
+ | The plugin failed to start or failed, and starts again | kept |
6554
+ | The user disables the plugin, or uninstalls it | cleared |
6555
+ | The player quits | cleared |
6556
+
6557
+ - **Per plugin.** A plugin sees only its own keys. Like
6558
+ dataDirectory, the values belong to the
6559
+ installation: a development load (`--plugin`) has its own.
6560
+ - **JSON.** `set` stores what `JSON.stringify(value)` writes, and `get`
6561
+ returns that, parsed: `undefined` object properties are dropped, a `Date`
6562
+ comes back as a string, `NaN` as `null`. **The order of an object's
6563
+ properties is not kept**: they may come back in another order. A value
6564
+ `JSON.stringify` cannot write (`undefined`, a function, a cycle, a
6565
+ `BigInt`) or a string with a lone surrogate (`"\uD800"`) fails with
6566
+ `invalid`. The value is not checked when it comes back: after an upgrade
6567
+ an older version's value may be read, so check its shape when it matters.
6568
+ - **Bounded.** Keys are 1–1024 bytes of text. A plugin holds at most 1 MiB,
6569
+ counting every key, its value as compact JSON and 128 bytes per key
6570
+ (SESSION_STORAGE_LIMITS); a
6571
+ `set` beyond it fails with `limit` and changes nothing. One `set` is
6572
+ also one message to the player, at most 1 MiB with its key, so a single
6573
+ value is a little smaller than that; a larger one fails with `limit`.
6574
+ Split large state over several keys.
6575
+ - **Each call is one request.** A `set` is kept once its promise
6576
+ resolves, so a crash right after loses nothing; there is no debounce.
6577
+ Calls answer in order.
6578
+
6579
+ ```ts
6580
+ interface SessionStorage {
6581
+ /** The value stored under `key`, or `undefined` when there is none. */
6582
+ get<T = unknown>(key: string): Promise<T | undefined>;
6583
+ /**
6584
+ * Store or replace `value` under `key`. `invalid` for a bad key or a value that is not JSON, `limit` when the plugin's values would be over 1 MiB or the request over the 1 MiB message limit.
6585
+ */
6586
+ set(key: string, value: unknown): Promise<void>;
6587
+ /** Remove a key; removing a missing one is fine. */
6588
+ delete(key: string): Promise<void>;
6589
+ /** The keys stored, in code point order. */
6590
+ keys(): Promise<string[]>;
6430
6591
  }
6431
6592
  ```
6432
6593
 
@@ -7436,6 +7597,10 @@ class TestHost implements Transport {
7436
7597
  * The player's secret store for this plugin: name → value. Preset entries to simulate stored secrets; inject failures with `host.handle("secrets.get", ...)`. Like the player, `secrets.set` of a new name fails `limit` once 256 names are stored.
7437
7598
  */
7438
7599
  readonly secrets: Map<string, string>;
7600
+ /**
7601
+ * `plugin.storage.session`: key → value, as the player keeps them. Preset entries to simulate values an earlier run of the plugin stored in the same player session, or pass `startPlugin(..., { sessionStorage })`. Like the player, a key is 1–1024 bytes, a `set` that would make the entries larger than 1 MiB (keys, values as compact JSON and `SESSION_STORAGE_LIMITS.entryBytes` per key) fails `limit`, and an object's properties are stored sorted, as the player hands them back.
7602
+ */
7603
+ readonly sessionStorage: Map<string, Json>;
7439
7604
  /** What `plugin.app.system()` reports. */
7440
7605
  systemInfo: WireSystemInfo;
7441
7606
  /** What `plugin.app` reports. Set it before startPlugin: the plugin receives it once at start-up. */
@@ -7720,6 +7885,10 @@ function startPlugin(activate: (plugin: Plugin) => unknown, options?: {
7720
7885
  host?: TestHost;
7721
7886
  /** Use an existing directory as `plugin.dataDirectory` (to test a restart). */
7722
7887
  dataDirectory?: string;
7888
+ /**
7889
+ * Start with these `plugin.storage.session` values (to test a restart within one player session): pass the previous run's `host.sessionStorage`. They are copied into the new host's TestHost.sessionStorage.
7890
+ */
7891
+ sessionStorage?: ReadonlyMap<string, Json>;
7723
7892
  /**
7724
7893
  * The interface language to play (TestHost.locale): the plugin's `locales/<locale>.po` (or `.json`) in `directory` is loaded and its menus, messages and titles show translated. Default: the host's.
7725
7894
  */
@@ -7765,8 +7934,11 @@ interface CapabilityInfo {
7765
7934
 
7766
7935
  Every manifest capability the player knows (`vestra/1`), in the player's
7767
7936
  order, with the members it opens, the capabilities they also need and the
7768
- store's wording. The player's tests check that the names agree with the
7769
- player's `host/src/plugins.rs` and the labels with its plugin store.
7937
+ store's wording. Built from the player's own capability table (its
7938
+ `src/host/capabilities.json`, copied into the SDK) and the requests each
7939
+ member sends, the same rule `vestra-plugin check` and the SDK's
7940
+ TypeScript plugin apply; the player's tests check that the copy is
7941
+ current.
7770
7942
 
7771
7943
  A member not listed under any capability needs none (`plugin.tools`,
7772
7944
  `plugin.storage`, `plugin.system`, `plugin.app`, `plugin.ui`,
@@ -8007,6 +8179,27 @@ const OSD_LIMITS: Readonly<{
8007
8179
  }>;
8008
8180
  ```
8009
8181
 
8182
+ ### SESSION_STORAGE_LIMITS
8183
+
8184
+ The player's limits on SessionStorage. A key outside them fails
8185
+ with code `invalid`; a `set` that would make the plugin's entries larger
8186
+ than `bytes` fails with `limit` and changes nothing. A single `set` must
8187
+ also fit in one message to the player
8188
+ (MESSAGE_LIMIT, 1 MiB
8189
+ with the key and the request around the value). The player's tests check
8190
+ that these agree with the player.
8191
+
8192
+ ```ts
8193
+ const SESSION_STORAGE_LIMITS: Readonly<{
8194
+ /** UTF-8 bytes of a key (1024, at least 1; no NUL character). */
8195
+ readonly keyBytes: 1024;
8196
+ /** Bytes one plugin may hold at the same time: for every key, its UTF-8 bytes, its value written as compact JSON (`JSON.stringify(value)`) and `entryBytes`. */
8197
+ readonly bytes: number;
8198
+ /** What each key costs on top of its text and value (128): the player's own bookkeeping, so that many tiny entries stay within `bytes` of memory. */
8199
+ readonly entryBytes: 128;
8200
+ }>;
8201
+ ```
8202
+
8010
8203
  ### SECRET_LIMITS
8011
8204
 
8012
8205
  The player's limits on Secrets. A name or value outside them
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.5.24: 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; it runs in its own Node.js process. Everything a plugin creates (commands, windows, panels, filters, listeners…) is a handle that is released automatically when the plugin stops. L<n> are line numbers in llms-full.txt, next to this file: every page, declaration and example.
3
+ > @vestra-player/sdk 1.5.26: 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; it runs in its own Node.js process. Everything a plugin creates (commands, windows, panels, filters, listeners…) is a handle that is released automatically when the plugin stops. L<n> are line numbers in llms-full.txt, next to this file: every page, declaration and example.
4
4
 
5
5
  **Rules**
6
6
 
@@ -33,7 +33,7 @@ my-plugin/
33
33
  }
34
34
  ```
35
35
 
36
- Fields: `id`, `version`, `apiVersion`, `entry`, `capabilities`, `name`, `author`, `punchline`, `description`, `tags`, `icon`, `readme`, `changelog`, `sourceUrl`, `license`, `minPlayerVersion`; unknown fields and capabilities are rejected. Rules and limits: L2128–2193.
36
+ Fields: `id`, `version`, `apiVersion`, `entry`, `capabilities`, `name`, `author`, `punchline`, `description`, `tags`, `icon`, `readme`, `changelog`, `sourceUrl`, `license`, `minPlayerVersion`; unknown fields and capabilities are rejected. Rules and limits: L2195–2261.
37
37
 
38
38
  **main.ts**
39
39
 
@@ -76,9 +76,9 @@ export default async function activate(plugin: Plugin) {
76
76
  - `ui.settings`: Settings.page/show
77
77
  - `ui.theme`: Appearance.set/setLanguage
78
78
  - `secrets`: Secrets.get/set/delete
79
- - No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme and packages.list. Details: L2194–2236.
79
+ - No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme and packages.list. Details: L2262–2320.
80
80
 
81
- **Test**: `tests/*.test.ts` with `node:test`, run by `vestra-plugin test`. `startPlugin(activate, { directory: new URL("..", import.meta.url) })` from `@vestra-player/sdk/testing` returns `{ host, stop }`; `host` is a TestHost (an in-memory player): `host.setSource(…)`, `host.command(id).run()`, `host.press(key)`, `await host.settle()`, then assert on `host.osd`, `host.window(id).props`… Guide: L1614–1779; TestHost: L6900.
81
+ **Test**: `tests/*.test.ts` with `node:test`, run by `vestra-plugin test`. `startPlugin(activate, { directory: new URL("..", import.meta.url) })` from `@vestra-player/sdk/testing` returns `{ host, stop }`; `host` is a TestHost (an in-memory player): `host.setSource(…)`, `host.command(id).run()`, `host.press(key)`, `await host.settle()`, then assert on `host.osd`, `host.window(id).props`… Guide: L1658–1846; TestHost: L7061.
82
82
 
83
83
  **Command line**
84
84
 
@@ -106,39 +106,39 @@ export default async function activate(plugin: Plugin) {
106
106
  - `limit`: A player-wide limit was reached, a property value is over the limits of PropValue
107
107
  - `reserved`: The id is `vestra` or starts with `vestra.`
108
108
  - `versionMismatch`: The package needs a newer player or another `apiVersion`
109
- - Others: PluginError, llms-full.txt L3135.
109
+ - Others: PluginError, llms-full.txt L3231.
110
110
 
111
111
  ## Reference
112
112
 
113
113
  - [llms-full.txt](https://vestrasys.github.io/vestra-player-sdk/llms-full.txt), also node_modules/@vestra-player/sdk/llms-full.txt; the pages are on the [site](https://vestrasys.github.io/vestra-player-sdk/).
114
- - Tutorials: Getting started L9–214
115
- - Concepts: How a plugin runs L215–364; Commands, menus and key bindings L365–462; Playback state, controls and sources L463–608; Video and audio processing L609–687; Windows, panels, settings pages, dialogs and the OSD L688–976
116
- - How-to: Keep a slider responsive while dragging L977–1151; Remember a window's state between runs L1152–1281; Translate a plugin L1282–1465; Migrate a plugin to English source text L1466–1613; Test a plugin L1614–1779; Publish a plugin L1780–1868; Migrating off package rollback (1.5.16, removed in 1.5.17) L1869–1944
117
- - Guidelines: Interface guidelines L1945–2029; Menu guidelines L2030–2127
118
- - Reference: Manifest L2128–2193; Capabilities L2194–2236; Limits L2237–2331; Commands, menus and bindings L2332–2800; Platforms L2801–2829; Theme L2830–3036; Command line L3037–3092
119
- - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3093–6735
120
- - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L6736–8109
114
+ - Tutorials: Getting started L9–237
115
+ - Concepts: How a plugin runs L238–408; Commands, menus and key bindings L409–506; Playback state, controls and sources L507–652; Video and audio processing L653–731; Windows, panels, settings pages, dialogs and the OSD L732–1020
116
+ - How-to: Keep a slider responsive while dragging L1021–1195; Remember a window's state between runs L1196–1325; Translate a plugin L1326–1509; Migrate a plugin to English source text L1510–1657; Test a plugin L1658–1846; Publish a plugin L1847–1935; Migrating off package rollback (1.5.16, removed in 1.5.17) L1936–2011
117
+ - Guidelines: Interface guidelines L2012–2096; Menu guidelines L2097–2194
118
+ - Reference: Manifest L2195–2261; Capabilities L2262–2320; Limits L2321–2425; Commands, menus and bindings L2426–2894; Platforms L2895–2923; Theme L2924–3130; Command line L3131–3188
119
+ - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3189–6896
120
+ - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L6897–8302
121
121
 
122
122
  ## API
123
123
 
124
124
  Where each module of `plugin` (`plugin.<module>`) is declared:
125
125
 
126
- - Plugin L3327, Scope L3196, PluginError L3135, Disposable L3108
127
- - Commands L3539; Windows L3769; Panels L4412; Dialogs L4698; Player L5165; video: VideoFilter L5472, VideoView L5535, RenderOptions L5577; audio: AudioFilter L5496; Subtitles L5675; media: MediaCapture L5835; Resolvers L5940; Packages L6182; Osd L5408; Host L5339; Clipboard L6314; Power L6334; Tools L6367; Storage L6410; System L6433; Secrets L6451; window: MainWindow L4535; App L4616; Bindings L6284; Ui L3644; Timeline L4492; Drops L4068; Appearance L4097; I18n L4239; Settings L4345.
126
+ - Plugin L3423, Scope L3292, PluginError L3231, Disposable L3204
127
+ - Commands L3635; Windows L3865; Panels L4508; Dialogs L4794; Player L5261; video: VideoFilter L5568, VideoView L5631, RenderOptions L5673; audio: AudioFilter L5592; Subtitles L5771; media: MediaCapture L5932; Resolvers L6037; Packages L6279; Osd L5504; Host L5435; Clipboard L6411; Power L6431; Tools L6464; Storage L6507; System L6594; Secrets L6612; window: MainWindow L4631; App L4712; Bindings L6381; Ui L3740; Timeline L4588; Drops L4164; Appearance L4193; I18n L4335; Settings L4441.
128
128
 
129
129
  ## Examples
130
130
 
131
131
  Tested plugins in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples):
132
132
 
133
- - hello: The README Quick start: one menu command and an on-screen message. L8533–8622
134
- - blur: A tool window with one slider that blurs the playing video. L8623–8884
135
- - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L8885–9217
136
- - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L9218–9402
137
- - panel-menu: A sidebar panel with its own right-click menu. L9403–9608
138
- - menus: Radio menu items, separator groups and a menu item whose title changes. L9609–9768
139
- - theme-widgets: A tool window drawn with the player's theme and widgets. L9769–9932
140
- - markdown: A window that shows a Markdown document. L9933–10127
141
- - i18n: A plugin in the player's language: English source text, a Chinese translation. L10128–10295
133
+ - hello: The README Quick start: one menu command and an on-screen message. L8726–8815
134
+ - blur: A tool window with one slider that blurs the playing video. L8816–9077
135
+ - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L9078–9410
136
+ - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L9411–9595
137
+ - panel-menu: A sidebar panel with its own right-click menu. L9596–9801
138
+ - menus: Radio menu items, separator groups and a menu item whose title changes. L9802–9961
139
+ - theme-widgets: A tool window drawn with the player's theme and widgets. L9962–10125
140
+ - markdown: A window that shows a Markdown document. L10126–10320
141
+ - i18n: A plugin in the player's language: English source text, a Chinese translation. L10321–10488
142
142
 
143
143
  ## Optional
144
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.5.24",
3
+ "version": "1.5.26",
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": {
@@ -24,6 +24,10 @@
24
24
  "types": "./dist/player-runtime.d.ts",
25
25
  "default": "./dist/player-runtime.js"
26
26
  },
27
+ "./ts-plugin": {
28
+ "types": "./ts-plugin/index.d.ts",
29
+ "default": "./ts-plugin/index.js"
30
+ },
27
31
  "./llms.txt": "./llms.txt",
28
32
  "./llms-full.txt": "./llms-full.txt",
29
33
  "./package.json": "./package.json"
@@ -35,6 +39,7 @@
35
39
  "dist",
36
40
  "bin",
37
41
  "slint",
42
+ "ts-plugin",
38
43
  "README.md",
39
44
  "CHANGELOG.md",
40
45
  "LICENSE",
@@ -43,6 +48,7 @@
43
48
  ],
44
49
  "scripts": {
45
50
  "build": "node scripts/build.mjs",
51
+ "capabilities:sync": "node scripts/capabilities-sync.mjs",
46
52
  "check": "tsc -p tsconfig.check.json",
47
53
  "doc-lint": "node scripts/doc-lint.mjs",
48
54
  "docs": "typedoc",
@@ -50,8 +56,8 @@
50
56
  "docs:llms": "node scripts/llms.mjs",
51
57
  "docs:tables": "node scripts/doc-tables.mjs",
52
58
  "examples": "node scripts/check-examples.mjs",
53
- "format": "prettier --write \"{src,test,bin,scripts,examples}/**/*.{ts,mjs}\" package.json tsconfig.json tsconfig.check.json typedoc.json .oxlintrc.json examples/tsconfig.json \"examples/*/package.json\"",
54
- "format:check": "prettier --check \"{src,test,bin,scripts,examples}/**/*.{ts,mjs}\" package.json tsconfig.json tsconfig.check.json typedoc.json .oxlintrc.json examples/tsconfig.json \"examples/*/package.json\"",
59
+ "format": "prettier --write \"{src,test,bin,scripts,examples}/**/*.{ts,mjs}\" ts-plugin/index.js ts-plugin/index.d.ts package.json tsconfig.json tsconfig.check.json typedoc.json .oxlintrc.json examples/tsconfig.json \"examples/*/package.json\"",
60
+ "format:check": "prettier --check \"{src,test,bin,scripts,examples}/**/*.{ts,mjs}\" ts-plugin/index.js ts-plugin/index.d.ts package.json tsconfig.json tsconfig.check.json typedoc.json .oxlintrc.json examples/tsconfig.json \"examples/*/package.json\"",
55
61
  "lint": "oxlint --deny-warnings",
56
62
  "pack:check": "node scripts/check-pack.mjs",
57
63
  "pack:smoke": "node scripts/smoke-pack.mjs",
@@ -0,0 +1,8 @@
1
+ // @vestra-player/sdk/ts-plugin: the TypeScript language-service plugin. List
2
+ // it in tsconfig.json; it is not meant to be imported.
3
+ import type ts from "typescript";
4
+
5
+ declare function init(modules: {
6
+ typescript: typeof ts;
7
+ }): ts.server.PluginModule;
8
+ export = init;
@@ -0,0 +1,6 @@
1
+ // @vestra-player/sdk/ts-plugin: the TypeScript server loads language-service
2
+ // plugins with require() and Node 10 resolution (no package.json "exports"),
3
+ // hence this CommonJS directory. The plugin itself is dist/ts-plugin.js, an
4
+ // ES module without top-level await, which require() loads (Node 22.12+).
5
+ "use strict";
6
+ module.exports = require("../dist/ts-plugin.js").default;
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }