@mosaicast/plugin-sdk 0.3.0 → 0.4.0

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/README.md CHANGED
@@ -29,7 +29,11 @@ src/testing.ts TS: @mosaicast/plugin-sdk/testing — makeMockCtx
29
29
 
30
30
  The contract version is a **single SemVer anchor** mirrored in four places that MUST move together
31
31
  (CI enforces it): `build.gradle.kts` · `package.json` · `PlatformApi.VERSION` · `PLATFORM_API_VERSION`.
32
- A breaking change is a major bump; the host rejects plugins with an incompatible `platformApi` at startup.
32
+
33
+ **How the host matches it:** core compares `major.minor` **exactly**. A plugin declaring `0.3.x` is
34
+ rejected the moment the host runs `0.4.0` — with the reason shown in the admin log viewer. While the SDK
35
+ is pre-1.0 a breaking change is therefore a **minor** bump (`0.3.0` → `0.4.0`), not a major one; from
36
+ `1.0.0` on, normal SemVer applies and breaking means major.
33
37
 
34
38
  ## Build & test
35
39
  ```bash
@@ -45,8 +49,8 @@ npm ci && npm run build # TypeScript: src → dist (.js + .d.ts)
45
49
  - Released: from **GitHub Packages** (see below).
46
50
  ```kotlin
47
51
  dependencies {
48
- compileOnly("dev.mosaicast:plugin-api:0.3.0") // contract, provided by the host
49
- testImplementation("dev.mosaicast:plugin-testkit:0.3.0") // test doubles only
52
+ compileOnly("dev.mosaicast:plugin-api:0.4.0") // contract, provided by the host
53
+ testImplementation("dev.mosaicast:plugin-testkit:0.4.0") // test doubles only
50
54
  }
