@pie-players/pie-assessment-toolkit 0.2.5 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/README.md +200 -67
  2. package/dist/attempt/TestSession.d.ts +23 -21
  3. package/dist/attempt/TestSession.d.ts.map +1 -1
  4. package/dist/attempt/TestSession.js +17 -15
  5. package/dist/attempt/TestSession.js.map +1 -1
  6. package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +50 -0
  7. package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts.map +1 -0
  8. package/dist/attempt/adapters/activity-to-test-attempt-session.js +135 -0
  9. package/dist/attempt/adapters/activity-to-test-attempt-session.js.map +1 -0
  10. package/dist/components/ItemToolBar.custom-element.js +628 -0
  11. package/dist/components/PieAssessmentToolkit.custom-element.js +670 -0
  12. package/dist/components/item-toolbar-element.d.ts +3 -0
  13. package/dist/components/item-toolbar-element.d.ts.map +1 -0
  14. package/dist/components/item-toolbar-element.js +2 -0
  15. package/dist/components/item-toolbar-element.js.map +1 -0
  16. package/dist/components/pie-assessment-toolkit-element.d.ts +3 -0
  17. package/dist/components/pie-assessment-toolkit-element.d.ts.map +1 -0
  18. package/dist/components/pie-assessment-toolkit-element.js +2 -0
  19. package/dist/components/pie-assessment-toolkit-element.js.map +1 -0
  20. package/dist/context/assessment-toolkit-context.d.ts +52 -0
  21. package/dist/context/assessment-toolkit-context.d.ts.map +1 -0
  22. package/dist/context/assessment-toolkit-context.js +6 -0
  23. package/dist/context/assessment-toolkit-context.js.map +1 -0
  24. package/dist/context/runtime-context-consumer.d.ts +14 -0
  25. package/dist/context/runtime-context-consumer.d.ts.map +1 -0
  26. package/dist/context/runtime-context-consumer.js +52 -0
  27. package/dist/context/runtime-context-consumer.js.map +1 -0
  28. package/dist/index.d.ts +53 -42
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +34 -32
  31. package/dist/index.js.map +1 -1
  32. package/dist/runtime/RuntimeRegistry.d.ts +13 -0
  33. package/dist/runtime/RuntimeRegistry.d.ts.map +1 -0
  34. package/dist/runtime/RuntimeRegistry.js +50 -0
  35. package/dist/runtime/RuntimeRegistry.js.map +1 -0
  36. package/dist/runtime/SectionRuntimeEngine.d.ts +45 -0
  37. package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -0
  38. package/dist/runtime/SectionRuntimeEngine.js +81 -0
  39. package/dist/runtime/SectionRuntimeEngine.js.map +1 -0
  40. package/dist/runtime/registration-events.d.ts +19 -0
  41. package/dist/runtime/registration-events.d.ts.map +1 -0
  42. package/dist/runtime/registration-events.js +4 -0
  43. package/dist/runtime/registration-events.js.map +1 -0
  44. package/dist/runtime/runtime-event-guards.d.ts +3 -0
  45. package/dist/runtime/runtime-event-guards.d.ts.map +1 -0
  46. package/dist/runtime/runtime-event-guards.js +9 -0
  47. package/dist/runtime/runtime-event-guards.js.map +1 -0
  48. package/dist/runtime/tool-host-contract.d.ts +30 -0
  49. package/dist/runtime/tool-host-contract.d.ts.map +1 -0
  50. package/dist/runtime/tool-host-contract.js +48 -0
  51. package/dist/runtime/tool-host-contract.js.map +1 -0
  52. package/dist/services/HighlightCoordinator.d.ts +2 -2
  53. package/dist/services/HighlightCoordinator.d.ts.map +1 -1
  54. package/dist/services/HighlightCoordinator.js +1 -1
  55. package/dist/services/HighlightCoordinator.js.map +1 -1
  56. package/dist/services/I18nService.js +1 -1
  57. package/dist/services/I18nService.js.map +1 -1
  58. package/dist/services/PNPToolResolver.d.ts +2 -2
  59. package/dist/services/PNPToolResolver.d.ts.map +1 -1
  60. package/dist/services/PNPToolResolver.js +1 -1
  61. package/dist/services/PNPToolResolver.js.map +1 -1
  62. package/dist/services/TTSService.d.ts +5 -3
  63. package/dist/services/TTSService.d.ts.map +1 -1
  64. package/dist/services/TTSService.js +13 -12
  65. package/dist/services/TTSService.js.map +1 -1
  66. package/dist/services/ThemeProvider.d.ts +2 -1
  67. package/dist/services/ThemeProvider.d.ts.map +1 -1
  68. package/dist/services/ThemeProvider.js +82 -46
  69. package/dist/services/ThemeProvider.js.map +1 -1
  70. package/dist/services/ToolConfigResolver.d.ts +1 -1
  71. package/dist/services/ToolConfigResolver.js +1 -1
  72. package/dist/services/ToolCoordinator.d.ts +2 -2
  73. package/dist/services/ToolCoordinator.d.ts.map +1 -1
  74. package/dist/services/ToolCoordinator.js +1 -1
  75. package/dist/services/ToolCoordinator.js.map +1 -1
  76. package/dist/services/ToolRegistry.d.ts +66 -69
  77. package/dist/services/ToolRegistry.d.ts.map +1 -1
  78. package/dist/services/ToolRegistry.js +72 -22
  79. package/dist/services/ToolRegistry.js.map +1 -1
  80. package/dist/services/ToolkitCoordinator.d.ts +112 -14
  81. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  82. package/dist/services/ToolkitCoordinator.js +508 -77
  83. package/dist/services/ToolkitCoordinator.js.map +1 -1
  84. package/dist/services/createDefaultToolRegistry.d.ts +15 -10
  85. package/dist/services/createDefaultToolRegistry.d.ts.map +1 -1
  86. package/dist/services/createDefaultToolRegistry.js +13 -17
  87. package/dist/services/createDefaultToolRegistry.js.map +1 -1
  88. package/dist/services/defaultPersonalNeedsProfile.d.ts +4 -0
  89. package/dist/services/defaultPersonalNeedsProfile.d.ts.map +1 -0
  90. package/dist/services/defaultPersonalNeedsProfile.js +25 -0
  91. package/dist/services/defaultPersonalNeedsProfile.js.map +1 -0
  92. package/dist/services/interfaces.d.ts +67 -7
  93. package/dist/services/interfaces.d.ts.map +1 -1
  94. package/dist/services/interfaces.js.map +1 -1
  95. package/dist/services/pnp-standard-features.d.ts +3 -4
  96. package/dist/services/pnp-standard-features.d.ts.map +1 -1
  97. package/dist/services/pnp-standard-features.js +0 -3
  98. package/dist/services/pnp-standard-features.js.map +1 -1
  99. package/dist/services/section-controller-types.d.ts +32 -0
  100. package/dist/services/section-controller-types.d.ts.map +1 -0
  101. package/dist/services/section-controller-types.js +2 -0
  102. package/dist/services/section-controller-types.js.map +1 -0
  103. package/dist/services/tool-context.d.ts +4 -4
  104. package/dist/services/tool-context.d.ts.map +1 -1
  105. package/dist/services/tool-context.js +106 -17
  106. package/dist/services/tool-context.js.map +1 -1
  107. package/dist/services/tool-instance-id.d.ts +17 -0
  108. package/dist/services/tool-instance-id.d.ts.map +1 -0
  109. package/dist/services/tool-instance-id.js +61 -0
  110. package/dist/services/tool-instance-id.js.map +1 -0
  111. package/dist/services/tool-providers/DesmosToolProvider.d.ts +1 -1
  112. package/dist/services/tool-providers/DesmosToolProvider.d.ts.map +1 -1
  113. package/dist/services/tool-providers/IToolProvider.d.ts +2 -3
  114. package/dist/services/tool-providers/IToolProvider.d.ts.map +1 -1
  115. package/dist/services/tool-providers/IToolProvider.js +1 -2
  116. package/dist/services/tool-providers/IToolProvider.js.map +1 -1
  117. package/dist/services/tool-providers/MathJsToolProvider.d.ts +25 -0
  118. package/dist/services/tool-providers/MathJsToolProvider.d.ts.map +1 -0
  119. package/dist/services/tool-providers/MathJsToolProvider.js +53 -0
  120. package/dist/services/tool-providers/MathJsToolProvider.js.map +1 -0
  121. package/dist/services/tool-providers/TIToolProvider.d.ts +1 -1
  122. package/dist/services/tool-providers/TIToolProvider.d.ts.map +1 -1
  123. package/dist/services/tool-providers/TIToolProvider.js +1 -1
  124. package/dist/services/tool-providers/TIToolProvider.js.map +1 -1
  125. package/dist/services/tool-providers/TTSToolProvider.d.ts +1 -1
  126. package/dist/services/tool-providers/TTSToolProvider.d.ts.map +1 -1
  127. package/dist/services/tool-providers/TTSToolProvider.js +1 -1
  128. package/dist/services/tool-providers/TTSToolProvider.js.map +1 -1
  129. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +1 -1
  130. package/dist/services/tool-providers/ToolProviderRegistry.d.ts.map +1 -1
  131. package/dist/services/tool-providers/index.d.ts +11 -9
  132. package/dist/services/tool-providers/index.d.ts.map +1 -1
  133. package/dist/services/tool-providers/index.js +5 -4
  134. package/dist/services/tool-providers/index.js.map +1 -1
  135. package/dist/services/tools-config-normalizer.d.ts +26 -0
  136. package/dist/services/tools-config-normalizer.d.ts.map +1 -0
  137. package/dist/services/tools-config-normalizer.js +63 -0
  138. package/dist/services/tools-config-normalizer.js.map +1 -0
  139. package/dist/services/tts/provider-interface.d.ts +1 -1
  140. package/dist/services/tts/provider-interface.d.ts.map +1 -1
  141. package/dist/tools/calculators/desmos-provider.js +1 -1
  142. package/dist/tools/calculators/desmos-provider.js.map +1 -1
  143. package/dist/tools/calculators/mathjs-provider.js +1 -1
  144. package/dist/tools/calculators/mathjs-provider.js.map +1 -1
  145. package/dist/tools/calculators/ti-provider.d.ts.map +1 -1
  146. package/dist/tools/calculators/ti-provider.js +127 -4
  147. package/dist/tools/calculators/ti-provider.js.map +1 -1
  148. package/dist/tools/client.d.ts +9 -8
  149. package/dist/tools/client.d.ts.map +1 -1
  150. package/dist/tools/client.js +9 -8
  151. package/dist/tools/client.js.map +1 -1
  152. package/dist/tools/default-tool-module-loaders.d.ts +9 -0
  153. package/dist/tools/default-tool-module-loaders.d.ts.map +1 -0
  154. package/dist/tools/default-tool-module-loaders.js +17 -0
  155. package/dist/tools/default-tool-module-loaders.js.map +1 -0
  156. package/dist/tools/index.d.ts +4 -3
  157. package/dist/tools/index.d.ts.map +1 -1
  158. package/dist/tools/index.js +4 -3
  159. package/dist/tools/index.js.map +1 -1
  160. package/dist/tools/library-loader.d.ts +2 -2
  161. package/dist/tools/library-loader.d.ts.map +1 -1
  162. package/dist/tools/library-loader.js +7 -2
  163. package/dist/tools/library-loader.js.map +1 -1
  164. package/dist/tools/registrations/accessibility-tools.d.ts +2 -10
  165. package/dist/tools/registrations/accessibility-tools.d.ts.map +1 -1
  166. package/dist/tools/registrations/accessibility-tools.js +97 -115
  167. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  168. package/dist/tools/registrations/calculator.d.ts +3 -3
  169. package/dist/tools/registrations/calculator.d.ts.map +1 -1
  170. package/dist/tools/registrations/calculator.js +35 -56
  171. package/dist/tools/registrations/calculator.js.map +1 -1
  172. package/dist/tools/registrations/interaction-tools.d.ts +1 -1
  173. package/dist/tools/registrations/interaction-tools.d.ts.map +1 -1
  174. package/dist/tools/registrations/interaction-tools.js +79 -54
  175. package/dist/tools/registrations/interaction-tools.js.map +1 -1
  176. package/dist/tools/registrations/measurement-tools.d.ts +1 -1
  177. package/dist/tools/registrations/measurement-tools.d.ts.map +1 -1
  178. package/dist/tools/registrations/measurement-tools.js +65 -40
  179. package/dist/tools/registrations/measurement-tools.js.map +1 -1
  180. package/dist/tools/registrations/subject-specific-tools.d.ts +1 -1
  181. package/dist/tools/registrations/subject-specific-tools.d.ts.map +1 -1
  182. package/dist/tools/registrations/subject-specific-tools.js +65 -40
  183. package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
  184. package/dist/tools/registrations/tts.d.ts +2 -2
  185. package/dist/tools/registrations/tts.d.ts.map +1 -1
  186. package/dist/tools/registrations/tts.js +28 -42
  187. package/dist/tools/registrations/tts.js.map +1 -1
  188. package/dist/tools/response-discovery.d.ts +13 -15
  189. package/dist/tools/response-discovery.d.ts.map +1 -1
  190. package/dist/tools/response-discovery.js +23 -80
  191. package/dist/tools/response-discovery.js.map +1 -1
  192. package/dist/tools/tool-coordinator.d.ts +1 -1
  193. package/dist/tools/tool-coordinator.d.ts.map +1 -1
  194. package/dist/tools/tool-tag-map.d.ts +4 -4
  195. package/dist/tools/tool-tag-map.d.ts.map +1 -1
  196. package/dist/tools/tool-tag-map.js +2 -3
  197. package/dist/tools/tool-tag-map.js.map +1 -1
  198. package/dist/tools/types.d.ts +3 -1
  199. package/dist/tools/types.d.ts.map +1 -1
  200. package/dist/tools/variant-resolver.d.ts +1 -1
  201. package/dist/tools/variant-resolver.d.ts.map +1 -1
  202. package/package.json +25 -28
  203. package/src/README.md +16 -32
  204. package/src/components/ItemToolBar.svelte +489 -0
  205. package/src/components/PNPProvenanceViewer.svelte +14 -4
  206. package/src/components/PieAssessmentToolkit.svelte +517 -0
  207. package/src/components/ToolButton.svelte +11 -11
  208. package/src/components/ToolButtonGroup.svelte +27 -11
  209. package/src/tools/README.md +6 -5
  210. package/src/tools/calculators/README.md +4 -4
  211. package/dist/player/AssessmentPlayer.d.ts +0 -397
  212. package/dist/player/AssessmentPlayer.d.ts.map +0 -1
  213. package/dist/player/AssessmentPlayer.js +0 -974
  214. package/dist/player/AssessmentPlayer.js.map +0 -1
  215. package/dist/player/index.d.ts +0 -17
  216. package/dist/player/index.d.ts.map +0 -1
  217. package/dist/player/index.js +0 -15
  218. package/dist/player/index.js.map +0 -1
  219. package/dist/player/navigation-types.d.ts +0 -63
  220. package/dist/player/navigation-types.d.ts.map +0 -1
  221. package/dist/player/navigation-types.js +0 -7
  222. package/dist/player/navigation-types.js.map +0 -1
  223. package/dist/player/qti-navigation.d.ts +0 -29
  224. package/dist/player/qti-navigation.d.ts.map +0 -1
  225. package/dist/player/qti-navigation.js +0 -160
  226. package/dist/player/qti-navigation.js.map +0 -1
  227. package/dist/reference-layout/index.d.ts +0 -19
  228. package/dist/reference-layout/index.d.ts.map +0 -1
  229. package/dist/reference-layout/index.js +0 -20
  230. package/dist/reference-layout/index.js.map +0 -1
  231. package/dist/services/AnnotationToolbarConfig.d.ts +0 -134
  232. package/dist/services/AnnotationToolbarConfig.d.ts.map +0 -1
  233. package/dist/services/AnnotationToolbarConfig.js +0 -100
  234. package/dist/services/AnnotationToolbarConfig.js.map +0 -1
  235. package/dist/services/AssessmentAuthoringService.d.ts +0 -60
  236. package/dist/services/AssessmentAuthoringService.d.ts.map +0 -1
  237. package/dist/services/AssessmentAuthoringService.js +0 -183
  238. package/dist/services/AssessmentAuthoringService.js.map +0 -1
  239. package/dist/services/PNPMapper.d.ts +0 -81
  240. package/dist/services/PNPMapper.d.ts.map +0 -1
  241. package/dist/services/PNPMapper.js +0 -108
  242. package/dist/services/PNPMapper.js.map +0 -1
  243. package/src/components/PNPProfileTester.example.svelte +0 -188
  244. package/src/components/PNPProfileTester.svelte +0 -397
  245. package/src/components/QuestionToolBar.svelte +0 -483
  246. package/src/player/AssessmentLayout.svelte +0 -68
  247. package/src/player/README.md +0 -154
  248. package/src/reference-layout/README.md +0 -135
  249. package/src/reference-layout/ReferenceLayout.svelte +0 -201
  250. package/src/reference-layout/components/AssessmentContent.svelte +0 -259
  251. package/src/reference-layout/components/AssessmentFooter.svelte +0 -89
  252. package/src/reference-layout/components/AssessmentHeader.svelte +0 -250
  253. package/src/reference-layout/components/AssessmentNavigation.svelte +0 -134
  254. package/src/reference-layout/components/AssessmentToolsBar.svelte +0 -176
  255. package/src/reference-layout/components/ItemPanel.svelte +0 -200
  256. package/src/reference-layout/components/NotesPanel.svelte +0 -179
  257. package/src/reference-layout/components/PassagePanel.svelte +0 -76
