@pie-players/pie-players-shared 0.3.73 → 0.3.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +30 -2
  2. package/dist/components/vendor/nds/README.md +7 -5
  3. package/dist/config/profile.d.ts +0 -2
  4. package/dist/config/profile.js +2 -8
  5. package/dist/i18n/index.d.ts +2 -3
  6. package/dist/i18n/index.js +2 -3
  7. package/dist/i18n/messages/en-US.d.ts +11 -24
  8. package/dist/i18n/messages/en-US.js +13 -26
  9. package/dist/i18n/messages/nl-NL.d.ts +7 -24
  10. package/dist/i18n/messages/nl-NL.js +9 -26
  11. package/dist/i18n/provider.d.ts +4 -5
  12. package/dist/i18n/provider.js +4 -5
  13. package/dist/i18n/types.d.ts +4 -5
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.js +3 -1
  16. package/dist/instrumentation/index.d.ts +5 -4
  17. package/dist/instrumentation/index.js +5 -4
  18. package/dist/instrumentation/new-relic-agent.d.ts +20 -0
  19. package/dist/instrumentation/new-relic-agent.js +24 -0
  20. package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +5 -4
  21. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +7 -7
  22. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +21 -32
  23. package/dist/instrumentation/types.d.ts +7 -3
  24. package/dist/loader-config.d.ts +16 -6
  25. package/dist/loaders/controller-loading.d.ts +14 -0
  26. package/dist/loaders/controller-loading.js +12 -0
  27. package/dist/loaders/defaults.d.ts +10 -0
  28. package/dist/loaders/defaults.js +10 -0
  29. package/dist/loaders/element-loader.d.ts +20 -8
  30. package/dist/loaders/element-loader.js +102 -36
  31. package/dist/loaders/element-package-policy.d.ts +2 -0
  32. package/dist/loaders/element-package-policy.js +2 -1
  33. package/dist/loaders/esm-adapter.d.ts +29 -0
  34. package/dist/loaders/esm-adapter.js +635 -102
  35. package/dist/loaders/iife-adapter.d.ts +0 -5
  36. package/dist/loaders/iife-adapter.js +3 -9
  37. package/dist/loaders/index.d.ts +12 -3
  38. package/dist/loaders/index.js +9 -3
  39. package/dist/loaders/mathjax-events.d.ts +22 -0
  40. package/dist/loaders/mathjax-events.js +65 -0
  41. package/dist/loaders/module-shim.d.ts +12 -0
  42. package/dist/loaders/module-shim.js +12 -0
  43. package/dist/loaders/preloaded-alignment.d.ts +24 -0
  44. package/dist/loaders/preloaded-alignment.js +57 -0
  45. package/dist/loaders/preloaded-authored-tags.d.ts +26 -0
  46. package/dist/loaders/preloaded-authored-tags.js +69 -0
  47. package/dist/loaders/preloaded-registration.d.ts +72 -0
  48. package/dist/loaders/preloaded-registration.js +141 -0
  49. package/dist/media/index.d.ts +69 -0
  50. package/dist/media/index.js +194 -0
  51. package/dist/pie/authoring.d.ts +1 -1
  52. package/dist/pie/authoring.js +25 -18
  53. package/dist/pie/element-announcements.d.ts +39 -0
  54. package/dist/pie/element-announcements.js +67 -0
  55. package/dist/pie/element-observer.d.ts +3 -1
  56. package/dist/pie/element-observer.js +1 -0
  57. package/dist/pie/elements-rendered.d.ts +25 -0
  58. package/dist/pie/elements-rendered.js +96 -0
  59. package/dist/pie/index.d.ts +8 -10
  60. package/dist/pie/index.js +9 -10
  61. package/dist/pie/initialization.js +13 -22
  62. package/dist/pie/initialize-element.d.ts +3 -0
  63. package/dist/pie/initialize-element.js +3 -4
  64. package/dist/pie/instrumentation-event-bridge.js +23 -1
  65. package/dist/pie/instrumentation-event-map.js +11 -25
  66. package/dist/pie/instrumentation-provider-resolution.d.ts +2 -1
  67. package/dist/pie/instrumentation-provider-resolution.js +0 -1
  68. package/dist/pie/item-controller.js +4 -0
  69. package/dist/pie/item-session-contract.d.ts +8 -0
  70. package/dist/pie/item-session-contract.js +30 -9
  71. package/dist/pie/logger.d.ts +6 -0
  72. package/dist/pie/logger.js +10 -0
  73. package/dist/pie/markup-math.d.ts +20 -0
  74. package/dist/pie/markup-math.js +118 -0
  75. package/dist/pie/math-assets.d.ts +48 -0
  76. package/dist/pie/math-assets.js +81 -0
  77. package/dist/pie/math-rendering.d.ts +10 -21
  78. package/dist/pie/math-rendering.js +50 -35
  79. package/dist/pie/registry.d.ts +13 -1
  80. package/dist/pie/registry.js +39 -1
  81. package/dist/pie/resource-monitor.d.ts +6 -33
  82. package/dist/pie/resource-monitor.js +14 -213
  83. package/dist/pie/scoring.d.ts +12 -8
  84. package/dist/pie/scoring.js +21 -24
  85. package/dist/pie/session-commit.d.ts +16 -0
  86. package/dist/pie/session-commit.js +34 -6
  87. package/dist/pie/session-snapshot.d.ts +80 -0
  88. package/dist/pie/session-snapshot.js +166 -0
  89. package/dist/pie/stages.d.ts +10 -47
  90. package/dist/pie/stages.js +10 -47
  91. package/dist/pie/tag-names.d.ts +1 -0
  92. package/dist/pie/tag-names.js +1 -0
  93. package/dist/pie/types.d.ts +4 -5
  94. package/dist/pie/types.js +1 -0
  95. package/dist/pie/updates.d.ts +2 -2
  96. package/dist/pie/updates.js +33 -15
  97. package/dist/pie/versioned-tag.d.ts +4 -0
  98. package/dist/pie/versioned-tag.js +4 -0
  99. package/dist/security/index.d.ts +2 -3
  100. package/dist/security/index.js +2 -3
  101. package/dist/security/sanitize-item-markup.d.ts +4 -2
  102. package/dist/security/sanitize-item-markup.js +48 -6
  103. package/dist/security/sanitize-svg-icon.d.ts +0 -2
  104. package/dist/security/sanitize-svg-icon.js +0 -5
  105. package/dist/security/wrap-overwide-images.js +19 -0
  106. package/dist/security/wrap-overwide.d.ts +13 -0
  107. package/dist/security/wrap-overwide.js +63 -3
  108. package/dist/timed-media/policy.js +5 -16
  109. package/dist/types/index.d.ts +67 -423
  110. package/dist/types/index.js +0 -68
  111. package/dist/ui/attribute-coercion.d.ts +12 -0
  112. package/dist/ui/attribute-coercion.js +33 -0
  113. package/dist/ui/content-styles.d.ts +18 -16
  114. package/dist/ui/content-styles.js +67 -36
  115. package/dist/ui/overlay-containment.d.ts +31 -0
  116. package/dist/ui/overlay-containment.js +63 -0
  117. package/dist/ui/overlay-placement.d.ts +110 -0
  118. package/dist/ui/overlay-placement.js +222 -0
  119. package/dist/ui/pointer-gesture.d.ts +32 -0
  120. package/dist/ui/pointer-gesture.js +56 -0
  121. package/dist/ui/pointer-rotate.d.ts +29 -0
  122. package/dist/ui/pointer-rotate.js +39 -0
  123. package/package.json +23 -18
  124. package/dist/pie/component-context.d.ts +0 -21
  125. package/dist/pie/component-context.js +0 -29
  126. package/dist/pie/player-initializer.d.ts +0 -54
  127. package/dist/pie/player-initializer.js +0 -122
  128. package/dist/pie/stage-tracker.d.ts +0 -50
  129. package/dist/pie/stage-tracker.js +0 -105
  130. package/dist/security/wrap-model-rich-content.d.ts +0 -1
  131. package/dist/security/wrap-model-rich-content.js +0 -40
  132. package/dist/server/npm-auth-env.d.ts +0 -13
  133. package/dist/server/npm-auth-env.js +0 -64
  134. package/dist/server/npm-registry.d.ts +0 -7
  135. package/dist/server/npm-registry.js +0 -59
  136. package/dist/ui/debug-panel-persistence.d.ts +0 -17
  137. package/dist/ui/debug-panel-persistence.js +0 -37
