@pie-players/pie-assessment-toolkit 0.3.35 → 0.3.36

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.
Files changed (2) hide show
  1. package/README.md +72 -0
  2. package/package.json +7 -7
package/README.md CHANGED
@@ -308,6 +308,78 @@ Use `subscribeSectionEvents(...)` when you need advanced/custom filtering mixes.
308
308
 
309
309
  To persist or snapshot an inactive section, use `coordinator.getSectionController({ sectionId, attemptId })` — that lookup is by id and is unaffected by the active-cohort behavior described above.
310
310
 
311
+ #### Migrating from `<0.3.35` (BREAKING — pre-1.0)
312
+
313
+ `0.3.35` is the first release where `subscribeSectionEvents` (and its two helper wrappers `subscribeItemEvents` / `subscribeSectionLifecycleEvents`) follows the toolkit's *active section cohort* automatically. The on-the-wire shape of the subscription args object changed.
314
+
315
+ If your host code looked like this:
316
+
317
+ ```typescript
318
+ const unsub = coordinator.subscribeItemEvents({
319
+ sectionId: 'section-1',
320
+ attemptId: 'attempt-1',
321
+ listener: handleEvent,
322
+ });
323
+ ```
324
+
325
+ Update it to drop `sectionId` / `attemptId`:
326
+
327
+ ```typescript
328
+ const unsub = coordinator.subscribeItemEvents({
329
+ listener: handleEvent,
330
+ });
331
+ ```
332
+
333
+ What this means in practice for typed integrations:
334
+
335
+ - **TypeScript breaking change.** `SectionEventSubscriptionArgs`, `SectionItemEventSubscriptionArgs`, and `SectionScopedEventSubscriptionArgs` no longer declare `sectionId?` / `attemptId?` properties. Any host that imports these arg types directly and passes those keys will fail to compile after upgrade. **Action required.**
336
+ - **Runtime is tolerant.** The runtime silently ignores extra unknown properties, so an untyped or lightly-typed call site that still passes `sectionId` / `attemptId` continues to work without source changes. The args have **no effect** at runtime — the subscription always follows the active cohort.
337
+ - **New precondition.** `subscribe*` now throws if no active section cohort exists. Subscribe **after** the first `getOrCreateSectionController(...)` resolves. Subscribing on `toolkit-ready` alone is no longer sufficient — though in practice the section player emits `toolkit-ready` *after* its first `getOrCreateSectionController(...)` resolves, so a `toolkit-ready` anchor is safe in section-player hosts.
338
+ - **Cohort migration is automatic.** If your wrapper previously re-subscribed on every navigation to keep listeners alive across sections, that wiring is no longer needed (and should be removed). A single subscribe call after the first controller-resolve is now enough — the listener migrates automatically and is replayed the new cohort's snapshot on every transition.
339
+ - **Watch for double-replay if you re-subscribe on every `toolkit-ready`.** Hosts that detached and re-subscribed on every `toolkit-ready` event (the correct pre-Phase D pattern, since each subscription was pinned to a `sectionId`) will now observe **two snapshot replays per navigation**: one delivered automatically when Phase D migrates the existing listener to the new active cohort, and a second when the manual re-subscribe attaches a fresh listener that replays again. Listener handlers that are not strictly idempotent will fire twice — analytics `pageAction`s, non-Set counters, side-effecting hydration. The fix is a one-line guard (`if (this.controllerUnsubscribe) return;`) so the subscribe runs only on the first `toolkit-ready`.
340
+ - **For intentionally-pinned subscriptions to inactive sections** (e.g. a host UI that wants to keep watching section A while the user views section B), the helper API does not support that pattern by design. Use `coordinator.getSectionController({ sectionId, attemptId })` and subscribe directly on the controller handle (`controller.subscribe?.(...)`) — that binding is pinned to one controller instance and does not migrate.
341
+
342
+ If your local types were hand-rolled structural copies of the public arg types (e.g. an Angular wrapper duplicating the shape rather than importing the package types), the legacy keys will compile but are dead code at runtime — recommend dropping them as part of the upgrade.
343
+
344
+ #### Pre-Phase D vs Phase D wrapper pattern
345
+
346
+ ```typescript
347
+ // BEFORE (pre-Phase D): rebind for every section change because the
348
+ // subscription was pinned to a sectionId.
349
+ public handleToolkitReady(event: Event): void {
350
+ const coordinator = (event as CustomEvent).detail?.coordinator;
351
+ if (!coordinator) return;
352
+ this.controllerUnsubscribe?.(); // detach prior pin
353
+ const itemUnsub = coordinator.subscribeItemEvents({
354
+ sectionId: this.sectionId,
355
+ listener: handleItemEvent,
356
+ });
357
+ const sectionUnsub = coordinator.subscribeSectionLifecycleEvents({
358
+ sectionId: this.sectionId,
359
+ listener: handleSectionEvent,
360
+ });
361
+ this.controllerUnsubscribe = () => { itemUnsub?.(); sectionUnsub?.(); };
362
+ }
363
+ ```
364
+
365
+ ```typescript
366
+ // AFTER (Phase D): subscribe once; the listener follows the active
367
+ // cohort across all subsequent navigation.
368
+ public handleToolkitReady(event: Event): void {
369
+ const coordinator = (event as CustomEvent).detail?.coordinator;
370
+ if (!coordinator) return;
371
+ this.toolkitCoordinator = coordinator;
372
+ if (this.controllerUnsubscribe) return; // already subscribed; do nothing on re-fire
373
+ const itemUnsub = coordinator.subscribeItemEvents({
374
+ listener: handleItemEvent,
375
+ });
376
+ const sectionUnsub = coordinator.subscribeSectionLifecycleEvents({
377
+ listener: handleSectionEvent,
378
+ });
379
+ this.controllerUnsubscribe = () => { itemUnsub?.(); sectionUnsub?.(); };
380
+ }
381
+ ```
382
+
311
383
  ### Option 2: Create Services Manually (Advanced)
312
384
 
313
385
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-assessment-toolkit",
3
- "version": "0.3.35",
3
+ "version": "0.3.36",
4
4
  "type": "module",
5
5
  "description": "PIE assessment toolkit: composable services + reference implementation for assessment players and tool coordination",
6
6
  "license": "MIT",
@@ -79,15 +79,15 @@
79
79
  "test": "bun test"
80
80
  },
81
81
  "dependencies": {
82
- "@pie-players/pie-calculator": "0.3.35",
83
- "@pie-players/pie-context": "0.3.35",
84
- "@pie-players/pie-players-shared": "0.3.35",
85
- "@pie-players/pie-tts": "0.3.35",
82
+ "@pie-players/pie-calculator": "0.3.36",
83
+ "@pie-players/pie-context": "0.3.36",
84
+ "@pie-players/pie-players-shared": "0.3.36",
85
+ "@pie-players/pie-tts": "0.3.36",
86
86
  "svelte": "^5.54.0"
87
87
  },
88
88
  "peerDependencies": {
89
- "@pie-players/pie-calculator-desmos": "0.3.35",
90
- "@pie-players/tts-client-server": "0.3.35"
89
+ "@pie-players/pie-calculator-desmos": "0.3.36",
90
+ "@pie-players/tts-client-server": "0.3.36"
91
91
  },
92
92
  "peerDependenciesMeta": {
93
93
  "@pie-players/pie-calculator-desmos": {