package/src/README.md CHANGED
@@ -76,18 +76,13 @@ sectionPlayer.catalogResolver = catalogResolver;
76
76
  sectionPlayer.section = section;
77
77
  ```
78
78
 
79
- ### ✅ Reference Implementation (Future)
79
+ ### ✅ Runtime Integration
80
80
 
81
- An optional **AssessmentPlayer** reference implementation may be provided for multi-section assessments. It would:
82
-
83
- - Manage navigation across sections
84
- - Coordinate section player instances
85
- - Provide assessment-level state management
86
- - But delegate to section players for rendering
81
+ Use this toolkit package for shared services, section-player orchestration, and host-app integrations.
87
82
 
88
83
  ## Project Structure
89
84
 
90
- ```
85
+ ```text
91
86
  assessment-toolkit/
92
87
  ├── core/
93
88
  │ └── TypedEventBus.ts # Event system
@@ -202,10 +197,10 @@ const studentProfile: StudentAccommodations = {
202
197
  accommodations: ['tts', 'calculator', 'extended-time']
203
198
  };
204
199
 
205
- // Test blocks dictionary but allows calculator
200
+ // Test blocks lineReader but allows calculator
206
201
  const rosterConfig: RosterToolConfig = {
207
202
  calculator: "1",
208
- dictionary: "0"
203
+ lineReader: "0"
209
204
  };
210
205
 
211
206
  // Current item requires scientific calculator
@@ -226,7 +221,7 @@ const resolved = resolver.resolveAll({
226
221
  // Result:
227
222
  // - calculator: enabled (scientific, required by item)
228
223
  // - tts: enabled (student accommodation)
229
- // - dictionary: disabled (blocked by roster)
224
+ // - lineReader: disabled (blocked by roster)
230
225
  ```
