@vestra-player/sdk 1.5.15 → 1.5.16

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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.16
4
+
5
+ **Package rollback is deprecated: the player keeps no previous plugin version.** `apiVersion` stays `vestra/1`; code still compiles, and plugins that do not manage other plugins need no change. 1.5.17 removes the deprecated symbols, as 1.5.8 removed `PackageCandidate.trial()` at patch level. Migration: [Migrating off package rollback](docs/how-to/rollback-migration.md).
6
+
7
+ - **Deprecated: `Packages.rollback`** (removed in 1.5.17). The player no longer has `packages.rollback` and answers it with `unsupported` ("unknown package method"); the SDK still forwards the call, so the error reaches the caller. To go back to an older version of a third-party plugin, the user uninstalls it and installs that version's `.vxp`.
8
+ - **Deprecated: `PluginInfo.previousVersion`** and the wire field `PackageInfo.previousVersion` (removed in 1.5.17): always `null`, since an upgrade replaces the package. The SDK reads a missing field as `null`.
9
+ - **Built-in plugins** run in place from the player installation: `PluginInfo.directory` of a built-in is `<install resources>/plugins/<id>` and read-only (plugins write only under `plugin.dataDirectory`); they can be disabled and enabled, not uninstalled (`unsupported`, as before).
10
+ - **`TestHost`** matches the player: `packages.list` and `packages.changed` report `previousVersion: null` for every plugin, an install no longer records the replaced version, and `packages.rollback` rejects with `unsupported` ("unknown package method"). The `previousVersion` field of `host.packages` entries is deprecated and ignored; it stays required (as `PluginInfo.previousVersion` does), so existing test code compiles unchanged.
11
+ - `"Packages.rollback"` stays in `CAPABILITY_INFO["plugins.manage"].apis` until 1.5.17; the Capabilities reference notes the deprecation.
12
+ - Docs: the new how-to *Migrating off package rollback (1.5.16)*; *Publish a plugin* no longer mentions a roll back button, installing an older version from the store or the user's rollback as a safety net; the data directory is "kept across restarts, disabling and upgrades"; `revisionConflict`, `PackageChange` and `PackageChangeStatus` speak of installs and uninstalls (the statuses are unchanged).
13
+
3
14
  ## 1.5.15
4
15
 
5
16
  **How many frames really reach the screen, and how the video is decoded** (player 0.5.1); every player error code documented; the fullscreen bottom bar's new behaviour. Additive; `apiVersion` stays `vestra/1`, and plugins need no change.
@@ -1,4 +1,4 @@
1
- /** Package validation and install/rollback (`plugins.manage`). */
1
+ /** Package validation, install and uninstall (`plugins.manage`). */
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { resolve } from "node:path";
4
4
  import { failure, sleep, uncertain } from "./runtime.js";
@@ -62,7 +62,7 @@ class ChangeHandle {
62
62
  this.#accept(await this.#scope.runtime.host.packages.cancel(this.#id));
63
63
  }
64
64
  }