@@ -0,0 +1,96 @@
1
+ /**
2
+ * When the PIE elements of a player have rendered.
3
+ *
4
+ * An element renders after its model and session are assigned, in a task of
5
+ * its own: legacy React elements debounce the render and commit through the
6
+ * React scheduler, so the commit lands tens of milliseconds after the
7
+ * assignment. No event marks it in both element generations — `model-set` is
8
+ * dispatched by about half of them, some before rendering — so this reads the
9
+ * DOM.
10
+ *
11
+ * An element has rendered once it holds content, and so has every custom
12
+ * element defined below it: an `ebsr` paints its parts, which then render
13
+ * themselves. Some elements render nothing, a rubric for a student among them,
14
+ * so an element still empty when the subtree has had no mutation for
15
+ * `quietMs` counts as rendered. A render slower than that window, with no
16
+ * mutation before it, is missed rather than waited for.
17
+ */
18
+ /** Longer than the 50ms render debounce legacy elements use, plus a scheduler task. */
19
+ export const ELEMENT_RENDER_QUIET_MS = 200;
20
+ function hasContent(element) {
21
+ if (element.firstElementChild || element.shadowRoot?.firstChild)
22
+ return true;
23
+ return Boolean(element.textContent?.trim());
24
+ }
25
+ function isDefinedCustomElement(element) {
26
+ return (element.localName.includes("-") &&
27
+ customElements.get(element.localName) !== undefined);
28
+ }
29
+ function hasRendered(element) {
30
+ if (!element.isConnected)
31
+ return true;
32
+ if (!hasContent(element))
33
+ return false;
34
+ for (const nested of element.querySelectorAll("*")) {
35
+ if (isDefinedCustomElement(nested) && !hasContent(nested))
36
+ return false;
37
+ }
38
+ return true;
39
+ }
40
+ /** The elements of `configs` in `root`, found as `updatePieElements` finds them. */
41
+ function renderedElements(root, configs) {
42
+ const elements = [];
43
+ for (const config of configs) {
44
+ const ids = new Set((config.models ?? []).map((model) => model?.id));
45
+ for (const tag of Object.keys(config.elements ?? {})) {
46
+ // An undefined tag renders nothing until its bundle loads.
47
+ if (!customElements.get(tag))
48
+ continue;
49
+ for (const element of root.querySelectorAll(tag)) {
50
+ if (ids.has(element.id))
51
+ elements.push(element);
52
+ }
53
+ }
54
+ }
55
+ return elements;
56
+ }
57
+ /**
58
+ * Resolves once every element of `configs` in `root` has rendered, or when
59
+ * `signal` aborts. Never rejects.
60
+ */
61
+ export function elementsRendered(root, configs, signal, quietMs = ELEMENT_RENDER_QUIET_MS) {
62
+ const elements = renderedElements(root, configs);
63
+ const allRendered = () => elements.every(hasRendered);
64
+ if (signal.aborted ||
65
+ allRendered() ||
66
+ typeof MutationObserver === "undefined") {
67
+ return Promise.resolve();
68
+ }
69
+ return new Promise((resolve) => {
70
+ let quiet;
71
+ const observer = new MutationObserver(() => {
72
+ if (allRendered()) {
73
+ done();
74
+ return;
75
+ }
76
+ armQuiet();
77
+ });
78
+ function armQuiet() {
79
+ clearTimeout(quiet);
80
+ quiet = setTimeout(done, quietMs);
81
+ }
82
+ function done() {
83
+ clearTimeout(quiet);
84
+ observer.disconnect();
85
+ signal.removeEventListener("abort", done);
86
+ resolve();
87
+ }
88
+ observer.observe(root, {
89
+ childList: true,
90
+ subtree: true,
91
+ characterData: true,
92
+ });
93
+ signal.addEventListener("abort", done);
94
+ armQuiet();
95
+ });
96
+ }
@@ -14,28 +14,26 @@ export { observePieElements, pieElementContextsWithin, } from "./element-observe
14
14
  export type { PieElementContext, PieElementContextSource, } from "./element-observer.js";