51
55
  ```
52
56
  Sources + Javadoc JARs give IDE hover docs automatically.
@@ -110,6 +114,167 @@ DELETE /api/plugins/{id}/data/{scopeType}/{scopeId}/{key}
110
114
 
111
115
  > Roadmap: custom plugin-defined server routes may arrive in a later `platformApi` version; plugins use the doc store.
112
116
 
117
+ ## Relational storage — `ctx.schema()` (since 0.4.0)
118
+
119
+ The doc store is the default and covers nearly everything. Declare a **schema** when you need what a JSON
120
+ document cannot give you: full-text search, revisions, backlinks. Any plugin may; most declare nothing.
121
+
122
+ You declare entities and fields in the manifest, and the **platform** provisions namespaced tables
123
+ (`plugin_<id>_*`) through its managed migration runner, dropping them when an admin purges the plugin.
124
+ **The plugin never writes DDL and never names a table:**
125
+
126
+ ```json
127
+ "storage": { "schema": { "page": {
128
+ "slug": "string:indexed:unique", "title": "string",
129
+ "markdown": "text:fulltext", "updatedAt": "timestamp:indexed" } } }
130
+ ```
131
+
132
+ ```java
133
+ record Page(long id, String slug, String title, String markdown, Instant updatedAt) {}
134
+
135
+ SchemaStore schema = ctx.schema(); // null unless the manifest declares one
136
+
137
+ long id = schema.insert("page", Map.of(
138
+ "slug", "getting-started", "title", "Getting started",
139
+ "markdown", "# Hello", "updatedAt", Instant.now()));
140
+
141
+ Optional<Page> byId = schema.find("page", id, Page.class);
142
+ List<Page> recent = schema.select("page",
143
+ Criteria.all().orderBy("updatedAt", Direction.DESC).limit(20), Page.class);
144
+ List<Page> hits = schema.search("page", "markdown", "lighthouse", Criteria.all(), Page.class);
145
+ long total = schema.count("page", Criteria.all());
146
+ schema.update("page", id, Map.of("markdown", "# Hello again"));
147
+ schema.delete("page", Criteria.where("slug", Op.EQ, "getting-started"));
148
+ ```
149
+
150
+ - Everything is addressed by **declared entity and field name — never SQL, never a table**. That is the
151
+ scoping guarantee: reaching another plugin's tables isn't blocked, it's inexpressible. The host binds
152
+ every value as a JDBC parameter and checks every field name against your manifest.
153
+ - Rows map to **your own record types**, component names matching field names — the same convention as
154
+ `store().get(...)` and `config().get(...)`. Each entity carries a platform-assigned `long id`.
155
+ - `Criteria` is immutable; predicates combine with **AND** (no `or` in 0.4.0). `search` needs a field
156
+ declared `:fulltext`; for a plain match use `Op.LIKE` with `select`.
157
+ - Naming an undeclared entity or field throws `IllegalArgumentException` — against the test kit too, so a
158
+ manifest that drifted from the code fails in your tests.
159
+
160
+ Test it with `FakeSchemaStore`, declaring the same entities your manifest does. Its `search` is a
161
+ substring match, **not** Postgres full-text: no stemming, no ranking. Assert on which rows come back, not
162
+ on their order.
163
+
164
+ ## Logging — `ctx.logger()` / `ctx.log()` (since 0.4.0)
165
+
166
+ ```java
167
+ ctx.logger().info("indexed {} pages", count); // org.slf4j.Logger, named plugin.<pluginId>
168
+ ```
169
+ ```ts
170
+ ctx.log('warn', `no board for episode ${ctx.scope.id}`);
171
+ ```
172
+
173
+ A plain SLF4J `Logger` on the backend: authors know the API, and parameterised messages and throwables
174
+ come free. **Attribution rides in the logger name**, which is why the host hands you a named logger rather
175
+ than offering `log(level, message)` — the name travels with the event, so output is still attributed to
176
+ your plugin from a thread you started or from an `onSchedule` task, where a thread-local MDC arrives
177
+ empty. Don't build your own `LoggerFactory.getLogger(...)`: it won't sit under the `plugin.` prefix and
178
+ the host can't attribute it.
179
+
180
+ The host stores `info` and above, surfaces `warn` and above in the admin log viewer, and **rate-limits**
181
+ this path. On the frontend, `ctx.log` replaces POSTing to `/api/plugins/{id}/log` through `ctx.api`.
182
+ `FakePluginContext.logger()` returns a `RecordingLogger`, so a test asserts on logging the way it asserts
183
+ on stored documents; `makeMockCtx` collects `ctx.logs`.
184
+
185
+ ## Consent & the manifest (core-owned schema)
186
+
187
+ Core runs **banner-free** — strictly necessary cookies need no consent. The only reason a visitor sees a
188
+ consent prompt is a plugin that asked for one, so treat it as the cost it is.
189
+
190
+ ### `ctx.consent` (frontend)
191
+
192
+ ```ts
193
+ if (ctx.consent.has('analytics')) mountChart(root);
194
+ else button.onclick = async () => {
195
+ if (await ctx.consent.request('analytics')) mountChart(root); // from a user gesture, never on mount
196
+ };
197
+
198
+ const off = ctx.consent.onChange(() => rerender()); // fires on every change
199
+ return off; // detach on cleanup
200
+ ```
201
+
202
+ `request(category)` opens the host's settings for that one purpose and resolves with the visitor's answer
203
+ (`false` if dismissed). `granted()` lists everything currently granted. `onChange` fires on **every**
204
+ change — including a withdrawal made in the settings page while you are mounted — and returns an
205
+ unsubscribe. Since 0.4.0 `filter`, `route`, `locale` and `player.on` return one too.
206
+
207
+ **Services describe, categories decide.** The manifest is per-service (`provider`, `privacyUrl`,
208
+ `thirdCountryTransfer`, each `storage[]` item) but every `ctx.consent` method takes a **category**. The
209
+ per-service detail exists so the notice can name who stores what for how long; what the visitor toggles is
210
+ the category. So if two plugins each declare an `analytics` service with different providers, the visitor
211
+ sees **one** decision listing both plugins, and granting it grants both — there is no way to accept one
212
+ provider and refuse the other. If you need your own services to be refusable independently, declare them
213
+ under **different categories** (a plugin-declared category is fine; the shell just has no translated label
214
+ for it). That is the only lever the contract gives you.
215
+
216
+ **`necessary` is never asked about.** `has('necessary')` is always `true` and the host never prompts for
217
+ it — it is the category the core itself uses, and a banner-free site stays banner-free. Declaring a
218
+ service `necessary` means "this loads unconditionally"; use it only for what genuinely cannot be refused.
219
+
220
+ **Concurrent `request()` calls.** A page of plugin tiles will produce them, so the contract is explicit:
221
+ there is **one consent surface host-wide** (a second call joins the one in flight rather than opening
222
+ another); the visitor decides **all** categories in one interaction, so other categories may move too;
223
+ every call resolves exactly once and is never dropped; and grants are **shared across plugins** — another
224
+ plugin's accepted `request('analytics')` resolves yours too. Don't serialize calls or build a queue, and
225
+ re-read `has(...)` after a change instead of caching what a request resolved with.
226
+
227
+ ### `consent.services[]` in `plugin.json`
228
+
229
+ The manifest type is **core-owned** — the SDK has no manifest type and will not grow one — but this is
230
+ where plugin authors look, so the required shape is documented here. **From `0.4.0` the legacy
231
+ `{ "categories": [...], "externalSources": [...] }` form is rejected** and the plugin will not load.
232
+
233
+ ```json
234
+ "consent": {
235
+ "services": [{
236
+ "id": "plausible",
237
+ "name": "Plausible Analytics",
238
+ "provider": "Plausible Insights OÜ",
239
+ "category": "necessary | functional | analytics | <plugin-declared>",
240
+ "privacyUrl": "https://plausible.io/privacy",
241
+ "hosts": ["https://plausible.example"],
242
+ "thirdCountryTransfer": false,
243
+ "storage": [
244
+ { "name": "plausible_ignore", "type": "cookie | localStorage | sessionStorage",
245
+ "purpose": "Remembers that you opted out of statistics", "duration": "persistent | session | 12 months" }
246
+ ]
247
+ }]
248
+ }
249
+ ```
250
+
251
+ | Field | Meaning |
252
+ |---|---|
253
+ | `id` | Stable identifier for this service within your plugin. |
254
+ | `name` | The service as a visitor would recognise it, e.g. "Plausible Analytics". |
255
+ | `provider` | The **legal entity** operating it — the company name, not your plugin's. |
256
+ | `category` | The consent category the service falls under; what `ctx.consent.has(...)` gates on. **`necessary` is always granted and never prompted for.** Two services sharing a category are decided together. |
257
+ | `privacyUrl` | The provider's own privacy policy. |
258
+ | `hosts` | Every origin the service is contacted on. **Also the CSP allow-list** — see below. |
259
+ | `thirdCountryTransfer` | Whether personal data leaves the EU/EEA. |
260
+ | `storage[]` | Each item the service stores on the device: `name`, `type`, `purpose`, `duration`. |
261
+
262
+ **Why the shape changed.** A cookie notice that satisfies §25 TDDDG / Art. 5(3) ePD has to name each
263
+ stored item, what it is for, how long it lasts, who the provider is, and whether data leaves the country.
264
+ Category slugs and bare hostnames cannot produce that — and they force the notice to talk about "plugins"
265
+ to visitors who only care about cookies and named companies.
266
+
267
+ **`hosts` doubles as the CSP allow-list.** Core widens `script-src` / `frame-src` / `connect-src` by
268
+ exactly these origins. An origin you did not declare **stays blocked even after consent is given** — if a
269
+ third-party embed silently fails to load with consent granted, an undeclared host is the first thing to
270
+ check.
271
+
272
+ **Authoring help:** the SDK exports `ConsentServiceDeclaration` (and `ConsentStorageDeclaration`) as a
273
+ **documentation-only** type. Nothing in the SDK reads `plugin.json` or validates it — the host owns and
274
+ enforces the manifest — but typing a literal against it gives you completion and catches a typo before
275
+ core rejects the plugin at load, which is otherwise your first feedback. If the type and core ever
276
+ disagree, **core wins**; treat the mismatch as an SDK bug. The manifest as a whole stays core-owned.
277
+
113
278
  ## Releasing (maintainers)
114
279
  Publishing is automated and fires on a **published GitHub Release**, not on PR merge
115
280
  (`.github/workflows/release.yml`).
package/dist/index.d.ts CHANGED
@@ -11,12 +11,30 @@
11
11
  * The plugin contract version (SemVer).
12
12
  *
13
13
  * Mirror of the Java `dev.mosaicast.plugin.api.PlatformApi.VERSION` constant and the npm package
14
- * version. The host rejects a plugin whose manifest `platformApi` is incompatible with this value
15
- * (ARCHITECTURE §7.2). **These move together — a breaking change is a major bump.**
14
+ * version — **these move together**, and CI fails the build if they drift.
15
+ *
16
+ * The host compares a plugin manifest's `platformApi` against this value on **`major.minor` exactly** and
17
+ * rejects a mismatch at startup (ARCHITECTURE §7.2). While the SDK is pre-1.0 a breaking change is
18
+ * therefore a *minor* bump; from `1.0.0` on, breaking means major.
16
19
  */
17
- export declare const PLATFORM_API_VERSION: '0.3.0';
20
+ export declare const PLATFORM_API_VERSION: '0.4.0';
18
21
  /** A user's role (ARCHITECTURE §8.5). Anonymous visitors have no role (`user` is `null`). */
19
22
  export type Role = 'admin' | 'podcaster' | 'fan';
23
+ /**
24
+ * The severity of a {@link PluginContext.log} call.
25
+ *
26
+ * Mirrors the SLF4J levels a plugin backend gets through the Java `ctx.logger()`, minus `trace` — a
27
+ * browser has no use for it, and the host would drop it anyway.
28
+ */
29
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
30
+ /**
31
+ * Unsubscribes a callback registered through one of the `onChange` handles.
32
+ *
33
+ * Call it when your component tears down — the cleanup callback returned from your
34
+ * {@link MosaicastRender} is the natural place. A subscription that outlives its render keeps a
35
+ * reference to a detached DOM tree and keeps firing into it.
36
+ */
37
+ export type Unsubscribe = () => void;
20
38
  /** The level plus concrete entity a view is scoped to (ARCHITECTURE §6.1). */
21
39
  export interface Scope {
22
40
  /** The scope level. */
@@ -200,15 +218,230 @@ export interface DisplaySnapshot {
200
218
  * @returns the resolved artwork URL, or `undefined` when neither the episode nor the feed declares one
201
219
  */
202
220
  export declare function resolveArtwork(snapshot: DisplaySnapshot): string | undefined;
221
+ /**
222
+ * The consent gate for anything that stores data on the visitor's device or talks to a third party
223
+ * (ARCHITECTURE §12.5).
224
+ *
225
+ * Consent is **denied until granted**. The core itself runs banner-free — strictly necessary cookies need
226
+ * no consent — so the only reason a visitor ever sees a consent prompt is a plugin that asked for one.
227
+ * Treat that as the cost it is.
228
+ *
229
+ * The categories are declared per service in your manifest's `consent.services[]` (`necessary`,
230
+ * `functional`, `analytics`, or one you declare yourself). The host builds the cookie notice from those
231
+ * declarations, and the `hosts` you list there are also the CSP allow-list: **an origin you did not
232
+ * declare stays blocked even after consent is given.**
233
+ *
234
+ * ## The decision is per category, not per service
235
+ *
236
+ * `consent.services[]` is richly per-service — `provider`, `privacyUrl`, `thirdCountryTransfer`, each
237
+ * `storage[]` item — but every method here takes a **category**. That asymmetry is deliberate and worth
238
+ * stating plainly, because the schema implies otherwise: **services describe, categories decide.** The
239
+ * per-service detail exists so the notice can name who stores what for how long; the thing a visitor
240
+ * actually toggles is the category.
241
+ *
242
+ * The consequence: if two plugins each declare a service under `analytics` with different providers, the
243
+ * visitor sees **one** decision listing both plugins, and granting it grants both. There is no way to
244
+ * consent to one provider and withhold the other. Do not build a UI that implies otherwise, and don't
245
+ * assume `has('analytics')` says anything about *which* provider was accepted — it says the category was.
246
+ *
247
+ * If you need a visitor to be able to accept one of your services and refuse another, declare them under
248
+ * **different categories** (a plugin-declared category is allowed, it just has no translated label in the
249
+ * shell). That is the only lever the contract gives you.
250
+ *
251
+ * ## `necessary` is never asked about
252
+ *
253
+ * `has('necessary')` is **always `true`** and the host never prompts for it — it is the category the core
254
+ * itself uses, and a banner-free site stays banner-free. Declaring a service as `necessary` therefore
255
+ * means "this loads unconditionally"; use it only for what genuinely cannot be refused, and expect
256
+ * `request('necessary')` to resolve `true` without showing the visitor anything.
257
+ *
258
+ * @example The click-to-load placeholder this exists for
259
+ * ```ts
260
+ * function render({ ctx, root }: { ctx: PluginContext; root: HTMLElement }) {
261
+ * if (ctx.consent.has('analytics')) {
262
+ * mountChart(root);
263
+ * return;
264
+ * }
265
+ *
266
+ * const button = document.createElement('button');
267
+ * button.textContent = 'Load chart (sets a cookie)';
268
+ * button.onclick = async () => {
269
+ * if (await ctx.consent.request('analytics')) mountChart(root);
270
+ * };
271
+ * root.append(button);
272
+ *
273
+ * // Someone may flip the purpose in the settings page while this is mounted.
274
+ * return ctx.consent.onChange(() => rerender());
275
+ * }
276
+ * ```
277
+ */
278
+ export interface ConsentApi {
279
+ /**
280
+ * Whether the visitor has granted a consent category.
281
+ *
282
+ * Check it before every load of a consent-requiring resource, not once at startup — consent can be
283
+ * withdrawn mid-session from the host's settings page.
284
+ *
285
+ * @param category the consent category, as declared on a service in your manifest
286
+ * @returns `true` if the category is currently granted
287
+ */
288
+ has(category: string): boolean;
289
+ /**
290
+ * Every category currently granted.
291
+ *
292
+ * For rendering a summary ("statistics: on, embeds: off"). Prefer {@link has} for a single gate.
293
+ *
294
+ * Includes `necessary`, which is always granted. These are categories, not service ids — a granted
295
+ * category covers every service declared under it, across all plugins.
296
+ *
297
+ * @returns the granted category names, in no guaranteed order
298
+ */
299
+ granted(): string[];
300
+ /**
301
+ * Asks the visitor to grant a category, and resolves with their answer.
302
+ *
303
+ * The host opens its consent settings for this one purpose and resolves once the visitor decides;
304
+ * dismissing it resolves `false`. **Call this from a user gesture** — the click on your placeholder —
305
+ * and never on mount: an unprompted call turns a banner-free site into one with a banner, which is
306
+ * exactly what §12.5 is arranged to avoid.
307
+ *
308
+ * Resolving `true` means the category is granted from that moment on; it does not load anything for
309
+ * you. Load the resource yourself afterwards.
310
+ *
311
+ * ## What a caller may rely on
312
+ *
313
+ * A page full of plugin tiles will produce concurrent calls, so the contract is explicit about them:
314
+ *
315
+ * - **There is one consent surface, host-wide.** Calling this does not open a dialog of your own, and a
316
+ * second call while one is already open does not open a second — it joins the one in flight. You are
317
+ * asking the host to surface *its* settings, not opening a modal.
318
+ * - **The visitor decides every category at once.** The host's settings cover all declared categories,
319
+ * so one interaction can change several. Your promise still resolves with the state of *the category
320
+ * you asked for* — but other categories may have moved too, which is why {@link onChange} fires for
321
+ * every change rather than only yours.
322
+ * - **Every call resolves exactly once, and always.** Concurrent calls are never dropped or left
323
+ * pending: each resolves when the visitor completes the decision, including calls made while the
324
+ * surface was already open.
325
+ * - **Grants are shared across plugins.** If another plugin's `request('analytics')` is what the
326
+ * visitor accepted, your pending `request('analytics')` resolves `true` too — the decision is per
327
+ * category and site-wide, not per plugin (see the note on granularity above).
328
+ *
329
+ * So: don't serialize your calls, don't build a queue, and don't assume the visitor only answered you.
330
+ * Re-read {@link has} after any change rather than caching what a request resolved with.
331
+ *
332
+ * @param category the consent category to ask for
333
+ * @returns whether the category is granted after the visitor decided
334
+ */
335
+ request(category: string): Promise<boolean>;
336
+ /**
337
+ * Subscribes to consent changes.
338
+ *
339
+ * Fires on **every** change to any category — including one made in the settings page while your
340
+ * component is mounted, and including a withdrawal. It carries no payload: re-read {@link has} or
341
+ * {@link granted} for the current state.
342
+ *
343
+ * @param cb called after each change
344
+ * @returns an {@link Unsubscribe} — return it from your render callback so the subscription dies with
345
+ * the component
346
+ */
347
+ onChange(cb: () => void): Unsubscribe;
348
+ }
349
+ /**
350
+ * One item a service stores on the visitor's device, as declared in `plugin.json`.
351
+ *
352
+ * Part of {@link ConsentServiceDeclaration} — see the caveats there before using either type.
353
+ */
354
+ export interface ConsentStorageDeclaration {
355
+ /** The cookie or storage key exactly as it appears on the device, e.g. `plausible_ignore`. */
356
+ name: string;
357
+ /** Where it is stored. */
358
+ type: 'cookie' | 'localStorage' | 'sessionStorage';
359
+ /** What it is for, in language a visitor reads — not an internal description. */
360
+ purpose: string;
361
+ /** How long it lasts: `session`, `persistent`, or a human duration such as `12 months`. */
362
+ duration: string;
363
+ }
364
+ /**
365
+ * The shape of one entry in your manifest's `consent.services[]`.
366
+ *
367
+ * **This type is documentation, not enforcement.** The manifest is owned and validated by the host — the
368
+ * SDK does not read `plugin.json`, and nothing here runs at build or load time. It exists so an author
369
+ * writing the declaration gets IDE completion and catches a typo before core rejects the plugin at load,
370
+ * which is otherwise the first feedback you get. Two consequences worth knowing:
371
+ *
372
+ * - **The host is authoritative.** If this type and core disagree, core wins. It is kept in step by hand,
373
+ * so treat a mismatch as a bug in the SDK rather than permission to ignore the host.
374
+ * - **It is not a manifest type.** The manifest as a whole stays core-owned and the SDK will not grow one;
375
+ * this covers a single nested shape that got deep enough in 0.4.0 to be worth typing.
376
+ *
377
+ * Use it by typing a literal you keep next to your manifest, or as a reference while writing the JSON:
378
+ *
379
+ * ```ts
380
+ * const services: ConsentServiceDeclaration[] = [{
381
+ * id: 'plausible',
382
+ * name: 'Plausible Analytics',
383
+ * provider: 'Plausible Insights OÜ',
384
+ * category: 'analytics',
385
+ * privacyUrl: 'https://plausible.io/privacy',
386
+ * hosts: ['https://plausible.example'],
387
+ * thirdCountryTransfer: false,
388
+ * storage: [{
389
+ * name: 'plausible_ignore', type: 'localStorage',
390
+ * purpose: 'Remembers that you opted out of statistics', duration: 'persistent',
391
+ * }],
392
+ * }];
393
+ * ```
394
+ */
395
+ export interface ConsentServiceDeclaration {
396
+ /** Stable identifier for this service within your plugin. */
397
+ id: string;
398
+ /** The service as a visitor would recognise it, e.g. `Plausible Analytics`. */
399
+ name: string;
400
+ /** The **legal entity** operating the service — the company, not your plugin. */
401
+ provider: string;
402
+ /**
403
+ * The consent category this service falls under, and therefore what {@link ConsentApi.has} gates on.
404
+ *
405
+ * `necessary` is never prompted for and always granted. Remember that the category — not the service —
406
+ * is what the visitor decides: two services sharing a category are accepted or refused together.
407
+ */
408
+ category: 'necessary' | 'functional' | 'analytics' | (string & {});
409
+ /** The provider's own privacy policy. */
410
+ privacyUrl: string;
411
+ /**
412
+ * Every origin the service is contacted on, scheme included (`https://plausible.example`).
413
+ *
414
+ * **Also the CSP allow-list**: core widens `script-src`/`frame-src`/`connect-src` by exactly these, so
415
+ * an undeclared or bare-hostname origin stays blocked even after consent is given.
416
+ */
417
+ hosts: string[];
418
+ /** Whether personal data leaves the EU/EEA. */
419
+ thirdCountryTransfer: boolean;
420
+ /** Each item the service stores on the visitor's device. */
421
+ storage: ConsentStorageDeclaration[];
422
+ }
203
423
  /**
204
424
  * Everything a frontend plugin is given, set by the host on the mounted custom element
205
425
  * (ARCHITECTURE §7.5). This is the **entire** interface a plugin author must learn.
206
426
  */
