@vestra-player/sdk 1.5.48 → 1.5.49
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 +10 -0
- package/dist/player-runtime.d.ts +1 -1
- package/dist/plugin/commands.js +23 -7
- package/dist/plugin/index.d.ts +11 -4
- package/dist/plugin/index.js +4 -2
- package/dist/plugin/runtime.d.ts +20 -1
- package/dist/plugin/runtime.js +44 -1
- package/dist/plugin/testing.d.ts +5 -2
- package/dist/plugin/testing.js +20 -12
- package/dist/plugin/types.d.ts +23 -5
- package/llms-full.txt +57 -26
- package/llms.txt +19 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.49
|
|
4
|
+
|
|
5
|
+
**Who must change: nobody.** No changes to the plugin API (`@vestra-player/sdk/runtime` gains the additive `RuntimeOptions` and an optional sixth `createPlugin` argument), `apiVersion` stays `vestra/1`, no new capability, no player version needed, and the player's runner needs no change. **Behaviour change, approved by the maintainer (2026-11-01):** in the player, a command declaration it refuses during activation no longer fails the whole plugin. What `commands.add` checks itself (an unknown `@` menu id, more than 8 path segments, an `order` out of range, a malformed or repeated binding, a bad `button`, a reason text of the wrong shape) still throws from `add`. A plugin whose `activate` relied on that failure (none is known) now runs without the refused command.
|
|
6
|
+
|
|
7
|
+
- **A refused command declaration is skipped, not fatal.** When the player answers a `commands.add` made during activation with `invalid`, `conflict` or `limit` (a text over its limit, a path another command has, too many commands), the SDK logs it with the command id and the player's message (`[<plugin>] command <id> skipped, the player refused it (conflict): …`), skips that command and lets the rest of the plugin activate: its other commands, panels, settings pages and resolvers register and the player gets `runtime.ready`. Before, the first refusal made activation fail (`runtime.failed`) and the plugin's menus and panels were withdrawn. The command's `ready` still rejects, and the error reaches the scope's `onError` once activation completes, so a listener added anywhere in `activate` hears it. This is how a slot, sequence or resolver another plugin holds was already treated, and how VS Code treats a bad contribution (logged, the extension activates). Other failures during activation (`capability`, a timeout, an error of the player) still fail it, and a refusal after activation goes to `onError` as before.
|
|
8
|
+
- **A refused command sends no updates.** After a definite refusal of its registration, later state changes (`set`, a binding) send nothing, instead of updates the player answers with `command missing`; dispose it to declare the id again.
|
|
9
|
+
- **`TestHost` refuses a binding listed twice as player 0.6.1 does.** The player now checks a command's bindings with its binding table (`Table::canonical`) inside the registration: each binding parsed and each input once, compared as parsed inputs, before the reason, the id and path conflicts and the limits, with the `invalid command: ` prefix (`invalid command: binding F3 is listed twice`). `TestHost` answered it without the prefix and only after the conflict and limit checks; it now gives the same answer in the same order.
|
|
10
|
+
- **`TestHost` stays strict.** `startPlugin` still rejects when a command declaration is refused during activation, so a plugin's tests show the bad declaration. Fix the declaration, not the test. (`createPlugin` in `@vestra-player/sdk/runtime` takes `{ strictDeclarations }`; `startPlugin` sets it, the player's runner does not.)
|
|
11
|
+
- _Docs_: [Example: Panel menu](docs/examples/panel-menu.md) draws its list in a `ListView` instead of a `for` in a `VerticalLayout`; the [interface guidelines](docs/guidelines/interface.md#panels) gain **Long lists use `ListView`**. `Command.ready`, `Commands`, `Plugin`, `startPlugin`, [The plugin model](docs/explanation/plugins.md), [Host and Node](docs/explanation/host-and-node.md) and [Limits](docs/reference/limits.md) describe the new activation rule.
|
|
12
|
+
|
|
3
13
|
## 1.5.48
|
|
4
14
|
|
|
5
15
|
**Who must change: nobody, but plugin tests may now fail.** No changes to the TypeScript API beyond new fields of `COMMAND_LIMITS`, `apiVersion` stays `vestra/1`, no new capability, no player version needed. `TestHost` now refuses the command declarations the player has always refused: a plugin test that starts failing shows a menu item, key or reason the real player already rejected (the command never appeared, or the plugin failed to activate). Fix the declaration, not the test.
|
package/dist/player-runtime.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
10
|
export { createPlugin } from "./plugin/index.ts";
|
|
11
|
-
export type { PluginInstance, PluginManifest } from "./plugin/index.ts";
|
|
11
|
+
export type { PluginInstance, PluginManifest, RuntimeOptions, } from "./plugin/index.ts";
|
|
12
12
|
export { HostError } from "./wire.ts";
|
|
13
13
|
export type { Json, Transport } from "./wire.ts";
|
|
14
14
|
export { API_VERSION, CAPABILITIES, CAPABILITY_INFO, MESSAGE_LIMIT, } from "./capabilities.ts";
|
package/dist/plugin/commands.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Menu commands: declaration, state sync (revisions hidden) and dispatch. */
|
|
2
|
-
import { failure, Latest, quiet } from "./runtime.js";
|
|
2
|
+
import { failure, Latest, quiet, uncertain } from "./runtime.js";
|
|
3
3
|
import { checkBindings } from "./bindings.js";
|
|
4
4
|
import { BAR_BUTTON_ICONS, BAR_BUTTON_LIMITS, MENU_IDS } from "../commands.js";
|
|
5
5
|
import { ImageHandle } from "./ui.js";
|
|
@@ -100,6 +100,10 @@ class CommandHandle {
|
|
|
100
100
|
#last = "";
|
|
101
101
|
#disposed = false;
|
|
102
102
|
#registered = false;
|
|
103
|
+
/** Its entry in `runtime.commands` (a later handle may own the id). */
|
|
104
|
+
#run = () => { };
|
|
105
|
+
/** The player refused the declaration: no state updates follow. */
|
|
106
|
+
#refused = false;
|
|
103
107
|
#pump;
|
|
104
108
|
#label;
|
|
105
109
|
/** Where the plugin added it, for the player's log. */
|
|
@@ -124,10 +128,20 @@ class CommandHandle {
|
|
|
124
128
|
// registration itself is revision 1, so every later state follows it.
|
|
125
129
|
const previous = removing(runtime).get(this.id);
|
|
126
130
|
this.#pump = new Latest(async ({ state, when }) => {
|
|
131
|
+
if (this.#refused)
|
|
132
|
+
return;
|
|
127
133
|
const revision = ++this.#revision;
|
|
128
134
|
if (revision === 1) {
|
|
129
135
|
await previous;
|
|
130
|
-
|
|
136
|
+
try {
|
|
137
|
+
await runtime.host.commands.register(this.id, 1, declaration, state, when);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
// A definite refusal: the player has no such command to update.
|
|
141
|
+
if (!uncertain(error))
|
|
142
|
+
this.#refused = true;
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
131
145
|
this.#registered = true;
|
|
132
146
|
}
|
|
133
147
|
else
|
|
@@ -136,12 +150,13 @@ class CommandHandle {
|
|
|
136
150
|
const initial = this.#state();
|
|
137
151
|
this.#last = JSON.stringify(initial);
|
|
138
152
|
this.ready = quiet(runtime
|
|
139
|
-
.
|
|
153
|
+
.declaring(this.#pump.push(initial), `command ${this.id}`, (error) => scope.report(error))
|
|
140
154
|
.then(() => { }));
|
|
141
|
-
|
|
155
|
+
this.#run = () => {
|
|
142
156
|
if (!this.#disposed)
|
|
143
157
|
scope.run(options.run);
|
|
144
|
-
}
|
|
158
|
+
};
|
|
159
|
+
runtime.commands.set(this.id, this.#run);
|
|
145
160
|
this.#binding = runtime.bind(() => this.#sync());
|
|
146
161
|
scope.add(this);
|
|
147
162
|
}
|
|
@@ -177,7 +192,7 @@ class CommandHandle {
|
|
|
177
192
|
return Object.keys(when).length ? { state, when } : { state };
|
|
178
193
|
}
|
|
179
194
|
#sync() {
|
|
180
|
-
if (this.#disposed)
|
|
195
|
+
if (this.#disposed || this.#refused)
|
|
181
196
|
return;
|
|
182
197
|
const state = this.#state();
|
|
183
198
|
const encoded = JSON.stringify(state);
|
|
@@ -196,7 +211,8 @@ class CommandHandle {
|
|
|
196
211
|
this.#disposed = true;
|
|
197
212
|
this.#scope.forget(this);
|
|
198
213
|
const runtime = this.#scope.runtime;
|
|
199
|
-
runtime.commands.
|
|
214
|
+
if (runtime.commands.get(this.id) === this.#run)
|
|
215
|
+
runtime.commands.delete(this.id);
|
|
200
216
|
this.#pump.stop();
|
|
201
217
|
// Removed from the menu once registered. Recorded before any await, so an
|
|
202
218
|
// immediate re-add of the same id registers after this removal.
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -6,16 +6,23 @@
|
|
|
6
6
|
* @internal
|
|
7
7
|
*/
|
|
8
8
|
import type { Transport } from "../wire.ts";
|
|
9
|
-
import type { PluginManifest } from "./runtime.ts";
|
|
9
|
+
import type { PluginManifest, RuntimeOptions } from "./runtime.ts";
|
|
10
10
|
import type { Plugin } from "./types.ts";
|
|
11
11
|
export type * from "./types.ts";
|
|
12
|
-
export type { PluginManifest };
|
|
12
|
+
export type { PluginManifest, RuntimeOptions };
|
|
13
13
|
export interface PluginInstance {
|
|
14
14
|
plugin: Plugin;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Wait for registrations started during activation; rejects if one failed.
|
|
17
|
+
* A command declaration the player refused is not a failure (it is
|
|
18
|
+
* logged, skipped and reported to `onError`) unless
|
|
19
|
+
* `RuntimeOptions.strictDeclarations` is set.
|
|
20
|
+
*/
|
|
16
21
|
activated(): Promise<void>;
|
|
17
22
|
dispose(): Promise<void>;
|
|
18
23
|
}
|
|
19
24
|
export declare function createPlugin(transport: Transport, manifest: PluginManifest, directory: string, dataDirectory?: string,
|
|
20
25
|
/** `plugin.app` (the runner passes the player's `VESTRA_APP_INFO`). */
|
|
21
|
-
app?: unknown
|
|
26
|
+
app?: unknown,
|
|
27
|
+
/** `TestHost` sets `strictDeclarations`; the player's runner does not. */
|
|
28
|
+
options?: RuntimeOptions): Promise<PluginInstance>;
|
package/dist/plugin/index.js
CHANGED
|
@@ -5,8 +5,10 @@ import { windowOf } from "./window.js";
|
|
|
5
5
|
import { timelineOf } from "./timeline.js";
|
|
6
6
|
export async function createPlugin(transport, manifest, directory, dataDirectory,
|
|
7
7
|
/** `plugin.app` (the runner passes the player's `VESTRA_APP_INFO`). */
|
|
8
|
-
app
|
|
9
|
-
|
|
8
|
+
app,
|
|
9
|
+
/** `TestHost` sets `strictDeclarations`; the player's runner does not. */
|
|
10
|
+
options) {
|
|
11
|
+
const runtime = new Runtime(transport, manifest, directory, dataDirectory, app, options);
|
|
10
12
|
// Player, window and timeline state are synchronous for plugins: read them first.
|
|
11
13
|
await Promise.all([
|
|
12
14
|
playerOf(runtime).init(),
|
package/dist/plugin/runtime.d.ts
CHANGED
|
@@ -37,6 +37,15 @@ export type Change = ReadonlyMap<string, ReadonlySet<string>>;
|
|
|
37
37
|
* without `update()`) still shows within about this long.
|
|
38
38
|
*/
|
|
39
39
|
export declare const SAFETY_PASS_MS = 1000;
|
|
40
|
+
/** How the plugin is run: the player's runner, or `TestHost`. */
|
|
41
|
+
export interface RuntimeOptions {
|
|
42
|
+
/**
|
|
43
|
+
* A declaration the player refuses during activation fails activation
|
|
44
|
+
* (`startPlugin`, so a plugin's tests show it) instead of being logged,
|
|
45
|
+
* reported to `onError` and skipped (the player's runner).
|
|
46
|
+
*/
|
|
47
|
+
strictDeclarations?: boolean;
|
|
48
|
+
}
|
|
40
49
|
export declare const same: (a: Json | undefined, b: Json | undefined) => boolean;
|
|
41
50
|
/** One value in flight, only the newest waiting. Each push learns its fate. */
|
|
42
51
|
export declare class Latest<T> {
|
|
@@ -74,7 +83,7 @@ export declare class Runtime {
|
|
|
74
83
|
* property writes). Not part of the plugin API; empty for older players.
|
|
75
84
|
*/
|
|
76
85
|
readonly features: ReadonlySet<string>;
|
|
77
|
-
constructor(transport: Transport, manifest: PluginManifest, directory: string, dataDirectory?: string, app?: unknown);
|
|
86
|
+
constructor(transport: Transport, manifest: PluginManifest, directory: string, dataDirectory?: string, app?: unknown, options?: RuntimeOptions);
|
|
78
87
|
can(capability: string): boolean;
|
|
79
88
|
/** Lazily created singletons shared by every scope of this plugin. */
|
|
80
89
|
service<T>(name: string, create: () => T): T;
|
|
@@ -83,6 +92,16 @@ export declare class Runtime {
|
|
|
83
92
|
* plugin reports ready; later ones report failures to their scope.
|
|
84
93
|
*/
|
|
85
94
|
starting<T>(work: Promise<T>, report: (error: unknown) => void): Promise<T>;
|
|
95
|
+
/**
|
|
96
|
+
* A declaration the player checks on its own (`what`: "command x"),
|
|
97
|
+
* started like `starting`. In the player's runner a refusal of it during
|
|
98
|
+
* activation (`invalid`, `conflict`, `limit`) does not fail the plugin:
|
|
99
|
+
* it is logged, skipped and reported to the scope once activation
|
|
100
|
+
* completes, so an `onError` added anywhere in `activate` hears it (SDK
|
|
101
|
+
* 1.5.49; VS Code logs a bad contribution and activates the extension).
|
|
102
|
+
* Under `strictDeclarations` (TestHost) it fails activation as before.
|
|
103
|
+
*/
|
|
104
|
+
declaring<T>(work: Promise<T>, what: string, report: (error: unknown) => void): Promise<T>;
|
|
86
105
|
activated(): Promise<void>;
|
|
87
106
|
bind(evaluate: () => void): Disposable;
|
|
88
107
|
/**
|
package/dist/plugin/runtime.js
CHANGED
|
@@ -86,6 +86,16 @@ function merge(into, from) {
|
|
|
86
86
|
* without `update()`) still shows within about this long.
|
|
87
87
|
*/
|
|
88
88
|
export const SAFETY_PASS_MS = 1000;
|
|
89
|
+
/**
|
|
90
|
+
* The player's answers to a declaration that refuse that one declaration
|
|
91
|
+
* (a command's path, texts or count): during activation they skip it
|
|
92
|
+
* instead of failing the plugin (`Runtime.declaring`, SDK 1.5.49).
|
|
93
|
+
*/
|
|
94
|
+
const DECLARATION_REFUSALS = new Set([
|
|
95
|
+
"invalid",
|
|
96
|
+
"conflict",
|
|
97
|
+
"limit",
|
|
98
|
+
]);
|
|
89
99
|
export const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
90
100
|
/** One value in flight, only the newest waiting. Each push learns its fate. */
|
|
91
101
|
export class Latest {
|
|
@@ -149,6 +159,10 @@ export class Runtime {
|
|
|
149
159
|
/** Registrations that activation must wait for before reporting ready. */
|
|
150
160
|
startup = new Set();
|
|
151
161
|
#activating = true;
|
|
162
|
+
/** `RuntimeOptions.strictDeclarations`. */
|
|
163
|
+
#strict;
|
|
164
|
+
/** Declarations skipped during activation: reported once it completes. */
|
|
165
|
+
#refusals = [];
|
|
152
166
|
#transport;
|
|
153
167
|
/** Each binding with the events whose state it read last time (held). */
|
|
154
168
|
#bindings = new Map();
|
|
@@ -187,8 +201,9 @@ export class Runtime {
|
|
|
187
201
|
* property writes). Not part of the plugin API; empty for older players.
|
|
188
202
|
*/
|
|
189
203
|
features;
|
|
190
|
-
constructor(transport, manifest, directory, dataDirectory, app) {
|
|
204
|
+
constructor(transport, manifest, directory, dataDirectory, app, options = {}) {
|
|
191
205
|
this.#transport = transport;
|
|
206
|
+
this.#strict = options.strictDeclarations ?? false;
|
|
192
207
|
this.dataDirectory = dataDirectory;
|
|
193
208
|
this.local = transport.local;
|
|
194
209
|
this.host = createWire(transport);
|
|
@@ -233,6 +248,32 @@ export class Runtime {
|
|
|
233
248
|
work.catch(() => { });
|
|
234
249
|
return work;
|
|
235
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* A declaration the player checks on its own (`what`: "command x"),
|
|
253
|
+
* started like `starting`. In the player's runner a refusal of it during
|
|
254
|
+
* activation (`invalid`, `conflict`, `limit`) does not fail the plugin:
|
|
255
|
+
* it is logged, skipped and reported to the scope once activation
|
|
256
|
+
* completes, so an `onError` added anywhere in `activate` hears it (SDK
|
|
257
|
+
* 1.5.49; VS Code logs a bad contribution and activates the extension).
|
|
258
|
+
* Under `strictDeclarations` (TestHost) it fails activation as before.
|
|
259
|
+
*/
|
|
260
|
+
declaring(work, what, report) {
|
|
261
|
+
if (!this.#activating || this.#strict)
|
|
262
|
+
return this.starting(work, report);
|
|
263
|
+
const settled = work.then(() => { }, (error) => {
|
|
264
|
+
if (!(error instanceof HostError) ||
|
|
265
|
+
!DECLARATION_REFUSALS.has(error.code))
|
|
266
|
+
throw error;
|
|
267
|
+
console.error(`[${this.manifest.id}] ${what} skipped, the player refused it (${error.code}): ${error.message}`);
|
|
268
|
+
this.#refusals.push(() => report(error));
|
|
269
|
+
});
|
|
270
|
+
// Observed now: `activated()` may come after `activate` awaits, and
|
|
271
|
+
// its `allSettled` still sees the rejection.
|
|
272
|
+
settled.catch(() => { });
|
|
273
|
+
this.startup.add(settled);
|
|
274
|
+
work.catch(() => { });
|
|
275
|
+
return work;
|
|
276
|
+
}
|
|
236
277
|
async activated() {
|
|
237
278
|
while (this.startup.size) {
|
|
238
279
|
const pending = [...this.startup];
|
|
@@ -242,6 +283,8 @@ export class Runtime {
|
|
|
242
283
|
throw result.reason;
|
|
243
284
|
}
|
|
244
285
|
this.#activating = false;
|
|
286
|
+
for (const report of this.#refusals.splice(0))
|
|
287
|
+
report();
|
|
245
288
|
}
|
|
246
289
|
bind(evaluate) {
|
|
247
290
|
const held = new Map();
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -2728,8 +2728,11 @@ export declare class TestHost implements Transport {
|
|
|
2728
2728
|
*
|
|
2729
2729
|
* Rejects when `manifest.json` cannot be read or parsed, when `activate`
|
|
2730
2730
|
* throws, or when a registration made during activation fails (for example
|
|
2731
|
-
* `limit`, `conflict`, `invalid`)
|
|
2732
|
-
*
|
|
2731
|
+
* `limit`, `conflict`, `invalid`). Stricter than the player's runner for
|
|
2732
|
+
* commands (SDK 1.5.49): there the SDK logs a command declaration the
|
|
2733
|
+
* player refuses during activation, skips it and activates the rest of the
|
|
2734
|
+
* plugin (`onError` hears it); `startPlugin` fails, so a test shows the
|
|
2735
|
+
* bad declaration.
|
|
2733
2736
|
*
|
|
2734
2737
|
* @param activate - The plugin's default-exported `activate` function.
|
|
2735
2738
|
* @param options - Where the plugin lives and how to start it.
|
package/dist/plugin/testing.js
CHANGED
|
@@ -3956,14 +3956,23 @@ export class TestHost {
|
|
|
3956
3956
|
!BAR_BUTTON_ICONS.includes(icon))
|
|
3957
3957
|
throw commandInvalid(`unknown button icon ${JSON.stringify(icon)}: a name of @vestra/icons.slint (${BAR_BUTTON_ICONS.join(", ")}) or an image from plugin.ui.image`);
|
|
3958
3958
|
}
|
|
3959
|
+
// The binding table's `Table::canonical` (player 0.6.1): each binding
|
|
3960
|
+
// parsed, and each input once, compared parsed, before the reason,
|
|
3961
|
+
// conflicts and limits.
|
|
3959
3962
|
const bindings = (declaration.bindings ?? []);
|
|
3960
|
-
|
|
3963
|
+
const seen = new Set();
|
|
3964
|
+
for (const binding of bindings.map(String)) {
|
|
3965
|
+
let input;
|
|
3961
3966
|
try {
|
|
3962
|
-
canonical(
|
|
3967
|
+
input = canonical(binding);
|
|
3963
3968
|
}
|
|
3964
3969
|
catch (error) {
|
|
3965
3970
|
throw commandInvalid(error.message);
|
|
3966
3971
|
}
|
|
3972
|
+
if (seen.has(input))
|
|
3973
|
+
throw commandInvalid(`binding ${binding} is listed twice`);
|
|
3974
|
+
seen.add(input);
|
|
3975
|
+
}
|
|
3967
3976
|
if (reason !== null)
|
|
3968
3977
|
commandText(reason, "reason", COMMAND_LIMITS.reasonBytes);
|
|
3969
3978
|
if (this.commands.has(id))
|
|
@@ -3991,13 +4000,6 @@ export class TestHost {
|
|
|
3991
4000
|
this.commands.size + this.coreCommands.size + hostOnly.length >=
|
|
3992
4001
|
COMMAND_LIMITS.total)
|
|
3993
4002
|
throw new HostError("limit", "command registry limit reached");
|
|
3994
|
-
// The binding table: each input once per command.
|
|
3995
|
-
const seen = new Set();
|
|
3996
|
-
for (const binding of bindings.map(String)) {
|
|
3997
|
-
if (seen.has(binding))
|
|
3998
|
-
throw new HostError("invalid", `binding ${binding} is listed twice`);
|
|
3999
|
-
seen.add(binding);
|
|
4000
|
-
}
|
|
4001
4003
|
const host = this;
|
|
4002
4004
|
let sent = p.state;
|
|
4003
4005
|
this.commands.set(id, {
|
|
@@ -5426,8 +5428,11 @@ function tellResidency(host) {
|
|
|
5426
5428
|
*
|
|
5427
5429
|
* Rejects when `manifest.json` cannot be read or parsed, when `activate`
|
|
5428
5430
|
* throws, or when a registration made during activation fails (for example
|
|
5429
|
-
* `limit`, `conflict`, `invalid`)
|
|
5430
|
-
*
|
|
5431
|
+
* `limit`, `conflict`, `invalid`). Stricter than the player's runner for
|
|
5432
|
+
* commands (SDK 1.5.49): there the SDK logs a command declaration the
|
|
5433
|
+
* player refuses during activation, skips it and activates the rest of the
|
|
5434
|
+
* plugin (`onError` hears it); `startPlugin` fails, so a test shows the
|
|
5435
|
+
* bad declaration.
|
|
5431
5436
|
*
|
|
5432
5437
|
* @param activate - The plugin's default-exported `activate` function.
|
|
5433
5438
|
* @param options - Where the plugin lives and how to start it.
|
|
@@ -5512,7 +5517,10 @@ export async function startPlugin(activate, options = {}) {
|
|
|
5512
5517
|
const instance = await createPlugin(host, manifest, directory, data,
|
|
5513
5518
|
// Like the player: protocol additions it takes (`app.features` set on
|
|
5514
5519
|
// the host replaces them, e.g. `[]` for an older player's behaviour).
|
|
5515
|
-
{ features: [PATCH_FEATURE], ...host.app }
|
|
5520
|
+
{ features: [PATCH_FEATURE], ...host.app },
|
|
5521
|
+
// Unlike the player's runner, which logs a refused command declaration
|
|
5522
|
+
// and activates the rest: a plugin's tests show the refusal.
|
|
5523
|
+
{ strictDeclarations: true });
|
|
5516
5524
|
await activate(instance.plugin);
|
|
5517
5525
|
// Like the player: a position or resolver another plugin holds is not an
|
|
5518
5526
|
// activation failure.
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -487,7 +487,11 @@ export interface Scope extends Disposable {
|
|
|
487
487
|
* Registrations started during activation (commands, panels, resolvers,
|
|
488
488
|
* subscriptions) do not need to be awaited: the plugin is reported ready
|
|
489
489
|
* once they all succeed, and activation fails if `activate` throws or any
|
|
490
|
-
* of them fails.
|
|
490
|
+
* of them fails. A command declaration the player refuses is the exception
|
|
491
|
+
* (SDK 1.5.49): the SDK logs and skips it, its `ready` rejects and
|
|
492
|
+
* {@link Scope.onError} hears it, the rest of the plugin activates
|
|
493
|
+
* (`startPlugin` still fails on it). The entry module must
|
|
494
|
+
* **default-export** `activate`.
|
|
491
495
|
*
|
|
492
496
|
* Stopping: the player first revokes everything the plugin owns on its side
|
|
493
497
|
* (menus, windows, panels, filters, subtitle layers, snapshots, jobs,
|
|
@@ -1138,8 +1142,21 @@ export interface Command extends Disposable {
|
|
|
1138
1142
|
* Resolves when the host has registered the command. Rejects with
|
|
1139
1143
|
* `conflict` (path already taken), `invalid` or `limit` (more than 160
|
|
1140
1144
|
* commands in this plugin, or 1024 in the whole player). You rarely need
|
|
1141
|
-
* to await it:
|
|
1142
|
-
*
|
|
1145
|
+
* to await it: the refusal goes to {@link Scope.onError}. During
|
|
1146
|
+
* activation the SDK, in the plugin's process, logs the refusal with the
|
|
1147
|
+
* command id and the player's message, skips the command and lets the
|
|
1148
|
+
* rest of the plugin activate (SDK 1.5.49; before, the refusal failed
|
|
1149
|
+
* activation); the error reaches `onError` once activation completes,
|
|
1150
|
+
* so a listener added anywhere in `activate` hears it. `startPlugin`
|
|
1151
|
+
* stays strict: there a refusal during activation fails it, so tests
|
|
1152
|
+
* show the bad declaration. Any other failure during activation
|
|
1153
|
+
* (`capability`, no answer) fails activation.
|
|
1154
|
+
*
|
|
1155
|
+
* Only the player's answer is skipped. What {@link Commands.add} checks
|
|
1156
|
+
* itself still throws from `add` (an unknown `@` menu id, more than 8
|
|
1157
|
+
* path segments, an `order` out of range, a malformed binding or one
|
|
1158
|
+
* listed twice, a bad `button`, a reason text of the wrong shape, an id
|
|
1159
|
+
* already declared), and fails activation unless the plugin catches it.
|
|
1143
1160
|
*
|
|
1144
1161
|
* @throws `conflict` (rejects) Another command already has this menu path,
|
|
1145
1162
|
* or the path would be both a menu item and a submenu.
|
|
@@ -1161,8 +1178,9 @@ export interface Command extends Disposable {
|
|
|
1161
1178
|
* when the user picks the item, presses a key or clicks the button.
|
|
1162
1179
|
* Commands of all plugins merge into the player's menus by their English
|
|
1163
1180
|
* path keys. Registration is asynchronous: `add` returns at once, and
|
|
1164
|
-
* {@link Command.ready} reports the player's answer (during activation
|
|
1165
|
-
* refusal
|
|
1181
|
+
* {@link Command.ready} reports the player's answer (during activation
|
|
1182
|
+
* the SDK logs a refusal and skips that command, SDK 1.5.49;
|
|
1183
|
+
* `startPlugin` fails on it). What `add` checks itself throws from `add`.
|
|
1166
1184
|
*
|
|
1167
1185
|
* @capability commands
|
|
1168
1186
|
*
|
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
1
|
+
# @vestra-player/sdk 1.5.49: 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
|
|
|
@@ -2357,7 +2357,7 @@ The player bounds what one request may cost it. The limits are guards against ru
|
|
|
2357
2357
|
|
|
2358
2358
|
#### Commands
|
|
2359
2359
|
|
|
2360
|
-
A command added past `perPlugin` or `total` fails with code `limit` (its `ready` rejects; during activation,
|
|
2360
|
+
A command added past `perPlugin` or `total` fails with code `limit` (its `ready` rejects and `onError` hears it; during activation the SDK logs and skips that command and the rest of the plugin activates, SDK 1.5.49, while `startPlugin` fails). Commands already registered are unaffected, and disposing one frees its slot at once. A declaration past the other limits fails with `invalid` the same way (SDK 1.5.48: `TestHost` refuses it too).
|
|
2361
2361
|
|
|
2362
2362
|
| Limit | Value | Meaning |
|
|
2363
2363
|
| --- | --- | --- |
|
|
@@ -3841,6 +3841,10 @@ When the player starts the plugin:
|
|
|
3841
3841
|
|
|
3842
3842
|
You do not need to `await` those registrations. `commands.add`, `panels.mount` and `resolvers.add` return their handle at once and register in the background; their `ready` promise is there for the rare case that you want to wait. **Activation fails** when `activate` throws or rejects, or when one of those registrations fails. A registration made later, after activation, cannot fail activation any more: its failure goes to `onError`.
|
|
3843
3843
|
|
|
3844
|
+
A command declaration the player refuses (`invalid`, `conflict` or `limit`: a bad text, a path another command has, too many commands) does not fail activation either (SDK 1.5.49). The SDK, in the plugin's process, logs it with the command id and the player's message, skips that command and lets the rest of the plugin activate, like VS Code with a bad contribution; the command's `ready` rejects and `onError` hears the refusal once activation completes. `startPlugin` stays strict: there the refusal fails the start, so your tests show the bad declaration.
|
|
3845
|
+
|
|
3846
|
+
This covers only the player's answer. What `commands.add` checks itself still throws from `add`: an unknown `@` menu id, more than 8 path segments, an `order` out of range, a malformed binding or one listed twice, a bad `button`, a reason text of the wrong shape, an id already declared. Uncaught, such a throw fails activation like any other exception in `activate`.
|
|
3847
|
+
|
|
3844
3848
|
Some refusals are deliberately not failures, because they depend on which other plugins are installed: a bottom bar or backdrop that another plugin's panel already holds, the next/previous sequence that another plugin provides, a file type or web host that another plugin already resolves. The plugin keeps running without that part, the player tells the user, and the plugin manager shows it as a warning (PluginInfo.warnings). The player's built-in plugins register first, so they win these contests at start-up.
|
|
3845
3849
|
|
|
3846
3850
|
#### Handles and scopes
|
|
@@ -4579,6 +4583,7 @@ A tool window (Window), a panel (Panel) and a settings page (SettingsPage) all s
|
|
|
4579
4583
|
- **A panel or settings page file exports one component and one global.** The player composes your component into a window it owns (the main window, the floating sidebar window, the Options window), so the component instance is not a stable address: a new instance is made whenever the main window is rebuilt or the sidebar moves to or from its floating window. The **global** is the bridge: properties and callbacks are declared on it, the component reads and calls them (`ListBridge.rows`, `ListBridge.select(index)`), and the plugin talks to the global. The SDK finds the two names from the file's `export component` and `export global` lines.
|
|
4580
4584
|
|
|
4581
4585
|
```slint
|
|
4586
|
+
import { ListView } from "std-widgets.slint";
|
|
4582
4587
|
import { Theme } from "@vestra/theme.slint";
|
|
4583
4588
|
|
|
4584
4589
|
export global ListBridge {
|
|
@@ -4591,7 +4596,9 @@ export global ListBridge {
|
|
|
4591
4596
|
export component ListPanel inherits Rectangle {
|
|
4592
4597
|
background: Theme.bg-list;
|
|
4593
4598
|
min-width: 200px;
|
|
4594
|
-
|
|
4599
|
+
// A ListView builds only the rows in view and scrolls the rest, so the
|
|
4600
|
+
// list costs the same with three rows or thirty thousand.
|
|
4601
|
+
ListView {
|
|
4595
4602
|
for row[index] in ListBridge.rows: Rectangle {
|
|
4596
4603
|
height: 20px;
|
|
4597
4604
|
background: index == ListBridge.selected ? Theme.selected-bg : transparent;
|
|
@@ -4614,9 +4621,6 @@ export component ListPanel inherits Rectangle {
|
|
|
4614
4621
|
}
|
|
4615
4622
|
}
|
|
4616
4623
|
}
|
|
4617
|
-
// Takes the space below the rows. Without it the layout's maximum
|
|
4618
|
-
// height (three 20px rows) would cap the player's main window.
|
|
4619
|
-
Rectangle { vertical-stretch: 1; }
|
|
4620
4624
|
}
|
|
4621
4625
|
}
|
|
4622
4626
|
```
|
|
@@ -5183,7 +5187,7 @@ self.activations.begin(owner, self.now_ms() + 15_000)?;
|
|
|
5183
5187
|
|
|
5184
5188
|
Source: src/host/src/packages/package_manager.rs
|
|
5185
5189
|
|
|
5186
|
-
`boot.ts` sends `runtime.ready` only after `activate` returned and every registration started during activation succeeded (a slot, sequence or resolver another plugin holds does not count as a failure). Otherwise it sends `runtime.failed` with a `code`. A failure or a missed deadline stops the plugin, and the plugin manager lists it as failed with the message. At start-up the installed plugins are started from a queue, about 25 ms of process spawning per UI tick, so the window stays responsive while they come up.
|
|
5190
|
+
`boot.ts` sends `runtime.ready` only after `activate` returned and every registration started during activation succeeded (a slot, sequence or resolver another plugin holds does not count as a failure, and the SDK skips a command declaration the player refuses with `invalid`, `conflict` or `limit`: it logs it and reports it to `onError`, SDK 1.5.49). Otherwise it sends `runtime.failed` with a `code`. A failure or a missed deadline stops the plugin, and the plugin manager lists it as failed with the message. At start-up the installed plugins are started from a queue, about 25 ms of process spawning per UI tick, so the window stays responsive while they come up.
|
|
5187
5191
|
|
|
5188
5192
|
#### The wire protocol
|
|
5189
5193
|
|
|
@@ -5491,6 +5495,7 @@ Use the layout of Theme: small adjustment windows: the same width, row height, l
|
|
|
5491
5495
|
- **No status or path lines in a panel.** Messages go to the OSD; a full path is a tooltip on its row.
|
|
5492
5496
|
- **Show state with colour, not words.** As in the player's playlist: what is playing in `Theme.playing` (with a thin bar at the left, Theme: layouts), what was played in `Theme.text-played`, the selection on `Theme.selected-bg`. A failed item gets the `failed` icon at the end of its row and an OSD message.
|
|
5493
5497
|
- **Lists are plain**: single-line rows, background `Theme.bg-list`, no cards, no stripes, no rounded rows; scroll bars come from `std-widgets.slint` so they follow the palette.
|
|
5498
|
+
- **Long lists use `ListView`.** A list whose length the user decides (a playlist, search results, files, messages) goes in a `ListView` from `std-widgets.slint`, with the `for` inside it: it creates only the rows in view, so a list of thousands of rows scrolls as smoothly as one of ten. A `for` in a layout builds every row up front. Keep a bare `for` for a short list of a fixed size, such as a row of buttons. Example: Panel menu is a panel list drawn this way.
|
|
5494
5499
|
- **Commands of a panel go into its right-click menu**, made of the plugin's commands (panel.showMenu); see Menus.
|
|
5495
5500
|
- **The bottom bar is icons only**: no text buttons, no source path, no status text (the source name is in the window title, status is an OSD message).
|
|
5496
5501
|
|
|
@@ -5827,6 +5832,7 @@ Every SDK release's CHANGELOG entry opens with who must change and what happens
|
|
|
5827
5832
|
|
|
5828
5833
|
| Release | Who must change | Guide |
|
|
5829
5834
|
| --- | --- | --- |
|
|
5835
|
+
| [1.5.49](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md#1549) | **Nobody.** No changes to the plugin API (`@vestra-player/sdk/runtime` gains the additive `RuntimeOptions` and an optional sixth `createPlugin` argument), `apiVersion` stays `vestra/1`, no new capability, no player version needed, and the player's runner needs no change. | — |
|
|
5830
5836
|
| [1.5.48](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md#1548) | **Nobody, but plugin tests may now fail.** No changes to the TypeScript API beyond new fields of `COMMAND_LIMITS`, `apiVersion` stays `vestra/1`, no new capability, no player version needed. | — |
|
|
5831
5837
|
| [1.5.47](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md#1547) | **Nobody.** No changes to the TypeScript API, `apiVersion` stays `vestra/1`, no new capability, no player version needed. | — |
|
|
5832
5838
|
| [1.5.46](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md#1546) | **Nobody.** No changes to the TypeScript API, `apiVersion` stays `vestra/1`, no new capability. | — |
|
|
@@ -6312,7 +6318,11 @@ initial player, window and timeline state, then calls `activate(plugin)`.
|
|
|
6312
6318
|
Registrations started during activation (commands, panels, resolvers,
|
|
6313
6319
|
subscriptions) do not need to be awaited: the plugin is reported ready
|
|
6314
6320
|
once they all succeed, and activation fails if `activate` throws or any
|
|
6315
|
-
of them fails.
|
|
6321
|
+
of them fails. A command declaration the player refuses is the exception
|
|
6322
|
+
(SDK 1.5.49): the SDK logs and skips it, its `ready` rejects and
|
|
6323
|
+
Scope.onError hears it, the rest of the plugin activates
|
|
6324
|
+
(`startPlugin` still fails on it). The entry module must
|
|
6325
|
+
**default-export** `activate`.
|
|
6316
6326
|
|
|
6317
6327
|
Stopping: the player first revokes everything the plugin owns on its side
|
|
6318
6328
|
(menus, windows, panels, filters, subtitle layers, snapshots, jobs,
|
|
@@ -6871,7 +6881,24 @@ interface Command extends Disposable {
|
|
|
6871
6881
|
checked: Bindable<boolean | null> | Condition;
|
|
6872
6882
|
}>): void;
|
|
6873
6883
|
/**
|
|
6874
|
-
* Resolves when the host has registered the command. Rejects with
|
|
6884
|
+
* Resolves when the host has registered the command. Rejects with
|
|
6885
|
+
* `conflict` (path already taken), `invalid` or `limit` (more than 160
|
|
6886
|
+
* commands in this plugin, or 1024 in the whole player). You rarely need
|
|
6887
|
+
* to await it: the refusal goes to Scope.onError. During
|
|
6888
|
+
* activation the SDK, in the plugin's process, logs the refusal with the
|
|
6889
|
+
* command id and the player's message, skips the command and lets the
|
|
6890
|
+
* rest of the plugin activate (SDK 1.5.49; before, the refusal failed
|
|
6891
|
+
* activation); the error reaches `onError` once activation completes,
|
|
6892
|
+
* so a listener added anywhere in `activate` hears it. `startPlugin`
|
|
6893
|
+
* stays strict: there a refusal during activation fails it, so tests
|
|
6894
|
+
* show the bad declaration. Any other failure during activation
|
|
6895
|
+
* (`capability`, no answer) fails activation.
|
|
6896
|
+
*
|
|
6897
|
+
* Only the player's answer is skipped. What Commands.add checks
|
|
6898
|
+
* itself still throws from `add` (an unknown `@` menu id, more than 8
|
|
6899
|
+
* path segments, an `order` out of range, a malformed binding or one
|
|
6900
|
+
* listed twice, a bad `button`, a reason text of the wrong shape, an id
|
|
6901
|
+
* already declared), and fails activation unless the plugin catches it.
|
|
6875
6902
|
* @throws conflict (rejects) Another command already has this menu path, or the path would be both a menu item and a submenu.
|
|
6876
6903
|
* @throws capability (rejects) The manifest does not declare `commands`.
|
|
6877
6904
|
* @throws invalid (rejects) The player refused the declaration.
|
|
@@ -6908,8 +6935,9 @@ the menu, the binding table and the user's key overrides: it calls `run`
|
|
|
6908
6935
|
when the user picks the item, presses a key or clicks the button.
|
|
6909
6936
|
Commands of all plugins merge into the player's menus by their English
|
|
6910
6937
|
path keys. Registration is asynchronous: `add` returns at once, and
|
|
6911
|
-
Command.ready reports the player's answer (during activation
|
|
6912
|
-
refusal
|
|
6938
|
+
Command.ready reports the player's answer (during activation
|
|
6939
|
+
the SDK logs a refusal and skips that command, SDK 1.5.49;
|
|
6940
|
+
`startPlugin` fails on it). What `add` checks itself throws from `add`.
|
|
6913
6941
|
|
|
6914
6942
|
- @capability commands
|
|
6915
6943
|
|
|
@@ -8464,6 +8492,7 @@ panel.on("contextMenu", (x: number, y: number) =>
|
|
|
8464
8492
|
`ui/panel.slint`:
|
|
8465
8493
|
|
|
8466
8494
|
```slint
|
|
8495
|
+
import { ListView } from "std-widgets.slint";
|
|
8467
8496
|
import { Theme } from "@vestra/theme.slint";
|
|
8468
8497
|
|
|
8469
8498
|
export global ListBridge {
|
|
@@ -8476,7 +8505,9 @@ export global ListBridge {
|
|
|
8476
8505
|
export component ListPanel inherits Rectangle {
|
|
8477
8506
|
background: Theme.bg-list;
|
|
8478
8507
|
min-width: 200px;
|
|
8479
|
-
|
|
8508
|
+
// A ListView builds only the rows in view and scrolls the rest, so the
|
|
8509
|
+
// list costs the same with three rows or thirty thousand.
|
|
8510
|
+
ListView {
|
|
8480
8511
|
for row[index] in ListBridge.rows: Rectangle {
|
|
8481
8512
|
height: 20px;
|
|
8482
8513
|
background: index == ListBridge.selected ? Theme.selected-bg : transparent;
|
|
@@ -8499,9 +8530,6 @@ export component ListPanel inherits Rectangle {
|
|
|
8499
8530
|
}
|
|
8500
8531
|
}
|
|
8501
8532
|
}
|
|
8502
|
-
// Takes the space below the rows. Without it the layout's maximum
|
|
8503
|
-
// height (three 20px rows) would cap the player's main window.
|
|
8504
|
-
Rectangle { vertical-stretch: 1; }
|
|
8505
8533
|
}
|
|
8506
8534
|
}
|
|
8507
8535
|
```
|
|
@@ -9403,6 +9431,7 @@ panel.bind(() => ({ rows: items, selected }));
|
|
|
9403
9431
|
`ui/panel.slint`:
|
|
9404
9432
|
|
|
9405
9433
|
```slint
|
|
9434
|
+
import { ListView } from "std-widgets.slint";
|
|
9406
9435
|
import { Theme } from "@vestra/theme.slint";
|
|
9407
9436
|
|
|
9408
9437
|
export global ListBridge {
|
|
@@ -9415,7 +9444,9 @@ export global ListBridge {
|
|
|
9415
9444
|
export component ListPanel inherits Rectangle {
|
|
9416
9445
|
background: Theme.bg-list;
|
|
9417
9446
|
min-width: 200px;
|
|
9418
|
-
|
|
9447
|
+
// A ListView builds only the rows in view and scrolls the rest, so the
|
|
9448
|
+
// list costs the same with three rows or thirty thousand.
|
|
9449
|
+
ListView {
|
|
9419
9450
|
for row[index] in ListBridge.rows: Rectangle {
|
|
9420
9451
|
height: 20px;
|
|
9421
9452
|
background: index == ListBridge.selected ? Theme.selected-bg : transparent;
|
|
@@ -9438,9 +9469,6 @@ export component ListPanel inherits Rectangle {
|
|
|
9438
9469
|
}
|
|
9439
9470
|
}
|
|
9440
9471
|
}
|
|
9441
|
-
// Takes the space below the rows. Without it the layout's maximum
|
|
9442
|
-
// height (three 20px rows) would cap the player's main window.
|
|
9443
|
-
Rectangle { vertical-stretch: 1; }
|
|
9444
9472
|
}
|
|
9445
9473
|
}
|
|
9446
9474
|
```
|
|
@@ -17151,8 +17179,11 @@ undeclared capability fail with `capability`, as in the player.
|
|
|
17151
17179
|
|
|
17152
17180
|
Rejects when `manifest.json` cannot be read or parsed, when `activate`
|
|
17153
17181
|
throws, or when a registration made during activation fails (for example
|
|
17154
|
-
`limit`, `conflict`, `invalid`)
|
|
17155
|
-
|
|
17182
|
+
`limit`, `conflict`, `invalid`). Stricter than the player's runner for
|
|
17183
|
+
commands (SDK 1.5.49): there the SDK logs a command declaration the
|
|
17184
|
+
player refuses during activation, skips it and activates the rest of the
|
|
17185
|
+
plugin (`onError` hears it); `startPlugin` fails, so a test shows the
|
|
17186
|
+
bad declaration.
|
|
17156
17187
|
|
|
17157
17188
|
The way into a plugin's tests: one call starts the plugin the way the
|
|
17158
17189
|
player's runner does and returns the host to drive, the `plugin` object
|
|
@@ -19419,7 +19450,7 @@ Add a window teaches the view surface panels share with tool windows; the rules
|
|
|
19419
19450
|
- **The global `ListBridge`** holds the properties and callbacks. A panel is part of the player's main window, and the player makes a new instance of the component whenever it rebuilds that window. The global stays put, so the plugin talks to it, not to the component.
|
|
19420
19451
|
- `rows` is the list (an array in TypeScript) and `selected` the selected index.
|
|
19421
19452
|
- `select(int)` reports a click; `context-menu(float, float)` asks for the menu at a point.
|
|
19422
|
-
- **The component** inherits `Rectangle`, not `Window`: the player places it. `for row[index] in ListBridge.rows`
|
|
19453
|
+
- **The component** inherits `Rectangle`, not `Window`: the player places it. A `ListView` from `std-widgets.slint` fills it; `for row[index] in ListBridge.rows` inside it gives one 20-pixel row per item, and the selected row gets `Theme.selected-bg`. The `ListView` creates only the rows in view and scrolls the rest, so the panel stays cheap however long the list grows; a bare `for` in a layout would build every row up front (and `vestra-plugin check` warns about one over a bound array).
|
|
19423
19454
|
- **The `TouchArea`** of each row: any button going down selects the row. The right button coming up asks for the menu, at the pointer's position in the window (`absolute-position` is the row's position, `mouse-x`/`mouse-y` the pointer's position in it, and `/ 1px` makes a plain number).
|
|
19424
19455
|
|
|
19425
19456
|
#### The code
|
|
@@ -19521,6 +19552,7 @@ export default async function activate(plugin: Plugin) {
|
|
|
19521
19552
|
#### examples/panel-menu/ui/panel.slint
|
|
19522
19553
|
|
|
19523
19554
|
```slint
|
|
19555
|
+
import { ListView } from "std-widgets.slint";
|
|
19524
19556
|
import { Theme } from "@vestra/theme.slint";
|
|
19525
19557
|
|
|
19526
19558
|
export global ListBridge {
|
|
@@ -19533,7 +19565,9 @@ export global ListBridge {
|
|
|
19533
19565
|
export component ListPanel inherits Rectangle {
|
|
19534
19566
|
background: Theme.bg-list;
|
|
19535
19567
|
min-width: 200px;
|
|
19536
|
-
|
|
19568
|
+
// A ListView builds only the rows in view and scrolls the rest, so the
|
|
19569
|
+
// list costs the same with three rows or thirty thousand.
|
|
19570
|
+
ListView {
|
|
19537
19571
|
for row[index] in ListBridge.rows: Rectangle {
|
|
19538
19572
|
height: 20px;
|
|
19539
19573
|
background: index == ListBridge.selected ? Theme.selected-bg : transparent;
|
|
@@ -19556,9 +19590,6 @@ export component ListPanel inherits Rectangle {
|
|
|
19556
19590
|
}
|
|
19557
19591
|
}
|
|
19558
19592
|
}
|
|
19559
|
-
// Takes the space below the rows. Without it the layout's maximum
|
|
19560
|
-
// height (three 20px rows) would cap the player's main window.
|
|
19561
|
-
Rectangle { vertical-stretch: 1; }
|
|
19562
19593
|
}
|
|
19563
19594
|
}
|
|
19564
19595
|
```
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vestra Player SDK
|
|
2
2
|
|
|
3
|
-
> @vestra-player/sdk 1.5.
|
|
3
|
+
> @vestra-player/sdk 1.5.49: 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 (the player's built-in plugins share one). Everything a plugin creates (commands, windows, panels, filters, listeners…) is a handle that is released automatically when the plugin stops. L<n>: a line of llms-full.txt (next to this file), which has every page, declaration and example.
|
|
4
4
|
|
|
5
5
|
**Rules**
|
|
6
6
|
|
|
@@ -80,7 +80,7 @@ export default async function activate(plugin: Plugin) {
|
|
|
80
80
|
- `video.upscaling`: VideoUpscaling.state/set/onChange
|
|
81
81
|
- No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme, packages.list and rendering.state. Details: L2293–2353.
|
|
82
82
|
|
|
83
|
-
**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` an in-memory player (TestHost): act (`host.command(id).run()`, `host.press(key)`), `await host.settle()`, assert on `host.osd`, `host.window(id).props`… Guide: L1861–2021; TestHost:
|
|
83
|
+
**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` an in-memory player (TestHost): act (`host.command(id).run()`, `host.press(key)`), `await host.settle()`, assert on `host.osd`, `host.window(id).props`… Guide: L1861–2021; TestHost: L15530.
|
|
84
84
|
|
|
85
85
|
**Command line**
|
|
86
86
|
|
|
@@ -108,7 +108,7 @@ export default async function activate(plugin: Plugin) {
|
|
|
108
108
|
- `limit`: A player-wide limit was reached, a property value is over the limits of PropValue
|
|
109
109
|
- `reserved`: The id is `vestra` or starts with `vestra.`
|
|
110
110
|
- `versionMismatch`: The package needs a newer player
|
|
111
|
-
- Others: PluginError, llms-full.txt
|
|
111
|
+
- Others: PluginError, llms-full.txt L5972.
|
|
112
112
|
|
|
113
113
|
## Reference
|
|
114
114
|
|
|
@@ -116,32 +116,32 @@ export default async function activate(plugin: Plugin) {
|
|
|
116
116
|
- Tutorials: Getting started L9–134; Add a window L135–213; Add settings and translations L214–367; Test, check, pack and publish L368–424; Write your own blur kernel L425–685
|
|
117
117
|
- How-to: Control playback L686–854; Store a plugin's data L855–961; Load subtitles from a plugin L962–1074; Keep a slider responsive while dragging L1075–1262; Remember a window's state between runs L1263–1414; Offer a GPU rendering switch L1415–1540; Keep a command's state without keeping the plugin running L1541–1697; Translate a plugin L1698–1860; Test a plugin L1861–2021; Publish a plugin L2022–2123; Troubleshooting L2124–2217
|
|
118
118
|
- Reference: Manifest L2218–2292; Capabilities L2293–2353; Limits L2354–2506; Commands, menus and bindings L2507–3033; Platforms L3034–3064; Theme L3065–3414; Command line L3415–3472; Error codes L3473–3520; Translations L3521–3613; Test host L3614–3800
|
|
119
|
-
- Explanation: How a plugin runs L3801–
|
|
120
|
-
- Guidelines: Plugin rules
|
|
121
|
-
- Upgrading: Migrate a plugin to English source text
|
|
122
|
-
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html):
|
|
123
|
-
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html):
|
|
119
|
+
- Explanation: How a plugin runs L3801–4007; Commands, menus and key bindings L4008–4162; Playback state, controls and sources L4163–4322; Video and audio processing L4323–4443; Windows, panels, settings pages, dialogs and the OSD L4444–4708; GPU and software rendering L4709–4725; The player's Rust core L4726–5067; How the player runs and talks to plugins L5068–5407
|
|
120
|
+
- Guidelines: Plugin rules L5408–5419; Interface guidelines L5420–5537; Menu guidelines L5538–5596
|
|
121
|
+
- Upgrading: Migrate a plugin to English source text L5597–5746; Migrating off package rollback (1.5.16, removed in 1.5.17) L5747–5824; Who must change, release by release L5825–5894
|
|
122
|
+
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L5895–15203
|
|
123
|
+
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L15204–18090
|
|
124
124
|
|
|
125
125
|
## API
|
|
126
126
|
|
|
127
127
|
Where each `plugin.<module>` is declared:
|
|
128
128
|
|
|
129
|
-
- Plugin
|
|
130
|
-
- Commands
|
|
129
|
+
- Plugin L6311, Scope L6047, PluginError L5972, Disposable L5910
|
|
130
|
+
- Commands L6927; When L6502; Windows L7875; Panels L9321; Dialogs L9991; Player L10732; video: VideoFilter L11612, VideoView L11712, RenderOptions L11799, VideoShaders L11877, VideoUpscaling L12235; audio: AudioFilter L11657; Subtitles L12471; media: MediaCapture L12875; Resolvers L13105; Packages L13507; Osd L11477; Host L11319; Clipboard L13836; Power L13923; Tools L13982; Storage L14134; System L14333; Secrets L14413; window: MainWindow L9638; App L9818; Rendering L14676; Bindings L13735; Ui L7221; Timeline L9550; Drops L8568; Appearance L8649; I18n L8882; Settings L9165.
|
|
131
131
|
|
|
132
132
|
## Examples
|
|
133
133
|
|
|
134
134
|
Tested plugins in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples):
|
|
135
135
|
|
|
136
|
-
- hello: The README Quick start: one menu command and an on-screen message.
|
|
137
|
-
- blur: A tool window with one slider that blurs the playing video.
|
|
138
|
-
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone.
|
|
139
|
-
- bookmark: A tool window bound to the playback state: remember the position and jump back to it.
|
|
140
|
-
- panel-menu: A sidebar panel with its own right-click menu.
|
|
141
|
-
- menus: Radio menu items, separator groups and a menu item whose title changes.
|
|
142
|
-
- theme-widgets: A tool window drawn with the player's theme and widgets.
|
|
143
|
-
- markdown: A window that shows a Markdown document.
|
|
144
|
-
- i18n: A plugin in the player's language: English source text, a Chinese translation.
|
|
136
|
+
- hello: The README Quick start: one menu command and an on-screen message. L18525–18620
|
|
137
|
+
- blur: A tool window with one slider that blurs the playing video. L18621–18888
|
|
138
|
+
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L18889–19227
|
|
139
|
+
- bookmark: A tool window bound to the playback state: remember the position and jump back to it. L19228–19420
|
|
140
|
+
- panel-menu: A sidebar panel with its own right-click menu. L19421–19635
|
|
141
|
+
- menus: Radio menu items, separator groups and a menu item whose title changes. L19636–19801
|
|
142
|
+
- theme-widgets: A tool window drawn with the player's theme and widgets. L19802–19971
|
|
143
|
+
- markdown: A window that shows a Markdown document. L19972–20174
|
|
144
|
+
- i18n: A plugin in the player's language: English source text, a Chinese translation. L20175–20348
|
|
145
145
|
|
|
146
146
|
## Optional
|
|
147
147
|
|