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

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 (121) hide show
  1. package/README.md +249 -134
  2. package/dist/browser/assets/{evaluation-worker-DkU_wGh3.js → evaluation-worker-CORp9m0K.js} +39245 -38027
  3. package/dist/browser/chunks/PUA-tLuTiQ7--f2b27953.js +1 -1
  4. package/dist/browser/chunks/accents-BTUYmhn9-cafeb96e.js +1 -1
  5. package/dist/browser/chunks/accents-b-i-CngHZsLL-e0eb98fd.js +1 -1
  6. package/dist/browser/chunks/arabic-BzEHMVZD-30ac1ade.js +1 -1
  7. package/dist/browser/chunks/arrows-VjxlU-jv-2e17c192.js +1 -1
  8. package/dist/browser/chunks/braille-BeWC_Ruq-a3bb8ef9.js +1 -1
  9. package/dist/browser/chunks/braille-d-Dk6J96GO-d832bfbf.js +1 -1
  10. package/dist/browser/chunks/browser-a3c32178.js +17 -16
  11. package/dist/browser/chunks/calculator-element-271c142f.js +7 -3
  12. package/dist/browser/chunks/calligraphic-Cew9cU3x-04cf0735.js +1 -1
  13. package/dist/browser/chunks/catalog-context-46d3e50e.js +1 -0
  14. package/dist/browser/chunks/catalog-owner-d602f784.js +1 -0
  15. package/dist/browser/chunks/cherokee-Cny3FYho-4da7d2f3.js +1 -1
  16. package/dist/browser/chunks/cyrillic-CfCDulXU-5550df0b.js +1 -1
  17. package/dist/browser/chunks/cyrillic-ss-lU7bNGG9-12eb0188.js +1 -1
  18. package/dist/browser/chunks/devanagari-CDIz2h-r-18c38725.js +1 -1
  19. package/dist/browser/chunks/dist-41b39e7f.js +1 -1
  20. package/dist/browser/chunks/dist-59132a53.js +1 -1
  21. package/dist/browser/chunks/dist-60d7afef.js +1 -1
  22. package/dist/browser/chunks/dist-ca9b9622.js +1 -1
  23. package/dist/browser/chunks/double-struck-BpJEQk8y-500ce8dd.js +1 -1
  24. package/dist/browser/chunks/fraktur-rLEq9DEH-44cc14d8.js +1 -1
  25. package/dist/browser/chunks/greek-BlraJTyL-90ce8924.js +1 -1
  26. package/dist/browser/chunks/greek-ss-D-EN2PK0-91610739.js +1 -1
  27. package/dist/browser/chunks/hebrew-GiQZeybp-b583f555.js +1 -1
  28. package/dist/browser/chunks/latin-b-BhEnC7R2-d13e06ff.js +1 -1
  29. package/dist/browser/chunks/latin-bi-DqiIKv3U-1dec8705.js +1 -1
  30. package/dist/browser/chunks/latin-i-B1xtwaE1-21637466.js +1 -1
  31. package/dist/browser/chunks/latin-xfWFlLkD-5bcea5cf.js +1 -1
  32. package/dist/browser/chunks/logger-8b07fb72.js +1 -1
  33. package/dist/browser/chunks/marrows-DD1NgMQi-15b63755.js +1 -1
  34. package/dist/browser/chunks/math-DpWn1mbG-38eeb914.js +1 -1
  35. package/dist/browser/chunks/math-rendering-ready-9a0fd90a.js +1 -1
  36. package/dist/browser/chunks/{mathjax-D2JHb_R_-64780dc5.js → mathjax-BKrHE4xs-3ca05bf6.js} +41 -41
  37. package/dist/browser/chunks/monospace-D116QD2a-98d484fd.js +1 -1
  38. package/dist/browser/chunks/monospace-ex-DcXpouKS-456b403c.js +1 -1
  39. package/dist/browser/chunks/monospace-l-Ct5wkMDs-f34a4a9e.js +1 -1
  40. package/dist/browser/chunks/mshapes-DCp86WSX-c0055952.js +1 -1
  41. package/dist/browser/chunks/nl-NL-364d61b9.js +1 -1
  42. package/dist/browser/chunks/nl-NL-8b7e6d4b.js +1 -1
  43. package/dist/browser/chunks/phonetics-BwOCw2Y9-d2e3af59.js +1 -1
  44. package/dist/browser/chunks/phonetics-ss-CRtMn02f-579b6135.js +1 -1
  45. package/dist/browser/chunks/pie-api-client-687c319a.js +1 -1
  46. package/dist/browser/chunks/provider-4c704118.js +1 -1
  47. package/dist/browser/chunks/{runtime-C7H1jS4E-64772f5e.js → runtime-wcU7KOFU-ac86d7b8.js} +401 -278
  48. package/dist/browser/chunks/sans-serif-BYPZKlE9-2efdbcd9.js +1 -1
  49. package/dist/browser/chunks/sans-serif-b-B0kRKiC--b5530dce.js +1 -1
  50. package/dist/browser/chunks/sans-serif-bi-DXWul6mu-5370dba4.js +1 -1
  51. package/dist/browser/chunks/sans-serif-ex-Boux1kMP-aa178f31.js +1 -1
  52. package/dist/browser/chunks/sans-serif-i-676RFaSO-2db54474.js +1 -1
  53. package/dist/browser/chunks/sans-serif-r-DrN9xdZt-0fc72eea.js +1 -1
  54. package/dist/browser/chunks/script-0ohehnXc-526904f4.js +1 -1
  55. package/dist/browser/chunks/shapes-BG47NodJ-d59b0eca.js +1 -1
  56. package/dist/browser/chunks/{sre-engine-wf9s43gg-f09289c5.js → sre-0162a7a9.js} +18 -18
  57. package/dist/browser/chunks/sre-engine-ace23548.js +1 -0
  58. package/dist/browser/chunks/sre-engine-jd8cc08x-42d270e5.js +1 -0
  59. package/dist/browser/chunks/sre-locales-112320af.js +1 -0
  60. package/dist/browser/chunks/symbols-BqnUxf_t-83087e87.js +1 -1
  61. package/dist/browser/chunks/symbols-b-i-eg9k5hmF-9c461ac2.js +1 -1
  62. package/dist/browser/chunks/tool-annotation-toolbar-997438b4.js +7 -6
  63. package/dist/browser/chunks/tool-answer-eliminator-35483abd.js +4 -4
  64. package/dist/browser/chunks/tool-color-scheme-b7de5cdf.js +2 -2
  65. package/dist/browser/chunks/tool-dictionary-cecff3b4.js +1 -1
  66. package/dist/browser/chunks/tool-graph-cb0338e8.js +4 -4
  67. package/dist/browser/chunks/tool-host-contract-b93b482c.js +1 -1
  68. package/dist/browser/chunks/tool-instance-id-2a92deb0.js +1 -0
  69. package/dist/browser/chunks/tool-line-reader-f8ef3c6a.js +2 -2
  70. package/dist/browser/chunks/tool-periodic-table-43b133b8.js +1 -1
  71. package/dist/browser/chunks/tool-picture-dictionary-33721e91.js +1 -1
  72. package/dist/browser/chunks/tool-protractor-23f77028.js +6 -6
  73. package/dist/browser/chunks/tool-ruler-1d9a03a6.js +6 -6
  74. package/dist/browser/chunks/tool-tts-inline-98c2ce7a.js +7 -7
  75. package/dist/browser/chunks/tts-runtime-config-a7ab1ad4.js +194 -0
  76. package/dist/browser/chunks/variants-2LRNhOUP-966fc49d.js +1 -1
  77. package/dist/browser/pie-section-player.js +199 -85
  78. package/dist/chunks/nl-NL-364d61b9.js +1 -24
  79. package/dist/chunks/{sre-engine-wf9s43gg-f09289c5.js → sre-engine-jd8cc08x-42d270e5.js} +1 -1
  80. package/dist/components/shared/composition.d.ts +3 -1
  81. package/dist/components/shared/framework-error-latch.d.ts +32 -0
  82. package/dist/components/shared/host-element.d.ts +10 -0
  83. package/dist/components/shared/kernel-host-default-body.d.ts +14 -0
  84. package/dist/components/shared/layout-host-methods.d.ts +12 -27
  85. package/dist/components/shared/missing-tool-providers.d.ts +8 -8
  86. package/dist/components/shared/section-player-backend-delivery.d.ts +1 -1
  87. package/dist/components/shared/section-player-card-context.d.ts +5 -1
  88. package/dist/components/shared/section-player-host-runtime.d.ts +32 -19
  89. package/dist/components/shared/section-player-layout-context.d.ts +77 -0
  90. package/dist/components/shared/section-player-own-event.d.ts +10 -0
  91. package/dist/components/shared/section-player-runtime-callbacks.d.ts +1 -1
  92. package/dist/components/shared/section-player-shell-layout.svelte.d.ts +3 -11
  93. package/dist/components/shared/section-player-view-state.d.ts +6 -1
  94. package/dist/components/shared/toolkit-ready-wait.d.ts +16 -0
  95. package/dist/contracts/host-hooks.d.ts +0 -7
  96. package/dist/contracts/runtime-host-contract.d.ts +0 -5
  97. package/dist/controllers/SectionController.d.ts +1 -0
  98. package/dist/controllers/types.d.ts +8 -1
  99. package/dist/item-section/index.d.ts +22 -0
  100. package/dist/item-section/index.js +37 -0
  101. package/dist/pie-section-player.d.ts +4 -6
  102. package/dist/pie-section-player.js +19437 -19877
  103. package/dist/policies/index.d.ts +1 -19
  104. package/dist/policies/index.js +6 -14
  105. package/dist/policies/types.d.ts +0 -4
  106. package/package.json +13 -53
  107. package/dist/browser/chunks/custom-element-define-2a92deb0.js +0 -1
  108. package/dist/browser/chunks/nds-icon-button-d602f784.js +0 -194
  109. package/dist/browser/chunks/pie-tool-calculator-cortex-0626bc24.js +0 -9
  110. package/dist/browser/chunks/pie-tool-calculator-geogebra-561236cb.js +0 -9
  111. package/dist/components/item-shell-element.d.ts +0 -1
  112. package/dist/components/shared/shell-registration.d.ts +0 -55
  113. package/dist/contracts/layout-contract.d.ts +0 -17
  114. package/dist/contracts/layout-contract.js +0 -0
  115. package/dist/contracts/layout-parity-metadata.d.ts +0 -4
  116. package/dist/contracts/layout-parity-metadata.js +0 -87
  117. package/dist/contracts/public-events.d.ts +0 -92
  118. package/dist/contracts/public-events.js +0 -12
  119. package/dist/policies/guards.d.ts +0 -2
  120. /package/dist/browser/chunks/{ItemToolBar-wr2myaah-2ce4b643.js → ItemToolBar-jx38w99s-58defccc.js} +0 -0
  121. /package/dist/chunks/{ItemToolBar-wr2myaah-1287ba31.js → ItemToolBar-jx38w99s-1287ba31.js} +0 -0