207
427
  export interface PluginContext {
208
- /** The scope this plugin instance is mounted in. */
428
+ /**
429
+ * The scope this plugin instance is mounted in. For the `episode` scope, `scope.id` is the episode's
430
+ * **public slug** (e.g. `the-sample-cast-s01e06`) — the same id used in its URL and as the doc-store
431
+ * partition (`data/episode/{slug}/…`), not the internal UUID.
432
+ */
209
433
  scope: Scope;
210
- /** The `EpisodeRef` ids in scope, resolved (and access-filtered) by the host. */
434
+ /**
435
+ * The episode ids in scope, resolved (and access-filtered) by the host — the public **slugs** (the same
436
+ * values used in URLs and doc-store paths). Pair with {@link episodeLabels} for display.
437
+ */
211
438
  episodes: string[];
439
+ /**
440
+ * Human-readable labels for {@link episodes}, keyed by slug (e.g. `"S01E06 · The Lighthouse…"`). The host
441
+ * builds them from the feed's season/episode + title; use them in pickers so users see titles, not slugs.
442
+ * Optional: absent (or partial) when the host does not provide a label for a given episode.
443
+ */
444
+ episodeLabels?: Record<string, string>;
212
445
  /** Present on the `episode` scope: lifecycle status of the current episode. */
213
446
  episode?: {
214
447
  status: 'PLANNED' | 'PUBLISHED' | 'WITHDRAWN';
@@ -224,31 +457,68 @@ export interface PluginContext {
224
457
  * uses via `ctx.store()`. See {@link PluginApiClient} for the endpoint shape and access rules.
225
458
  */
226
459
  api: PluginApiClient;
227
- /** Cookie/consent gate for third-party resources (ARCHITECTURE §12.5). */
228
- consent: {
229
- has(cat: string): boolean;
230
- onChange(cb: () => void): void;
231
- };
232
- /** Read-only access to the host's URL filter state (ARCHITECTURE §6.1). */
460
+ /**
461
+ * Writes one line to the host's log, attributed to this plugin.
462
+ *
463
+ * The counterpart of the backend's `ctx.logger()`, and the **only** supported way for a frontend
464
+ * component to log to the host — do not POST to `/api/plugins/{id}/log` through {@link api}, which is a
465
+ * data endpoint. The host attributes the entry to your plugin, stores `info` and above, surfaces `warn`
466
+ * and above in the admin log viewer, and rate-limits this path: a render loop logging per frame will be
467
+ * throttled, not stored.
468
+ *
469
+ * Messages are read by site operators, not by you — they land next to core's own output. Keep them
470
+ * short, and keep personal data out of them.
471
+ *
472
+ * @param level the severity
473
+ * @param message the message; already-formatted, since there is no placeholder syntax here
474
+ *
475
+ * @example
476
+ * ```ts
477
+ * ctx.log('warn', `no board for episode ${ctx.scope.id}`);
478
+ * ```
479
+ */
480
+ log(level: LogLevel, message: string): void;
481
+ /** Cookie/consent gate for third-party resources — see {@link ConsentApi} (ARCHITECTURE §12.5). */
482
+ consent: ConsentApi;
483
+ /**
484
+ * Read-only access to the host's URL filter state (ARCHITECTURE §6.1).
485
+ *
486
+ * Plugins *consume* filters, they never define them: the axes (season, tags, sorting) belong to the
487
+ * host and live in the URL. `onChange` returns an {@link Unsubscribe}.
488
+ */
233
489
  filter: {
234
490
  current(): FilterState;
235
- onChange(cb: (f: FilterState) => void): void;
491
+ onChange(cb: (f: FilterState) => void): Unsubscribe;
236
492
  };
237
- /** Player position + control, for sync plugins (ARCHITECTURE §6.5). */
493
+ /**
494
+ * Player position + control, for sync plugins (ARCHITECTURE §6.5).
495
+ *
496
+ * `on` returns an {@link Unsubscribe} — player events outlive a single render, so detaching matters
497
+ * here more than anywhere else on this context.
498
+ */
238
499
  player: {
239
500
  currentTime(): number;
240
501
  seekTo(s: number): void;
241
- on(ev: string, cb: (...args: unknown[]) => void): void;
502
+ on(ev: string, cb: (...args: unknown[]) => void): Unsubscribe;
242
503
  };
243
- /** The subpath under `/p/<pluginId>/`, for deep-linkable plugin content (ARCHITECTURE §6.4). */
504
+ /**
505
+ * The subpath under `/p/<pluginId>/`, for deep-linkable plugin content (ARCHITECTURE §6.4).
506
+ *
507
+ * `onChange` returns an {@link Unsubscribe}.
508
+ */
244
509
  route: {
245
510
  path: string;
246
- onChange(cb: (p: string) => void): void;
511
+ onChange(cb: (p: string) => void): Unsubscribe;
247
512
  };
248
- /** The active UI locale (ARCHITECTURE §12.7). */
513
+ /**
514
+ * The active UI locale (ARCHITECTURE §12.7).
515
+ *
516
+ * `onChange` returns an {@link Unsubscribe}. {@link createPluginI18n} subscribes to this for you and
517
+ * hands back a `dispose` to undo it.
518
+ */
249
519
  locale: {
250
520
  current(): string;
251
- onChange(cb: (l: string) => void): void;
521
+ onChange(cb: (l: string) => void): Unsubscribe;
252
522
  };
253
523
  /** Core listening progress in seconds, or `null` if unknown (ARCHITECTURE §6.5). */
254
524
  progress: {
@@ -306,6 +576,14 @@ export interface PluginI18n {
306
576
  t(key: string, params?: Record<string, string | number>): string;
307
577
  /** The currently active locale code. */
308
578
  readonly locale: string;
579
+ /**
580
+ * Detaches the translator from `ctx.locale`.
581
+ *
582
+ * A translator subscribes to locale changes for its whole life. Call this when the component that owns
583
+ * it goes away — from the cleanup callback your render returns — or the subscription keeps a dead
584
+ * translator alive.
585
+ */
586
+ dispose(): void;
309
587
  }
310
588
  /**
311
589
  * Creates a plugin-local translator bound to the host's active locale (ARCHITECTURE §12.7).
@@ -314,6 +592,9 @@ export interface PluginI18n {
314
592
  * change — the same convention as the shell, no extra i18n library required. English (`en`) is the
315
593
  * source language and the fallback.
316
594
  *
595
+ * The translator subscribes to `locale.onChange` for as long as it lives — call {@link PluginI18n.dispose}
596
+ * from your render's cleanup callback when the component goes away.
597
+ *
317
598
  * @param catalogs catalogs keyed by locale code
318
599
  * @param locale the host locale handle, i.e. `ctx.locale`; its `onChange` drives re-selection
319
600
  * @returns a translator whose `t` and `locale` reflect the currently active locale
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -13,10 +13,13 @@
13
13
  * The plugin contract version (SemVer).
14
14
  *
15
15
  * Mirror of the Java `dev.mosaicast.plugin.api.PlatformApi.VERSION` constant and the npm package
16
- * version. The host rejects a plugin whose manifest `platformApi` is incompatible with this value
17
- * (ARCHITECTURE §7.2). **These move together — a breaking change is a major bump.**
16
+ * version — **these move together**, and CI fails the build if they drift.
17
+ *
18
+ * The host compares a plugin manifest's `platformApi` against this value on **`major.minor` exactly** and
19
+ * rejects a mismatch at startup (ARCHITECTURE §7.2). While the SDK is pre-1.0 a breaking change is
20
+ * therefore a *minor* bump; from `1.0.0` on, breaking means major.
18
21
  */
19
- export const PLATFORM_API_VERSION = '0.3.0';
22
+ export const PLATFORM_API_VERSION = '0.4.0';
20
23
  /**
21
24
  * The artwork to display for an episode: its own {@link DisplaySnapshot.imageUrl} if present, otherwise
22
25
  * the {@link DisplaySnapshot.feedImageUrl feed cover}, otherwise `undefined`.
@@ -120,19 +123,27 @@ function interpolate(template, params) {
120
123
  * change — the same convention as the shell, no extra i18n library required. English (`en`) is the
121
124
  * source language and the fallback.
122
125
  *
126
+ * The translator subscribes to `locale.onChange` for as long as it lives — call {@link PluginI18n.dispose}
127
+ * from your render's cleanup callback when the component goes away.
128
+ *
123
129
  * @param catalogs catalogs keyed by locale code
124
130
  * @param locale the host locale handle, i.e. `ctx.locale`; its `onChange` drives re-selection
125
131
  * @returns a translator whose `t` and `locale` reflect the currently active locale
126
132
  */
127
133
  export function createPluginI18n(catalogs, locale) {
128
134
  let active = locale.current();
129
- locale.onChange((l) => {
135
+ const unsubscribe = locale.onChange((l) => {
130
136
  active = l;
131
137
  });
132
138
  return {
133
139
  get locale() {
134
140
  return active;
135
141
  },
142
+ dispose() {
143
+ // Optional call: a host built against 0.3.x returns nothing here, and a translator that cannot be
144
+ // disposed is better than one that throws while a component is tearing down.
145
+ unsubscribe?.();
146
+ },
136
147
  t(key, params) {
137
148
  const template = catalogs[active]?.[key] ?? catalogs[SOURCE_LOCALE]?.[key] ?? key;
138
149
  return interpolate(template, params);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/testing.d.ts CHANGED
@@ -6,7 +6,14 @@
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
9
- import type { PluginApiClient, PluginContext, ThemeTokens } from './index.js';
9
+ import type { ConsentApi, LogLevel, PluginApiClient, PluginContext, ThemeTokens } from './index.js';
10
+ /** One recorded {@link PluginContext.log} call. */
11
+ export interface LogRecord {
12
+ /** The severity it was logged at. */
13
+ level: LogLevel;
14
+ /** The logged message. */
15
+ message: string;
16
+ }
10
17
  /** One recorded {@link MockApiClient} call. */
11
18
  export interface RecordedCall {
12
19
  /** HTTP method, lowercase. */
@@ -26,8 +33,66 @@ export interface MockApiClient extends PluginApiClient {
26
33
  */
27
34
  responses: Record<string, unknown>;
28
35
  }
36
+ /** A {@link ConsentApi} whose grants a test can drive. */
37
+ export interface MockConsent extends ConsentApi {
38
+ /** Grants a category and notifies subscribers. Granting an already-granted category does nothing. */
39
+ grant(category: string): void;
40
+ /**
41
+ * Withdraws a category and notifies subscribers, as the host's settings page can mid-session.
42
+ *
43
+ * Revoking `necessary` does nothing — the host cannot withdraw it either.
44
+ */
45
+ revoke(category: string): void;
46
+ /** Every category passed to {@link ConsentApi.request}, in order. */
47
+ readonly requests: string[];
48
+ /**
49
+ * What {@link ConsentApi.request} resolves with — i.e. what the visitor decides.
50
+ *
51
+ * `false` by default: the visitor says no. Set it to `true` to test the accepted path, which also
52
+ * grants the category as the host would.
53
+ */
54
+ autoGrantOnRequest: boolean;
55
+ }
56
+ /**
57
+ * Builds a {@link ConsentApi} for tests, **denying everything** unless told otherwise.
58
+ *
59
+ * Deny is the default on purpose. The host denies until granted, so a component written against a
60
+ * permissive mock is a component whose placeholder path was never exercised — and that path is the entire
61
+ * point of the consent contract (ARCHITECTURE §12.5).
62
+ *
63
+ * The one exception is `necessary`, which is granted from the start and cannot be revoked in the host
64
+ * either — it is the category the core itself uses.
65
+ *
66
+ * @param initial categories granted from the start, on top of `necessary`
67
+ * @returns a consent double with `grant`/`revoke` and a recorded `requests` list
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * const consent = makeMockConsent();
72
+ * const ctx = makeMockCtx({ consent });
73
+ *
74
+ * mount(ctx); // renders the click-to-load placeholder
75
+ * consent.autoGrantOnRequest = true;
76
+ * await clickPlaceholder();
77
+ * expect(consent.requests).toEqual(['analytics']);
78
+ *
79
+ * consent.revoke('analytics'); // fires onChange; the component should go back to the placeholder
80
+ * ```
81
+ */
82
+ export declare function makeMockConsent(initial?: string[]): MockConsent;
29
83
  /** Default theme tokens (neutral light values) used when a test does not override them. */
30
84
  export declare const DEFAULT_THEME: ThemeTokens;
85
+ /** A {@link PluginContext} wired for tests: the mock `api` and the recorded `log` calls are reachable. */
86
+ export type MockPluginContext = PluginContext & {
87
+ /** The recording client every `api` call goes through. */
88
+ api: MockApiClient;
89
+ /**
90
+ * Every {@link PluginContext.log} call, in order.
91
+ *
92
+ * Stays empty if you override `log` yourself — the recording lives in the default implementation.
93
+ */
94
+ logs: LogRecord[];
95
+ };
31
96
  /** Overrides accepted by {@link makeMockCtx}. */
32
97
  export interface MockCtxOverrides extends Partial<Omit<PluginContext, 'api'>> {
33
98
  /** Canned responses for the mock `api`, keyed as described on {@link MockApiClient.responses}. */
@@ -38,14 +103,18 @@ export interface MockCtxOverrides extends Partial<Omit<PluginContext, 'api'>> {
38
103
  /**
39
104
  * Builds a {@link PluginContext} wired with in-memory doubles for unit tests.
40
105
  *
41
- * Defaults: `site` scope, no episodes, anonymous user, all consent denied, empty filter, a player
106
+ * Defaults: `site` scope, no episodes, anonymous user, **all consent denied** (a {@link makeMockConsent}
107
+ * double — pass your own to drive grants), empty filter, a player
42
108
  * parked at 0s, empty route, `en` locale, unknown progress, and {@link DEFAULT_THEME}. Pass overrides to
43
- * change any field; the returned `api` is a {@link MockApiClient} recording every call.
109
+ * change any field; the returned `api` is a {@link MockApiClient} recording every call, and `logs`
110
+ * collects every `ctx.log(...)`.
111
+ *
112
+ * `episodeLabels` is deliberately **absent** by default. It is optional on the real context and the host
113
+ * may supply it partially, so a component that renders labels has to survive their absence — the mock
114
+ * makes you face that unless you pass labels in.
44
115
  *
45
116
  * @param overrides partial context plus optional `apiResponses`
46
- * @returns a full context whose `api` is a {@link MockApiClient}
117
+ * @returns a full context whose `api` is a {@link MockApiClient} and whose `log` calls are recorded
47
118
  */
48
- export declare function makeMockCtx(overrides?: MockCtxOverrides): PluginContext & {
49
- api: MockApiClient;
50
- };
119
+ export declare function makeMockCtx(overrides?: MockCtxOverrides): MockPluginContext;
51
120
  //# sourceMappingURL=testing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAEV,eAAe,EACf,aAAa,EACb,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC1C,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC;IAC/B;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAoBD,2FAA2F;AAC3F,eAAO,MAAM,aAAa,EAAE,WAS3B,CAAC;AAEF,iDAAiD;AACjD,MAAM,WAAW,gBAAiB,SAAQ,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IAC3E,kGAAkG;IAClG,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,kFAAkF;IAClF,GAAG,CAAC,EAAE,aAAa,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,SAAS,GAAE,gBAAqB,GAAG,aAAa,GAAG;IAAE,GAAG,EAAE,aAAa,CAAA;CAAE,CAoBpG"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,UAAU,EAEV,QAAQ,EACR,eAAe,EACf,aAAa,EACb,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,qCAAqC;IACrC,KAAK,EAAE,QAAQ,CAAC;IAChB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC1C,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC;IAC/B;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAuBD,0DAA0D;AAC1D,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,qGAAqG;IACrG,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;OAIG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,MAAM,EAAO,GAAG,WAAW,CAwCnE;AAED,2FAA2F;AAC3F,eAAO,MAAM,aAAa,EAAE,WAS3B,CAAC;AAEF,0GAA0G;AAC1G,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG;IAC9C,0DAA0D;IAC1D,GAAG,EAAE,aAAa,CAAC;IACnB;;;;OAIG;IACH,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB,CAAC;AAEF,iDAAiD;AACjD,MAAM,WAAW,gBAAiB,SAAQ,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IAC3E,kGAAkG;IAClG,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,kFAAkF;IAClF,GAAG,CAAC,EAAE,aAAa,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,SAAS,GAAE,gBAAqB,GAAG,iBAAiB,CA2B/E"}
package/dist/testing.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2026 The Mosaicast Authors
3
+ /** The unsubscribe returned by the inert `onChange` doubles — there is nothing to detach. */
4
+ const noop = () => { };
3
5
  function makeMockApi(responses) {
4
6
  const calls = [];
5
7
  const resolve = (method, path, body) => {
@@ -17,6 +19,72 @@ function makeMockApi(responses) {
17
19
  };
18
20
  return api;
19
21
  }
22
+ /**
23
+ * Builds a {@link ConsentApi} for tests, **denying everything** unless told otherwise.
24
+ *
25
+ * Deny is the default on purpose. The host denies until granted, so a component written against a
26
+ * permissive mock is a component whose placeholder path was never exercised — and that path is the entire
27
+ * point of the consent contract (ARCHITECTURE §12.5).
28
+ *
29
+ * The one exception is `necessary`, which is granted from the start and cannot be revoked in the host
30
+ * either — it is the category the core itself uses.
31
+ *
32
+ * @param initial categories granted from the start, on top of `necessary`
33
+ * @returns a consent double with `grant`/`revoke` and a recorded `requests` list
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const consent = makeMockConsent();
38
+ * const ctx = makeMockCtx({ consent });
39
+ *
40
+ * mount(ctx); // renders the click-to-load placeholder
41
+ * consent.autoGrantOnRequest = true;
42
+ * await clickPlaceholder();
43
+ * expect(consent.requests).toEqual(['analytics']);
44
+ *
45
+ * consent.revoke('analytics'); // fires onChange; the component should go back to the placeholder
46
+ * ```
47
+ */
48
+ export function makeMockConsent(initial = []) {
49
+ // `necessary` is what the core itself uses: always granted, never prompted for. Seeding it means a
50
+ // component gated on it behaves in tests the way it will in the host.
51
+ const granted = new Set(['necessary', ...initial]);
52
+ const subscribers = new Set();
53
+ const requests = [];
54
+ const notify = () => {
55
+ subscribers.forEach((cb) => cb());
56
+ };
57
+ return {
58
+ requests,
59
+ autoGrantOnRequest: false,
60
+ has: (category) => granted.has(category),
61
+ granted: () => [...granted],
62
+ request(category) {
63
+ requests.push(category);
64
+ if (this.autoGrantOnRequest && !granted.has(category)) {
65
+ granted.add(category);
66
+ notify();
67
+ }
68
+ return Promise.resolve(granted.has(category));
69
+ },
70
+ grant(category) {
71
+ if (!granted.has(category)) {
72
+ granted.add(category);
73
+ notify();
74
+ }
75
+ },
76
+ revoke(category) {
77
+ // The host cannot withdraw `necessary`, so neither can the double.
78
+ if (category !== 'necessary' && granted.delete(category)) {
79
+ notify();
80
+ }
81
+ },
82
+ onChange(cb) {
83
+ subscribers.add(cb);
84
+ return () => subscribers.delete(cb);
85
+ },
86
+ };
87
+ }
20
88
  /** Default theme tokens (neutral light values) used when a test does not override them. */
21
89
  export const DEFAULT_THEME = {
22
90
  bg: '#ffffff',
@@ -31,30 +99,43 @@ export const DEFAULT_THEME = {
31
99
  /**
32
100
  * Builds a {@link PluginContext} wired with in-memory doubles for unit tests.
33
101
  *
34
- * Defaults: `site` scope, no episodes, anonymous user, all consent denied, empty filter, a player
102
+ * Defaults: `site` scope, no episodes, anonymous user, **all consent denied** (a {@link makeMockConsent}
103
+ * double — pass your own to drive grants), empty filter, a player
35
104
  * parked at 0s, empty route, `en` locale, unknown progress, and {@link DEFAULT_THEME}. Pass overrides to
36
- * change any field; the returned `api` is a {@link MockApiClient} recording every call.
105
+ * change any field; the returned `api` is a {@link MockApiClient} recording every call, and `logs`
106
+ * collects every `ctx.log(...)`.
107
+ *
108
+ * `episodeLabels` is deliberately **absent** by default. It is optional on the real context and the host
109
+ * may supply it partially, so a component that renders labels has to survive their absence — the mock
110
+ * makes you face that unless you pass labels in.
37
111
  *
38
112
  * @param overrides partial context plus optional `apiResponses`
39
- * @returns a full context whose `api` is a {@link MockApiClient}
113
+ * @returns a full context whose `api` is a {@link MockApiClient} and whose `log` calls are recorded
40
114
  */
41
115
  export function makeMockCtx(overrides = {}) {
42
116
  const { apiResponses, api, ...rest } = overrides;
43
117
  const mockApi = api ?? makeMockApi(apiResponses ?? {});
44
118
  const filter = rest.filter?.current() ?? {};
119
+ const logs = [];
45
120
  const base = {
46
121
  scope: { type: 'site', id: 'main' },
47
122
  episodes: [],
48
123
  user: null,
49
124
  api: mockApi,
50
- consent: { has: () => false, onChange: () => { } },
51
- filter: { current: () => filter, onChange: () => { } },
52
- player: { currentTime: () => 0, seekTo: () => { }, on: () => { } },
53
- route: { path: '', onChange: () => { } },
54
- locale: { current: () => 'en', onChange: () => { } },
125
+ logs,
126
+ log: (level, message) => {
127
+ logs.push({ level, message });
128
+ },
129
+ consent: makeMockConsent(),
130
+ // The no-op handles still return a working unsubscribe, so a component that detaches on cleanup
131
+ // behaves the same against the mock as against the host.
132
+ filter: { current: () => filter, onChange: () => noop },
133
+ player: { currentTime: () => 0, seekTo: () => { }, on: () => noop },
134
+ route: { path: '', onChange: () => noop },
135
+ locale: { current: () => 'en', onChange: () => noop },
55
136
  progress: { get: () => Promise.resolve(null) },
56
137
  theme: DEFAULT_THEME,
57
138
  };
58
- return { ...base, ...rest, api: mockApi };
139
+ return { ...base, ...rest, api: mockApi, logs };
59
140
  }
60
141
  //# sourceMappingURL=testing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mosaicast/plugin-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Versioned plugin contract for Mosaicast: the frontend PluginContext types, a Web Component base helper, an i18n helper, plus a test kit under the /testing subpath.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {