@pie-players/pie-section-player 0.3.72 → 0.3.74

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 (123) hide show
  1. package/README.md +198 -37
  2. package/dist/browser/assets/evaluation-worker-DkU_wGh3.js +155241 -0
  3. package/dist/browser/chunks/ItemToolBar-wr2myaah-2ce4b643.js +1 -0
  4. package/dist/browser/chunks/PUA-tLuTiQ7--f2b27953.js +1 -0
  5. package/dist/browser/chunks/ToolCoordinator-cc2dc319.js +1 -0
  6. package/dist/browser/chunks/accents-BTUYmhn9-cafeb96e.js +1 -0
  7. package/dist/browser/chunks/accents-b-i-CngHZsLL-e0eb98fd.js +1 -0
  8. package/dist/browser/chunks/arabic-BzEHMVZD-30ac1ade.js +1 -0
  9. package/dist/browser/chunks/arrows-VjxlU-jv-2e17c192.js +1 -0
  10. package/dist/browser/chunks/base-dfd67c41.js +1 -0
  11. package/dist/browser/chunks/braille-BeWC_Ruq-a3bb8ef9.js +1 -0
  12. package/dist/browser/chunks/braille-d-Dk6J96GO-d832bfbf.js +1 -0
  13. package/dist/browser/chunks/browser-a3c32178.js +36 -0
  14. package/dist/browser/chunks/calculator-element-271c142f.js +9 -0
  15. package/dist/browser/chunks/calligraphic-Cew9cU3x-04cf0735.js +1 -0
  16. package/dist/browser/chunks/cherokee-Cny3FYho-4da7d2f3.js +1 -0
  17. package/dist/browser/chunks/classPrivateFieldGet2-338e0179.js +1 -0
  18. package/dist/browser/chunks/custom-element-define-2a92deb0.js +1 -0
  19. package/dist/browser/chunks/cyrillic-CfCDulXU-5550df0b.js +1 -0
  20. package/dist/browser/chunks/cyrillic-ss-lU7bNGG9-12eb0188.js +1 -0
  21. package/dist/browser/chunks/defineProperty-a20a667d.js +1 -0
  22. package/dist/browser/chunks/devanagari-CDIz2h-r-18c38725.js +1 -0
  23. package/dist/browser/chunks/dist-41b39e7f.js +1 -0
  24. package/dist/browser/chunks/dist-59132a53.js +1 -0
  25. package/dist/browser/chunks/dist-60d7afef.js +1 -0
  26. package/dist/browser/chunks/dist-ca9b9622.js +1 -0
  27. package/dist/browser/chunks/double-struck-BpJEQk8y-500ce8dd.js +1 -0
  28. package/dist/browser/chunks/en-5ff22e17.js +1 -0
  29. package/dist/browser/chunks/es-a0ba6cb6.js +1 -0
  30. package/dist/browser/chunks/focus-trap-44913bf3.js +1 -0
  31. package/dist/browser/chunks/fraktur-rLEq9DEH-44cc14d8.js +1 -0
  32. package/dist/browser/chunks/greek-BlraJTyL-90ce8924.js +1 -0
  33. package/dist/browser/chunks/greek-ss-D-EN2PK0-91610739.js +1 -0
  34. package/dist/browser/chunks/hebrew-GiQZeybp-b583f555.js +1 -0
  35. package/dist/browser/chunks/latin-b-BhEnC7R2-d13e06ff.js +1 -0
  36. package/dist/browser/chunks/latin-bi-DqiIKv3U-1dec8705.js +1 -0
  37. package/dist/browser/chunks/latin-i-B1xtwaE1-21637466.js +1 -0
  38. package/dist/browser/chunks/latin-xfWFlLkD-5bcea5cf.js +1 -0
  39. package/dist/browser/chunks/logger-8b07fb72.js +1 -0
  40. package/dist/browser/chunks/marrows-DD1NgMQi-15b63755.js +1 -0
  41. package/dist/browser/chunks/math-DpWn1mbG-38eeb914.js +1 -0
  42. package/dist/browser/chunks/math-rendering-ready-9a0fd90a.js +1 -0
  43. package/dist/browser/chunks/mathjax-D2JHb_R_-64780dc5.js +232 -0
  44. package/dist/browser/chunks/module-0191e321.js +74 -0
  45. package/dist/browser/chunks/module-shim-58659a86.js +0 -0
  46. package/dist/browser/chunks/module-shim-b4470740.js +12 -0
  47. package/dist/browser/chunks/monospace-D116QD2a-98d484fd.js +1 -0
  48. package/dist/browser/chunks/monospace-ex-DcXpouKS-456b403c.js +1 -0
  49. package/dist/browser/chunks/monospace-l-Ct5wkMDs-f34a4a9e.js +1 -0
  50. package/dist/browser/chunks/mshapes-DCp86WSX-c0055952.js +1 -0
  51. package/dist/browser/chunks/nds-icon-button-d602f784.js +194 -0
  52. package/dist/browser/chunks/nl-NL-364d61b9.js +1 -0
  53. package/dist/browser/chunks/nl-NL-8b7e6d4b.js +1 -0
  54. package/dist/browser/chunks/overlay-containment-edaaff13.js +1 -0
  55. package/dist/browser/chunks/overlay-placement-e8058232.js +1 -0
  56. package/dist/browser/chunks/phonetics-BwOCw2Y9-d2e3af59.js +1 -0
  57. package/dist/browser/chunks/phonetics-ss-CRtMn02f-579b6135.js +1 -0
  58. package/dist/browser/chunks/pie-api-client-687c319a.js +1 -0
  59. package/dist/browser/chunks/pie-tool-calculator-cortex-0626bc24.js +9 -0
  60. package/dist/browser/chunks/pie-tool-calculator-geogebra-561236cb.js +9 -0
  61. package/dist/browser/chunks/pointer-gesture-ce75f0a7.js +1 -0
  62. package/dist/browser/chunks/preload-helper-6ad28da3.js +1 -0
  63. package/dist/browser/chunks/provider-4c704118.js +1 -0
  64. package/dist/browser/chunks/rolldown-runtime-1287ba31.js +1 -0
  65. package/dist/browser/chunks/runtime-C7H1jS4E-64772f5e.js +8413 -0
  66. package/dist/browser/chunks/sanitize-svg-icon-b9082bba.js +3 -0
  67. package/dist/browser/chunks/sans-serif-BYPZKlE9-2efdbcd9.js +1 -0
  68. package/dist/browser/chunks/sans-serif-b-B0kRKiC--b5530dce.js +1 -0
  69. package/dist/browser/chunks/sans-serif-bi-DXWul6mu-5370dba4.js +1 -0
  70. package/dist/browser/chunks/sans-serif-ex-Boux1kMP-aa178f31.js +1 -0
  71. package/dist/browser/chunks/sans-serif-i-676RFaSO-2db54474.js +1 -0
  72. package/dist/browser/chunks/sans-serif-r-DrN9xdZt-0fc72eea.js +1 -0
  73. package/dist/browser/chunks/script-0ohehnXc-526904f4.js +1 -0
  74. package/dist/browser/chunks/shapes-BG47NodJ-d59b0eca.js +1 -0
  75. package/dist/browser/chunks/src-BcptuNS5-ed25d7fd.js +191 -0
  76. package/dist/browser/chunks/sre-engine-wf9s43gg-f09289c5.js +53 -0
  77. package/dist/browser/chunks/symbols-BqnUxf_t-83087e87.js +1 -0
  78. package/dist/browser/chunks/symbols-b-i-eg9k5hmF-9c461ac2.js +1 -0
  79. package/dist/browser/chunks/tool-annotation-toolbar-997438b4.js +248 -0
  80. package/dist/browser/chunks/tool-answer-eliminator-35483abd.js +13 -0
  81. package/dist/browser/chunks/tool-color-scheme-b7de5cdf.js +26 -0
  82. package/dist/browser/chunks/tool-dictionary-cecff3b4.js +7 -0
  83. package/dist/browser/chunks/tool-graph-cb0338e8.js +11 -0
  84. package/dist/browser/chunks/tool-host-contract-b93b482c.js +1 -0
  85. package/dist/browser/chunks/tool-line-reader-f8ef3c6a.js +62 -0
  86. package/dist/browser/chunks/tool-periodic-table-43b133b8.js +153 -0
  87. package/dist/browser/chunks/tool-picture-dictionary-33721e91.js +4 -0
  88. package/dist/browser/chunks/tool-protractor-23f77028.js +17 -0
  89. package/dist/browser/chunks/tool-ruler-1d9a03a6.js +15 -0
  90. package/dist/browser/chunks/tool-tts-inline-98c2ce7a.js +179 -0
  91. package/dist/browser/chunks/variants-2LRNhOUP-966fc49d.js +1 -0
  92. package/dist/browser/chunks/versioned-tag-54a76be9.js +1 -0
  93. package/dist/browser/pie-section-player.js +1613 -0
  94. package/dist/browser.d.ts +10 -0
  95. package/dist/chunks/ItemToolBar-wr2myaah-1287ba31.js +28 -0
  96. package/dist/chunks/module-shim-58659a86.js +2129 -0
  97. package/dist/chunks/{nl-NL-9312c316.js → nl-NL-364d61b9.js} +8 -2
  98. package/dist/chunks/sre-engine-wf9s43gg-f09289c5.js +8 -0
  99. package/dist/components/shared/composition.d.ts +0 -1
  100. package/dist/components/shared/layout-host-methods.d.ts +400 -0
  101. package/dist/components/shared/missing-tool-providers.d.ts +16 -0
  102. package/dist/components/shared/player-preload.d.ts +14 -10
  103. package/dist/contracts/host-hooks.d.ts +4 -5
  104. package/dist/contracts/host-hooks.js +0 -0
  105. package/dist/contracts/layout-contract.js +0 -0
  106. package/dist/contracts/layout-parity-metadata.js +87 -0
  107. package/dist/contracts/public-events.d.ts +14 -15
  108. package/dist/contracts/public-events.js +12 -0
  109. package/dist/contracts/runtime-host-contract.js +0 -0
  110. package/dist/controllers/SectionController.d.ts +21 -0
  111. package/dist/controllers/toolkit-section-contracts.d.ts +1 -17
  112. package/dist/controllers/types.d.ts +17 -10
  113. package/dist/pie-section-player.js +19353 -37273
  114. package/dist/policies/index.d.ts +9 -2
  115. package/dist/policies/index.js +25 -0
  116. package/package.json +34 -29
  117. package/dist/chunks/module-a516757f.js +0 -73997
  118. package/dist/chunks/nl-NL-ef6dd604.js +0 -505
  119. package/dist/chunks/pie-api-client-22de9d9c.js +0 -161
  120. package/dist/chunks/rolldown-runtime-1287ba31.js +0 -20
  121. package/dist/chunks/sre-0162a7a9.js +0 -17637
  122. package/dist/contracts/stages.d.ts +0 -10
  123. package/dist/utils/z-index.d.ts +0 -5
