@pie-players/pie-assessment-toolkit 0.3.74 → 0.3.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +349 -313
  2. package/dist/attempt/TestSession.d.ts +1 -20
  3. package/dist/attempt/TestSession.js +8 -99
  4. package/dist/components/ItemScope.custom-element.d.ts +1 -0
  5. package/dist/components/ItemScope.custom-element.js +1 -0
  6. package/dist/components/ItemToolBar.custom-element.js +1 -1
  7. package/dist/components/PieAssessmentToolkit.custom-element.js +14 -14
  8. package/dist/components/SectionToolBar.custom-element.js +1 -1
  9. package/dist/components/chunks/ItemToolBar-7tt7v2xp.js +4 -0
  10. package/dist/components/chunks/{ItemToolBar-gmrryv2v.js → ItemToolBar-97hx60z4.js} +1 -1
  11. package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +2 -0
  12. package/dist/components/chunks/ItemToolBar-jx38w99s.js +2 -0
  13. package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +40 -0
  14. package/dist/components/chunks/ItemToolBar-vdkyn9f3.js +2 -0
  15. package/dist/components/chunks/sre-engine-jd8cc08x.js +1 -0
  16. package/dist/components/item-scope-element.d.ts +2 -0
  17. package/dist/components/item-scope-element.js +2 -0
  18. package/dist/context/assessment-toolkit-context.d.ts +27 -6
  19. package/dist/index.d.ts +22 -47
  20. package/dist/index.js +12 -36
  21. package/dist/policy/core/PolicySource.d.ts +1 -1
  22. package/dist/policy/core/ToolPolicyEngine.d.ts +77 -32
  23. package/dist/policy/core/ToolPolicyEngine.js +136 -46
  24. package/dist/policy/core/compose-decision.d.ts +28 -7
  25. package/dist/policy/core/compose-decision.js +75 -54
  26. package/dist/policy/core/decision-types.d.ts +65 -34
  27. package/dist/policy/core/feature-decision.d.ts +16 -9
  28. package/dist/policy/core/feature-decision.js +14 -8
  29. package/dist/policy/core/pnp-policy-inputs.d.ts +24 -33
  30. package/dist/policy/core/pnp-policy-inputs.js +28 -56
  31. package/dist/policy/core/provenance.d.ts +9 -4
  32. package/dist/policy/core/provenance.js +1 -1
  33. package/dist/policy/engine.d.ts +7 -23
  34. package/dist/policy/engine.js +5 -20
  35. package/dist/policy/sources/PnpPolicySource.d.ts +44 -61
  36. package/dist/policy/sources/PnpPolicySource.js +117 -118
  37. package/dist/runtime/SectionControllerBinding.d.ts +279 -0
  38. package/dist/runtime/SectionControllerBinding.js +450 -0
  39. package/dist/runtime/SectionRuntimeEngine.d.ts +26 -289
  40. package/dist/runtime/SectionRuntimeEngine.js +30 -372
  41. package/dist/runtime/adapter/SectionEngineAdapter.d.ts +19 -80
  42. package/dist/runtime/adapter/SectionEngineAdapter.js +19 -92
  43. package/dist/runtime/adapter/dom-event-bridge.d.ts +15 -30
  44. package/dist/runtime/adapter/dom-event-bridge.js +17 -44
  45. package/dist/runtime/adapter/subscriber-fanout.d.ts +1 -2
  46. package/dist/runtime/adapter/subscriber-fanout.js +1 -2
  47. package/dist/runtime/catalog-context.d.ts +19 -0
  48. package/dist/runtime/catalog-context.js +28 -0
  49. package/dist/runtime/composition-emit-scheduler.d.ts +9 -15
  50. package/dist/runtime/composition-emit-scheduler.js +9 -15
  51. package/dist/runtime/content-language.d.ts +32 -0
  52. package/dist/runtime/content-language.js +46 -0
  53. package/dist/runtime/content-region.d.ts +17 -0
  54. package/dist/runtime/content-region.js +29 -0
  55. package/dist/runtime/content-registration.d.ts +13 -0
  56. package/dist/runtime/content-registration.js +25 -0
  57. package/dist/runtime/core/SectionEngineCore.d.ts +2 -2
  58. package/dist/runtime/core/SectionEngineCore.js +2 -2
  59. package/dist/runtime/core/cohort.d.ts +1 -1
  60. package/dist/runtime/core/cohort.js +1 -1
  61. package/dist/runtime/core/engine-input.d.ts +18 -30
  62. package/dist/runtime/core/engine-input.js +11 -12
  63. package/dist/runtime/core/engine-output.d.ts +16 -40
  64. package/dist/runtime/core/engine-output.js +9 -21
  65. package/dist/runtime/core/engine-readiness.d.ts +5 -37
  66. package/dist/runtime/core/engine-readiness.js +8 -41
  67. package/dist/runtime/core/engine-resolver.d.ts +46 -74
  68. package/dist/runtime/core/engine-resolver.js +18 -43
  69. package/dist/runtime/core/engine-state.d.ts +28 -36
  70. package/dist/runtime/core/engine-state.js +18 -21
  71. package/dist/runtime/core/engine-transition.d.ts +14 -19
  72. package/dist/runtime/core/engine-transition.js +71 -51
  73. package/dist/runtime/engine.d.ts +16 -19
  74. package/dist/runtime/engine.js +12 -19
  75. package/dist/runtime/page-warnings.d.ts +13 -0
  76. package/dist/runtime/page-warnings.js +22 -0
  77. package/dist/runtime/policy-input-forwarding.d.ts +25 -0
  78. package/dist/runtime/policy-input-forwarding.js +25 -0
  79. package/dist/runtime/registration-events.d.ts +11 -0
  80. package/dist/runtime/runtime-event-claim.d.ts +8 -0
  81. package/dist/runtime/runtime-event-claim.js +15 -0
  82. package/dist/runtime/shell-event-bridge.d.ts +45 -0
  83. package/dist/runtime/shell-event-bridge.js +127 -0
  84. package/dist/runtime/shell-registration.d.ts +73 -0
  85. package/dist/runtime/shell-registration.js +95 -0
  86. package/dist/runtime/shell-scope.d.ts +68 -0
  87. package/dist/runtime/shell-scope.js +188 -0
  88. package/dist/runtime/tool-host-contract.d.ts +8 -10
  89. package/dist/runtime/tool-host-contract.js +11 -11
  90. package/dist/runtime/unclaimed-registration-watch.d.ts +7 -0
  91. package/dist/runtime/unclaimed-registration-watch.js +23 -0
  92. package/dist/services/ElementToolStateStore.d.ts +14 -10
  93. package/dist/services/ElementToolStateStore.js +37 -13
  94. package/dist/services/HighlightCoordinator.d.ts +21 -21
  95. package/dist/services/HighlightCoordinator.js +431 -325
  96. package/dist/services/RangeSerializer.d.ts +10 -1
  97. package/dist/services/RangeSerializer.js +87 -14
  98. package/dist/services/TTSService.d.ts +117 -70
  99. package/dist/services/TTSService.js +668 -578
  100. package/dist/services/ToolCoordinator.d.ts +43 -60
  101. package/dist/services/ToolCoordinator.js +164 -206
  102. package/dist/services/ToolRegistry.d.ts +74 -57
  103. package/dist/services/ToolRegistry.js +104 -114
  104. package/dist/services/ToolkitCoordinator.d.ts +183 -189
  105. package/dist/services/ToolkitCoordinator.js +477 -431
  106. package/dist/services/audio-handoff.d.ts +6 -7
  107. package/dist/services/audio-handoff.js +20 -14
  108. package/dist/services/catalog-owner.d.ts +0 -1
  109. package/dist/services/framework-error-bus.d.ts +3 -6
  110. package/dist/services/framework-error-bus.js +3 -6
  111. package/dist/services/framework-error.d.ts +22 -7
  112. package/dist/services/framework-error.js +9 -7
  113. package/dist/services/interfaces.d.ts +158 -129
  114. package/dist/services/section-session-assignment.d.ts +21 -0
  115. package/dist/services/section-session-assignment.js +82 -0
  116. package/dist/services/spoken-audio-cards.d.ts +0 -2
  117. package/dist/services/spoken-audio-cards.js +0 -2
  118. package/dist/services/tool-config-defaults.d.ts +3 -3
  119. package/dist/services/tool-config-defaults.js +3 -3
  120. package/dist/services/tool-config-validation.d.ts +4 -5
  121. package/dist/services/tool-config-validation.js +36 -61
  122. package/dist/services/tool-context.d.ts +0 -8
  123. package/dist/services/tool-context.js +0 -12
  124. package/dist/services/tool-icons.d.ts +4 -3
  125. package/dist/services/tool-icons.js +4 -3
  126. package/dist/services/tool-instance-id.d.ts +0 -1
  127. package/dist/services/tool-instance-id.js +0 -6
  128. package/dist/services/tool-providers/TTSToolProvider.d.ts +18 -19
  129. package/dist/services/tool-providers/TTSToolProvider.js +37 -34
  130. package/dist/services/tool-providers/ToolProviderApi.d.ts +10 -4
  131. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +30 -26
  132. package/dist/services/tool-providers/ToolProviderRegistry.js +119 -93
  133. package/dist/services/tool-request.d.ts +27 -12
  134. package/dist/services/tool-request.js +24 -14
  135. package/dist/services/toolbar-decided-tools.d.ts +15 -0
  136. package/dist/services/toolbar-decided-tools.js +28 -0
  137. package/dist/services/tools-config-normalizer.d.ts +17 -22
  138. package/dist/services/tools-config-normalizer.js +2 -10
  139. package/dist/services/tts/browser-provider.d.ts +38 -2
  140. package/dist/services/tts/browser-provider.js +276 -140
  141. package/dist/services/tts/flat-tree.d.ts +71 -0
  142. package/dist/services/tts/flat-tree.js +269 -0
  143. package/dist/services/tts/generated-speech/assemble-plan.d.ts +3 -1
  144. package/dist/services/tts/generated-speech/assemble-plan.js +3 -1
  145. package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +2 -0
  146. package/dist/services/tts/generated-speech/dom/build-dom-plan.js +1 -1
  147. package/dist/services/tts/generated-speech/math-speech-cache.d.ts +3 -1
  148. package/dist/services/tts/generated-speech/math-speech-cache.js +5 -1
  149. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +12 -4
  150. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +6 -4
  151. package/dist/services/tts/highlight-pipeline/readable-region.js +3 -2
  152. package/dist/services/tts/highlight-pipeline/types.d.ts +5 -0
  153. package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +12 -0
  154. package/dist/services/tts/highlight-pipeline/visible-map-range.js +73 -7
  155. package/dist/services/tts/math-aware-text-processing.d.ts +5 -1
  156. package/dist/services/tts/math-aware-text-processing.js +32 -18
  157. package/dist/services/tts/math-control-names.d.ts +21 -0
  158. package/dist/services/tts/math-control-names.js +142 -0
  159. package/dist/services/tts/math-speech.d.ts +11 -0
  160. package/dist/services/tts/math-speech.js +51 -18
  161. package/dist/services/tts/provider-options.d.ts +41 -0
  162. package/dist/services/tts/provider-options.js +1 -0
  163. package/dist/services/tts/ssml/spoken-text.d.ts +2 -26
  164. package/dist/services/tts/ssml/spoken-text.js +29 -0
  165. package/dist/services/tts/start-failure.d.ts +11 -0
  166. package/dist/services/tts/start-failure.js +19 -0
  167. package/dist/services/tts/text-processing.d.ts +9 -0
  168. package/dist/services/tts/text-processing.js +46 -33
  169. package/dist/services/tts/text-segmentation.d.ts +7 -0
  170. package/dist/services/tts/text-segmentation.js +47 -0
  171. package/dist/services/tts-runtime-config.d.ts +25 -17
  172. package/dist/services/tts-runtime-config.js +16 -95
  173. package/dist/tools/content-capability-resolution.d.ts +7 -9
  174. package/dist/tools/content-capability-resolution.js +8 -45
  175. package/dist/tools/registration.d.ts +58 -0
  176. package/dist/tools/registration.js +54 -0
  177. package/dist/tools/registrations/toolbar-registration-helpers.js +2 -2
  178. package/dist/tools/tool-surface-host.d.ts +6 -10
  179. package/dist/tools/tool-surface-host.js +44 -42
  180. package/dist/tools/tool-tag-map.d.ts +5 -15
  181. package/dist/tools/tool-tag-map.js +6 -20
  182. package/dist/utils/structural-equality.d.ts +11 -0
  183. package/dist/utils/structural-equality.js +47 -0
  184. package/package.json +13 -29
  185. package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +0 -49
  186. package/dist/attempt/adapters/activity-to-test-attempt-session.js +0 -138
  187. package/dist/components/chunks/ItemToolBar-b9dz5jvd.js +0 -40
  188. package/dist/components/chunks/ItemToolBar-h4xap0t0.js +0 -4
  189. package/dist/components/chunks/ItemToolBar-wr2myaah.js +0 -2
  190. package/dist/components/chunks/sre-engine-wf9s43gg.js +0 -1
  191. package/dist/context/runtime-context-consumer.d.ts +0 -13
  192. package/dist/context/runtime-context-consumer.js +0 -18
  193. package/dist/item-loader.d.ts +0 -44
  194. package/dist/item-loader.js +0 -61
  195. package/dist/policy/core/engine-context.d.ts +0 -29
  196. package/dist/policy/core/engine-context.js +0 -27
  197. package/dist/policy/core/policy-source-tag.d.ts +0 -31
  198. package/dist/policy/core/policy-source-tag.js +0 -28
  199. package/dist/policy/internal.d.ts +0 -25
  200. package/dist/policy/internal.js +0 -25
  201. package/dist/runtime/adapter/coordinator-bridge.d.ts +0 -92
  202. package/dist/runtime/adapter/coordinator-bridge.js +0 -126
  203. package/dist/runtime/adapter/framework-error-bridge.d.ts +0 -36
  204. package/dist/runtime/adapter/framework-error-bridge.js +0 -46
  205. package/dist/runtime/adapter/instrumentation-bridge.d.ts +0 -31
  206. package/dist/runtime/adapter/instrumentation-bridge.js +0 -38
  207. package/dist/runtime/core/engine-stage-derivation.d.ts +0 -18
  208. package/dist/runtime/core/engine-stage-derivation.js +0 -29
  209. package/dist/runtime/engine-context.d.ts +0 -39
  210. package/dist/runtime/engine-context.js +0 -31
  211. package/dist/runtime/internal.d.ts +0 -43
  212. package/dist/runtime/internal.js +0 -45
  213. package/dist/runtime/section-runtime-engine-host-context.d.ts +0 -74
  214. package/dist/runtime/section-runtime-engine-host-context.js +0 -57
  215. package/dist/runtime/stage-emit-gate.d.ts +0 -92
  216. package/dist/runtime/stage-emit-gate.js +0 -82
  217. package/dist/services/I18nService.d.ts +0 -47
  218. package/dist/services/I18nService.js +0 -84
  219. package/dist/services/ThemeProvider.d.ts +0 -95
  220. package/dist/services/ThemeProvider.js +0 -334
  221. package/dist/services/defaultPersonalNeedsProfile.d.ts +0 -21
  222. package/dist/services/defaultPersonalNeedsProfile.js +0 -26
  223. package/dist/services/pnp-standard-features.d.ts +0 -238
  224. package/dist/services/pnp-standard-features.js +0 -338
  225. package/dist/tools/client.d.ts +0 -10
  226. package/dist/tools/client.js +0 -10
  227. package/dist/tools/internal.d.ts +0 -44
  228. package/dist/tools/internal.js +0 -39