15
15
  export { createAuthoringAssetEventManager, initializeAuthoringConfigures, resolveConfigureConfiguration, validateAuthoringModels, } from "./authoring.js";
16
16
  export type { AuthoringMediaHandlers, AuthoringValidationResult, InitializedConfigureModel, } from "./authoring.js";
17
- export { initializeMathRendering, renderMath, setMathRenderer, } from "./math-rendering.js";
17
+ export { initializeMathRendering, setMathRenderer, } from "./math-rendering.js";
18
18
  export { attachInstrumentationEventBridge } from "./instrumentation-event-bridge.js";
19
19
  export type { AttachInstrumentationEventBridgeArgs } from "./instrumentation-event-bridge.js";
20
20
  export { resolveInstrumentationProvider } from "./instrumentation-provider-resolution.js";
21
- export { STAGES, applicableStages, stageOrdinal, } from "./stages.js";
22
- export type { LoadingCompleteDetail, Stage, StageChangeDetail, StageSourceCe, StageStatus, } from "./stages.js";
23
- export { createStageTracker } from "./stage-tracker.js";
24
- export type { CreateStageTrackerOptions, StageTracker, } from "./stage-tracker.js";
21
+ export { STAGES } from "./stages.js";
22
+ export type { LoadingCompleteDetail, Stage, StageChangeDetail, StageStatus, } from "./stages.js";
25
23
  export { ASSESSMENT_INSTRUMENTATION_EVENT_MAP, ITEM_INSTRUMENTATION_EVENT_MAP, SECTION_INSTRUMENTATION_EVENT_MAP, TOOLKIT_INSTRUMENTATION_EVENT_MAP, } from "./instrumentation-event-map.js";
26
24
  export type { InstrumentationEventMapping } from "./instrumentation-event-map.js";
27
25
  export type { ItemControllerOptions } from "./item-controller.js";
28
26
  export { ItemController, normalizeItemSessionContainer, } from "./item-controller.js";
29
27
  export type { ItemSessionContainer as CanonicalItemSessionContainer, ItemSessionUpdateIntent, NormalizedItemSessionChange, } from "./item-session-contract.js";
30
- export { ensureHostSessionEntries, hasLearnerResponse, hasResponseValue, normalizeItemSessionChange, projectSessionIntoHostContainer, } from "./item-session-contract.js";
28
+ export { ensureHostSessionEntries, hasLearnerResponse, hasResponseField, hasResponseValue, normalizeItemSessionChange, projectSessionIntoHostContainer, } from "./item-session-contract.js";
31
29
  export type { ItemSessionContainer, ItemSessionStorageStrategy, } from "./item-controller-storage.js";
32
30
  export { MemoryItemSessionStorage, SessionStorageItemSessionStorage, } from "./item-controller-storage.js";
33
- export { SESSION_COMMIT_METHOD, bindPageLifecycleCommit, commitPendingSessions, noteSessionBaseline, noteSessionObserved, } from "./session-commit.js";
31
+ export { SESSION_COMMIT_METHOD, bindPageLifecycleCommit, commitPendingSessions, flushPendingSessionNotifications, noteSessionBaseline, noteSessionObserved, } from "./session-commit.js";
34
32
  export type { BindPageLifecycleCommitOptions, CommitPendingSessionsOptions, CommitPendingSessionsResult, SessionCommitReason, } from "./session-commit.js";
33
+ export { createSessionSnapshot, isSessionSnapshotEnabled, isSnapshotIdentitySpecific, sessionSnapshotKey, sessionStorageSnapshotStore, } from "./session-snapshot.js";
34
+ export type { SessionSnapshot, SessionSnapshotConfig, SessionSnapshotIdentity, SessionSnapshotRecord, SessionSnapshotStore, } from "./session-snapshot.js";
35
35
  export type { PieLogger } from "./logger.js";