65
- /** Install and rollback reuse the request id as the operation id after a lost reply. */
65
+ /** Install and uninstall reuse the request id as the operation id after a lost reply. */
66
66
  async function change(scope, submit, id) {
67
67
  const host = scope.runtime.host.packages;
68
68
  const installed = await host.installed(id);
@@ -126,6 +126,8 @@ export const packages = (scope) => ({
126
126
  const checked = await scope.runtime.host.packages.validateArchive(path);
127
127
  return candidate(scope, path, checked);
128
128
  },
129
+ // Deprecated (removed in 1.5.17): still forwarded, so the player's
130
+ // `unsupported` ("unknown package method") reaches the caller.
129
131
  rollback: (id) => change(scope, (requestId, revision) => scope.runtime.host.packages.rollback(id, revision, requestId), id),
130
132
  list: async () => (await scope.runtime.host.packages.list()).map(info),
131
133
  onChange(listener) {
@@ -152,7 +154,7 @@ const info = (plugin) => Object.freeze({
152
154
  error: plugin.error,
153
155
  capabilities: [...plugin.capabilities],
154
156
  directory: plugin.directory,
155
- previousVersion: plugin.previousVersion,
157
+ previousVersion: plugin.previousVersion ?? null,
156
158
  builtin: plugin.builtin === true,
157
159
  source: plugin.source ? Object.freeze({ ...plugin.source }) : null,
158
160
  dataDirectory: plugin.dataDirectory ?? null,
@@ -1311,7 +1311,9 @@ export declare class TestHost implements Transport {
1311
1311
  /**
1312
1312
  * What `packages.list` reports; `setEnabled`, `uninstall` and `install`
1313
1313
  * update it. `builtin`, `source` and `dataDirectory` are optional here
1314
- * (default `false`, `null`, `null`).
1314
+ * (default `false`, `null`, `null`). Like the player, the host lists every
1315
+ * plugin with `previousVersion: null` and answers `packages.rollback` with
1316
+ * `unsupported` (since 1.5.16).
1315
1317
  */
1316
1318
  packages: {
1317
1319
  /** The plugin's manifest id. */
@@ -1328,7 +1330,12 @@ export declare class TestHost implements Transport {
1328
1330
  capabilities: string[];
1329
1331
  /** The package directory. */
1330
1332
  directory: string;
1331
- /** The version a rollback would restore, `null` without one. */
1333
+ /**
1334
+ * Ignored: `packages.list` always reports `null`. Still required in
1335
+ * 1.5.16 so that test data shaped like a `PluginInfo` keeps compiling.
1336
+ *
1337
+ * @deprecated Removed in 1.5.17 with `PluginInfo.previousVersion`.
1338
+ */
1332
1339
  previousVersion: string | null;
1333
1340
  /** Shipped with the player: `uninstall` and `install` are `unsupported`. */
1334
1341
  builtin?: boolean;
@@ -1862,7 +1862,9 @@ export class TestHost {
1862
1862
  /**
1863
1863
  * What `packages.list` reports; `setEnabled`, `uninstall` and `install`
1864
1864
  * update it. `builtin`, `source` and `dataDirectory` are optional here
1865
- * (default `false`, `null`, `null`).
1865
+ * (default `false`, `null`, `null`). Like the player, the host lists every
1866
+ * plugin with `previousVersion: null` and answers `packages.rollback` with
1867
+ * `unsupported` (since 1.5.16).
1866
1868
  */
1867
1869
  packages = [];
1868
1870
  /** Every `packages.setEnabled` / `uninstall` / `install`, newest last. */
@@ -1919,7 +1921,14 @@ export class TestHost {
1919
1921
  */
1920
1922
  setPackages(list) {
1921
1923
  this.packages = list;
1922
- this.emit("packages.changed", structuredClone(list));
1924
+ this.emit("packages.changed", this.#listed());
1925
+ }
1926
+ /** `packages.list` as the player sends it: no previous version is kept. */
1927
+ #listed() {
1928
+ return this.packages.map((entry) => ({
1929
+ ...structuredClone(entry),
1930
+ previousVersion: null,
1931
+ }));
1923
1932
  }
1924
1933
  #split(key) {
1925
1934
  const at = key.indexOf("/");
@@ -3153,7 +3162,7 @@ export class TestHost {
3153
3162
  this.bindings.overlay.delete(`${p.plugin}/${p.command}`);
3154
3163
  this.emit("bindings.changed", null);
3155
3164
  },
3156
- "packages.list": () => structuredClone(this.packages),
3165
+ "packages.list": () => this.#listed(),
3157
3166
  "packages.setEnabled": (p) => {
3158
3167
  const id = String(p.id);
3159
3168
  if (id === this.manifestId && p.enabled === false)
@@ -3205,9 +3214,7 @@ export class TestHost {
3205
3214
  error: null,
3206
3215
  capabilities: [...manifest.capabilities],
3207
3216
  directory: `/test-host/packages/${manifest.id}`,
3208
- previousVersion: entry && identity(entry.source) === identity(source)
3209
- ? entry.version
3210
- : null,
3217
+ previousVersion: null,
3211
3218
  builtin: false,
3212
3219
  source,
3213
3220
  dataDirectory: entry?.dataDirectory ?? null,
@@ -3235,6 +3242,11 @@ export class TestHost {
3235
3242
  this.setPackages(this.packages.filter((each) => each.id !== id));
3236
3243
  return { operationId: String(p.requestId), status: "committed" };
3237
3244
  },
3245
+ // The player no longer keeps a previous version (SDK 1.5.16): the method
3246
+ // is gone and fails like any unknown package method.
3247
+ "packages.rollback": () => {
3248
+ throw new HostError("unsupported", "unknown package method");
3249
+ },
3238
3250
  "ui.markdownScan": (p) => {
3239
3251
  const text = String(p.text);
3240
3252
  const images = [
@@ -90,7 +90,7 @@ export interface Disposable {
90
90
  * | `pending` | An operation with the same token is still running in the player. The SDK waits and asks again, so it rarely reaches a plugin. |
91
91
  * | `producer` | `openStream`: the player refused or lost the generated stream; `message` has the reason. |
92
92
  * | `resolution` | A file or web resolver's result could not be opened; `message` has the reason. |
93
- * | `revisionConflict` | {@link Packages}: the installed revision changed since it was read (another install, rollback or uninstall ran). Read again and retry; the SDK passes the revision it read, so this reaches plugins that act on old data. |
93
+ * | `revisionConflict` | {@link Packages}: the installed revision changed since it was read (another install or uninstall ran). Read again and retry; the SDK passes the revision it read, so this reaches plugins that act on old data. |
94
94
  * | `source-geometry`, `source-geometry-unsupported` | The source's picture size or rotation could not be applied: invalid decoder dimensions / a rotation that is not a multiple of 90°. |
95
95
  * | `source-unavailable` | The source is not ready (still loading, or its time or native binding is not available yet). |
96
96
  * | `unknown-expired` | The player no longer knows the operation or snapshot (it expired or was released). The SDK maps it to `unknownExpired` results where it can; do not resubmit the operation blindly. |
@@ -336,7 +336,7 @@ export interface Plugin extends Scope {
336
336
  * A writable directory for this plugin's own files (settings, caches,
337
337
  * remembered folders), created by the player before it starts the plugin.
338
338
  * Each installation has its own directory (player 0.2.0): it is kept across
339
- * restarts, disabling, upgrades and rollbacks, and a reinstall of the same
339
+ * restarts, disabling and upgrades, and a reinstall of the same
340
340
  * id from a different source gets a new, empty one. It is deleted only by
341
341
  * `packages.uninstall(id, { removeData: true })`. No capability.
342
342
  *
@@ -4693,10 +4693,10 @@ export interface PackageCandidate {
4693
4693
  */
4694
4694
  install(options?: InstallOptions): Promise<PackageChange>;
4695
4695
  }
4696
- /** The progress of an install, rollback or uninstall. */
4696
+ /** The progress of an install or uninstall. */
4697
4697
  export type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed" | "cancelled" | "failed" | "failedDegraded" | "tooLate";
4698
4698
  /**
4699
- * An install, rollback or uninstall in progress. The SDK never submits a
4699
+ * An install or uninstall in progress. The SDK never submits a
4700
4700
  * change twice: after a lost reply it only queries the same request.
4701
4701
  */
4702
4702
  export interface PackageChange {
@@ -4725,9 +4725,21 @@ export interface PluginInfo {
4725
4725
  error: string | null;
4726
4726
  /** Declared capabilities. */
4727
4727
  capabilities: string[];
4728
- /** The package directory (read its `manifest.json` for display fields). */
4728
+ /**
4729
+ * The package directory (read its `manifest.json` for display fields).
4730
+ * A built-in's directory is inside the player installation
4731
+ * (`<install resources>/plugins/<id>`) and read-only: a plugin writes only
4732
+ * under its own {@link Plugin.dataDirectory}.
4733
+ */
4729
4734
  directory: string;
4730
- /** The version a rollback would restore, `null` without one. */
4735
+ /**
4736
+ * Always `null`: the player no longer keeps a previous version, and an
4737
+ * upgrade replaces the package.
4738
+ *
4739
+ * @deprecated Removed in 1.5.17. The player no longer keeps a previous
4740
+ * version, so this is always `null`. See
4741
+ * [Migrating off package rollback](../../docs/how-to/rollback-migration.md).
4742
+ */
4731
4743
  previousVersion: string | null;
4732
4744
  /**
4733
4745
  * Shipped with the player: it can be disabled, not uninstalled or
@@ -4827,7 +4839,15 @@ export interface Packages {
4827
4839
  * fails with `reserved`, as in {@link Packages.validate}.
4828
4840
  */
4829
4841
  validateArchive(file: string): Promise<PackageCandidate>;
4830
- /** Restore a plugin's previous version (`plugins.manage`). */
4842
+ /**
4843
+ * Formerly restored a plugin's previous version (`plugins.manage`).
4844
+ *
4845
+ * @deprecated Removed in 1.5.17. The player no longer keeps a previous
4846
+ * version, so this always fails with `unsupported`. To go back to an older
4847
+ * version of a third-party plugin, the user uninstalls it and installs that
4848
+ * version's `.vxp`. See
4849
+ * [Migrating off package rollback](../../docs/how-to/rollback-migration.md).
4850
+ */
4831
4851
  rollback(id: string): Promise<PackageChange>;
4832
4852
  /** Every plugin: running, installed, and this run's development loads. No capability. */
4833
4853
  list(): Promise<PluginInfo[]>;
package/dist/wire.d.ts CHANGED
@@ -102,6 +102,11 @@ export interface PackageInfo {
102
102
  error: string | null;
103
103
  capabilities: string[];
104
104
  directory: string;
105
+ /**
106
+ * Always `null` from players that ship with SDK 1.5.16 or later.
107
+ *
108
+ * @deprecated Removed in 1.5.17: the player keeps no previous version.
109
+ */
105
110
  previousVersion: string | null;
106
111
  /** Player 0.2.0; absent from older players. */
107
112
  builtin?: boolean;
@@ -567,6 +572,10 @@ export declare function createWire(transport: Transport): {
567
572
  source?: WireSource;
568
573
  replace?: boolean;
569
574
  }) => Promise<PackageOperation>;
575
+ /**
576
+ * @deprecated Removed in 1.5.17. The player no longer has this method and
577
+ * rejects it with `unsupported` ("unknown package method").
578
+ */
570
579
  rollback: (id: string, expectedRevision: number, requestId: string) => Promise<PackageOperation>;
571
580
  operation: (operationId: string) => Promise<PackageOperation>;
572
581
  cancel: (operationId: string) => Promise<PackageOperation>;
@@ -593,7 +602,7 @@ export declare function createWire(transport: Transport): {
593
602
  sampleTime?: number;
594
603
  } | {
595
604
  kind: "rawJpeg" | "processedJpeg";
596
- sampleTime?: number;
605
+ sampleTime? /** The path's keys (player 0.4.3). */: number;
597
606
  quality?: number;
598
607
  } | {
599
608
  kind: "rawMp4" | "processedMp4";
package/dist/wire.js CHANGED
@@ -278,6 +278,10 @@ export function createWire(transport) {
278
278
  ...(options.source === undefined ? {} : { source: options.source }),
279
279
  ...(options.replace ? { replace: true } : {}),
280
280
  }),
281
+ /**
282
+ * @deprecated Removed in 1.5.17. The player no longer has this method and
283
+ * rejects it with `unsupported` ("unknown package method").
284
+ */
281
285
  rollback: (id, expectedRevision, requestId) => transport.request("packages.rollback", {
282
286
  id,
283
287
  expectedRevision,
package/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @vestra-player/sdk 1.5.15: full reference
1
+ # @vestra-player/sdk 1.5.16: 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
 
@@ -319,7 +319,7 @@ So `onDispose` cleanup must be quick and must not rely on the player. Media the
319
319
  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.
320
320
 
321
321
  - **One directory per installation.** Each fresh installation of a plugin gets its own directory (player 0.2.0). Installing the same id again from the same source is an upgrade and keeps the directory. Installing the same id from a different source, and confirming the replacement, gives the new plugin a new, empty directory; the old one is kept and reconnected if the user goes back to the first source later. A development load (`--plugin`) has a directory of its own.
322
- - **Kept** when the plugin is disabled, reloaded, upgraded or rolled back. Uninstalling deletes it only when the user asks for that (uninstall with `removeData: true`); otherwise reinstalling from the same source reconnects it.
322
+ - **Kept** when the plugin is disabled, reloaded or upgraded. Uninstalling deletes it only when the user asks for that (uninstall with `removeData: true`); otherwise reinstalling from the same source reconnects it.
323
323
  - **The path's layout is not part of the API.** Do not derive another plugin's directory from yours; a plugin manager reads it from PluginInfo.dataDirectory.
324
324
  - **Write atomically.** Write to a temporary name and rename it, so a player that quits mid-write never leaves half a file; fall back to defaults when a file is missing or unreadable. For JSON settings use plugin.storage.json, which already does this (defaults, debounced atomic writes, a corrupt file moved aside).
325
325
  - Reading the property throws `unavailable` when the player could not provide a directory.
@@ -1753,9 +1753,9 @@ Every member of `TestHost` is in the @vestra-player/sdk/testing reference.
1753
1753
 
1754
1754
  ## How-to: Publish a plugin
1755
1755
 
1756
- **Goal:** your plugin appears in the player's plugin store, where users install, update and roll it back.
1756
+ **Goal:** your plugin appears in the player's plugin store, where users install, update and uninstall it.
1757
1757
 
1758
- The store reads the official plugin repository, [vestrasys/vestra-player-plugins](https://github.com/vestrasys/vestra-player-plugins). Every change to it is a pull request to its `main` branch, and **a merge is a release**: there are no beta channels, and CI, the user's rollback and revocation are the safety net. Every plugin in it is a third-party plugin; only the plugins built into the player are official. The repository's [README](https://github.com/vestrasys/vestra-player-plugins#contributing-a-plugin) and [CONTRIBUTING](https://github.com/vestrasys/vestra-player-plugins/blob/main/CONTRIBUTING.md) hold the full rules; this page is the author's path through them.
1758
+ The store reads the official plugin repository, [vestrasys/vestra-player-plugins](https://github.com/vestrasys/vestra-player-plugins). Every change to it is a pull request to its `main` branch, and **a merge is a release**: there are no beta channels, and CI and revocation are the safety net. Every plugin in it is a third-party plugin; only the plugins built into the player are official. The repository's [README](https://github.com/vestrasys/vestra-player-plugins#contributing-a-plugin) and [CONTRIBUTING](https://github.com/vestrasys/vestra-player-plugins/blob/main/CONTRIBUTING.md) hold the full rules; this page is the author's path through them.
1759
1759
 
1760
1760
  #### Two ways in
1761
1761
 
@@ -1836,10 +1836,84 @@ After the merge, the repository's publish workflow builds the package, releases
1836
1836
  The store shows the newest version's manifest fields, so what you write there is the plugin's page:
1837
1837
 
1838
1838
  - **The list**: name and version, author and repository, the punchline. Search covers the name, id, author, punchline and tags; the list can be filtered by tag and sorted by name or by the date of the newest version. There are no download counts.
1839
- - **The plugin's page**: the icon, name, author, punchline and the buttons (install, update, roll back, uninstall). Three tabs show the README, the changelog and the version history (version, release date, `minPlayerVersion`, and whether the version is current, incompatible or revoked; an older compatible version can be installed from there). A side column shows the version, the date, the license, a link to `sourceUrl` and the capabilities in plain words, the risky ones highlighted (the wording is the "Shown in the store as" column of Capabilities), with a fixed note that capabilities limit only the player API, not file or network access.
1839
+ - **The plugin's page**: the icon, name, author, punchline and the buttons (install, update, uninstall). Three tabs show the README, the changelog and the version history (version, release date, `minPlayerVersion`, and whether the version is current, incompatible or revoked). The player keeps no previous version, and the store has no action to install an older one: a user who wants an older version uninstalls the plugin and installs that version's `.vxp` (Migrating off package rollback). A side column shows the version, the date, the license, a link to `sourceUrl` and the capabilities in plain words, the risky ones highlighted (the wording is the "Shown in the store as" column of Capabilities), with a fixed note that capabilities limit only the player API, not file or network access.
1840
1840
  - **Before installing**, the user confirms a dialog that lists the capabilities; an update that adds capabilities lists the new ones. A plugin whose every version needs a newer player shows "needs Vestra Player x.y.z or later" instead of an install button.
1841
1841
  - **The README** is rendered as Markdown; relative links and images resolve against the README's own address, and remote images are shown for the official repository.
1842
1842
 
1843
+ ## How-to: Migrating off package rollback (1.5.16)
1844
+
1845
+ For plugins that manage other plugins (`plugins.manage`): stores, plugin managers, anything that calls packages.rollback or reads PluginInfo.previousVersion. A plugin that only uses its own API needs no change.
1846
+
1847
+ #### What changed
1848
+
1849
+ The player no longer keeps a previous version of a plugin. An upgrade replaces the package, so there is nothing to roll back to:
1850
+
1851
+ - `packages.rollback(id)` always fails with `unsupported` (the player answers "unknown package method").
1852
+ - Every entry of `packages.list()` and `packages.onChange` has `previousVersion: null`.
1853
+ - Built-in plugins (`builtin: true`) run in place from the player installation. They can be disabled and enabled, never uninstalled or replaced; `uninstall` fails with `unsupported`. A built-in's `directory` is inside the installation (`<install resources>/plugins/<id>`) and read-only.
1854
+ - Third-party plugins can still be uninstalled.
1855
+
1856
+ Why: built-ins now run from the read-only installation instead of a copy, and an upgrade replaces the package instead of keeping the old one beside it. Going back to an older version becomes an ordinary uninstall and install that the user starts.
1857
+
1858
+ `TestHost` behaves the same way since 1.5.16, so tests show it too.
1859
+
1860
+ #### 1. Remove rollback calls and `previousVersion` checks
1861
+
1862
+ Before:
1863
+
1864
+ ```ts nocheck
1865
+ if (info.previousVersion !== null) showButton("Roll back", () => plugin.packages.rollback(info.id));
1866
+ ```
1867
+
1868
+ After: delete both. There is no replacement call; nothing on the player can restore an earlier package. Remove the button, menu item or command, and any state, text or test that depends on `previousVersion`.
1869
+
1870
+ #### 2. Going back to an older version
1871
+
1872
+ Managing plugins has exactly three operations: **install**, **uninstall** and **disable/enable**. There is no rollback, no "install older version" and no "Undo install".
1873
+
1874
+ To go back to an older version, the user uninstalls the plugin and installs the older version's `.vxp` file themselves, as two ordinary operations. Do not offer an action that does both, an older-version install from a store, or an "Undo" after an install (the official store and the AI window drop theirs). Uninstalling without `removeData` keeps the plugin's data, and a reinstall from the same source reconnects to it; a `.vxp` file is a different source from a repository, so a plugin first installed from the store gets a new, empty data directory.
1875
+
1876
+ #### 3. Remove "Undo install"
1877
+
1878
+ Remove any Undo action after an install, its texts and translations, and the code that only served it: copies of installed `.vxp` files, remembered "last install" state, and tests of it. A user who wants a plugin gone uninstalls it.
1879
+
1880
+ #### 4. Built-in plugins
1881
+
1882
+ Check `builtin` before offering actions:
1883
+
1884
+ ```ts
1885
+ for (const info of await plugin.packages.list()) {
1886
+ const actions = info.builtin
1887
+ ? [info.state === "disabled" ? "Enable" : "Disable"]
1888
+ : ["Disable", "Uninstall"];
1889
+ console.log(info.id, actions);
1890
+ }
1891
+ ```
1892
+
1893
+ - Offer only disable and enable for a built-in (`packages.setEnabled`); never uninstall or install over it.
1894
+ - Never write into a built-in's `directory`, or into any plugin's `directory`: read `manifest.json` from it if you need to. A plugin writes only under its own plugin.dataDirectory.
1895
+
1896
+ #### 5. Tests
1897
+
1898
+ `TestHost` lists every plugin with `previousVersion: null`, whatever a test puts in `host.packages`, and rejects `packages.rollback` with `unsupported`. The `previousVersion` field of `host.packages` entries is deprecated and ignored; it stays required in 1.5.16 so that test data shaped like a `PluginInfo` keeps compiling, and goes in 1.5.17. A test that expected a rollback to succeed should now assert the rejection or be removed.
1899
+
1900
+ #### Timeline
1901
+
1902
+ | SDK | Player | What happens |
1903
+ | --- | --- | --- |
1904
+ | 1.5.16 | ships with it | `Packages.rollback` and `PluginInfo.previousVersion` (and the wire field) are `@deprecated`. The player and `TestHost` answer rollback with `unsupported` and list `previousVersion: null`. Code still compiles. |
1905
+ | 1.5.17 | — | Both symbols are removed. Remaining uses are TypeScript compile errors. |
1906
+
1907
+ `apiVersion` stays `vestra/1` in both releases, as when 1.5.8 removed `PackageCandidate.trial()`.
1908
+
1909
+ #### Before you hand it in
1910
+
1911
+ - [ ] No call to `packages.rollback`; no UI or logic gated on `previousVersion`.
1912
+ - [ ] Built-ins offer only disable and enable.
1913
+ - [ ] Nothing writes into a plugin's `directory`.
1914
+ - [ ] No Undo after an install, no older-version install; older versions go through uninstall and a user-chosen `.vxp`.
1915
+ - [ ] Tests pass with SDK 1.5.16; the only deprecated name left in them is the `previousVersion: null` of `host.packages` test data.
1916
+
1843
1917
  ## Guideline: Interface guidelines
1844
1918
 
1845
1919
  These are the rules the player's built-in plugins follow for their windows, panels and messages. Follow them and your plugin looks like part of the player. They assume you know the five surfaces from Windows, panels, settings pages, dialogs and the OSD; token names, sizes, fonts, icons and widgets are listed in Theme.
@@ -2126,6 +2200,8 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
2126
2200
  - A file resolver (Resolvers.add with file types) also needs `producer.av`.
2127
2201
  - 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.
2128
2202
 
2203
+ **Deprecated:** Packages.rollback in the `plugins.manage` row (since 1.5.16; removed in 1.5.17). The player keeps no previous version, so it always fails with `unsupported`; see Migrating off package rollback.
2204
+
2129
2205
  #### Without a capability
2130
2206
 
2131
2207
  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).
@@ -3070,7 +3146,7 @@ change).
3070
3146
  | `pending` | An operation with the same token is still running in the player. The SDK waits and asks again, so it rarely reaches a plugin. |
3071
3147
  | `producer` | `openStream`: the player refused or lost the generated stream; `message` has the reason. |
3072
3148
  | `resolution` | A file or web resolver's result could not be opened; `message` has the reason. |
3073
- | `revisionConflict` | Packages: the installed revision changed since it was read (another install, rollback or uninstall ran). Read again and retry; the SDK passes the revision it read, so this reaches plugins that act on old data. |
3149
+ | `revisionConflict` | Packages: the installed revision changed since it was read (another install or uninstall ran). Read again and retry; the SDK passes the revision it read, so this reaches plugins that act on old data. |
3074
3150
  | `source-geometry`, `source-geometry-unsupported` | The source's picture size or rotation could not be applied: invalid decoder dimensions / a rotation that is not a multiple of 90°. |
3075
3151
  | `source-unavailable` | The source is not ready (still loading, or its time or native binding is not available yet). |
3076
3152
  | `unknown-expired` | The player no longer knows the operation or snapshot (it expired or was released). The SDK maps it to `unknownExpired` results where it can; do not resubmit the operation blindly. |
@@ -3245,7 +3321,7 @@ interface Plugin extends Scope {
3245
3321
  * A writable directory for this plugin's own files (settings, caches,
3246
3322
  * remembered folders), created by the player before it starts the plugin.
3247
3323
  * Each installation has its own directory (player 0.2.0): it is kept across
3248
- * restarts, disabling, upgrades and rollbacks, and a reinstall of the same
3324
+ * restarts, disabling and upgrades, and a reinstall of the same
3249
3325
  * id from a different source gets a new, empty one. It is deleted only by
3250
3326
  * `packages.uninstall(id, { removeData: true })`. No capability.
3251
3327
  *
@@ -5885,7 +5961,7 @@ interface PackageCandidate {
5885
5961
 
5886
5962
  ### PackageChangeStatus
5887
5963
 
5888
- The progress of an install, rollback or uninstall.
5964
+ The progress of an install or uninstall.
5889
5965
 
5890
5966
  ```ts
5891
5967
  type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed" | "cancelled" | "failed" | "failedDegraded" | "tooLate";
@@ -5893,7 +5969,7 @@ type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed
5893
5969
 
5894
5970
  ### PackageChange
5895
5971
 
5896
- An install, rollback or uninstall in progress. The SDK never submits a
5972
+ An install or uninstall in progress. The SDK never submits a
5897
5973
  change twice: after a lost reply it only queries the same request.
5898
5974
 
5899
5975
  ```ts
@@ -5929,9 +6005,14 @@ interface PluginInfo {
5929
6005
  error: string | null;
5930
6006
  /** Declared capabilities. */
5931
6007
  capabilities: string[];
5932
- /** The package directory (read its `manifest.json` for display fields). */
6008
+ /**
6009
+ * The package directory (read its `manifest.json` for display fields). A built-in's directory is inside the player installation (`<install resources>/plugins/<id>`) and read-only: a plugin writes only under its own Plugin.dataDirectory.
6010
+ */
5933
6011
  directory: string;
5934
- /** The version a rollback would restore, `null` without one. */
6012
+ /**
6013
+ * Always `null`: the player no longer keeps a previous version, and an upgrade replaces the package.
6014
+ * @deprecated Removed in 1.5.17. The player no longer keeps a previous version, so this is always `null`. See Migrating off package rollback.
6015
+ */
5935
6016
  previousVersion: string | null;
5936
6017
  /** Shipped with the player: it can be disabled, not uninstalled or replaced. `false` from players before 0.2.0. */
5937
6018
  builtin: boolean;
@@ -6006,7 +6087,10 @@ interface Packages {
6006
6087
  * Validate a `.vxp` package file (`plugins.manage`, player 0.2.0): the same checks as Packages.validate after unpacking. Check the file's sha256 yourself before calling when you know it. A reserved id fails with `reserved`, as in Packages.validate.
6007
6088
  */
6008
6089
  validateArchive(file: string): Promise<PackageCandidate>;
6009
- /** Restore a plugin's previous version (`plugins.manage`). */
6090
+ /**
6091
+ * Formerly restored a plugin's previous version (`plugins.manage`).
6092
+ * @deprecated Removed in 1.5.17. The player no longer keeps a previous version, so this always fails with `unsupported`. To go back to an older version of a third-party plugin, the user uninstalls it and installs that version's `.vxp`. See Migrating off package rollback.
6093
+ */
6010
6094
  rollback(id: string): Promise<PackageChange>;
6011
6095
  /** Every plugin: running, installed, and this run's development loads. No capability. */
6012
6096
  list(): Promise<PluginInfo[]>;
@@ -7327,7 +7411,7 @@ class TestHost implements Transport {
7327
7411
  overlay: Map<string, string[]>;
7328
7412
  };
7329
7413
  /**
7330
- * What `packages.list` reports; `setEnabled`, `uninstall` and `install` update it. `builtin`, `source` and `dataDirectory` are optional here (default `false`, `null`, `null`).
7414
+ * What `packages.list` reports; `setEnabled`, `uninstall` and `install` update it. `builtin`, `source` and `dataDirectory` are optional here (default `false`, `null`, `null`). Like the player, the host lists every plugin with `previousVersion: null` and answers `packages.rollback` with `unsupported` (since 1.5.16).
7331
7415
  */
7332
7416
  packages: {
7333
7417
  /** The plugin's manifest id. */
@@ -7344,7 +7428,10 @@ class TestHost implements Transport {
7344
7428
  capabilities: string[];
7345
7429
  /** The package directory. */
7346
7430
  directory: string;
7347
- /** The version a rollback would restore, `null` without one. */
7431
+ /**
7432
+ * Ignored: `packages.list` always reports `null`. Still required in 1.5.16 so that test data shaped like a `PluginInfo` keeps compiling.
7433
+ * @deprecated Removed in 1.5.17 with `PluginInfo.previousVersion`.
7434
+ */
7348
7435
  previousVersion: string | null;
7349
7436
  /** Shipped with the player: `uninstall` and `install` are `unsupported`. */
7350
7437
  builtin?: boolean;
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.5.15: 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.16: 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: L2026–2089.
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: L2100–2163.
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: L2090–2132.
79
+ - No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme and packages.list. Details: L2164–2208.
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: L1588–1753; TestHost: L6675.
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: L1588–1753; TestHost: L6759.
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 L3029.
109
+ - Others: PluginError, llms-full.txt L3105.
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
114
  - Tutorials: Getting started L9–214
115
115
  - Concepts: How a plugin runs L215–364; Commands, menus and key bindings L365–462; Playback state, controls and sources L463–585; Video and audio processing L586–663; Windows, panels, settings pages, dialogs and the OSD L664–950
116
- - How-to: Keep a slider responsive while dragging L951–1125; Remember a window's state between runs L1126–1255; Translate a plugin L1256–1439; Migrate a plugin to English source text L1440–1587; Test a plugin L1588–1753; Publish a plugin L1754–1842
117
- - Guidelines: Interface guidelines L1843–1927; Menu guidelines L1928–2025
118
- - Reference: Manifest L2026–2089; Capabilities L2090–2132; Limits L2133–2227; Commands, menus and bindings L2228–2696; Platforms L2697–2725; Theme L2726–2930; Command line L2931–2986
119
- - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L2987–6510
120
- - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L6511–7835
116
+ - How-to: Keep a slider responsive while dragging L951–1125; Remember a window's state between runs L1126–1255; Translate a plugin L1256–1439; Migrate a plugin to English source text L1440–1587; Test a plugin L1588–1753; Publish a plugin L1754–1842; Migrating off package rollback (1.5.16) L1843–1916
117
+ - Guidelines: Interface guidelines L1917–2001; Menu guidelines L2002–2099
118
+ - Reference: Manifest L2100–2163; Capabilities L2164–2208; Limits L2209–2303; Commands, menus and bindings L2304–2772; Platforms L2773–2801; Theme L2802–3006; Command line L3007–3062
119
+ - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3063–6594
120
+ - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L6595–7922
121
121
 
122
122
  ## API
123
123
 
124
124
  Where each module of `plugin` (`plugin.<module>`) is declared:
125
125
 
126
- - Plugin L3221, Scope L3090, PluginError L3029, Disposable L3002
127
- - Commands L3433; Windows L3638; Panels L4281; Dialogs L4567; Player L5015; video: VideoFilter L5280, VideoView L5343, RenderOptions L5385; audio: AudioFilter L5304; Subtitles L5483; media: MediaCapture L5643; Resolvers L5748; Packages L5988; Osd L5216; Host L5164; Clipboard L6105; Power L6125; Tools L6158; Storage L6201; System L6224; Secrets L6242; window: MainWindow L4404; App L4485; Bindings L6075; Ui L3525; Timeline L4361; Drops L3937; Appearance L3966; I18n L4108; Settings L4214.
126
+ - Plugin L3297, Scope L3166, PluginError L3105, Disposable L3078
127
+ - Commands L3509; Windows L3714; Panels L4357; Dialogs L4643; Player L5091; video: VideoFilter L5356, VideoView L5419, RenderOptions L5461; audio: AudioFilter L5380; Subtitles L5559; media: MediaCapture L5719; Resolvers L5824; Packages L6069; Osd L5292; Host L5240; Clipboard L6189; Power L6209; Tools L6242; Storage L6285; System L6308; Secrets L6326; window: MainWindow L4480; App L4561; Bindings L6159; Ui L3601; Timeline L4437; Drops L4013; Appearance L4042; I18n L4184; Settings L4290.
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. L8259–8348
134
- - blur: A tool window with one slider that blurs the playing video. L8349–8610
135
- - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L8611–8943
136
- - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L8944–9128
137
- - panel-menu: A sidebar panel with its own right-click menu. L9129–9334
138
- - menus: Radio menu items, separator groups and a menu item whose title changes. L9335–9494
139
- - theme-widgets: A tool window drawn with the player's theme and widgets. L9495–9658
140
- - markdown: A window that shows a Markdown document. L9659–9853
141
- - i18n: A plugin in the player's language: English source text, a Chinese translation. L9854–10021
133
+ - hello: The README Quick start: one menu command and an on-screen message. L8346–8435
134
+ - blur: A tool window with one slider that blurs the playing video. L8436–8697
135
+ - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L8698–9030
136
+ - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L9031–9215
137
+ - panel-menu: A sidebar panel with its own right-click menu. L9216–9421
138
+ - menus: Radio menu items, separator groups and a menu item whose title changes. L9422–9581
139
+ - theme-widgets: A tool window drawn with the player's theme and widgets. L9582–9745
140
+ - markdown: A window that shows a Markdown document. L9746–9940
141
+ - i18n: A plugin in the player's language: English source text, a Chinese translation. L9941–10108
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.15",
3
+ "version": "1.5.16",
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": {