@pie-players/pie-assessment-toolkit 0.3.75 → 1.0.0-beta.1
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 +451 -541
- package/custom-elements.json +1315 -0
- package/dist/attempt/TestSession.d.ts +0 -1
- package/dist/attempt/TestSession.js +10 -5
- package/dist/components/ItemScope.custom-element.js +1 -1
- package/dist/components/ItemToolBar.custom-element.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.js +8 -8
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/{ItemToolBar-7tt7v2xp.js → ItemToolBar-3zrc4p3d.js} +4 -4
- package/dist/components/chunks/{ItemToolBar-jx38w99s.js → ItemToolBar-66hcwmfw.js} +2 -2
- package/dist/components/chunks/ItemToolBar-asenkvx6.js +40 -0
- package/dist/components/chunks/ItemToolBar-hzc5wyjp.js +2 -0
- package/dist/components/chunks/{ItemToolBar-97hx60z4.js → ItemToolBar-kkj69ptr.js} +1 -1
- package/dist/components/chunks/sre-engine-bj997w4b.js +1 -0
- package/dist/context/assessment-toolkit-context.d.ts +6 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +4 -10
- package/dist/policy/core/PolicySource.d.ts +1 -1
- package/dist/policy/core/ToolPolicyEngine.d.ts +46 -7
- package/dist/policy/core/ToolPolicyEngine.js +73 -16
- package/dist/policy/core/compose-decision.d.ts +3 -3
- package/dist/policy/core/compose-decision.js +32 -30
- package/dist/policy/core/decision-types.d.ts +51 -34
- package/dist/policy/core/feature-decision.d.ts +17 -15
- package/dist/policy/core/feature-decision.js +11 -13
- package/dist/policy/core/pnp-policy-inputs.d.ts +7 -3
- package/dist/policy/core/pnp-policy-inputs.js +17 -33
- package/dist/policy/core/provenance.d.ts +2 -2
- package/dist/policy/core/tool-parameters.d.ts +12 -0
- package/dist/policy/core/tool-parameters.js +27 -0
- package/dist/policy/engine.d.ts +1 -1
- package/dist/policy/sources/PnpPolicySource.d.ts +18 -24
- package/dist/policy/sources/PnpPolicySource.js +57 -29
- package/dist/runtime/SectionControllerBinding.d.ts +6 -2
- package/dist/runtime/SectionControllerBinding.js +21 -15
- package/dist/runtime/adapter/subscriber-fanout.d.ts +0 -1
- package/dist/runtime/adapter/subscriber-fanout.js +1 -4
- package/dist/runtime/composition-emit-scheduler.d.ts +3 -3
- package/dist/runtime/composition-emit-scheduler.js +1 -1
- package/dist/runtime/content-region.d.ts +0 -4
- package/dist/runtime/content-region.js +2 -2
- package/dist/runtime/core/SectionEngineCore.d.ts +0 -2
- package/dist/runtime/core/SectionEngineCore.js +0 -4
- package/dist/runtime/core/cohort.d.ts +0 -5
- package/dist/runtime/core/cohort.js +0 -9
- package/dist/runtime/core/engine-output.d.ts +3 -2
- package/dist/runtime/core/engine-transition.js +3 -2
- package/dist/runtime/registration-events.d.ts +11 -14
- package/dist/runtime/runtime-id.d.ts +1 -1
- package/dist/runtime/runtime-id.js +1 -1
- package/dist/runtime/shell-scope.d.ts +4 -4
- package/dist/runtime/shell-scope.js +15 -9
- package/dist/runtime/tool-host-contract.d.ts +0 -4
- package/dist/runtime/tool-host-contract.js +1 -1
- package/dist/services/AccessibilityCatalogResolver.d.ts +21 -31
- package/dist/services/AccessibilityCatalogResolver.js +29 -42
- package/dist/services/ElementToolStateStore.d.ts +32 -61
- package/dist/services/ElementToolStateStore.js +37 -67
- package/dist/services/HighlightCoordinator.d.ts +9 -21
- package/dist/services/HighlightCoordinator.js +52 -80
- package/dist/services/RangeSerializer.d.ts +4 -35
- package/dist/services/RangeSerializer.js +4 -48
- package/dist/services/SSMLExtractor.d.ts +8 -31
- package/dist/services/SSMLExtractor.js +18 -40
- package/dist/services/TTSService.d.ts +51 -57
- package/dist/services/TTSService.js +323 -284
- package/dist/services/ToolCoordinator.d.ts +6 -49
- package/dist/services/ToolCoordinator.js +25 -61
- package/dist/services/ToolRegistry.d.ts +83 -80
- package/dist/services/ToolRegistry.js +91 -67
- package/dist/services/ToolkitCoordinator.d.ts +83 -81
- package/dist/services/ToolkitCoordinator.js +186 -174
- package/dist/services/framework-error.d.ts +30 -7
- package/dist/services/framework-error.js +10 -13
- package/dist/services/interfaces.d.ts +51 -71
- package/dist/services/section-controller-types.d.ts +7 -7
- package/dist/services/section-session-assignment.js +6 -8
- package/dist/services/spoken-audio-cards.d.ts +2 -4
- package/dist/services/spoken-audio-cards.js +2 -4
- package/dist/services/tool-config-validation.d.ts +5 -7
- package/dist/services/tool-config-validation.js +11 -26
- package/dist/services/tool-context.d.ts +39 -24
- package/dist/services/tool-context.js +158 -92
- package/dist/services/tool-failure.d.ts +30 -0
- package/dist/services/tool-failure.js +11 -0
- package/dist/services/tool-icons.d.ts +1 -2
- package/dist/services/tool-icons.js +2 -2
- package/dist/services/tool-instance-id.d.ts +8 -6
- package/dist/services/tool-instance-id.js +12 -18
- package/dist/services/tool-providers/TTSToolProvider.d.ts +6 -43
- package/dist/services/tool-providers/TTSToolProvider.js +7 -63
- package/dist/services/tool-providers/ToolProviderApi.d.ts +0 -7
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +7 -42
- package/dist/services/tool-providers/ToolProviderRegistry.js +21 -63
- package/dist/services/tool-request.d.ts +4 -0
- package/dist/services/tool-request.js +7 -2
- package/dist/services/toolbar-decided-tools.d.ts +2 -0
- package/dist/services/toolbar-decided-tools.js +2 -0
- package/dist/services/toolbar-items.js +2 -5
- package/dist/services/tools-config-normalizer.d.ts +18 -9
- package/dist/services/tools-config-normalizer.js +25 -19
- package/dist/services/tts/browser-provider.d.ts +2 -5
- package/dist/services/tts/browser-provider.js +13 -22
- package/dist/services/tts/catalog-span-alignment.js +14 -7
- package/dist/services/tts/flat-tree.d.ts +0 -7
- package/dist/services/tts/flat-tree.js +2 -2
- 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/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 +10 -12
- package/dist/services/tts/highlight-target-resolver.d.ts +12 -0
- package/dist/services/tts/math-aware-text-processing.js +10 -29
- package/dist/services/tts/math-speech.d.ts +1 -1
- package/dist/services/tts/math-speech.js +3 -0
- 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 +12 -1
- package/dist/services/tts/ssml/spoken-text.js +1 -1
- package/dist/services/tts/start-failure.d.ts +0 -7
- package/dist/services/tts/start-failure.js +1 -1
- package/dist/services/tts/text-segmentation.d.ts +5 -0
- package/dist/services/tts/text-segmentation.js +13 -10
- package/dist/services/tts-runtime-config.d.ts +32 -12
- package/dist/services/tts-runtime-config.js +14 -20
- package/dist/tools/content-capability-resolution.d.ts +2 -2
- package/dist/tools/registration.d.ts +7 -6
- package/dist/tools/registration.js +10 -9
- package/dist/tools/registrations/toolbar-registration-helpers.d.ts +6 -0
- package/dist/tools/registrations/toolbar-registration-helpers.js +6 -0
- package/dist/tools/tool-surface-host.js +1 -8
- package/package.json +8 -6
- package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +0 -2
- package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +0 -40
- package/dist/components/chunks/sre-engine-jd8cc08x.js +0 -1
- package/dist/services/ContextVariableStore.d.ts +0 -113
- package/dist/services/ContextVariableStore.js +0 -206
- package/dist/services/tts-control-events.d.ts +0 -13
- package/dist/services/tts-control-events.js +0 -7
- 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
|
|
|
@@ -21,133 +49,36 @@ Server-backed TTS (`backend: "server"`) loads
|
|
|
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
|
|
52
|
+
## Capabilities
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
46
67
|
|
|
47
|
-
|
|
48
|
-
player.ttsService = ttsService;
|
|
49
|
-
player.toolCoordinator = toolCoordinator;
|
|
50
|
-
// ...
|
|
51
|
-
```
|
|
68
|
+
## Architecture overview
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
import {
|
|
56
|
-
createPackagedToolRegistry,
|
|
57
|
-
DEFAULT_TOOL_MODULE_LOADERS,
|
|
58
|
-
} from '@pie-players/pie-default-tool-loaders';
|
|
59
|
-
|
|
60
|
-
// Create one coordinator with configuration
|
|
61
|
-
const toolRegistry = createPackagedToolRegistry({
|
|
62
|
-
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
63
|
-
});
|
|
64
|
-
const toolkitCoordinator = new ToolkitCoordinator({
|
|
65
|
-
assessmentId: 'my-assessment',
|
|
66
|
-
toolRegistry,
|
|
67
|
-
tools: {
|
|
68
|
-
providers: {
|
|
69
|
-
textToSpeech: { enabled: true, backend: 'browser' },
|
|
70
|
-
// Desmos needs an application key; see "Simple Default" below.
|
|
71
|
-
calculator: { enabled: true }
|
|
72
|
-
},
|
|
73
|
-
placement: {
|
|
74
|
-
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
75
|
-
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
76
|
-
passage: ['textToSpeech']
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Pass single coordinator to section-player through runtime
|
|
82
|
-
player.runtime = { ...(player.runtime ?? {}), coordinator: toolkitCoordinator };
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## What Does It Solve?
|
|
86
|
-
|
|
87
|
-
- **Centralized service management**: One coordinator owns all toolkit services
|
|
88
|
-
- **Tool coordination**: z-index management, visibility state, element-level state
|
|
89
|
-
- **Accommodation support**: IEP/504 tool configuration logic
|
|
90
|
-
- **TTS + annotation coordination**: Prevent conflicts between highlights
|
|
91
|
-
- **Event communication**: Standard contracts between components
|
|
92
|
-
- **Accessibility theming**: Consistent high-contrast, font sizing
|
|
93
|
-
- **State separation**: Ephemeral tool state separate from persistent session data
|
|
94
|
-
|
|
95
|
-
## Instrumentation and Observability
|
|
96
|
-
|
|
97
|
-
Toolkit instrumentation is provider-agnostic and additive. It uses the shared
|
|
98
|
-
`InstrumentationProvider` contract from `@pie-players/pie-players-shared`.
|
|
99
|
-
|
|
100
|
-
### Injection Path
|
|
101
|
-
|
|
102
|
-
When toolkit is hosted by section/assessment player flows, the canonical
|
|
103
|
-
provider path is the item-player loader config:
|
|
104
|
-
|
|
105
|
-
- `runtime.player.loaderConfig.instrumentationProvider`
|
|
106
|
-
|
|
107
|
-
### Semantics
|
|
108
|
-
|
|
109
|
-
- With `trackPageActions: true`, missing/`undefined` providers use the default New Relic provider path.
|
|
110
|
-
- `instrumentationProvider: null` explicitly disables instrumentation.
|
|
111
|
-
- Invalid provider objects are ignored (optional debug warning), also no-op.
|
|
112
|
-
- Existing `item-player` behavior remains the compatibility anchor.
|
|
113
|
-
- Debug overlays can consume the same stream by composing providers with
|
|
114
|
-
`CompositeInstrumentationProvider` (for example New Relic + debug panel).
|
|
115
|
-
- Toolkit telemetry forwarding uses the same provider path, so tool/backend
|
|
116
|
-
instrumentation is sent to production providers and is visible in debug panel
|
|
117
|
-
overlays.
|
|
118
|
-
|
|
119
|
-
### Toolkit-Owned Canonical Event Stream
|
|
120
|
-
|
|
121
|
-
- `pie-toolkit-runtime-owned`
|
|
122
|
-
- `pie-toolkit-runtime-inherited`
|
|
123
|
-
- `pie-toolkit-runtime-ready`
|
|
124
|
-
- `pie-toolkit-ready`
|
|
125
|
-
- `pie-toolkit-section-ready`
|
|
126
|
-
- `pie-toolkit-framework-error`
|
|
127
|
-
|
|
128
|
-
Toolkit tool/backend operational stream:
|
|
129
|
-
|
|
130
|
-
- `pie-tool-init-start|success|error`
|
|
131
|
-
- `pie-tool-backend-call-start|success|error`
|
|
132
|
-
- `pie-tool-library-load-start|success|error`
|
|
133
|
-
|
|
134
|
-
Ownership boundary: toolkit emits toolkit lifecycle semantics only. Section and
|
|
135
|
-
assessment semantic streams stay in their own layers to avoid overlap. Bridge
|
|
136
|
-
dedupe is a safety net, not a substitute for clear ownership.
|
|
137
|
-
|
|
138
|
-
## Architecture Overview
|
|
139
|
-
|
|
140
|
-
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.
|
|
141
72
|
|
|
142
73
|
### Core Principles
|
|
143
74
|
|
|
144
|
-
1. **Centralized Coordination**: ToolkitCoordinator
|
|
75
|
+
1. **Centralized Coordination**: ToolkitCoordinator owns the services, and players reach them through it
|
|
145
76
|
2. **Composable Services**: Import only what you need (or use coordinator for convenience)
|
|
146
77
|
3. **No Framework Lock-in**: Works with any JavaScript framework
|
|
147
|
-
4. **Product Control**: Products control navigation, layout and backend. Session persistence defaults to `localStorage` at the section and assessment layers, stored per attempt id and inactive without one, and a product replaces either through its persistence hook (`createSectionSessionPersistence` on the coordinator, `createAssessmentSessionPersistence` on the assessment player)
|
|
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)
|
|
148
79
|
5. **Standard Contracts**: Well-defined event types for component communication
|
|
149
|
-
6. **Element-Level Granularity**: Tool state tracked per PIE element
|
|
150
|
-
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
|
|
151
82
|
|
|
152
83
|
## Configuration: attributes and a constructed coordinator
|
|
153
84
|
|
|
@@ -170,20 +101,16 @@ than the element inputs builds the coordinator itself.
|
|
|
170
101
|
```
|
|
171
102
|
|
|
172
103
|
- **A constructed `ToolkitCoordinator` (or a
|
|
173
|
-
`runtime` object on
|
|
104
|
+
`runtime` object on the player custom elements).** Use this for advanced cases: composed
|
|
174
105
|
configuration, dynamic overrides, runtime mutation, fields without an
|
|
175
106
|
attribute, or anything that benefits from being a single typed
|
|
176
107
|
object passed by reference. Example:
|
|
177
108
|
|
|
178
109
|
```ts
|
|
179
|
-
import {
|
|
180
|
-
|
|
181
|
-
DEFAULT_TOOL_MODULE_LOADERS,
|
|
182
|
-
} from "@pie-players/pie-default-tool-loaders";
|
|
110
|
+
import { ToolkitCoordinator } from "@pie-players/pie-assessment-toolkit";
|
|
111
|
+
import { createPackagedToolRegistry } from "@pie-players/pie-default-tool-loaders";
|
|
183
112
|
|
|
184
|
-
const toolRegistry = createPackagedToolRegistry(
|
|
185
|
-
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
186
|
-
});
|
|
113
|
+
const toolRegistry = createPackagedToolRegistry();
|
|
187
114
|
const coordinator = new ToolkitCoordinator({
|
|
188
115
|
assessmentId: "my-assessment",
|
|
189
116
|
toolRegistry,
|
|
@@ -211,33 +138,36 @@ callbacks; attributes carry section identity, layout controls, `nds-icons`,
|
|
|
211
138
|
`locale` and `tool-config-strictness`. Unset inputs take their documented
|
|
212
139
|
defaults.
|
|
213
140
|
|
|
214
|
-
`<pie-assessment-toolkit>` takes the same fields as its own properties
|
|
215
|
-
|
|
216
|
-
|
|
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`.
|
|
217
147
|
|
|
218
148
|
### Canonical attribute set
|
|
219
149
|
|
|
220
150
|
- Identity: `assessment-id` on the toolkit, `section-id`, `attempt-id`
|
|
221
|
-
- Runtime config on section-player
|
|
222
|
-
- Toolkit-only object properties: `tools`, `
|
|
151
|
+
- Runtime config on section-player custom elements: `runtime`
|
|
152
|
+
- Toolkit-only object properties: `tools`, `toolRegistry`, `coordinator`,
|
|
223
153
|
`accessibility`
|
|
224
154
|
- Interface: `nds-icons`, `locale`
|
|
225
|
-
- Diagnostics: `tool-config-strictness`, `debug
|
|
155
|
+
- Diagnostics: `tool-config-strictness`, and `debug` on the section-player layouts. Framework-error
|
|
226
156
|
delivery is via the `onFrameworkError` callback (a toolkit property,
|
|
227
157
|
`runtime.onFrameworkError` on a section player) and the
|
|
228
|
-
`framework-error` DOM event dispatched on the layout
|
|
158
|
+
`framework-error` DOM event dispatched on the layout element.
|
|
229
159
|
|
|
230
160
|
Inputs outside the runtime config:
|
|
231
161
|
|
|
232
162
|
- Identity (`section-id`, `attempt-id`, `section`): per-attempt host
|
|
233
163
|
state, not configuration.
|
|
234
|
-
- Layout-only shell knobs on the section-player layout
|
|
164
|
+
- Layout-only shell knobs on the section-player layout elements
|
|
235
165
|
(`show-toolbar`, `toolbar-position`, `narrow-layout-breakpoint`,
|
|
236
|
-
`split-pane-collapse-strategy`):
|
|
166
|
+
`split-pane-collapse-strategy`): rendering concerns of the layout element.
|
|
237
167
|
- Per-region toolbar tool placement: hosts populate
|
|
238
168
|
`tools.placement.item` / `tools.placement.passage` (object form) or
|
|
239
169
|
`runtime.tools.placement.{item,passage}` directly.
|
|
240
|
-
- Runtime-only keys on the section-player layout
|
|
170
|
+
- Runtime-only keys on the section-player layout elements
|
|
241
171
|
(`createSectionController`, `isolation`): accepted only via
|
|
242
172
|
`runtime.<key>`. Section-player layouts forward both to the wrapped
|
|
243
173
|
toolkit as properties. `<pie-assessment-toolkit>` keeps
|
|
@@ -245,42 +175,46 @@ Inputs outside the runtime config:
|
|
|
245
175
|
attribute or a property. A `coordinator` passed to it wins over an
|
|
246
176
|
outer toolkit's.
|
|
247
177
|
|
|
248
|
-
###
|
|
178
|
+
### Adding an attribute (contributors)
|
|
249
179
|
|
|
250
|
-
|
|
180
|
+
A new input becomes an attribute only if all of the following hold:
|
|
251
181
|
|
|
252
182
|
- It is a common case that hosts set without composing a `ToolkitCoordinator`
|
|
253
183
|
/ `runtime` object.
|
|
254
184
|
- Its value is a primitive or small typed object that round-trips through
|
|
255
185
|
HTML attributes (string, boolean-like, number; structured data passes via
|
|
256
186
|
property assignment).
|
|
257
|
-
- It exists on every
|
|
187
|
+
- It exists on every custom element that owns the same knob, or has a
|
|
258
188
|
deliberate documented exclusion.
|
|
259
189
|
|
|
260
190
|
Otherwise expose it through the configuration object only.
|
|
261
191
|
|
|
262
192
|
## Quick Start
|
|
263
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
|
+
|
|
264
203
|
### Option 1: Use ToolkitCoordinator (Recommended)
|
|
265
204
|
|
|
266
205
|
```typescript
|
|
267
206
|
import { ToolkitCoordinator } from '@pie-players/pie-assessment-toolkit';
|
|
268
|
-
import {
|
|
269
|
-
createPackagedToolRegistry,
|
|
270
|
-
DEFAULT_TOOL_MODULE_LOADERS,
|
|
271
|
-
} from '@pie-players/pie-default-tool-loaders';
|
|
207
|
+
import { createPackagedToolRegistry } from '@pie-players/pie-default-tool-loaders';
|
|
272
208
|
|
|
273
209
|
// Create coordinator with configuration
|
|
274
|
-
const toolRegistry = createPackagedToolRegistry(
|
|
275
|
-
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
276
|
-
});
|
|
210
|
+
const toolRegistry = createPackagedToolRegistry();
|
|
277
211
|
const coordinator = new ToolkitCoordinator({
|
|
278
212
|
assessmentId: 'demo-assessment',
|
|
279
213
|
toolRegistry,
|
|
280
214
|
tools: {
|
|
281
215
|
providers: {
|
|
282
216
|
textToSpeech: { enabled: true, backend: 'browser' },
|
|
283
|
-
// Desmos needs an
|
|
217
|
+
// Desmos needs an API key; see "Calculator providers" below.
|
|
284
218
|
calculator: { enabled: true }
|
|
285
219
|
},
|
|
286
220
|
placement: {
|
|
@@ -306,7 +240,7 @@ const toolState = coordinator.elementToolStateStore.getAllState();
|
|
|
306
240
|
|
|
307
241
|
### Controller Event Subscriptions (Helper First)
|
|
308
242
|
|
|
309
|
-
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:
|
|
310
244
|
|
|
311
245
|
```typescript
|
|
312
246
|
const unsubscribeItem = coordinator.subscribeItemEvents({
|
|
@@ -327,7 +261,7 @@ unsubscribeItem?.();
|
|
|
327
261
|
unsubscribeSection?.();
|
|
328
262
|
```
|
|
329
263
|
|
|
330
|
-
|
|
264
|
+
Subscription behavior:
|
|
331
265
|
|
|
332
266
|
- Subscribe **after** the first `getOrCreateSectionController(...)` resolves; calling subscribe before any cohort exists throws.
|
|
333
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.
|
|
@@ -339,85 +273,12 @@ Use `subscribeSectionEvents(...)` when you need advanced/custom filtering mixes.
|
|
|
339
273
|
|
|
340
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.
|
|
341
275
|
|
|
342
|
-
#### Migrating from `<0.3.35` (BREAKING — pre-1.0)
|
|
343
|
-
|
|
344
|
-
`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.
|
|
345
|
-
|
|
346
|
-
If your host code looked like this:
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
const unsub = coordinator.subscribeItemEvents({
|
|
350
|
-
sectionId: 'section-1',
|
|
351
|
-
attemptId: 'attempt-1',
|
|
352
|
-
listener: handleEvent,
|
|
353
|
-
});
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
Update it to drop `sectionId` / `attemptId`:
|
|
357
|
-
|
|
358
|
-
```typescript
|
|
359
|
-
const unsub = coordinator.subscribeItemEvents({
|
|
360
|
-
listener: handleEvent,
|
|
361
|
-
});
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
What this means in practice for typed integrations:
|
|
365
|
-
|
|
366
|
-
- **TypeScript breaking change.** `SectionEventSubscriptionArgs`, `SectionItemEventSubscriptionArgs`, and `SectionScopedEventSubscriptionArgs` no longer declare `sectionId?` / `attemptId?` properties. Any host that imports these arg types directly and passes those keys will fail to compile after upgrade. **Action required.**
|
|
367
|
-
- **Runtime is tolerant.** The runtime silently ignores extra unknown properties, so an untyped or lightly-typed call site that still passes `sectionId` / `attemptId` continues to work without source changes. The args have **no effect** at runtime — the subscription always follows the active cohort.
|
|
368
|
-
- **New precondition.** `subscribe*` throws until the first `getOrCreateSectionController(...)` call; a listener added while a section is starting binds when that section becomes active. Subscribe **after** the first `getOrCreateSectionController(...)` resolves. Subscribing on `toolkit-ready` alone is no longer sufficient — though in practice the section player emits `toolkit-ready` *after* its first `getOrCreateSectionController(...)` resolves, so a `toolkit-ready` anchor is safe in section-player hosts.
|
|
369
|
-
- **Cohort migration is automatic.** If your wrapper previously re-subscribed on every navigation to keep listeners alive across sections, that wiring is no longer needed (and should be removed). A single subscribe call after the first controller-resolve is now enough — the listener migrates automatically and is replayed the new cohort's snapshot on every transition.
|
|
370
|
-
- **Watch for double-replay if you re-subscribe on every `toolkit-ready`.** Hosts that detached and re-subscribed on every `toolkit-ready` event (the correct pre-Phase D pattern, since each subscription was pinned to a `sectionId`) will now observe **two snapshot replays per navigation**: one delivered automatically when Phase D migrates the existing listener to the new active cohort, and a second when the manual re-subscribe attaches a fresh listener that replays again. Listener handlers that are not strictly idempotent will fire twice — analytics `pageAction`s, non-Set counters, side-effecting hydration. The fix is a one-line guard (`if (this.controllerUnsubscribe) return;`) so the subscribe runs only on the first `toolkit-ready`.
|
|
371
|
-
- **For intentionally-pinned subscriptions to inactive sections** (e.g. a host UI that wants to keep watching section A while the user views section B), the helper API does not support that pattern by design. Use `coordinator.getSectionController({ sectionId, attemptId })` and subscribe directly on the controller handle (`controller.subscribe?.(...)`) — that binding is pinned to one controller instance and does not migrate.
|
|
372
|
-
|
|
373
|
-
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.
|
|
374
|
-
|
|
375
|
-
#### Pre-Phase D vs Phase D wrapper pattern
|
|
376
|
-
|
|
377
|
-
```typescript
|
|
378
|
-
// BEFORE (pre-Phase D): rebind for every section change because the
|
|
379
|
-
// subscription was pinned to a sectionId.
|
|
380
|
-
public handleToolkitReady(event: Event): void {
|
|
381
|
-
const coordinator = (event as CustomEvent).detail?.coordinator;
|
|
382
|
-
if (!coordinator) return;
|
|
383
|
-
this.controllerUnsubscribe?.(); // detach prior pin
|
|
384
|
-
const itemUnsub = coordinator.subscribeItemEvents({
|
|
385
|
-
sectionId: this.sectionId,
|
|
386
|
-
listener: handleItemEvent,
|
|
387
|
-
});
|
|
388
|
-
const sectionUnsub = coordinator.subscribeSectionLifecycleEvents({
|
|
389
|
-
sectionId: this.sectionId,
|
|
390
|
-
listener: handleSectionEvent,
|
|
391
|
-
});
|
|
392
|
-
this.controllerUnsubscribe = () => { itemUnsub?.(); sectionUnsub?.(); };
|
|
393
|
-
}
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
```typescript
|
|
397
|
-
// AFTER (Phase D): subscribe once; the listener follows the active
|
|
398
|
-
// cohort across all subsequent navigation.
|
|
399
|
-
public handleToolkitReady(event: Event): void {
|
|
400
|
-
const coordinator = (event as CustomEvent).detail?.coordinator;
|
|
401
|
-
if (!coordinator) return;
|
|
402
|
-
this.toolkitCoordinator = coordinator;
|
|
403
|
-
if (this.controllerUnsubscribe) return; // already subscribed; do nothing on re-fire
|
|
404
|
-
const itemUnsub = coordinator.subscribeItemEvents({
|
|
405
|
-
listener: handleItemEvent,
|
|
406
|
-
});
|
|
407
|
-
const sectionUnsub = coordinator.subscribeSectionLifecycleEvents({
|
|
408
|
-
listener: handleSectionEvent,
|
|
409
|
-
});
|
|
410
|
-
this.controllerUnsubscribe = () => { itemUnsub?.(); sectionUnsub?.(); };
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
276
|
### Option 2: Create Services Manually (Advanced)
|
|
415
277
|
|
|
416
278
|
```typescript
|
|
417
279
|
import {
|
|
418
280
|
TTSService,
|
|
419
281
|
BrowserTTSProvider,
|
|
420
|
-
ToolCoordinator,
|
|
421
282
|
HighlightCoordinator,
|
|
422
283
|
AccessibilityCatalogResolver,
|
|
423
284
|
ElementToolStateStore
|
|
@@ -425,7 +286,6 @@ import {
|
|
|
425
286
|
|
|
426
287
|
// Initialize each service independently
|
|
427
288
|
const ttsService = new TTSService();
|
|
428
|
-
const toolCoordinator = new ToolCoordinator();
|
|
429
289
|
const highlightCoordinator = new HighlightCoordinator();
|
|
430
290
|
const elementToolStateStore = new ElementToolStateStore();
|
|
431
291
|
const catalogResolver = new AccessibilityCatalogResolver([], 'en-US');
|
|
@@ -439,15 +299,15 @@ ttsService.setCatalogResolver(catalogResolver);
|
|
|
439
299
|
No player element takes services one by one. The section and assessment
|
|
440
300
|
players reach services only through a coordinator, `runtime.coordinator` on a
|
|
441
301
|
section player and `coordinator` on an assessment player; manually created
|
|
442
|
-
services serve host code that drives them directly.
|
|
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`.
|
|
443
304
|
|
|
444
305
|
### Without a Section Player
|
|
445
306
|
|
|
446
|
-
`<pie-assessment-toolkit>` needs no section. Bind none and it
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
builds:
|
|
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:
|
|
451
311
|
|
|
452
312
|
```html
|
|
453
313
|
<pie-assessment-toolkit>
|
|
@@ -465,20 +325,17 @@ scope registers as soon as it mounts, and that first registration starts the
|
|
|
465
325
|
coordinator from the toolkit's inputs at that moment; see the binding rules
|
|
466
326
|
below.
|
|
467
327
|
|
|
328
|
+

|
|
329
|
+
|
|
468
330
|
```typescript
|
|
469
331
|
import '@pie-players/pie-assessment-toolkit/components/pie-assessment-toolkit-element';
|
|
470
332
|
import '@pie-players/pie-assessment-toolkit/components/item-scope-element';
|
|
471
333
|
import '@pie-players/pie-assessment-toolkit/components/item-toolbar-element';
|
|
472
334
|
import '@pie-players/pie-item-player';
|
|
473
|
-
import {
|
|
474
|
-
createPackagedToolRegistry,
|
|
475
|
-
DEFAULT_TOOL_MODULE_LOADERS,
|
|
476
|
-
} from '@pie-players/pie-default-tool-loaders';
|
|
335
|
+
import { createPackagedToolRegistry } from '@pie-players/pie-default-tool-loaders';
|
|
477
336
|
|
|
478
337
|
// With no section player, the registry's loaders are what define the tool elements.
|
|
479
|
-
const toolRegistry = createPackagedToolRegistry(
|
|
480
|
-
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
481
|
-
});
|
|
338
|
+
const toolRegistry = createPackagedToolRegistry();
|
|
482
339
|
|
|
483
340
|
const toolkit = document.createElement('pie-assessment-toolkit');
|
|
484
341
|
toolkit.tools = {
|
|
@@ -501,76 +358,109 @@ toolkit.append(scope);
|
|
|
501
358
|
container.append(toolkit);
|
|
502
359
|
```
|
|
503
360
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
`
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
A
|
|
517
|
-
|
|
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
|
|
518
385
|
`{ runtimeId, coordinator, ownership }`, once per coordinator, with or without a
|
|
519
386
|
section. Without one, the coordinator starts at the first item that registers,
|
|
520
387
|
and a host reads its readiness from `coordinator.waitUntilReady()` or
|
|
521
|
-
`isReady()
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
`
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
had none, which the coordinator adopts in place
|
|
537
|
-
`assessment` and `toolContextResolvers` apply
|
|
538
|
-
time.
|
|
539
|
-
coordinator reports feature policy asked with no assessment bound only
|
|
540
|
-
`tools.pnpEnforcement` is `on`: a toolkit given no `assessment` and no
|
|
541
|
-
has asked for no accommodation.
|
|
542
|
-
|
|
543
|
-
A toolkit nested in another inherits the outer one's
|
|
544
|
-
one has a coordinator by the time the inner one
|
|
545
|
-
mounted together. Otherwise the inner one builds
|
|
546
|
-
reports an outer coordinator arriving later once in
|
|
547
|
-
`"force"` keeps a nested toolkit on its own coordinator
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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.
|
|
556
432
|
|
|
557
433
|
## Tool Configuration Model
|
|
558
434
|
|
|
559
|
-
The toolkit uses one canonical `tools` model with
|
|
435
|
+
The toolkit uses one canonical `tools` model with four keys:
|
|
560
436
|
|
|
561
|
-
- `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.
|
|
562
439
|
- `placement`: where tools appear (`section`, `item`, `passage`). Each tool
|
|
563
440
|
declares the levels it supports; `calculator` is item-only, and a tool placed
|
|
564
441
|
at a level it does not support fails validation, which throws under the
|
|
565
442
|
default `toolConfigStrictness: "error"`.
|
|
566
|
-
- `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, and `createPersonalNeedsProfileFromAfa` in
|
|
453
|
+
`@pie-players/pie-default-tool-loaders` converts an AfA record
|
|
454
|
+
([AfA PNP 3.0 records](docs/PNP_CONFIGURATION.md#afa-pnp-30-records)).
|
|
567
455
|
|
|
568
456
|
Example:
|
|
569
457
|
|
|
570
458
|
```typescript
|
|
571
459
|
tools: {
|
|
572
460
|
policy: {
|
|
461
|
+
// Only these tools may appear...
|
|
573
462
|
allowed: ['calculator', 'textToSpeech', 'answerEliminator', 'graph', 'periodicTable'],
|
|
463
|
+
// ...and graph never does.
|
|
574
464
|
blocked: ['graph']
|
|
575
465
|
},
|
|
576
466
|
placement: {
|
|
@@ -589,25 +479,38 @@ tools: {
|
|
|
589
479
|
}
|
|
590
480
|
```
|
|
591
481
|
|
|
592
|
-
|
|
482
|
+
With this configuration the section toolbar shows only `periodicTable`:
|
|
483
|
+
`graph` is blocked, and `protractor`, `lineReader` and `ruler` are outside the
|
|
484
|
+
allow-list. The item and passage placements show every tool they list.
|
|
593
485
|
|
|
594
|
-
|
|
486
|
+
### Scope and Lifecycle
|
|
595
487
|
|
|
596
|
-
Tool instances use structured
|
|
488
|
+
Tool instances use structured ids that make their scope explicit:
|
|
597
489
|
|
|
598
490
|
```text
|
|
599
|
-
<toolId>:<scopeLevel>:<scopeId>
|
|
491
|
+
<toolId>:<scopeLevel>:<scopeId>
|
|
600
492
|
```
|
|
601
493
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
494
|
+
`scopeLevel` is one of `assessment`, `section`, `item`, `passage` or `rubric`,
|
|
495
|
+
and `createScopedToolId` throws on any other. The toolbars the players render
|
|
496
|
+
scope their ids at `section`, `item` or `passage`, and a rubric block's toolbar
|
|
497
|
+
scopes at `passage`. Examples from a section player:
|
|
498
|
+
|
|
499
|
+
- `lineReader:section:reading-1`
|
|
500
|
+
- `theme:section:reading-1`
|
|
501
|
+
- `calculator:item:q1`
|
|
605
502
|
- `textToSpeech:passage:passage-1`
|
|
606
|
-
|
|
503
|
+
|
|
504
|
+
The section player keeps every item card of a section mounted while the student
|
|
505
|
+
moves between its items. The ElementToolStateStore keeps answer eliminations,
|
|
506
|
+
annotation highlights and the color-scheme choice, and the coordinator's
|
|
507
|
+
`saveToolState`/`loadToolState` hooks carry them across reloads
|
|
508
|
+
([Persistence Integration](#persistence-integration)). Which tools are open is
|
|
509
|
+
not restored, and graph points start over.
|
|
607
510
|
|
|
608
511
|
### Item-Level Tools (`tools.placement.item`)
|
|
609
512
|
|
|
610
|
-
Tools that operate
|
|
513
|
+
Tools that operate within the context of one item:
|
|
611
514
|
|
|
612
515
|
```typescript
|
|
613
516
|
tools: {
|
|
@@ -618,24 +521,37 @@ tools: {
|
|
|
618
521
|
```
|
|
619
522
|
|
|
620
523
|
**Characteristics:**
|
|
621
|
-
- **Scope**:
|
|
622
|
-
- **
|
|
623
|
-
|
|
624
|
-
- **
|
|
625
|
-
- **
|
|
626
|
-
|
|
627
|
-
**
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
- **
|
|
524
|
+
- **Scope**: bound to one item's DOM context
|
|
525
|
+
- **Lifetime**: an item's tools live as long as its card, which stays mounted
|
|
526
|
+
until the section changes
|
|
527
|
+
- **State**: isolated per item (eliminations on Q5 don't affect Q6)
|
|
528
|
+
- **UI pattern**: buttons in the item toolbar; tools such as the calculator and
|
|
529
|
+
the dictionaries open in a floating panel
|
|
530
|
+
- **Persistence**: answer eliminations and annotation highlights are kept per
|
|
531
|
+
PIE element in the ElementToolStateStore
|
|
532
|
+
|
|
533
|
+
**Available item-level tools:**
|
|
534
|
+
- **Text-to-speech** (`textToSpeech`): reads the item or passage text
|
|
535
|
+
- **Answer eliminator** (`answerEliminator`, item only): strikes through answer
|
|
536
|
+
choices
|
|
537
|
+
- **Calculator** (`calculator`, item only): basic, scientific or graphing
|
|
538
|
+
calculator
|
|
539
|
+
- **Annotation toolbar** (`annotationToolbar`): highlights and annotates
|
|
540
|
+
selected text; it opens from a text selection, outside the toolbars
|
|
541
|
+
- **Line reader** (`lineReader`), **ruler** (`ruler`), **protractor**
|
|
542
|
+
(`protractor`), **graph** (`graph`) and **periodic table** (`periodicTable`)
|
|
543
|
+
- **Dictionaries** (`dictionary`, `pictureDictionary`, `dictionarySpanish`,
|
|
544
|
+
`pictureDictionarySpanish`)
|
|
545
|
+
|
|
546
|
+
[Default Tools](docs/TOOL_REGISTRY.md#default-tools) lists every packaged tool
|
|
547
|
+
with the content it applies to.
|
|
632
548
|
|
|
633
549
|
**Example Use Case:**
|
|
634
550
|
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.
|
|
635
551
|
|
|
636
552
|
### Section-Level Tools (`tools.placement.section`)
|
|
637
553
|
|
|
638
|
-
Tools that
|
|
554
|
+
Tools that float above the section and stay open across its items:
|
|
639
555
|
|
|
640
556
|
```typescript
|
|
641
557
|
tools: {
|
|
@@ -645,26 +561,30 @@ tools: {
|
|
|
645
561
|
}
|
|
646
562
|
```
|
|
647
563
|
|
|
648
|
-
The section player renders the section toolbar only when its `show-toolbar`
|
|
649
|
-
attribute is `true`; the attribute defaults to `false`.
|
|
650
|
-
|
|
651
564
|
**Characteristics:**
|
|
652
|
-
- **Scope**:
|
|
653
|
-
- **
|
|
654
|
-
-
|
|
655
|
-
- **
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
- **
|
|
661
|
-
- **
|
|
662
|
-
- **
|
|
663
|
-
- **
|
|
664
|
-
- **
|
|
565
|
+
- **Scope**: section-wide, shared across the section's items
|
|
566
|
+
- **Lifetime**: one instance per section; at a section change the tool
|
|
567
|
+
re-scopes to the new section and its state starts over
|
|
568
|
+
- **State**: an open tool stays open and in place while the student moves
|
|
569
|
+
between the section's items
|
|
570
|
+
- **UI pattern**: draggable floating panels with z-index management
|
|
571
|
+
|
|
572
|
+
**Available section-level tools:**
|
|
573
|
+
- **Graph** (`graph`): coordinate plane
|
|
574
|
+
- **Periodic table** (`periodicTable`): interactive periodic table reference
|
|
575
|
+
- **Protractor** (`protractor`): angle measurement
|
|
576
|
+
- **Ruler** (`ruler`): linear measurement (metric and imperial)
|
|
577
|
+
- **Line reader** (`lineReader`): reading guide and masking overlay
|
|
578
|
+
- **Dictionaries** (`dictionary`, `pictureDictionary`, `dictionarySpanish`,
|
|
579
|
+
`pictureDictionarySpanish`)
|
|
580
|
+
- **The color-scheme tool** (`theme`): high-contrast color schemes; it also
|
|
581
|
+
supports assessment level
|
|
582
|
+
|
|
583
|
+
Every section-level tool except the color-scheme tool also supports item
|
|
584
|
+
placement.
|
|
665
585
|
|
|
666
586
|
**Example Use Case:**
|
|
667
|
-
A student opens the periodic table on Question 2 and moves it beside the passage. When they navigate to Question 7, it is still open where they left it.
|
|
587
|
+
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.
|
|
668
588
|
|
|
669
589
|
### When to Use Each
|
|
670
590
|
|
|
@@ -674,25 +594,23 @@ Use **item-level tools** when:
|
|
|
674
594
|
- Tool appears inline with the question (space-efficient)
|
|
675
595
|
- Tool behavior is contextual to the current item
|
|
676
596
|
|
|
677
|
-
Use **
|
|
597
|
+
Use **section-level tools** when:
|
|
678
598
|
- Tool is a general-purpose utility used across multiple questions
|
|
679
|
-
- State should persist
|
|
599
|
+
- State should persist while the student moves between a section's items
|
|
680
600
|
- Tool needs independent positioning and sizing
|
|
681
601
|
- Tool provides reference information or computation capability
|
|
682
602
|
|
|
683
|
-
### Configuration
|
|
603
|
+
### Configuration example
|
|
684
604
|
|
|
685
|
-
|
|
605
|
+
A coordinator shows a tool only where `tools.placement` lists it, and its
|
|
606
|
+
default placement is empty. This configuration places the commonly used tools
|
|
607
|
+
at both kinds of level:
|
|
686
608
|
|
|
687
609
|
```typescript
|
|
688
|
-
import {
|
|
689
|
-
|
|
690
|
-
DEFAULT_TOOL_MODULE_LOADERS,
|
|
691
|
-
} from '@pie-players/pie-default-tool-loaders';
|
|
610
|
+
import { ToolkitCoordinator } from '@pie-players/pie-assessment-toolkit';
|
|
611
|
+
import { createPackagedToolRegistry } from '@pie-players/pie-default-tool-loaders';
|
|
692
612
|
|
|
693
|
-
const toolRegistry = createPackagedToolRegistry(
|
|
694
|
-
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
695
|
-
});
|
|
613
|
+
const toolRegistry = createPackagedToolRegistry();
|
|
696
614
|
const coordinator = new ToolkitCoordinator({
|
|
697
615
|
assessmentId: 'math-exam',
|
|
698
616
|
toolRegistry,
|
|
@@ -726,52 +644,16 @@ const coordinator = new ToolkitCoordinator({
|
|
|
726
644
|
});
|
|
727
645
|
```
|
|
728
646
|
|
|
729
|
-
|
|
647
|
+
### Calculator providers
|
|
730
648
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
DEFAULT_TOOL_MODULE_LOADERS,
|
|
740
|
-
} from '@pie-players/pie-default-tool-loaders';
|
|
741
|
-
|
|
742
|
-
const toolRegistry = createPackagedToolRegistry({
|
|
743
|
-
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
744
|
-
});
|
|
745
|
-
const coordinator = new ToolkitCoordinator({
|
|
746
|
-
assessmentId: 'my-assessment',
|
|
747
|
-
toolRegistry,
|
|
748
|
-
tools: {
|
|
749
|
-
placement: {
|
|
750
|
-
section: ['graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
|
|
751
|
-
item: ['calculator', 'textToSpeech', 'answerEliminator'],
|
|
752
|
-
passage: ['textToSpeech']
|
|
753
|
-
},
|
|
754
|
-
providers: {
|
|
755
|
-
calculator: {
|
|
756
|
-
enabled: true,
|
|
757
|
-
provider: {
|
|
758
|
-
runtime: { authFetcher: fetchLicensedDesmosApiKey }
|
|
759
|
-
}
|
|
760
|
-
},
|
|
761
|
-
textToSpeech: { enabled: true, backend: 'browser' }
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
});
|
|
765
|
-
```
|
|
766
|
-
|
|
767
|
-
The ToolkitCoordinator handles service initialization, provider management, and
|
|
768
|
-
state coordination. With no calculator provider configuration, the Desmos
|
|
769
|
-
implementation remains the default. Desmos's CDN rejects a `calculator.js`
|
|
770
|
-
request without an `apiKey` (HTTP 403), so the Desmos calculator opens only when
|
|
771
|
-
the host supplies `provider.init.apiKey` or `provider.runtime.authFetcher`, or
|
|
772
|
-
has already loaded `window.Desmos`; the adapter's keyless request grants no
|
|
773
|
-
Desmos license either. Runtime key delivery avoids committing the key but does
|
|
774
|
-
not hide it from the browser's Desmos script request. The bundled open-source
|
|
649
|
+
With no calculator provider configured, the calculator is Desmos. Desmos's CDN
|
|
650
|
+
rejects a `calculator.js` request without an `apiKey` (HTTP 403), so the Desmos
|
|
651
|
+
calculator opens only when the host supplies a key through
|
|
652
|
+
`provider.runtime.authFetcher`, a `provider.init.proxyEndpoint` that returns
|
|
653
|
+
`{ apiKey }`, or `provider.init.apiKey`, or has already loaded `window.Desmos`.
|
|
654
|
+
Without one the adapter throws "An apiKey or proxyEndpoint is required to load
|
|
655
|
+
Desmos." Runtime key delivery keeps the key out of the bundle, and the
|
|
656
|
+
browser's Desmos script request still carries it. The bundled open-source
|
|
775
657
|
implementation below needs no key.
|
|
776
658
|
|
|
777
659
|
Select the separate GeoGebra implementation explicitly without changing the
|
|
@@ -819,8 +701,9 @@ tools: {
|
|
|
819
701
|
By default, server-backed TTS resolves:
|
|
820
702
|
|
|
821
703
|
- `apiEndpoint: '/api/tts'`
|
|
822
|
-
- `transportMode: 'pie'`
|
|
823
|
-
- `endpointValidationMode: 'voices'
|
|
704
|
+
- `transportMode: 'custom'` for `serverProvider: 'custom'`, else `'pie'`
|
|
705
|
+
- `endpointValidationMode: 'voices'`, so the provider reads the voices route
|
|
706
|
+
before it reports ready; `'none'` skips the probe
|
|
824
707
|
|
|
825
708
|
You can still set `apiEndpoint` explicitly when your host route is not `/api/tts`.
|
|
826
709
|
|
|
@@ -957,9 +840,9 @@ Recommended host boundary:
|
|
|
957
840
|
upstream.
|
|
958
841
|
- Browser never receives shared secret, API key, or signing material.
|
|
959
842
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
configures
|
|
843
|
+
The section demos' `/api/tts/sc` route, backed by the SC adapter
|
|
844
|
+
(`@pie-players/tts-server-sc`), is the worked example of custom transport. TTS
|
|
845
|
+
stays on browser speech unless the host configures a server backend.
|
|
963
846
|
|
|
964
847
|
## Test Attempt Session
|
|
965
848
|
|
|
@@ -994,7 +877,7 @@ const itemSessions = toItemSessionsRecord(testAttemptSession);
|
|
|
994
877
|
### Integration Boundary
|
|
995
878
|
|
|
996
879
|
- `@pie-players/pie-section-player` stays backend-agnostic and emits session/state changes.
|
|
997
|
-
- Host applications own backend I/O
|
|
880
|
+
- Host applications own backend I/O.
|
|
998
881
|
- Hosts decide persistence policy (immediate, debounced, checkpoint, submit).
|
|
999
882
|
|
|
1000
883
|
### Section session API (controller + persistence)
|
|
@@ -1006,34 +889,6 @@ For section-level session flows, the toolkit supports two complementary APIs:
|
|
|
1006
889
|
|
|
1007
890
|
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.
|
|
1008
891
|
|
|
1009
|
-
## Implementation Status
|
|
1010
|
-
|
|
1011
|
-
### ✅ Toolkit Services
|
|
1012
|
-
|
|
1013
|
-
- **ToolkitCoordinator**: ⭐ NEW - Centralized service orchestration
|
|
1014
|
-
- **ElementToolStateStore**: ⭐ NEW - Element-level ephemeral tool state management
|
|
1015
|
-
- **ToolRegistry**: ⭐ NEW - Registry-based tool management with AfA PNP 3.0 profile support
|
|
1016
|
-
- **ToolPolicyEngine**: AfA PNP 3.0 Personal Needs Profile and host policy decisions via registry-backed policy sources
|
|
1017
|
-
- **ToolCoordinator**: Manages z-index layering and visibility for floating tools
|
|
1018
|
-
- **HighlightCoordinator**: Separate highlight layers for TTS (temporary) and annotations (persistent)
|
|
1019
|
-
- **TTSService**: Text-to-speech with QTI 3.0 catalog support
|
|
1020
|
-
- **AccessibilityCatalogResolver**: QTI 3.0 accessibility catalog management
|
|
1021
|
-
- **SSMLExtractor**: Automatic extraction of embedded `<speak>` tags
|
|
1022
|
-
|
|
1023
|
-
### ✅ PNP Support Ids
|
|
1024
|
-
|
|
1025
|
-
- **Support ids are tool ids**: A profile, district policy or item setting names a tool by its `toolId`, and an id no registered tool carries raises `tool-policy.unknownSupportId`. [Support ids](docs/TOOL_REGISTRY.md#support-ids) lists the AfA PNP 3.0 terms the packaged ids serve.
|
|
1026
|
-
|
|
1027
|
-
### ✅ Section Player Integration
|
|
1028
|
-
|
|
1029
|
-
The toolkit integrates seamlessly with the **PIE Section Player**:
|
|
1030
|
-
|
|
1031
|
-
- **Primary Interface**: Section player is the main integration point
|
|
1032
|
-
- **Default Coordinator**: Creates ToolkitCoordinator automatically if not provided
|
|
1033
|
-
- **Automatic SSML Extraction**: Extracts embedded `<speak>` tags from passages and items
|
|
1034
|
-
- **Catalog Lifecycle**: Manages item-level catalogs automatically
|
|
1035
|
-
- **Service Coordination**: All toolkit services work together automatically
|
|
1036
|
-
|
|
1037
892
|
## ToolkitCoordinator API
|
|
1038
893
|
|
|
1039
894
|
### Configuration
|
|
@@ -1051,13 +906,18 @@ export interface ToolkitCoordinatorConfig {
|
|
|
1051
906
|
item?: string[];
|
|
1052
907
|
passage?: string[];
|
|
1053
908
|
};
|
|
909
|
+
pnpEnforcement?: 'on' | 'off'; // omitted: auto-detected from the assessment
|
|
1054
910
|
providers?: {
|
|
911
|
+
// Abridged; TextToSpeechToolProviderConfig carries every option.
|
|
1055
912
|
textToSpeech?: {
|
|
1056
913
|
enabled?: boolean;
|
|
1057
914
|
backend?: 'browser' | 'server';
|
|
1058
915
|
serverProvider?: 'polly' | 'google' | 'custom';
|
|
916
|
+
apiEndpoint?: string;
|
|
1059
917
|
defaultVoice?: string;
|
|
1060
918
|
rate?: number;
|
|
919
|
+
speedOptions?: TTSSpeedOption[];
|
|
920
|
+
layoutMode?: 'reserved-row' | 'expanding-row' | 'floating-overlay' | 'left-aligned';
|
|
1061
921
|
};
|
|
1062
922
|
calculator?: {
|
|
1063
923
|
enabled?: boolean;
|
|
@@ -1073,63 +933,108 @@ export interface ToolkitCoordinatorConfig {
|
|
|
1073
933
|
};
|
|
1074
934
|
};
|
|
1075
935
|
};
|
|
936
|
+
toolConfigStrictness?: 'off' | 'warn' | 'error'; // default 'error'
|
|
1076
937
|
toolRegistry?: ToolRegistry | null;
|
|
938
|
+
toolContextResolvers?: ToolContextResolverMap;
|
|
1077
939
|
accessibility?: {
|
|
1078
940
|
catalogs?: any[];
|
|
1079
941
|
language?: string;
|
|
1080
942
|
};
|
|
943
|
+
hooks?: ToolkitCoordinatorHooks;
|
|
944
|
+
lazyInit?: boolean; // default false
|
|
945
|
+
eagerInit?: boolean; // default !lazyInit
|
|
1081
946
|
}
|
|
1082
947
|
```
|
|
1083
948
|
|
|
949
|
+
A `ToolkitCoordinator` registers tool providers only from its `toolRegistry`.
|
|
950
|
+
Built without one, it adopts the registry of the toolkit it is bound to, such as
|
|
951
|
+
the section player's, which then validates its config and registers its
|
|
952
|
+
providers. A registry passed at construction is never replaced. Bound to a
|
|
953
|
+
toolkit that has none, it registers no providers, skips tool-id and placement
|
|
954
|
+
validation, and warns once (`tools.registryUnavailable`).
|
|
955
|
+
|
|
1084
956
|
### Methods
|
|
1085
957
|
|
|
1086
958
|
```typescript
|
|
1087
959
|
// Tool configuration
|
|
1088
|
-
coordinator.getToolConfig('textToSpeech');
|
|
1089
|
-
coordinator.updateToolConfig('textToSpeech', { rate: 1.5 }); //
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
960
|
+
coordinator.getToolConfig('textToSpeech');
|
|
961
|
+
coordinator.updateToolConfig('textToSpeech', { rate: 1.5 }); // replaces the tool's provider, which starts again on next use
|
|
962
|
+
coordinator.updateToolsPlacement({ item: ['calculator', 'textToSpeech'] });
|
|
963
|
+
|
|
964
|
+
// Tool policy
|
|
965
|
+
coordinator.decideToolPolicy({ level: 'item', scope: { level: 'item', scopeId: 'item-42' } });
|
|
966
|
+
coordinator.decideFeaturePolicy('transcript', { level: 'item', scopeId: 'item-42' });
|
|
967
|
+
const unregisterItem = coordinator.registerItemSettings('item-42', itemSettings);
|
|
968
|
+
const offPolicy = coordinator.onPolicyChange(listener);
|
|
969
|
+
const offDiagnostics = coordinator.onPolicyDiagnostic(listener);
|
|
970
|
+
coordinator.updateAssessment(assessment); // new profile or settings; null unbinds
|
|
971
|
+
coordinator.setPnpEnforcement('on'); // 'on' | 'off'; null returns to auto-detection
|
|
972
|
+
coordinator.getPolicyInputs(); // read-only inputs behind current decisions
|
|
973
|
+
const unregisterSource = coordinator.registerPolicySource(source);
|
|
974
|
+
|
|
975
|
+
// Tool requests: ask the toolbar hosting a tool to open it
|
|
976
|
+
const unregisterTarget = coordinator.registerToolRequestTarget(target);
|
|
977
|
+
coordinator.canRequestTool('calculator'); // false for a tool the deployment lacks
|
|
978
|
+
coordinator.requestTool({ toolId: 'calculator' }); // whether a toolbar claimed it; throws on an unknown id
|
|
979
|
+
const offTargets = coordinator.onToolRequestTargetsChange(listener);
|
|
980
|
+
|
|
981
|
+
// Providers and events
|
|
982
|
+
const provider = await coordinator.ensureProviderReady('textToSpeech');
|
|
983
|
+
const offErrors = coordinator.subscribeFrameworkErrors(listener);
|
|
984
|
+
const offCatalogs = coordinator.onCatalogsChange(listener);
|
|
985
|
+
|
|
986
|
+
// Readiness and teardown
|
|
987
|
+
await coordinator.waitUntilReady();
|
|
988
|
+
coordinator.isReady();
|
|
989
|
+
coordinator.getToolRegistry();
|
|
990
|
+
await coordinator.dispose(); // final teardown by the owner that constructed the coordinator
|
|
1093
991
|
```
|
|
1094
992
|
|
|
993
|
+
Each `register*`, `on*` and `subscribe*` call returns a function that undoes it.
|
|
994
|
+
The section-controller methods are under
|
|
995
|
+
[Controller Event Subscriptions](#controller-event-subscriptions-helper-first).
|
|
996
|
+
|
|
1095
997
|
### Direct Service Access
|
|
1096
998
|
|
|
1097
999
|
All services are public properties for direct access:
|
|
1098
1000
|
|
|
1099
1001
|
```typescript
|
|
1100
1002
|
coordinator.ttsService // TTSService instance
|
|
1101
|
-
coordinator.toolCoordinator //
|
|
1003
|
+
coordinator.toolCoordinator // ToolCoordinatorApi, owned by the coordinator
|
|
1102
1004
|
coordinator.highlightCoordinator // HighlightCoordinator instance
|
|
1103
1005
|
coordinator.elementToolStateStore // ElementToolStateStore instance
|
|
1104
1006
|
coordinator.catalogResolver // AccessibilityCatalogResolver instance
|
|
1007
|
+
coordinator.toolProviderRegistry // ToolProviderRegistry, the providers registered from toolRegistry
|
|
1105
1008
|
```
|
|
1106
1009
|
|
|
1107
1010
|
## ElementToolStateStore API
|
|
1108
1011
|
|
|
1109
|
-
The `ElementToolStateStore`
|
|
1012
|
+
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)).
|
|
1110
1013
|
|
|
1111
1014
|
### Key Concepts
|
|
1112
1015
|
|
|
1113
|
-
- **Global Element ID**: Composite key format: `${assessmentId}:${sectionId}:${itemId}:${elementId}`
|
|
1114
|
-
- **Element-Level Granularity**: State tracked per PIE element
|
|
1115
|
-
- **
|
|
1016
|
+
- **Global Element ID**: Composite key format: `${assessmentId}:${sectionId}:${attemptId}:${itemId}:${elementId}`, with `attemptId` `""` when the host names no attempt
|
|
1017
|
+
- **Element-Level Granularity**: State tracked per PIE element; a tool keyed by item or section leaves `elementId`, and for a section `itemId`, as `""`
|
|
1018
|
+
- **Per-Attempt State**: two attempts at one section never share a key
|
|
1019
|
+
- **Separate from session data**: tool state never enters the PIE session and is never scored
|
|
1116
1020
|
- **Cross-Section Persistence**: State persists when navigating between sections
|
|
1117
1021
|
|
|
1118
1022
|
### ID Utilities
|
|
1119
1023
|
|
|
1120
1024
|
```typescript
|
|
1121
1025
|
// Generate global element ID
|
|
1122
|
-
const globalElementId = store.getGlobalElementId(
|
|
1123
|
-
'demo-assessment',
|
|
1124
|
-
'section-1',
|
|
1125
|
-
'
|
|
1126
|
-
'
|
|
1127
|
-
|
|
1128
|
-
|
|
1026
|
+
const globalElementId = store.getGlobalElementId({
|
|
1027
|
+
assessmentId: 'demo-assessment',
|
|
1028
|
+
sectionId: 'section-1',
|
|
1029
|
+
attemptId: 'attempt-1',
|
|
1030
|
+
itemId: 'question-1',
|
|
1031
|
+
elementId: 'mc1'
|
|
1032
|
+
});
|
|
1033
|
+
// Returns: "demo-assessment:section-1:attempt-1:question-1:mc1"
|
|
1129
1034
|
|
|
1130
1035
|
// Parse global element ID
|
|
1131
1036
|
const components = store.parseGlobalElementId(globalElementId);
|
|
1132
|
-
// Returns: { assessmentId, sectionId, itemId, elementId }
|
|
1037
|
+
// Returns: { assessmentId, sectionId, attemptId, itemId, elementId }
|
|
1133
1038
|
```
|
|
1134
1039
|
|
|
1135
1040
|
### CRUD Operations
|
|
@@ -1159,7 +1064,7 @@ store.clearElement(globalElementId);
|
|
|
1159
1064
|
// Clear state for a specific tool across all elements
|
|
1160
1065
|
store.clearTool('answerEliminator');
|
|
1161
1066
|
|
|
1162
|
-
// Clear all elements in a specific section
|
|
1067
|
+
// Clear all elements in a specific section, across its attempts
|
|
1163
1068
|
store.clearSection('demo-assessment', 'section-1');
|
|
1164
1069
|
|
|
1165
1070
|
// Clear all state
|
|
@@ -1168,17 +1073,21 @@ store.clearAll();
|
|
|
1168
1073
|
|
|
1169
1074
|
### Persistence Integration
|
|
1170
1075
|
|
|
1076
|
+
A coordinator's store reports changes to the coordinator, which forwards them
|
|
1077
|
+
to `hooks.saveToolState`; `loadToolState` is read once while the coordinator
|
|
1078
|
+
gets ready. Persist tool state through those hooks. `store.setOnStateChange`
|
|
1079
|
+
holds a single callback, so calling it on a coordinator's store disconnects
|
|
1080
|
+
`saveToolState`.
|
|
1081
|
+
|
|
1171
1082
|
```typescript
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1083
|
+
const coordinator = new ToolkitCoordinator({
|
|
1084
|
+
assessmentId: 'demo-assessment',
|
|
1085
|
+
toolRegistry,
|
|
1086
|
+
hooks: {
|
|
1087
|
+
loadToolState: () => JSON.parse(localStorage.getItem('tool-state') ?? 'null'),
|
|
1088
|
+
saveToolState: (state) => localStorage.setItem('tool-state', JSON.stringify(state)),
|
|
1089
|
+
},
|
|
1175
1090
|
});
|
|
1176
|
-
|
|
1177
|
-
// Load state from persistence
|
|
1178
|
-
const saved = localStorage.getItem('tool-state');
|
|
1179
|
-
if (saved) {
|
|
1180
|
-
store.loadState(JSON.parse(saved));
|
|
1181
|
-
}
|
|
1182
1091
|
```
|
|
1183
1092
|
|
|
1184
1093
|
### Reactivity
|
|
@@ -1252,7 +1161,8 @@ onDestroy(() => registration.release());
|
|
|
1252
1161
|
Visibility and stacking then go through the coordinator by scoped tool id:
|
|
1253
1162
|
`showTool`, `hideTool`, `toggleTool`, `isToolVisible`, `bringToFront(element)`,
|
|
1254
1163
|
and `getVisibleTools({ baseId })` / `hideAllTools({ baseId })` for one tool
|
|
1255
|
-
across scopes.
|
|
1164
|
+
across scopes. Disposing the toolkit coordinator releases every entry. Its
|
|
1165
|
+
debug lines print only under `window.PIE_DEBUG = true`.
|
|
1256
1166
|
|
|
1257
1167
|
### HighlightCoordinator
|
|
1258
1168
|
|
|
@@ -1282,12 +1192,14 @@ Reading highlights resolve through five `component-public` tokens registered in
|
|
|
1282
1192
|
| `--pie-tts-word-underline` | `--pie-text` at 70% | Underline marking the current word |
|
|
1283
1193
|
| `--pie-tts-word-shadow` | `--pie-text` at 35% | Shadow carrying the word over its fill |
|
|
1284
1194
|
|
|
1285
|
-
The coordinator derives all five from the active theme's
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1195
|
+
The coordinator derives all five from the active theme's `--pie-missing`,
|
|
1196
|
+
`--pie-text` and `--pie-background`. `--pie-missing` is the theme's
|
|
1197
|
+
missing-response feedback color, which reading highlights reuse as their accent.
|
|
1198
|
+
Opacities are clamped to a legible band, and the underline color is picked by
|
|
1199
|
+
background luminance so the cue survives a dark scheme. The coordinator writes
|
|
1200
|
+
the five inline on the document element. Inline styles outrank any author
|
|
1201
|
+
selector, so a host override takes `!important` and owns the contrast the
|
|
1202
|
+
derivation was maintaining, across every scheme it ships.
|
|
1291
1203
|
|
|
1292
1204
|
### AccessibilityCatalogResolver
|
|
1293
1205
|
|
|
@@ -1374,11 +1286,8 @@ catalogResolver.addItemCatalogs(result.catalogs);
|
|
|
1374
1286
|
|
|
1375
1287
|
### Cards without an extractor
|
|
1376
1288
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
existed and was removed, because nothing produced the inline form and a runtime
|
|
1380
|
-
that could not parse the markup left the video in the visible content, showing the
|
|
1381
|
-
accommodation to every learner regardless of eligibility.
|
|
1289
|
+
A signed alternate is authored or written by an importer; no extractor lifts it
|
|
1290
|
+
from markup.
|
|
1382
1291
|
|
|
1383
1292
|
This package resolves and registers those cards through
|
|
1384
1293
|
`AccessibilityCatalogResolver` and the generic media helpers it re-exports
|
|
@@ -1391,25 +1300,18 @@ needs them — signing's card validators and its resolution rules live in
|
|
|
1391
1300
|
|
|
1392
1301
|
`ToolPolicyEngine.decideFeature(featureId, scope?)` (and
|
|
1393
1302
|
`ToolkitCoordinator.decideFeaturePolicy(featureId, scope?)`) resolve one feature id
|
|
1394
|
-
through `PnpPolicySource`'s
|
|
1395
|
-
placement. Use it for capabilities that are not toolbar surfaces
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1303
|
+
through `PnpPolicySource`'s PNP precedence, independent of any toolbar
|
|
1304
|
+
placement. Use it for capabilities that are not toolbar surfaces, such as
|
|
1305
|
+
signing: an item-level `decideToolPolicy(...)` also reports a tool absent when it
|
|
1306
|
+
was never placed, so it cannot tell a denial from a missing placement. An item
|
|
1307
|
+
scope applies that item's registered settings, the item restriction and
|
|
1399
1308
|
requirement rungs; a decision without one applies no item's.
|
|
1400
1309
|
|
|
1401
|
-
This package ships no profile.
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
configuration alongside the district and test-administration levels. Hosts that
|
|
1405
|
-
want today's universal set take `createUniversalPersonalNeedsProfile()` from
|
|
1406
|
-
`@pie-players/pie-default-tool-loaders`, which ships it as data beside
|
|
1407
|
-
`createEmptyPersonalNeedsProfile()`.
|
|
1310
|
+
This package ships no profile.
|
|
1311
|
+
[Universal and empty profiles](../default-tool-loaders/README.md#universal-and-empty-profiles)
|
|
1312
|
+
covers the profiles `@pie-players/pie-default-tool-loaders` builds.
|
|
1408
1313
|
|
|
1409
|
-
|
|
1410
|
-
membership as eligibility tier — registration means "policy-addressable", not
|
|
1411
|
-
"universal, on by default" — and had to be corrected with a compile-time list of
|
|
1412
|
-
ids to exclude that a host could not extend for its own accommodation.
|
|
1314
|
+
Registry membership makes a tool policy-addressable; it grants nothing.
|
|
1413
1315
|
|
|
1414
1316
|
### Live registry changes
|
|
1415
1317
|
|
|
@@ -1430,15 +1332,10 @@ The section player provides automatic ToolkitCoordinator integration:
|
|
|
1430
1332
|
|
|
1431
1333
|
<script type="module">
|
|
1432
1334
|
import { ToolkitCoordinator } from '@pie-players/pie-assessment-toolkit';
|
|
1433
|
-
import {
|
|
1434
|
-
createPackagedToolRegistry,
|
|
1435
|
-
DEFAULT_TOOL_MODULE_LOADERS,
|
|
1436
|
-
} from '@pie-players/pie-default-tool-loaders';
|
|
1335
|
+
import { createPackagedToolRegistry } from '@pie-players/pie-default-tool-loaders';
|
|
1437
1336
|
|
|
1438
1337
|
// Create coordinator
|
|
1439
|
-
const toolRegistry = createPackagedToolRegistry(
|
|
1440
|
-
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
1441
|
-
});
|
|
1338
|
+
const toolRegistry = createPackagedToolRegistry();
|
|
1442
1339
|
const coordinator = new ToolkitCoordinator({
|
|
1443
1340
|
assessmentId: 'my-assessment',
|
|
1444
1341
|
toolRegistry,
|
|
@@ -1511,7 +1408,7 @@ player.section = mySection;
|
|
|
1511
1408
|
|
|
1512
1409
|
// Internally creates:
|
|
1513
1410
|
// new ToolkitCoordinator({
|
|
1514
|
-
// assessmentId:
|
|
1411
|
+
// assessmentId: runtime.assessmentId ?? section.identifier ?? 'assessment-<random>',
|
|
1515
1412
|
// toolRegistry, // the player's toolRegistry, else the packaged registry
|
|
1516
1413
|
// tools: player.runtime?.tools // no tools are placed when this is unset
|
|
1517
1414
|
// })
|
|
@@ -1519,12 +1416,7 @@ player.section = mySection;
|
|
|
1519
1416
|
|
|
1520
1417
|
### Safe Custom Tool Configuration
|
|
1521
1418
|
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
- Framework logs a deterministic console error prefix: `[pie-framework:<kind>:<source>]`
|
|
1525
|
-
- Framework emits a canonical `framework-error` event
|
|
1526
|
-
- Framework renders a fallback error panel instead of a blank player
|
|
1527
|
-
- Startup tool-config validation can surface as `kind: "coordinator-init"` when the owned coordinator construction path throws.
|
|
1419
|
+
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).
|
|
1528
1420
|
|
|
1529
1421
|
Use `createToolsConfig()` when you want to pre-validate and inspect diagnostics before mounting:
|
|
1530
1422
|
|
|
@@ -1533,14 +1425,9 @@ import {
|
|
|
1533
1425
|
createToolsConfig,
|
|
1534
1426
|
ToolkitCoordinator
|
|
1535
1427
|
} from "@pie-players/pie-assessment-toolkit";
|
|
1536
|
-
import {
|
|
1537
|
-
createPackagedToolRegistry,
|
|
1538
|
-
DEFAULT_TOOL_MODULE_LOADERS,
|
|
1539
|
-
} from "@pie-players/pie-default-tool-loaders";
|
|
1428
|
+
import { createPackagedToolRegistry } from "@pie-players/pie-default-tool-loaders";
|
|
1540
1429
|
|
|
1541
|
-
const toolRegistry = createPackagedToolRegistry(
|
|
1542
|
-
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
|
|
1543
|
-
});
|
|
1430
|
+
const toolRegistry = createPackagedToolRegistry();
|
|
1544
1431
|
const { config, diagnostics } = createToolsConfig({
|
|
1545
1432
|
source: "host.bootstrap",
|
|
1546
1433
|
strictness: "error",
|
|
@@ -1569,22 +1456,49 @@ Notes:
|
|
|
1569
1456
|
- `providers.textToSpeech` is the canonical TTS provider key.
|
|
1570
1457
|
- `providers.tts` is rejected by the validation contract.
|
|
1571
1458
|
- A tool registration can declare `sanitizeConfig` and `validateConfig` hooks for its `providers.<toolId>` entry.
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1459
|
+
|
|
1460
|
+
## Instrumentation and Observability
|
|
1461
|
+
|
|
1462
|
+
Toolkit instrumentation is provider-agnostic. It uses the shared
|
|
1463
|
+
`InstrumentationProvider` contract from `@pie-players/pie-players-shared`.
|
|
1464
|
+
|
|
1465
|
+
### Injection Path
|
|
1466
|
+
|
|
1467
|
+
When the toolkit is hosted by a section or assessment player, the provider
|
|
1468
|
+
comes from the item-player loader config:
|
|
1469
|
+
|
|
1470
|
+
- `runtime.player.loaderConfig.instrumentationProvider`
|
|
1471
|
+
|
|
1472
|
+
### Semantics
|
|
1473
|
+
|
|
1474
|
+
- How an unset, `null` or invalid provider resolves is set out in [Instrumentation providers](../../docs/architecture/instrumentation-providers.md#provider-resolution).
|
|
1475
|
+
- Debug overlays can consume the same stream by composing providers with
|
|
1476
|
+
`CompositeInstrumentationProvider` (for example New Relic + debug panel).
|
|
1477
|
+
- Toolkit telemetry forwarding uses the same provider path, so tool/backend
|
|
1478
|
+
instrumentation is sent to production providers and is visible in debug panel
|
|
1479
|
+
overlays.
|
|
1480
|
+
|
|
1481
|
+
### Toolkit-Owned Canonical Event Stream
|
|
1482
|
+
|
|
1483
|
+
- `pie-toolkit-runtime-owned`
|
|
1484
|
+
- `pie-toolkit-runtime-inherited`
|
|
1485
|
+
- `pie-toolkit-runtime-ready`
|
|
1486
|
+
- `pie-toolkit-ready`
|
|
1487
|
+
- `pie-toolkit-section-ready`
|
|
1488
|
+
- `pie-toolkit-framework-error`
|
|
1489
|
+
|
|
1490
|
+
Toolkit operational events are listed in [Instrumentation providers](../../docs/architecture/instrumentation-providers.md#operational-events).
|
|
1491
|
+
|
|
1492
|
+
The toolkit emits toolkit lifecycle events only; section and assessment events
|
|
1493
|
+
stay in their own layers, so no event has two emitters. The event bridge's
|
|
1494
|
+
optional `dedupeWindowMs` drops repeats within a window and is off by default.
|
|
1582
1495
|
|
|
1583
1496
|
## Section Runtime Engine (advanced)
|
|
1584
1497
|
|
|
1585
1498
|
The section runtime engine is the section player's stage chain: a pure FSM that
|
|
1586
|
-
turns the cohort a layout shows
|
|
1587
|
-
signals the layout derives into
|
|
1499
|
+
turns the cohort a layout shows (its `(sectionId, attemptId)` pair), the
|
|
1500
|
+
controller resolving and the readiness signals the layout derives into
|
|
1501
|
+
`pie-stage-change` and `pie-loading-complete`.
|
|
1588
1502
|
The section player's layout kernel owns one per layout element, and it is the
|
|
1589
1503
|
only stage emitter. `<pie-assessment-toolkit>` emits no stages: its
|
|
1590
1504
|
`SectionControllerBinding` resolves the section's controller, which the toolkit
|
|
@@ -1611,14 +1525,16 @@ before `interactive` ends the chain: the first stage the cohort did not reach is
|
|
|
1611
1525
|
`failed` and the rest up to `interactive` are `skipped`. The section player sets
|
|
1612
1526
|
`runtimeError` from a non-recoverable `framework-error`, so a host waiting on
|
|
1613
1527
|
`engine-ready` or `interactive` always hears an answer. `pie-loading-complete`
|
|
1614
|
-
fires once per cohort,
|
|
1528
|
+
fires once per cohort, on the condition that opens `interactive`: the section
|
|
1529
|
+
controller is ready and the element pre-warm for the current items has
|
|
1530
|
+
resolved. The items load after it; the controller's `section-loading-complete`
|
|
1531
|
+
marks them loaded.
|
|
1615
1532
|
|
|
1616
1533
|
### Common-host wiring example
|
|
1617
1534
|
|
|
1618
1535
|
Most hosts never construct the engine: the section-player layout elements do,
|
|
1619
1536
|
and a host-built section layout takes its engine from
|
|
1620
|
-
`pie-section-player-kernel-host` (
|
|
1621
|
-
[Custom layout authoring](../section-player/README.md#custom-layout-authoring)).
|
|
1537
|
+
`pie-section-player-kernel-host` ([custom section layouts](../../docs/section-player/custom-layouts.md)).
|
|
1622
1538
|
Use the facade for a section renderer outside the section player. The shape
|
|
1623
1539
|
mirrors the section-player kernel:
|
|
1624
1540
|
|
|
@@ -1695,21 +1611,23 @@ in the package's build with a pattern that covers subpaths, not a bare specifier
|
|
|
1695
1611
|
|
|
1696
1612
|
`@pie-players/pie-tool-sign-language` is the worked example end to end: a
|
|
1697
1613
|
registration, a content resolver, its own custom element, and no edit to any
|
|
1698
|
-
generic package.
|
|
1699
|
-
deployment then composes it in, and
|
|
1614
|
+
generic package. The [default-tool-loaders README](../default-tool-loaders/README.md)
|
|
1615
|
+
covers how a deployment then composes it in, and the
|
|
1616
|
+
[Tool Registry Reference](docs/TOOL_REGISTRY.md) the registration and
|
|
1700
1617
|
host-surface contracts.
|
|
1701
1618
|
|
|
1702
|
-
## State
|
|
1619
|
+
## State separation
|
|
1703
1620
|
|
|
1704
|
-
The toolkit
|
|
1621
|
+
The toolkit keeps tool state and PIE session data in separate stores.
|
|
1705
1622
|
|
|
1706
|
-
### Tool
|
|
1623
|
+
### Tool state (ElementToolStateStore)
|
|
1707
1624
|
|
|
1708
|
-
|
|
1625
|
+
Never scored; it leaves the browser only through the coordinator's
|
|
1626
|
+
`saveToolState` hook ([Persistence Integration](#persistence-integration)):
|
|
1709
1627
|
|
|
1710
1628
|
```typescript
|
|
1711
1629
|
{
|
|
1712
|
-
"demo-assessment:section-1:question-1:mc1": {
|
|
1630
|
+
"demo-assessment:section-1:attempt-1:question-1:mc1": {
|
|
1713
1631
|
"answerEliminator": {
|
|
1714
1632
|
"eliminatedChoices": ["choice-b", "choice-d"]
|
|
1715
1633
|
},
|
|
@@ -1726,9 +1644,9 @@ The toolkit enforces a clear separation between ephemeral tool state and persist
|
|
|
1726
1644
|
- Tool preferences
|
|
1727
1645
|
- UI state
|
|
1728
1646
|
|
|
1729
|
-
### PIE
|
|
1647
|
+
### PIE session data
|
|
1730
1648
|
|
|
1731
|
-
|
|
1649
|
+
The host persists it, and its backend scores it:
|
|
1732
1650
|
|
|
1733
1651
|
```typescript
|
|
1734
1652
|
{
|
|
@@ -1748,11 +1666,7 @@ The toolkit enforces a clear separation between ephemeral tool state and persist
|
|
|
1748
1666
|
|
|
1749
1667
|
## Examples
|
|
1750
1668
|
|
|
1751
|
-
See
|
|
1752
|
-
|
|
1753
|
-
- **Three Questions Demo**: Element-level answer eliminator with state persistence
|
|
1754
|
-
- **TTS Integration Demo**: Toolkit coordinator with TTS service
|
|
1755
|
-
- **Paired Passages Demo**: Multi-section assessment with cross-section state
|
|
1669
|
+
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".
|
|
1756
1670
|
|
|
1757
1671
|
## TypeScript Support
|
|
1758
1672
|
|
|
@@ -1776,31 +1690,27 @@ from tool configuration. Two sanitization layers apply:
|
|
|
1776
1690
|
`pie-item-player`. See
|
|
1777
1691
|
[pie-item-player README](../item-player/README.md#content-trust-boundary)
|
|
1778
1692
|
for the `trust-markup` opt-out and the `sanitizeMarkup` override.
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
overwide images surface a horizontal scrollbar instead of being
|
|
1782
|
-
clipped by the section layout's `overflow-x: hidden` ancestors
|
|
1783
|
-
(PIE-94 / WCAG 1.4.10 Reflow at 400% zoom). The wrapper is
|
|
1784
|
-
keyboard-scrollable (`tabindex="0"`, `role="region"`) and carries
|
|
1785
|
-
the image's `alt` text in its `aria-label`; matching CSS lives in
|
|
1786
|
-
`@pie-players/pie-theme` (`components.css`). Authored `<table>`
|
|
1787
|
-
elements outside a `pie-*` custom element get the same treatment via
|
|
1788
|
-
`<div class="pie-table-scroll">` so wide data grids reflow into a
|
|
1789
|
-
horizontally scrollable region; the wrapper's `aria-label` is derived
|
|
1790
|
-
from the table's `aria-label` / `aria-labelledby` / `<caption>`.
|
|
1693
|
+
The player also wraps overwide images and tables in scrollable regions
|
|
1694
|
+
([reflow wrappers](../item-player/README.md#reflow-wrappers)).
|
|
1791
1695
|
- **Tool icons and SSML** - tool-registered icon markup is parsed and
|
|
1792
1696
|
DOMPurified inside the toolbar at render time; SSML payloads are
|
|
1793
1697
|
restricted to an allow-listed subset of SSML tags/attributes before
|
|
1794
1698
|
being forwarded to TTS providers. Do not ship tools that rely on raw
|
|
1795
1699
|
`<script>` or event-handler attributes in their icon strings.
|
|
1796
1700
|
|
|
1701
|
+
[Security](../../docs/security/readme.md) sets out the full trust boundary and
|
|
1702
|
+
the host's obligations.
|
|
1703
|
+
|
|
1797
1704
|
## Related Documentation
|
|
1798
1705
|
|
|
1799
|
-
-
|
|
1800
|
-
-
|
|
1801
|
-
- [
|
|
1706
|
+
- [Tool Registry Reference](docs/TOOL_REGISTRY.md) - Registry-based tool management and AfA PNP 3.0 profile support
|
|
1707
|
+
- [PNP Configuration Guide](docs/PNP_CONFIGURATION.md) - How to configure student profiles, district policies, and governance rules
|
|
1708
|
+
- [Configuring Tools](../../docs/tools-and-accomodations/tool_provider_system.md) - Placement, policy and provider configuration
|
|
1709
|
+
- [Toolkit Tool Host Contract](../../docs/tools-and-accomodations/tool_host_contract.md) - Runtime guarantees between hosts and toolkit-managed tools
|
|
1710
|
+
- [Tools and Accommodations Architecture](../../docs/tools-and-accomodations/architecture.md) - Tool design, scopes and activation
|
|
1711
|
+
- [Assessment toolkit in the architecture overview](../../docs/architecture/architecture.md#assessment-toolkit) - Where the toolkit sits among the players
|
|
1802
1712
|
- [Section Player README](../section-player/README.md) - Section player integration
|
|
1803
|
-
- [Section Player Architecture](../section-player/ARCHITECTURE.md#layered-runtime-engine
|
|
1713
|
+
- [Section Player Architecture](../section-player/ARCHITECTURE.md#layered-runtime-engine) - Layered runtime engine, kernel/toolkit wiring, lifecycle emit invariant
|
|
1804
1714
|
- [Framework-Owned Error Handling](../../docs/tools-and-accomodations/framework-owned-error-handling.md) - Canonical framework error model/events and fallback behavior
|
|
1805
1715
|
- [Safe Custom Tool Configuration](../../docs/tools-and-accomodations/safe-custom-tool-config.md) - Host-side config patterns and validation guidance
|
|
1806
1716
|
- [Architecture Overview](../../docs/architecture/architecture.md) - Complete system architecture
|