package/README.md CHANGED
@@ -36,8 +36,11 @@ import "@pie-players/pie-section-player";
36
36
  If hosts need explicit registration control, keep using documented component
37
37
  entrypoints under `@pie-players/pie-section-player/components/*`.
38
38
 
39
- Standalone browser variants for this package are intentionally deferred; the
40
- current supported contract is the default bundler entrypoints under `dist`.
39
+ The entries are bundler-only: they import `@pie-players/pie-item-player`,
40
+ `@pie-players/pie-default-tool-loaders` and `speech-rule-engine`, with the
41
+ engine's JSON locale tables, by bare specifier and without import attributes.
42
+ Items render through the host's one `@pie-players/pie-item-player`, which this
43
+ package depends on at its own version.
41
44
 
42
45
  ## SectionController
43
46
 
@@ -59,13 +62,21 @@ the per-method contract.
59
62
 
60
63
  ```ts
61
64
  const host = document.querySelector("pie-section-player-splitpane") as any;
62
- const controller = await host.waitForSectionController?.(5000);
65
+ const controller = await host.waitForSectionController(5000);
63
66
  ```
64
67
 
65
68
  `waitForSectionController(timeoutMs)` resolves when the layout CE has wired
66
69
  its controller (the same anchor `pie-stage-change` reaches with
67
- `detail.stage === "engine-ready"`). Use `getSectionController()` if you've
68
- already passed the readiness anchor synchronously.
70
+ `detail.stage === "engine-ready"`), or with `null` once `timeoutMs` passes.
71
+ Use `getSectionController()` if you've already passed the readiness anchor
72
+ synchronously.
73
+
74
+ The layout elements define their host methods from the moment they are
75
+ created, so a host can call them before the element mounts. Until it mounts,
76
+ `waitForSectionController` waits, `getSectionController()` returns `null`, the
77
+ navigation methods return `false`, and `getSnapshot()` and the `select*` reads
78
+ return `null` (`pie-section-player-kernel-host` returns its bootstrapping
79
+ snapshot).
69
80
 