36
- export { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
37
- export type { ItemData, PiePlayerConfig, PiePlayerElements, } from "./player-initializer.js";
38
- export { buildApiParams, buildEventListenersMap, ensurePiePlayerLoaded, extractPassageMarkup, fetchItemData, initializePiePlayer, } from "./player-initializer.js";
36
+ export { createPieLogger, isGlobalDebugEnabled, isTtsDebugEnabled, } from "./logger.js";
39
37
  export { pieRegistry } from "./registry.js";
40
38
  export { findPieController, scorePieItem } from "./scoring.js";
41
39
  export type { Entry, EventListeners, EventListenersMap, LoadPieElementsOptions, PieElement, PieRegistry, UpdatePieElementOptions, } from "./types.js";
package/dist/pie/index.js CHANGED
@@ -20,21 +20,20 @@ export { initializePiesFromLoadedBundle, loadBundleFromString, loadPieModule, lo
20
20
  // owns.
21
21
  export { observePieElements, pieElementContextsWithin, } from "./element-observer.js";
22
22
  export { createAuthoringAssetEventManager, initializeAuthoringConfigures, resolveConfigureConfiguration, validateAuthoringModels, } from "./authoring.js";
23
- export { initializeMathRendering, renderMath, setMathRenderer, } from "./math-rendering.js";
23
+ export { initializeMathRendering, setMathRenderer, } from "./math-rendering.js";
24
24
  export { attachInstrumentationEventBridge } from "./instrumentation-event-bridge.js";
25
25
  export { resolveInstrumentationProvider } from "./instrumentation-provider-resolution.js";
26
- // Canonical stage vocabulary (M6). Both `<pie-section-player-*>` and
27
- // `<pie-assessment-toolkit>` share this primitive so the
28
- // `pie-stage-change` event family stays coherent across CE shapes.
29
- export { STAGES, applicableStages, stageOrdinal, } from "./stages.js";
30
- export { createStageTracker } from "./stage-tracker.js";
26
+ // Stage vocabulary of the section player's `pie-stage-change` event.
27
+ export { STAGES } from "./stages.js";
31
28
  export { ASSESSMENT_INSTRUMENTATION_EVENT_MAP, ITEM_INSTRUMENTATION_EVENT_MAP, SECTION_INSTRUMENTATION_EVENT_MAP, TOOLKIT_INSTRUMENTATION_EVENT_MAP, } from "./instrumentation-event-map.js";
32
29
  export { ItemController, normalizeItemSessionContainer, } from "./item-controller.js";
33
- export { ensureHostSessionEntries, hasLearnerResponse, hasResponseValue, normalizeItemSessionChange, projectSessionIntoHostContainer, } from "./item-session-contract.js";
30
+ export { ensureHostSessionEntries, hasLearnerResponse, hasResponseField, hasResponseValue, normalizeItemSessionChange, projectSessionIntoHostContainer, } from "./item-session-contract.js";
34
31
  export { MemoryItemSessionStorage, SessionStorageItemSessionStorage, } from "./item-controller-storage.js";
35
- export { SESSION_COMMIT_METHOD, bindPageLifecycleCommit, commitPendingSessions, noteSessionBaseline, noteSessionObserved, } from "./session-commit.js";
36
- export { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
37
- export { buildApiParams, buildEventListenersMap, ensurePiePlayerLoaded, extractPassageMarkup, fetchItemData, initializePiePlayer, } from "./player-initializer.js";
32
+ export { SESSION_COMMIT_METHOD, bindPageLifecycleCommit, commitPendingSessions, flushPendingSessionNotifications, noteSessionBaseline, noteSessionObserved, } from "./session-commit.js";
33
+ // Opt-in device-local snapshot of a committed session, offered back to the host
34
+ // after a crash rather than applied.
35
+ export { createSessionSnapshot, isSessionSnapshotEnabled, isSnapshotIdentitySpecific, sessionSnapshotKey, sessionStorageSnapshotStore, } from "./session-snapshot.js";
36
+ export { createPieLogger, isGlobalDebugEnabled, isTtsDebugEnabled, } from "./logger.js";
38
37
  // Registry
39
38
  export { pieRegistry } from "./registry.js";
40
39
  // Scoring
@@ -11,7 +11,7 @@ import { editorPostFix } from "../types/index.js";
11
11
  import { initializePieElement } from "./initialize-element.js";
12
12
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
13
13
  import { initializeMathRendering } from "./math-rendering.js";
14
- import { pieRegistry } from "./registry.js";
14
+ import { pieRegistry, writeRegistryEntry } from "./registry.js";
15
15
  import { defineCustomElementSafely } from "./custom-element-define.js";
16
16
  import { validateCustomElementTag } from "./tag-names.js";
17
17
  import { BundleType, isCustomElementConstructor, isPieAvailable, Status, } from "./types.js";
@@ -38,6 +38,7 @@ const updateRegisteredElement = (elementTagName, config, session, options, omitE
38
38
  config,
39
39
  session,
40
40
  ...(omitEnv ? {} : { env: options.env }),
41
+ bundleType: options.bundleType,
41
42
  container: options.container,
42
43
  ...(options.eventListeners?.[elementTagName] && {
43
44
  eventListeners: options.eventListeners[elementTagName],
@@ -119,30 +120,21 @@ const registerPieElementsFromBundle = (elementModule, config, session, registry,
119
120
  : [],
120
121
  bundleType: options.bundleType,
121
122
  });
122
- // Validate controller presence based on bundle type
123
- if (!elementData.controller) {
124
- if (options.bundleType === BundleType.clientPlayer) {
125
- logger.error(`[registerPieElementsFromBundle] ❌ CRITICAL: No controller found for ${pkgStripped}!`);
126
- logger.error(`[registerPieElementsFromBundle] Bundle type: ${options.bundleType} (controllers required)`);
127
- throw new Error(`No controller found for ${pkgStripped}. client-player.js bundles MUST include controllers!`);
128
- }
129
- else {
130
- logger.debug(`[registerPieElementsFromBundle] ℹ️ No controller found for ${pkgStripped} - using server-processed models (player.js bundle)`);
131
- }
132
- }
133
123
  {
134
- // Register the element in our registry
124
+ // Register the element in our registry. A package without a
125
+ // controller, such as a legacy `@pie-element/protractor`, renders
126
+ // the model it is given under client-player.js too.
135
127
  logger.debug(`[registerPieElementsFromBundle] Registering ${elName} in registry${elementData.controller
136
128
  ? " with controller"
137
- : " (no controller - server-processed models)"}`);
138
- registry[elementTagName] = {
129
+ : " (no controller; its model is used as given)"}`);
130
+ writeRegistryEntry({
139
131
  package: pkg,
140
132
  status: Status.loading,
141
133
  tagName: elementTagName,
142
134
  controller: elementData.controller || null,
143
135
  config: elementData.config,
144
136
  bundleType: options.bundleType,
145
- };
137
+ });
146
138
  if (isCustomElementConstructor(elementData.Element)) {
147
139
  defineCustomElementSafely(elementTagName, elementData.Element, `element tag in config.elements for ${String(pkg)}`);
148
140
  // Initialize existing elements
@@ -155,13 +147,13 @@ const registerPieElementsFromBundle = (elementModule, config, session, registry,
155
147
  session,
156
148
  env: options.env,
157
149
  eventListeners: options.eventListeners?.[elementTagName],
150
+ bundleType: options.bundleType,
158
151
  });
159
152
  });
160
- // Update registry status
161
- registry[elementTagName] = {
153
+ writeRegistryEntry({
162
154
  ...registry[elementTagName],
163
155
  status: Status.loaded,
164
- };
156
+ });
165
157
  promises.push(customElements.whenDefined(elementTagName).then(() => {
166
158
  logger.debug("[registerPieElementsFromBundle] defined custom PIE element: %s", elementTagName);
167
159
  }));
@@ -297,9 +289,8 @@ export const loadPieModule = async (config, session, opts = {}) => {
297
289
  }
298
290
  catch (error) {
299
291
  // `registerPieElementsFromBundle` throws synchronously for a
300
- // package missing from the bundle and for a client-player
301
- // bundle with no controller. Inside a DOM event handler that
302
- // throw reaches the window instead of the caller.
292
+ // package missing from the bundle. Inside a DOM event handler
293
+ // that throw reaches the window instead of the caller.
303
294
  fail(error instanceof Error ? error : new Error(String(error)));
304
295
  }
305
296
  });
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { ConfigEntity, Env } from "../types/index.js";
8
8
  import type { EventListeners, PieElement } from "./types.js";
9
+ import { BundleType } from "./types.js";
9
10
  /**
10
11
  * Bind `element` to the model in `options.config` whose `id` matches it.
11
12
  *
@@ -20,4 +21,6 @@ export declare const initializePieElement: (element: PieElement, options: {
20
21
  session: any[];
21
22
  env?: Env;
22
23
  eventListeners?: EventListeners;
24
+ /** The delivering player's bundle type, see `findPieController`. */
25
+ bundleType?: BundleType;
23
26
  }) => boolean;
@@ -4,7 +4,6 @@
4
4
  * Split out of `initialization.ts` so the late-arrival observer in
5
5
  * `element-observer.ts` can reuse it without importing the bundle loaders.
6
6
  */
7
- import { wrapModelRichContent } from "../security/wrap-model-rich-content.js";
8
7
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
9
8
  import { pieRegistry } from "./registry.js";
10
9
  import { findPieController } from "./scoring.js";
@@ -21,7 +20,7 @@ const logger = createPieLogger("pie-initialize-element", () => isGlobalDebugEnab
21
20
  * separately — uses that to stop at the config the element belongs to.
22
21
  */
23
22
  export const initializePieElement = (element, options) => {
24
- const { config, session, env, eventListeners } = options;
23
+ const { config, session, env, eventListeners, bundleType } = options;
25
24
  if (element.__pieInitialized) {
26
25
  return true;
27
26
  }
@@ -45,7 +44,7 @@ export const initializePieElement = (element, options) => {
45
44
  element.__pieInitialized = true;
46
45
  logger.debug(`[initializePieElement] Session set for ${tagName}#${element.id}:`, elementSession);
47
46
  // Set model - use controller if available (client-player.js), or use server-processed model (player.js)
48
- const controller = findPieController(tagName);
47
+ const controller = findPieController(tagName, bundleType);
49
48
  if (!env) {
50
49
  logger.error(`[initializePieElement] ❌ FATAL: No env provided for ${tagName}`);
51
50
  throw new Error(`No env provided for ${tagName}. PIE elements require an env object with mode and role.`);
@@ -61,7 +60,7 @@ export const initializePieElement = (element, options) => {
61
60
  role: env.role,
62
61
  });
63
62
  // Set model directly - server already processed it
64
- element.model = wrapModelRichContent(model);
63
+ element.model = model;
65
64
  }
66
65
  else {
67
66
  // Controller available - run client-side processing (client-player.js bundle)
@@ -1,9 +1,31 @@
1
1
  import { isInstrumentationProvider } from "../instrumentation/provider-guards.js";
2
2
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
3
3
  const logger = createPieLogger("instrumentation-event-bridge", () => isGlobalDebugEnabled());
4
+ /**
5
+ * Telemetry attributes are data. A detail's live objects (a coordinator on
6
+ * `toolkit-ready`, an `Error` cause, a function) stay on the event for its
7
+ * listeners and are left out of the attributes; primitives, arrays and plain
8
+ * objects pass.
9
+ */
10
+ function isAttributeValue(value) {
11
+ if (value === null)
12
+ return true;
13
+ const type = typeof value;
14
+ if (type === "function" || type === "symbol")
15
+ return false;
16
+ if (type !== "object" || Array.isArray(value))
17
+ return true;
18
+ const prototype = Object.getPrototypeOf(value);
19
+ return prototype === Object.prototype || prototype === null;
20
+ }
4
21
  function normalizeEventDetail(detail) {
5
22
  if (detail && typeof detail === "object") {
6
- return detail;
23
+ const attributes = {};
24
+ for (const [key, value] of Object.entries(detail)) {
25
+ if (isAttributeValue(value))
26
+ attributes[key] = value;
27
+ }
28
+ return attributes;
7
29
  }
8
30
  if (typeof detail === "undefined") {
9
31
  return {};
@@ -1,15 +1,7 @@
1
1
  export const TOOLKIT_INSTRUMENTATION_EVENT_MAP = [
2
- // M6 canonical readiness vocabulary. The four-stage canonical list
3
- // (`composed`, `engine-ready`, `interactive`, `disposed`) is identical
4
- // across the toolkit CE and the layout CEs — the M6 retro removed the
5
- // `attached`, `runtime-bound`, and `ui-rendered` stages because they
6
- // had zero internal or external consumers. Hosts listen for the
7
- // DOM-prefixed name directly (`pie-stage-change`); the instrumentation
8
- // bridge forwards the same name to telemetry.
9
- {
10
- sourceEventName: "pie-stage-change",
11
- instrumentationEventName: "pie-toolkit-stage-change",
12
- },
2
+ // The toolkit emits no stage events: the section player's runtime
3
+ // engine dispatches `pie-stage-change` on the layout element, above the
4
+ // toolkit, and the section map tracks it there.
13
5
  {
14
6
  sourceEventName: "runtime-owned",
15
7
  instrumentationEventName: "pie-toolkit-runtime-owned",
@@ -18,6 +10,10 @@ export const TOOLKIT_INSTRUMENTATION_EVENT_MAP = [
18
10
  sourceEventName: "runtime-inherited",
19
11
  instrumentationEventName: "pie-toolkit-runtime-inherited",
20
12
  },
13
+ {
14
+ sourceEventName: "runtime-ready",
15
+ instrumentationEventName: "pie-toolkit-runtime-ready",
16
+ },
21
17
  {
22
18
  sourceEventName: "toolkit-ready",
23
19
  instrumentationEventName: "pie-toolkit-ready",
@@ -32,12 +28,10 @@ export const TOOLKIT_INSTRUMENTATION_EVENT_MAP = [
32
28
  },
33
29
  ];
34
30
  export const SECTION_INSTRUMENTATION_EVENT_MAP = [
35
- // M6 canonical readiness vocabulary. Hosts listen for the DOM-prefixed
36
- // names directly (`pie-stage-change`, `pie-loading-complete`); the
37
- // instrumentation bridge forwards the same names to telemetry. The
38
- // readiness alias mappings (`readiness-change`, `interaction-ready`,
39
- // `ready`) and the `section-controller-ready` mapping were removed
40
- // alongside their DOM-event surfaces.
31
+ // The runtime engine dispatches `pie-stage-change` and
32
+ // `pie-loading-complete` on the layout element; `framework-error`
33
+ // bubbles to it from the player's toolkit. `session-changed` stays off
34
+ // the bridge for the reason the item map gives below.
41
35
  {
42
36
  sourceEventName: "pie-stage-change",
43
37
  instrumentationEventName: "pie-section-stage-change",
@@ -46,14 +40,6 @@ export const SECTION_INSTRUMENTATION_EVENT_MAP = [
46
40
  sourceEventName: "pie-loading-complete",
47
41
  instrumentationEventName: "pie-section-loading-complete",
48
42
  },
49
- {
50
- sourceEventName: "session-changed",
51
- instrumentationEventName: "pie-section-session-changed",
52
- },
53
- {
54
- sourceEventName: "composition-changed",
55
- instrumentationEventName: "pie-section-composition-changed",
56
- },
57
43
  {
58
44
  sourceEventName: "framework-error",
59
45
  instrumentationEventName: "pie-section-framework-error",
@@ -1,6 +1,7 @@
1
+ import type { InstrumentationProvider } from "../instrumentation/types.js";
1
2
  export declare function resolveInstrumentationProvider(args: {
2
3
  runtimePlayer?: unknown;
3
4
  player?: unknown;
4
5
  debug?: boolean;
5
6
  component?: string;
6
- }): unknown;
7
+ }): InstrumentationProvider | undefined;
@@ -37,7 +37,6 @@ const logger = createPieLogger("instrumentation-provider-resolution", () => isGl
37
37
  function getDefaultInstrumentationProvider() {
38
38
  if (!defaultProvider) {
39
39
  defaultProvider = new NewRelicInstrumentationProvider();
40
- // New Relic provider sets readiness based on window.newrelic.
41
40
  void defaultProvider.initialize();
42
41
  }
43
42
  return defaultProvider;
@@ -45,7 +45,11 @@ export class ItemController {
45
45
  setSession(input, options = {}) {
46
46
  const next = normalizeSessionContainer(input, this.sessionId);
47
47
  const allowMetadataOverwrite = options.allowMetadataOverwrite ?? false;
48
+ // The guard keeps a response from metadata churn on the same session. A
49
+ // session with another id is a new attempt or item, and replaces it.
50
+ const sameSession = next.id === "" || this.session.id === "" || next.id === this.session.id;
48
51
  if (!allowMetadataOverwrite &&
52
+ sameSession &&
49
53
  hasResponseValue(this.session) &&
50
54
  !hasResponseValue(next) &&
51
55
  !hasResponseField(next)) {
@@ -1,3 +1,4 @@
1
+ import type { SessionCommitReason } from "./session-commit.js";
1
2
  export type ItemSessionContainer = {
2
3
  id: string;
3
4
  data: unknown[];
@@ -8,7 +9,14 @@ export type NormalizedItemSessionChange = {
8
9
  session: ItemSessionContainer | null;
9
10
  intent: ItemSessionUpdateIntent;
10
11
  component?: string;
12
+ /**
13
+ * The id of the element whose change this is: the detail's `elementId`, or
14
+ * the `id` of an element session record carried as `session`.
15
+ */
16
+ elementId?: string;
11
17
  complete?: boolean;
18
+ /** The detail's `sessionCommitReason`, when the change is a commit. */
19
+ sessionCommitReason?: SessionCommitReason;
12
20
  };
13
21
  export declare function normalizeItemSessionContainer(input: unknown, fallbackSessionId?: string): ItemSessionContainer;
14
22
  export declare function hasResponseValue(value: unknown): boolean;
@@ -2,6 +2,7 @@ const DEFAULT_SESSION_ID = "";
2
2
  const METADATA_ONLY_SESSION_KEYS = new Set([
3
3
  "complete",
4
4
  "component",
5
+ "elementId",
5
6
  "timestamp",
6
7
  "sourceRuntimeId",
7
8
  ]);
@@ -162,6 +163,18 @@ function getMetadataComplete(sessionDetail, candidate) {
162
163
  return candidate.complete;
163
164
  return undefined;
164
165
  }
166
+ /**
167
+ * `elementRecord` is an element's own session record, whose `id` is the
168
+ * element's; an item session container's `id` is the item session's.
169
+ */
170
+ function withElementId(sessionDetail, elementRecord) {
171
+ const elementId = typeof sessionDetail.elementId === "string" && sessionDetail.elementId
172
+ ? sessionDetail.elementId
173
+ : typeof elementRecord?.id === "string" && elementRecord.id
174
+ ? elementRecord.id
175
+ : undefined;
176
+ return elementId ? { elementId } : {};
177
+ }
165
178
  function isElementIdentityOnlyPayload(candidate) {
166
179
  const candidateKeys = Object.keys(candidate);
167
180
  const hasIdentityKey = "id" in candidate || "element" in candidate;
@@ -211,6 +224,14 @@ export function mergeElementIntoSession(itemId, previousItemSession, entryId, en
211
224
  };
212
225
  }
213
226
  export function normalizeItemSessionChange(args) {
227
+ const change = classifyItemSessionChange(args);
228
+ const reason = args.sessionDetail
229
+ ?.sessionCommitReason;
230
+ return typeof reason === "string" && reason
231
+ ? { ...change, sessionCommitReason: reason }
232
+ : change;
233
+ }
234
+ function classifyItemSessionChange(args) {
214
235
  const sessionDetail = (args.sessionDetail || {});
215
236
  const actualSession = sessionDetail &&
216
237
  typeof sessionDetail === "object" &&
@@ -229,6 +250,7 @@ export function normalizeItemSessionChange(args) {
229
250
  session: null,
230
251
  intent: "metadata-only",
231
252
  component: getMetadataComponent(sessionDetail),
253
+ ...withElementId(sessionDetail),
232
254
  complete: getMetadataComplete(sessionDetail),
233
255
  };
234
256
  }
@@ -241,11 +263,7 @@ export function normalizeItemSessionChange(args) {
241
263
  const previousForMetadataComparison = alignPreviousSessionIdForRendererSnapshot(normalizedCandidate, previousNormalized);
242
264
  const sessionDetailKeys = Object.keys(sessionDetail);
243
265
  const metadataWithSessionOnly = sessionDetailKeys.length > 0 &&
244
- sessionDetailKeys.every((key) => key === "session" ||
245
- key === "complete" ||
246
- key === "component" ||
247
- key === "timestamp" ||
248
- key === "sourceRuntimeId");
266
+ sessionDetailKeys.every((key) => key === "session" || METADATA_ONLY_SESSION_KEYS.has(key));
249
267
  const hasMetadataBeyondSession = sessionDetailKeys.some((key) => key !== "session");
250
268
  if (metadataWithSessionOnly &&
251
269
  hasMetadataBeyondSession &&
@@ -256,6 +274,7 @@ export function normalizeItemSessionChange(args) {
256
274
  session: null,
257
275
  intent: "metadata-only",
258
276
  component: getMetadataComponent(sessionDetail),
277
+ ...withElementId(sessionDetail),
259
278
  complete: getMetadataComplete(sessionDetail),
260
279
  };
261
280
  }
@@ -266,6 +285,7 @@ export function normalizeItemSessionChange(args) {
266
285
  session: null,
267
286
  intent: "metadata-only",
268
287
  component: getMetadataComponent(sessionDetail),
288
+ ...withElementId(sessionDetail),
269
289
  complete: getMetadataComplete(sessionDetail),
270
290
  };
271
291
  }
@@ -274,21 +294,20 @@ export function normalizeItemSessionChange(args) {
274
294
  session: normalizedCandidate,
275
295
  intent: "replace-item-session",
276
296
  component: getMetadataComponent(sessionDetail),
297
+ ...withElementId(sessionDetail),
277
298
  complete: getMetadataComplete(sessionDetail),
278
299
  };
279
300
  }
280
301
  const candidateKeys = Object.keys(candidate);
281
302
  const isMetadataOnlyPayload = candidateKeys.length > 0 &&
282
- candidateKeys.every((key) => key === "complete" ||
283
- key === "component" ||
284
- key === "timestamp" ||
285
- key === "sourceRuntimeId");
303
+ candidateKeys.every((key) => METADATA_ONLY_SESSION_KEYS.has(key));
286
304
  if (isMetadataOnlyPayload) {
287
305
  return {
288
306
  itemId: safeItemId,
289
307
  session: null,
290
308
  intent: "metadata-only",
291
309
  component: getMetadataComponent(sessionDetail, candidate),
310
+ ...withElementId(sessionDetail),
292
311
  complete: getMetadataComplete(sessionDetail, candidate),
293
312
  };
294
313
  }
@@ -298,6 +317,7 @@ export function normalizeItemSessionChange(args) {
298
317
  session: null,
299
318
  intent: "metadata-only",
300
319
  component: getMetadataComponent(sessionDetail, candidate),
320
+ ...withElementId(sessionDetail, candidate),
301
321
  complete: getMetadataComplete(sessionDetail, candidate),
302
322
  };
303
323
  }
@@ -309,6 +329,7 @@ export function normalizeItemSessionChange(args) {
309
329
  session: merged,
310
330
  intent: "merge-element-session",
311
331
  component,
332
+ ...withElementId(sessionDetail, candidate),
312
333
  complete: getMetadataComplete(sessionDetail, candidate),
313
334
  };
314
335
  }
@@ -61,3 +61,9 @@ declare global {
61
61
  * Check if debug mode is enabled globally
62
62
  */
63
63
  export declare function isGlobalDebugEnabled(): boolean;
64
+ /**
65
+ * Read-aloud's debug flag: `PIE_TTS_DEBUG=1` in the environment, or
66
+ * `globalThis.__PIE_TTS_DEBUG__ = true` in a page. Every read-aloud logger is
67
+ * created with it, and reads it on each debug line.
68
+ */
69
+ export declare function isTtsDebugEnabled(): boolean;
@@ -42,3 +42,13 @@ export function createPieLogger(namespace, debugEnabledFn) {
42
42
  export function isGlobalDebugEnabled() {
43
43
  return typeof window !== "undefined" && window.PIE_DEBUG === true;
44
44
  }
45
+ /**
46
+ * Read-aloud's debug flag: `PIE_TTS_DEBUG=1` in the environment, or
47
+ * `globalThis.__PIE_TTS_DEBUG__ = true` in a page. Every read-aloud logger is
48
+ * created with it, and reads it on each debug line.
49
+ */
50
+ export function isTtsDebugEnabled() {
51
+ const scope = globalThis;
52
+ return (scope.process?.env?.PIE_TTS_DEBUG === "1" ||
53
+ scope.__PIE_TTS_DEBUG__ === true);
54
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Math in an item's own markup: what the item and passage markup carry outside
3
+ * every PIE element.
4
+ *
5
+ * Each element typesets its own subtree, so the player hands its renderer only
6
+ * the markup around them. A renderer finds math below the element it is given,
7
+ * so no root holds a PIE element: the markup splits into the elements that hold
8
+ * none, and math sharing a parent with one is wrapped in a span of its own.
9
+ */
10
+ import type { MathRenderer } from "./math-rendering.js";
11
+ /**
12
+ * The parts of `container` holding math and no PIE element, each one a root to
13
+ * typeset. `pieTags` are the tag names of the PIE elements its markup places.
14
+ */
15
+ export declare function collectMarkupMathRoots(container: HTMLElement, pieTags: ReadonlySet<string>): HTMLElement[];
16
+ /**
17
+ * Typesets the math in `containers` outside every PIE element with `render`,
18
+ * one root at a time.
19
+ */
20
+ export declare function typesetMarkupMath(containers: Iterable<HTMLElement>, pieTags: ReadonlySet<string>, render: MathRenderer): Promise<void>;