@pie-players/pie-assessment-toolkit 0.3.74 → 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +658 -714
- package/custom-elements.json +1315 -0
- package/dist/attempt/TestSession.d.ts +1 -21
- package/dist/attempt/TestSession.js +16 -102
- 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.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.js +14 -14
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/ItemToolBar-3zrc4p3d.js +4 -0
- package/dist/components/chunks/ItemToolBar-66hcwmfw.js +2 -0
- package/dist/components/chunks/ItemToolBar-9gqhfxtn.js +40 -0
- package/dist/components/chunks/ItemToolBar-hzc5wyjp.js +2 -0
- package/dist/components/chunks/{ItemToolBar-gmrryv2v.js → ItemToolBar-kkj69ptr.js} +1 -1
- package/dist/components/chunks/ItemToolBar-vdkyn9f3.js +2 -0
- package/dist/components/chunks/sre-engine-bj997w4b.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 +33 -6
- package/dist/index.d.ts +25 -50
- package/dist/index.js +15 -45
- package/dist/policy/core/PolicySource.d.ts +2 -2
- package/dist/policy/core/ToolPolicyEngine.d.ts +118 -34
- package/dist/policy/core/ToolPolicyEngine.js +203 -56
- package/dist/policy/core/compose-decision.d.ts +28 -7
- package/dist/policy/core/compose-decision.js +95 -74
- package/dist/policy/core/decision-types.d.ts +98 -55
- package/dist/policy/core/feature-decision.d.ts +28 -19
- package/dist/policy/core/feature-decision.js +15 -11
- package/dist/policy/core/pnp-policy-inputs.d.ts +29 -35
- package/dist/policy/core/pnp-policy-inputs.js +40 -88
- package/dist/policy/core/provenance.d.ts +10 -5
- package/dist/policy/core/provenance.js +1 -1
- package/dist/policy/core/tool-parameters.d.ts +11 -0
- package/dist/policy/core/tool-parameters.js +15 -0
- package/dist/policy/engine.d.ts +7 -23
- package/dist/policy/engine.js +5 -20
- package/dist/policy/sources/PnpPolicySource.d.ts +43 -68
- package/dist/policy/sources/PnpPolicySource.js +133 -132
- package/dist/runtime/SectionControllerBinding.d.ts +283 -0
- package/dist/runtime/SectionControllerBinding.js +456 -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 -3
- package/dist/runtime/adapter/subscriber-fanout.js +2 -6
- 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 +12 -18
- package/dist/runtime/composition-emit-scheduler.js +10 -16
- package/dist/runtime/content-language.d.ts +32 -0
- package/dist/runtime/content-language.js +46 -0
- package/dist/runtime/content-region.d.ts +13 -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 -4
- package/dist/runtime/core/SectionEngineCore.js +2 -6
- package/dist/runtime/core/cohort.d.ts +1 -6
- package/dist/runtime/core/cohort.js +1 -10
- 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 +17 -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 +74 -53
- 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 +22 -14
- package/dist/runtime/runtime-event-claim.d.ts +8 -0
- package/dist/runtime/runtime-event-claim.js +15 -0
- package/dist/runtime/runtime-id.d.ts +1 -1
- package/dist/runtime/runtime-id.js +1 -1
- 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 +194 -0
- package/dist/runtime/tool-host-contract.d.ts +8 -14
- package/dist/runtime/tool-host-contract.js +12 -12
- package/dist/runtime/unclaimed-registration-watch.d.ts +7 -0
- package/dist/runtime/unclaimed-registration-watch.js +23 -0
- package/dist/services/AccessibilityCatalogResolver.d.ts +21 -31
- package/dist/services/AccessibilityCatalogResolver.js +29 -42
- package/dist/services/ElementToolStateStore.d.ts +42 -67
- package/dist/services/ElementToolStateStore.js +65 -71
- package/dist/services/HighlightCoordinator.d.ts +28 -40
- package/dist/services/HighlightCoordinator.js +459 -381
- package/dist/services/RangeSerializer.d.ts +11 -33
- package/dist/services/RangeSerializer.js +88 -59
- package/dist/services/SSMLExtractor.d.ts +8 -31
- package/dist/services/SSMLExtractor.js +18 -40
- package/dist/services/TTSService.d.ts +159 -119
- package/dist/services/TTSService.js +937 -822
- package/dist/services/ToolCoordinator.d.ts +42 -102
- package/dist/services/ToolCoordinator.js +178 -256
- package/dist/services/ToolRegistry.d.ts +152 -133
- package/dist/services/ToolRegistry.js +192 -178
- package/dist/services/ToolkitCoordinator.d.ts +233 -237
- package/dist/services/ToolkitCoordinator.js +615 -557
- package/dist/services/audio-handoff.d.ts +6 -7
- package/dist/services/audio-handoff.js +20 -14
- package/dist/services/catalog-owner.d.ts +0 -1
- 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 +51 -13
- package/dist/services/framework-error.js +19 -20
- package/dist/services/interfaces.d.ts +189 -184
- package/dist/services/section-controller-types.d.ts +7 -7
- package/dist/services/section-session-assignment.d.ts +21 -0
- package/dist/services/section-session-assignment.js +80 -0
- package/dist/services/spoken-audio-cards.d.ts +2 -6
- package/dist/services/spoken-audio-cards.js +2 -6
- 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 +9 -12
- package/dist/services/tool-config-validation.js +47 -87
- package/dist/services/tool-context.d.ts +7 -31
- package/dist/services/tool-context.js +99 -65
- package/dist/services/tool-failure.d.ts +30 -0
- package/dist/services/tool-failure.js +11 -0
- package/dist/services/tool-icons.d.ts +5 -5
- package/dist/services/tool-icons.js +6 -5
- package/dist/services/tool-instance-id.d.ts +8 -7
- package/dist/services/tool-instance-id.js +12 -24
- package/dist/services/tool-providers/TTSToolProvider.d.ts +20 -58
- package/dist/services/tool-providers/TTSToolProvider.js +32 -85
- package/dist/services/tool-providers/ToolProviderApi.d.ts +10 -11
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +29 -60
- package/dist/services/tool-providers/ToolProviderRegistry.js +128 -144
- package/dist/services/tool-request.d.ts +31 -12
- package/dist/services/tool-request.js +31 -16
- package/dist/services/toolbar-decided-tools.d.ts +16 -0
- package/dist/services/toolbar-decided-tools.js +29 -0
- package/dist/services/toolbar-items.js +2 -5
- package/dist/services/tools-config-normalizer.d.ts +33 -29
- package/dist/services/tools-config-normalizer.js +26 -28
- package/dist/services/tts/browser-provider.d.ts +39 -6
- package/dist/services/tts/browser-provider.js +280 -160
- package/dist/services/tts/catalog-span-alignment.js +14 -7
- package/dist/services/tts/flat-tree.d.ts +64 -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/dom/to-chunk-inputs.d.ts +2 -2
- package/dist/services/tts/generated-speech/index.d.ts +1 -1
- package/dist/services/tts/generated-speech/index.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 +1 -1
- package/dist/services/tts/generated-speech/types.js +1 -1
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +22 -16
- 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/highlight-target-resolver.d.ts +12 -0
- package/dist/services/tts/math-aware-text-processing.d.ts +5 -1
- package/dist/services/tts/math-aware-text-processing.js +42 -47
- 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 +12 -1
- package/dist/services/tts/math-speech.js +54 -18
- package/dist/services/tts/mathml-sanitization.d.ts +7 -0
- package/dist/services/tts/mathml-sanitization.js +9 -1
- package/dist/services/tts/provider-options.d.ts +52 -0
- package/dist/services/tts/provider-options.js +1 -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 +4 -0
- package/dist/services/tts/start-failure.js +19 -0
- package/dist/services/tts/text-processing.d.ts +9 -0
- package/dist/services/tts/text-processing.js +46 -33
- package/dist/services/tts/text-segmentation.d.ts +12 -0
- package/dist/services/tts/text-segmentation.js +60 -10
- package/dist/services/tts-runtime-config.d.ts +54 -26
- package/dist/services/tts-runtime-config.js +27 -112
- package/dist/tools/content-capability-resolution.d.ts +9 -11
- package/dist/tools/content-capability-resolution.js +8 -45
- package/dist/tools/registration.d.ts +59 -0
- package/dist/tools/registration.js +55 -0
- package/dist/tools/registrations/toolbar-registration-helpers.d.ts +6 -0
- package/dist/tools/registrations/toolbar-registration-helpers.js +8 -2
- package/dist/tools/tool-surface-host.d.ts +6 -10
- package/dist/tools/tool-surface-host.js +45 -50
- 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 +15 -29
- 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-b9dz5jvd.js +0 -40
- package/dist/components/chunks/ItemToolBar-h4xap0t0.js +0 -4
- package/dist/components/chunks/ItemToolBar-wr2myaah.js +0 -2
- package/dist/components/chunks/sre-engine-wf9s43gg.js +0 -1
- package/dist/context/runtime-context-consumer.d.ts +0 -13
- package/dist/context/runtime-context-consumer.js +0 -18
- 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 -74
- package/dist/runtime/section-runtime-engine-host-context.js +0 -57
- package/dist/runtime/stage-emit-gate.d.ts +0 -92
- package/dist/runtime/stage-emit-gate.js +0 -82
- package/dist/services/ContextVariableStore.d.ts +0 -113
- package/dist/services/ContextVariableStore.js +0 -206
- 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/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/tts-control-events.d.ts +0 -13
- package/dist/services/tts-control-events.js +0 -7
- package/dist/tools/client.d.ts +0 -10
- package/dist/tools/client.js +0 -10
- package/dist/tools/internal.d.ts +0 -44
- package/dist/tools/internal.js +0 -39
- package/dist/utils/logger.d.ts +0 -6
- package/dist/utils/logger.js +0 -10
package/README.md
CHANGED
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
# PIE Assessment Toolkit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The assessment toolkit coordinates the services around PIE item players: tool
|
|
4
|
+
policy and placement, tool providers, accommodations, text-to-speech (TTS),
|
|
5
|
+
highlighting and per-element tool state. It ships the `ToolkitCoordinator`, the
|
|
6
|
+
services it owns and the `<pie-assessment-toolkit>` custom element. This README
|
|
7
|
+
is for host integrators wiring the toolkit into a section player, an item player
|
|
8
|
+
or their own shell, and for contributors working on toolkit core. The
|
|
9
|
+
[tools and accommodations architecture](../../docs/tools-and-accomodations/architecture.md)
|
|
10
|
+
sets out the design, and [Configuring Tools](../../docs/tools-and-accomodations/tool_provider_system.md)
|
|
11
|
+
is the configuration guide.
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- [Install](#install)
|
|
16
|
+
- [Capabilities](#capabilities)
|
|
17
|
+
- [Architecture overview](#architecture-overview)
|
|
18
|
+
- [Configuration: attributes and a constructed coordinator](#configuration-attributes-and-a-constructed-coordinator)
|
|
19
|
+
- [Quick Start](#quick-start)
|
|
20
|
+
- [Tool Configuration Model](#tool-configuration-model)
|
|
21
|
+
- [Test Attempt Session](#test-attempt-session)
|
|
22
|
+
- [ToolkitCoordinator API](#toolkitcoordinator-api)
|
|
23
|
+
- [ElementToolStateStore API](#elementtoolstatestore-api)
|
|
24
|
+
- [Service APIs](#service-apis)
|
|
25
|
+
- [Integration with Section Player](#integration-with-section-player)
|
|
26
|
+
- [Instrumentation and Observability](#instrumentation-and-observability)
|
|
27
|
+
- [Section Runtime Engine (advanced)](#section-runtime-engine-advanced)
|
|
28
|
+
- [Writing a capability package](#writing-a-capability-package)
|
|
29
|
+
- [State separation](#state-separation)
|
|
30
|
+
- [Examples](#examples)
|
|
31
|
+
- [TypeScript Support](#typescript-support)
|
|
32
|
+
- [Content trust boundary](#content-trust-boundary)
|
|
33
|
+
- [Related Documentation](#related-documentation)
|
|
6
34
|
|
|
7
35
|
## Install
|
|
8
36
|
|
|
@@ -15,144 +43,52 @@ The examples build their tool registry with `createPackagedToolRegistry()` from
|
|
|
15
43
|
in its own dependencies. The section player depends on it too, but strict
|
|
16
44
|
installers such as pnpm expose only the packages a host declares.
|
|
17
45
|
|
|
18
|
-
Server-backed TTS (`backend: "
|
|
46
|
+
Server-backed TTS (`backend: "server"`) loads
|
|
19
47
|
`@pie-players/tts-client-server`, a dependency of
|
|
20
48
|
`@pie-players/pie-default-tool-loaders` whose TTS registration imports it on
|
|
21
49
|
first use. If it fails to load, TTS initialization reports a `provider-init`
|
|
22
50
|
framework error and falls back to browser speech.
|
|
23
51
|
|
|
24
|
-
|
|
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
|
-
|
|
31
|
-
## What's New: ToolkitCoordinator
|
|
32
|
-
|
|
33
|
-
✨ **Centralized Service Management**: The new `ToolkitCoordinator` provides a single entry point for all toolkit services, simplifying initialization and configuration.
|
|
34
|
-
|
|
35
|
-
**Before** (scattered services):
|
|
36
|
-
```typescript
|
|
37
|
-
// Create 5+ services independently
|
|
38
|
-
const ttsService = new TTSService();
|
|
39
|
-
const toolCoordinator = new ToolCoordinator();
|
|
40
|
-
const highlightCoordinator = new HighlightCoordinator();
|
|
41
|
-
const catalogResolver = new AccessibilityCatalogResolver([...]);
|
|
42
|
-
// Missing: ElementToolStateStore
|
|
43
|
-
|
|
44
|
-
await ttsService.initialize(new BrowserTTSProvider());
|
|
45
|
-
ttsService.setCatalogResolver(catalogResolver);
|
|
46
|
-
|
|
47
|
-
// Pass all services separately
|
|
48
|
-
player.ttsService = ttsService;
|
|
49
|
-
player.toolCoordinator = toolCoordinator;
|
|
50
|
-
// ...
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**After** (coordinator orchestrates):
|
|
54
|
-
```typescript
|
|
55
|
-
import { createPackagedToolRegistry } from '@pie-players/pie-default-tool-loaders';
|
|
56
|
-
|
|
57
|
-
// Create one coordinator with configuration
|
|
58
|
-
const toolRegistry = createPackagedToolRegistry();
|
|
59
|
-
const toolkitCoordinator = new ToolkitCoordinator({
|
|
60
|
-
assessmentId: 'my-assessment',
|
|
61
|
-
toolRegistry,
|
|
62
|
-
tools: {
|
|
63
|
-
providers: {
|
|
64
|
-
textToSpeech: { enabled: true, backend: 'browser' },
|
|
65
|
-
// Desmos needs an application key; see "Simple Default" below.
|
|
66
|
-
calculator: { enabled: true }
|
|
67
|
-
},
|
|
68
|
-
placement: {
|
|
69
|
-
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
70
|
-
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
71
|
-
passage: ['textToSpeech']
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Pass single coordinator to section-player through runtime
|
|
77
|
-
player.runtime = { ...(player.runtime ?? {}), coordinator: toolkitCoordinator };
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## What Does It Solve?
|
|
81
|
-
|
|
82
|
-
- **Centralized service management**: One coordinator owns all toolkit services
|
|
83
|
-
- **Tool coordination**: z-index management, visibility state, element-level state
|
|
84
|
-
- **Accommodation support**: IEP/504 tool configuration logic
|
|
85
|
-
- **TTS + annotation coordination**: Prevent conflicts between highlights
|
|
86
|
-
- **Event communication**: Standard contracts between components
|
|
87
|
-
- **Accessibility theming**: Consistent high-contrast, font sizing
|
|
88
|
-
- **State separation**: Ephemeral tool state separate from persistent session data
|
|
89
|
-
|
|
90
|
-
## Instrumentation and Observability
|
|
91
|
-
|
|
92
|
-
Toolkit instrumentation is provider-agnostic and additive. It uses the shared
|
|
93
|
-
`InstrumentationProvider` contract from `@pie-players/pie-players-shared`.
|
|
94
|
-
|
|
95
|
-
### Injection Path
|
|
96
|
-
|
|
97
|
-
When toolkit is hosted by section/assessment player flows, the canonical
|
|
98
|
-
provider path is the item-player loader config:
|
|
99
|
-
|
|
100
|
-
- `runtime.player.loaderConfig.instrumentationProvider`
|
|
101
|
-
|
|
102
|
-
### Semantics
|
|
103
|
-
|
|
104
|
-
- With `trackPageActions: true`, missing/`undefined` providers use the default New Relic provider path.
|
|
105
|
-
- `instrumentationProvider: null` explicitly disables instrumentation.
|
|
106
|
-
- Invalid provider objects are ignored (optional debug warning), also no-op.
|
|
107
|
-
- Existing `item-player` behavior remains the compatibility anchor.
|
|
108
|
-
- Debug overlays can consume the same stream by composing providers with
|
|
109
|
-
`CompositeInstrumentationProvider` (for example New Relic + debug panel).
|
|
110
|
-
- Toolkit telemetry forwarding uses the same provider path, so tool/backend
|
|
111
|
-
instrumentation is sent to production providers and is visible in debug panel
|
|
112
|
-
overlays.
|
|
113
|
-
|
|
114
|
-
### Toolkit-Owned Canonical Event Stream
|
|
115
|
-
|
|
116
|
-
- `pie-toolkit-stage-change`
|
|
117
|
-
- `pie-toolkit-runtime-owned`
|
|
118
|
-
- `pie-toolkit-runtime-inherited`
|
|
119
|
-
- `pie-toolkit-ready`
|
|
120
|
-
- `pie-toolkit-section-ready`
|
|
121
|
-
- `pie-toolkit-framework-error`
|
|
122
|
-
|
|
123
|
-
Toolkit tool/backend operational stream:
|
|
52
|
+
## Capabilities
|
|
124
53
|
|
|
125
|
-
-
|
|
126
|
-
|
|
127
|
-
-
|
|
54
|
+
- **Centralized service management**: one coordinator owns the toolkit services
|
|
55
|
+
and hands them to the players
|
|
56
|
+
- **Tool coordination**: z-index management, visibility state and per-element
|
|
57
|
+
tool state
|
|
58
|
+
- **Accommodation support**: tool policy driven by the student's personal needs
|
|
59
|
+
profile (PNP), including tools an IEP (Individualized Education Program) or
|
|
60
|
+
Section 504 plan requires
|
|
61
|
+
- **TTS and annotation coordination**: TTS reading highlights and student
|
|
62
|
+
annotations share one highlight layer without conflict
|
|
63
|
+
- **Event communication**: typed events between players, toolkit and tools
|
|
64
|
+
- **Accessibility theming**: high-contrast color schemes through the
|
|
65
|
+
color-scheme tool (`theme`)
|
|
66
|
+
- **State separation**: tool state kept apart from PIE session data
|
|
128
67
|
|
|
129
|
-
|
|
130
|
-
assessment semantic streams stay in their own layers to avoid overlap. Bridge
|
|
131
|
-
dedupe is a safety net, not a substitute for clear ownership.
|
|
68
|
+
## Architecture overview
|
|
132
69
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
See the [ToolkitCoordinator section in the architecture overview](../../docs/architecture/architecture.md#toolkitcoordinator-centralized-service-management) for the current design documentation.
|
|
70
|
+
The [Assessment toolkit section of the architecture overview](../../docs/architecture/architecture.md#assessment-toolkit)
|
|
71
|
+
places the toolkit among the players.
|
|
136
72
|
|
|
137
73
|
### Core Principles
|
|
138
74
|
|
|
139
|
-
1. **Centralized Coordination**: ToolkitCoordinator
|
|
75
|
+
1. **Centralized Coordination**: ToolkitCoordinator owns the services, and players reach them through it
|
|
140
76
|
2. **Composable Services**: Import only what you need (or use coordinator for convenience)
|
|
141
77
|
3. **No Framework Lock-in**: Works with any JavaScript framework
|
|
142
|
-
4. **Product Control**: Products control navigation, persistence,
|
|
78
|
+
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 (`hooks.createSectionSessionPersistence` on the coordinator, `createAssessmentSessionPersistence` on the assessment player)
|
|
143
79
|
5. **Standard Contracts**: Well-defined event types for component communication
|
|
144
|
-
6. **Element-Level Granularity**: Tool state tracked per PIE element
|
|
145
|
-
7. **State Separation**: Tool state
|
|
80
|
+
6. **Element-Level Granularity**: Tool state is tracked per PIE element
|
|
81
|
+
7. **State Separation**: Tool state is stored apart from PIE session data and never scored
|
|
146
82
|
|
|
147
|
-
## Configuration
|
|
83
|
+
## Configuration: attributes and a constructed coordinator
|
|
148
84
|
|
|
149
|
-
This package and `@pie-players/pie-section-player`
|
|
150
|
-
|
|
151
|
-
|
|
85
|
+
This package and `@pie-players/pie-section-player` give each input one entry
|
|
86
|
+
point (see [One tier per input](#one-tier-per-input)). A host that needs more
|
|
87
|
+
than the element inputs builds the coordinator itself.
|
|
152
88
|
|
|
153
|
-
###
|
|
89
|
+
### Element inputs and a constructed coordinator
|
|
154
90
|
|
|
155
|
-
- **
|
|
91
|
+
- **Element attributes / properties.** Use these for the
|
|
156
92
|
common cases that are static for the lifetime of the player or that hosts
|
|
157
93
|
want to set declaratively in HTML / templating frameworks. Example:
|
|
158
94
|
|
|
@@ -164,13 +100,14 @@ tier; the choice is about ergonomics, not capability.
|
|
|
164
100
|
></pie-assessment-toolkit>
|
|
165
101
|
```
|
|
166
102
|
|
|
167
|
-
- **
|
|
168
|
-
`runtime` object on
|
|
169
|
-
configuration, dynamic overrides, runtime mutation, fields without
|
|
170
|
-
|
|
103
|
+
- **A constructed `ToolkitCoordinator` (or a
|
|
104
|
+
`runtime` object on the player custom elements).** Use this for advanced cases: composed
|
|
105
|
+
configuration, dynamic overrides, runtime mutation, fields without an
|
|
106
|
+
attribute, or anything that benefits from being a single typed
|
|
171
107
|
object passed by reference. Example:
|
|
172
108
|
|
|
173
109
|
```ts
|
|
110
|
+
import { ToolkitCoordinator } from "@pie-players/pie-assessment-toolkit";
|
|
174
111
|
import { createPackagedToolRegistry } from "@pie-players/pie-default-tool-loaders";
|
|
175
112
|
|
|
176
113
|
const toolRegistry = createPackagedToolRegistry();
|
|
@@ -192,73 +129,77 @@ Top-level attributes use kebab-case (`assessment-id`,
|
|
|
192
129
|
`tool-config-strictness`). Section-player runtime configuration is grouped
|
|
193
130
|
under the `runtime` object instead of duplicated as top-level props.
|
|
194
131
|
|
|
195
|
-
###
|
|
196
|
-
|
|
197
|
-
The configuration object owns runtime fields. Section-player layout attributes
|
|
198
|
-
cover identity, layout, diagnostics, and callback/event convenience:
|
|
132
|
+
### One tier per input
|
|
199
133
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
134
|
+
Each input has one entry point. On the section-player layout elements,
|
|
135
|
+
`runtime` carries `assessmentId`, the player, tool, accessibility,
|
|
136
|
+
coordinator, env and isolation fields, runtime factories and the `on*`
|
|
137
|
+
callbacks; attributes carry section identity, layout controls, `nds-icons`,
|
|
138
|
+
`locale` and `tool-config-strictness`. Unset inputs take their documented
|
|
139
|
+
defaults.
|
|
204
140
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
`
|
|
209
|
-
`
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
kebab-attribute ↔ camelCaseProp ↔ runtime.<sameCamelCaseKey>
|
|
214
|
-
```
|
|
141
|
+
`<pie-assessment-toolkit>` takes the same fields as its own properties. Its
|
|
142
|
+
string and boolean attributes are `assessment-id`, `section-id`, `attempt-id`,
|
|
143
|
+
`nds-icons`, `locale`, `content-language`, `lazy-init`,
|
|
144
|
+
`tool-config-strictness`, `enabled-tools`, `player-type` and `isolation`;
|
|
145
|
+
`section`, `env`, `tools` and `player` also accept JSON attributes. It reads
|
|
146
|
+
`pnpEnforcement` from `tools.pnpEnforcement`.
|
|
215
147
|
|
|
216
|
-
|
|
148
|
+
### Canonical attribute set
|
|
217
149
|
|
|
218
|
-
- Identity: `assessment-id
|
|
219
|
-
- Runtime config on section-player
|
|
220
|
-
- Toolkit-only object properties: `tools`, `
|
|
150
|
+
- Identity: `assessment-id` on the toolkit, `section-id`, `attempt-id`
|
|
151
|
+
- Runtime config on section-player custom elements: `runtime`
|
|
152
|
+
- Toolkit-only object properties: `tools`, `toolRegistry`, `coordinator`,
|
|
221
153
|
`accessibility`
|
|
222
|
-
-
|
|
223
|
-
|
|
224
|
-
|
|
154
|
+
- Interface: `nds-icons`, `locale`
|
|
155
|
+
- Diagnostics: `tool-config-strictness`, and `debug` on the section-player layouts. Framework-error
|
|
156
|
+
delivery is via the `onFrameworkError` callback (a toolkit property,
|
|
157
|
+
`runtime.onFrameworkError` on a section player) and the
|
|
158
|
+
`framework-error` DOM event dispatched on the layout element.
|
|
225
159
|
|
|
226
|
-
|
|
160
|
+
Inputs outside the runtime config:
|
|
227
161
|
|
|
228
162
|
- Identity (`section-id`, `attempt-id`, `section`): per-attempt host
|
|
229
163
|
state, not configuration.
|
|
230
|
-
- Layout-only shell knobs on the section-player layout
|
|
164
|
+
- Layout-only shell knobs on the section-player layout elements
|
|
231
165
|
(`show-toolbar`, `toolbar-position`, `narrow-layout-breakpoint`,
|
|
232
|
-
`split-pane-collapse-strategy`):
|
|
166
|
+
`split-pane-collapse-strategy`): rendering concerns of the layout element.
|
|
233
167
|
- Per-region toolbar tool placement: hosts populate
|
|
234
168
|
`tools.placement.item` / `tools.placement.passage` (object form) or
|
|
235
169
|
`runtime.tools.placement.{item,passage}` directly.
|
|
236
|
-
- Runtime-only keys on the section-player layout
|
|
170
|
+
- Runtime-only keys on the section-player layout elements
|
|
237
171
|
(`createSectionController`, `isolation`): accepted only via
|
|
238
|
-
`runtime.<key>`.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
to override coordinator inheritance should pass an explicit
|
|
244
|
-
`coordinator={...}` instead.
|
|
172
|
+
`runtime.<key>`. Section-player layouts forward both to the wrapped
|
|
173
|
+
toolkit as properties. `<pie-assessment-toolkit>` keeps
|
|
174
|
+
`createSectionController` JS-only and takes `isolation` as an
|
|
175
|
+
attribute or a property. A `coordinator` passed to it wins over an
|
|
176
|
+
outer toolkit's.
|
|
245
177
|
|
|
246
|
-
###
|
|
178
|
+
### Adding an attribute (contributors)
|
|
247
179
|
|
|
248
|
-
|
|
180
|
+
A new input becomes an attribute only if all of the following hold:
|
|
249
181
|
|
|
250
182
|
- It is a common case that hosts set without composing a `ToolkitCoordinator`
|
|
251
183
|
/ `runtime` object.
|
|
252
184
|
- Its value is a primitive or small typed object that round-trips through
|
|
253
185
|
HTML attributes (string, boolean-like, number; structured data passes via
|
|
254
186
|
property assignment).
|
|
255
|
-
- It exists on every
|
|
187
|
+
- It exists on every custom element that owns the same knob, or has a
|
|
256
188
|
deliberate documented exclusion.
|
|
257
189
|
|
|
258
190
|
Otherwise expose it through the configuration object only.
|
|
259
191
|
|
|
260
192
|
## Quick Start
|
|
261
193
|
|
|
194
|
+
Three defaults decide whether tools show up at all:
|
|
195
|
+
|
|
196
|
+
- Placement is empty by default. A tool appears only at the levels
|
|
197
|
+
`tools.placement` names, and a profile grant does not place a tool.
|
|
198
|
+
- The section player renders its section toolbar only when `show-toolbar` is
|
|
199
|
+
`true`; the attribute defaults to `false`.
|
|
200
|
+
- The default calculator, Desmos, needs an API key
|
|
201
|
+
([Calculator providers](#calculator-providers)).
|
|
202
|
+
|
|
262
203
|
### Option 1: Use ToolkitCoordinator (Recommended)
|
|
263
204
|
|
|
264
205
|
```typescript
|
|
@@ -273,7 +214,7 @@ const coordinator = new ToolkitCoordinator({
|
|
|
273
214
|
tools: {
|
|
274
215
|
providers: {
|
|
275
216
|
textToSpeech: { enabled: true, backend: 'browser' },
|
|
276
|
-
// Desmos needs an
|
|
217
|
+
// Desmos needs an API key; see "Calculator providers" below.
|
|
277
218
|
calculator: { enabled: true }
|
|
278
219
|
},
|
|
279
220
|
placement: {
|
|
@@ -299,7 +240,7 @@ const toolState = coordinator.elementToolStateStore.getAllState();
|
|
|
299
240
|
|
|
300
241
|
### Controller Event Subscriptions (Helper First)
|
|
301
242
|
|
|
302
|
-
For host-side session
|
|
243
|
+
For host-side session and progress logic, the helper subscriptions below are simpler than the generic filter API. A cohort is the `(sectionId, attemptId)` pair a section controller serves. Subscriptions follow the toolkit's active cohort, so a single `subscribe*` call survives navigation between sections without re-wiring:
|
|
303
244
|
|
|
304
245
|
```typescript
|
|
305
246
|
const unsubscribeItem = coordinator.subscribeItemEvents({
|
|
@@ -320,10 +261,11 @@ unsubscribeItem?.();
|
|
|
320
261
|
unsubscribeSection?.();
|
|
321
262
|
```
|
|
322
263
|
|
|
323
|
-
|
|
264
|
+
Subscription behavior:
|
|
324
265
|
|
|
325
266
|
- Subscribe **after** the first `getOrCreateSectionController(...)` resolves; calling subscribe before any cohort exists throws.
|
|
326
267
|
- 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.
|
|
268
|
+
- 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.
|
|
327
269
|
- Subscribing the **same listener function** twice replaces the first subscription (filter args from the second call win).
|
|
328
270
|
- A listener that throws is caught and `console.warn`-logged; the throw does not interrupt fan-out to other listeners.
|
|
329
271
|
|
|
@@ -331,85 +273,12 @@ Use `subscribeSectionEvents(...)` when you need advanced/custom filtering mixes.
|
|
|
331
273
|
|
|
332
274
|
To persist or snapshot an inactive section, use `coordinator.getSectionController({ sectionId, attemptId })` — that lookup is by id and is unaffected by the active-cohort behavior described above.
|
|
333
275
|
|
|
334
|
-
#### Migrating from `<0.3.35` (BREAKING — pre-1.0)
|
|
335
|
-
|
|
336
|
-
`0.3.35` is the first release where `subscribeSectionEvents` (and its two helper wrappers `subscribeItemEvents` / `subscribeSectionLifecycleEvents`) follows the toolkit's *active section cohort* automatically. The on-the-wire shape of the subscription args object changed.
|
|
337
|
-
|
|
338
|
-
If your host code looked like this:
|
|
339
|
-
|
|
340
|
-
```typescript
|
|
341
|
-
const unsub = coordinator.subscribeItemEvents({
|
|
342
|
-
sectionId: 'section-1',
|
|
343
|
-
attemptId: 'attempt-1',
|
|
344
|
-
listener: handleEvent,
|
|
345
|
-
});
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
Update it to drop `sectionId` / `attemptId`:
|
|
349
|
-
|
|
350
|
-
```typescript
|
|
351
|
-
const unsub = coordinator.subscribeItemEvents({
|
|
352
|
-
listener: handleEvent,
|
|
353
|
-
});
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
What this means in practice for typed integrations:
|
|
357
|
-
|
|
358
|
-
- **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.**
|
|
359
|
-
- **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.
|
|
360
|
-
- **New precondition.** `subscribe*` now throws if no active section cohort exists. Subscribe **after** the first `getOrCreateSectionController(...)` resolves. Subscribing on `toolkit-ready` alone is no longer sufficient — though in practice the section player emits `toolkit-ready` *after* its first `getOrCreateSectionController(...)` resolves, so a `toolkit-ready` anchor is safe in section-player hosts.
|
|
361
|
-
- **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.
|
|
362
|
-
- **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`.
|
|
363
|
-
- **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.
|
|
364
|
-
|
|
365
|
-
If your local types were hand-rolled structural copies of the public arg types (e.g. an Angular wrapper duplicating the shape rather than importing the package types), `sectionId` / `attemptId` keys will compile but are dead code at runtime — recommend dropping them as part of the upgrade.
|
|
366
|
-
|
|
367
|
-
#### Pre-Phase D vs Phase D wrapper pattern
|
|
368
|
-
|
|
369
|
-
```typescript
|
|
370
|
-
// BEFORE (pre-Phase D): rebind for every section change because the
|
|
371
|
-
// subscription was pinned to a sectionId.
|
|
372
|
-
public handleToolkitReady(event: Event): void {
|
|
373
|
-
const coordinator = (event as CustomEvent).detail?.coordinator;
|
|
374
|
-
if (!coordinator) return;
|
|
375
|
-
this.controllerUnsubscribe?.(); // detach prior pin
|
|
376
|
-
const itemUnsub = coordinator.subscribeItemEvents({
|
|
377
|
-
sectionId: this.sectionId,
|
|
378
|
-
listener: handleItemEvent,
|
|
379
|
-
});
|
|
380
|
-
const sectionUnsub = coordinator.subscribeSectionLifecycleEvents({
|
|
381
|
-
sectionId: this.sectionId,
|
|
382
|
-
listener: handleSectionEvent,
|
|
383
|
-
});
|
|
384
|
-
this.controllerUnsubscribe = () => { itemUnsub?.(); sectionUnsub?.(); };
|
|
385
|
-
}
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
```typescript
|
|
389
|
-
// AFTER (Phase D): subscribe once; the listener follows the active
|
|
390
|
-
// cohort across all subsequent navigation.
|
|
391
|
-
public handleToolkitReady(event: Event): void {
|
|
392
|
-
const coordinator = (event as CustomEvent).detail?.coordinator;
|
|
393
|
-
if (!coordinator) return;
|
|
394
|
-
this.toolkitCoordinator = coordinator;
|
|
395
|
-
if (this.controllerUnsubscribe) return; // already subscribed; do nothing on re-fire
|
|
396
|
-
const itemUnsub = coordinator.subscribeItemEvents({
|
|
397
|
-
listener: handleItemEvent,
|
|
398
|
-
});
|
|
399
|
-
const sectionUnsub = coordinator.subscribeSectionLifecycleEvents({
|
|
400
|
-
listener: handleSectionEvent,
|
|
401
|
-
});
|
|
402
|
-
this.controllerUnsubscribe = () => { itemUnsub?.(); sectionUnsub?.(); };
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
276
|
### Option 2: Create Services Manually (Advanced)
|
|
407
277
|
|
|
408
278
|
```typescript
|
|
409
279
|
import {
|
|
410
280
|
TTSService,
|
|
411
281
|
BrowserTTSProvider,
|
|
412
|
-
ToolCoordinator,
|
|
413
282
|
HighlightCoordinator,
|
|
414
283
|
AccessibilityCatalogResolver,
|
|
415
284
|
ElementToolStateStore
|
|
@@ -417,7 +286,6 @@ import {
|
|
|
417
286
|
|
|
418
287
|
// Initialize each service independently
|
|
419
288
|
const ttsService = new TTSService();
|
|
420
|
-
const toolCoordinator = new ToolCoordinator();
|
|
421
289
|
const highlightCoordinator = new HighlightCoordinator();
|
|
422
290
|
const elementToolStateStore = new ElementToolStateStore();
|
|
423
291
|
const catalogResolver = new AccessibilityCatalogResolver([], 'en-US');
|
|
@@ -425,92 +293,172 @@ const catalogResolver = new AccessibilityCatalogResolver([], 'en-US');
|
|
|
425
293
|
await ttsService.initialize(new BrowserTTSProvider());
|
|
426
294
|
ttsService.setCatalogResolver(catalogResolver);
|
|
427
295
|
|
|
428
|
-
//
|
|
429
|
-
player.ttsService = ttsService;
|
|
430
|
-
player.toolCoordinator = toolCoordinator;
|
|
431
|
-
// ...
|
|
296
|
+
// Use them from host-built UI
|
|
432
297
|
```
|
|
433
298
|
|
|
299
|
+
No player element takes services one by one. The section and assessment
|
|
300
|
+
players reach services only through a coordinator, `runtime.coordinator` on a
|
|
301
|
+
section player and `coordinator` on an assessment player; manually created
|
|
302
|
+
services serve host code that drives them directly. The tool coordinator is
|
|
303
|
+
not among them: it is created and disposed by its `ToolkitCoordinator`.
|
|
304
|
+
|
|
434
305
|
### Without a Section Player
|
|
435
306
|
|
|
436
|
-
`<pie-assessment-toolkit>` needs no section. Bind none and it
|
|
437
|
-
|
|
438
|
-
|
|
307
|
+
`<pie-assessment-toolkit>` needs no section. Bind none and it serves the item
|
|
308
|
+
toolbars inside it and the tools they open, around a plain item player that
|
|
309
|
+
takes nothing from the toolkit. `<pie-item-scope>` holds the item for its tools,
|
|
310
|
+
as it does in a section player's card. The tree this builds:
|
|
439
311
|
|
|
440
312
|
```html
|
|
441
|
-
<pie-assessment-toolkit
|
|
442
|
-
<pie-item-
|
|
443
|
-
|
|
313
|
+
<pie-assessment-toolkit>
|
|
314
|
+
<pie-item-scope item-id="q1">
|
|
315
|
+
<pie-item-toolbar></pie-item-toolbar>
|
|
316
|
+
<div data-region="content">
|
|
317
|
+
<pie-item-player></pie-item-player>
|
|
318
|
+
</div>
|
|
319
|
+
</pie-item-scope>
|
|
444
320
|
</pie-assessment-toolkit>
|
|
445
321
|
```
|
|
446
322
|
|
|
323
|
+
The host sets the toolkit's properties before the tree enters the document. A
|
|
324
|
+
scope registers as soon as it mounts, and that first registration starts the
|
|
325
|
+
coordinator from the toolkit's inputs at that moment; see the binding rules
|
|
326
|
+
below.
|
|
327
|
+
|
|
328
|
+

|
|
329
|
+
|
|
447
330
|
```typescript
|
|
448
331
|
import '@pie-players/pie-assessment-toolkit/components/pie-assessment-toolkit-element';
|
|
332
|
+
import '@pie-players/pie-assessment-toolkit/components/item-scope-element';
|
|
449
333
|
import '@pie-players/pie-assessment-toolkit/components/item-toolbar-element';
|
|
450
334
|
import '@pie-players/pie-item-player';
|
|
451
|
-
import {
|
|
452
|
-
createPackagedToolRegistry,
|
|
453
|
-
DEFAULT_TOOL_MODULE_LOADERS,
|
|
454
|
-
} from '@pie-players/pie-default-tool-loaders';
|
|
335
|
+
import { createPackagedToolRegistry } from '@pie-players/pie-default-tool-loaders';
|
|
455
336
|
|
|
456
337
|
// With no section player, the registry's loaders are what define the tool elements.
|
|
457
|
-
const toolRegistry = createPackagedToolRegistry(
|
|
458
|
-
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
459
|
-
});
|
|
338
|
+
const toolRegistry = createPackagedToolRegistry();
|
|
460
339
|
|
|
461
|
-
toolkit
|
|
340
|
+
const toolkit = document.createElement('pie-assessment-toolkit');
|
|
341
|
+
toolkit.tools = {
|
|
342
|
+
pnpEnforcement: 'on',
|
|
343
|
+
placement: { item: ['textToSpeech', 'calculator'] },
|
|
344
|
+
};
|
|
462
345
|
toolkit.toolRegistry = toolRegistry;
|
|
463
346
|
toolkit.toolContextResolvers = toolContextResolvers;
|
|
464
347
|
toolkit.assessment = { id: 'a1', personalNeedsProfile: { supports: ['calculator'] } };
|
|
465
348
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
349
|
+
const scope = document.createElement('pie-item-scope');
|
|
350
|
+
scope.setAttribute('item-id', 'q1');
|
|
351
|
+
scope.item = item;
|
|
352
|
+
scope.innerHTML = `
|
|
353
|
+
<pie-item-toolbar></pie-item-toolbar>
|
|
354
|
+
<div data-region="content"><pie-item-player></pie-item-player></div>
|
|
355
|
+
`;
|
|
356
|
+
|
|
357
|
+
toolkit.append(scope);
|
|
358
|
+
container.append(toolkit);
|
|
469
359
|
```
|
|
470
360
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
A
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
`
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
`
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
361
|
+
Inside a scope:
|
|
362
|
+
|
|
363
|
+
- The toolbars and tools take the item and its id from the scope, and act on
|
|
364
|
+
the scope or on the element its `scopeElement` property names.
|
|
365
|
+
`textToSpeech` reads the scope's `[data-region="content"]` first.
|
|
366
|
+
- A toolbar's own `item`, `item-id` and `scopeElement` override the scope, and
|
|
367
|
+
its `toolRegistry` overrides the toolkit's.
|
|
368
|
+
- The scope registers the item with the toolkit once it finds one above it, so
|
|
369
|
+
it may mount first. The toolkit files the item's accessibility catalogs,
|
|
370
|
+
which read-aloud speaks in place of the markup they name.
|
|
371
|
+
- Item player events pass through the scope unchanged, and the session stays
|
|
372
|
+
the item player's.
|
|
373
|
+
- A registry whose `toolModuleLoaders` leave a tool out renders a button for it
|
|
374
|
+
whose element never loads unless the host defines it.
|
|
375
|
+
|
|
376
|
+
**Profile changes.** A profile change is a new `assessment` value; the toolbars
|
|
377
|
+
re-derive on the policy change it emits. A host that holds the coordinator from
|
|
378
|
+
`runtime-ready`, or passes its own as `coordinator`, changes the profile with
|
|
379
|
+
`coordinator.updateAssessment(...)`; the toolkit applies its `assessment`
|
|
380
|
+
property only to a coordinator it owns. The
|
|
381
|
+
[`calculator-pnp` section demo](../../apps/section-demos/src/routes/%28demos%29/calculator-pnp/+page.svelte)
|
|
382
|
+
composes the owned-coordinator form.
|
|
383
|
+
|
|
384
|
+
**Readiness.** The toolkit announces `runtime-ready`, with
|
|
385
|
+
`{ runtimeId, coordinator, ownership }`, once per coordinator, with or without a
|
|
386
|
+
section. Without one, the coordinator starts at the first item that registers,
|
|
387
|
+
and a host reads its readiness from `coordinator.waitUntilReady()` or
|
|
388
|
+
`isReady()`. `toolkit-ready` and `section-ready` wait for a bound section, and
|
|
389
|
+
both fire again for every section the toolkit initializes, `toolkit-ready`
|
|
390
|
+
first, so a handler that needs the coordinator once guards itself. The toolkit
|
|
391
|
+
emits no stage events: `pie-stage-change` is the section player's.
|
|
392
|
+
|
|
393
|
+
**Coordinator binding.** The toolkit builds its own coordinator at mount from
|
|
394
|
+
`tools`, `enabled-tools`, `assessment-id`, `accessibility`, `lazy-init`,
|
|
395
|
+
`tool-config-strictness` and `toolRegistry`.
|
|
396
|
+
|
|
397
|
+
- Its first content binds it: the section, or without one the first item scope
|
|
398
|
+
that registers.
|
|
399
|
+
- Content that arrives after one of those inputs changed binds a coordinator
|
|
400
|
+
rebuilt from the current values, and the toolbars move to it.
|
|
401
|
+
- Once bound, a change to those inputs is reported once in the console and does
|
|
402
|
+
not reach the coordinator. The exception is a `toolRegistry` given to a
|
|
403
|
+
toolkit that had none, which the coordinator adopts in place.
|
|
404
|
+
- `tools.pnpEnforcement`, `assessment` and `toolContextResolvers` apply at any
|
|
405
|
+
time.
|
|
406
|
+
- The coordinator reports feature policy asked with no assessment bound only
|
|
407
|
+
while `tools.pnpEnforcement` is `on`: a toolkit given no `assessment` and no
|
|
408
|
+
enforcement has asked for no accommodation.
|
|
409
|
+
|
|
410
|
+
**Nested toolkits.** A toolkit nested in another inherits the outer one's
|
|
411
|
+
coordinator when the outer one has a coordinator by the time the inner one
|
|
412
|
+
connects, which holds for both mounted together. Otherwise the inner one builds
|
|
413
|
+
its own and keeps it, and reports an outer coordinator arriving later once in
|
|
414
|
+
the console. `isolation` `"force"` keeps a nested toolkit on its own coordinator
|
|
415
|
+
by design.
|
|
416
|
+
|
|
417
|
+
**Unmount.** At unmount the toolkit disposes the controller of every section it
|
|
418
|
+
bound. A coordinator it owns it then disposes too; one the host passes, or an
|
|
419
|
+
outer toolkit lends, stays with its owner, and a later toolkit on it restores
|
|
420
|
+
those sections from their sessions.
|
|
421
|
+
|
|
422
|
+
**TTS and provider startup.** Text-to-speech starts at the toolkit's first
|
|
423
|
+
content, once the section composes or the first item scope registers, and
|
|
424
|
+
`coordinator.waitUntilReady()` waits for it. With `lazy-init` it starts at the
|
|
425
|
+
first read-aloud instead, unless policy grants it. A tool provider or
|
|
426
|
+
text-to-speech that fails to start is a recoverable framework error: the tool
|
|
427
|
+
reports itself unavailable and the assessment goes on. When policy grants the
|
|
428
|
+
tool, through an item or district requirement, a profile support or a
|
|
429
|
+
test-administration override set to `true`, the failure is fatal, including one
|
|
430
|
+
that a later policy change grants. For a toolbar tool, only grants from
|
|
431
|
+
decisions that PNP enforcement applies to make the failure fatal.
|
|
496
432
|
|
|
497
433
|
## Tool Configuration Model
|
|
498
434
|
|
|
499
|
-
The toolkit uses one canonical `tools` model with
|
|
435
|
+
The toolkit uses one canonical `tools` model with four keys:
|
|
500
436
|
|
|
501
|
-
- `policy`:
|
|
437
|
+
- `policy`: which tools may appear at all. A non-empty `allowed` list is an
|
|
438
|
+
allow-list; `blocked` removes a tool absolutely, whatever else grants it.
|
|
502
439
|
- `placement`: where tools appear (`section`, `item`, `passage`). Each tool
|
|
503
440
|
declares the levels it supports; `calculator` is item-only, and a tool placed
|
|
504
441
|
at a level it does not support fails validation, which throws under the
|
|
505
442
|
default `toolConfigStrictness: "error"`.
|
|
506
|
-
- `providers`: provider
|
|
443
|
+
- `providers`: provider and runtime options per tool (calculator,
|
|
444
|
+
textToSpeech and so on). `providers.<toolId>.enabled: false` vetoes the tool.
|
|
445
|
+
- `pnpEnforcement`: `"on"` or `"off"` forces personal needs profile gating;
|
|
446
|
+
omitted, the toolkit turns it on when the assessment carries profile or
|
|
447
|
+
district material ([PNP enforcement](docs/PNP_CONFIGURATION.md#pnp-enforcement)).
|
|
448
|
+
|
|
449
|
+
A profile, district policy or item setting names a tool by its `toolId`, and an
|
|
450
|
+
id no registered tool carries raises `tool-policy.unknownSupportId`.
|
|
451
|
+
[Support ids](docs/TOOL_REGISTRY.md#support-ids) lists the AfA PNP 3.0 terms the
|
|
452
|
+
packaged ids serve.
|
|
507
453
|
|
|
508
454
|
Example:
|
|
509
455
|
|
|
510
456
|
```typescript
|
|
511
457
|
tools: {
|
|
512
458
|
policy: {
|
|
459
|
+
// Only these tools may appear...
|
|
513
460
|
allowed: ['calculator', 'textToSpeech', 'answerEliminator', 'graph', 'periodicTable'],
|
|
461
|
+
// ...and graph never does.
|
|
514
462
|
blocked: ['graph']
|
|
515
463
|
},
|
|
516
464
|
placement: {
|
|
@@ -529,25 +477,38 @@ tools: {
|
|
|
529
477
|
}
|
|
530
478
|
```
|
|
531
479
|
|
|
532
|
-
|
|
480
|
+
With this configuration the section toolbar shows only `periodicTable`:
|
|
481
|
+
`graph` is blocked, and `protractor`, `lineReader` and `ruler` are outside the
|
|
482
|
+
allow-list. The item and passage placements show every tool they list.
|
|
533
483
|
|
|
534
|
-
|
|
484
|
+
### Scope and Lifecycle
|
|
535
485
|
|
|
536
|
-
Tool instances use structured
|
|
486
|
+
Tool instances use structured ids that make their scope explicit:
|
|
537
487
|
|
|
538
488
|
```text
|
|
539
|
-
<toolId>:<scopeLevel>:<scopeId>
|
|
489
|
+
<toolId>:<scopeLevel>:<scopeId>
|
|
540
490
|
```
|
|
541
491
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
492
|
+
`scopeLevel` is one of `assessment`, `section`, `item`, `passage` or `rubric`,
|
|
493
|
+
and `createScopedToolId` throws on any other. The toolbars the players render
|
|
494
|
+
scope their ids at `section`, `item` or `passage`, and a rubric block's toolbar
|
|
495
|
+
scopes at `passage`. Examples from a section player:
|
|
496
|
+
|
|
497
|
+
- `lineReader:section:reading-1`
|
|
498
|
+
- `theme:section:reading-1`
|
|
499
|
+
- `calculator:item:q1`
|
|
545
500
|
- `textToSpeech:passage:passage-1`
|
|
546
|
-
|
|
501
|
+
|
|
502
|
+
The section player keeps every item card of a section mounted while the student
|
|
503
|
+
moves between its items. The ElementToolStateStore keeps answer eliminations,
|
|
504
|
+
annotation highlights and the color-scheme choice, and the coordinator's
|
|
505
|
+
`saveToolState`/`loadToolState` hooks carry them across reloads
|
|
506
|
+
([Persistence Integration](#persistence-integration)). Which tools are open is
|
|
507
|
+
not restored, and graph points start over.
|
|
547
508
|
|
|
548
509
|
### Item-Level Tools (`tools.placement.item`)
|
|
549
510
|
|
|
550
|
-
Tools that operate
|
|
511
|
+
Tools that operate within the context of one item:
|
|
551
512
|
|
|
552
513
|
```typescript
|
|
553
514
|
tools: {
|
|
@@ -558,24 +519,37 @@ tools: {
|
|
|
558
519
|
```
|
|
559
520
|
|
|
560
521
|
**Characteristics:**
|
|
561
|
-
- **Scope**:
|
|
562
|
-
- **
|
|
563
|
-
|
|
564
|
-
- **
|
|
565
|
-
- **
|
|
566
|
-
|
|
567
|
-
**
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
- **
|
|
522
|
+
- **Scope**: bound to one item's DOM context
|
|
523
|
+
- **Lifetime**: an item's tools live as long as its card, which stays mounted
|
|
524
|
+
until the section changes
|
|
525
|
+
- **State**: isolated per item (eliminations on Q5 don't affect Q6)
|
|
526
|
+
- **UI pattern**: buttons in the item toolbar; tools such as the calculator and
|
|
527
|
+
the dictionaries open in a floating panel
|
|
528
|
+
- **Persistence**: answer eliminations and annotation highlights are kept per
|
|
529
|
+
PIE element in the ElementToolStateStore
|
|
530
|
+
|
|
531
|
+
**Available item-level tools:**
|
|
532
|
+
- **Text-to-speech** (`textToSpeech`): reads the item or passage text
|
|
533
|
+
- **Answer eliminator** (`answerEliminator`, item only): strikes through answer
|
|
534
|
+
choices
|
|
535
|
+
- **Calculator** (`calculator`, item only): basic, scientific or graphing
|
|
536
|
+
calculator
|
|
537
|
+
- **Annotation toolbar** (`annotationToolbar`): highlights and annotates
|
|
538
|
+
selected text; it opens from a text selection, outside the toolbars
|
|
539
|
+
- **Line reader** (`lineReader`), **ruler** (`ruler`), **protractor**
|
|
540
|
+
(`protractor`), **graph** (`graph`) and **periodic table** (`periodicTable`)
|
|
541
|
+
- **Dictionaries** (`dictionary`, `pictureDictionary`, `dictionarySpanish`,
|
|
542
|
+
`pictureDictionarySpanish`)
|
|
543
|
+
|
|
544
|
+
[Default Tools](docs/TOOL_REGISTRY.md#default-tools) lists every packaged tool
|
|
545
|
+
with the content it applies to.
|
|
572
546
|
|
|
573
547
|
**Example Use Case:**
|
|
574
548
|
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.
|
|
575
549
|
|
|
576
550
|
### Section-Level Tools (`tools.placement.section`)
|
|
577
551
|
|
|
578
|
-
Tools that
|
|
552
|
+
Tools that float above the section and stay open across its items:
|
|
579
553
|
|
|
580
554
|
```typescript
|
|
581
555
|
tools: {
|
|
@@ -585,26 +559,30 @@ tools: {
|
|
|
585
559
|
}
|
|
586
560
|
```
|
|
587
561
|
|
|
588
|
-
The section player renders the section toolbar only when its `show-toolbar`
|
|
589
|
-
attribute is `true`; the attribute defaults to `false`.
|
|
590
|
-
|
|
591
562
|
**Characteristics:**
|
|
592
|
-
- **Scope**:
|
|
593
|
-
- **
|
|
594
|
-
-
|
|
595
|
-
- **
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
- **
|
|
601
|
-
- **
|
|
602
|
-
- **
|
|
603
|
-
- **
|
|
604
|
-
- **
|
|
563
|
+
- **Scope**: section-wide, shared across the section's items
|
|
564
|
+
- **Lifetime**: one instance per section; at a section change the tool
|
|
565
|
+
re-scopes to the new section and its state starts over
|
|
566
|
+
- **State**: an open tool stays open and in place while the student moves
|
|
567
|
+
between the section's items
|
|
568
|
+
- **UI pattern**: draggable floating panels with z-index management
|
|
569
|
+
|
|
570
|
+
**Available section-level tools:**
|
|
571
|
+
- **Graph** (`graph`): coordinate plane
|
|
572
|
+
- **Periodic table** (`periodicTable`): interactive periodic table reference
|
|
573
|
+
- **Protractor** (`protractor`): angle measurement
|
|
574
|
+
- **Ruler** (`ruler`): linear measurement (metric and imperial)
|
|
575
|
+
- **Line reader** (`lineReader`): reading guide and masking overlay
|
|
576
|
+
- **Dictionaries** (`dictionary`, `pictureDictionary`, `dictionarySpanish`,
|
|
577
|
+
`pictureDictionarySpanish`)
|
|
578
|
+
- **The color-scheme tool** (`theme`): high-contrast color schemes; it also
|
|
579
|
+
supports assessment level
|
|
580
|
+
|
|
581
|
+
Every section-level tool except the color-scheme tool also supports item
|
|
582
|
+
placement.
|
|
605
583
|
|
|
606
584
|
**Example Use Case:**
|
|
607
|
-
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.
|
|
585
|
+
A student opens the periodic table on Question 2 and moves it beside the passage. When they navigate to Question 7 of the same section, it is still open where they left it.
|
|
608
586
|
|
|
609
587
|
### When to Use Each
|
|
610
588
|
|
|
@@ -614,17 +592,20 @@ Use **item-level tools** when:
|
|
|
614
592
|
- Tool appears inline with the question (space-efficient)
|
|
615
593
|
- Tool behavior is contextual to the current item
|
|
616
594
|
|
|
617
|
-
Use **
|
|
595
|
+
Use **section-level tools** when:
|
|
618
596
|
- Tool is a general-purpose utility used across multiple questions
|
|
619
|
-
- State should persist
|
|
597
|
+
- State should persist while the student moves between a section's items
|
|
620
598
|
- Tool needs independent positioning and sizing
|
|
621
599
|
- Tool provides reference information or computation capability
|
|
622
600
|
|
|
623
|
-
### Configuration
|
|
601
|
+
### Configuration example
|
|
624
602
|
|
|
625
|
-
|
|
603
|
+
A coordinator shows a tool only where `tools.placement` lists it, and its
|
|
604
|
+
default placement is empty. This configuration places the commonly used tools
|
|
605
|
+
at both kinds of level:
|
|
626
606
|
|
|
627
607
|
```typescript
|
|
608
|
+
import { ToolkitCoordinator } from '@pie-players/pie-assessment-toolkit';
|
|
628
609
|
import { createPackagedToolRegistry } from '@pie-players/pie-default-tool-loaders';
|
|
629
610
|
|
|
630
611
|
const toolRegistry = createPackagedToolRegistry();
|
|
@@ -661,44 +642,16 @@ const coordinator = new ToolkitCoordinator({
|
|
|
661
642
|
});
|
|
662
643
|
```
|
|
663
644
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
For most use cases, simply enable all available tools:
|
|
645
|
+
### Calculator providers
|
|
667
646
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
placement: {
|
|
677
|
-
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
678
|
-
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
679
|
-
passage: ['textToSpeech']
|
|
680
|
-
},
|
|
681
|
-
providers: {
|
|
682
|
-
calculator: {
|
|
683
|
-
enabled: true,
|
|
684
|
-
provider: {
|
|
685
|
-
runtime: { authFetcher: fetchLicensedDesmosApiKey }
|
|
686
|
-
}
|
|
687
|
-
},
|
|
688
|
-
textToSpeech: { enabled: true, backend: 'browser' }
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
});
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
The ToolkitCoordinator handles service initialization, provider management, and
|
|
695
|
-
state coordination. With no calculator provider configuration, the Desmos
|
|
696
|
-
implementation remains the default. Desmos's CDN rejects a `calculator.js`
|
|
697
|
-
request without an `apiKey` (HTTP 403), so the Desmos calculator opens only when
|
|
698
|
-
the host supplies `provider.init.apiKey` or `provider.runtime.authFetcher`, or
|
|
699
|
-
has already loaded `window.Desmos`; the adapter's keyless request grants no
|
|
700
|
-
Desmos license either. Runtime key delivery avoids committing the key but does
|
|
701
|
-
not hide it from the browser's Desmos script request. The bundled open-source
|
|
647
|
+
With no calculator provider configured, the calculator is Desmos. Desmos's CDN
|
|
648
|
+
rejects a `calculator.js` request without an `apiKey` (HTTP 403), so the Desmos
|
|
649
|
+
calculator opens only when the host supplies a key through
|
|
650
|
+
`provider.runtime.authFetcher`, a `provider.init.proxyEndpoint` that returns
|
|
651
|
+
`{ apiKey }`, or `provider.init.apiKey`, or has already loaded `window.Desmos`.
|
|
652
|
+
Without one the adapter throws "An apiKey or proxyEndpoint is required to load
|
|
653
|
+
Desmos." Runtime key delivery keeps the key out of the bundle, and the
|
|
654
|
+
browser's Desmos script request still carries it. The bundled open-source
|
|
702
655
|
implementation below needs no key.
|
|
703
656
|
|
|
704
657
|
Select the separate GeoGebra implementation explicitly without changing the
|
|
@@ -736,7 +689,8 @@ tools: {
|
|
|
736
689
|
providers: {
|
|
737
690
|
textToSpeech: {
|
|
738
691
|
enabled: true,
|
|
739
|
-
backend: '
|
|
692
|
+
backend: 'server',
|
|
693
|
+
serverProvider: 'polly'
|
|
740
694
|
}
|
|
741
695
|
}
|
|
742
696
|
}
|
|
@@ -745,14 +699,15 @@ tools: {
|
|
|
745
699
|
By default, server-backed TTS resolves:
|
|
746
700
|
|
|
747
701
|
- `apiEndpoint: '/api/tts'`
|
|
748
|
-
- `transportMode: 'pie'`
|
|
749
|
-
- `endpointValidationMode: 'voices'
|
|
702
|
+
- `transportMode: 'custom'` for `serverProvider: 'custom'`, else `'pie'`
|
|
703
|
+
- `endpointValidationMode: 'voices'`, so the provider reads the voices route
|
|
704
|
+
before it reports ready; `'none'` skips the probe
|
|
750
705
|
|
|
751
706
|
You can still set `apiEndpoint` explicitly when your host route is not `/api/tts`.
|
|
752
707
|
|
|
753
708
|
### Inline TTS Speed Options
|
|
754
709
|
|
|
755
|
-
Inline TTS speed buttons are configurable via `speedOptions`
|
|
710
|
+
Inline TTS speed buttons are configurable via `speedOptions` on the provider config.
|
|
756
711
|
|
|
757
712
|
```typescript
|
|
758
713
|
tools: {
|
|
@@ -760,9 +715,7 @@ tools: {
|
|
|
760
715
|
textToSpeech: {
|
|
761
716
|
enabled: true,
|
|
762
717
|
backend: "browser",
|
|
763
|
-
|
|
764
|
-
speedOptions: [2, 1.25, 1.5] // host options keep this order; Normal is added if omitted
|
|
765
|
-
}
|
|
718
|
+
speedOptions: [2, 1.25, 1.5] // host options keep this order; Normal is added if omitted
|
|
766
719
|
}
|
|
767
720
|
}
|
|
768
721
|
}
|
|
@@ -779,13 +732,11 @@ tools: {
|
|
|
779
732
|
enabled: true,
|
|
780
733
|
backend: "server",
|
|
781
734
|
serverProvider: "custom",
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
]
|
|
788
|
-
}
|
|
735
|
+
speedOptions: [
|
|
736
|
+
{ rate: 0.8, label: "Slow", ariaLabel: "Slow speed" },
|
|
737
|
+
{ rate: 1, label: "Normal", ariaLabel: "Normal speed", default: true },
|
|
738
|
+
{ rate: 1.5, label: "Fast", ariaLabel: "Fast speed" }
|
|
739
|
+
]
|
|
789
740
|
}
|
|
790
741
|
}
|
|
791
742
|
}
|
|
@@ -810,25 +761,15 @@ tools: {
|
|
|
810
761
|
- Speed choices are exposed as a `Playback speed` radio group: one option is
|
|
811
762
|
always selected, and choosing the already-selected speed is a no-op.
|
|
812
763
|
- If only one speed option remains, the speed group is hidden by default. Set
|
|
813
|
-
`showSingleSpeedOption: true`
|
|
814
|
-
|
|
815
|
-
### Runtime Fallback: Server TTS -> Browser TTS
|
|
816
|
-
|
|
817
|
-
When server-backed playback fails at runtime (for example `503`, network outage,
|
|
818
|
-
or synthesized asset fetch failure), `TTSService` now performs a one-time
|
|
819
|
-
runtime fallback for that session:
|
|
764
|
+
`showSingleSpeedOption: true` on the provider config to surface that one-option state.
|
|
820
765
|
|
|
821
|
-
|
|
822
|
-
2. Rebinds highlight callbacks to the browser provider.
|
|
823
|
-
3. Retries the same `speak()` request once.
|
|
766
|
+
### Browser Fallback
|
|
824
767
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
- `pie-tool-runtime-fallback` (fallback switch succeeded)
|
|
831
|
-
- `pie-tool-runtime-fallback-error` (fallback switch failed)
|
|
768
|
+
The coordinator owns fallback to browser speech. When a server-backed provider
|
|
769
|
+
fails to initialize, the coordinator re-initializes TTS on the browser backend
|
|
770
|
+
and reports `pie-tool-init-fallback`. `TTSService.initialize` itself rejects
|
|
771
|
+
when its provider fails to start, and a playback failure after initialization
|
|
772
|
+
rejects that read without switching provider.
|
|
832
773
|
|
|
833
774
|
`provider.runtime.authFetcher` is optional. Add it only when your host environment
|
|
834
775
|
requires runtime auth material for TTS requests:
|
|
@@ -838,7 +779,8 @@ tools: {
|
|
|
838
779
|
providers: {
|
|
839
780
|
textToSpeech: {
|
|
840
781
|
enabled: true,
|
|
841
|
-
backend: '
|
|
782
|
+
backend: 'server',
|
|
783
|
+
serverProvider: 'polly',
|
|
842
784
|
apiEndpoint: '/api/tts',
|
|
843
785
|
provider: {
|
|
844
786
|
runtime: {
|
|
@@ -858,10 +800,10 @@ provider config: a returned `authToken` is sent as `Authorization: Bearer <token
|
|
|
858
800
|
and returned `headers` with every synthesis request, and under
|
|
859
801
|
`includeAuthOnAssetFetch` the `Authorization` header also reaches the custom
|
|
860
802
|
transport's speech-mark and audio fetches. A failed fetch falls back to browser
|
|
861
|
-
speech and reports `pie-tool-init-
|
|
803
|
+
speech and reports `pie-tool-init-fallback`.
|
|
862
804
|
|
|
863
805
|
For a server that authenticates by cookie on another origin, set `credentials:
|
|
864
|
-
"include"`
|
|
806
|
+
"include"` on the `textToSpeech` provider config, beside
|
|
865
807
|
`includeAuthOnAssetFetch`. The cookie reaches speech-mark and audio fetches only
|
|
866
808
|
for origins in `assetOrigins`; unset, every TTS fetch keeps the browser default.
|
|
867
809
|
|
|
@@ -896,37 +838,44 @@ Recommended host boundary:
|
|
|
896
838
|
upstream.
|
|
897
839
|
- Browser never receives shared secret, API key, or signing material.
|
|
898
840
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
configures
|
|
841
|
+
The section demos' `/api/tts/sc` route, backed by the SC adapter
|
|
842
|
+
(`@pie-players/tts-server-sc`), is the worked example of custom transport. TTS
|
|
843
|
+
stays on browser speech unless the host configures a server backend.
|
|
902
844
|
|
|
903
|
-
## Test Attempt Session
|
|
845
|
+
## Test Attempt Session
|
|
904
846
|
|
|
905
|
-
The toolkit exposes a canonical `TestAttemptSession` runtime
|
|
847
|
+
The toolkit exposes a canonical `TestAttemptSession` runtime. The host maps its
|
|
848
|
+
backend's attempt payload into it and back out.
|
|
906
849
|
|
|
907
850
|
```typescript
|
|
908
851
|
import {
|
|
909
|
-
|
|
852
|
+
createNewTestAttemptSession,
|
|
910
853
|
toItemSessionsRecord,
|
|
911
|
-
|
|
854
|
+
upsertItemSessionFromPieSessionChange,
|
|
912
855
|
} from "@pie-players/pie-assessment-toolkit";
|
|
913
856
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
857
|
+
let testAttemptSession = createNewTestAttemptSession({
|
|
858
|
+
testAttemptSessionIdentifier: attempt.id,
|
|
859
|
+
assessmentId: assessment.id,
|
|
860
|
+
seed: attempt.id,
|
|
861
|
+
itemIdentifiers: assessment.itemIdentifiers,
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
// Record a PIE session change against its item
|
|
865
|
+
testAttemptSession = upsertItemSessionFromPieSessionChange(testAttemptSession, {
|
|
866
|
+
itemIdentifier: "q1",
|
|
867
|
+
pieSessionId: change.session.id,
|
|
868
|
+
session: change.session,
|
|
917
869
|
});
|
|
918
870
|
|
|
919
871
|
// Use in section-player handoff (same item session shape as item players expect)
|
|
920
872
|
const itemSessions = toItemSessionsRecord(testAttemptSession);
|
|
921
|
-
|
|
922
|
-
// Host-owned backend persistence payload
|
|
923
|
-
const patch = buildActivitySessionPatchFromTestAttemptSession(testAttemptSession);
|
|
924
873
|
```
|
|
925
874
|
|
|
926
875
|
### Integration Boundary
|
|
927
876
|
|
|
928
877
|
- `@pie-players/pie-section-player` stays backend-agnostic and emits session/state changes.
|
|
929
|
-
- Host applications own backend I/O
|
|
878
|
+
- Host applications own backend I/O.
|
|
930
879
|
- Hosts decide persistence policy (immediate, debounced, checkpoint, submit).
|
|
931
880
|
|
|
932
881
|
### Section session API (controller + persistence)
|
|
@@ -938,38 +887,6 @@ For section-level session flows, the toolkit supports two complementary APIs:
|
|
|
938
887
|
|
|
939
888
|
The persistence strategy works with the same `SectionControllerSessionState` shape exposed by the controller, so hosts can choose bulk restore (`applySession`) and fine-grained updates (`updateItemSession`) without internal runtime coupling.
|
|
940
889
|
|
|
941
|
-
## Implementation Status
|
|
942
|
-
|
|
943
|
-
### ✅ Toolkit Services
|
|
944
|
-
|
|
945
|
-
- **ToolkitCoordinator**: ⭐ NEW - Centralized service orchestration
|
|
946
|
-
- **ElementToolStateStore**: ⭐ NEW - Element-level ephemeral tool state management
|
|
947
|
-
- **ToolRegistry**: ⭐ NEW - Registry-based tool management with QTI 3.0 PNP support
|
|
948
|
-
- **ToolPolicyEngine**: QTI 3.0 Personal Needs Profile and host policy decisions via registry-backed policy sources
|
|
949
|
-
- **ToolCoordinator**: Manages z-index layering and visibility for floating tools
|
|
950
|
-
- **HighlightCoordinator**: Separate highlight layers for TTS (temporary) and annotations (persistent)
|
|
951
|
-
- **TTSService**: Text-to-speech with QTI 3.0 catalog support
|
|
952
|
-
- **AccessibilityCatalogResolver**: QTI 3.0 accessibility catalog management
|
|
953
|
-
- **SSMLExtractor**: Automatic extraction of embedded `<speak>` tags
|
|
954
|
-
- **ThemeProvider**: Consistent accessibility theming
|
|
955
|
-
|
|
956
|
-
### ✅ QTI 3.0 Standard Access Features
|
|
957
|
-
|
|
958
|
-
- **95+ Standardized Features**: Complete QTI 3.0 / IMS AfA 3.0 accessibility features
|
|
959
|
-
- **8 Feature Categories**: Visual, auditory, motor, cognitive, reading, navigation, linguistic, assessment
|
|
960
|
-
- **Example Configurations**: Illustrative PNP profile examples (low vision, dyslexia, ADHD, etc.)
|
|
961
|
-
- **Tool Mappings**: Every packaged tool registration maps to standard QTI 3.0 features
|
|
962
|
-
|
|
963
|
-
### ✅ Section Player Integration
|
|
964
|
-
|
|
965
|
-
The toolkit integrates seamlessly with the **PIE Section Player**:
|
|
966
|
-
|
|
967
|
-
- **Primary Interface**: Section player is the main integration point
|
|
968
|
-
- **Default Coordinator**: Creates ToolkitCoordinator automatically if not provided
|
|
969
|
-
- **Automatic SSML Extraction**: Extracts embedded `<speak>` tags from passages and items
|
|
970
|
-
- **Catalog Lifecycle**: Manages item-level catalogs automatically
|
|
971
|
-
- **Service Coordination**: All toolkit services work together automatically
|
|
972
|
-
|
|
973
890
|
## ToolkitCoordinator API
|
|
974
891
|
|
|
975
892
|
### Configuration
|
|
@@ -987,12 +904,18 @@ export interface ToolkitCoordinatorConfig {
|
|
|
987
904
|
item?: string[];
|
|
988
905
|
passage?: string[];
|
|
989
906
|
};
|
|
907
|
+
pnpEnforcement?: 'on' | 'off'; // omitted: auto-detected from the assessment
|
|
990
908
|
providers?: {
|
|
909
|
+
// Abridged; TextToSpeechToolProviderConfig carries every option.
|
|
991
910
|
textToSpeech?: {
|
|
992
911
|
enabled?: boolean;
|
|
993
|
-
backend?: 'browser' | '
|
|
912
|
+
backend?: 'browser' | 'server';
|
|
913
|
+
serverProvider?: 'polly' | 'google' | 'custom';
|
|
914
|
+
apiEndpoint?: string;
|
|
994
915
|
defaultVoice?: string;
|
|
995
916
|
rate?: number;
|
|
917
|
+
speedOptions?: TTSSpeedOption[];
|
|
918
|
+
layoutMode?: 'reserved-row' | 'expanding-row' | 'floating-overlay' | 'left-aligned';
|
|
996
919
|
};
|
|
997
920
|
calculator?: {
|
|
998
921
|
enabled?: boolean;
|
|
@@ -1008,68 +931,108 @@ export interface ToolkitCoordinatorConfig {
|
|
|
1008
931
|
};
|
|
1009
932
|
};
|
|
1010
933
|
};
|
|
934
|
+
toolConfigStrictness?: 'off' | 'warn' | 'error'; // default 'error'
|
|
1011
935
|
toolRegistry?: ToolRegistry | null;
|
|
936
|
+
toolContextResolvers?: ToolContextResolverMap;
|
|
1012
937
|
accessibility?: {
|
|
1013
938
|
catalogs?: any[];
|
|
1014
939
|
language?: string;
|
|
1015
940
|
};
|
|
941
|
+
hooks?: ToolkitCoordinatorHooks;
|
|
942
|
+
lazyInit?: boolean; // default false
|
|
943
|
+
eagerInit?: boolean; // default !lazyInit
|
|
1016
944
|
}
|
|
1017
945
|
```
|
|
1018
946
|
|
|
947
|
+
A `ToolkitCoordinator` registers tool providers only from its `toolRegistry`.
|
|
948
|
+
Built without one, it adopts the registry of the toolkit it is bound to, such as
|
|
949
|
+
the section player's, which then validates its config and registers its
|
|
950
|
+
providers. A registry passed at construction is never replaced. Bound to a
|
|
951
|
+
toolkit that has none, it registers no providers, skips tool-id and placement
|
|
952
|
+
validation, and warns once (`tools.registryUnavailable`).
|
|
953
|
+
|
|
1019
954
|
### Methods
|
|
1020
955
|
|
|
1021
956
|
```typescript
|
|
1022
|
-
// Get all services as a bundle
|
|
1023
|
-
const services = coordinator.getServiceBundle();
|
|
1024
|
-
// Returns: { ttsService, toolCoordinator, highlightCoordinator, elementToolStateStore, catalogResolver }
|
|
1025
|
-
|
|
1026
957
|
// Tool configuration
|
|
1027
|
-
coordinator.
|
|
1028
|
-
coordinator.
|
|
1029
|
-
coordinator.
|
|
1030
|
-
|
|
1031
|
-
//
|
|
1032
|
-
|
|
958
|
+
coordinator.getToolConfig('textToSpeech');
|
|
959
|
+
coordinator.updateToolConfig('textToSpeech', { rate: 1.5 }); // replaces the tool's provider, which starts again on next use
|
|
960
|
+
coordinator.updateToolsPlacement({ item: ['calculator', 'textToSpeech'] });
|
|
961
|
+
|
|
962
|
+
// Tool policy
|
|
963
|
+
coordinator.decideToolPolicy({ level: 'item', scope: { level: 'item', scopeId: 'item-42' } });
|
|
964
|
+
coordinator.decideFeaturePolicy('transcript', { level: 'item', scopeId: 'item-42' });
|
|
965
|
+
const unregisterItem = coordinator.registerItemSettings('item-42', itemSettings);
|
|
966
|
+
const offPolicy = coordinator.onPolicyChange(listener);
|
|
967
|
+
const offDiagnostics = coordinator.onPolicyDiagnostic(listener);
|
|
968
|
+
coordinator.updateAssessment(assessment); // new profile or settings; null unbinds
|
|
969
|
+
coordinator.setPnpEnforcement('on'); // 'on' | 'off'; null returns to auto-detection
|
|
970
|
+
coordinator.getPolicyInputs(); // read-only inputs behind current decisions
|
|
971
|
+
const unregisterSource = coordinator.registerPolicySource(source);
|
|
972
|
+
|
|
973
|
+
// Tool requests: ask the toolbar hosting a tool to open it
|
|
974
|
+
const unregisterTarget = coordinator.registerToolRequestTarget(target);
|
|
975
|
+
coordinator.canRequestTool('calculator'); // false for a tool the deployment lacks
|
|
976
|
+
coordinator.requestTool({ toolId: 'calculator' }); // whether a toolbar claimed it; throws on an unknown id
|
|
977
|
+
const offTargets = coordinator.onToolRequestTargetsChange(listener);
|
|
978
|
+
|
|
979
|
+
// Providers and events
|
|
980
|
+
const provider = await coordinator.ensureProviderReady('textToSpeech');
|
|
981
|
+
const offErrors = coordinator.subscribeFrameworkErrors(listener);
|
|
982
|
+
const offCatalogs = coordinator.onCatalogsChange(listener);
|
|
983
|
+
|
|
984
|
+
// Readiness and teardown
|
|
985
|
+
await coordinator.waitUntilReady();
|
|
986
|
+
coordinator.isReady();
|
|
987
|
+
coordinator.getToolRegistry();
|
|
988
|
+
await coordinator.dispose(); // final teardown by the owner that constructed the coordinator
|
|
1033
989
|
```
|
|
1034
990
|
|
|
991
|
+
Each `register*`, `on*` and `subscribe*` call returns a function that undoes it.
|
|
992
|
+
The section-controller methods are under
|
|
993
|
+
[Controller Event Subscriptions](#controller-event-subscriptions-helper-first).
|
|
994
|
+
|
|
1035
995
|
### Direct Service Access
|
|
1036
996
|
|
|
1037
997
|
All services are public properties for direct access:
|
|
1038
998
|
|
|
1039
999
|
```typescript
|
|
1040
1000
|
coordinator.ttsService // TTSService instance
|
|
1041
|
-
coordinator.toolCoordinator //
|
|
1001
|
+
coordinator.toolCoordinator // ToolCoordinatorApi, owned by the coordinator
|
|
1042
1002
|
coordinator.highlightCoordinator // HighlightCoordinator instance
|
|
1043
1003
|
coordinator.elementToolStateStore // ElementToolStateStore instance
|
|
1044
1004
|
coordinator.catalogResolver // AccessibilityCatalogResolver instance
|
|
1005
|
+
coordinator.toolProviderRegistry // ToolProviderRegistry, the providers registered from toolRegistry
|
|
1045
1006
|
```
|
|
1046
1007
|
|
|
1047
1008
|
## ElementToolStateStore API
|
|
1048
1009
|
|
|
1049
|
-
The `ElementToolStateStore`
|
|
1010
|
+
The `ElementToolStateStore` holds tool state per PIE element under globally unique composite keys. It is kept apart from PIE session data and leaves the browser only through the coordinator's `saveToolState` hook ([Persistence Integration](#persistence-integration)).
|
|
1050
1011
|
|
|
1051
1012
|
### Key Concepts
|
|
1052
1013
|
|
|
1053
|
-
- **Global Element ID**: Composite key format: `${assessmentId}:${sectionId}:${itemId}:${elementId}`
|
|
1054
|
-
- **Element-Level Granularity**: State tracked per PIE element
|
|
1055
|
-
- **
|
|
1014
|
+
- **Global Element ID**: Composite key format: `${assessmentId}:${sectionId}:${attemptId}:${itemId}:${elementId}`, with `attemptId` `""` when the host names no attempt
|
|
1015
|
+
- **Element-Level Granularity**: State tracked per PIE element; a tool keyed by item or section leaves `elementId`, and for a section `itemId`, as `""`
|
|
1016
|
+
- **Per-Attempt State**: two attempts at one section never share a key
|
|
1017
|
+
- **Separate from session data**: tool state never enters the PIE session and is never scored
|
|
1056
1018
|
- **Cross-Section Persistence**: State persists when navigating between sections
|
|
1057
1019
|
|
|
1058
1020
|
### ID Utilities
|
|
1059
1021
|
|
|
1060
1022
|
```typescript
|
|
1061
1023
|
// Generate global element ID
|
|
1062
|
-
const globalElementId = store.getGlobalElementId(
|
|
1063
|
-
'demo-assessment',
|
|
1064
|
-
'section-1',
|
|
1065
|
-
'
|
|
1066
|
-
'
|
|
1067
|
-
|
|
1068
|
-
|
|
1024
|
+
const globalElementId = store.getGlobalElementId({
|
|
1025
|
+
assessmentId: 'demo-assessment',
|
|
1026
|
+
sectionId: 'section-1',
|
|
1027
|
+
attemptId: 'attempt-1',
|
|
1028
|
+
itemId: 'question-1',
|
|
1029
|
+
elementId: 'mc1'
|
|
1030
|
+
});
|
|
1031
|
+
// Returns: "demo-assessment:section-1:attempt-1:question-1:mc1"
|
|
1069
1032
|
|
|
1070
1033
|
// Parse global element ID
|
|
1071
1034
|
const components = store.parseGlobalElementId(globalElementId);
|
|
1072
|
-
// Returns: { assessmentId, sectionId, itemId, elementId }
|
|
1035
|
+
// Returns: { assessmentId, sectionId, attemptId, itemId, elementId }
|
|
1073
1036
|
```
|
|
1074
1037
|
|
|
1075
1038
|
### CRUD Operations
|
|
@@ -1099,7 +1062,7 @@ store.clearElement(globalElementId);
|
|
|
1099
1062
|
// Clear state for a specific tool across all elements
|
|
1100
1063
|
store.clearTool('answerEliminator');
|
|
1101
1064
|
|
|
1102
|
-
// Clear all elements in a specific section
|
|
1065
|
+
// Clear all elements in a specific section, across its attempts
|
|
1103
1066
|
store.clearSection('demo-assessment', 'section-1');
|
|
1104
1067
|
|
|
1105
1068
|
// Clear all state
|
|
@@ -1108,17 +1071,21 @@ store.clearAll();
|
|
|
1108
1071
|
|
|
1109
1072
|
### Persistence Integration
|
|
1110
1073
|
|
|
1074
|
+
A coordinator's store reports changes to the coordinator, which forwards them
|
|
1075
|
+
to `hooks.saveToolState`; `loadToolState` is read once while the coordinator
|
|
1076
|
+
gets ready. Persist tool state through those hooks. `store.setOnStateChange`
|
|
1077
|
+
holds a single callback, so calling it on a coordinator's store disconnects
|
|
1078
|
+
`saveToolState`.
|
|
1079
|
+
|
|
1111
1080
|
```typescript
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1081
|
+
const coordinator = new ToolkitCoordinator({
|
|
1082
|
+
assessmentId: 'demo-assessment',
|
|
1083
|
+
toolRegistry,
|
|
1084
|
+
hooks: {
|
|
1085
|
+
loadToolState: () => JSON.parse(localStorage.getItem('tool-state') ?? 'null'),
|
|
1086
|
+
saveToolState: (state) => localStorage.setItem('tool-state', JSON.stringify(state)),
|
|
1087
|
+
},
|
|
1115
1088
|
});
|
|
1116
|
-
|
|
1117
|
-
// Load state from persistence
|
|
1118
|
-
const saved = localStorage.getItem('tool-state');
|
|
1119
|
-
if (saved) {
|
|
1120
|
-
store.loadState(JSON.parse(saved));
|
|
1121
|
-
}
|
|
1122
1089
|
```
|
|
1123
1090
|
|
|
1124
1091
|
### Reactivity
|
|
@@ -1138,6 +1105,8 @@ unsubscribe();
|
|
|
1138
1105
|
### TTSService
|
|
1139
1106
|
|
|
1140
1107
|
```typescript
|
|
1108
|
+
import { BrowserTTSProvider, TTSService } from '@pie-players/pie-assessment-toolkit';
|
|
1109
|
+
|
|
1141
1110
|
const ttsService = new TTSService();
|
|
1142
1111
|
|
|
1143
1112
|
// Initialize with provider
|
|
@@ -1146,8 +1115,8 @@ await ttsService.initialize(new BrowserTTSProvider());
|
|
|
1146
1115
|
// Set catalog resolver for SSML support
|
|
1147
1116
|
ttsService.setCatalogResolver(catalogResolver);
|
|
1148
1117
|
|
|
1149
|
-
// Playback
|
|
1150
|
-
await ttsService.speak(
|
|
1118
|
+
// Playback: reads an element or a range, or the named card in its place
|
|
1119
|
+
await ttsService.speak(promptElement, {
|
|
1151
1120
|
catalogId: 'prompt-001',
|
|
1152
1121
|
language: 'en-US'
|
|
1153
1122
|
});
|
|
@@ -1166,36 +1135,47 @@ await ttsService.updateSettings({
|
|
|
1166
1135
|
|
|
1167
1136
|
### ToolCoordinator
|
|
1168
1137
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
// Register tools
|
|
1173
|
-
toolCoordinator.registerTool('calculator', 'Calculator', element);
|
|
1138
|
+
The toolkit owns the coordinator. A tool element registers with the one in its
|
|
1139
|
+
runtime context and hands it the element once it renders:
|
|
1174
1140
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1141
|
+
```typescript
|
|
1142
|
+
import {
|
|
1143
|
+
createToolCoordinatorRegistration,
|
|
1144
|
+
ZIndexLayer,
|
|
1145
|
+
} from '@pie-players/pie-assessment-toolkit/tools/registration';
|
|
1179
1146
|
|
|
1180
|
-
|
|
1181
|
-
toolCoordinator.bringToFront(element);
|
|
1147
|
+
const registration = createToolCoordinatorRegistration('Calculator', ZIndexLayer.TOOL);
|
|
1182
1148
|
|
|
1183
|
-
//
|
|
1184
|
-
|
|
1149
|
+
// Re-registers when a republished context brings a new coordinator.
|
|
1150
|
+
$effect(() => registration.sync(coordinator, toolId));
|
|
1151
|
+
$effect(() => {
|
|
1152
|
+
if (coordinator && containerEl && toolId) {
|
|
1153
|
+
coordinator.updateToolElement(toolId, containerEl);
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
onDestroy(() => registration.release());
|
|
1185
1157
|
```
|
|
1186
1158
|
|
|
1159
|
+
Visibility and stacking then go through the coordinator by scoped tool id:
|
|
1160
|
+
`showTool`, `hideTool`, `toggleTool`, `isToolVisible`, `bringToFront(element)`,
|
|
1161
|
+
and `getVisibleTools({ baseId })` / `hideAllTools({ baseId })` for one tool
|
|
1162
|
+
across scopes. Disposing the toolkit coordinator releases every entry. Its
|
|
1163
|
+
debug lines print only under `window.PIE_DEBUG = true`.
|
|
1164
|
+
|
|
1187
1165
|
### HighlightCoordinator
|
|
1188
1166
|
|
|
1189
1167
|
```typescript
|
|
1168
|
+
import { HighlightColor, HighlightCoordinator } from '@pie-players/pie-assessment-toolkit';
|
|
1169
|
+
|
|
1190
1170
|
const highlightCoordinator = new HighlightCoordinator();
|
|
1191
1171
|
|
|
1192
1172
|
// TTS highlights (temporary)
|
|
1193
|
-
highlightCoordinator.highlightTTSWord(
|
|
1173
|
+
highlightCoordinator.highlightTTSWord([wordRange]); // one range per tree the word spans
|
|
1194
1174
|
highlightCoordinator.highlightTTSSentence([range1, range2]);
|
|
1195
1175
|
highlightCoordinator.clearTTS();
|
|
1196
1176
|
|
|
1197
1177
|
// Annotation highlights (persistent)
|
|
1198
|
-
const id = highlightCoordinator.addAnnotation(range,
|
|
1178
|
+
const id = highlightCoordinator.addAnnotation(range, HighlightColor.YELLOW);
|
|
1199
1179
|
highlightCoordinator.removeAnnotation(id);
|
|
1200
1180
|
```
|
|
1201
1181
|
|
|
@@ -1210,16 +1190,20 @@ Reading highlights resolve through five `component-public` tokens registered in
|
|
|
1210
1190
|
| `--pie-tts-word-underline` | `--pie-text` at 70% | Underline marking the current word |
|
|
1211
1191
|
| `--pie-tts-word-shadow` | `--pie-text` at 35% | Shadow carrying the word over its fill |
|
|
1212
1192
|
|
|
1213
|
-
The coordinator derives all five from the active theme's
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1193
|
+
The coordinator derives all five from the active theme's `--pie-missing`,
|
|
1194
|
+
`--pie-text` and `--pie-background`. `--pie-missing` is the theme's
|
|
1195
|
+
missing-response feedback color, which reading highlights reuse as their accent.
|
|
1196
|
+
Opacities are clamped to a legible band, and the underline color is picked by
|
|
1197
|
+
background luminance so the cue survives a dark scheme. The coordinator writes
|
|
1198
|
+
the five inline on the document element. Inline styles outrank any author
|
|
1199
|
+
selector, so a host override takes `!important` and owns the contrast the
|
|
1200
|
+
derivation was maintaining, across every scheme it ships.
|
|
1219
1201
|
|
|
1220
1202
|
### AccessibilityCatalogResolver
|
|
1221
1203
|
|
|
1222
1204
|
```typescript
|
|
1205
|
+
import { AccessibilityCatalogResolver } from '@pie-players/pie-assessment-toolkit';
|
|
1206
|
+
|
|
1223
1207
|
const resolver = new AccessibilityCatalogResolver(
|
|
1224
1208
|
assessment.accessibilityCatalogs,
|
|
1225
1209
|
'en-US'
|
|
@@ -1283,6 +1267,8 @@ such as TTS may still call `getAlternative(...)` with a context built by
|
|
|
1283
1267
|
### SSMLExtractor
|
|
1284
1268
|
|
|
1285
1269
|
```typescript
|
|
1270
|
+
import { SSMLExtractor } from '@pie-players/pie-assessment-toolkit';
|
|
1271
|
+
|
|
1286
1272
|
const extractor = new SSMLExtractor();
|
|
1287
1273
|
|
|
1288
1274
|
// Extract from item config
|
|
@@ -1298,11 +1284,8 @@ catalogResolver.addItemCatalogs(result.catalogs);
|
|
|
1298
1284
|
|
|
1299
1285
|
### Cards without an extractor
|
|
1300
1286
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
existed and was removed, because nothing produced the inline form and a runtime
|
|
1304
|
-
that could not parse the markup left the video in the visible content, showing the
|
|
1305
|
-
accommodation to every learner regardless of eligibility.
|
|
1287
|
+
A signed alternate is authored or written by an importer; no extractor lifts it
|
|
1288
|
+
from markup.
|
|
1306
1289
|
|
|
1307
1290
|
This package resolves and registers those cards through
|
|
1308
1291
|
`AccessibilityCatalogResolver` and the generic media helpers it re-exports
|
|
@@ -1313,25 +1296,20 @@ needs them — signing's card validators and its resolution rules live in
|
|
|
1313
1296
|
|
|
1314
1297
|
### Feature policy without a placement
|
|
1315
1298
|
|
|
1316
|
-
`ToolPolicyEngine.decideFeature(featureId)` (and
|
|
1317
|
-
`ToolkitCoordinator.decideFeaturePolicy(featureId)`) resolve one feature id
|
|
1318
|
-
through `PnpPolicySource`'s
|
|
1319
|
-
placement. Use it for capabilities that are not toolbar surfaces
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
Nothing derives a profile from the registry any more. Doing so read registry
|
|
1332
|
-
membership as eligibility tier — registration means "policy-addressable", not
|
|
1333
|
-
"universal, on by default" — and had to be corrected with a compile-time list of
|
|
1334
|
-
ids to exclude that a host could not extend for its own accommodation.
|
|
1299
|
+
`ToolPolicyEngine.decideFeature(featureId, scope?)` (and
|
|
1300
|
+
`ToolkitCoordinator.decideFeaturePolicy(featureId, scope?)`) resolve one feature id
|
|
1301
|
+
through `PnpPolicySource`'s PNP precedence, independent of any toolbar
|
|
1302
|
+
placement. Use it for capabilities that are not toolbar surfaces, such as
|
|
1303
|
+
signing: an item-level `decideToolPolicy(...)` also reports a tool absent when it
|
|
1304
|
+
was never placed, so it cannot tell a denial from a missing placement. An item
|
|
1305
|
+
scope applies that item's registered settings, the item restriction and
|
|
1306
|
+
requirement rungs; a decision without one applies no item's.
|
|
1307
|
+
|
|
1308
|
+
This package ships no profile.
|
|
1309
|
+
[Universal and empty profiles](../default-tool-loaders/README.md#universal-and-empty-profiles)
|
|
1310
|
+
covers the profiles `@pie-players/pie-default-tool-loaders` builds.
|
|
1311
|
+
|
|
1312
|
+
Registry membership makes a tool policy-addressable; it grants nothing.
|
|
1335
1313
|
|
|
1336
1314
|
### Live registry changes
|
|
1337
1315
|
|
|
@@ -1378,21 +1356,27 @@ The section player provides automatic ToolkitCoordinator integration:
|
|
|
1378
1356
|
// - Extracts services from coordinator
|
|
1379
1357
|
// - Scopes its runtime engine to `section-id`
|
|
1380
1358
|
// - Provides runtime context to child components
|
|
1381
|
-
// -
|
|
1359
|
+
// - Registers item catalogs, including SSML catalogs a host extracted
|
|
1360
|
+
// into `config.extractedCatalogs` with `SSMLExtractor`
|
|
1382
1361
|
// - Handles catalog lifecycle
|
|
1383
1362
|
</script>
|
|
1384
1363
|
```
|
|
1385
1364
|
|
|
1386
1365
|
### Runtime Context Contract
|
|
1387
1366
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1367
|
+
Section-player and toolkit components share one runtime context. An element
|
|
1368
|
+
inside the toolkit's tree connects to it:
|
|
1390
1369
|
|
|
1391
1370
|
```typescript
|
|
1392
1371
|
import {
|
|
1393
|
-
|
|
1372
|
+
connectToolRuntimeContext,
|
|
1394
1373
|
type AssessmentToolkitRuntimeContext
|
|
1395
1374
|
} from "@pie-players/pie-assessment-toolkit";
|
|
1375
|
+
|
|
1376
|
+
let runtime: AssessmentToolkitRuntimeContext | undefined;
|
|
1377
|
+
const disconnect = connectToolRuntimeContext(hostElement, (value) => {
|
|
1378
|
+
runtime = value;
|
|
1379
|
+
});
|
|
1396
1380
|
```
|
|
1397
1381
|
|
|
1398
1382
|
`AssessmentToolkitRuntimeContext` carries ambient orchestration dependencies
|
|
@@ -1422,7 +1406,7 @@ player.section = mySection;
|
|
|
1422
1406
|
|
|
1423
1407
|
// Internally creates:
|
|
1424
1408
|
// new ToolkitCoordinator({
|
|
1425
|
-
// assessmentId:
|
|
1409
|
+
// assessmentId: runtime.assessmentId ?? section.identifier ?? 'assessment-<random>',
|
|
1426
1410
|
// toolRegistry, // the player's toolRegistry, else the packaged registry
|
|
1427
1411
|
// tools: player.runtime?.tools // no tools are placed when this is unset
|
|
1428
1412
|
// })
|
|
@@ -1430,12 +1414,7 @@ player.section = mySection;
|
|
|
1430
1414
|
|
|
1431
1415
|
### Safe Custom Tool Configuration
|
|
1432
1416
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
- Framework logs a deterministic console error prefix: `[pie-framework:<kind>:<source>]`
|
|
1436
|
-
- Framework emits a canonical `framework-error` event
|
|
1437
|
-
- Framework renders a fallback error panel instead of a blank player
|
|
1438
|
-
- Startup tool-config validation can surface as `kind: "coordinator-init"` when the owned coordinator construction path throws.
|
|
1417
|
+
Invalid tool or runtime initialization is handled inside `pie-assessment-toolkit`; the host needs no try/catch. The error model, its events and hooks, and the fallback panel are in [Framework-owned error handling](../../docs/tools-and-accomodations/framework-owned-error-handling.md); host-side config patterns are in [Safe custom tool configuration](../../docs/tools-and-accomodations/safe-custom-tool-config.md).
|
|
1439
1418
|
|
|
1440
1419
|
Use `createToolsConfig()` when you want to pre-validate and inspect diagnostics before mounting:
|
|
1441
1420
|
|
|
@@ -1474,147 +1453,106 @@ const coordinator = new ToolkitCoordinator({
|
|
|
1474
1453
|
Notes:
|
|
1475
1454
|
- `providers.textToSpeech` is the canonical TTS provider key.
|
|
1476
1455
|
- `providers.tts` is rejected by the validation contract.
|
|
1477
|
-
-
|
|
1478
|
-
- Hosts can react to framework errors via the `framework-error` DOM event,
|
|
1479
|
-
the `onFrameworkError(model)` callback prop, or by subscribing directly
|
|
1480
|
-
to the package-internal bus via
|
|
1481
|
-
`ToolkitCoordinator.subscribeFrameworkErrors(listener)`. The callback
|
|
1482
|
-
prop fires exactly once per error, regardless of wrapper depth. Filter
|
|
1483
|
-
by `model.kind` (e.g. `"tts-init"`, `"provider-init"`,
|
|
1484
|
-
`"provider-register"`, `"tool-surface"`) for tool- or provider-specific
|
|
1485
|
-
handling. Recoverable warnings remain observable but do not move section
|
|
1486
|
-
readiness to `error`.
|
|
1487
|
-
- See `docs/tools-and-accomodations/framework-owned-error-handling.md` for event payload and error-kind mapping details.
|
|
1456
|
+
- A tool registration can declare `sanitizeConfig` and `validateConfig` hooks for its `providers.<toolId>` entry.
|
|
1488
1457
|
|
|
1489
|
-
##
|
|
1458
|
+
## Instrumentation and Observability
|
|
1490
1459
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
DOM-event fan-out, and instrumentation into a single object hosts can mount
|
|
1494
|
-
and dispose. The engine is what `<pie-section-player-…>` and
|
|
1495
|
-
`<pie-assessment-toolkit>` use internally, and it is also the surface
|
|
1496
|
-
custom hosts (or alternate layout shells) consume directly.
|
|
1497
|
-
|
|
1498
|
-
### Two import paths
|
|
1499
|
-
|
|
1500
|
-
The engine ships with two deliberately separate entry points so consumers
|
|
1501
|
-
pick the stability surface that matches their use case:
|
|
1502
|
-
|
|
1503
|
-
- **Stable facade — `@pie-players/pie-assessment-toolkit/runtime/engine`.**
|
|
1504
|
-
Narrow, semver-stable surface for hosts that want to mount, drive, and
|
|
1505
|
-
dispose a section runtime. Re-exports `SectionRuntimeEngine`,
|
|
1506
|
-
`SECTION_RUNTIME_ENGINE_KEY` (Svelte context), the cross-CE host
|
|
1507
|
-
context (`sectionRuntimeEngineHostContext`), and the consumer-side
|
|
1508
|
-
helper for that bridge (`connectSectionRuntimeEngineHostContext`).
|
|
1509
|
-
The cross-CE host context exposes only a lifecycle handle; controller
|
|
1510
|
-
methods stay on `SectionRuntimeEngine`.
|
|
1511
|
-
- **Internal surface — `@pie-players/pie-assessment-toolkit/runtime/internal`.**
|
|
1512
|
-
Wider, evolving surface for advanced hosts that need to construct an
|
|
1513
|
-
engine manually, inspect FSM state, or build alternate fan-out paths.
|
|
1514
|
-
Exposes `SectionEngineCore`, the four adapter bridges
|
|
1515
|
-
(`createDomEventBridge`, `createFrameworkErrorBridge`,
|
|
1516
|
-
`createCoordinatorBridge`, `createInstrumentationBridge`),
|
|
1517
|
-
`FrameworkErrorBus`, cohort helpers,
|
|
1518
|
-
and the `resolveRuntime` / `resolveToolsConfig` /
|
|
1519
|
-
`resolveSectionEngineRuntimeState` helpers. Symbols here may change
|
|
1520
|
-
between minor versions with a changeset note.
|
|
1460
|
+
Toolkit instrumentation is provider-agnostic. It uses the shared
|
|
1461
|
+
`InstrumentationProvider` contract from `@pie-players/pie-players-shared`.
|
|
1521
1462
|
|
|
1522
|
-
|
|
1463
|
+
### Injection Path
|
|
1523
1464
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
provider descriptor creates, the surface and content dependency types,
|
|
1527
|
-
`resolveToolTag` and the toolbar registration helpers. Same
|
|
1528
|
-
stability contract as the other `*/internal` entry points — symbols may change
|
|
1529
|
-
between minor versions with a changeset note.
|
|
1465
|
+
When the toolkit is hosted by a section or assessment player, the provider
|
|
1466
|
+
comes from the item-player loader config:
|
|
1530
1467
|
|
|
1531
|
-
|
|
1532
|
-
`TTSService` and the components, none of which a registration needs, and a
|
|
1533
|
-
capability bundle that inlines them ends up with a second `ToolRegistry` class
|
|
1534
|
-
that fails every `instanceof` across the host boundary. Mark the toolkit external
|
|
1535
|
-
in the package's build with a pattern that covers subpaths, not a bare specifier.
|
|
1468
|
+
- `runtime.player.loaderConfig.instrumentationProvider`
|
|
1536
1469
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1470
|
+
### Semantics
|
|
1471
|
+
|
|
1472
|
+
- How an unset, `null` or invalid provider resolves is set out in [Instrumentation providers](../../docs/architecture/instrumentation-providers.md#provider-resolution).
|
|
1473
|
+
- Debug overlays can consume the same stream by composing providers with
|
|
1474
|
+
`CompositeInstrumentationProvider` (for example New Relic + debug panel).
|
|
1475
|
+
- Toolkit telemetry forwarding uses the same provider path, so tool/backend
|
|
1476
|
+
instrumentation is sent to production providers and is visible in debug panel
|
|
1477
|
+
overlays.
|
|
1542
1478
|
|
|
1543
|
-
###
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
`
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1479
|
+
### Toolkit-Owned Canonical Event Stream
|
|
1480
|
+
|
|
1481
|
+
- `pie-toolkit-runtime-owned`
|
|
1482
|
+
- `pie-toolkit-runtime-inherited`
|
|
1483
|
+
- `pie-toolkit-runtime-ready`
|
|
1484
|
+
- `pie-toolkit-ready`
|
|
1485
|
+
- `pie-toolkit-section-ready`
|
|
1486
|
+
- `pie-toolkit-framework-error`
|
|
1487
|
+
|
|
1488
|
+
Toolkit operational events are listed in [Instrumentation providers](../../docs/architecture/instrumentation-providers.md#operational-events).
|
|
1489
|
+
|
|
1490
|
+
The toolkit emits toolkit lifecycle events only; section and assessment events
|
|
1491
|
+
stay in their own layers, so no event has two emitters. The event bridge's
|
|
1492
|
+
optional `dedupeWindowMs` drops repeats within a window and is off by default.
|
|
1493
|
+
|
|
1494
|
+
## Section Runtime Engine (advanced)
|
|
1495
|
+
|
|
1496
|
+
The section runtime engine is the section player's stage chain: a pure FSM that
|
|
1497
|
+
turns the cohort a layout shows (its `(sectionId, attemptId)` pair), the
|
|
1498
|
+
controller resolving and the readiness signals the layout derives into
|
|
1499
|
+
`pie-stage-change` and `pie-loading-complete`.
|
|
1500
|
+
The section player's layout kernel owns one per layout element, and it is the
|
|
1501
|
+
only stage emitter. `<pie-assessment-toolkit>` emits no stages: its
|
|
1502
|
+
`SectionControllerBinding` resolves the section's controller, which the toolkit
|
|
1503
|
+
announces with `toolkit-ready`, and the toolkit publishes framework errors.
|
|
1504
|
+
|
|
1505
|
+
### Entry point
|
|
1506
|
+
|
|
1507
|
+
`@pie-players/pie-assessment-toolkit/runtime/engine` is the stable entry for a
|
|
1508
|
+
host that drives a section's stage chain. It carries `SectionRuntimeEngine` and
|
|
1509
|
+
the vocabulary of its surface: the `SectionEngineInput`s `dispatchInput` takes,
|
|
1510
|
+
the `SectionEngineOutput`s `subscribe` delivers, the `SectionEngineState` and
|
|
1511
|
+
`SectionEnginePhase` `getState` returns, the cohort helpers (`makeCohort`,
|
|
1512
|
+
`cohortsEqual`), the runtime config types with
|
|
1513
|
+
`resolveSectionEngineRuntimeState`, and the readiness signals. The engine core, its adapter and its DOM bridge have no
|
|
1514
|
+
entry: a host reaches them through the facade.
|
|
1515
|
+
|
|
1516
|
+
### Stage chain
|
|
1517
|
+
|
|
1518
|
+
A cohort moves through `composed`, `engine-ready` and `interactive`, and ends at
|
|
1519
|
+
`disposed` on a cohort change or unmount. `engine-ready` follows the
|
|
1520
|
+
`section-controller-resolved` input, `interactive` the readiness signals that
|
|
1521
|
+
satisfy the readiness mode. A readiness update that reports `runtimeError`
|
|
1522
|
+
before `interactive` ends the chain: the first stage the cohort did not reach is
|
|
1523
|
+
`failed` and the rest up to `interactive` are `skipped`. The section player sets
|
|
1524
|
+
`runtimeError` from a non-recoverable `framework-error`, so a host waiting on
|
|
1525
|
+
`engine-ready` or `interactive` always hears an answer. `pie-loading-complete`
|
|
1526
|
+
fires once per cohort, on the condition that opens `interactive`: the section
|
|
1527
|
+
controller is ready and the element pre-warm for the current items has
|
|
1528
|
+
resolved. The items load after it; the controller's `section-loading-complete`
|
|
1529
|
+
marks them loaded.
|
|
1565
1530
|
|
|
1566
1531
|
### Common-host wiring example
|
|
1567
1532
|
|
|
1568
|
-
Most hosts never construct the engine
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1533
|
+
Most hosts never construct the engine: the section-player layout elements do,
|
|
1534
|
+
and a host-built section layout takes its engine from
|
|
1535
|
+
`pie-section-player-kernel-host` ([custom section layouts](../../docs/section-player/custom-layouts.md)).
|
|
1536
|
+
Use the facade for a section renderer outside the section player. The shape
|
|
1537
|
+
mirrors the section-player kernel:
|
|
1572
1538
|
|
|
1573
1539
|
```ts
|
|
1574
|
-
import { ContextProvider } from "@pie-players/pie-context";
|
|
1575
1540
|
import {
|
|
1576
1541
|
SectionRuntimeEngine,
|
|
1577
|
-
sectionRuntimeEngineHostContext,
|
|
1578
|
-
} from "@pie-players/pie-assessment-toolkit/runtime/engine";
|
|
1579
|
-
import {
|
|
1580
|
-
FrameworkErrorBus,
|
|
1581
1542
|
makeCohort,
|
|
1582
|
-
} from "@pie-players/pie-assessment-toolkit/runtime/
|
|
1543
|
+
} from "@pie-players/pie-assessment-toolkit/runtime/engine";
|
|
1583
1544
|
|
|
1584
|
-
const bus = new FrameworkErrorBus();
|
|
1585
1545
|
const engine = new SectionRuntimeEngine();
|
|
1586
1546
|
|
|
1587
|
-
// 1. Attach to the layout
|
|
1588
|
-
|
|
1589
|
-
engine.attachHost({
|
|
1590
|
-
host: layoutHostElement,
|
|
1591
|
-
sourceCe: "my-custom-layout",
|
|
1592
|
-
frameworkErrorBus: bus,
|
|
1593
|
-
coordinator: toolkitCoordinator,
|
|
1594
|
-
});
|
|
1595
|
-
|
|
1596
|
-
// 2. Publish a lifecycle handle on the layout CE host so any wrapped
|
|
1597
|
-
// <pie-assessment-toolkit> suppresses duplicate external lifecycle
|
|
1598
|
-
// emits. The toolkit still owns its controller registration/session
|
|
1599
|
-
// plumbing locally.
|
|
1600
|
-
const engineProvider = new ContextProvider(layoutHostElement, {
|
|
1601
|
-
context: sectionRuntimeEngineHostContext,
|
|
1602
|
-
initialValue: {
|
|
1603
|
-
engine: {
|
|
1604
|
-
getRuntimeId: () => engine.getRuntimeId(),
|
|
1605
|
-
},
|
|
1606
|
-
},
|
|
1607
|
-
});
|
|
1608
|
-
engineProvider.connect();
|
|
1547
|
+
// 1. Attach to the layout host. `sourceCe` is stamped onto every event.
|
|
1548
|
+
engine.attachHost({ host: layoutHostElement, sourceCe: "my-custom-layout" });
|
|
1609
1549
|
|
|
1610
|
-
//
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
// tap stage transitions, readiness updates, framework errors,
|
|
1614
|
-
// instrumentation events
|
|
1550
|
+
// 2. (Optional) Subscribe to the output batches the DOM bridge dispatches.
|
|
1551
|
+
engine.subscribe((outputs) => {
|
|
1552
|
+
// `stage-change` and `loading-complete` outputs
|
|
1615
1553
|
});
|
|
1616
1554
|
|
|
1617
|
-
//
|
|
1555
|
+
// 3. Drive the engine with `SectionEngineInput`s.
|
|
1618
1556
|
const cohort = makeCohort({ sectionId, attemptId });
|
|
1619
1557
|
engine.dispatchInput({
|
|
1620
1558
|
kind: "initialize",
|
|
@@ -1624,7 +1562,10 @@ engine.dispatchInput({
|
|
|
1624
1562
|
itemCount,
|
|
1625
1563
|
});
|
|
1626
1564
|
|
|
1627
|
-
//
|
|
1565
|
+
// When the toolkit's `section-ready` delivers the cohort's section controller:
|
|
1566
|
+
engine.dispatchInput({ kind: "section-controller-resolved" });
|
|
1567
|
+
|
|
1568
|
+
// On readiness signal updates:
|
|
1628
1569
|
engine.dispatchInput({
|
|
1629
1570
|
kind: "update-readiness-signals",
|
|
1630
1571
|
signals: {
|
|
@@ -1633,46 +1574,58 @@ engine.dispatchInput({
|
|
|
1633
1574
|
allLoadingComplete,
|
|
1634
1575
|
runtimeError,
|
|
1635
1576
|
},
|
|
1636
|
-
loadedCount,
|
|
1637
1577
|
itemCount,
|
|
1638
1578
|
mode: "progressive",
|
|
1639
1579
|
});
|
|
1640
1580
|
|
|
1641
|
-
// On unmount
|
|
1642
|
-
engineProvider.disconnect();
|
|
1581
|
+
// On unmount; emits `disposed` for the active cohort.
|
|
1643
1582
|
engine.dispose();
|
|
1644
1583
|
```
|
|
1645
1584
|
|
|
1646
|
-
The
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
section-player
|
|
1653
|
-
|
|
1654
|
-
layout host sees only the canonical engine-bridge emit. Direct
|
|
1655
|
-
listeners on `<pie-assessment-toolkit>` itself still see the toolkit's
|
|
1656
|
-
own emit (the toolkit dispatch reaches them before the kernel listener
|
|
1657
|
-
runs). A coordinator the host passes in reports through the same
|
|
1658
|
-
surfaces. `packages/section-player/tests/section-player-event-delivery.spec.ts`
|
|
1659
|
-
pins these counts.
|
|
1660
|
-
The layout host emits one `framework-error` DOM event per framework error.
|
|
1585
|
+
The adapter dispatches `pie-stage-change` and `pie-loading-complete` on `host`,
|
|
1586
|
+
bubbling and composed, as the toolkit dispatches its own events. Framework
|
|
1587
|
+
errors are not an engine output. The toolkit that owns the coordinator publishes
|
|
1588
|
+
each one once: one bubbling, composed `framework-error` event, which reaches the
|
|
1589
|
+
layout host and `document`, and one `onFrameworkError` call. A coordinator the
|
|
1590
|
+
host passes in reports through the same surfaces.
|
|
1591
|
+
`packages/section-player/tests/section-player-event-delivery.spec.ts` pins these
|
|
1592
|
+
counts.
|
|
1661
1593
|
|
|
1662
|
-
|
|
1663
|
-
available via the kernel's `selectReadiness()`) and `pie-loading-complete`.
|
|
1594
|
+
## Writing a capability package
|
|
1664
1595
|
|
|
1665
|
-
|
|
1596
|
+
`@pie-players/pie-assessment-toolkit/tools/registration` is the stable entry a
|
|
1597
|
+
capability package imports: the `ToolRegistration` contract, the
|
|
1598
|
+
`ToolProviderApi` its provider descriptor creates, the surface and content
|
|
1599
|
+
dependency types, the context predicates, `createToolElement`, `resolveToolTag`
|
|
1600
|
+
and the toolbar registration helpers. A renderer that puts registrations on
|
|
1601
|
+
screen uses the same entry for `createToolSurfaceHost` and
|
|
1602
|
+
`resolveContentCapabilities`.
|
|
1666
1603
|
|
|
1667
|
-
|
|
1604
|
+
Import it rather than the package root: the root pulls in `ToolkitCoordinator`,
|
|
1605
|
+
`TTSService` and the components, none of which a registration needs, and a
|
|
1606
|
+
capability bundle that inlines them ends up with a second `ToolRegistry` class
|
|
1607
|
+
that fails every `instanceof` across the host boundary. Mark the toolkit external
|
|
1608
|
+
in the package's build with a pattern that covers subpaths, not a bare specifier.
|
|
1668
1609
|
|
|
1669
|
-
|
|
1610
|
+
`@pie-players/pie-tool-sign-language` is the worked example end to end: a
|
|
1611
|
+
registration, a content resolver, its own custom element, and no edit to any
|
|
1612
|
+
generic package. The [default-tool-loaders README](../default-tool-loaders/README.md)
|
|
1613
|
+
covers how a deployment then composes it in, and the
|
|
1614
|
+
[Tool Registry Reference](docs/TOOL_REGISTRY.md) the registration and
|
|
1615
|
+
host-surface contracts.
|
|
1616
|
+
|
|
1617
|
+
## State separation
|
|
1618
|
+
|
|
1619
|
+
The toolkit keeps tool state and PIE session data in separate stores.
|
|
1670
1620
|
|
|
1671
|
-
|
|
1621
|
+
### Tool state (ElementToolStateStore)
|
|
1622
|
+
|
|
1623
|
+
Never scored; it leaves the browser only through the coordinator's
|
|
1624
|
+
`saveToolState` hook ([Persistence Integration](#persistence-integration)):
|
|
1672
1625
|
|
|
1673
1626
|
```typescript
|
|
1674
1627
|
{
|
|
1675
|
-
"demo-assessment:section-1:question-1:mc1": {
|
|
1628
|
+
"demo-assessment:section-1:attempt-1:question-1:mc1": {
|
|
1676
1629
|
"answerEliminator": {
|
|
1677
1630
|
"eliminatedChoices": ["choice-b", "choice-d"]
|
|
1678
1631
|
},
|
|
@@ -1689,9 +1642,9 @@ The toolkit enforces a clear separation between ephemeral tool state and persist
|
|
|
1689
1642
|
- Tool preferences
|
|
1690
1643
|
- UI state
|
|
1691
1644
|
|
|
1692
|
-
### PIE
|
|
1645
|
+
### PIE session data
|
|
1693
1646
|
|
|
1694
|
-
|
|
1647
|
+
The host persists it, and its backend scores it:
|
|
1695
1648
|
|
|
1696
1649
|
```typescript
|
|
1697
1650
|
{
|
|
@@ -1711,11 +1664,7 @@ The toolkit enforces a clear separation between ephemeral tool state and persist
|
|
|
1711
1664
|
|
|
1712
1665
|
## Examples
|
|
1713
1666
|
|
|
1714
|
-
See
|
|
1715
|
-
|
|
1716
|
-
- **Three Questions Demo**: Element-level answer eliminator with state persistence
|
|
1717
|
-
- **TTS Integration Demo**: Toolkit coordinator with TTS service
|
|
1718
|
-
- **Paired Passages Demo**: Multi-section assessment with cross-section state
|
|
1667
|
+
See [section-demos](../../apps/section-demos/) for complete examples, among them "Demo 3: Three Questions, One Passage", "Demo 4: TTS with SSML Extraction" and "Two Passages with One Question".
|
|
1719
1668
|
|
|
1720
1669
|
## TypeScript Support
|
|
1721
1670
|
|
|
@@ -1725,8 +1674,7 @@ Full TypeScript definitions included:
|
|
|
1725
1674
|
import type {
|
|
1726
1675
|
ToolkitCoordinatorApi,
|
|
1727
1676
|
ElementToolStateStoreApi,
|
|
1728
|
-
ToolkitCoordinatorConfig
|
|
1729
|
-
ToolkitServiceBundle
|
|
1677
|
+
ToolkitCoordinatorConfig
|
|
1730
1678
|
} from '@pie-players/pie-assessment-toolkit';
|
|
1731
1679
|
```
|
|
1732
1680
|
|
|
@@ -1740,31 +1688,27 @@ from tool configuration. Two sanitization layers apply:
|
|
|
1740
1688
|
`pie-item-player`. See
|
|
1741
1689
|
[pie-item-player README](../item-player/README.md#content-trust-boundary)
|
|
1742
1690
|
for the `trust-markup` opt-out and the `sanitizeMarkup` override.
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
overwide images surface a horizontal scrollbar instead of being
|
|
1746
|
-
clipped by the section layout's `overflow-x: hidden` ancestors
|
|
1747
|
-
(PIE-94 / WCAG 1.4.10 Reflow at 400% zoom). The wrapper is
|
|
1748
|
-
keyboard-scrollable (`tabindex="0"`, `role="region"`) and carries
|
|
1749
|
-
the image's `alt` text in its `aria-label`; matching CSS lives in
|
|
1750
|
-
`@pie-players/pie-theme` (`components.css`). Authored `<table>`
|
|
1751
|
-
elements outside a `pie-*` custom element get the same treatment via
|
|
1752
|
-
`<div class="pie-table-scroll">` so wide data grids reflow into a
|
|
1753
|
-
horizontally scrollable region; the wrapper's `aria-label` is derived
|
|
1754
|
-
from the table's `aria-label` / `aria-labelledby` / `<caption>`.
|
|
1691
|
+
The player also wraps overwide images and tables in scrollable regions
|
|
1692
|
+
([reflow wrappers](../item-player/README.md#reflow-wrappers)).
|
|
1755
1693
|
- **Tool icons and SSML** - tool-registered icon markup is parsed and
|
|
1756
1694
|
DOMPurified inside the toolbar at render time; SSML payloads are
|
|
1757
1695
|
restricted to an allow-listed subset of SSML tags/attributes before
|
|
1758
1696
|
being forwarded to TTS providers. Do not ship tools that rely on raw
|
|
1759
1697
|
`<script>` or event-handler attributes in their icon strings.
|
|
1760
1698
|
|
|
1699
|
+
[Security](../../docs/security/readme.md) sets out the full trust boundary and
|
|
1700
|
+
the host's obligations.
|
|
1701
|
+
|
|
1761
1702
|
## Related Documentation
|
|
1762
1703
|
|
|
1763
|
-
-
|
|
1764
|
-
-
|
|
1765
|
-
- [
|
|
1704
|
+
- [Tool Registry Reference](docs/TOOL_REGISTRY.md) - Registry-based tool management and AfA PNP 3.0 profile support
|
|
1705
|
+
- [PNP Configuration Guide](docs/PNP_CONFIGURATION.md) - How to configure student profiles, district policies, and governance rules
|
|
1706
|
+
- [Configuring Tools](../../docs/tools-and-accomodations/tool_provider_system.md) - Placement, policy and provider configuration
|
|
1707
|
+
- [Toolkit Tool Host Contract](../../docs/tools-and-accomodations/tool_host_contract.md) - Runtime guarantees between hosts and toolkit-managed tools
|
|
1708
|
+
- [Tools and Accommodations Architecture](../../docs/tools-and-accomodations/architecture.md) - Tool design, scopes and activation
|
|
1709
|
+
- [Assessment toolkit in the architecture overview](../../docs/architecture/architecture.md#assessment-toolkit) - Where the toolkit sits among the players
|
|
1766
1710
|
- [Section Player README](../section-player/README.md) - Section player integration
|
|
1767
|
-
- [Section Player Architecture](../section-player/ARCHITECTURE.md#layered-runtime-engine
|
|
1711
|
+
- [Section Player Architecture](../section-player/ARCHITECTURE.md#layered-runtime-engine) - Layered runtime engine, kernel/toolkit wiring, lifecycle emit invariant
|
|
1768
1712
|
- [Framework-Owned Error Handling](../../docs/tools-and-accomodations/framework-owned-error-handling.md) - Canonical framework error model/events and fallback behavior
|
|
1769
1713
|
- [Safe Custom Tool Configuration](../../docs/tools-and-accomodations/safe-custom-tool-config.md) - Host-side config patterns and validation guidance
|
|
1770
1714
|
- [Architecture Overview](../../docs/architecture/architecture.md) - Complete system architecture
|