@pie-players/pie-assessment-toolkit 0.3.73 → 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.
- package/README.md +469 -318
- package/dist/attempt/AssessmentSession.d.ts +1 -5
- package/dist/attempt/AssessmentSession.js +0 -29
- package/dist/attempt/TestSession.d.ts +1 -20
- package/dist/attempt/TestSession.js +8 -99
- package/dist/components/ItemScope.custom-element.d.ts +1 -0
- package/dist/components/ItemScope.custom-element.js +1 -0
- package/dist/components/ItemToolBar.custom-element.d.ts +1 -0
- package/dist/components/ItemToolBar.custom-element.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.d.ts +1 -0
- package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
- package/dist/components/SectionToolBar.custom-element.d.ts +1 -0
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/ItemToolBar-7tt7v2xp.js +4 -0
- package/dist/components/chunks/ItemToolBar-97hx60z4.js +2 -0
- package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +2 -0
- package/dist/components/chunks/ItemToolBar-jx38w99s.js +2 -0
- package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +40 -0
- package/dist/components/chunks/ItemToolBar-vdkyn9f3.js +2 -0
- package/dist/components/chunks/sre-engine-jd8cc08x.js +1 -0
- package/dist/components/item-scope-element.d.ts +2 -0
- package/dist/components/item-scope-element.js +2 -0
- package/dist/context/assessment-toolkit-context.d.ts +32 -6
- package/dist/index.d.ts +23 -48
- package/dist/index.js +12 -37
- package/dist/policy/core/PolicySource.d.ts +1 -1
- package/dist/policy/core/ToolPolicyEngine.d.ts +85 -34
- package/dist/policy/core/ToolPolicyEngine.js +148 -46
- package/dist/policy/core/compose-decision.d.ts +28 -7
- package/dist/policy/core/compose-decision.js +75 -54
- package/dist/policy/core/decision-types.d.ts +65 -34
- package/dist/policy/core/feature-decision.d.ts +16 -9
- package/dist/policy/core/feature-decision.js +14 -8
- package/dist/policy/core/pnp-policy-inputs.d.ts +24 -33
- package/dist/policy/core/pnp-policy-inputs.js +28 -56
- package/dist/policy/core/provenance.d.ts +9 -4
- package/dist/policy/core/provenance.js +1 -1
- package/dist/policy/engine.d.ts +7 -23
- package/dist/policy/engine.js +5 -20
- package/dist/policy/sources/PnpPolicySource.d.ts +44 -61
- package/dist/policy/sources/PnpPolicySource.js +117 -118
- package/dist/runtime/SectionControllerBinding.d.ts +279 -0
- package/dist/runtime/SectionControllerBinding.js +450 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts +26 -289
- package/dist/runtime/SectionRuntimeEngine.js +30 -372
- package/dist/runtime/adapter/SectionEngineAdapter.d.ts +19 -80
- package/dist/runtime/adapter/SectionEngineAdapter.js +19 -92
- package/dist/runtime/adapter/dom-event-bridge.d.ts +15 -30
- package/dist/runtime/adapter/dom-event-bridge.js +17 -44
- package/dist/runtime/adapter/subscriber-fanout.d.ts +1 -2
- package/dist/runtime/adapter/subscriber-fanout.js +1 -2
- package/dist/runtime/catalog-context.d.ts +19 -0
- package/dist/runtime/catalog-context.js +28 -0
- package/dist/runtime/composition-emit-scheduler.d.ts +9 -15
- package/dist/runtime/composition-emit-scheduler.js +9 -15
- package/dist/runtime/content-language.d.ts +32 -0
- package/dist/runtime/content-language.js +46 -0
- package/dist/runtime/content-region.d.ts +17 -0
- package/dist/runtime/content-region.js +29 -0
- package/dist/runtime/content-registration.d.ts +13 -0
- package/dist/runtime/content-registration.js +25 -0
- package/dist/runtime/core/SectionEngineCore.d.ts +2 -2
- package/dist/runtime/core/SectionEngineCore.js +2 -2
- package/dist/runtime/core/cohort.d.ts +1 -1
- package/dist/runtime/core/cohort.js +1 -1
- package/dist/runtime/core/engine-input.d.ts +18 -30
- package/dist/runtime/core/engine-input.js +11 -12
- package/dist/runtime/core/engine-output.d.ts +16 -40
- package/dist/runtime/core/engine-output.js +9 -21
- package/dist/runtime/core/engine-readiness.d.ts +5 -37
- package/dist/runtime/core/engine-readiness.js +8 -41
- package/dist/runtime/core/engine-resolver.d.ts +46 -74
- package/dist/runtime/core/engine-resolver.js +18 -43
- package/dist/runtime/core/engine-state.d.ts +28 -36
- package/dist/runtime/core/engine-state.js +18 -21
- package/dist/runtime/core/engine-transition.d.ts +14 -19
- package/dist/runtime/core/engine-transition.js +71 -51
- package/dist/runtime/engine.d.ts +16 -19
- package/dist/runtime/engine.js +12 -19
- package/dist/runtime/page-warnings.d.ts +13 -0
- package/dist/runtime/page-warnings.js +22 -0
- package/dist/runtime/policy-input-forwarding.d.ts +25 -0
- package/dist/runtime/policy-input-forwarding.js +25 -0
- package/dist/runtime/registration-events.d.ts +14 -0
- package/dist/runtime/runtime-event-claim.d.ts +8 -0
- package/dist/runtime/runtime-event-claim.js +15 -0
- package/dist/runtime/shell-event-bridge.d.ts +45 -0
- package/dist/runtime/shell-event-bridge.js +127 -0
- package/dist/runtime/shell-registration.d.ts +73 -0
- package/dist/runtime/shell-registration.js +95 -0
- package/dist/runtime/shell-scope.d.ts +68 -0
- package/dist/runtime/shell-scope.js +188 -0
- package/dist/runtime/tool-host-contract.d.ts +28 -10
- package/dist/runtime/tool-host-contract.js +31 -10
- package/dist/runtime/unclaimed-registration-watch.d.ts +7 -0
- package/dist/runtime/unclaimed-registration-watch.js +23 -0
- package/dist/services/ElementToolStateStore.d.ts +14 -10
- package/dist/services/ElementToolStateStore.js +37 -13
- package/dist/services/HighlightCoordinator.d.ts +22 -28
- package/dist/services/HighlightCoordinator.js +431 -327
- package/dist/services/RangeSerializer.d.ts +10 -1
- package/dist/services/RangeSerializer.js +87 -14
- package/dist/services/TTSService.d.ts +130 -69
- package/dist/services/TTSService.js +713 -570
- package/dist/services/ToolCoordinator.d.ts +43 -66
- package/dist/services/ToolCoordinator.js +164 -208
- package/dist/services/ToolRegistry.d.ts +78 -54
- package/dist/services/ToolRegistry.js +107 -105
- package/dist/services/ToolkitCoordinator.d.ts +261 -175
- package/dist/services/ToolkitCoordinator.js +667 -433
- package/dist/services/audio-handoff.d.ts +6 -7
- package/dist/services/audio-handoff.js +20 -14
- package/dist/services/catalog-owner.d.ts +11 -1
- package/dist/services/catalog-owner.js +25 -0
- package/dist/services/create-tools-config.d.ts +2 -2
- package/dist/services/framework-error-bus.d.ts +3 -6
- package/dist/services/framework-error-bus.js +3 -6
- package/dist/services/framework-error.d.ts +30 -8
- package/dist/services/framework-error.js +9 -7
- package/dist/services/interfaces.d.ts +158 -129
- package/dist/services/section-controller-types.d.ts +15 -0
- package/dist/services/section-session-assignment.d.ts +21 -0
- package/dist/services/section-session-assignment.js +82 -0
- package/dist/services/spoken-audio-cards.d.ts +0 -2
- package/dist/services/spoken-audio-cards.js +1 -3
- package/dist/services/tool-config-defaults.d.ts +3 -3
- package/dist/services/tool-config-defaults.js +3 -3
- package/dist/services/tool-config-validation.d.ts +14 -4
- package/dist/services/tool-config-validation.js +63 -71
- package/dist/services/tool-context.d.ts +0 -8
- package/dist/services/tool-context.js +29 -25
- package/dist/services/tool-icons.d.ts +4 -3
- package/dist/services/tool-icons.js +4 -3
- package/dist/services/tool-instance-id.d.ts +0 -1
- package/dist/services/tool-instance-id.js +0 -6
- package/dist/services/tool-providers/TTSToolProvider.d.ts +41 -80
- package/dist/services/tool-providers/TTSToolProvider.js +81 -40
- package/dist/services/tool-providers/ToolProviderApi.d.ts +10 -4
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +32 -28
- package/dist/services/tool-providers/ToolProviderRegistry.js +121 -107
- package/dist/services/tool-providers/index.d.ts +0 -9
- package/dist/services/tool-providers/index.js +2 -4
- package/dist/services/tool-request.d.ts +27 -12
- package/dist/services/tool-request.js +24 -14
- package/dist/services/toolbar-decided-tools.d.ts +15 -0
- package/dist/services/toolbar-decided-tools.js +28 -0
- package/dist/services/tools-config-normalizer.d.ts +37 -23
- package/dist/services/tools-config-normalizer.js +2 -10
- package/dist/services/tts/browser-provider.d.ts +38 -2
- package/dist/services/tts/browser-provider.js +276 -140
- package/dist/services/tts/catalog-span-alignment.d.ts +2 -17
- package/dist/services/tts/catalog-span-alignment.js +17 -142
- package/dist/services/tts/flat-tree.d.ts +71 -0
- package/dist/services/tts/flat-tree.js +269 -0
- package/dist/services/tts/generated-speech/assemble-plan.d.ts +3 -1
- package/dist/services/tts/generated-speech/assemble-plan.js +3 -1
- package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +2 -0
- package/dist/services/tts/generated-speech/dom/build-dom-plan.js +1 -1
- package/dist/services/tts/generated-speech/math-speech-cache.d.ts +3 -1
- package/dist/services/tts/generated-speech/math-speech-cache.js +5 -1
- package/dist/services/tts/generated-speech/types.d.ts +0 -12
- package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +12 -62
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +38 -5
- package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +3 -6
- package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +6 -4
- package/dist/services/tts/highlight-pipeline/readable-region.js +3 -2
- package/dist/services/tts/highlight-pipeline/types.d.ts +5 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +12 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.js +73 -7
- package/dist/services/tts/math-alignment/index.d.ts +0 -5
- package/dist/services/tts/math-alignment/index.js +5 -25
- package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +9 -1
- package/dist/services/tts/math-alignment/speech-tokenizer.js +38 -36
- package/dist/services/tts/math-alignment/types.d.ts +4 -2
- package/dist/services/tts/math-aware-text-processing.d.ts +5 -1
- package/dist/services/tts/math-aware-text-processing.js +74 -18
- package/dist/services/tts/math-control-names.d.ts +21 -0
- package/dist/services/tts/math-control-names.js +142 -0
- package/dist/services/tts/math-speech.d.ts +16 -1
- package/dist/services/tts/math-speech.js +55 -19
- package/dist/services/tts/provider-options.d.ts +41 -0
- package/dist/services/tts/provider-options.js +1 -0
- package/dist/services/tts/sre-engine.d.ts +7 -0
- package/dist/services/tts/sre-engine.js +11 -0
- package/dist/services/tts/sre-locales.d.ts +14 -0
- package/dist/services/tts/sre-locales.js +57 -0
- package/dist/services/tts/ssml/spoken-text.d.ts +2 -26
- package/dist/services/tts/ssml/spoken-text.js +29 -0
- package/dist/services/tts/start-failure.d.ts +11 -0
- package/dist/services/tts/start-failure.js +19 -0
- package/dist/services/tts/text-processing.d.ts +9 -1
- package/dist/services/tts/text-processing.js +46 -34
- package/dist/services/tts/text-segmentation.d.ts +7 -0
- package/dist/services/tts/text-segmentation.js +47 -0
- package/dist/services/tts-runtime-config.d.ts +44 -18
- package/dist/services/tts-runtime-config.js +22 -93
- package/dist/tools/content-capability-resolution.d.ts +7 -9
- package/dist/tools/content-capability-resolution.js +8 -45
- package/dist/tools/registration.d.ts +58 -0
- package/dist/tools/registration.js +54 -0
- package/dist/tools/registrations/toolbar-registration-helpers.js +2 -2
- package/dist/tools/tool-surface-host.d.ts +6 -10
- package/dist/tools/tool-surface-host.js +44 -42
- package/dist/tools/tool-tag-map.d.ts +5 -15
- package/dist/tools/tool-tag-map.js +6 -20
- package/dist/utils/structural-equality.d.ts +11 -0
- package/dist/utils/structural-equality.js +47 -0
- package/package.json +24 -59
- package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +0 -49
- package/dist/attempt/adapters/activity-to-test-attempt-session.js +0 -138
- package/dist/components/chunks/ItemToolBar-byq80z42.js +0 -40
- package/dist/components/chunks/ItemToolBar-d36g3aw4.js +0 -49
- package/dist/context/runtime-context-consumer.d.ts +0 -13
- package/dist/context/runtime-context-consumer.js +0 -51
- package/dist/item-loader.d.ts +0 -44
- package/dist/item-loader.js +0 -61
- package/dist/policy/core/engine-context.d.ts +0 -29
- package/dist/policy/core/engine-context.js +0 -27
- package/dist/policy/core/policy-source-tag.d.ts +0 -31
- package/dist/policy/core/policy-source-tag.js +0 -28
- package/dist/policy/internal.d.ts +0 -25
- package/dist/policy/internal.js +0 -25
- package/dist/runtime/adapter/coordinator-bridge.d.ts +0 -92
- package/dist/runtime/adapter/coordinator-bridge.js +0 -126
- package/dist/runtime/adapter/framework-error-bridge.d.ts +0 -36
- package/dist/runtime/adapter/framework-error-bridge.js +0 -46
- package/dist/runtime/adapter/instrumentation-bridge.d.ts +0 -31
- package/dist/runtime/adapter/instrumentation-bridge.js +0 -38
- package/dist/runtime/core/engine-stage-derivation.d.ts +0 -18
- package/dist/runtime/core/engine-stage-derivation.js +0 -29
- package/dist/runtime/engine-context.d.ts +0 -39
- package/dist/runtime/engine-context.js +0 -31
- package/dist/runtime/internal.d.ts +0 -43
- package/dist/runtime/internal.js +0 -45
- package/dist/runtime/section-runtime-engine-host-context.d.ts +0 -80
- package/dist/runtime/section-runtime-engine-host-context.js +0 -95
- package/dist/runtime/stage-emit-gate.d.ts +0 -92
- package/dist/runtime/stage-emit-gate.js +0 -82
- package/dist/services/I18nService.d.ts +0 -47
- package/dist/services/I18nService.js +0 -84
- package/dist/services/ThemeProvider.d.ts +0 -95
- package/dist/services/ThemeProvider.js +0 -334
- package/dist/services/ToolConfigResolver.d.ts +0 -161
- package/dist/services/ToolConfigResolver.js +0 -229
- package/dist/services/catalog-media.d.ts +0 -56
- package/dist/services/catalog-media.js +0 -190
- package/dist/services/createDefaultToolRegistry.d.ts +0 -53
- package/dist/services/createDefaultToolRegistry.js +0 -45
- package/dist/services/defaultPersonalNeedsProfile.d.ts +0 -21
- package/dist/services/defaultPersonalNeedsProfile.js +0 -26
- package/dist/services/pnp-standard-features.d.ts +0 -238
- package/dist/services/pnp-standard-features.js +0 -338
- package/dist/services/tool-providers/CortexToolProvider.d.ts +0 -13
- package/dist/services/tool-providers/CortexToolProvider.js +0 -32
- package/dist/services/tool-providers/DesmosToolProvider.d.ts +0 -14
- package/dist/services/tool-providers/DesmosToolProvider.js +0 -32
- package/dist/services/tool-providers/GeoGebraToolProvider.d.ts +0 -16
- package/dist/services/tool-providers/GeoGebraToolProvider.js +0 -32
- package/dist/services/tool-providers/LazyCalculatorToolProvider.d.ts +0 -35
- package/dist/services/tool-providers/LazyCalculatorToolProvider.js +0 -95
- package/dist/services/tts/visual-line-ranges.d.ts +0 -18
- package/dist/services/tts/visual-line-ranges.js +0 -226
- package/dist/tools/client.d.ts +0 -12
- package/dist/tools/client.js +0 -14
- package/dist/tools/index.d.ts +0 -8
- package/dist/tools/index.js +0 -10
- package/dist/tools/internal.d.ts +0 -43
- package/dist/tools/internal.js +0 -41
- package/dist/tools/response-discovery.d.ts +0 -61
- package/dist/tools/response-discovery.js +0 -113
- package/dist/tools/types.d.ts +0 -337
- package/dist/tools/types.js +0 -12
- package/dist/tools/variant-resolver.d.ts +0 -41
- package/dist/tools/variant-resolver.js +0 -207
package/README.md
CHANGED
|
@@ -4,6 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
This is not an opinionated framework or monolithic "player" - it's a toolkit that solves specific problems through centralized service management.
|
|
6
6
|
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @pie-players/pie-assessment-toolkit @pie-players/pie-default-tool-loaders
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The examples build their tool registry with `createPackagedToolRegistry()` from
|
|
14
|
+
`@pie-players/pie-default-tool-loaders`, so a host that imports it declares it
|
|
15
|
+
in its own dependencies. The section player depends on it too, but strict
|
|
16
|
+
installers such as pnpm expose only the packages a host declares.
|
|
17
|
+
|
|
18
|
+
Server-backed TTS (`backend: "server"`) loads
|
|
19
|
+
`@pie-players/tts-client-server`, a dependency of
|
|
20
|
+
`@pie-players/pie-default-tool-loaders` whose TTS registration imports it on
|
|
21
|
+
first use. If it fails to load, TTS initialization reports a `provider-init`
|
|
22
|
+
framework error and falls back to browser speech.
|
|
23
|
+
|
|
24
|
+
A `ToolkitCoordinator` registers tool providers only from its `toolRegistry`.
|
|
25
|
+
Built without one, it adopts the registry of the toolkit it is bound to, such as
|
|
26
|
+
the section player's, which then validates its config and registers its
|
|
27
|
+
providers. A registry passed at construction is never replaced. Bound to a
|
|
28
|
+
toolkit that has none, it registers no providers, skips tool-id and placement
|
|
29
|
+
validation, and warns once (`tools.registryUnavailable`).
|
|
30
|
+
|
|
7
31
|
## What's New: ToolkitCoordinator
|
|
8
32
|
|
|
9
33
|
✨ **Centralized Service Management**: The new `ToolkitCoordinator` provides a single entry point for all toolkit services, simplifying initialization and configuration.
|
|
@@ -28,16 +52,26 @@ player.toolCoordinator = toolCoordinator;
|
|
|
28
52
|
|
|
29
53
|
**After** (coordinator orchestrates):
|
|
30
54
|
```typescript
|
|
55
|
+
import {
|
|
56
|
+
createPackagedToolRegistry,
|
|
57
|
+
DEFAULT_TOOL_MODULE_LOADERS,
|
|
58
|
+
} from '@pie-players/pie-default-tool-loaders';
|
|
59
|
+
|
|
31
60
|
// Create one coordinator with configuration
|
|
61
|
+
const toolRegistry = createPackagedToolRegistry({
|
|
62
|
+
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
63
|
+
});
|
|
32
64
|
const toolkitCoordinator = new ToolkitCoordinator({
|
|
33
65
|
assessmentId: 'my-assessment',
|
|
66
|
+
toolRegistry,
|
|
34
67
|
tools: {
|
|
35
68
|
providers: {
|
|
36
69
|
textToSpeech: { enabled: true, backend: 'browser' },
|
|
70
|
+
// Desmos needs an application key; see "Simple Default" below.
|
|
37
71
|
calculator: { enabled: true }
|
|
38
72
|
},
|
|
39
73
|
placement: {
|
|
40
|
-
section: ['
|
|
74
|
+
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
41
75
|
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
42
76
|
passage: ['textToSpeech']
|
|
43
77
|
}
|
|
@@ -86,6 +120,7 @@ provider path is the item-player loader config:
|
|
|
86
120
|
|
|
87
121
|
- `pie-toolkit-runtime-owned`
|
|
88
122
|
- `pie-toolkit-runtime-inherited`
|
|
123
|
+
- `pie-toolkit-runtime-ready`
|
|
89
124
|
- `pie-toolkit-ready`
|
|
90
125
|
- `pie-toolkit-section-ready`
|
|
91
126
|
- `pie-toolkit-framework-error`
|
|
@@ -109,20 +144,20 @@ See the [ToolkitCoordinator section in the architecture overview](../../docs/arc
|
|
|
109
144
|
1. **Centralized Coordination**: ToolkitCoordinator orchestrates all services
|
|
110
145
|
2. **Composable Services**: Import only what you need (or use coordinator for convenience)
|
|
111
146
|
3. **No Framework Lock-in**: Works with any JavaScript framework
|
|
112
|
-
4. **Product Control**: Products control navigation, persistence,
|
|
147
|
+
4. **Product Control**: Products control navigation, layout and backend. Session persistence defaults to `localStorage` at the section and assessment layers, stored per attempt id and inactive without one, and a product replaces either through its persistence hook (`createSectionSessionPersistence` on the coordinator, `createAssessmentSessionPersistence` on the assessment player)
|
|
113
148
|
5. **Standard Contracts**: Well-defined event types for component communication
|
|
114
149
|
6. **Element-Level Granularity**: Tool state tracked per PIE element, not per item
|
|
115
150
|
7. **State Separation**: Tool state (ephemeral) separate from PIE session data (persistent)
|
|
116
151
|
|
|
117
|
-
## Configuration
|
|
152
|
+
## Configuration: attributes and a constructed coordinator
|
|
118
153
|
|
|
119
|
-
This package and `@pie-players/pie-section-player`
|
|
120
|
-
|
|
121
|
-
|
|
154
|
+
This package and `@pie-players/pie-section-player` give each input one entry
|
|
155
|
+
point (see [One tier per input](#one-tier-per-input)). A host that needs more
|
|
156
|
+
than the element inputs builds the coordinator itself.
|
|
122
157
|
|
|
123
|
-
###
|
|
158
|
+
### Element inputs and a constructed coordinator
|
|
124
159
|
|
|
125
|
-
- **
|
|
160
|
+
- **Element attributes / properties.** Use these for the
|
|
126
161
|
common cases that are static for the lifetime of the player or that hosts
|
|
127
162
|
want to set declaratively in HTML / templating frameworks. Example:
|
|
128
163
|
|
|
@@ -134,15 +169,24 @@ tier; the choice is about ergonomics, not capability.
|
|
|
134
169
|
></pie-assessment-toolkit>
|
|
135
170
|
```
|
|
136
171
|
|
|
137
|
-
- **
|
|
172
|
+
- **A constructed `ToolkitCoordinator` (or a
|
|
138
173
|
`runtime` object on consumer CEs).** Use this for advanced cases: composed
|
|
139
|
-
configuration, dynamic overrides, runtime mutation, fields without
|
|
140
|
-
|
|
174
|
+
configuration, dynamic overrides, runtime mutation, fields without an
|
|
175
|
+
attribute, or anything that benefits from being a single typed
|
|
141
176
|
object passed by reference. Example:
|
|
142
177
|
|
|
143
178
|
```ts
|
|
179
|
+
import {
|
|
180
|
+
createPackagedToolRegistry,
|
|
181
|
+
DEFAULT_TOOL_MODULE_LOADERS,
|
|
182
|
+
} from "@pie-players/pie-default-tool-loaders";
|
|
183
|
+
|
|
184
|
+
const toolRegistry = createPackagedToolRegistry({
|
|
185
|
+
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
186
|
+
});
|
|
144
187
|
const coordinator = new ToolkitCoordinator({
|
|
145
188
|
assessmentId: "my-assessment",
|
|
189
|
+
toolRegistry,
|
|
146
190
|
toolConfigStrictness: "warn",
|
|
147
191
|
tools: {
|
|
148
192
|
providers: { calculator: { enabled: true } },
|
|
@@ -158,38 +202,32 @@ Top-level attributes use kebab-case (`assessment-id`,
|
|
|
158
202
|
`tool-config-strictness`). Section-player runtime configuration is grouped
|
|
159
203
|
under the `runtime` object instead of duplicated as top-level props.
|
|
160
204
|
|
|
161
|
-
###
|
|
205
|
+
### One tier per input
|
|
162
206
|
|
|
163
|
-
|
|
164
|
-
|
|
207
|
+
Each input has one entry point. On the section-player layout elements,
|
|
208
|
+
`runtime` carries `assessmentId`, the player, tool, accessibility,
|
|
209
|
+
coordinator, env and isolation fields, runtime factories and the `on*`
|
|
210
|
+
callbacks; attributes carry section identity, layout controls, `nds-icons`,
|
|
211
|
+
`locale` and `tool-config-strictness`. Unset inputs take their documented
|
|
212
|
+
defaults.
|
|
165
213
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
3. Use documented defaults when neither is provided.
|
|
214
|
+
`<pie-assessment-toolkit>` takes the same fields as its own properties,
|
|
215
|
+
with `assessment-id`, `nds-icons`, `locale` and `tool-config-strictness` as
|
|
216
|
+
attributes, and reads `pnpEnforcement` from `tools.pnpEnforcement`.
|
|
170
217
|
|
|
171
|
-
### Canonical
|
|
172
|
-
|
|
173
|
-
The tier-1 attribute set is the same shape across
|
|
174
|
-
`pie-assessment-toolkit`, `pie-section-player-base`, and the
|
|
175
|
-
`pie-section-player-*` layout elements (locked in M5). Every tier-1
|
|
176
|
-
surface obeys the strict mirror rule:
|
|
177
|
-
|
|
178
|
-
```
|
|
179
|
-
kebab-attribute ↔ camelCaseProp ↔ runtime.<sameCamelCaseKey>
|
|
180
|
-
```
|
|
218
|
+
### Canonical attribute set
|
|
181
219
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
- Identity: `assessment-id`, `section-id`, `attempt-id`
|
|
220
|
+
- Identity: `assessment-id` on the toolkit, `section-id`, `attempt-id`
|
|
185
221
|
- Runtime config on section-player CEs: `runtime`
|
|
186
222
|
- Toolkit-only object properties: `tools`, `tool-registry`, `coordinator`,
|
|
187
223
|
`accessibility`
|
|
224
|
+
- Interface: `nds-icons`, `locale`
|
|
188
225
|
- Diagnostics: `tool-config-strictness`, `debug`. Framework-error
|
|
189
|
-
delivery is via the
|
|
226
|
+
delivery is via the `onFrameworkError` callback (a toolkit property,
|
|
227
|
+
`runtime.onFrameworkError` on a section player) and the
|
|
190
228
|
`framework-error` DOM event dispatched on the layout CE host.
|
|
191
229
|
|
|
192
|
-
|
|
230
|
+
Inputs outside the runtime config:
|
|
193
231
|
|
|
194
232
|
- Identity (`section-id`, `attempt-id`, `section`): per-attempt host
|
|
195
233
|
state, not configuration.
|
|
@@ -201,17 +239,15 @@ Documented exceptions to the mirror rule:
|
|
|
201
239
|
`runtime.tools.placement.{item,passage}` directly.
|
|
202
240
|
- Runtime-only keys on the section-player layout CEs
|
|
203
241
|
(`createSectionController`, `isolation`): accepted only via
|
|
204
|
-
`runtime.<key>`.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
to override coordinator inheritance should pass an explicit
|
|
210
|
-
`coordinator={...}` instead.
|
|
242
|
+
`runtime.<key>`. Section-player layouts forward both to the wrapped
|
|
243
|
+
toolkit as properties. `<pie-assessment-toolkit>` keeps
|
|
244
|
+
`createSectionController` JS-only and takes `isolation` as an
|
|
245
|
+
attribute or a property. A `coordinator` passed to it wins over an
|
|
246
|
+
outer toolkit's.
|
|
211
247
|
|
|
212
|
-
### When to add
|
|
248
|
+
### When to add an attribute
|
|
213
249
|
|
|
214
|
-
Add
|
|
250
|
+
Add an attribute only if all of the following hold:
|
|
215
251
|
|
|
216
252
|
- It is a common case that hosts set without composing a `ToolkitCoordinator`
|
|
217
253
|
/ `runtime` object.
|
|
@@ -229,17 +265,26 @@ Otherwise expose it through the configuration object only.
|
|
|
229
265
|
|
|
230
266
|
```typescript
|
|
231
267
|
import { ToolkitCoordinator } from '@pie-players/pie-assessment-toolkit';
|
|
268
|
+
import {
|
|
269
|
+
createPackagedToolRegistry,
|
|
270
|
+
DEFAULT_TOOL_MODULE_LOADERS,
|
|
271
|
+
} from '@pie-players/pie-default-tool-loaders';
|
|
232
272
|
|
|
233
273
|
// Create coordinator with configuration
|
|
274
|
+
const toolRegistry = createPackagedToolRegistry({
|
|
275
|
+
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
276
|
+
});
|
|
234
277
|
const coordinator = new ToolkitCoordinator({
|
|
235
278
|
assessmentId: 'demo-assessment',
|
|
279
|
+
toolRegistry,
|
|
236
280
|
tools: {
|
|
237
281
|
providers: {
|
|
238
282
|
textToSpeech: { enabled: true, backend: 'browser' },
|
|
283
|
+
// Desmos needs an application key; see "Simple Default" below.
|
|
239
284
|
calculator: { enabled: true }
|
|
240
285
|
},
|
|
241
286
|
placement: {
|
|
242
|
-
section: ['
|
|
287
|
+
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
243
288
|
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
244
289
|
passage: ['textToSpeech']
|
|
245
290
|
}
|
|
@@ -286,6 +331,7 @@ Behavior pins (PIE-512 Phase D):
|
|
|
286
331
|
|
|
287
332
|
- Subscribe **after** the first `getOrCreateSectionController(...)` resolves; calling subscribe before any cohort exists throws.
|
|
288
333
|
- On every cohort transition (navigation, fresh `getOrCreateSectionController` for a new section), the listener is automatically migrated to the new controller and receives a snapshot replay (`content-loaded` × N then `section-loading-complete`) in the same order a fresh subscriber would have seen.
|
|
334
|
+
- A section's subscriptions receive `content-loaded` only for that section's renderables, live or replayed; the previous section's loads stay with it. A renderable in both sections, such as a passage that stays mounted across the switch, counts as loaded in each.
|
|
289
335
|
- Subscribing the **same listener function** twice replaces the first subscription (filter args from the second call win).
|
|
290
336
|
- A listener that throws is caught and `console.warn`-logged; the throw does not interrupt fan-out to other listeners.
|
|
291
337
|
|
|
@@ -319,7 +365,7 @@ What this means in practice for typed integrations:
|
|
|
319
365
|
|
|
320
366
|
- **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.**
|
|
321
367
|
- **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.
|
|
322
|
-
- **New precondition.** `subscribe*`
|
|
368
|
+
- **New precondition.** `subscribe*` throws until the first `getOrCreateSectionController(...)` call; a listener added while a section is starting binds when that section becomes active. 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.
|
|
323
369
|
- **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.
|
|
324
370
|
- **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`.
|
|
325
371
|
- **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.
|
|
@@ -387,18 +433,136 @@ const catalogResolver = new AccessibilityCatalogResolver([], 'en-US');
|
|
|
387
433
|
await ttsService.initialize(new BrowserTTSProvider());
|
|
388
434
|
ttsService.setCatalogResolver(catalogResolver);
|
|
389
435
|
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
436
|
+
// Use them from host-built UI
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
No player element takes services one by one. The section and assessment
|
|
440
|
+
players reach services only through a coordinator, `runtime.coordinator` on a
|
|
441
|
+
section player and `coordinator` on an assessment player; manually created
|
|
442
|
+
services serve host code that drives them directly.
|
|
443
|
+
|
|
444
|
+
### Without a Section Player
|
|
445
|
+
|
|
446
|
+
`<pie-assessment-toolkit>` needs no section. Bind none and it provides tools,
|
|
447
|
+
policy and services to the item toolbars and item players inside it, which is
|
|
448
|
+
how the toolkit accompanies a plain item player. `<pie-item-scope>` holds the
|
|
449
|
+
item for its tools, as it does in a section player's card. The tree this
|
|
450
|
+
builds:
|
|
451
|
+
|
|
452
|
+
```html
|
|
453
|
+
<pie-assessment-toolkit>
|
|
454
|
+
<pie-item-scope item-id="q1">
|
|
455
|
+
<pie-item-toolbar></pie-item-toolbar>
|
|
456
|
+
<div data-region="content">
|
|
457
|
+
<pie-item-player></pie-item-player>
|
|
458
|
+
</div>
|
|
459
|
+
</pie-item-scope>
|
|
460
|
+
</pie-assessment-toolkit>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
The host sets the toolkit's properties before the tree enters the document. A
|
|
464
|
+
scope registers as soon as it mounts, and that first registration starts the
|
|
465
|
+
coordinator from the toolkit's inputs at that moment; see the binding rules
|
|
466
|
+
below.
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
import '@pie-players/pie-assessment-toolkit/components/pie-assessment-toolkit-element';
|
|
470
|
+
import '@pie-players/pie-assessment-toolkit/components/item-scope-element';
|
|
471
|
+
import '@pie-players/pie-assessment-toolkit/components/item-toolbar-element';
|
|
472
|
+
import '@pie-players/pie-item-player';
|
|
473
|
+
import {
|
|
474
|
+
createPackagedToolRegistry,
|
|
475
|
+
DEFAULT_TOOL_MODULE_LOADERS,
|
|
476
|
+
} from '@pie-players/pie-default-tool-loaders';
|
|
477
|
+
|
|
478
|
+
// With no section player, the registry's loaders are what define the tool elements.
|
|
479
|
+
const toolRegistry = createPackagedToolRegistry({
|
|
480
|
+
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const toolkit = document.createElement('pie-assessment-toolkit');
|
|
484
|
+
toolkit.tools = {
|
|
485
|
+
pnpEnforcement: 'on',
|
|
486
|
+
placement: { item: ['textToSpeech', 'calculator'] },
|
|
487
|
+
};
|
|
488
|
+
toolkit.toolRegistry = toolRegistry;
|
|
489
|
+
toolkit.toolContextResolvers = toolContextResolvers;
|
|
490
|
+
toolkit.assessment = { id: 'a1', personalNeedsProfile: { supports: ['calculator'] } };
|
|
491
|
+
|
|
492
|
+
const scope = document.createElement('pie-item-scope');
|
|
493
|
+
scope.setAttribute('item-id', 'q1');
|
|
494
|
+
scope.item = item;
|
|
495
|
+
scope.innerHTML = `
|
|
496
|
+
<pie-item-toolbar></pie-item-toolbar>
|
|
497
|
+
<div data-region="content"><pie-item-player></pie-item-player></div>
|
|
498
|
+
`;
|
|
499
|
+
|
|
500
|
+
toolkit.append(scope);
|
|
501
|
+
container.append(toolkit);
|
|
394
502
|
```
|
|
395
503
|
|
|
504
|
+
The toolbars and tools inside the scope take the item and its id from it, and
|
|
505
|
+
act on the scope, or on the element its `scopeElement` property names;
|
|
506
|
+
`textToSpeech` reads the scope's `[data-region="content"]` first. A toolbar's
|
|
507
|
+
own `item`, `item-id` and `scopeElement` still override the scope, and its
|
|
508
|
+
`toolRegistry` the toolkit's. The scope
|
|
509
|
+
registers the item with the toolkit once it finds one above it, so it may mount
|
|
510
|
+
first, and the toolkit files the item's accessibility catalogs, which read-aloud
|
|
511
|
+
speaks in place of the markup they name. Item player events pass through the
|
|
512
|
+
scope unchanged, and the session stays the item player's. A registry built
|
|
513
|
+
without `toolModuleLoaders` renders toolbar buttons whose tool elements never
|
|
514
|
+
load.
|
|
515
|
+
|
|
516
|
+
A profile change is a new `assessment` value; the toolbars re-derive on the
|
|
517
|
+
policy change it emits. The toolkit announces `runtime-ready`, with
|
|
518
|
+
`{ runtimeId, coordinator, ownership }`, once per coordinator, with or without a
|
|
519
|
+
section. Without one, the coordinator starts at the first item that registers,
|
|
520
|
+
and a host reads its readiness from `coordinator.waitUntilReady()` or
|
|
521
|
+
`isReady()`; `toolkit-ready` and `section-ready` wait for a bound section. The
|
|
522
|
+
toolkit emits no stage events: `pie-stage-change` is the section player's. A
|
|
523
|
+
host that holds
|
|
524
|
+
the coordinator from `runtime-ready`, or passes its own as `coordinator`,
|
|
525
|
+
changes the profile with `coordinator.updateAssessment(...)`; the toolkit
|
|
526
|
+
applies its `assessment` property only to a coordinator it owns. The
|
|
527
|
+
`calculator-pnp` section demo composes the owned-coordinator form.
|
|
528
|
+
|
|
529
|
+
The toolkit builds its own coordinator at mount from `tools`, `enabled-tools`,
|
|
530
|
+
`assessment-id`, `accessibility`, `lazy-init`, `tool-config-strictness` and
|
|
531
|
+
`toolRegistry`. Its first content binds it: the section, or without one the
|
|
532
|
+
first item scope that registers. Content that arrives after one of those inputs
|
|
533
|
+
changed binds a coordinator rebuilt from the current values, and the toolbars
|
|
534
|
+
move to it. After that, a change to them is reported once in the console and
|
|
535
|
+
does not reach the coordinator, except a `toolRegistry` given to a toolkit that
|
|
536
|
+
had none, which the coordinator adopts in place; `tools.pnpEnforcement`,
|
|
537
|
+
`assessment` and `toolContextResolvers` apply to it at any
|
|
538
|
+
time. That
|
|
539
|
+
coordinator reports feature policy asked with no assessment bound only while
|
|
540
|
+
`tools.pnpEnforcement` is `on`: a toolkit given no `assessment` and no enforcement
|
|
541
|
+
has asked for no accommodation.
|
|
542
|
+
|
|
543
|
+
A toolkit nested in another inherits the outer one's coordinator when the outer
|
|
544
|
+
one has a coordinator by the time the inner one connects, which holds for both
|
|
545
|
+
mounted together. Otherwise the inner one builds its own and keeps it, and
|
|
546
|
+
reports an outer coordinator arriving later once in the console. `isolation`
|
|
547
|
+
`"force"` keeps a nested toolkit on its own coordinator by design.
|
|
548
|
+
|
|
549
|
+
Text-to-speech starts at the toolkit's first content, once the section composes
|
|
550
|
+
or the first item scope registers, and `coordinator.waitUntilReady()` waits for it. With `lazy-init` it starts at the first read-aloud instead, unless policy
|
|
551
|
+
grants it. A tool provider or text-to-speech that fails to start is a
|
|
552
|
+
recoverable framework error: the tool reports itself unavailable and the
|
|
553
|
+
assessment goes on. When policy grants the tool, through an item or district
|
|
554
|
+
requirement or a profile support, the failure is fatal, including one that a
|
|
555
|
+
later policy change grants.
|
|
556
|
+
|
|
396
557
|
## Tool Configuration Model
|
|
397
558
|
|
|
398
559
|
The toolkit uses one canonical `tools` model with three concerns:
|
|
399
560
|
|
|
400
561
|
- `policy`: allow/block constraints (global gates)
|
|
401
|
-
- `placement`: where tools appear (`
|
|
562
|
+
- `placement`: where tools appear (`section`, `item`, `passage`). Each tool
|
|
563
|
+
declares the levels it supports; `calculator` is item-only, and a tool placed
|
|
564
|
+
at a level it does not support fails validation, which throws under the
|
|
565
|
+
default `toolConfigStrictness: "error"`.
|
|
402
566
|
- `providers`: provider/runtime options (calculator, textToSpeech, etc.)
|
|
403
567
|
|
|
404
568
|
Example:
|
|
@@ -410,11 +574,9 @@ tools: {
|
|
|
410
574
|
blocked: ['graph']
|
|
411
575
|
},
|
|
412
576
|
placement: {
|
|
413
|
-
|
|
414
|
-
section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
577
|
+
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
415
578
|
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
416
|
-
passage: ['textToSpeech']
|
|
417
|
-
rubric: []
|
|
579
|
+
passage: ['textToSpeech']
|
|
418
580
|
},
|
|
419
581
|
providers: {
|
|
420
582
|
calculator: {
|
|
@@ -465,7 +627,8 @@ tools: {
|
|
|
465
627
|
**Available Item-Level Tools:**
|
|
466
628
|
- **TTS (Text-to-Speech)**: Reads the specific question/passage text
|
|
467
629
|
- **Answer Eliminator**: Strikes through answer choices for that question
|
|
468
|
-
- **
|
|
630
|
+
- **Calculator**: Basic, scientific or graphing calculator
|
|
631
|
+
- **Highlighter** (`annotationToolbar`): Highlights and annotates selected text; it opens from a text selection, outside the toolbars
|
|
469
632
|
|
|
470
633
|
**Example Use Case:**
|
|
471
634
|
A student uses answer eliminator on Question 3 to cross out choices B and D. When they navigate to Question 4, they see fresh, uneliminated choices. When they return to Question 3, their eliminations are restored.
|
|
@@ -477,38 +640,31 @@ Tools that **float above the entire assessment** and persist across questions:
|
|
|
477
640
|
```typescript
|
|
478
641
|
tools: {
|
|
479
642
|
placement: {
|
|
480
|
-
section: ['
|
|
481
|
-
},
|
|
482
|
-
providers: {
|
|
483
|
-
calculator: {
|
|
484
|
-
enabled: true,
|
|
485
|
-
provider: {
|
|
486
|
-
runtime: { authFetcher: async () => { /* ... */ } }
|
|
487
|
-
}
|
|
488
|
-
}
|
|
643
|
+
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler', 'theme']
|
|
489
644
|
}
|
|
490
645
|
}
|
|
491
646
|
```
|
|
492
647
|
|
|
648
|
+
The section player renders the section toolbar only when its `show-toolbar`
|
|
649
|
+
attribute is `true`; the attribute defaults to `false`.
|
|
650
|
+
|
|
493
651
|
**Characteristics:**
|
|
494
652
|
- **Scope**: Section-wide, shared across all questions
|
|
495
653
|
- **Lifecycle**: Single instance initialized for entire section
|
|
496
|
-
- **State**: Persistent (
|
|
654
|
+
- **State**: Persistent (a tool stays open and in place as you navigate)
|
|
497
655
|
- **UI Pattern**: Draggable floating panels/overlays with z-index management
|
|
498
656
|
- **State Persistence**: Global state maintained throughout section
|
|
499
657
|
|
|
500
658
|
**Available Floating Tools:**
|
|
501
|
-
- **
|
|
502
|
-
- **Graph**: Graphing tool for plotting functions
|
|
659
|
+
- **Graph**: Coordinate plane
|
|
503
660
|
- **Periodic Table**: Interactive periodic table reference
|
|
504
661
|
- **Protractor**: Angle measurement tool
|
|
505
662
|
- **Ruler**: Linear measurement tool (metric/imperial)
|
|
506
663
|
- **Line Reader**: Reading guide/masking overlay
|
|
507
|
-
- **
|
|
508
|
-
- **Color Scheme**: High-contrast color adjustments
|
|
664
|
+
- **Color Scheme** (`theme`): High-contrast color adjustments
|
|
509
665
|
|
|
510
666
|
**Example Use Case:**
|
|
511
|
-
A student opens the
|
|
667
|
+
A student opens the periodic table on Question 2 and moves it beside the passage. When they navigate to Question 7, it is still open where they left it.
|
|
512
668
|
|
|
513
669
|
### When to Use Each
|
|
514
670
|
|
|
@@ -529,15 +685,24 @@ Use **floating tools** when:
|
|
|
529
685
|
Complete example showing both types:
|
|
530
686
|
|
|
531
687
|
```typescript
|
|
688
|
+
import {
|
|
689
|
+
createPackagedToolRegistry,
|
|
690
|
+
DEFAULT_TOOL_MODULE_LOADERS,
|
|
691
|
+
} from '@pie-players/pie-default-tool-loaders';
|
|
692
|
+
|
|
693
|
+
const toolRegistry = createPackagedToolRegistry({
|
|
694
|
+
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
695
|
+
});
|
|
532
696
|
const coordinator = new ToolkitCoordinator({
|
|
533
697
|
assessmentId: 'math-exam',
|
|
698
|
+
toolRegistry,
|
|
534
699
|
tools: {
|
|
535
700
|
placement: {
|
|
536
701
|
// Contextual placement
|
|
537
702
|
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
538
703
|
passage: ['textToSpeech'],
|
|
539
704
|
// Section-level utilities
|
|
540
|
-
section: ['
|
|
705
|
+
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler', 'theme']
|
|
541
706
|
},
|
|
542
707
|
providers: {
|
|
543
708
|
calculator: {
|
|
@@ -561,16 +726,28 @@ const coordinator = new ToolkitCoordinator({
|
|
|
561
726
|
});
|
|
562
727
|
```
|
|
563
728
|
|
|
564
|
-
**
|
|
729
|
+
**A common placement:**
|
|
565
730
|
|
|
566
|
-
|
|
731
|
+
A coordinator shows a tool only where `tools.placement` lists it. Its default
|
|
732
|
+
placement is empty, so a coordinator configured without one shows no tools, and
|
|
733
|
+
a profile grant does not place a tool either. This placement covers the
|
|
734
|
+
commonly used tools:
|
|
567
735
|
|
|
568
736
|
```typescript
|
|
737
|
+
import {
|
|
738
|
+
createPackagedToolRegistry,
|
|
739
|
+
DEFAULT_TOOL_MODULE_LOADERS,
|
|
740
|
+
} from '@pie-players/pie-default-tool-loaders';
|
|
741
|
+
|
|
742
|
+
const toolRegistry = createPackagedToolRegistry({
|
|
743
|
+
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
744
|
+
});
|
|
569
745
|
const coordinator = new ToolkitCoordinator({
|
|
570
746
|
assessmentId: 'my-assessment',
|
|
747
|
+
toolRegistry,
|
|
571
748
|
tools: {
|
|
572
749
|
placement: {
|
|
573
|
-
section: ['
|
|
750
|
+
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
574
751
|
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
575
752
|
passage: ['textToSpeech']
|
|
576
753
|
},
|
|
@@ -588,12 +765,14 @@ const coordinator = new ToolkitCoordinator({
|
|
|
588
765
|
```
|
|
589
766
|
|
|
590
767
|
The ToolkitCoordinator handles service initialization, provider management, and
|
|
591
|
-
state coordination. With no calculator provider configuration, the
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
`
|
|
596
|
-
|
|
768
|
+
state coordination. With no calculator provider configuration, the Desmos
|
|
769
|
+
implementation remains the default. Desmos's CDN rejects a `calculator.js`
|
|
770
|
+
request without an `apiKey` (HTTP 403), so the Desmos calculator opens only when
|
|
771
|
+
the host supplies `provider.init.apiKey` or `provider.runtime.authFetcher`, or
|
|
772
|
+
has already loaded `window.Desmos`; the adapter's keyless request grants no
|
|
773
|
+
Desmos license either. Runtime key delivery avoids committing the key but does
|
|
774
|
+
not hide it from the browser's Desmos script request. The bundled open-source
|
|
775
|
+
implementation below needs no key.
|
|
597
776
|
|
|
598
777
|
Select the separate GeoGebra implementation explicitly without changing the
|
|
599
778
|
calculator capability, placement, or item policy:
|
|
@@ -630,7 +809,8 @@ tools: {
|
|
|
630
809
|
providers: {
|
|
631
810
|
textToSpeech: {
|
|
632
811
|
enabled: true,
|
|
633
|
-
backend: '
|
|
812
|
+
backend: 'server',
|
|
813
|
+
serverProvider: 'polly'
|
|
634
814
|
}
|
|
635
815
|
}
|
|
636
816
|
}
|
|
@@ -646,7 +826,7 @@ You can still set `apiEndpoint` explicitly when your host route is not `/api/tts
|
|
|
646
826
|
|
|
647
827
|
### Inline TTS Speed Options
|
|
648
828
|
|
|
649
|
-
Inline TTS speed buttons are configurable via `speedOptions`
|
|
829
|
+
Inline TTS speed buttons are configurable via `speedOptions` on the provider config.
|
|
650
830
|
|
|
651
831
|
```typescript
|
|
652
832
|
tools: {
|
|
@@ -654,9 +834,7 @@ tools: {
|
|
|
654
834
|
textToSpeech: {
|
|
655
835
|
enabled: true,
|
|
656
836
|
backend: "browser",
|
|
657
|
-
|
|
658
|
-
speedOptions: [2, 1.25, 1.5] // host options keep this order; Normal is added if omitted
|
|
659
|
-
}
|
|
837
|
+
speedOptions: [2, 1.25, 1.5] // host options keep this order; Normal is added if omitted
|
|
660
838
|
}
|
|
661
839
|
}
|
|
662
840
|
}
|
|
@@ -673,13 +851,11 @@ tools: {
|
|
|
673
851
|
enabled: true,
|
|
674
852
|
backend: "server",
|
|
675
853
|
serverProvider: "custom",
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
]
|
|
682
|
-
}
|
|
854
|
+
speedOptions: [
|
|
855
|
+
{ rate: 0.8, label: "Slow", ariaLabel: "Slow speed" },
|
|
856
|
+
{ rate: 1, label: "Normal", ariaLabel: "Normal speed", default: true },
|
|
857
|
+
{ rate: 1.5, label: "Fast", ariaLabel: "Fast speed" }
|
|
858
|
+
]
|
|
683
859
|
}
|
|
684
860
|
}
|
|
685
861
|
}
|
|
@@ -704,25 +880,15 @@ tools: {
|
|
|
704
880
|
- Speed choices are exposed as a `Playback speed` radio group: one option is
|
|
705
881
|
always selected, and choosing the already-selected speed is a no-op.
|
|
706
882
|
- If only one speed option remains, the speed group is hidden by default. Set
|
|
707
|
-
`showSingleSpeedOption: true`
|
|
708
|
-
|
|
709
|
-
### Runtime Fallback: Server TTS -> Browser TTS
|
|
710
|
-
|
|
711
|
-
When server-backed playback fails at runtime (for example `503`, network outage,
|
|
712
|
-
or synthesized asset fetch failure), `TTSService` now performs a one-time
|
|
713
|
-
runtime fallback for that session:
|
|
714
|
-
|
|
715
|
-
1. Switches provider from server-backed implementation to browser speech synthesis.
|
|
716
|
-
2. Rebinds highlight callbacks to the browser provider.
|
|
717
|
-
3. Retries the same `speak()` request once.
|
|
883
|
+
`showSingleSpeedOption: true` on the provider config to surface that one-option state.
|
|
718
884
|
|
|
719
|
-
|
|
720
|
-
incidents without requiring host-side reconfiguration.
|
|
885
|
+
### Browser Fallback
|
|
721
886
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
887
|
+
The coordinator owns fallback to browser speech. When a server-backed provider
|
|
888
|
+
fails to initialize, the coordinator re-initializes TTS on the browser backend
|
|
889
|
+
and reports `pie-tool-init-fallback`. `TTSService.initialize` itself rejects
|
|
890
|
+
when its provider fails to start, and a playback failure after initialization
|
|
891
|
+
rejects that read without switching provider.
|
|
726
892
|
|
|
727
893
|
`provider.runtime.authFetcher` is optional. Add it only when your host environment
|
|
728
894
|
requires runtime auth material for TTS requests:
|
|
@@ -732,7 +898,8 @@ tools: {
|
|
|
732
898
|
providers: {
|
|
733
899
|
textToSpeech: {
|
|
734
900
|
enabled: true,
|
|
735
|
-
backend: '
|
|
901
|
+
backend: 'server',
|
|
902
|
+
serverProvider: 'polly',
|
|
736
903
|
apiEndpoint: '/api/tts',
|
|
737
904
|
provider: {
|
|
738
905
|
runtime: {
|
|
@@ -747,6 +914,18 @@ tools: {
|
|
|
747
914
|
}
|
|
748
915
|
```
|
|
749
916
|
|
|
917
|
+
The fetch completes before the first TTS request, and its result merges over the
|
|
918
|
+
provider config: a returned `authToken` is sent as `Authorization: Bearer <token>`
|
|
919
|
+
and returned `headers` with every synthesis request, and under
|
|
920
|
+
`includeAuthOnAssetFetch` the `Authorization` header also reaches the custom
|
|
921
|
+
transport's speech-mark and audio fetches. A failed fetch falls back to browser
|
|
922
|
+
speech and reports `pie-tool-init-fallback`.
|
|
923
|
+
|
|
924
|
+
For a server that authenticates by cookie on another origin, set `credentials:
|
|
925
|
+
"include"` on the `textToSpeech` provider config, beside
|
|
926
|
+
`includeAuthOnAssetFetch`. The cookie reaches speech-mark and audio fetches only
|
|
927
|
+
for origins in `assetOrigins`; unset, every TTS fetch keeps the browser default.
|
|
928
|
+
|
|
750
929
|
### Custom Transport via Server Proxy (SC-style)
|
|
751
930
|
|
|
752
931
|
For custom backends that return URL assets (for example `{ audioContent, word }`),
|
|
@@ -782,27 +961,34 @@ SchoolCity is used as a host-configured integration example for custom transport
|
|
|
782
961
|
Toolkit defaults still remain browser/standard providers unless the host explicitly
|
|
783
962
|
configures custom server-backed TTS.
|
|
784
963
|
|
|
785
|
-
## Test Attempt Session
|
|
964
|
+
## Test Attempt Session
|
|
786
965
|
|
|
787
|
-
The toolkit exposes a canonical `TestAttemptSession` runtime
|
|
966
|
+
The toolkit exposes a canonical `TestAttemptSession` runtime. The host maps its
|
|
967
|
+
backend's attempt payload into it and back out.
|
|
788
968
|
|
|
789
969
|
```typescript
|
|
790
970
|
import {
|
|
791
|
-
|
|
971
|
+
createNewTestAttemptSession,
|
|
792
972
|
toItemSessionsRecord,
|
|
793
|
-
|
|
973
|
+
upsertItemSessionFromPieSessionChange,
|
|
794
974
|
} from "@pie-players/pie-assessment-toolkit";
|
|
795
975
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
976
|
+
let testAttemptSession = createNewTestAttemptSession({
|
|
977
|
+
testAttemptSessionIdentifier: attempt.id,
|
|
978
|
+
assessmentId: assessment.id,
|
|
979
|
+
seed: attempt.id,
|
|
980
|
+
itemIdentifiers: assessment.itemIdentifiers,
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
// Record a PIE session change against its item
|
|
984
|
+
testAttemptSession = upsertItemSessionFromPieSessionChange(testAttemptSession, {
|
|
985
|
+
itemIdentifier: "q1",
|
|
986
|
+
pieSessionId: change.session.id,
|
|
987
|
+
session: change.session,
|
|
799
988
|
});
|
|
800
989
|
|
|
801
990
|
// Use in section-player handoff (same item session shape as item players expect)
|
|
802
991
|
const itemSessions = toItemSessionsRecord(testAttemptSession);
|
|
803
|
-
|
|
804
|
-
// Host-owned backend persistence payload
|
|
805
|
-
const patch = buildActivitySessionPatchFromTestAttemptSession(testAttemptSession);
|
|
806
992
|
```
|
|
807
993
|
|
|
808
994
|
### Integration Boundary
|
|
@@ -826,21 +1012,17 @@ The persistence strategy works with the same `SectionControllerSessionState` sha
|
|
|
826
1012
|
|
|
827
1013
|
- **ToolkitCoordinator**: ⭐ NEW - Centralized service orchestration
|
|
828
1014
|
- **ElementToolStateStore**: ⭐ NEW - Element-level ephemeral tool state management
|
|
829
|
-
- **ToolRegistry**: ⭐ NEW - Registry-based tool management with
|
|
830
|
-
- **ToolPolicyEngine**:
|
|
1015
|
+
- **ToolRegistry**: ⭐ NEW - Registry-based tool management with AfA PNP 3.0 profile support
|
|
1016
|
+
- **ToolPolicyEngine**: AfA PNP 3.0 Personal Needs Profile and host policy decisions via registry-backed policy sources
|
|
831
1017
|
- **ToolCoordinator**: Manages z-index layering and visibility for floating tools
|
|
832
1018
|
- **HighlightCoordinator**: Separate highlight layers for TTS (temporary) and annotations (persistent)
|
|
833
1019
|
- **TTSService**: Text-to-speech with QTI 3.0 catalog support
|
|
834
1020
|
- **AccessibilityCatalogResolver**: QTI 3.0 accessibility catalog management
|
|
835
1021
|
- **SSMLExtractor**: Automatic extraction of embedded `<speak>` tags
|
|
836
|
-
- **ThemeProvider**: Consistent accessibility theming
|
|
837
1022
|
|
|
838
|
-
### ✅
|
|
1023
|
+
### ✅ PNP Support Ids
|
|
839
1024
|
|
|
840
|
-
- **
|
|
841
|
-
- **9 Feature Categories**: Visual, auditory, motor, cognitive, reading, navigation, linguistic, assessment
|
|
842
|
-
- **Example Configurations**: Illustrative PNP profile examples (low vision, dyslexia, ADHD, etc.)
|
|
843
|
-
- **Tool Mappings**: All 12 default tools map to standard QTI 3.0 features
|
|
1025
|
+
- **Support ids are tool ids**: A profile, district policy or item setting names a tool by its `toolId`, and an id no registered tool carries raises `tool-policy.unknownSupportId`. [Support ids](docs/TOOL_REGISTRY.md#support-ids) lists the AfA PNP 3.0 terms the packaged ids serve.
|
|
844
1026
|
|
|
845
1027
|
### ✅ Section Player Integration
|
|
846
1028
|
|
|
@@ -865,16 +1047,15 @@ export interface ToolkitCoordinatorConfig {
|
|
|
865
1047
|
blocked?: string[];
|
|
866
1048
|
};
|
|
867
1049
|
placement?: {
|
|
868
|
-
assessment?: string[];
|
|
869
1050
|
section?: string[];
|
|
870
1051
|
item?: string[];
|
|
871
1052
|
passage?: string[];
|
|
872
|
-
rubric?: string[];
|
|
873
1053
|
};
|
|
874
1054
|
providers?: {
|
|
875
1055
|
textToSpeech?: {
|
|
876
1056
|
enabled?: boolean;
|
|
877
|
-
backend?: 'browser' | '
|
|
1057
|
+
backend?: 'browser' | 'server';
|
|
1058
|
+
serverProvider?: 'polly' | 'google' | 'custom';
|
|
878
1059
|
defaultVoice?: string;
|
|
879
1060
|
rate?: number;
|
|
880
1061
|
};
|
|
@@ -903,14 +1084,9 @@ export interface ToolkitCoordinatorConfig {
|
|
|
903
1084
|
### Methods
|
|
904
1085
|
|
|
905
1086
|
```typescript
|
|
906
|
-
// Get all services as a bundle
|
|
907
|
-
const services = coordinator.getServiceBundle();
|
|
908
|
-
// Returns: { ttsService, toolCoordinator, highlightCoordinator, elementToolStateStore, catalogResolver }
|
|
909
|
-
|
|
910
1087
|
// Tool configuration
|
|
911
|
-
coordinator.isToolEnabled('textToSpeech'); // Check if tool is enabled
|
|
912
1088
|
coordinator.getToolConfig('textToSpeech'); // Get tool-specific config
|
|
913
|
-
coordinator.updateToolConfig('textToSpeech', { rate: 1.5 }); // Update tool config
|
|
1089
|
+
coordinator.updateToolConfig('textToSpeech', { rate: 1.5 }); // Update tool config; replaces the tool's provider, which starts again on next use
|
|
914
1090
|
|
|
915
1091
|
// Final teardown by the owner that constructed the coordinator
|
|
916
1092
|
await coordinator.dispose();
|
|
@@ -1022,6 +1198,8 @@ unsubscribe();
|
|
|
1022
1198
|
### TTSService
|
|
1023
1199
|
|
|
1024
1200
|
```typescript
|
|
1201
|
+
import { BrowserTTSProvider, TTSService } from '@pie-players/pie-assessment-toolkit';
|
|
1202
|
+
|
|
1025
1203
|
const ttsService = new TTSService();
|
|
1026
1204
|
|
|
1027
1205
|
// Initialize with provider
|
|
@@ -1030,8 +1208,8 @@ await ttsService.initialize(new BrowserTTSProvider());
|
|
|
1030
1208
|
// Set catalog resolver for SSML support
|
|
1031
1209
|
ttsService.setCatalogResolver(catalogResolver);
|
|
1032
1210
|
|
|
1033
|
-
// Playback
|
|
1034
|
-
await ttsService.speak(
|
|
1211
|
+
// Playback: reads an element or a range, or the named card in its place
|
|
1212
|
+
await ttsService.speak(promptElement, {
|
|
1035
1213
|
catalogId: 'prompt-001',
|
|
1036
1214
|
language: 'en-US'
|
|
1037
1215
|
});
|
|
@@ -1050,36 +1228,46 @@ await ttsService.updateSettings({
|
|
|
1050
1228
|
|
|
1051
1229
|
### ToolCoordinator
|
|
1052
1230
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
// Register tools
|
|
1057
|
-
toolCoordinator.registerTool('calculator', 'Calculator', element);
|
|
1231
|
+
The toolkit owns the coordinator. A tool element registers with the one in its
|
|
1232
|
+
runtime context and hands it the element once it renders:
|
|
1058
1233
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1234
|
+
```typescript
|
|
1235
|
+
import {
|
|
1236
|
+
createToolCoordinatorRegistration,
|
|
1237
|
+
ZIndexLayer,
|
|
1238
|
+
} from '@pie-players/pie-assessment-toolkit/tools/registration';
|
|
1063
1239
|
|
|
1064
|
-
|
|
1065
|
-
toolCoordinator.bringToFront(element);
|
|
1240
|
+
const registration = createToolCoordinatorRegistration('Calculator', ZIndexLayer.TOOL);
|
|
1066
1241
|
|
|
1067
|
-
//
|
|
1068
|
-
|
|
1242
|
+
// Re-registers when a republished context brings a new coordinator.
|
|
1243
|
+
$effect(() => registration.sync(coordinator, toolId));
|
|
1244
|
+
$effect(() => {
|
|
1245
|
+
if (coordinator && containerEl && toolId) {
|
|
1246
|
+
coordinator.updateToolElement(toolId, containerEl);
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
onDestroy(() => registration.release());
|
|
1069
1250
|
```
|
|
1070
1251
|
|
|
1252
|
+
Visibility and stacking then go through the coordinator by scoped tool id:
|
|
1253
|
+
`showTool`, `hideTool`, `toggleTool`, `isToolVisible`, `bringToFront(element)`,
|
|
1254
|
+
and `getVisibleTools({ baseId })` / `hideAllTools({ baseId })` for one tool
|
|
1255
|
+
across scopes.
|
|
1256
|
+
|
|
1071
1257
|
### HighlightCoordinator
|
|
1072
1258
|
|
|
1073
1259
|
```typescript
|
|
1260
|
+
import { HighlightColor, HighlightCoordinator } from '@pie-players/pie-assessment-toolkit';
|
|
1261
|
+
|
|
1074
1262
|
const highlightCoordinator = new HighlightCoordinator();
|
|
1075
1263
|
|
|
1076
1264
|
// TTS highlights (temporary)
|
|
1077
|
-
highlightCoordinator.highlightTTSWord(
|
|
1265
|
+
highlightCoordinator.highlightTTSWord([wordRange]); // one range per tree the word spans
|
|
1078
1266
|
highlightCoordinator.highlightTTSSentence([range1, range2]);
|
|
1079
1267
|
highlightCoordinator.clearTTS();
|
|
1080
1268
|
|
|
1081
1269
|
// Annotation highlights (persistent)
|
|
1082
|
-
const id = highlightCoordinator.addAnnotation(range,
|
|
1270
|
+
const id = highlightCoordinator.addAnnotation(range, HighlightColor.YELLOW);
|
|
1083
1271
|
highlightCoordinator.removeAnnotation(id);
|
|
1084
1272
|
```
|
|
1085
1273
|
|
|
@@ -1104,6 +1292,8 @@ maintaining, across every scheme it ships.
|
|
|
1104
1292
|
### AccessibilityCatalogResolver
|
|
1105
1293
|
|
|
1106
1294
|
```typescript
|
|
1295
|
+
import { AccessibilityCatalogResolver } from '@pie-players/pie-assessment-toolkit';
|
|
1296
|
+
|
|
1107
1297
|
const resolver = new AccessibilityCatalogResolver(
|
|
1108
1298
|
assessment.accessibilityCatalogs,
|
|
1109
1299
|
'en-US'
|
|
@@ -1167,6 +1357,8 @@ such as TTS may still call `getAlternative(...)` with a context built by
|
|
|
1167
1357
|
### SSMLExtractor
|
|
1168
1358
|
|
|
1169
1359
|
```typescript
|
|
1360
|
+
import { SSMLExtractor } from '@pie-players/pie-assessment-toolkit';
|
|
1361
|
+
|
|
1170
1362
|
const extractor = new SSMLExtractor();
|
|
1171
1363
|
|
|
1172
1364
|
// Extract from item config
|
|
@@ -1189,28 +1381,30 @@ that could not parse the markup left the video in the visible content, showing t
|
|
|
1189
1381
|
accommodation to every learner regardless of eligibility.
|
|
1190
1382
|
|
|
1191
1383
|
This package resolves and registers those cards through
|
|
1192
|
-
`AccessibilityCatalogResolver` and the generic media helpers
|
|
1193
|
-
|
|
1384
|
+
`AccessibilityCatalogResolver` and the generic media helpers it re-exports
|
|
1385
|
+
from `@pie-players/pie-players-shared/media`. Which card types mean what belongs to the capability that
|
|
1194
1386
|
needs them — signing's card validators and its resolution rules live in
|
|
1195
1387
|
`@pie-players/pie-tool-sign-language`, behind that capability's
|
|
1196
1388
|
`requiresAuthoredContent`.
|
|
1197
1389
|
|
|
1198
1390
|
### Feature policy without a placement
|
|
1199
1391
|
|
|
1200
|
-
`ToolPolicyEngine.decideFeature(featureId)` (and
|
|
1201
|
-
`ToolkitCoordinator.decideFeaturePolicy(featureId)`) resolve one feature id
|
|
1392
|
+
`ToolPolicyEngine.decideFeature(featureId, scope?)` (and
|
|
1393
|
+
`ToolkitCoordinator.decideFeaturePolicy(featureId, scope?)`) resolve one feature id
|
|
1202
1394
|
through `PnpPolicySource`'s six-level precedence, independent of any toolbar
|
|
1203
1395
|
placement. Use it for capabilities that are not toolbar surfaces — signing is the
|
|
1204
1396
|
first — where a placement-scoped `decide(...)` would answer the wrong question:
|
|
1205
1397
|
absent because it was never placed, rather than absent because policy said no.
|
|
1398
|
+
An item scope applies that item's registered settings, the item restriction and
|
|
1399
|
+
requirement rungs; a decision without one applies no item's.
|
|
1206
1400
|
|
|
1207
|
-
|
|
1208
|
-
grants nothing. Which capabilities a deployment grants by default is a property
|
|
1401
|
+
This package ships no profile. Which capabilities a deployment grants by default is a property
|
|
1209
1402
|
of the program rather than of a capability — TTS is a universal feature in one
|
|
1210
1403
|
program and a documented accommodation in another — so it belongs in policy
|
|
1211
1404
|
configuration alongside the district and test-administration levels. Hosts that
|
|
1212
1405
|
want today's universal set take `createUniversalPersonalNeedsProfile()` from
|
|
1213
|
-
`@pie-players/pie-default-tool-loaders`, which ships it as data
|
|
1406
|
+
`@pie-players/pie-default-tool-loaders`, which ships it as data beside
|
|
1407
|
+
`createEmptyPersonalNeedsProfile()`.
|
|
1214
1408
|
|
|
1215
1409
|
Nothing derives a profile from the registry any more. Doing so read registry
|
|
1216
1410
|
membership as eligibility tier — registration means "policy-addressable", not
|
|
@@ -1232,18 +1426,26 @@ mounted surface elements immediately.
|
|
|
1232
1426
|
The section player provides automatic ToolkitCoordinator integration:
|
|
1233
1427
|
|
|
1234
1428
|
```html
|
|
1235
|
-
<pie-section-player id="player"></pie-section-player>
|
|
1429
|
+
<pie-section-player-splitpane id="player" section-id="section-1"></pie-section-player-splitpane>
|
|
1236
1430
|
|
|
1237
1431
|
<script type="module">
|
|
1238
1432
|
import { ToolkitCoordinator } from '@pie-players/pie-assessment-toolkit';
|
|
1433
|
+
import {
|
|
1434
|
+
createPackagedToolRegistry,
|
|
1435
|
+
DEFAULT_TOOL_MODULE_LOADERS,
|
|
1436
|
+
} from '@pie-players/pie-default-tool-loaders';
|
|
1239
1437
|
|
|
1240
1438
|
// Create coordinator
|
|
1439
|
+
const toolRegistry = createPackagedToolRegistry({
|
|
1440
|
+
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
1441
|
+
});
|
|
1241
1442
|
const coordinator = new ToolkitCoordinator({
|
|
1242
1443
|
assessmentId: 'my-assessment',
|
|
1444
|
+
toolRegistry,
|
|
1243
1445
|
tools: {
|
|
1244
1446
|
providers: { textToSpeech: { enabled: true, backend: 'browser' } },
|
|
1245
1447
|
placement: {
|
|
1246
|
-
section: ['
|
|
1448
|
+
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
1247
1449
|
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
1248
1450
|
passage: ['textToSpeech']
|
|
1249
1451
|
}
|
|
@@ -1257,23 +1459,29 @@ The section player provides automatic ToolkitCoordinator integration:
|
|
|
1257
1459
|
|
|
1258
1460
|
// Player automatically:
|
|
1259
1461
|
// - Extracts services from coordinator
|
|
1260
|
-
// -
|
|
1462
|
+
// - Scopes its runtime engine to `section-id`
|
|
1261
1463
|
// - Provides runtime context to child components
|
|
1262
|
-
// -
|
|
1464
|
+
// - Registers item catalogs, including SSML catalogs a host extracted
|
|
1465
|
+
// into `config.extractedCatalogs` with `SSMLExtractor`
|
|
1263
1466
|
// - Handles catalog lifecycle
|
|
1264
1467
|
</script>
|
|
1265
1468
|
```
|
|
1266
1469
|
|
|
1267
1470
|
### Runtime Context Contract
|
|
1268
1471
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1472
|
+
Section-player and toolkit components share one runtime context. An element
|
|
1473
|
+
inside the toolkit's tree connects to it:
|
|
1271
1474
|
|
|
1272
1475
|
```typescript
|
|
1273
1476
|
import {
|
|
1274
|
-
|
|
1477
|
+
connectToolRuntimeContext,
|
|
1275
1478
|
type AssessmentToolkitRuntimeContext
|
|
1276
1479
|
} from "@pie-players/pie-assessment-toolkit";
|
|
1480
|
+
|
|
1481
|
+
let runtime: AssessmentToolkitRuntimeContext | undefined;
|
|
1482
|
+
const disconnect = connectToolRuntimeContext(hostElement, (value) => {
|
|
1483
|
+
runtime = value;
|
|
1484
|
+
});
|
|
1277
1485
|
```
|
|
1278
1486
|
|
|
1279
1487
|
`AssessmentToolkitRuntimeContext` carries ambient orchestration dependencies
|
|
@@ -1303,15 +1511,9 @@ player.section = mySection;
|
|
|
1303
1511
|
|
|
1304
1512
|
// Internally creates:
|
|
1305
1513
|
// new ToolkitCoordinator({
|
|
1306
|
-
// assessmentId: '
|
|
1307
|
-
//
|
|
1308
|
-
//
|
|
1309
|
-
// placement: {
|
|
1310
|
-
// section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
1311
|
-
// item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
1312
|
-
// passage: ['textToSpeech']
|
|
1313
|
-
// }
|
|
1314
|
-
// }
|
|
1514
|
+
// assessmentId: 'section-demo-direct', // or runtime.assessmentId
|
|
1515
|
+
// toolRegistry, // the player's toolRegistry, else the packaged registry
|
|
1516
|
+
// tools: player.runtime?.tools // no tools are placed when this is unset
|
|
1315
1517
|
// })
|
|
1316
1518
|
```
|
|
1317
1519
|
|
|
@@ -1331,9 +1533,14 @@ import {
|
|
|
1331
1533
|
createToolsConfig,
|
|
1332
1534
|
ToolkitCoordinator
|
|
1333
1535
|
} from "@pie-players/pie-assessment-toolkit";
|
|
1334
|
-
import {
|
|
1536
|
+
import {
|
|
1537
|
+
createPackagedToolRegistry,
|
|
1538
|
+
DEFAULT_TOOL_MODULE_LOADERS,
|
|
1539
|
+
} from "@pie-players/pie-default-tool-loaders";
|
|
1335
1540
|
|
|
1336
|
-
const toolRegistry = createPackagedToolRegistry(
|
|
1541
|
+
const toolRegistry = createPackagedToolRegistry({
|
|
1542
|
+
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
1543
|
+
});
|
|
1337
1544
|
const { config, diagnostics } = createToolsConfig({
|
|
1338
1545
|
source: "host.bootstrap",
|
|
1339
1546
|
strictness: "error",
|
|
@@ -1361,7 +1568,7 @@ const coordinator = new ToolkitCoordinator({
|
|
|
1361
1568
|
Notes:
|
|
1362
1569
|
- `providers.textToSpeech` is the canonical TTS provider key.
|
|
1363
1570
|
- `providers.tts` is rejected by the validation contract.
|
|
1364
|
-
-
|
|
1571
|
+
- A tool registration can declare `sanitizeConfig` and `validateConfig` hooks for its `providers.<toolId>` entry.
|
|
1365
1572
|
- Hosts can react to framework errors via the `framework-error` DOM event,
|
|
1366
1573
|
the `onFrameworkError(model)` callback prop, or by subscribing directly
|
|
1367
1574
|
to the package-internal bus via
|
|
@@ -1375,132 +1582,63 @@ Notes:
|
|
|
1375
1582
|
|
|
1376
1583
|
## Section Runtime Engine (advanced)
|
|
1377
1584
|
|
|
1378
|
-
The
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
`<pie-assessment-toolkit>`
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
## Writing a capability package
|
|
1410
|
-
|
|
1411
|
-
`@pie-players/pie-assessment-toolkit/tools/internal` is what a capability
|
|
1412
|
-
package imports: the `ToolRegistration` contract, the surface and content
|
|
1413
|
-
dependency types, `resolveToolTag` and the toolbar registration helpers. Same
|
|
1414
|
-
stability contract as the other `*/internal` entry points — symbols may change
|
|
1415
|
-
between minor versions with a changeset note.
|
|
1416
|
-
|
|
1417
|
-
Import it rather than the package root: the root pulls in `ToolkitCoordinator`,
|
|
1418
|
-
`TTSService` and the components, none of which a registration needs, and a
|
|
1419
|
-
capability bundle that inlines them ends up with a second `ToolRegistry` class
|
|
1420
|
-
that fails every `instanceof` across the host boundary. Mark the toolkit external
|
|
1421
|
-
in the package's build with a pattern that covers subpaths, not a bare specifier.
|
|
1422
|
-
|
|
1423
|
-
`@pie-players/pie-tool-sign-language` is the worked example end to end: a
|
|
1424
|
-
registration, a content resolver, its own custom element, and no edit to any
|
|
1425
|
-
generic package. `packages/default-tool-loaders/README.md` covers how a
|
|
1426
|
-
deployment then composes it in, and `docs/TOOL_REGISTRY.md` the registration and
|
|
1427
|
-
host-surface contracts.
|
|
1428
|
-
|
|
1429
|
-
### Lifecycle emit coordination
|
|
1430
|
-
|
|
1431
|
-
When `<pie-assessment-toolkit>` is nested inside a section-player layout,
|
|
1432
|
-
the layout kernel publishes a lifecycle handle via
|
|
1433
|
-
`sectionRuntimeEngineHostContext`. The toolkit detects that host
|
|
1434
|
-
lifecycle owner and **suppresses its own external lifecycle DOM emits
|
|
1435
|
-
and `onStageChange` callback** in favor of the layout CE host. From the
|
|
1436
|
-
outside, one cohort yields one `pie-stage-change` /
|
|
1437
|
-
`pie-loading-complete` chain on the layout CE host regardless of wrapper
|
|
1438
|
-
depth. Controller-side
|
|
1439
|
-
registration, content loading, session propagation, and persistence
|
|
1440
|
-
remain toolkit-local through its own `SectionRuntimeEngine` instance.
|
|
1441
|
-
A standalone `<pie-assessment-toolkit>` (no host context) emits from
|
|
1442
|
-
its own engine.
|
|
1443
|
-
|
|
1444
|
-
**Detection.** If a custom layout shell emits two `pie-stage-change`
|
|
1445
|
-
events per stage transition (or two `pie-loading-complete` per cohort)
|
|
1446
|
-
on the same layout CE — typically with two distinct `detail.runtimeId`
|
|
1447
|
-
values — the shell has not published its engine via
|
|
1448
|
-
`sectionRuntimeEngineHostContext`, so the wrapped
|
|
1449
|
-
`<pie-assessment-toolkit>` falls back to its standalone lifecycle emit
|
|
1450
|
-
path. Wire the bridge as shown below.
|
|
1585
|
+
The section runtime engine is the section player's stage chain: a pure FSM that
|
|
1586
|
+
turns the cohort a layout shows, the controller resolving and the readiness
|
|
1587
|
+
signals the layout derives into `pie-stage-change` and `pie-loading-complete`.
|
|
1588
|
+
The section player's layout kernel owns one per layout element, and it is the
|
|
1589
|
+
only stage emitter. `<pie-assessment-toolkit>` emits no stages: its
|
|
1590
|
+
`SectionControllerBinding` resolves the section's controller, which the toolkit
|
|
1591
|
+
announces with `toolkit-ready`, and the toolkit publishes framework errors.
|
|
1592
|
+
|
|
1593
|
+
### Entry point
|
|
1594
|
+
|
|
1595
|
+
`@pie-players/pie-assessment-toolkit/runtime/engine` is the stable entry for a
|
|
1596
|
+
host that drives a section's stage chain. It carries `SectionRuntimeEngine` and
|
|
1597
|
+
the vocabulary of its surface: the `SectionEngineInput`s `dispatchInput` takes,
|
|
1598
|
+
the `SectionEngineOutput`s `subscribe` delivers, the `SectionEngineState` and
|
|
1599
|
+
`SectionEnginePhase` `getState` returns, the cohort helpers (`makeCohort`,
|
|
1600
|
+
`cohortsEqual`), the runtime config types with
|
|
1601
|
+
`resolveSectionEngineRuntimeState`, and the readiness signals. The engine core, its adapter and its DOM bridge have no
|
|
1602
|
+
entry: a host reaches them through the facade.
|
|
1603
|
+
|
|
1604
|
+
### Stage chain
|
|
1605
|
+
|
|
1606
|
+
A cohort moves through `composed`, `engine-ready` and `interactive`, and ends at
|
|
1607
|
+
`disposed` on a cohort change or unmount. `engine-ready` follows the
|
|
1608
|
+
`section-controller-resolved` input, `interactive` the readiness signals that
|
|
1609
|
+
satisfy the readiness mode. A readiness update that reports `runtimeError`
|
|
1610
|
+
before `interactive` ends the chain: the first stage the cohort did not reach is
|
|
1611
|
+
`failed` and the rest up to `interactive` are `skipped`. The section player sets
|
|
1612
|
+
`runtimeError` from a non-recoverable `framework-error`, so a host waiting on
|
|
1613
|
+
`engine-ready` or `interactive` always hears an answer. `pie-loading-complete`
|
|
1614
|
+
fires once per cohort, when every item has loaded.
|
|
1451
1615
|
|
|
1452
1616
|
### Common-host wiring example
|
|
1453
1617
|
|
|
1454
|
-
Most hosts never construct the engine
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1618
|
+
Most hosts never construct the engine: the section-player layout elements do,
|
|
1619
|
+
and a host-built section layout takes its engine from
|
|
1620
|
+
`pie-section-player-kernel-host` (see the section player's
|
|
1621
|
+
[Custom layout authoring](../section-player/README.md#custom-layout-authoring)).
|
|
1622
|
+
Use the facade for a section renderer outside the section player. The shape
|
|
1623
|
+
mirrors the section-player kernel:
|
|
1458
1624
|
|
|
1459
1625
|
```ts
|
|
1460
|
-
import { ContextProvider } from "@pie-players/pie-context";
|
|
1461
1626
|
import {
|
|
1462
1627
|
SectionRuntimeEngine,
|
|
1463
|
-
sectionRuntimeEngineHostContext,
|
|
1464
|
-
} from "@pie-players/pie-assessment-toolkit/runtime/engine";
|
|
1465
|
-
import {
|
|
1466
|
-
FrameworkErrorBus,
|
|
1467
1628
|
makeCohort,
|
|
1468
|
-
} from "@pie-players/pie-assessment-toolkit/runtime/
|
|
1629
|
+
} from "@pie-players/pie-assessment-toolkit/runtime/engine";
|
|
1469
1630
|
|
|
1470
|
-
const bus = new FrameworkErrorBus();
|
|
1471
1631
|
const engine = new SectionRuntimeEngine();
|
|
1472
1632
|
|
|
1473
|
-
// 1. Attach to the layout
|
|
1474
|
-
|
|
1475
|
-
engine.attachHost({
|
|
1476
|
-
host: layoutHostElement,
|
|
1477
|
-
sourceCe: "my-custom-layout",
|
|
1478
|
-
frameworkErrorBus: bus,
|
|
1479
|
-
coordinator: toolkitCoordinator,
|
|
1480
|
-
});
|
|
1633
|
+
// 1. Attach to the layout host. `sourceCe` is stamped onto every event.
|
|
1634
|
+
engine.attachHost({ host: layoutHostElement, sourceCe: "my-custom-layout" });
|
|
1481
1635
|
|
|
1482
|
-
// 2.
|
|
1483
|
-
|
|
1484
|
-
//
|
|
1485
|
-
// plumbing locally.
|
|
1486
|
-
const engineProvider = new ContextProvider(layoutHostElement, {
|
|
1487
|
-
context: sectionRuntimeEngineHostContext,
|
|
1488
|
-
initialValue: {
|
|
1489
|
-
engine: {
|
|
1490
|
-
getRuntimeId: () => engine.getRuntimeId(),
|
|
1491
|
-
},
|
|
1492
|
-
},
|
|
1636
|
+
// 2. (Optional) Subscribe to the output batches the DOM bridge dispatches.
|
|
1637
|
+
engine.subscribe((outputs) => {
|
|
1638
|
+
// `stage-change` and `loading-complete` outputs
|
|
1493
1639
|
});
|
|
1494
|
-
engineProvider.connect();
|
|
1495
1640
|
|
|
1496
|
-
// 3.
|
|
1497
|
-
// of outputs the DOM-event bridge fans out to the host element.
|
|
1498
|
-
engine.subscribe((output) => {
|
|
1499
|
-
// tap stage transitions, readiness updates, framework errors,
|
|
1500
|
-
// instrumentation events
|
|
1501
|
-
});
|
|
1502
|
-
|
|
1503
|
-
// 4. Drive the engine. Use real `SectionEngineInput` shapes:
|
|
1641
|
+
// 3. Drive the engine with `SectionEngineInput`s.
|
|
1504
1642
|
const cohort = makeCohort({ sectionId, attemptId });
|
|
1505
1643
|
engine.dispatchInput({
|
|
1506
1644
|
kind: "initialize",
|
|
@@ -1510,7 +1648,10 @@ engine.dispatchInput({
|
|
|
1510
1648
|
itemCount,
|
|
1511
1649
|
});
|
|
1512
1650
|
|
|
1513
|
-
//
|
|
1651
|
+
// When the toolkit's `section-ready` delivers the cohort's section controller:
|
|
1652
|
+
engine.dispatchInput({ kind: "section-controller-resolved" });
|
|
1653
|
+
|
|
1654
|
+
// On readiness signal updates:
|
|
1514
1655
|
engine.dispatchInput({
|
|
1515
1656
|
kind: "update-readiness-signals",
|
|
1516
1657
|
signals: {
|
|
@@ -1519,33 +1660,44 @@ engine.dispatchInput({
|
|
|
1519
1660
|
allLoadingComplete,
|
|
1520
1661
|
runtimeError,
|
|
1521
1662
|
},
|
|
1522
|
-
loadedCount,
|
|
1523
1663
|
itemCount,
|
|
1524
1664
|
mode: "progressive",
|
|
1525
1665
|
});
|
|
1526
1666
|
|
|
1527
|
-
// On unmount
|
|
1528
|
-
engineProvider.disconnect();
|
|
1667
|
+
// On unmount; emits `disposed` for the active cohort.
|
|
1529
1668
|
engine.dispose();
|
|
1530
1669
|
```
|
|
1531
1670
|
|
|
1532
|
-
The
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
section-player
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
`
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1671
|
+
The adapter dispatches `pie-stage-change` and `pie-loading-complete` on `host`,
|
|
1672
|
+
bubbling and composed, as the toolkit dispatches its own events. Framework
|
|
1673
|
+
errors are not an engine output. The toolkit that owns the coordinator publishes
|
|
1674
|
+
each one once: one bubbling, composed `framework-error` event, which reaches the
|
|
1675
|
+
layout host and `document`, and one `onFrameworkError` call. A coordinator the
|
|
1676
|
+
host passes in reports through the same surfaces.
|
|
1677
|
+
`packages/section-player/tests/section-player-event-delivery.spec.ts` pins these
|
|
1678
|
+
counts.
|
|
1679
|
+
|
|
1680
|
+
## Writing a capability package
|
|
1681
|
+
|
|
1682
|
+
`@pie-players/pie-assessment-toolkit/tools/registration` is the stable entry a
|
|
1683
|
+
capability package imports: the `ToolRegistration` contract, the
|
|
1684
|
+
`ToolProviderApi` its provider descriptor creates, the surface and content
|
|
1685
|
+
dependency types, the context predicates, `createToolElement`, `resolveToolTag`
|
|
1686
|
+
and the toolbar registration helpers. A renderer that puts registrations on
|
|
1687
|
+
screen uses the same entry for `createToolSurfaceHost` and
|
|
1688
|
+
`resolveContentCapabilities`.
|
|
1689
|
+
|
|
1690
|
+
Import it rather than the package root: the root pulls in `ToolkitCoordinator`,
|
|
1691
|
+
`TTSService` and the components, none of which a registration needs, and a
|
|
1692
|
+
capability bundle that inlines them ends up with a second `ToolRegistry` class
|
|
1693
|
+
that fails every `instanceof` across the host boundary. Mark the toolkit external
|
|
1694
|
+
in the package's build with a pattern that covers subpaths, not a bare specifier.
|
|
1695
|
+
|
|
1696
|
+
`@pie-players/pie-tool-sign-language` is the worked example end to end: a
|
|
1697
|
+
registration, a content resolver, its own custom element, and no edit to any
|
|
1698
|
+
generic package. `packages/default-tool-loaders/README.md` covers how a
|
|
1699
|
+
deployment then composes it in, and `docs/TOOL_REGISTRY.md` the registration and
|
|
1700
|
+
host-surface contracts.
|
|
1549
1701
|
|
|
1550
1702
|
## State Separation: Tool State vs Session Data
|
|
1551
1703
|
|
|
@@ -1608,10 +1760,9 @@ Full TypeScript definitions included:
|
|
|
1608
1760
|
|
|
1609
1761
|
```typescript
|
|
1610
1762
|
import type {
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
ToolkitCoordinatorConfig
|
|
1614
|
-
ToolkitServiceBundle
|
|
1763
|
+
ToolkitCoordinatorApi,
|
|
1764
|
+
ElementToolStateStoreApi,
|
|
1765
|
+
ToolkitCoordinatorConfig
|
|
1615
1766
|
} from '@pie-players/pie-assessment-toolkit';
|
|
1616
1767
|
```
|
|
1617
1768
|
|
|
@@ -1623,7 +1774,7 @@ from tool configuration. Two sanitization layers apply:
|
|
|
1623
1774
|
|
|
1624
1775
|
- **Item / passage markup** - sanitized by default in
|
|
1625
1776
|
`pie-item-player`. See
|
|
1626
|
-
[pie-item-player README](
|
|
1777
|
+
[pie-item-player README](../item-player/README.md#content-trust-boundary)
|
|
1627
1778
|
for the `trust-markup` opt-out and the `sanitizeMarkup` override.
|
|
1628
1779
|
As a post-sanitization step, every authored `<img>` outside a `pie-*`
|
|
1629
1780
|
custom element is wrapped in `<span class="pie-image-scroll">` so
|
|
@@ -1645,7 +1796,7 @@ from tool configuration. Two sanitization layers apply:
|
|
|
1645
1796
|
|
|
1646
1797
|
## Related Documentation
|
|
1647
1798
|
|
|
1648
|
-
- **[Tool Registry Architecture](docs/TOOL_REGISTRY.md)** - ⭐ NEW - Registry-based tool management and
|
|
1799
|
+
- **[Tool Registry Architecture](docs/TOOL_REGISTRY.md)** - ⭐ NEW - Registry-based tool management and AfA PNP 3.0 profile support
|
|
1649
1800
|
- **[PNP Configuration Guide](docs/PNP_CONFIGURATION.md)** - ⭐ NEW - How to configure student profiles, district policies, and governance rules
|
|
1650
1801
|
- [ToolkitCoordinator Architecture](../../docs/architecture/architecture.md#toolkitcoordinator-centralized-service-management) - Design decisions and patterns
|
|
1651
1802
|
- [Section Player README](../section-player/README.md) - Section player integration
|