70
81
  ### Session lifecycle
71
82
 
@@ -97,11 +108,25 @@ policy](#js-api-example-for-advanced-host-policy). Key event types:
97
108
 
98
109
  - `item-selected` — item navigation within the current section.
99
110
  - `item-session-data-changed` / `item-session-meta-changed` — per-item
100
- session updates the persistence layer should observe.
101
- - `content-loaded` — passage / item / rubric finished loading.
111
+ session updates the persistence layer should observe. `sectionId` names the
112
+ section. `elementId` names the reporting element when the change carries one,
113
+ and `complete` is then that element's own. A commit carries
114
+ `sessionCommitReason` (see [Commit at a section
115
+ boundary](#commit-at-a-section-boundary)).
116
+ - `item-complete-changed` — an item's completion flipped. An item is complete
117
+ when every element that has reported its completion is complete. A report
118
+ without `elementId`, and a restored session's item-level `complete`, set the
119
+ item's completion directly. A session restored through `applySession`
120
+ without `complete` is complete when it holds a response. A passage's own
121
+ `session-changed` stays inside its shell: a passage holds no response.
122
+ - `content-loaded` — passage / item / rubric finished loading. Carries
123
+ `contentKind`, `itemId`, and `canonicalItemId`.
102
124
  - `section-loading-complete` — every renderable in the section finished
103
125
  loading.
104
126
  - `section-items-complete-changed` — aggregate completion flip.
127
+ - `section-error` — an item player failed (`source: "item-player"`), or the
128
+ section runtime did (`source: "section-runtime"`), a rejected element warmup
129
+ included, which leaves the items unmounted.
105
130
  - `section-navigation-change` — the controller's section identity changed.
106
131
  - `formative-try-recorded` — a learner checked an answer.
107
132
  - `formative-reveal-changed` — the reveal state changed without a Try: a learner
@@ -119,6 +144,20 @@ policy](#js-api-example-for-advanced-host-policy). Key event types:
119
144
  - `timed-media-invalid` — authored `timedMedia` that cannot be delivered; the
120
145
  section renders without cue behavior.
121
146
 
147
+ The helpers' default event sets leave out `formative-try-recorded`,
148
+ `formative-reveal-changed`, `section-mastery-changed` and
149
+ `timed-media-audio-started`. Name them in `subscribeItemEvents({ eventTypes })`,
150
+ or subscribe through `subscribeSectionEvents`.
151
+
152
+ Item-scoped events carry both id forms, and both are always populated.
153
+ `itemId` is the bare `item.id` — the form the map returned by
154
+ `getItemSessionsByItemId()` is keyed by, and the form `applySession` expects.
155
+ `canonicalItemId` is that item's adapter identifier, and falls back to `itemId`
156
+ when the section was not built from an adapter or no adapter ref matches it.
157
+ Correlate with formative policy and `runtime.player.resolveBackend` by
158
+ `canonicalItemId`; reach the session by `itemId`. `content-loaded` and
159
+ `item-player-error` both carry the pair.
160
+
122
161
  ### Formative delivery
123
162
 
124
163
  Set `formative` on the section and the player renders a check-answer control per
@@ -150,7 +189,7 @@ Read the resolved state from `getFormativeProjection()`, or off
150
189
  same handle:
151
190
 
152
191
  ```ts
153
- const controller = await host.waitForSectionController?.(5000);
192
+ const controller = await host.waitForSectionController(5000);
154
193
  // The learner's actions, budget-respecting.
155
194
  controller?.recordFormativeTry?.({ itemId, outcomes }); // outcomes from provideScore()
156
195
  controller?.retryFormativeItem?.({ itemId });
@@ -225,7 +264,7 @@ with its own player registers its own port instead, and that port outranks the
225
264
  card's discovery for as long as it is attached:
226
265
 
227
266
  ```ts
228
- const controller = await host.waitForSectionController?.(5000);
267
+ const controller = await host.waitForSectionController(5000);
229
268
  // No `renderableId`: a host is asserting its own port, where a renderable's adapter
230
269
  // has to name itself and is ignored unless it is the resolved stimulus.
231
270
  controller?.attachMediaTimeSource?.(myThirdPartyPort);
@@ -280,15 +319,27 @@ Render in HTML/Svelte/JSX:
280
319
  <pie-section-player-splitpane></pie-section-player-splitpane>
281
320
  ```
282
321
 
283
- Set complex values (`runtime`, `section`, `env`) as JS properties.
322
+ Set complex values (`runtime`, `section`) as JS properties. `env` is a
323
+ `runtime` field (`runtime.env`); the layout elements have no `env` property.
324
+
325
+ Set `runtime` no later than `section`. When the player builds its own
326
+ coordinator, the section's arrival rebuilds that coordinator from the current
327
+ `runtime`, so both can be set a tick after the element mounts. Once the section
328
+ has initialized, a change to `runtime.tools`, `runtime.assessmentId`,
329
+ `runtime.accessibility`, `runtime.lazyInit`, `runtime.toolConfigStrictness` or
330
+ `toolRegistry` is reported once in the console and does not reach that
331
+ coordinator; `runtime.tools.pnpEnforcement` still applies. Change a running
332
+ coordinator through the one `toolkit-ready` delivers, with
333
+ `updateToolConfig(...)` or `updateToolsPlacement(...)`, or pass your own as
334
+ `runtime.coordinator`.
284
335
 
285
336
  ## Runtime Inputs
286
337
 
287
- Both layout elements support:
338
+ The layout elements (`pie-section-player-splitpane`,
339
+ `pie-section-player-vertical`, `pie-section-player-tabbed`) support:
288
340
 
289
341
  - `runtime` (object): primary coordinator/tools/player runtime bundle
290
342
  - `section` (object): assessment section payload
291
- - `env` (object): optional top-level override for `{ mode, role }`
292
343
  - `debug` (boolean-like): verbose debug logging control (`"true"` enables, `"false"`/`"0"` disables)
293
344
  - `toolbar-position` (string): `top|right|bottom|left|none`
294
345
  - `narrow-layout-breakpoint` (number, optional): viewport width in px below which the layout collapses (split pane: single column; vertical: toolbar moves to top). Clamped to 400–2000; default 1100.
@@ -297,7 +348,12 @@ Both layout elements support:
297
348
  - `split-pane-min-region-width` (number, optional): splitpane minimum pane width in px. Clamped to 160–1200. Unset by default (split bounds stay at 20–80). (Ignored by vertical layout; supported for API parity.)
298
349
  - `split-pane-collapse-strategy` (string, optional): splitpane stacked-mode strategy. Supported values: `tabbed` (default) and `vertical`. (Ignored by vertical/tabbed layouts; supported for API parity.)
299
350
  - `base-heading-level` (number, optional): the heading level this player's card headings occupy, and the level every descendant's outline derives from. Clamped to 1–6; default 2. See [Heading structure](#heading-structure).
300
- - `show-toolbar` (boolean-like): accepts `true/false` and common string forms (`"true"`, `"false"`, `"1"`, `"0"`, `"yes"`, `"no"`)
351
+ - `show-toolbar` (boolean-like): accepts `true/false` and common string forms (`"true"`, `"false"`, `"1"`, `"0"`, `"yes"`, `"no"`); default `false`, so tools placed at `section` level render only when it is `true`
352
+ - `locale` (string, optional): BCP-47 locale for the player's own interface text. Mirrored onto `runtime.locale`, which wins when both are set. Unset renders `en-US`.
353
+ - `nds-icons` (boolean): opt in to NDS icon buttons. Mirrored onto `runtime.ndsIcons`, which wins when both are set.
354
+ - `tool-config-strictness` (string, optional): `off|warn|error` for tool-config validation; default `error`. `runtime.toolConfigStrictness` wins when both are set.
355
+ - `split-pane-initial-passage-width` (number, optional): splitpane passage pane width in percent at mount. Clamped to 20–80; default 50.
356
+ - `iife-bundle-host` (string, optional): bundle host for the IIFE element pre-warm when `runtime.player.loaderOptions.bundleHost` is unset.
301
357
  - Host extension props (JS properties only): `toolRegistry`, `sectionHostButtons`, `itemHostButtons`, `passageHostButtons`, `hooks`
302
358
 
303
359
  When viewport width is within the collapsed range (~1100px and below), splitpane and
@@ -534,9 +590,9 @@ Advanced runtime configuration is supplied through the `runtime` object. Set pla
534
590
 
535
591
  Hosts can configure item-player backend delivery once at the section-player
536
592
  runtime level. Section-player derives a concrete `backend` prop for each
537
- embedded item player before it renders the item. This is intended for hosts such
538
- as Quiz Engine that need server-processed PIE models and server scoring without
539
- querying every nested `<pie-item-player>`.
593
+ embedded item player before it renders the item. This is intended for hosts
594
+ that need server-processed PIE models and server scoring without querying every
595
+ nested `<pie-item-player>`.
540
596
 
541
597
  ```ts
542
598
  import type {
@@ -577,6 +633,10 @@ each embedded item player. Static delivery fields such as `baseUrl`, `auth`,
577
633
  `endpoints`, `assignmentId`, and `autosave` are preserved. Use `assignmentId`
578
634
  for shared attempt/player identity.
579
635
 
636
+ An enabled `backend.delivery` also sets `hosted: true` on each embedded item
637
+ player unless `runtime.player.hosted` is set, so the item players load no
638
+ element controllers and render the models the server returns.
639
+
580
640
  `runtime.player.resolveBackend` is a section-player-reserved key. It is called
581
641
  with `{ itemId, canonicalItemId, item, itemIndex, itemSession, sectionId, env,
582
642
  baseBackend }` and is stripped before props reach `<pie-item-player>`. Use it
@@ -594,12 +654,66 @@ backend config is preserved.
594
654
  This backend delivery config is separate from the element-loader backend used
595
655
  for IIFE/ESM bundle preloading.
596
656
 
657
+ ### Preloaded elements
658
+
659
+ With `runtime.playerType: "preloaded"` the section player loads no element
660
+ code. Its pre-warm asserts that every tag the section's items and passages name
661
+ is registered, so the host registers the elements first:
662
+
663
+ ```ts
664
+ import { registerPreloadedElements } from "@pie-players/pie-item-player/preloaded";
665
+ import * as delivery from "@pie-element/multiple-choice/browser/delivery";
666
+ import * as controller from "@pie-element/multiple-choice/browser/controller";
667
+ import manifest from "../package.json"; // pins "@pie-element/multiple-choice" exactly
668
+
669
+ registerPreloadedElements([
670
+ {
671
+ tag: "pie-element-multiple-choice",
672
+ package: "@pie-element/multiple-choice",
673
+ version: manifest.dependencies["@pie-element/multiple-choice"],
674
+ element: delivery,
675
+ controller,
676
+ },
677
+ ]);
678
+
679
+ sectionPlayer.runtime = { ...sectionPlayer.runtime, playerType: "preloaded" };
680
+ ```
681
+
682
+ - Register before the section player mounts. A tag missing at pre-warm leaves
683
+ the items unmounted and raises a non-recoverable `element-preload` framework
684
+ error.
685
+ - Install element packages with `npm install --save-exact`. npm otherwise saves
686
+ a caret range, which registration rejects as a `version`, and which a fresh
687
+ install can resolve to another release line: `^13.4.0-next.15` resolves to
688
+ the legacy `13.4.4`, which has no `./browser/*` modules.
689
+ - Install every pie-elements-ng package from one release, in one install from
690
+ the same dist-tag, and upgrade them together. Elements whose `./browser/*`
691
+ builds typeset on `window.MathJax` share the MathJax the first of them loads,
692
+ in the build and configuration of that element's release, so in a mixed set
693
+ an element can typeset with a MathJax it was not built for. Elements that
694
+ bundle their own MathJax share none
695
+ ([One MathJax version per page](../../docs/item-player/loading-strategies.md#one-mathjax-version-per-page)).
696
+ - Register one version per package. The players align every authored version
697
+ of a package to the registered one, and registering a second version throws.
698
+ - Register each package's `controller` unless the item players are hosted
699
+ (`runtime.player.hosted`, or an enabled `runtime.player.backend.delivery`).
700
+ A player that is not hosted runs `model()` in the browser and warns for each
701
+ tag registered without one.
702
+ - Only `@pie-element/*` builds from pie-elements-ng publish
703
+ `./browser/delivery` and `./browser/controller`.
704
+ - Under TypeScript, the `package.json` import needs `resolveJsonModule`, and a
705
+ package version that ships no declarations for `./browser/*` needs a
706
+ `declare module` shim for those subpaths.
707
+
708
+ See [`strategy="preloaded"`](../../docs/item-player/loading-strategies.md#strategypreloaded)
709
+ for the registration contract.
710
+
597
711
  ### Host-owned focus
598
712
 
599
713
  Section-player does not move focus on behalf of host-level affordances such
600
714
  as "Skip to Main", nor does it make passage/question containers tab stops.
601
715
  Hosts own page chrome, skip links, landmarks, and any special focus placement.
602
- For example, Quiz Engine's Fixed Player shell can focus its own
716
+ For example, a host shell can focus its own
603
717
  `main#main-content`; the next Tab then follows the browser's natural order
604
718
  into the first actionable control rendered inside the section player.
605
719
 
@@ -615,11 +729,16 @@ sequential keyboard navigation.
615
729
  </main>
616
730
  ```
617
731
 
618
- **Wired policy toggles.** Each `SectionPlayerPolicies` field has a real
619
- runtime effect; nothing in this surface is decorative.
732
+ **Policy fields.** The layout accepts a partial `policies` object: every
733
+ unset field, a missing section included, takes its value from
734
+ `DEFAULT_SECTION_PLAYER_POLICIES`.
620
735
 
621
- - `readiness.mode` (`"progressive"` | `"strict"`) — drives readiness-event
622
- emission via `createReadinessDetail` in `SectionPlayerLayoutKernel`.
736
+ - `readiness.mode` (`"progressive"` | `"strict"`) — the mode
737
+ `SectionPlayerLayoutKernel` passes to `createReadinessDetail`. Both modes
738
+ hold the `interactive` stage and `pie-loading-complete` until the section's
739
+ element pre-warm resolves and the item cards can mount. The kernel has no
740
+ later loading signal, so the two modes currently emit the same sequence.
741
+ Default: `"progressive"`.
623
742
  - `preload.enabled` — when `false`, `SectionItemsPane` short-circuits the
624
743
  section-level element warmup pipeline (`warmupSectionElements`). Items
625
744
  still mount and item-players register their own elements on demand. Use
@@ -727,7 +846,7 @@ Minimal pattern for package layout components:
727
846
 
728
847
  ```ts
729
848
  const host = document.querySelector("pie-section-player-splitpane") as any;
730
- const controller = await host.waitForSectionController?.(5000);
849
+ const controller = await host.waitForSectionController(5000);
731
850
  let sectionComplete = false;
732
851
 
733
852
  const unsubscribe = controller?.subscribe?.((event: any) => {
@@ -815,20 +934,21 @@ Canonical lifecycle stream (engine-routed, dispatched on the outer layout CE):
815
934
  - `pie-stage-change` — single typed transition stream covering
816
935
  `composed` → `engine-ready` → `interactive` → `disposed`. Payload is a
817
936
  `StageChangeDetail`.
818
- - `pie-loading-complete` — fires once per cohort when every item has
819
- loaded (kernel-routed; gated on `interactive`).
937
+ - `pie-loading-complete` — fires once per cohort, when the section's element
938
+ pre-warm resolves for the current composition and the item cards can mount
939
+ (kernel-routed).
820
940
  - `framework-error` — canonical error event for any failure crossing the
821
941
  framework boundary. Payload is a `FrameworkErrorModel`. The toolkit's
822
942
  package-internal `FrameworkErrorBus` and the `onFrameworkError`
823
943
  callback prop deliver each error exactly once regardless of wrapper
824
- depth. The `framework-error` *DOM event* on the outer layout CE
825
- also delivers each error exactly once: the kernel listener at
826
- `<pie-section-player-base>` intercepts the toolkit's bubbled emit
827
- and calls `event.stopPropagation()`, leaving only the canonical
828
- engine-bridge emit on the layout host. The single-emit contract is
829
- pinned by `tests/section-player-framework-error-dual-emit.test.ts`.
830
- Direct listeners attached to `<pie-assessment-toolkit>` itself
831
- still see the toolkit's own emit.
944
+ depth, including errors from a coordinator the host passes as
945
+ `runtime.coordinator`. The `framework-error` *DOM event* on the outer
946
+ layout CE also delivers each error exactly once: the kernel listener at
947
+ `<pie-section-player-base>` stops the toolkit's bubbled emit, and the
948
+ engine dispatches the error on the layout host without bubbling, so it
949
+ does not reach `document`. `tests/section-player-event-delivery.spec.ts`
950
+ pins these counts. Direct listeners attached to `<pie-assessment-toolkit>`
951
+ itself still see the toolkit's own emit.
832
952
 
833
953
  Callback-prop mirrors with two-tier precedence (`runtime.<key>` wins over
834
954
  the top-level prop):
@@ -847,9 +967,9 @@ Section-player owned instrumentation stream:
847
967
 
848
968
  - `pie-section-stage-change`
849
969
  - `pie-section-loading-complete`
850
- - `pie-section-session-changed`
851
- - `pie-section-composition-changed`
852
970
  - `pie-section-framework-error`
971
+ - `pie-section-element-preload-retry`
972
+ - `pie-section-element-preload-error`
853
973
 
854
974
  Build consumers against these canonical lifecycle events:
855
975
 
@@ -880,7 +1000,7 @@ Section session data can be managed either through persistence hooks or directly
880
1000
 
881
1001
  ```ts
882
1002
  const host = document.querySelector("pie-section-player-splitpane") as any;
883
- const controller = await host.waitForSectionController?.(5000);
1003
+ const controller = await host.waitForSectionController(5000);
884
1004
 
885
1005
  // Read current section session snapshot.
886
1006
  const currentSession = controller?.getSession?.();
@@ -908,11 +1028,52 @@ await controller?.updateItemSession?.("q1", {
908
1028
  The same controller snapshot is what the persistence strategy saves/loads.
909
1029
  When a controller is reused for the same `sectionId`/`attemptId`, `updateInput()` refreshes composition input while preserving in-memory section session data.
910
1030
 
1031
+ ### Commit at a section boundary
1032
+
1033
+ A delivery element coalesces its `session-changed` dispatch, so a response the
1034
+ learner has finished entering can still be pending when the section moves. The
1035
+ controller commits pending element sessions before item navigation, before
1036
+ `updateInput()` snapshots the session for a section swap, and before `persist()`.
1037
+ The player also commits when an item shell tears down and when the page goes
1038
+ hidden.
1039
+
1040
+ A committed response travels like any other. A raw element `session-changed`
1041
+ does not leave its `<pie-item-shell>`, which re-dispatches it as the normalized
1042
+ `item-session-changed` (`PIE_ITEM_SESSION_CHANGED_EVENT`); the toolkit then
1043
+ publishes the section's canonical `session-changed`. Both bubble through the
1044
+ layout element to `document`, and a listener on either receives each dispatch
1045
+ once.
1046
+ Host code persists from the controller's events or from its session snapshot.
1047
+
1048
+ Each of those events marks a commit with `sessionCommitReason`
1049
+ (`"teardown" | "navigate" | "page-hidden"`): `item-session-changed`, the
1050
+ toolkit's `session-changed`, and the controller's `item-session-data-changed`
1051
+ and `item-session-meta-changed`. A commit at a section swap or item navigation
1052
+ reports the item being left, which the host may already have moved past, so a
1053
+ handler that sets its current item or navigation state from these events leaves
1054
+ that state alone on a commit and persists the commit's session as usual. The
1055
+ controller's events also carry `sectionId`, the section the item belongs to.
1056
+
1057
+ Navigation inside a section keeps every item mounted, so nothing is discarded
1058
+ and the element's own debounce would complete on its own. The commit still runs
1059
+ there because the response belongs to the item being left: a host that persists
1060
+ on the navigation event, or a `persist()` that follows it, would otherwise
1061
+ snapshot a session the learner had already changed.
1062
+
1063
+ `SectionController` stays DOM-free; the player supplies the commit through
1064
+ `setPendingSessionCommit()`, declared on `SectionControllerHandle`. The player
1065
+ registers it both on the controllers it creates and on one the toolkit has
1066
+ already built, since the first section's controller usually exists before the
1067
+ player can override the factory. A host-built controller that leaves the method
1068
+ unimplemented keeps the behaviour it had before the hook existed, and loses a
1069
+ pending response at those boundaries.
1070
+
911
1071
  ## Content trust boundary
912
1072
 
913
1073
  Section-player layouts embed `<pie-item-player>` elements for each item.
914
- Item and passage markup is sanitized by default via DOMPurify; see
915
- [pie-item-player README](./README.md#content-trust-boundary)
1074
+ Item and passage markup is sanitized by default via DOMPurify; see the
1075
+ [pie-item-player README](../item-player/README.md#content-trust-boundary)
1076
+ for the allow-list, the `trust-markup` opt-out and the `sanitizeMarkup`
916
1077
  property. Hosts can forward those settings through the section-player
917
1078
  `runtime.player` overrides — the runtime flattens these onto the embedded
918
1079
  `<pie-item-player>` instance, so any field not recognized by the kernel is