@@ -7,7 +7,7 @@
7
7
  import type { I18nProvider } from "@pie-players/pie-players-shared/i18n/types";
8
8
  import type { CatalogOwnerSnapshot } from "./AccessibilityCatalogResolver.js";
9
9
  import type { ToolContext, ToolLevel } from "./tool-context.js";
10
- import type { ToolComponentOverrides } from "../tools/tool-tag-map.js";
10
+ import { type ToolComponentOverrides } from "../tools/tool-tag-map.js";
11
11
  import type { AccessibilityCatalogResolverApi, ElementToolStateStoreApi, ToolCoordinatorApi, ToolkitCoordinatorApi, TtsServiceApi } from "./interfaces.js";
12
12
  import type { ToolProviderApi } from "./tool-providers/ToolProviderApi.js";
13
13
  import type { ToolProviderConfig as ToolRuntimeConfig } from "./tools-config-normalizer.js";
@@ -65,9 +65,10 @@ export interface ToolbarContext {
65
65
  * Content-alternate language: which authored alternate the catalog resolver
66
66
  * should select. Not the interface locale — see {@link ToolbarContext.i18n}. The
67
67
  * two are independent by QTI 3's own statement, and conflating them is how a
68
- * Spanish passage ends up forcing Spanish widget chrome.
68
+ * Spanish passage ends up forcing Spanish widget chrome. Absent when neither
69
+ * the toolbar's `language` nor the host's `content-language` names one.
69
70
  */
70
- language: string;
71
+ language?: string;
71
72
  /**
72
73
  * Interface-locale provider for this capability's own UI strings.
73
74
  *
@@ -83,12 +84,19 @@ export interface ToolbarContext {
83
84
  size?: string;
84
85
  };
85
86
  getScopeElement?: () => HTMLElement | null;
86
- getGlobalElementId?: () => string | null;
87
+ /**
88
+ * State key of one PIE element in this toolbar's item, by its model id, for
89
+ * `elementToolStateStore`. `null` until the store and the ids it needs are
90
+ * known.
91
+ */
92
+ getGlobalElementId?: (elementId: string) => string | null;
87
93
  toolCoordinator: ToolCoordinatorApi | null;
88
94
  toolkitCoordinator: ToolkitCoordinatorApi | null;
89
95
  ttsService: TtsServiceApi | null;
90
96
  elementToolStateStore: ElementToolStateStoreApi | null;
97
+ /** Toggles this toolbar's instance of a base tool id. */
91
98
  toggleTool: (toolId: string) => void;
99
+ /** Whether this toolbar's instance of a base tool id is shown. */
92
100
  isToolVisible: (toolId: string) => boolean;
93
101
  subscribeVisibility: ((listener: () => void) => () => void) | null;
94
102
  componentOverrides?: ToolComponentOverrides;
@@ -207,24 +215,18 @@ export interface HostedToolSize {
207
215
  width: number;
208
216
  height: number;
209
217
  }
218
+ /**
219
+ * How the coordinator builds a tool's provider from the tool's config. The
220
+ * provider registers under the tool's own id, so `ensureProviderReady(toolId)`
221
+ * finds it; `provider.id` in the config selects an implementation and never
222
+ * renames the registration.
223
+ */
210
224
  export interface ToolProviderDescriptor {
211
- getProviderId?: (config: ToolRuntimeConfig | undefined) => string;
212
225
  createProvider: (config: ToolRuntimeConfig | undefined) => ToolProviderApi;
213
226
  getInitConfig?: (config: ToolRuntimeConfig | undefined) => Record<string, unknown>;
214
- sanitizeConfig?: (config: ToolRuntimeConfig) => ToolRuntimeConfig;
215
- validateConfig?: (config: ToolRuntimeConfig) => ToolConfigDiagnostic[];
216
227
  getAuthFetcher?: (config: ToolRuntimeConfig | undefined) => (() => Promise<Record<string, unknown>>) | undefined;
217
228
  lazy?: boolean;
218
229
  }
219
- /**
220
- * The id a registration's provider registers under for one tool config: the
221
- * descriptor's answer, then the config's `provider.id`, then the tool id. `null`
222
- * when the registration carries no provider.
223
- *
224
- * The coordinator registers providers under this id and the section player looks
225
- * for them in a host-supplied coordinator by it, so the two resolve it one way.
226
- */
227
- export declare function resolveToolProviderId(registration: Pick<ToolRegistration, "toolId" | "provider">, config: ToolRuntimeConfig | undefined): string | null;
228
230
  export interface ToolToolbarRenderResult {
229
231
  toolId: string;
230
232
  elements?: ToolRenderElement[];
@@ -280,8 +282,14 @@ export interface ToolSurfaceServices {
280
282
  */
281
283
  export interface ToolSurfaceRenderContext {
282
284
  toolId: string;
283
- /** The PNP/AfA support id policy granted for this render. */
284
- featureId: string;
285
+ /**
286
+ * Whether a PNP support, a requirement or a test-administration override
287
+ * granted the capability. `false` for a capability rendered because it is
288
+ * placed with no grant, and for a
289
+ * {@link ToolRegistration.resolvesWithoutGrant} capability answering from
290
+ * content alone.
291
+ */
292
+ granted: boolean;
285
293
  /** Host slot being filled. */
286
294
  surface: string;
287
295
  /** Feature parameters from the policy decision, if any. */
@@ -296,8 +304,6 @@ export interface ToolSurfaceRenderContext {
296
304
  * present.
297
305
  */
298
306
  export interface ToolContentDependencyContext {
299
- /** The PNP/AfA support id being resolved. */
300
- featureId: string;
301
307
  /** Feature parameters from the policy decision, if any. */
302
308
  parameters?: unknown;
303
309
  /**
@@ -309,7 +315,7 @@ export interface ToolContentDependencyContext {
309
315
  */
310
316
  catalogs: CatalogOwnerSnapshot | null;
311
317
  /**
312
- * Whether policy granted one of this capability's support ids.
318
+ * Whether policy granted this capability's support id.
313
319
  *
314
320
  * `false` reaches `resolve` only for a capability that declares
315
321
  * {@link ToolRegistration.resolvesWithoutGrant}, and it is the signal that the
@@ -448,12 +454,6 @@ export interface ToolRegistration {
448
454
  * Optional singleton scope for activation models that mount exactly one instance.
449
455
  */
450
456
  singletonScope?: ToolSingletonScope;
451
- /**
452
- * PNP support IDs that enable this tool (optional)
453
- * Used by the tool policy engine to determine if a PNP support enables this tool.
454
- * Example: ['calculator', 'basic-calculator', 'scientific-calculator']
455
- */
456
- pnpSupportIds?: string[];
457
457
  /**
458
458
  * Authored content this capability needs before it has anything to show.
459
459
  *
@@ -484,6 +484,16 @@ export interface ToolRegistration {
484
484
  * without hardcoded tool-specific branches.
485
485
  */
486
486
  provider?: ToolProviderDescriptor;
487
+ /**
488
+ * Normalize this tool's `tools.providers.<toolId>` entry. Tools-config
489
+ * validation runs it before {@link ToolRegistration.validateConfig}; a throw is
490
+ * reported as a diagnostic and the entry passes through unchanged.
491
+ */
492
+ sanitizeConfig?: (config: ToolRuntimeConfig) => ToolRuntimeConfig;
493
+ /**
494
+ * Diagnostics for this tool's sanitized `tools.providers.<toolId>` entry.
495
+ */
496
+ validateConfig?: (config: ToolRuntimeConfig) => ToolConfigDiagnostic[];
487
497
  /**
488
498
  * Optional shell-host lifecycle hooks for hosted (floating) tools.
489
499
  */
@@ -547,10 +557,12 @@ export interface ToolRegistration {
547
557
  */
548
558
  export declare class ToolRegistry {
549
559
  private tools;
550
- private pnpIndex;
551
560
  private componentOverrides;
561
+ private watchedUndefinedToolElements;
552
562
  private moduleLoaders;
553
563
  private loadedToolModules;
564
+ private warnedLoaderReplacements;
565
+ private warnedUnregisteredAllowedTools;
554
566
  private moduleLoadPromises;
555
567
  private changeListeners;
556
568
  private emitChange;
@@ -560,11 +572,11 @@ export declare class ToolRegistry {
560
572
  */
561
573
  onRegistryChange(listener: ToolRegistryChangeListener): () => void;
562
574
  /**
563
- * Normalize a single tool alias to canonical toolId.
575
+ * Normalize a single tool id (trims surrounding whitespace).
564
576
  */
565
577
  normalizeToolId(toolId: string): string;
566
578
  /**
567
- * Normalize a list of tool aliases to canonical toolIds.
579
+ * Normalize a list of tool ids (trims surrounding whitespace).
568
580
  */
569
581
  normalizeToolIds(toolIds: string[]): string[];
570
582
  /**
@@ -612,13 +624,6 @@ export declare class ToolRegistry {
612
624
  * @returns Array of tool registrations
613
625
  */
614
626
  getAllTools(): ToolRegistration[];
615
- /**
616
- * Find tool IDs that support a given PNP support ID
617
- *
618
- * @param pnpSupportId - PNP support ID (e.g., 'calculator')
619
- * @returns Set of tool IDs that support this PNP ID
620
- */
621
- getToolsByPNPSupport(pnpSupportId: string): Set<string>;
622
627
  /**
623
628
  * Get tools that support a specific level
624
629
  *
@@ -644,11 +649,12 @@ export declare class ToolRegistry {
644
649
  */
645
650
  getToolsBySurface(surface: string): ToolRegistration[];
646
651
  /**
647
- * Support ids belonging to capabilities that need authored content.
652
+ * Support ids, which are tool ids, of the capabilities that need authored
653
+ * content.
648
654
  *
649
- * What a host filters a default grant list on, in place of the compile-time
650
- * exclusion array this replaced: granting one of these wholesale grants an
651
- * accommodation to learners with no documented need for it.
655
+ * What a host filters a default grant list on: granting one of these
656
+ * wholesale grants an accommodation to learners with no documented need
657
+ * for it.
652
658
  */
653
659
  getContentDependentSupportIds(): string[];
654
660
  /**
@@ -658,7 +664,7 @@ export declare class ToolRegistry {
658
664
  /**
659
665
  * Filter tools by visibility in a given context
660
666
  *
661
- * Pass 2 of the two-pass model: Given a list of allowed tool IDs (from Pass 1),
667
+ * Pass 2 of the three-pass model: Given a list of allowed tool IDs (from Pass 1),
662
668
  * ask each tool if it's relevant in this context.
663
669
  *
664
670
  * @param allowedToolIds - Tool IDs that passed Pass 1 (orchestrator approval)
@@ -684,13 +690,12 @@ export declare class ToolRegistry {
684
690
  * Get tool metadata for building UIs
685
691
  * Useful for building PNP configuration interfaces
686
692
  *
687
- * @returns Array of tool metadata (id, name, description, pnpSupportIds)
693
+ * @returns Array of tool metadata (id, name, description, levels, activation)
688
694
  */
689
695
  getToolMetadata(): Array<{
690
696
  toolId: string;
691
697
  name: string;
692
698
  description: string;
693
- pnpSupportIds: string[];
694
699
  supportedLevels: ToolLevel[];
695
700
  activation: ToolActivation;
696
701
  singletonScope: ToolSingletonScope | null;
@@ -698,14 +703,6 @@ export declare class ToolRegistry {
698
703
  requiresAuthoredContent: boolean;
699
704
  contentDependencyDescription: string | null;
700
705
  }>;
701
- /**
702
- * Generate PNP support IDs from enabled tools
703
- * Useful for creating PNP profiles
704
- *
705
- * @param enabledToolIds - Tool IDs to enable
706
- * @returns Array of unique PNP support IDs
707
- */
708
- generatePNPSupportsFromTools(enabledToolIds: string[]): string[];
709
706
  /**
710
707
  * Clear all registrations (useful for testing)
711
708
  */
@@ -717,17 +714,39 @@ export declare class ToolRegistry {
717
714
  /**
718
715
  * Register lazy module loaders by toolId.
719
716
  * Toolbars call ensureToolModuleLoaded(toolId) before instance creation.
717
+ * A tool whose module has loaded, or is loading, keeps its loader: its
718
+ * elements are already defined, so a replacement could never take effect.
720
719
  */
721
720
  setToolModuleLoaders(loaders: Partial<Record<string, ToolModuleLoader>>): void;
721
+ private warnLoaderReplacedAfterLoad;
722
722
  /**
723
723
  * Ensure tool module side-effects are loaded exactly once.
724
724
  * Safe to call repeatedly; concurrent callers share the same promise.
725
725
  */
726
726
  ensureToolModuleLoaded(toolId: string): Promise<void>;
727
727
  /**
728
- * Ensure a set of tool modules are loaded.
728
+ * The undefined element tag a tool would render, or `null` when the tool is
729
+ * built by a component factory, its element is defined, or there is no DOM.
730
+ */
731
+ private undefinedToolElementTag;
732
+ /**
733
+ * Warn when a tool's module loader finished without defining the element the
734
+ * tag map points the tool at, as when a host remaps the tag but keeps a
735
+ * loader that defines the packaged element. The tool otherwise renders blank.
736
+ */
737
+ private warnIfLoadedToolElementUndefined;
738
+ /**
739
+ * Warn when a tool with no module loader still has no element definition
740
+ * after the pending-input delay. A registry built without loaders otherwise
741
+ * renders the tool as an unknown element with no error.
742
+ */
743
+ private watchUndefinedToolElement;
744
+ /**
745
+ * Load a set of tool modules, each to completion, and resolve with the error
746
+ * of every tool whose module failed, by tool id. One failure leaves the other
747
+ * tools loaded and usable. A failed tool is retried on the next call.
729
748
  */
730
- ensureToolModulesLoaded(toolIds: string[]): Promise<void>;
749
+ ensureToolModulesLoaded(toolIds: string[]): Promise<Map<string, unknown>>;
731
750
  /**
732
751
  * Whether a tool module has already been loaded.
733
752
  */
@@ -745,7 +764,5 @@ export declare class ToolRegistry {
745
764
  * nothing and fail on every packaged capability. Overrides passed in the
746
765
  * context still win, matching the toolbar path's precedence.
747
766
  */
748
- renderForSurface(toolId: string, context: Omit<ToolSurfaceRenderContext, "componentOverrides"> & {
749
- componentOverrides?: ToolComponentOverrides;
750
- }): ToolSurfaceRenderResult | null;
767
+ renderForSurface(toolId: string, context: ToolSurfaceRenderContext): ToolSurfaceRenderResult | null;
751
768
  }
@@ -5,23 +5,8 @@
5
5
  * and button/instance creation. Supports dynamic registration and override by integrators.
6
6
  */
7
7
  import { dynamicMessageKey } from "@pie-players/pie-players-shared/i18n/provider";
8
- import { normalizeToolAlias } from "./tools-config-normalizer.js";
9
- /**
10
- * The id a registration's provider registers under for one tool config: the
11
- * descriptor's answer, then the config's `provider.id`, then the tool id. `null`
12
- * when the registration carries no provider.
13
- *
14
- * The coordinator registers providers under this id and the section player looks
15
- * for them in a host-supplied coordinator by it, so the two resolve it one way.
16
- */
17
- export function resolveToolProviderId(registration, config) {
18
- const descriptor = registration.provider;
19
- if (!descriptor)
20
- return null;
21
- return (descriptor.getProviderId?.(config) ??
22
- config?.provider?.id ??
23
- registration.toolId);
24
- }
8
+ import { resolveToolTag, } from "../tools/tool-tag-map.js";
9
+ import { PENDING_INPUT_WARNING_DELAY_MS, warnOncePerDocument, } from "../runtime/page-warnings.js";
25
10
  /**
26
11
  * A registration's display name in the interface locale.
27
12
  *
@@ -163,11 +148,6 @@ function assertToolRegistrationShape(registration) {
163
148
  registration.singletonScope !== "section") {
164
149
  throw new Error(`Invalid tool registration "${registration.toolId}": selection-gateway tools must declare singletonScope "section".`);
165
150
  }
166
- if (registration.pnpSupportIds !== undefined &&
167
- (!Array.isArray(registration.pnpSupportIds) ||
168
- registration.pnpSupportIds.some((pnpId) => typeof pnpId !== "string" || pnpId.trim().length === 0))) {
169
- throw new Error(`Invalid tool registration "${registration.toolId}": "pnpSupportIds" must be an array of non-empty strings.`);
170
- }
171
151
  if (registration.activation !== "region" &&
172
152
  typeof registration.isVisibleInContext !== "function") {
173
153
  throw new Error(`Invalid tool registration "${registration.toolId}": "isVisibleInContext" must be a function.`);
@@ -186,12 +166,6 @@ function assertToolRegistrationShape(registration) {
186
166
  typeof registration.requiresAuthoredContent.resolve !== "function") {
187
167
  throw new Error(`Invalid tool registration "${registration.toolId}": "requiresAuthoredContent" must be an object with a "resolve" function.`);
188
168
  }
189
- if (!registration.pnpSupportIds?.length) {
190
- // A content dependency's second job is keeping the capability out of a
191
- // wholesale grant, and a host filters that by support id. Declaring one
192
- // with no id to filter on would silently drop that guarantee.
193
- throw new Error(`Invalid tool registration "${registration.toolId}": "requiresAuthoredContent" requires at least one entry in "pnpSupportIds", which is what a host filters a default grant list on.`);
194
- }
195
169
  }
196
170
  if (registration.renderToolbar !== undefined) {
197
171
  if (typeof registration.renderToolbar !== "function") {
@@ -209,10 +183,12 @@ function assertToolRegistrationShape(registration) {
209
183
  */
210
184
  export class ToolRegistry {
211
185
  tools = new Map();
212
- pnpIndex = new Map(); // pnpSupportId → Set<toolId>
213
186
  componentOverrides = {};
187
+ watchedUndefinedToolElements = new Set();
214
188
  moduleLoaders = new Map();
215
189
  loadedToolModules = new Set();
190
+ warnedLoaderReplacements = new Set();
191
+ warnedUnregisteredAllowedTools = new Set();
216
192
  moduleLoadPromises = new Map();
217
193
  changeListeners = new Set();
218
194
  emitChange(event) {
@@ -240,13 +216,13 @@ export class ToolRegistry {
240
216
  };
241
217
  }
242
218
  /**
243
- * Normalize a single tool alias to canonical toolId.
219
+ * Normalize a single tool id (trims surrounding whitespace).
244
220
  */
245
221
  normalizeToolId(toolId) {
246
- return normalizeToolAlias(toolId);
222
+ return toolId.trim();
247
223
  }
248
224
  /**
249
- * Normalize a list of tool aliases to canonical toolIds.
225
+ * Normalize a list of tool ids (trims surrounding whitespace).
250
226
  */
251
227
  normalizeToolIds(toolIds) {
252
228
  return toolIds.map((toolId) => this.normalizeToolId(toolId));
@@ -263,15 +239,6 @@ export class ToolRegistry {
263
239
  throw new Error(`Tool '${registration.toolId}' is already registered`);
264
240
  }
265
241
  this.tools.set(registration.toolId, registration);
266
- // Index PNP support IDs
267
- if (registration.pnpSupportIds) {
268
- for (const pnpId of registration.pnpSupportIds) {
269
- if (!this.pnpIndex.has(pnpId)) {
270
- this.pnpIndex.set(pnpId, new Set());
271
- }
272
- this.pnpIndex.get(pnpId).add(registration.toolId);
273
- }
274
- }
275
242
  this.emitChange({ kind: "register", toolIds: [registration.toolId] });
276
243
  }
277
244
  /**
@@ -284,24 +251,7 @@ export class ToolRegistry {
284
251
  if (!this.tools.has(registration.toolId)) {
285
252
  throw new Error(`Cannot override non-existent tool '${registration.toolId}'`);
286
253
  }
287
- // Remove old PNP index entries
288
- const oldReg = this.tools.get(registration.toolId);
289
- if (oldReg.pnpSupportIds) {
290
- for (const pnpId of oldReg.pnpSupportIds) {
291
- this.pnpIndex.get(pnpId)?.delete(registration.toolId);
292
- }
293
- }
294
- // Add new registration
295
254
  this.tools.set(registration.toolId, registration);
296
- // Re-index PNP support IDs
297
- if (registration.pnpSupportIds) {
298
- for (const pnpId of registration.pnpSupportIds) {
299
- if (!this.pnpIndex.has(pnpId)) {
300
- this.pnpIndex.set(pnpId, new Set());
301
- }
302
- this.pnpIndex.get(pnpId).add(registration.toolId);
303
- }
304
- }
305
255
  this.emitChange({ kind: "override", toolIds: [registration.toolId] });
306
256
  }
307
257
  /**
@@ -310,15 +260,8 @@ export class ToolRegistry {
310
260
  * @param toolId - Tool ID to remove
311
261
  */
312
262
  unregister(toolId) {
313
- const reg = this.tools.get(toolId);
314
- if (!reg)
263
+ if (!this.tools.has(toolId))
315
264
  return;
316
- // Remove PNP index entries
317
- if (reg.pnpSupportIds) {
318
- for (const pnpId of reg.pnpSupportIds) {
319
- this.pnpIndex.get(pnpId)?.delete(toolId);
320
- }
321
- }
322
265
  this.tools.delete(toolId);
323
266
  this.emitChange({ kind: "unregister", toolIds: [toolId] });
324
267
  }
@@ -356,15 +299,6 @@ export class ToolRegistry {
356
299
  getAllTools() {
357
300
  return Array.from(this.tools.values());
358
301
  }
359
- /**
360
- * Find tool IDs that support a given PNP support ID
361
- *
362
- * @param pnpSupportId - PNP support ID (e.g., 'calculator')
363
- * @returns Set of tool IDs that support this PNP ID
364
- */
365
- getToolsByPNPSupport(pnpSupportId) {
366
- return this.pnpIndex.get(pnpSupportId) || new Set();
367
- }
368
302
  /**
369
303
  * Get tools that support a specific level
370
304
  *
@@ -401,21 +335,18 @@ export class ToolRegistry {
401
335
  tool.surfaces?.includes(surface));
402
336
  }
403
337
  /**
404
- * Support ids belonging to capabilities that need authored content.
338
+ * Support ids, which are tool ids, of the capabilities that need authored
339
+ * content.
405
340
  *
406
- * What a host filters a default grant list on, in place of the compile-time
407
- * exclusion array this replaced: granting one of these wholesale grants an
408
- * accommodation to learners with no documented need for it.
341
+ * What a host filters a default grant list on: granting one of these
342
+ * wholesale grants an accommodation to learners with no documented need
343
+ * for it.
409
344
  */
410
345
  getContentDependentSupportIds() {
411
- const ids = new Set();
412
- for (const tool of this.getAllTools()) {
413
- if (!tool.requiresAuthoredContent)
414
- continue;
415
- for (const supportId of tool.pnpSupportIds || [])
416
- ids.add(supportId);
417
- }
418
- return [...ids].sort();
346
+ return this.getAllTools()
347
+ .filter((tool) => tool.requiresAuthoredContent)
348
+ .map((tool) => tool.toolId)
349
+ .sort();
419
350
  }
420
351
  /**
421
352
  * Filter tool IDs by activation type.
@@ -426,7 +357,7 @@ export class ToolRegistry {
426
357
  /**
427
358
  * Filter tools by visibility in a given context
428
359
  *
429
- * Pass 2 of the two-pass model: Given a list of allowed tool IDs (from Pass 1),
360
+ * Pass 2 of the three-pass model: Given a list of allowed tool IDs (from Pass 1),
430
361
  * ask each tool if it's relevant in this context.
431
362
  *
432
363
  * @param allowedToolIds - Tool IDs that passed Pass 1 (orchestrator approval)
@@ -438,7 +369,11 @@ export class ToolRegistry {
438
369
  for (const toolId of allowedToolIds) {
439
370
  const tool = this.get(toolId);
440
371
  if (!tool) {
441
- console.warn(`Tool '${toolId}' is allowed but not registered`);
372
+ // Toolbars filter on every render; one warning per id carries it.
373
+ if (!this.warnedUnregisteredAllowedTools.has(toolId)) {
374
+ this.warnedUnregisteredAllowedTools.add(toolId);
375
+ console.warn(`Tool '${toolId}' is allowed but not registered`);
376
+ }
442
377
  continue;
443
378
  }
444
379
  // Check if tool supports this level
@@ -492,14 +427,13 @@ export class ToolRegistry {
492
427
  * Get tool metadata for building UIs
493
428
  * Useful for building PNP configuration interfaces
494
429
  *
495
- * @returns Array of tool metadata (id, name, description, pnpSupportIds)
430
+ * @returns Array of tool metadata (id, name, description, levels, activation)
496
431
  */
497
432
  getToolMetadata() {
498
433
  return this.getAllTools().map((tool) => ({
499
434
  toolId: tool.toolId,
500
435
  name: tool.name,
501
436
  description: tool.description,
502
- pnpSupportIds: tool.pnpSupportIds || [],
503
437
  supportedLevels: tool.supportedLevels,
504
438
  activation: tool.activation || "toolbar-toggle",
505
439
  singletonScope: tool.singletonScope || null,
@@ -508,25 +442,6 @@ export class ToolRegistry {
508
442
  contentDependencyDescription: tool.requiresAuthoredContent?.description ?? null,
509
443
  }));
510
444
  }
511
- /**
512
- * Generate PNP support IDs from enabled tools
513
- * Useful for creating PNP profiles
514
- *
515
- * @param enabledToolIds - Tool IDs to enable
516
- * @returns Array of unique PNP support IDs
517
- */
518
- generatePNPSupportsFromTools(enabledToolIds) {
519
- const pnpSupports = new Set();
520
- for (const toolId of enabledToolIds) {
521
- const tool = this.get(toolId);
522
- if (tool?.pnpSupportIds) {
523
- for (const pnpId of tool.pnpSupportIds) {
524
- pnpSupports.add(pnpId);
525
- }
526
- }
527
- }
528
- return Array.from(pnpSupports);
529
- }
530
445
  /**
531
446
  * Clear all registrations (useful for testing)
532
447
  */
@@ -535,7 +450,6 @@ export class ToolRegistry {
535
450
  if (toolIds.length === 0)
536
451
  return;
537
452
  this.tools.clear();
538
- this.pnpIndex.clear();
539
453
  this.emitChange({ kind: "clear", toolIds });
540
454
  }
541
455
  /**
@@ -550,6 +464,8 @@ export class ToolRegistry {
550
464
  /**
551
465
  * Register lazy module loaders by toolId.
552
466
  * Toolbars call ensureToolModuleLoaded(toolId) before instance creation.
467
+ * A tool whose module has loaded, or is loading, keeps its loader: its
468
+ * elements are already defined, so a replacement could never take effect.
553
469
  */
554
470
  setToolModuleLoaders(loaders) {
555
471
  const entries = Object.entries(loaders).filter((entry) => entry[1] !== undefined);
@@ -563,6 +479,11 @@ export class ToolRegistry {
563
479
  for (const [toolId, loader] of entries) {
564
480
  if (this.moduleLoaders.get(toolId) === loader)
565
481
  continue;
482
+ if (this.loadedToolModules.has(toolId) ||
483
+ this.moduleLoadPromises.has(toolId)) {
484
+ this.warnLoaderReplacedAfterLoad(toolId);
485
+ continue;
486
+ }
566
487
  this.moduleLoaders.set(toolId, loader);
567
488
  changedToolIds.push(toolId);
568
489
  }
@@ -570,6 +491,12 @@ export class ToolRegistry {
570
491
  this.emitChange({ kind: "module-loaders", toolIds: changedToolIds });
571
492
  }
572
493
  }
494
+ warnLoaderReplacedAfterLoad(toolId) {
495
+ if (this.warnedLoaderReplacements.has(toolId))
496
+ return;
497
+ this.warnedLoaderReplacements.add(toolId);
498
+ console.warn(`[ToolRegistry] Ignored a new module loader for "${toolId}": its module already loaded, so the elements it defines stay in place. Register loaders before the tool first renders.`);
499
+ }
573
500
  /**
574
501
  * Ensure tool module side-effects are loaded exactly once.
575
502
  * Safe to call repeatedly; concurrent callers share the same promise.
@@ -583,11 +510,14 @@ export class ToolRegistry {
583
510
  return;
584
511
  }
585
512
  const loader = this.moduleLoaders.get(toolId);
586
- if (!loader)
513
+ if (!loader) {
514
+ this.watchUndefinedToolElement(toolId);
587
515
  return;
516
+ }
588
517
  const loadPromise = (async () => {
589
518
  await loader();
590
519
  this.loadedToolModules.add(toolId);
520
+ this.warnIfLoadedToolElementUndefined(toolId);
591
521
  })();
592
522
  this.moduleLoadPromises.set(toolId, loadPromise);
593
523
  try {
@@ -598,10 +528,70 @@ export class ToolRegistry {
598
528
  }
599
529
  }
600
530
  /**
601
- * Ensure a set of tool modules are loaded.
531
+ * The undefined element tag a tool would render, or `null` when the tool is
532
+ * built by a component factory, its element is defined, or there is no DOM.
533
+ */
534
+ undefinedToolElementTag(toolId) {
535
+ if (typeof customElements === "undefined" || typeof document === "undefined") {
536
+ return null;
537
+ }
538
+ const overrides = this.componentOverrides;
539
+ if (overrides.toolComponentFactories?.[toolId])
540
+ return null;
541
+ let tagName;
542
+ try {
543
+ tagName = resolveToolTag(toolId, overrides);
544
+ }
545
+ catch {
546
+ // Element creation reports the missing tag mapping.
547
+ return null;
548
+ }
549
+ return customElements.get(tagName) ? null : tagName;
550
+ }
551
+ /**
552
+ * Warn when a tool's module loader finished without defining the element the
553
+ * tag map points the tool at, as when a host remaps the tag but keeps a
554
+ * loader that defines the packaged element. The tool otherwise renders blank.
555
+ */
556
+ warnIfLoadedToolElementUndefined(toolId) {
557
+ const tagName = this.undefinedToolElementTag(toolId);
558
+ if (!tagName)
559
+ return;
560
+ warnOncePerDocument(document, `loadedToolElementUndefined.${toolId}.${tagName}`, `[ToolRegistry] Tool "${toolId}" renders <${tagName}>, but its module loader finished without defining it, so the tool renders blank. The tag map and the loader disagree: map "${toolId}" to the tag its loader defines, or register a loader that defines <${tagName}>. Reported once per page.`);
561
+ }
562
+ /**
563
+ * Warn when a tool with no module loader still has no element definition
564
+ * after the pending-input delay. A registry built without loaders otherwise
565
+ * renders the tool as an unknown element with no error.
566
+ */
567
+ watchUndefinedToolElement(toolId) {
568
+ if (this.watchedUndefinedToolElements.has(toolId))
569
+ return;
570
+ const tagName = this.undefinedToolElementTag(toolId);
571
+ if (!tagName)
572
+ return;
573
+ this.watchedUndefinedToolElements.add(toolId);
574
+ const doc = document;
575
+ const timer = setTimeout(() => {
576
+ if (customElements.get(tagName))
577
+ return;
578
+ warnOncePerDocument(doc, `undefinedToolElement.${toolId}`, `[ToolRegistry] Tool "${toolId}" renders <${tagName}>, which is still undefined after ${PENDING_INPUT_WARNING_DELAY_MS / 1000} s, and its registry has no module loader for it. Pass toolModuleLoaders to createPackagedToolRegistry (DEFAULT_TOOL_MODULE_LOADERS from @pie-players/pie-default-tool-loaders loads the stock tools), register one with setToolModuleLoaders, or import the tool's package before it renders. Reported once per page.`);
579
+ }, PENDING_INPUT_WARNING_DELAY_MS);
580
+ void customElements.whenDefined(tagName).then(() => clearTimeout(timer));
581
+ }
582
+ /**
583
+ * Load a set of tool modules, each to completion, and resolve with the error
584
+ * of every tool whose module failed, by tool id. One failure leaves the other
585
+ * tools loaded and usable. A failed tool is retried on the next call.
602
586
  */
603
587
  async ensureToolModulesLoaded(toolIds) {
604
- await Promise.all(toolIds.map((toolId) => this.ensureToolModuleLoaded(toolId)));
588
+ const results = await Promise.allSettled(toolIds.map((toolId) => this.ensureToolModuleLoaded(toolId)));
589
+ const failures = new Map();
590
+ results.forEach((result, index) => {
591
+ if (result.status === "rejected")
592
+ failures.set(toolIds[index], result.reason);
593
+ });
594
+ return failures;
605
595
  }
606
596
  /**
607
597
  * Whether a tool module has already been loaded.