@vestra-player/sdk 1.6.7 → 1.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.9
4
+
5
+ **Who must change: nobody.** Additions only: timeline ranges, OSD messages with action buttons, a limits constant, a wire method and `TestHost` support, and documentation of a bottom bar's `content-full-width`. `apiVersion` stays `vestra/1`, no new capability (`timeline.marks`, `playback.read` and `ui.osd` cover them). A plugin that does not use them sends exactly what it sent before; `onChange` of `plugin.timeline` still runs only when the marks change. One that uses them needs player 0.7.6 (`"minPlayerVersion": "0.7.6"`): the player builds `plugin` from its own SDK, so on older players `setRanges` and `ranges` do not exist (a `TypeError`), and `osd.show` there takes no options.
6
+
7
+ - **Timeline ranges** (player 0.7.6): `TimelineLayer.setRanges(ranges, { ifCurrent? })` puts spans of the media on the seek bar, each `{ start, end, style, title? }` with `style` `"selection"` (a span the user picked to act on, such as a clip to export; drawn in the accent colour) or `"region"` (a part of the media the plugin describes; drawn dimmer). A layer's marks and ranges are replaced on their own (`set` keeps the ranges, `setRanges` keeps the marks); disposing the layer, its scope or stopping the plugin removes both, and a new source clears every layer. At most 100 ranges per layer, `0 ≤ start < end`, `end` within the media, titles ≤ 100 characters (`TIMELINE_LIMITS`, new, with the marks' 1000); failures go to `onError` as for marks. `plugin.timeline.ranges` (new, `playback.read`) is every layer's ranges merged by start, with `TimelineRange` (`start`, `end`, `style`, `title`, `plugin`); read it in a binding to follow it. Wire: `timeline.setRanges` (needs `timeline.marks`), and `ranges` beside `marks` in `timeline.marks` and `timeline.changed`.
8
+ - **OSD messages with actions** (player 0.7.6): `plugin.osd.show(text, args?, { actions })` ends the message in up to two buttons (`OsdAction`: `title`, translated like the text, 1–40 characters; `command`, the local id of one of the plugin's own commands). The message stays about 4 seconds instead of 2 and does not fade while the pointer is on it and moving (it goes 2 seconds after the pointer leaves or rests, and at the latest 30 seconds after it showed); a click runs the command as if picked from a menu and removes the message. It stays one line, and nothing in it is created or removed while the pointer moves. A newer message replaces it with its buttons; the buttons go when the plugin stops, and while they show the plugin is not stopped for being idle. `osd.show` rejects with `invalid` for more than two actions, a bad title or a command the plugin has not declared. `OSD_LIMITS` gains `actions` (2) and `actionChars` (40). Older players ignore `actions`.
9
+ - **`TestHost`**: `timeline.rangeLayers`, `timeline.ranges` and `setTimelineRanges(pluginId, ranges)` (another plugin's ranges); `timeline.setRanges` checked as the player does; `osdActions` (each message's buttons, in step with `osd`) and `pressOsdAction(index)`, which runs the command like a click.
10
+ - **A bottom bar's `content-full-width`** (player 0.7.6, docs only): a bar that drops controls of its own also reports its row with every control shown in `content-full-width`, a public `length` property declared like `content-min-width`. The player then gives plugin buttons only the width beyond it, so every plugin button moves to » before the bar drops a control, and narrowing the window never brings a button back. A bar with only `content-min-width` keeps the earlier rule. [Writing a bottom bar](docs/guidelines/interface.md#writing-a-bottom-bar) and [Reference: theme](docs/reference/theme.md#bar-buttons) say so.
11
+
12
+ ## 1.6.8
13
+
14
+ **Who must change: nobody; authors of a bottom bar should move to `content-min-width`.** Documentation, a read-only Slint property and a removed CLI warning: no change to the TypeScript API, the wire or `TestHost`, `apiVersion` stays `vestra/1`, no new capability. A plugin's `button` declarations stay as they are. A bottom bar that still places `BarButtons` keeps working on player 0.7.6 (it shows that strip, and the player draws no second one); it is deprecated and goes in a later patch.
15
+
16
+ - **The player places the bottom bar's button strip** (player 0.7.6, menu redesign step 3a). It draws the strip at the right end of the bottom slot, also when no plugin mounts a bottom bar (in a band of its own): the plugins' buttons, a » menu, the player's **Command Palette** (`host/palette`, a magnifier) and **Toggle Sidebar**. [Bottom-bar buttons](docs/explanation/commands.md#bottom-bar-buttons) now reads: buttons are ordered by `order`, then by plugin id; lower `order` comes first and stays visible longest; when the strip runs out of room, or more than 8 plugin buttons are declared, the remaining buttons move to the » menu, in the same order; a button is never dropped. Before player 0.7.6 a narrow strip lost its lowest orders first and the player left out all but the eight highest; `BAR_BUTTON_LIMITS.total` is now what the strip holds. [Reference: commands](docs/reference/commands.md#bottom-bar-buttons), [Reference: limits](docs/reference/limits.md#bottom-bar-buttons), `CommandButton`, `CommandOptions.button` and `BAR_BUTTON_LIMITS` say so.
17
+ - **Writing a bottom bar** ([Interface guidelines](docs/guidelines/interface.md#writing-a-bottom-bar)): a bar places nothing. It reports the least width of its own controls in a `content-min-width` length property on the mounted component (the player gives the strip the rest, so the time label is never covered) and ends its row at the strip, reading `BarButtonsHost.width` (new, read only; `fixed` for an optional control kept ahead of plugin buttons) from `@vestra/bar-buttons.slint`. The file stays in the Slint library copy for that global; its `BarButtons` component is deprecated.
18
+ - **`@vestra/bar-buttons.slint`** (the read-only copy of the player's widget library, synced): `BarButtonsHost` gains `width`, `fixed`, `shown` and `more` (out: the strip's width, the cells a bar must leave free (the player's own and the » cell), the plugin buttons in the strip, whether » shows) and the player's `palette`, `overflow` and `room`; the comments describe the player's strip and mark `BarButtons` deprecated.
19
+ - **`vestra-plugin check` no longer warns** that a bottom bar does not place `BarButtons`: since player 0.7.6 that is the right thing to do.
20
+
3
21
  ## 1.6.7
4
22
 
5
23
  **Who must change: nobody.** The 1.6.6 release below, published again under a new number: its npm publish failed (the provenance transparency log returned 500, then 409 for the same tarball), so 1.6.6 never reached npm. Nothing else changed.
@@ -81,7 +81,6 @@ import {
81
81
  basename,
82
82
  dirname,
83
83
  extname,
84
- isAbsolute,
85
84
  join,
86
85
  relative,
87
86
  resolve,
@@ -363,10 +362,7 @@ async function check(target, report = null) {
363
362
  }
364
363
  say(`${target.name}: capabilities declared for every API use`);
365
364
  if (report) return;
366
- for (const line of [
367
- ...(await bottomBarWarnings(target)),
368
- ...(await forWarnings(target)),
369
- ])
365
+ for (const line of await forWarnings(target))
370
366
  console.log(`${target.name}: warning: ${line}`);
371
367
  // Translation warnings never fail the check.
372
368
  if (present(join(target.directory, "locales"))) {
@@ -432,85 +428,6 @@ function slintCode(text) {
432
428
  return out;
433
429
  }
434
430
 
435
- /** Whether `path` lies inside `directory` (not a sibling sharing its prefix). */
436
- function inside(directory, path) {
437
- const rel = relative(resolve(directory), path);
438
- return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
439
- }
440
-
441
- /**
442
- * Whether the Slint file at `file` (relative to the plugin directory)
443
- * imports `BarButtons` from `@vestra/bar-buttons.slint`, itself or
444
- * through the plugin's own Slint files (relative paths, any depth, inside
445
- * the plugin directory). Local re-exports count as such files too:
446
- * `export { Row } from "row.slint"` and `export * from "row.slint"` are
447
- * followed, and `export { BarButtons } from "@vestra/bar-buttons.slint"` (or
448
- * `export * from` it) in a file the bar imports counts as placing it.
449
- * Comments do not count.
450
- */
451
- async function importsBarButtons(target, file, seen = new Set()) {
452
- const path = resolve(target.directory, file);
453
- if (seen.has(path) || !inside(target.directory, path)) return false;
454
- seen.add(path);
455
- const text = await readFile(path, "utf8").catch(() => null);
456
- if (text === null) return false;
457
- const code = slintCode(text);
458
- // { names (or "*"), from }: imports, and re-exports with a source file.
459
- const imports = [
460
- ...code.matchAll(
461
- /\b(?:import|export)\s*(?:(\*)\s*from\s*|\{([^}]*)\}\s*from\s*)?"([^"]+)"/g,
462
- ),
463
- ].map(([, star, names, from]) => ({ names: star ?? names ?? "", from }));
464
- for (const { names, from } of imports)
465
- if (
466
- from === "@vestra/bar-buttons.slint" &&
467
- (names === "*" ||
468
- names
469
- .split(",")
470
- .some((name) => /^BarButtons(\s+as\s+[\w-]+)?$/.test(name.trim())))
471
- )
472
- return true;
473
- for (const { from } of imports) {
474
- if (
475
- from.startsWith("@") ||
476
- !from.endsWith(".slint") ||
477
- from === "std-widgets.slint"
478
- )
479
- continue;
480
- if (await importsBarButtons(target, join(dirname(file), from), seen))
481
- return true;
482
- }
483
- return false;
484
- }
485
-
486
- /**
487
- * A plugin that mounts a bottom bar (`panels.mount("bottom", "<file>")`)
488
- * whose Slint file does not import `BarButtons` (`@vestra/bar-buttons.slint`),
489
- * itself or through its local imports: while it is the bottom bar, no
490
- * plugin's command button shows (SDK 1.5.29). The mount is read from the
491
- * source text: a literal file name in the call.
492
- */
493
- async function bottomBarWarnings(target) {
494
- const { typescript: sources } = await sourceFiles(target);
495
- const files = new Set();
496
- for (const file of sources) {
497
- const text = await readFile(join(target.directory, file), "utf8");
498
- for (const match of text.matchAll(
499
- /\.mount\(\s*["'`]bottom["'`]\s*,\s*["'`]([^"'`$]+)["'`]/g,
500
- ))
501
- files.add(match[1]);
502
- }
503
- const warnings = [];
504
- for (const file of files) {
505
- if (!present(join(target.directory, file))) continue;
506
- if (await importsBarButtons(target, file)) continue;
507
- warnings.push(
508
- `${file} is a bottom bar without BarButtons (import { BarButtons } from "@vestra/bar-buttons.slint"): while it is mounted, the command buttons of every plugin (and the player's sidebar toggle) show nowhere; see docs/guidelines/interface.md#writing-a-bottom-bar`,
509
- );
510
- }
511
- return warnings;
512
- }
513
-
514
431
  /**
515
432
  * A `for` over an array the plugin binds (an `in` or `in-out` property of
516
433
  * array type of a component or global in the same file), outside a
@@ -71,8 +71,12 @@ export const API_REQUESTS = Object.freeze({
71
71
  "Subtitles.remove": [{ method: "subtitles.remove" }, readsSubtitles],
72
72
  "Subtitles.setStyle": [{ method: "subtitles.style" }, readsSubtitles],
73
73
  "Timeline.marks": [{ method: "timeline.marks" }],
74
+ "Timeline.ranges": [{ method: "timeline.marks" }],
74
75
  "Timeline.onChange": [listen("timeline.changed")],
75
- "Timeline.layer": [{ method: "timeline.set" }],
76
+ "Timeline.layer": [
77
+ { method: "timeline.set" },
78
+ { method: "timeline.setRanges" },
79
+ ],
76
80
  "MainWindow.onChange": [listen("window.state")],
77
81
  "MainWindow.setVideoArea": [{ method: "window.setVideoArea" }],
78
82
  "MainWindow.setAlwaysOnTop": [{ method: "window.setAlwaysOnTop" }],
@@ -158,12 +158,11 @@ export declare const BAR_BUTTON_ICONS: readonly ["play", "pause", "stop", "back"
158
158
  /**
159
159
  * The player's limits on bottom-bar buttons (`CommandOptions.button`), the
160
160
  * same values as the player's (its tests check that they agree). A button
161
- * past `perPlugin` or `total` is left out: its command still registers and
162
- * its menu entry stays, and the player logs the button it left out
163
- * (`limit`). Which ones stay: each plugin's first declared button, then the
164
- * `total` highest by order, plugin id and declaration order: as in a narrow
165
- * strip, the lowest order goes first (equal orders: the smaller plugin id,
166
- * then the earlier declaration, goes first).
161
+ * past `perPlugin` has none (its command still registers and its menu entry
162
+ * stays). `total` is what the strip holds: the `total` lowest by order,
163
+ * plugin id and declaration order; the rest are in the strip's » menu, in
164
+ * the same order (older players keep the `total` highest and leave the
165
+ * others out).
167
166
  *
168
167
  * @since 1.5.29
169
168
  *
@@ -172,7 +171,7 @@ export declare const BAR_BUTTON_ICONS: readonly ["play", "pause", "stop", "back"
172
171
  export declare const BAR_BUTTON_LIMITS: {
173
172
  /** Buttons one plugin shows (1). */
174
173
  readonly perPlugin: 1;
175
- /** Buttons the player shows, every plugin together, besides its own sidebar toggle (8). */
174
+ /** Buttons in the strip, every plugin together, besides the player's own (8); the rest are in its » menu. */
176
175
  readonly total: 8;
177
176
  /** The highest `order` (999; the lowest is 0). */
178
177
  readonly maxOrder: 999;
package/dist/commands.js CHANGED
@@ -202,12 +202,11 @@ export const BAR_BUTTON_ICONS = [
202
202
  /**
203
203
  * The player's limits on bottom-bar buttons (`CommandOptions.button`), the
204
204
  * same values as the player's (its tests check that they agree). A button
205
- * past `perPlugin` or `total` is left out: its command still registers and
206
- * its menu entry stays, and the player logs the button it left out
207
- * (`limit`). Which ones stay: each plugin's first declared button, then the
208
- * `total` highest by order, plugin id and declaration order: as in a narrow
209
- * strip, the lowest order goes first (equal orders: the smaller plugin id,
210
- * then the earlier declaration, goes first).
205
+ * past `perPlugin` has none (its command still registers and its menu entry
206
+ * stays). `total` is what the strip holds: the `total` lowest by order,
207
+ * plugin id and declaration order; the rest are in the strip's » menu, in
208
+ * the same order (older players keep the `total` highest and leave the
209
+ * others out).
211
210
  *
212
211
  * @since 1.5.29
213
212
  *
@@ -216,7 +215,7 @@ export const BAR_BUTTON_ICONS = [
216
215
  export const BAR_BUTTON_LIMITS = {
217
216
  /** Buttons one plugin shows (1). */
218
217
  perPlugin: 1,
219
- /** Buttons the player shows, every plugin together, besides its own sidebar toggle (8). */
218
+ /** Buttons in the strip, every plugin together, besides the player's own (8); the rest are in its » menu. */
220
219
  total: 8,
221
220
  /** The highest `order` (999; the lowest is 0). */
222
221
  maxOrder: 999,
Binary file
@@ -300,7 +300,10 @@ export const HOST_CAPABILITIES = {
300
300
  },
301
301
  {
302
302
  label: "Show marks on the progress bar",
303
- methods: [{ name: "timeline.set", needs: "timeline.marks" }],
303
+ methods: [
304
+ { name: "timeline.set", needs: "timeline.marks" },
305
+ { name: "timeline.setRanges", needs: "timeline.marks" },
306
+ ],
304
307
  name: "timeline.marks",
305
308
  requires: [],
306
309
  residency: "live",
@@ -16,6 +16,7 @@ export { HOST_CAPABILITIES } from "./host-capabilities.ts";
16
16
  export { BAR_BUTTON_ICONS, BAR_BUTTON_LIMITS, BUILTIN_COMMANDS, BUILTIN_SUBMENUS, COMMAND_LIMITS, MENU_IDS, } from "./commands.ts";
17
17
  export { BINDING_INPUTS } from "./plugin/bindings.ts";
18
18
  export { OSD_LIMITS } from "./plugin/osd.ts";
19
+ export { TIMELINE_LIMITS } from "./plugin/timeline.ts";
19
20
  export { SECRET_LIMITS, SESSION_STORAGE_LIMITS } from "./plugin/services.ts";
20
21
  export { SUBTITLE_LIMITS } from "./plugin/subtitles.ts";
21
22
  export { VIDEO_PROCESSING_LIMITS } from "./processing.ts";
@@ -14,6 +14,7 @@ export { HOST_CAPABILITIES } from "./host-capabilities.js";
14
14
  export { BAR_BUTTON_ICONS, BAR_BUTTON_LIMITS, BUILTIN_COMMANDS, BUILTIN_SUBMENUS, COMMAND_LIMITS, MENU_IDS, } from "./commands.js";
15
15
  export { BINDING_INPUTS } from "./plugin/bindings.js";
16
16
  export { OSD_LIMITS } from "./plugin/osd.js";
17
+ export { TIMELINE_LIMITS } from "./plugin/timeline.js";
17
18
  export { SECRET_LIMITS, SESSION_STORAGE_LIMITS } from "./plugin/services.js";
18
19
  export { SUBTITLE_LIMITS } from "./plugin/subtitles.js";
19
20
  export { VIDEO_PROCESSING_LIMITS } from "./processing.js";
@@ -46,6 +46,10 @@ export declare const OSD_LIMITS: Readonly<{
46
46
  readonly minColumns: 8;
47
47
  /** The largest `OsdPanelOptions.maxColumns` (1000). */
48
48
  readonly maxColumns: 1000;
49
+ /** Action buttons of one `osd.show` message (`OsdShowOptions.actions`; player 0.7.6) (2). */
50
+ readonly actions: 2;
51
+ /** Characters of an action's title, as shown (40; at least 1 that is not white space). */
52
+ readonly actionChars: 40;
49
53
  }>;
50
54
  /**
51
55
  * Copy and check template arguments: an array or object of at most 16
@@ -47,7 +47,32 @@ export const OSD_LIMITS = Object.freeze({
47
47
  minColumns: 8,
48
48
  /** The largest `OsdPanelOptions.maxColumns` (1000). */
49
49
  maxColumns: 1000,
50
+ /** Action buttons of one `osd.show` message (`OsdShowOptions.actions`; player 0.7.6) (2). */
51
+ actions: 2,
52
+ /** Characters of an action's title, as shown (40; at least 1 that is not white space). */
53
+ actionChars: 40,
50
54
  });
55
+ /** Copy and check `OsdShowOptions.actions`: what the SDK can check before the player does. */
56
+ function osdActions(options) {
57
+ if (options === undefined || options === null)
58
+ return undefined;
59
+ if (typeof options !== "object")
60
+ throw failure("invalid", "options is an object");
61
+ const actions = options.actions;
62
+ if (actions === undefined)
63
+ return undefined;
64
+ if (!Array.isArray(actions) || actions.length > OSD_LIMITS.actions)
65
+ throw failure("invalid", `actions is a list of at most ${OSD_LIMITS.actions}`);
66
+ return actions.map((action, i) => {
67
+ const { title, command } = (action ?? {});
68
+ if (typeof command !== "string" || command === "")
69
+ throw failure("invalid", `actions[${i}].command is a command id`);
70
+ return {
71
+ title: shownText(title, undefined, `actions[${i}].title`),
72
+ command,
73
+ };
74
+ });
75
+ }
51
76
  /**
52
77
  * Copy and check template arguments: an array or object of at most 16
53
78
  * strings, finite numbers, texts and lists (`TEXT_LIMITS`).
@@ -134,6 +159,6 @@ class PanelHandle {
134
159
  }
135
160
  }
136
161
  export const osd = (scope) => ({
137
- show: async (text, args) => scope.runtime.host.osd.show(shownText(text, args), templateArgs(args)),
162
+ show: async (text, args, options) => scope.runtime.host.osd.show(shownText(text, args), templateArgs(args), osdActions(options)),
138
163
  panel: () => new PanelHandle(scope),
139
164
  });
@@ -7,6 +7,7 @@ import type { BuiltinCommand, BuiltinSubmenu } from "../commands.ts";
7
7
  import { CAPABILITY_INFO, MESSAGE_LIMIT } from "../capabilities.ts";
8
8
  import type { CapabilityInfo } from "../capabilities.ts";
9
9
  import { OSD_LIMITS } from "./osd.ts";
10
+ import { TIMELINE_LIMITS } from "./timeline.ts";
10
11
  import { SECRET_LIMITS, SESSION_STORAGE_LIMITS } from "./services.ts";
11
12
  import { SUBTITLE_LIMITS } from "./subtitles.ts";
12
13
  import { BINDING_INPUTS } from "./bindings.ts";
@@ -36,6 +37,8 @@ export type { CapabilityInfo };
36
37
  export { MESSAGE_LIMIT };
37
38
  /** The player's limits on the on-screen display. */
38
39
  export { OSD_LIMITS };
40
+ /** The player's limits on a timeline layer. */
41
+ export { TIMELINE_LIMITS };
39
42
  /** The player's limits on stored secrets. */
40
43
  export { SECRET_LIMITS };
41
44
  /** The player's limits on `plugin.storage.session`. */
@@ -647,6 +650,43 @@ export declare class TestHost implements Transport {
647
650
  * template and args the plugin sent are in `host.sent("osd.show")`.
648
651
  */
649
652
  readonly osd: string[];
653
+ /**
654
+ * The action buttons of each `osd.show` message, in step with `osd`
655
+ * (index for index): each button's title as shown and its command; `[]`
656
+ * for a message without actions (SDK 1.6.9).
657
+ */
658
+ readonly osdActions: {
659
+ /** The button title as shown, in the plugin's locale. */
660
+ title: string;
661
+ /** The local id of the plugin's command the button runs. */
662
+ command: string;
663
+ }[][];
664
+ /**
665
+ * Press button `index` of the newest OSD message, as the user does
666
+ * (player 0.7.6): its command runs as if picked from a menu, and settles.
667
+ * A disabled or busy command does nothing, as in the player.
668
+ *
669
+ * @param index - 0 or 1.
670
+ *
671
+ * @throws `Error` (rejects) The newest message has no button `index`.
672
+ *
673
+ * @example Press "Show in Folder" on the saved message
674
+ * The test checks the button the plugin put on its newest message, then
675
+ * presses it as the user would: the plugin's `reveal` command runs.
676
+ * ```ts
677
+ * import assert from "node:assert/strict";
678
+ * declare const host: TestHost;
679
+ * // ---cut---
680
+ * assert.deepEqual(host.osdActions.at(-1), [
681
+ * { title: "Show in Folder", command: "reveal" },
682
+ * ]);
683
+ * await host.pressOsdAction(0); // runs the plugin's `reveal` command
684
+ * ```
685
+ *
686
+ * @capability None.
687
+ * @since 1.6.9
688
+ */
689
+ pressOsdAction(index: number): Promise<void>;
650
690
  /** The text of this plugin's osd panel as shown, `null` when hidden. */
651
691
  osdPanel: string | null;
652
692
  /**
@@ -948,7 +988,65 @@ export declare class TestHost implements Transport {
948
988
  /** The mark's title, `null` without one. */
949
989
  title: string | null;
950
990
  }[];
991
+ /**
992
+ * Each plugin's ranges by plugin id; layers without ranges are absent
993
+ * (SDK 1.6.9).
994
+ */
995
+ rangeLayers: Map<string, {
996
+ /** Seconds from the start. */
997
+ start: number;
998
+ /** Seconds from the start, after `start`. */
999
+ end: number;
1000
+ /** `"selection"` or `"region"`. */
1001
+ style: string;
1002
+ /** The range's title, `null` without one. */
1003
+ title: string | null;
1004
+ }[]>;
1005
+ /** Every layer's ranges merged, sorted by start, with the plugin's id. */
1006
+ readonly ranges: {
1007
+ /** The manifest id of the plugin that published the range. */
1008
+ plugin: string;
1009
+ /** Seconds from the start. */
1010
+ start: number;
1011
+ /** Seconds from the start, after `start`. */
1012
+ end: number;
1013
+ /** `"selection"` or `"region"`. */
1014
+ style: string;
1015
+ /** The range's title, `null` without one. */
1016
+ title: string | null;
1017
+ }[];
951
1018
  };
1019
+ /**
1020
+ * Simulate another plugin publishing its timeline ranges (or clearing
1021
+ * them with `[]`); pushes `timeline.changed` when the plugin subscribed.
1022
+ *
1023
+ * @param pluginId - The other plugin's manifest id.
1024
+ * @param ranges - All its ranges.
1025
+ *
1026
+ * @example Let another plugin publish a clip range
1027
+ * The plugin under test sees a range another plugin put on the seek bar:
1028
+ * `setTimelineRanges` stores that plugin's ranges and pushes
1029
+ * `timeline.changed`, and `settle()` lets it arrive.
1030
+ * ```ts
1031
+ * import assert from "node:assert/strict";
1032
+ * declare const host: TestHost;
1033
+ * // ---cut---
1034
+ * host.setTimelineRanges("community.clips", [
1035
+ * { start: 40, end: 52.5, style: "selection", title: "Clip" },
1036
+ * ]);
1037
+ * await host.settle();
1038
+ * assert.equal(plugin.timeline.ranges[0]?.plugin, "community.clips");
1039
+ * ```
1040
+ *
1041
+ * @capability None.
1042
+ * @since 1.6.9
1043
+ */
1044
+ setTimelineRanges(pluginId: string, ranges: {
1045
+ start: number;
1046
+ end: number;
1047
+ style: string;
1048
+ title?: string | null;
1049
+ }[]): void;
952
1050
  /**
953
1051
  * Simulate another plugin publishing its timeline layer (or clearing it
954
1052
  * with `[]`); pushes `timeline.changed` when the plugin subscribed.
@@ -2645,8 +2743,8 @@ export declare class TestHost implements Transport {
2645
2743
  * The plugin's bottom-bar buttons (`CommandOptions.button`) as the player
2646
2744
  * shows them: at most one, the first declared (`BAR_BUTTON_LIMITS`; a
2647
2745
  * later one stays a menu item only). Empty once the command is disposed.
2648
- * Whether the bar places them is the bar's business: the list is what
2649
- * the player hands to `BarButtons`.
2746
+ * Whether one is in the strip or its » menu is the player's business
2747
+ * (the room and the other plugins' buttons).
2650
2748
  *
2651
2749
  * @example Click the plugin's bottom-bar button in a test
2652
2750
  * The test finds the plugin's Store button, clicks it and checks that the