package/README.md CHANGED
@@ -6,7 +6,9 @@ Section rendering package with layout custom elements:
6
6
  - `pie-section-player-vertical`
7
7
  - `pie-section-player-tabbed`
8
8
 
9
- Use the layout custom elements listed above for section rendering.
9
+ A host that arranges the section itself builds its layout from
10
+ `pie-section-player-kernel-host` and the two panes; see
11
+ [Custom layout authoring](#custom-layout-authoring).
10
12
 
11
13
  ## Install
12
14
 
@@ -33,8 +35,10 @@ opt-out.
33
35
  import "@pie-players/pie-section-player";
34
36
  ```
35
37
 
36
- If hosts need explicit registration control, keep using documented component
37
- entrypoints under `@pie-players/pie-section-player/components/*`.
38
+ The entrypoints under `@pie-players/pie-section-player/components/*` load one
39
+ element each, together with every element it renders: a layout entry also
40
+ registers the cards, panes and shell. They choose which layouts a host loads; the
41
+ tag names stay fixed.
38
42
 
39
43
  The entries are bundler-only: they import `@pie-players/pie-item-player`,
40
44
  `@pie-players/pie-default-tool-loaders` and `speech-rule-engine`, with the
@@ -80,7 +84,22 @@ snapshot).
80
84
 
81
85
  ### Session lifecycle
82
86
 
83
- A typical host flow:
87
+ Set `session` with `section` to restore a section. The controller created for
88
+ that section applies it in replace mode in place of hydrating from the
89
+ persistence strategy, before the controller is published, so the first
90
+ composition, `engine-ready` and every reader see it. The strategy still receives
91
+ every `persist()`.
92
+
93
+ A later assignment applies through `applySession(value, { mode: "replace" })`
94
+ under the rules `<pie-item-player>` applies to its `session`: a value equal to
95
+ the current session is a no-op, so echoing `session-changed` back is safe, and an
96
+ item session with neither a response value nor a response field leaves one that
97
+ holds responses in place. `null` after creation is a no-op. A `section` change
98
+ without a `session` assignment hydrates the new controller from the strategy.
99
+ Reading `session` returns the assigned value until the controller is published,
100
+ and a fresh `getSession()` snapshot after.
101
+
102
+ Driving the controller directly:
84
103
 
85
104
  ```ts
86
105
  controller?.configureSessionPersistence?.({ context, strategy });
@@ -184,9 +203,9 @@ projected over the section env — with `role: "instructor"` under
184
203
  `feedback: "solution"` — and the element draws the rest. Only that item's env
185
204
  changes; its neighbours stay editable. A retry withdraws the projection.
186
205
 
187
- Read the resolved state from `getFormativeProjection()`, or off
188
- `compositionModel.formative` in a custom layout. Drive it from a host through the
189
- same handle:
206
+ Read the resolved state from `getFormativeProjection()`, or from
207
+ `composition.formative` in a `composition-changed` event's detail. Drive it from a
208
+ host through the same handle:
190
209
 
191
210
  ```ts
192
211
  const controller = await host.waitForSectionController(5000);
@@ -303,14 +322,10 @@ for the contract and the decision record.
303
322
 
304
323
  ## Usage
305
324
 
306
- Import the custom-element registration entrypoint in consumers:
325
+ Import the package root, which registers every section-player element:
307
326
 
308
327
  ```ts
309
- import '@pie-players/pie-section-player/components/section-player-splitpane-element';
310
- import '@pie-players/pie-section-player/components/section-player-vertical-element';
311
- import '@pie-players/pie-section-player/components/section-player-tabbed-element';
312
- import '@pie-players/pie-section-player/components/section-player-item-card-element';
313
- import '@pie-players/pie-section-player/components/section-player-passage-card-element';
328
+ import '@pie-players/pie-section-player';
314
329
  ```
315
330
 
316
331
  Render in HTML/Svelte/JSX:
@@ -319,17 +334,17 @@ Render in HTML/Svelte/JSX:
319
334
  <pie-section-player-splitpane></pie-section-player-splitpane>
320
335
  ```
321
336
 
322
- Set complex values (`runtime`, `section`) as JS properties. `env` is a
337
+ Set complex values (`runtime`, `section`, `session`) as JS properties. `env` is a
323
338
  `runtime` field (`runtime.env`); the layout elements have no `env` property.
324
339
 
325
340
  Set `runtime` no later than `section`. When the player builds its own
326
341
  coordinator, the section's arrival rebuilds that coordinator from the current
327
342
  `runtime`, so both can be set a tick after the element mounts. Once the section
328
343
  has initialized, a change to `runtime.tools`, `runtime.assessmentId`,
329
- `runtime.accessibility`, `runtime.lazyInit`, `runtime.toolConfigStrictness` or
344
+ `runtime.accessibility`, `runtime.lazyInit`, `tool-config-strictness` or
330
345
  `toolRegistry` is reported once in the console and does not reach that
331
346
  coordinator; `runtime.tools.pnpEnforcement` still applies. Change a running
332
- coordinator through the one `toolkit-ready` delivers, with
347
+ coordinator through the one `toolkit-ready` carries, with
333
348
  `updateToolConfig(...)` or `updateToolsPlacement(...)`, or pass your own as
334
349
  `runtime.coordinator`.
335
350
 
@@ -340,25 +355,30 @@ The layout elements (`pie-section-player-splitpane`,
340
355
 
341
356
  - `runtime` (object): primary coordinator/tools/player runtime bundle
342
357
  - `section` (object): assessment section payload
358
+ - `session` (object, JS property only): the section's session, a `SectionControllerSessionState`; see [Session lifecycle](#session-lifecycle)
359
+ - `assessment` (object, JS property only): the `AssessmentEntity` whose `personalNeedsProfile` and `settings` tool policy reads, forwarded to the coordinator the player builds. A coordinator passed in `runtime` is the host's to bind with `updateAssessment`. A section's own `personalNeedsProfile` is not read, and the player warns once when it finds one
343
360
  - `debug` (boolean-like): verbose debug logging control (`"true"` enables, `"false"`/`"0"` disables)
344
361
  - `toolbar-position` (string): `top|right|bottom|left|none`
345
362
  - `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.
346
363
  - `content-max-width-no-passage` (number, optional): max width in px when no passages exist. Clamped to 320–2200. Unset by default (layout uses available width).
347
364
  - `content-max-width-with-passage` (number, optional): max width in px when passages are present. Clamped to 320–2200. Unset by default (layout uses available width).
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.)
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.)
365
+ - `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). Splitpane only.
366
+ - `split-pane-collapse-strategy` (string, optional): splitpane stacked-mode strategy. Supported values: `tabbed` (default) and `vertical`. Splitpane only.
350
367
  - `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).
351
368
  - `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.
369
+ - `locale` (string, optional): BCP-47 locale for the player's own interface text. Unset renders `en-US`.
370
+ - `runtime.contentLanguage` (string, optional, `runtime` only): BCP-47 language of the content where its markup names none, which read-aloud speaks in and picks catalog cards by. A `lang` between the content and its card wins; unset reads `en-US`. `locale` never sets it.
371
+ - `nds-icons` (boolean): opt in to NDS icon buttons.
372
+ - `tool-config-strictness` (string, optional): `off|warn|error` for tool-config validation; default `error`.
373
+ - `split-pane-initial-passage-width` (number, optional): splitpane passage pane width in percent at mount. Clamped to 20–80; default 50. Splitpane only.
356
374
  - `iife-bundle-host` (string, optional): bundle host for the IIFE element pre-warm when `runtime.player.loaderOptions.bundleHost` is unset.
357
375
  - Host extension props (JS properties only): `toolRegistry`, `sectionHostButtons`, `itemHostButtons`, `passageHostButtons`, `hooks`
358
376
 
359
- When viewport width is within the collapsed range (~1100px and below), splitpane and
360
- vertical layout hosts normalize section toolbar placement to `top`. This includes
361
- `left`, `right`, `bottom`, and `none` values.
377
+ When the viewport is no wider than `narrow-layout-breakpoint` (default 1100px),
378
+ splitpane and vertical layout hosts normalize section toolbar placement to `top`.
379
+ This includes `left`, `right`, `bottom`, and `none` values. Separately, the shell
380
+ moves a `left` or `right` toolbar to `top` at a fixed 1100px, so with a smaller
381
+ breakpoint side toolbars still move to the top from 1100px down.
362
382
 
363
383
  `hooks.cardTitleFormatter` remains active across responsive splitpane transitions (split -> stacked and stacked -> split), because title rendering is provided through shared card context rather than layout-specific state.
364
384
 
@@ -440,10 +460,7 @@ hooks for theming:
440
460
  - `pie-section-player-tab--active`
441
461
  - `pie-section-player-tab-panel`
442
462
 
443
- For theme compatibility with existing passage-label patterns, tabs also expose:
444
-
445
- - `data-pie-purpose="passage-label"` and alias class `passage-label`
446
- - `data-pie-purpose="item-label"` and alias class `item-label`
463
+ Tabs also carry `data-pie-purpose="passage-label"` and `data-pie-purpose="item-label"`.
447
464
 
448
465
  Tab colors, spacing, and track geometry can be themed via CSS variables:
449
466
  `--pie-section-player-tab-color`, `--pie-section-player-tab-background`,
@@ -559,14 +576,14 @@ mountable-but-unoccupied result, not an error.
559
576
  The intended usage model is:
560
577
 
561
578
  - **CE props for default/standard flows (roughly 90% use cases)**:
562
- - `assessment-id`, `section`, `section-id`, `attempt-id`, `debug`
579
+ - `section`, `section-id`, `attempt-id`, `debug`, `locale`, `nds-icons`, `tool-config-strictness`
563
580
  - `show-toolbar`, `toolbar-position`, `narrow-layout-breakpoint`
564
- - `content-max-width-no-passage`, `content-max-width-with-passage`, `split-pane-min-region-width`, `split-pane-collapse-strategy`
581
+ - `content-max-width-no-passage`, `content-max-width-with-passage`, and on splitpane `split-pane-min-region-width`, `split-pane-collapse-strategy`
565
582
  - **JS API for advanced customization**:
566
583
  - Get the controller handle via `getSectionController()` or `waitForSectionController()` (preferred)
567
584
  - Listen for `pie-stage-change` and filter on `detail.stage === "engine-ready"` for an event-driven entry point
568
585
  - Apply custom policy/gating in host code (for example, domain-specific `canNext` based on controller events like `section-items-complete-changed`)
569
- - Compose forward/backward eligibility in host code using `selectNavigation()` + host state; there is intentionally no separate parallel CE gating API for this
586
+ - Compose forward/backward eligibility in host code using `getSnapshot().navigation` + host state; there is intentionally no separate parallel CE gating API for this
570
587
  - Inject custom toolbar tooling with `toolRegistry` and optional host button arrays (`sectionHostButtons`, `itemHostButtons`, `passageHostButtons`)
571
588
  - Register host callbacks via `hooks` (for example `hooks.cardTitleFormatter`)
572
589
 
@@ -666,15 +683,18 @@ import * as delivery from "@pie-element/multiple-choice/browser/delivery";
666
683
  import * as controller from "@pie-element/multiple-choice/browser/controller";
667
684
  import manifest from "../package.json"; // pins "@pie-element/multiple-choice" exactly
668
685
 
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
- ]);
686
+ registerPreloadedElements(
687
+ [
688
+ {
689
+ tag: "pie-element-multiple-choice",
690
+ package: "@pie-element/multiple-choice",
691
+ version: manifest.dependencies["@pie-element/multiple-choice"],
692
+ element: delivery,
693
+ controller,
694
+ },
695
+ ],
696
+ { math: { assetRoot: "https://assets.example.com/npm" } },
697
+ );
678
698
 
679
699
  sectionPlayer.runtime = { ...sectionPlayer.runtime, playerType: "preloaded" };
680
700
  ```
@@ -693,6 +713,10 @@ sectionPlayer.runtime = { ...sectionPlayer.runtime, playerType: "preloaded" };
693
713
  an element can typeset with a MathJax it was not built for. Elements that
694
714
  bundle their own MathJax share none
695
715
  ([One MathJax version per page](../../docs/item-player/loading-strategies.md#one-mathjax-version-per-page)).
716
+ - Pass `math.assetRoot`, an npm root serving the fonts and speech the elements'
717
+ bundled MathJax loads, or `math.assetUrls`, each file's URL; without either,
718
+ elements on adapter 0.1.3 or later render without web fonts and speech
719
+ ([MathJax assets](../../docs/item-player/loading-strategies.md#mathjax-assets)).
696
720
  - Register one version per package. The players align every authored version
697
721
  of a package to the registered one, and registering a second version throws.
698
722
  - Register each package's `controller` unless the item players are hosted
@@ -733,8 +757,8 @@ sequential keyboard navigation.
733
757
  unset field, a missing section included, takes its value from
734
758
  `DEFAULT_SECTION_PLAYER_POLICIES`.
735
759
 
736
- - `readiness.mode` (`"progressive"` | `"strict"`) — the mode
737
- `SectionPlayerLayoutKernel` passes to `createReadinessDetail`. Both modes
760
+ - `readiness.mode` (`"progressive"` | `"strict"`) — the mode the layout
761
+ kernel gates readiness by. Both modes
738
762
  hold the `interactive` stage and `pie-loading-complete` until the section's
739
763
  element pre-warm resolves and the item cards can mount. The kernel has no
740
764
  later loading signal, so the two modes currently emit the same sequence.
@@ -750,10 +774,8 @@ unset field, a missing section included, takes its value from
750
774
  shape of opt-out can still override `runtime.player.loaderConfig.instrumentationProvider`.
751
775
  Default: `true`.
752
776
 
753
- The exported `isPreloadEnabled(policies)` and `isTelemetryEnabled(policies)`
754
- helpers read these toggles with the documented default-true semantics, so
755
- host code that needs to mirror the same gate (e.g. when composing a custom
756
- layout host) can call them directly.
777
+ `resolveSectionPlayerPolicies(policies)` returns the filled-in object, for
778
+ host code that needs the same gates.
757
779
 
758
780
  ### Navigation signals
759
781
 
@@ -762,20 +784,20 @@ layout host) can call them directly.
762
784
 
763
785
  Runtime configuration is explicit:
764
786
 
765
- - `runtime` owns runtime fields (`assessmentId`, `playerType`, `player`, `lazyInit`, `tools`, `accessibility`, `coordinator`, `isolation`, `env`, `createSectionController`).
787
+ - `runtime` owns runtime fields (`assessmentId`, `playerType`, `player`, `lazyInit`, `tools`, `toolContextResolvers`, `accessibility`, `coordinator`, `createSectionController`, `isolation`, `env`, `contentLanguage` and the `on*` callbacks). `locale`, `nds-icons` and `tool-config-strictness` are attributes only.
766
788
  - Tool placement is configured through `runtime.tools.placement.section`, `runtime.tools.placement.item`, and `runtime.tools.placement.passage`.
767
- - Tool configuration validation is canonical in toolkit initialization (`pie-assessment-toolkit`), including toolbar overlays. Use `runtime.toolConfigStrictness` (`off` | `warn` | `error`) to control warning-only vs fail-fast behavior.
789
+ - Tool configuration validation is canonical in toolkit initialization (`pie-assessment-toolkit`), including toolbar overlays. Use the `tool-config-strictness` attribute (`off` | `warn` | `error`) to control warning-only vs fail-fast behavior.
768
790
  - TTS provider config must use `tools.providers.textToSpeech` (canonical). `tools.providers.tts` is rejected by validation.
769
- - Host tool overrides are additive:
770
- - `toolRegistry` overrides the default toolbar registry when provided
791
+ - Host tool overrides:
792
+ - `toolRegistry` replaces the default toolbar registry when provided. Build it with `createPackagedToolRegistry({ toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS })` and register custom tools on it, since toolbars load each tool's element through the registry's loaders. A player that builds its own coordinator gives it this registry; a coordinator passed as `runtime.coordinator` keeps its own, which decides policy, so build that coordinator with the same registry
771
793
  - host buttons are appended per toolbar scope via `sectionHostButtons`, `itemHostButtons`, `passageHostButtons`
772
794
 
773
- Debug logging can be controlled per section-player host:
795
+ Debug logging is page-wide. A layout's `debug` attribute writes `window.PIE_DEBUG`, the flag every PIE logger on the page reads, so the last host to set it decides for all of them:
774
796
 
775
797
  - Enable: `<pie-section-player-splitpane debug="true">`
776
798
  - Disable: `<pie-section-player-splitpane debug="false">` (or `debug="0"`)
777
799
 
778
- You can also disable globally via `window.PIE_DEBUG = false`.
800
+ Without a `debug` attribute a layout follows `window.PIE_DEBUG`, which a host can set directly.
779
801
 
780
802
  See the progressive demo routes in `apps/section-demos/src/routes/(demos)` (for example `single-question/+page.svelte` and `session-hydrate-db/+page.svelte`) for end-to-end host integrations.
781
803
 
@@ -804,44 +826,133 @@ Structural composition changes (new/removed/reordered entities) may legitimately
804
826
 
805
827
  ## Custom layout authoring
806
828
 
807
- For section layout authors, `pie-section-player-shell` is the primary abstraction:
808
-
809
- - Use `pie-section-player-shell` to place the section toolbar around your layout body.
810
- - Keep your custom layout logic focused on passages/items and layout UI.
811
- - Treat `pie-section-player-base` as internal runtime plumbing that wraps the shell.
812
- - Use `pie-section-player-item-card` and `pie-section-player-passage-card` as reusable card primitives.
813
- - Prefer shared context for cross-cutting card render plumbing (resolved player tag/action) over repeated prop drilling.
814
-
815
- Minimal pattern for package layout components:
816
-
817
- ```svelte
818
- <pie-section-player-base runtime={effectiveRuntime} {section} section-id={sectionId} attempt-id={attemptId}>
819
- <pie-section-player-shell
820
- show-toolbar={showToolbar}
821
- toolbar-position={toolbarPosition}
822
- toolRegistry={toolRegistry}
823
- sectionHostButtons={sectionHostButtons}
824
- >
825
- <!-- layout-specific body -->
826
- <pie-section-player-passage-card
827
- passage={passage}
828
- playerParams={passagePlayerParams}
829
- passageToolbarTools={passageToolbarTools}
830
- toolRegistry={toolRegistry}
831
- hostButtons={passageHostButtons}
832
- ></pie-section-player-passage-card>
833
- <pie-section-player-item-card
834
- item={item}
835
- canonicalItemId={canonicalItemId}
836
- playerParams={itemPlayerParams}
837
- itemToolbarTools={itemToolbarTools}
838
- toolRegistry={toolRegistry}
839
- hostButtons={itemHostButtons}
840
- ></pie-section-player-item-card>
841
- </pie-section-player-shell>
842
- </pie-section-player-base>
829
+ A host builds its own section layout from `pie-section-player-kernel-host` and the
830
+ two panes. The kernel host runs the section: toolkit, section controller,
831
+ readiness, element pre-warm and the section toolbar. Its element children are the
832
+ layout, and the panes inside them render the section's items and passages. The
833
+ stock layouts are built from the same panes.
834
+
835
+ ```html
836
+ <!doctype html>
837
+ <html lang="en">
838
+ <head>
839
+ <meta charset="utf-8" />
840
+ <style>
841
+ body { margin: 0; }
842
+ pie-section-player-kernel-host { display: block; height: 100vh; }
843
+ .columns { display: grid; grid-template-columns: 3fr 2fr; height: 100%; }
844
+ .column { min-height: 0; overflow: auto; }
845
+ </style>
846
+ <script type="module">
847
+ import "https://cdn.jsdelivr.net/npm/@pie-players/pie-section-player@x.y.z/dist/browser/pie-section-player.js";
848
+
849
+ const player = document.querySelector("pie-section-player-kernel-host");
850
+ player.addEventListener("pie-loading-complete", () => {
851
+ console.log("ready", player.getSnapshot().navigation);
852
+ });
853
+ player.runtime = { env: { mode: "gather", role: "student" } };
854
+ player.section = {
855
+ identifier: "water-cycle",
856
+ rubricBlocks: [
857
+ {
858
+ identifier: "passage-1",
859
+ class: "stimulus",
860
+ view: ["candidate"],
861
+ passage: {
862
+ id: "passage-1",
863
+ config: {
864
+ markup: "<p>Water evaporates, condenses into clouds and falls as rain.</p>",
865
+ elements: {},
866
+ models: [],
867
+ },
868
+ },
869
+ },
870
+ ],
871
+ assessmentItemRefs: [
872
+ {
873
+ identifier: "item-1",
874
+ item: {
875
+ id: "item-1",
876
+ config: {
877
+ markup: "<p>Name the stage in which water vapour forms clouds.</p>",
878
+ elements: {},
879
+ models: [],
880
+ },
881
+ },
882
+ },
883
+ ],
884
+ };
885
+ </script>
886
+ </head>
887
+ <body>
888
+ <pie-section-player-kernel-host section-id="water-cycle" attempt-id="attempt-1">
889
+ <div class="columns">
890
+ <div class="column"><pie-section-player-items-pane></pie-section-player-items-pane></div>
891
+ <div class="column"><pie-section-player-passages-pane></pie-section-player-passages-pane></div>
892
+ </div>
893
+ </pie-section-player-kernel-host>
894
+ </body>
895
+ </html>
843
896
  ```
844
897
 
898
+ The example loads the [browser build](../../docs/setup/cdn_usage.md#section-player-browser-build).
899
+ A bundled host imports `@pie-players/pie-section-player`, which defines the
900
+ kernel host and the panes too. An item with PIE elements names them in
901
+ `config.elements` and carries their `config.models`, as in any section. The
902
+ `/custom-layout` route of `apps/section-demos`
903
+ ([source](../../apps/section-demos/src/routes/%28demos%29/custom-layout/+page.svelte))
904
+ builds the same two columns in Svelte.
905
+
906
+ ### Kernel host
907
+
908
+ - **Inputs and host methods.** Those of the layout elements in
909
+ [Runtime Inputs](#runtime-inputs), without the layout dimensions
910
+ (`narrow-layout-breakpoint`, `content-max-width-*`, `split-pane-*`).
911
+ - **DOM.** The open shadow root holds the toolkit and the section toolbar around
912
+ one default slot; `show-toolbar` and `toolbar-position` place the toolbar around
913
+ the layout. The children, the panes and the content they render stay in light
914
+ DOM, where page styles and the content stylesheet reach them. The element has
915
+ no styles of its own, so the host gives it `display` and a height.
916
+ - **Stock body.** With no element children it renders its own layout, the
917
+ passages pane above the items pane, and places the passages pane only for a
918
+ section with passages. The stock body leaves when the first element child
919
+ arrives and returns when the last one leaves. Text and comment children do not
920
+ count, so markup whitespace and a framework's placeholder comments leave it in
921
+ place.
922
+ - **Navigation and state.** The host methods (`navigateNext`, `navigatePrevious`,
923
+ `navigateTo`, `getSnapshot`, `getSectionController`, `waitForSectionController`)
924
+ and the events (`pie-stage-change`, `pie-loading-complete`,
925
+ `composition-changed`, `session-changed`, `framework-error`, `toolkit-ready`)
926
+ are those of the stock layouts. `detail.sourceCe` on its `pie-stage-change`
927
+ and `pie-loading-complete` events reads `pie-section-player-kernel-host`.
928
+
929
+ ### Panes
930
+
931
+ `<pie-section-player-items-pane>` renders the item cards of the current
932
+ composition, with their toolbars, and runs the element pre-warm;
933
+ `<pie-section-player-passages-pane>` renders the passage cards. A pane takes no
934
+ attributes or properties, and its pre-warm failures arrive as the section
935
+ player's `framework-error` and `element-preload-error` events. It reads the
936
+ section player it belongs to from a context the kernel publishes, so it renders
937
+ at any depth below the kernel host, and outside a section player it renders
938
+ nothing.
939
+
940
+ - **One pane of each kind renders**: the first connected. A second pane of a
941
+ kind renders nothing and takes over when the first disconnects; the player
942
+ reports the duplicate once in the console.
943
+ - **Readiness follows the rendering items pane.** `interactive` and
944
+ `pie-loading-complete` wait for its element pre-warm, and reports from any
945
+ other pane are ignored. A section with items and no items pane never reaches
946
+ either; the player reports it once in the console a task after
947
+ `section-ready`.
948
+ - **The passages pane is optional.** A section without passages needs none, and
949
+ readiness does not wait for it.
950
+ - **Scrolling belongs to the layout.** The items pane's scroll hint follows the
951
+ nearest ancestor with `overflow-y: auto` or `scroll`, so the layout gives each
952
+ pane's container that overflow and a bounded height. The stock layouts' pane backdrops and margins
953
+ stay with those layouts; the cards keep their tags and
954
+ [styling hooks](#card-header-styling-hooks).
955
+
845
956
  ### JS API example for advanced host policy
846
957
 
847
958
  ```ts
@@ -856,7 +967,7 @@ const unsubscribe = controller?.subscribe?.((event: any) => {
856
967
  });
857
968
 
858
969
  function canAdvance() {
859
- const nav = host.selectNavigation?.();
970
+ const nav = host.getSnapshot?.()?.navigation;
860
971
  return Boolean(nav?.canNext && sectionComplete);
861
972
  }
862
973
  ```
@@ -877,7 +988,7 @@ const unsubscribeSection = coordinator.subscribeSectionLifecycleEvents({
877
988
  });
878
989
  ```
879
990
 
880
- Subscribe **after** the first `getOrCreateSectionController(...)` resolves (or after `toolkit-ready` once the section player has fully wired its controller — typically the safest anchor in host code is `toolkit-ready` followed by the first controller-resolve). Calling subscribe before any cohort exists throws.
991
+ Subscribe **after** the first `getOrCreateSectionController(...)` resolves (or after `toolkit-ready` once the section player has fully wired its controller — typically the safest anchor in host code is `toolkit-ready` followed by the first controller-resolve). Calling subscribe before the first `getOrCreateSectionController(...)` call throws; a listener added while a section is starting binds when that section becomes active.
881
992
 
882
993
  Use `subscribeSectionEvents(...)` only for advanced mixed filtering requirements.
883
994
 
@@ -929,39 +1040,29 @@ Section-player instrumentation is provider-agnostic and uses the shared
929
1040
  - Toolkit telemetry forwarding uses the same provider path, so tool/backend
930
1041
  operational events are visible alongside section events when toolkit is mounted.
931
1042
 
932
- Canonical lifecycle stream (engine-routed, dispatched on the outer layout CE):
1043
+ Canonical lifecycle stream (engine-routed, dispatched on the outer layout CE,
1044
+ bubbling and composed):
933
1045
 
934
1046
  - `pie-stage-change` — single typed transition stream covering
935
1047
  `composed` → `engine-ready` → `interactive` → `disposed`. Payload is a
936
- `StageChangeDetail`.
1048
+ `StageChangeDetail`. A non-recoverable framework error before
1049
+ `interactive` emits the current stage `failed` and each stage it never
1050
+ reached `skipped`.
937
1051
  - `pie-loading-complete` — fires once per cohort, when the section's element
938
1052
  pre-warm resolves for the current composition and the item cards can mount
939
1053
  (kernel-routed).
940
1054
  - `framework-error` — canonical error event for any failure crossing the
941
- framework boundary. Payload is a `FrameworkErrorModel`. The toolkit's
942
- package-internal `FrameworkErrorBus` and the `onFrameworkError`
943
- callback prop deliver each error exactly once regardless of wrapper
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.
952
-
953
- Callback-prop mirrors with two-tier precedence (`runtime.<key>` wins over
954
- the top-level prop):
955
-
956
- - `onStageChange(detail)` — on every layout CE, `pie-section-player-base`,
957
- and `pie-assessment-toolkit`.
958
- - `onLoadingComplete(detail)` — on the kernel-backed layout CEs only
959
- (split-pane / vertical / tabbed / kernel-host).
960
- - `onFrameworkError(model)` — on every layout CE and
961
- `pie-section-player-base`. Fires exactly once per error regardless of
962
- wrapper depth (delivered through the package-internal
963
- `FrameworkErrorBus`). The `framework-error` DOM event on the layout
964
- CE host is also single-fire; consume either.
1055
+ framework boundary. Payload is a `FrameworkErrorModel`. The toolkit
1056
+ dispatches it once per error, bubbling and composed, so it reaches the
1057
+ layout CE and `document`; errors from a coordinator the host passes as
1058
+ `runtime.coordinator` arrive the same way.
1059
+ `tests/section-player-event-delivery.spec.ts` pins these counts.
1060
+
1061
+ Callbacks on `runtime`:
1062
+
1063
+ - `runtime.onStageChange(detail)` and `runtime.onLoadingComplete(detail)`.
1064
+ - `runtime.onFrameworkError(model)` fires once per error regardless of
1065
+ wrapper depth, like the `framework-error` DOM event; consume either.
965
1066
 
966
1067
  Section-player owned instrumentation stream:
967
1068
 
@@ -973,9 +1074,7 @@ Section-player owned instrumentation stream:
973
1074
 
974
1075
  Build consumers against these canonical lifecycle events:
975
1076
 
976
- - `readiness-change` → listen for `pie-stage-change`. The readiness
977
- payload is also available via `selectReadiness()` /
978
- `getSnapshot().readiness` on the layout CE.
1077
+ - `readiness-change` → listen for `pie-stage-change`.
979
1078
  - `interaction-ready` → `pie-stage-change` filtered on
980
1079
  `detail.stage === "interactive"`.
981
1080
  - `ready` → `pie-loading-complete`.
@@ -1025,9 +1124,34 @@ await controller?.updateItemSession?.("q1", {
1025
1124
  });
1026
1125
  ```
1027
1126
 
1028
- The same controller snapshot is what the persistence strategy saves/loads.
1127
+ The same controller snapshot is what the persistence strategy saves/loads, and
1128
+ what the layout's `session` property takes.
1029
1129
  When a controller is reused for the same `sectionId`/`attemptId`, `updateInput()` refreshes composition input while preserving in-memory section session data.
1030
1130
 
1131
+ ### One item as a section
1132
+
1133
+ `sectionFromItem` wraps one item config, in the shape `<pie-item-player config>`
1134
+ takes, and optionally its session, in the `section` and `session` the layouts
1135
+ take. It is exported from the package root and from
1136
+ `@pie-players/pie-section-player/item-section`, which defines no custom element
1137
+ and imports in Node.
1138
+
1139
+ ```ts
1140
+ import { sectionFromItem } from "@pie-players/pie-section-player/item-section";
1141
+
1142
+ const { section, session } = sectionFromItem(itemConfig, { session: itemSession });
1143
+ player.setAttribute("section-id", section.identifier);
1144
+ player.section = section;
1145
+ player.session = session;
1146
+ ```
1147
+
1148
+ The item ref's `identifier` and the item's `id` are the config's `id`, so every
1149
+ `itemId` the section reports is the id the host already holds. An advanced
1150
+ config's `passage` becomes the item's passage, and its `instructorResources` and
1151
+ `defaultExtraModels` stay on the item's config. The section carries no `baseId`
1152
+ or `version`; `options.sectionId` names the section, which defaults to the
1153
+ config's `id`.
1154
+
1031
1155
  ### Commit at a section boundary
1032
1156
 
1033
1157
  A delivery element coalesces its `session-changed` dispatch, so a response the
@@ -1038,7 +1162,7 @@ The player also commits when an item shell tears down and when the page goes
1038
1162
  hidden.
1039
1163
 
1040
1164
  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
1165
+ does not leave its `<pie-item-scope>`, which re-dispatches it as the normalized
1042
1166
  `item-session-changed` (`PIE_ITEM_SESSION_CHANGED_EVENT`); the toolkit then
1043
1167
  publishes the section's canonical `session-changed`. Both bubble through the
1044
1168
  layout element to `document`, and a listener on either receives each dispatch
@@ -1097,21 +1221,12 @@ produced by a trusted pipeline.
1097
1221
  Published exports are intentionally minimal:
1098
1222
 
1099
1223
  - `@pie-players/pie-section-player`
1100
- - `@pie-players/pie-section-player/components/section-player-splitpane-element`
1101
- - `@pie-players/pie-section-player/components/section-player-vertical-element`
1102
- - `@pie-players/pie-section-player/components/section-player-tabbed-element`
1103
- - `@pie-players/pie-section-player/components/section-player-kernel-host-element`
1104
- - `@pie-players/pie-section-player/components/section-player-shell-element`
1105
- - `@pie-players/pie-section-player/components/section-player-item-card-element`
1106
- - `@pie-players/pie-section-player/components/section-player-passage-card-element`
1107
- - `@pie-players/pie-section-player/components/section-player-items-pane-element`
1108
- - `@pie-players/pie-section-player/components/section-player-passages-pane-element`
1109
- - `@pie-players/pie-section-player/contracts/layout-contract`
1110
- - `@pie-players/pie-section-player/contracts/public-events`
1224
+ - `@pie-players/pie-section-player/browser`, the self-contained browser build ([CDN usage](../../docs/setup/cdn_usage.md#section-player-browser-build))
1225
+ - `@pie-players/pie-section-player/components/section-player-splitpane-element`, which resolves to the package root
1111
1226
  - `@pie-players/pie-section-player/contracts/runtime-host-contract`
1112
- - `@pie-players/pie-section-player/contracts/layout-parity-metadata`
1113
1227
  - `@pie-players/pie-section-player/contracts/host-hooks`
1114
1228
  - `@pie-players/pie-section-player/policies`
1229
+ - `@pie-players/pie-section-player/item-section`
1115
1230
 
1116
1231
  ## Development
1117
1232