231
226
 
232
227
  ## Integration Patterns
@@ -374,8 +369,6 @@ await ttsService.updateSettings({
374
369
  });
375
370
  ```
376
371
 
377
- **Unified Settings UI**: A pre-built settings component is available in `section-demos/src/lib/components/AssessmentToolkitSettings.svelte` that provides a tabbed interface for configuring TTS, highlighting, and other toolkit features. See the component's README for integration details.
378
-
379
372
  ### AccessibilityCatalogResolver
380
373
 
381
374
  Manages QTI 3.0 accessibility catalogs (SSML, sign language, braille, simplified language, etc.).
@@ -434,6 +427,7 @@ catalogResolver.addItemCatalogs(result.catalogs);
434
427
  ```
435
428
 
436
429
  **Example Input:**
430
+
437
431
  ```typescript
438
432
  {
439
433
  prompt: `<div>
@@ -444,6 +438,7 @@ catalogResolver.addItemCatalogs(result.catalogs);
444
438
  ```
445
439
 
446
440
  **Example Output:**
441
+
447
442
  ```typescript
448
443
  {
449
444
  // Cleaned config
@@ -513,7 +508,7 @@ The toolkit natively supports QTI 3.0 features for standards-compliant assessmen
513
508
  Student accommodations and IEP/504 support with automatic tool resolution:
514
509
 
515
510
  ```typescript
516
- import { AssessmentPlayer, PNPToolResolver } from '@pie-players/pie-assessment-toolkit';
511
+ import { PNPToolResolver } from '@pie-players/pie-assessment-toolkit';
517
512
 
518
513
  // QTI 3.0 assessment with PNP
519
514
  const assessment = {
@@ -535,9 +530,6 @@ const assessment = {
535
530
  }
536
531
  };
537
532
 
538
- // Simple initialization - tools automatically resolved
539
- const player = new AssessmentPlayer({ assessment, loadItem });
540
-
541
533
  // Or use PNPToolResolver directly
542
534
  const resolver = new PNPToolResolver();
543
535
  const tools = resolver.resolveTools(assessment, currentItemRef);
@@ -553,7 +545,6 @@ const tools = resolver.resolveTools(assessment, currentItemRef);
553
545
  'protractor' → 'protractor'
554
546
  'highlighter' → 'highlighter'
555
547
  'lineReader' → 'lineReader'
556
- 'magnifier' → 'magnifier'
557
548
  'colorContrast' → 'colorScheme'
558
549
  'answerMasking' → 'answerEliminator'
559
550
  ```
@@ -563,8 +554,11 @@ and can be overridden via `createDefaultToolRegistry(...)`:
563
554
 
564
555
  ```typescript
565
556
  import { createDefaultToolRegistry } from '@pie-players/pie-assessment-toolkit';
557
+ import { DEFAULT_TOOL_MODULE_LOADERS } from '@pie-players/pie-default-tool-loaders';
566
558
 
567
559
  const toolRegistry = createDefaultToolRegistry({
560
+ // Lazy tool module loading (recommended)
561
+ toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
568
562
  toolTagMap: {
569
563
  calculator: 'my-calculator-tool',
570
564
  textToSpeech: 'my-tts-tool'
@@ -577,7 +571,7 @@ const toolRegistry = createDefaultToolRegistry({
577
571
  Global variables shared across assessment items:
578
572
 
579
573
  ```typescript
580
- import { AssessmentPlayer, ContextVariableStore } from '@pie-players/pie-assessment-toolkit';
574
+ import { ContextVariableStore } from '@pie-players/pie-assessment-toolkit';
581
575
 
582
576
  // Assessment with context declarations
583
577
  const assessment = {
@@ -597,17 +591,7 @@ const assessment = {
597
591
  ]
598
592
  };
599
593
 
600
- // Use with AssessmentPlayer
601
- const player = new AssessmentPlayer({ assessment, loadItem });
602
-
603
- const seed = player.getContextVariable('RANDOM_SEED');
604
- player.setContextVariable('DIFFICULTY_LEVEL', 'hard');
605
-
606
- // Pass to PIE elements
607
- const context = player.getContextVariables();
608
- await renderItem(item, session, context);
609
-
610
- // Or use ContextVariableStore directly
594
+ // Use ContextVariableStore directly
611
595
  const store = new ContextVariableStore(assessment.contextDeclarations);
612
596
  store.set('RANDOM_SEED', 12345);
613
597
  const context = store.toObject();
@@ -624,7 +608,7 @@ const context = store.toObject();
624
608
 
625
609
  Tool resolution follows this precedence (highest to lowest):
626
610
 
627
- ```
611
+ ```text
628
612
  1. District Block (absolute veto)
629
613
  2. Test Administration Override
630
614
  3. Item Restriction (per-item block)
@@ -636,5 +620,5 @@ Tool resolution follows this precedence (highest to lowest):
636
620
  ## Related Documentation
637
621
 
638
622
  - [Architecture Overview](../../../docs/ARCHITECTURE.md) - Complete system architecture
639
- - [Assessment Toolkit Architecture](../../../docs/tools-and-accomodations/assessment-player-architecture.md) - Toolkit design
623
+ - [Assessment Toolkit Architecture](../../../docs/tools-and-accomodations/tools-high-level-architecture.md) - Toolkit design
640
624
  - [Tools Architecture](../../../docs/tools-and-accomodations/tools-high-level-architecture.md) - Tool coordination
@@ -0,0 +1,489 @@
1
+ <svelte:options
2
+ customElement={{
3
+ tag: 'pie-item-toolbar',
4
+ shadow: 'open',
5
+ props: {
6
+ itemId: { type: 'String', attribute: 'item-id' },
7
+ catalogId: { type: 'String', attribute: 'catalog-id' },
8
+ tools: { type: 'String', attribute: 'tools' },
9
+ contentKind: { type: 'String', attribute: 'content-kind' },
10
+ size: { type: 'String', attribute: 'size' },
11
+ language: { type: 'String', attribute: 'language' },
12
+
13
+ // Local runtime contracts (passed as JS properties, not attributes)
14
+ scopeElement: { type: 'Object', reflect: false },
15
+ toolRegistry: { type: 'Object', reflect: false },
16
+ pnpResolver: { type: 'Object', reflect: false },
17
+ assessment: { type: 'Object', reflect: false },
18
+ itemRef: { type: 'Object', reflect: false },
19
+ item: { type: 'Object', reflect: false }
20
+ }
21
+ }}
22
+ />
23
+
24
+ <!--
25
+ ItemToolBar - Inline toolbar for item/passage headers
26
+
27
+ Button visibility is always registry-driven:
28
+ - Pass 1: allowed tool IDs (PNP resolver when available, otherwise explicit tools prop)
29
+ - Pass 2: tool-owned isVisibleInContext(context)
30
+ -->
31
+ <script lang="ts">
32
+ import {
33
+ type AssessmentToolkitShellContext,
34
+ type AssessmentToolkitRuntimeContext
35
+ } from '../context/assessment-toolkit-context.js';
36
+ import {
37
+ connectAssessmentToolkitRuntimeContext,
38
+ connectAssessmentToolkitShellContext,
39
+ } from '../context/runtime-context-consumer.js';
40
+ import type { ToolRegistry, ToolToolbarRenderResult, ToolbarContext } from '../services/ToolRegistry.js';
41
+ import type { PNPToolResolver } from '../services/PNPToolResolver.js';
42
+ import { createDefaultToolRegistry } from '../services/createDefaultToolRegistry.js';
43
+ import { DEFAULT_TOOL_MODULE_LOADERS } from '../tools/default-tool-module-loaders.js';
44
+ import {
45
+ normalizeToolsConfig,
46
+ parseToolList,
47
+ resolveToolsForLevel,
48
+ } from '../services/tools-config-normalizer.js';
49
+ import { createScopedToolId, parseScopedToolId } from '../services/tool-instance-id.js';
50
+ import type { AssessmentEntity, AssessmentItemRef, ItemEntity } from '@pie-players/pie-players-shared/types';
51
+ import type { ElementToolContext, ItemToolContext } from '../services/tool-context.js';
52
+
53
+ const isBrowser = typeof window !== 'undefined';
54
+ const fallbackToolRegistry = createDefaultToolRegistry({
55
+ toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS
56
+ });
57
+
58
+ let activeToolState = $state<Record<string, boolean>>({});
59
+
60
+ // Props
61
+ let {
62
+ itemId = '',
63
+ catalogId = '',
64
+ tools = 'calculator,textToSpeech,answerEliminator',
65
+ contentKind = 'assessment-item',
66
+ scopeElement = null,
67
+ toolRegistry = null,
68
+ pnpResolver = null,
69
+ assessment = null,
70
+ itemRef = null,
71
+ item = null,
72
+ class: className = '',
73
+ size = 'md' as 'sm' | 'md' | 'lg',
74
+ language = 'en-US'
75
+ }: {
76
+ itemId?: string;
77
+ catalogId?: string;
78
+ tools?: string;
79
+ contentKind?: string;
80
+ scopeElement?: HTMLElement | null;
81
+ toolRegistry?: ToolRegistry | null;
82
+ pnpResolver?: PNPToolResolver | null;
83
+ assessment?: AssessmentEntity | null;
84
+ itemRef?: AssessmentItemRef | null;
85
+ item?: ItemEntity | null;
86
+ class?: string;
87
+ size?: 'sm' | 'md' | 'lg';
88
+ language?: string;
89
+ } = $props();
90
+
91
+ let toolbarRootElement = $state<HTMLDivElement | null>(null);
92
+ let runtimeContext = $state<AssessmentToolkitRuntimeContext | null>(null);
93
+ let shellContext = $state<AssessmentToolkitShellContext | null>(null);
94
+ let moduleLoadVersion = $state(0);
95
+
96
+ $effect(() => {
97
+ if (!toolbarRootElement) return;
98
+ return connectAssessmentToolkitRuntimeContext(toolbarRootElement, (value) => {
99
+ runtimeContext = value;
100
+ });
101
+ });
102
+
103
+ $effect(() => {
104
+ if (!toolbarRootElement) return;
105
+ return connectAssessmentToolkitShellContext(toolbarRootElement, (value) => {
106
+ shellContext = value;
107
+ });
108
+ });
109
+
110
+ const effectiveToolCoordinator = $derived(runtimeContext?.toolCoordinator);
111
+ const effectiveTTSService = $derived(runtimeContext?.ttsService);
112
+ const effectiveElementToolStateStore = $derived(runtimeContext?.elementToolStateStore);
113
+ const effectiveAssessmentId = $derived(runtimeContext?.assessmentId ?? '');
114
+ const effectiveSectionId = $derived(runtimeContext?.sectionId ?? '');
115
+ const effectiveItemId = $derived(itemId || shellContext?.itemId || '');
116
+ const effectiveCanonicalItemId = $derived(shellContext?.canonicalItemId || effectiveItemId);
117
+ const effectiveCatalogId = $derived(catalogId || effectiveItemId);
118
+ const effectiveContentKind = $derived(contentKind || shellContext?.contentKind || 'assessment-item');
119
+ const effectiveItem = $derived((item as ItemEntity | null) || (shellContext?.item as ItemEntity | null) || null);
120
+
121
+ // Effective registry for visibility and metadata ownership.
122
+ const effectiveToolRegistry = $derived(toolRegistry || fallbackToolRegistry);
123
+
124
+ const explicitTools = $derived(parseToolList(tools));
125
+ const normalizedExplicitTools = $derived(
126
+ effectiveToolRegistry.normalizeToolIds(explicitTools).filter(Boolean)
127
+ );
128
+ const effectiveToolsConfig = $derived.by(() => {
129
+ const coordinatorConfig = runtimeContext?.toolkitCoordinator?.config?.tools as any;
130
+ return normalizeToolsConfig(coordinatorConfig || {});
131
+ });
132
+ const placementLevel = $derived.by(() =>
133
+ effectiveContentKind === 'rubric-block-stimulus' ? 'passage' : 'item'
134
+ );
135
+ const placementAllowedToolIds = $derived.by(() => {
136
+ const resolved = resolveToolsForLevel(effectiveToolsConfig, placementLevel);
137
+ return effectiveToolRegistry.normalizeToolIds(resolved).filter(Boolean);
138
+ });
139
+
140
+ // Pass 1: determine allowed tools
141
+ const allowedToolIds = $derived.by(() => {
142
+ const configuredTools =
143
+ normalizedExplicitTools.length > 0 ? normalizedExplicitTools : placementAllowedToolIds;
144
+ if (pnpResolver && assessment && itemRef) {
145
+ const allowedByPnp = pnpResolver.getAllowedToolIds(assessment, itemRef);
146
+ if (configuredTools.length === 0) return allowedByPnp;
147
+ return allowedByPnp.filter((toolId) => configuredTools.includes(toolId));
148
+ }
149
+ return configuredTools;
150
+ });
151
+
152
+ const contentReady = $derived.by(() => {
153
+ const config = (effectiveItem as ItemEntity | null)?.config;
154
+ return !!(effectiveItem && config && typeof config === 'object');
155
+ });
156
+
157
+ const toolContext = $derived.by((): ItemToolContext | null => {
158
+ if (!contentReady || !effectiveItem) {
159
+ return null;
160
+ }
161
+ return {
162
+ level: 'item',
163
+ assessment: (assessment || {}) as AssessmentEntity,
164
+ itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
165
+ item: effectiveItem as ItemEntity
166
+ };
167
+ });
168
+
169
+ const renderContext = $derived.by((): ItemToolContext => {
170
+ if (toolContext) return toolContext;
171
+ return {
172
+ level: 'item',
173
+ assessment: (assessment || {}) as AssessmentEntity,
174
+ itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
175
+ item: ((effectiveItem as ItemEntity | null) || ({ id: effectiveCanonicalItemId, config: {} } as ItemEntity)) as ItemEntity
176
+ };
177
+ });
178
+
179
+ const elementContexts = $derived.by((): ElementToolContext[] => {
180
+ if (!contentReady || !effectiveItem) return [];
181
+ const modelsRaw = (effectiveItem as any)?.config?.models;
182
+ const models = Array.isArray(modelsRaw)
183
+ ? modelsRaw
184
+ : modelsRaw && typeof modelsRaw === 'object'
185
+ ? Object.values(modelsRaw)
186
+ : [];
187
+ return models
188
+ .filter((model: any) => model && typeof model === 'object' && typeof model.id === 'string')
189
+ .map((model: any) => ({
190
+ level: 'element' as const,
191
+ assessment: (assessment || {}) as AssessmentEntity,
192
+ itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
193
+ item: effectiveItem as ItemEntity,
194
+ elementId: model.id as string
195
+ }));
196
+ });
197
+
198
+ // Pass 2: tool-owned context filtering (item + element aggregation)
199
+ const visibleToolIds = $derived.by(() => {
200
+ if (!contentReady) {
201
+ // Stage 1: orchestrator-level allow-list only.
202
+ return allowedToolIds;
203
+ }
204
+ if (!toolContext && elementContexts.length === 0) {
205
+ return allowedToolIds;
206
+ }
207
+ const visible = new Set<string>();
208
+ if (toolContext) {
209
+ effectiveToolRegistry.filterVisibleInContext(allowedToolIds, toolContext).forEach((tool) => visible.add(tool.toolId));
210
+ }
211
+
212
+ for (const context of elementContexts) {
213
+ effectiveToolRegistry.filterVisibleInContext(allowedToolIds, context).forEach((tool) => visible.add(tool.toolId));
214
+ }
215
+
216
+ return Array.from(visible);
217
+ });
218
+
219
+ // Dynamically load whatever tools are currently visible.
220
+ // The registry owns module loader configuration by toolId.
221
+ $effect(() => {
222
+ if (!isBrowser) return;
223
+ let cancelled = false;
224
+ void effectiveToolRegistry
225
+ .ensureToolModulesLoaded(visibleToolIds)
226
+ .then(() => {
227
+ if (!cancelled) moduleLoadVersion += 1;
228
+ })
229
+ .catch((error: unknown) => {
230
+ console.error('[ItemToolBar] Failed to load one or more tool modules:', error);
231
+ });
232
+ return () => {
233
+ cancelled = true;
234
+ };
235
+ });
236
+
237
+ function resolveScopeElement(): HTMLElement | null {
238
+ if (scopeElement) return scopeElement;
239
+ return shellContext?.scopeElement || null;
240
+ }
241
+
242
+ function resolveGlobalElementId(): string | null {
243
+ if (!effectiveElementToolStateStore || !effectiveAssessmentId || !effectiveSectionId || !effectiveCanonicalItemId)
244
+ return null;
245
+ return effectiveElementToolStateStore.getGlobalElementId(
246
+ effectiveAssessmentId,
247
+ effectiveSectionId,
248
+ effectiveCanonicalItemId,
249
+ effectiveCanonicalItemId
250
+ );
251
+ }
252
+
253
+ const toolbarContext = $derived.by((): ToolbarContext => {
254
+ const toolkitCoordinator = runtimeContext?.toolkitCoordinator || null;
255
+ const toInstanceToolId = (toolId: string): string => {
256
+ // Accept either base tool IDs (e.g. "calculator") or already-scoped IDs.
257
+ // This keeps toolbar contracts stable across mixed registration implementations.
258
+ return parseScopedToolId(toolId)
259
+ ? toolId
260
+ : createScopedToolId(toolId, 'item', effectiveCanonicalItemId);
261
+ };
262
+ return {
263
+ itemId: effectiveCanonicalItemId,
264
+ catalogId: effectiveCatalogId,
265
+ language,
266
+ ui: {
267
+ size
268
+ },
269
+ getScopeElement: resolveScopeElement,
270
+ getGlobalElementId: resolveGlobalElementId,
271
+ toolCoordinator: effectiveToolCoordinator || null,
272
+ toolkitCoordinator,
273
+ ttsService: effectiveTTSService || null,
274
+ elementToolStateStore: effectiveElementToolStateStore || null,
275
+ toggleTool: (toolId: string) => {
276
+ if (!effectiveToolCoordinator) return;
277
+ effectiveToolCoordinator.toggleTool(toInstanceToolId(toolId));
278
+ },
279
+ isToolVisible: (toolId: string) => {
280
+ if (!effectiveToolCoordinator) return false;
281
+ return effectiveToolCoordinator.isToolVisible(toInstanceToolId(toolId));
282
+ },
283
+ subscribeVisibility: effectiveToolCoordinator
284
+ ? (listener: () => void) => effectiveToolCoordinator.subscribe(listener)
285
+ : null,
286
+ ensureTTSReady: toolkitCoordinator ? () => toolkitCoordinator.ensureTTSReady() : null
287
+ };
288
+ });
289
+
290
+ let renderedTools = $derived.by((): ToolToolbarRenderResult[] => {
291
+ if (!isBrowser) return [];
292
+ moduleLoadVersion;
293
+
294
+ const rendered: ToolToolbarRenderResult[] = [];
295
+ for (const toolId of visibleToolIds) {
296
+ const result = effectiveToolRegistry.renderForToolbar(toolId, renderContext, toolbarContext);
297
+ if (result) {
298
+ rendered.push(result);
299
+ }
300
+ }
301
+ return rendered;
302
+ });
303
+
304
+ function syncRenderedToolsState() {
305
+ const nextActiveState: Record<string, boolean> = {};
306
+ for (const renderedTool of renderedTools) {
307
+ renderedTool.sync?.();
308
+ if (renderedTool.button) {
309
+ nextActiveState[renderedTool.toolId] = renderedTool.button.active ?? false;
310
+ }
311
+ }
312
+ activeToolState = nextActiveState;
313
+ }
314
+
315
+ $effect(() => {
316
+ const unsubs: Array<() => void> = [];
317
+ syncRenderedToolsState();
318
+ for (const renderedTool of renderedTools) {
319
+ if (renderedTool.subscribeActive) {
320
+ const unsubscribe = renderedTool.subscribeActive((active) => {
321
+ activeToolState = {
322
+ ...activeToolState,
323
+ [renderedTool.toolId]: active
324
+ };
325
+ renderedTool.sync?.();
326
+ });
327
+ unsubs.push(unsubscribe);
328
+ }
329
+ }
330
+
331
+ return () => {
332
+ unsubs.forEach((unsub) => unsub());
333
+ };
334
+ });
335
+
336
+ $effect(() => {
337
+ if (!toolbarContext.subscribeVisibility) return;
338
+ return toolbarContext.subscribeVisibility(() => {
339
+ syncRenderedToolsState();
340
+ });
341
+ });
342
+
343
+ function mountElement(node: HTMLSpanElement, element: HTMLElement | null) {
344
+ let mountedElement: HTMLElement | null = null;
345
+ const updateMountedElement = (nextElement: HTMLElement | null) => {
346
+ if (mountedElement === nextElement) return;
347
+ if (mountedElement && mountedElement.parentNode === node) {
348
+ node.removeChild(mountedElement);
349
+ }
350
+ mountedElement = nextElement;
351
+ if (mountedElement) {
352
+ if (mountedElement.parentNode && mountedElement.parentNode !== node) {
353
+ mountedElement.parentNode.removeChild(mountedElement);
354
+ }
355
+ node.appendChild(mountedElement);
356
+ }
357
+ };
358
+ updateMountedElement(element);
359
+ return {
360
+ update(nextElement: HTMLElement | null) {
361
+ updateMountedElement(nextElement);
362
+ },
363
+ destroy() {
364
+ updateMountedElement(null);
365
+ }
366
+ };
367
+ }
368
+ </script>
369
+
370
+ {#if isBrowser}
371
+ <div
372
+ class="item-toolbar {className} item-toolbar--{size}"
373
+ data-content-kind={effectiveContentKind}
374
+ bind:this={toolbarRootElement}
375
+ >
376
+ {#each renderedTools as renderedTool (renderedTool.toolId)}
377
+ {#if renderedTool.inlineElement}
378
+ <span class="item-toolbar__element-host" use:mountElement={renderedTool.inlineElement}></span>
379
+ {/if}
380
+
381
+ {#if renderedTool.button}
382
+ <button
383
+ type="button"
384
+ class="item-toolbar__button {renderedTool.button.className || ''}"
385
+ class:item-toolbar__button--active={activeToolState[renderedTool.toolId] ?? renderedTool.button.active ?? false}
386
+ onclick={() => {
387
+ renderedTool.button?.onClick();
388
+ syncRenderedToolsState();
389
+ }}
390
+ aria-label={renderedTool.button.ariaLabel || renderedTool.button.label}
391
+ aria-pressed={activeToolState[renderedTool.toolId] ?? renderedTool.button.active ?? false}
392
+ title={renderedTool.button.tooltip || renderedTool.button.label}
393
+ disabled={renderedTool.button.disabled}
394
+ >
395
+ {#if renderedTool.button.icon.startsWith('<svg')}
396
+ {@html renderedTool.button.icon}
397
+ {:else if renderedTool.button.icon.startsWith('http')}
398
+ <img class="item-toolbar__icon-image" src={renderedTool.button.icon} alt="" />
399
+ {:else}
400
+ <i class={`icon icon-${renderedTool.button.icon}`} aria-hidden="true"></i>
401
+ {/if}
402
+ </button>
403
+ {/if}
404
+
405
+ {#if renderedTool.overlayElement}
406
+ <span class="item-toolbar__element-host" use:mountElement={renderedTool.overlayElement}></span>
407
+ {/if}
408
+ {/each}
409
+ </div>
410
+ {/if}
411
+
412
+ <style>
413
+ .item-toolbar {
414
+ display: flex;
415
+ align-items: center;
416
+ gap: 0.5rem;
417
+ }
418
+
419
+ .item-toolbar__button {
420
+ display: flex;
421
+ align-items: center;
422
+ justify-content: center;
423
+ width: 2rem;
424
+ height: 2rem;
425
+ padding: 0.25rem;
426
+ border: 1px solid var(--pie-border, #ccc);
427
+ border-radius: 0.25rem;
428
+ background-color: var(--pie-background, white);
429
+ color: var(--pie-text, #333);
430
+ cursor: pointer;
431
+ transition: all 0.15s ease;
432
+ }
433
+
434
+ .item-toolbar__element-host {
435
+ display: contents;
436
+ }
437
+
438
+ .item-toolbar--sm .item-toolbar__button {
439
+ width: 2.75rem;
440
+ height: 2.75rem;
441
+ }
442
+
443
+ .item-toolbar--lg .item-toolbar__button {
444
+ width: 2.5rem;
445
+ height: 2.5rem;
446
+ }
447
+
448
+ .item-toolbar__button:hover:not(:disabled) {
449
+ background-color: var(--pie-secondary-background, #f5f5f5);
450
+ transform: translateY(-1px);
451
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
452
+ }
453
+
454
+ .item-toolbar__button:active:not(:disabled) {
455
+ transform: translateY(0);
456
+ box-shadow: none;
457
+ }
458
+
459
+ .item-toolbar__button--active {
460
+ background-color: var(--pie-primary, #1976d2);
461
+ color: white;
462
+ border-color: var(--pie-primary, #1976d2);
463
+ }
464
+
465
+ .item-toolbar__button--active:hover:not(:disabled) {
466
+ background-color: var(--pie-primary-dark, #1565c0);
467
+ }
468
+
469
+ .item-toolbar__button:focus-visible {
470
+ outline: 2px solid var(--pie-primary, #1976d2);
471
+ outline-offset: 2px;
472
+ }
473
+
474
+ .item-toolbar__button:disabled {
475
+ opacity: 0.5;
476
+ cursor: not-allowed;
477
+ }
478
+
479
+ .item-toolbar__button :global(svg) {
480
+ width: 100%;
481
+ height: 100%;
482
+ }
483
+
484
+ .item-toolbar__icon-image {
485
+ width: 100%;
486
+ height: 100%;
487
+ object-fit: contain;
488
+ }
489
+ </style>
@@ -1,9 +1,8 @@
1
1
  <script lang="ts">
2
- import type { PNPResolutionProvenance } from '@pie-players/pie-assessment-toolkit/services/pnp-provenance';
2
+ import type { PNPResolutionProvenance } from '../services/pnp-provenance.js';
3
3
 
4
4
  // PNP Provenance Viewer Component
5
5
  // Visualizes how PNP resolution decisions were made.
6
- // See PNP_TESTING.md for usage documentation.
7
6
 
8
7
  interface Props {
9
8
  provenance: PNPResolutionProvenance;
@@ -37,6 +36,17 @@
37
36
  };
38
37
  return icons[action] || '?';
39
38
  }
39
+
40
+ function getSourceName(source: unknown): string | undefined {
41
+ if (!source || typeof source !== 'object') return undefined;
42
+ if ('name' in source && typeof (source as any).name === 'string') {
43
+ return (source as any).name;
44
+ }
45
+ if ('title' in source && typeof (source as any).title === 'string') {
46
+ return (source as any).title;
47
+ }
48
+ return undefined;
49
+ }
40
50
  </script>
41
51
 
42
52
  <div class="pnp-provenance-viewer" class:compact>
@@ -75,7 +85,7 @@
75
85
  <div class="flex items-center gap-2">
76
86
  <div class="badge badge-outline">{type}</div>
77
87
  <div class="text-sm">
78
- {source.name || source.id}
88
+ {getSourceName(source) || source.id}
79
89
  <span class="text-xs text-base-content/50">({source.id})</span>
80
90
  </div>
81
91
  </div>
@@ -226,7 +236,7 @@
226
236
  {decision.reason}
227
237
  </div>
228
238
  <div class="text-base-content/50 mt-1">
229
- Source: {decision.source.name || decision.source.type}
239
+ Source: {getSourceName(decision.source) || decision.source.type}
230
240
  </div>
231
241
  </div>
232
242
  </div>