@pie-players/pie-players-shared 0.3.48 → 0.3.50

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.
@@ -1 +1 @@
1
- {"version":3,"file":"item-session-contract.js","sourceRoot":"","sources":["../../src/pie/item-session-contract.ts"],"names":[],"mappings":"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","sourcesContent":["export type ItemSessionContainer = {\n\tid: string;\n\tdata: unknown[];\n};\n\nexport type ItemSessionUpdateIntent =\n\t| \"replace-item-session\"\n\t| \"merge-element-session\"\n\t| \"metadata-only\";\n\nexport type NormalizedItemSessionChange = {\n\titemId: string;\n\tsession: ItemSessionContainer | null;\n\tintent: ItemSessionUpdateIntent;\n\tcomponent?: string;\n\tcomplete?: boolean;\n};\n\nconst DEFAULT_SESSION_ID = \"\";\n\nfunction areSessionContainersEqual(\n\tleft: ItemSessionContainer | null,\n\tright: ItemSessionContainer | null,\n): boolean {\n\tif (!left && !right) return true;\n\tif (!left || !right) return false;\n\tif ((left.id || \"\") !== (right.id || \"\")) return false;\n\ttry {\n\t\treturn JSON.stringify(left.data || []) === JSON.stringify(right.data || []);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport function normalizeItemSessionContainer(\n\tinput: unknown,\n\tfallbackSessionId: string = DEFAULT_SESSION_ID,\n): ItemSessionContainer {\n\tif (input && typeof input === \"object\") {\n\t\tconst candidate = input as Record<string, unknown>;\n\t\tif (Array.isArray(candidate.data)) {\n\t\t\treturn {\n\t\t\t\tid: typeof candidate.id === \"string\" ? candidate.id : fallbackSessionId,\n\t\t\t\tdata: candidate.data,\n\t\t\t};\n\t\t}\n\t\tif (Array.isArray(input)) {\n\t\t\treturn { id: fallbackSessionId, data: input };\n\t\t}\n\t\treturn { id: fallbackSessionId, data: [input] };\n\t}\n\tif (Array.isArray(input)) {\n\t\treturn { id: fallbackSessionId, data: input };\n\t}\n\treturn { id: fallbackSessionId, data: [] };\n}\n\nexport function hasResponseValue(value: unknown): boolean {\n\tif (value == null) return false;\n\tif (Array.isArray(value))\n\t\treturn value.some((entry) => hasResponseValue(entry));\n\tif (typeof value !== \"object\") return false;\n\tfor (const [key, nested] of Object.entries(\n\t\tvalue as Record<string, unknown>,\n\t)) {\n\t\tif (\n\t\t\tkey === \"value\" &&\n\t\t\tnested !== undefined &&\n\t\t\tnested !== null &&\n\t\t\t!(typeof nested === \"string\" && nested.trim() === \"\") &&\n\t\t\t!(Array.isArray(nested) && nested.length === 0)\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tif (hasResponseValue(nested)) return true;\n\t}\n\treturn false;\n}\n\nexport function hasResponseField(value: unknown): boolean {\n\tif (value == null) return false;\n\tif (Array.isArray(value))\n\t\treturn value.some((entry) => hasResponseField(entry));\n\tif (typeof value !== \"object\") return false;\n\tfor (const [key, nested] of Object.entries(\n\t\tvalue as Record<string, unknown>,\n\t)) {\n\t\tif (key === \"value\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (hasResponseField(nested)) return true;\n\t}\n\treturn false;\n}\n\nexport function mergeElementIntoSession(\n\titemId: string,\n\tpreviousItemSession: unknown,\n\tentryId: string,\n\tentry: Record<string, unknown>,\n): ItemSessionContainer {\n\tconst previous = normalizeItemSessionContainer(previousItemSession, itemId);\n\tconst nextData = [...previous.data];\n\tconst existingIndex = nextData.findIndex((candidate) => {\n\t\tif (!candidate || typeof candidate !== \"object\") return false;\n\t\treturn (candidate as Record<string, unknown>).id === entryId;\n\t});\n\tif (existingIndex >= 0) {\n\t\tconst existing = nextData[existingIndex];\n\t\tnextData[existingIndex] =\n\t\t\texisting && typeof existing === \"object\"\n\t\t\t\t? { ...(existing as Record<string, unknown>), ...entry }\n\t\t\t\t: entry;\n\t} else {\n\t\tnextData.push(entry);\n\t}\n\treturn {\n\t\tid: previous.id || itemId,\n\t\tdata: nextData,\n\t};\n}\n\nexport function normalizeItemSessionChange(args: {\n\titemId: string;\n\tsessionDetail: unknown;\n\tpreviousItemSession?: unknown;\n}): NormalizedItemSessionChange {\n\tconst sessionDetail = (args.sessionDetail || {}) as Record<string, unknown>;\n\tconst actualSession =\n\t\tsessionDetail &&\n\t\ttypeof sessionDetail === \"object\" &&\n\t\t\"session\" in sessionDetail\n\t\t\t? sessionDetail.session\n\t\t\t: args.sessionDetail;\n\n\tconst safeItemId =\n\t\ttypeof args.itemId === \"string\" && args.itemId\n\t\t\t? args.itemId\n\t\t\t: typeof (actualSession as Record<string, unknown> | null)?.id ===\n\t\t\t\t\t\"string\"\n\t\t\t\t? String((actualSession as Record<string, unknown>).id)\n\t\t\t\t: \"\";\n\n\tif (!actualSession || typeof actualSession !== \"object\") {\n\t\treturn {\n\t\t\titemId: safeItemId,\n\t\t\tsession: null,\n\t\t\tintent: \"metadata-only\",\n\t\t\tcomponent:\n\t\t\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t\t\t? sessionDetail.component\n\t\t\t\t\t: undefined,\n\t\t\tcomplete:\n\t\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t\t? sessionDetail.complete\n\t\t\t\t\t: undefined,\n\t\t};\n\t}\n\n\tconst candidate = actualSession as Record<string, unknown>;\n\tif (Array.isArray(candidate.data)) {\n\t\tconst normalizedCandidate = normalizeItemSessionContainer(\n\t\t\tcandidate,\n\t\t\tsafeItemId,\n\t\t);\n\t\tconst previousNormalized =\n\t\t\targs.previousItemSession !== undefined\n\t\t\t\t? normalizeItemSessionContainer(args.previousItemSession, safeItemId)\n\t\t\t\t: null;\n\t\tconst sessionDetailKeys = Object.keys(sessionDetail);\n\t\tconst metadataWithSessionOnly =\n\t\t\tsessionDetailKeys.length > 0 &&\n\t\t\tsessionDetailKeys.every(\n\t\t\t\t(key) =>\n\t\t\t\t\tkey === \"session\" ||\n\t\t\t\t\tkey === \"complete\" ||\n\t\t\t\t\tkey === \"component\" ||\n\t\t\t\t\tkey === \"timestamp\" ||\n\t\t\t\t\tkey === \"sourceRuntimeId\",\n\t\t\t);\n\t\tif (\n\t\t\tmetadataWithSessionOnly &&\n\t\t\tpreviousNormalized &&\n\t\t\tareSessionContainersEqual(normalizedCandidate, previousNormalized)\n\t\t) {\n\t\t\treturn {\n\t\t\t\titemId: safeItemId,\n\t\t\t\tsession: null,\n\t\t\t\tintent: \"metadata-only\",\n\t\t\t\tcomponent:\n\t\t\t\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t\t\t\t? sessionDetail.component\n\t\t\t\t\t\t: undefined,\n\t\t\t\tcomplete:\n\t\t\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t\t\t? sessionDetail.complete\n\t\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\titemId: safeItemId,\n\t\t\tsession: normalizedCandidate,\n\t\t\tintent: \"replace-item-session\",\n\t\t\tcomponent:\n\t\t\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t\t\t? sessionDetail.component\n\t\t\t\t\t: undefined,\n\t\t\tcomplete:\n\t\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t\t? sessionDetail.complete\n\t\t\t\t\t: undefined,\n\t\t};\n\t}\n\n\tconst candidateKeys = Object.keys(candidate);\n\tconst isMetadataOnlyPayload =\n\t\tcandidateKeys.length > 0 &&\n\t\tcandidateKeys.every(\n\t\t\t(key) =>\n\t\t\t\tkey === \"complete\" ||\n\t\t\t\tkey === \"component\" ||\n\t\t\t\tkey === \"timestamp\" ||\n\t\t\t\tkey === \"sourceRuntimeId\",\n\t\t);\n\tif (isMetadataOnlyPayload) {\n\t\treturn {\n\t\t\titemId: safeItemId,\n\t\t\tsession: null,\n\t\t\tintent: \"metadata-only\",\n\t\t\tcomponent:\n\t\t\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t\t\t? sessionDetail.component\n\t\t\t\t\t: undefined,\n\t\t\tcomplete:\n\t\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t\t? sessionDetail.complete\n\t\t\t\t\t: undefined,\n\t\t};\n\t}\n\n\tconst component =\n\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t? sessionDetail.component\n\t\t\t: typeof candidate.component === \"string\"\n\t\t\t\t? candidate.component\n\t\t\t\t: \"response\";\n\tconst elementEntryId =\n\t\ttypeof candidate.id === \"string\" && candidate.id ? candidate.id : component;\n\tconst merged = mergeElementIntoSession(\n\t\tsafeItemId,\n\t\targs.previousItemSession,\n\t\telementEntryId,\n\t\t{ id: elementEntryId, ...candidate },\n\t);\n\treturn {\n\t\titemId: safeItemId,\n\t\tsession: merged,\n\t\tintent: \"merge-element-session\",\n\t\tcomponent,\n\t\tcomplete:\n\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t? sessionDetail.complete\n\t\t\t\t: undefined,\n\t};\n}\n"]}
1
+ {"version":3,"file":"item-session-contract.js","sourceRoot":"","sources":["../../src/pie/item-session-contract.ts"],"names":[],"mappings":"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","sourcesContent":["export type ItemSessionContainer = {\n\tid: string;\n\tdata: unknown[];\n};\n\nexport type ItemSessionUpdateIntent =\n\t| \"replace-item-session\"\n\t| \"merge-element-session\"\n\t| \"metadata-only\";\n\nexport type NormalizedItemSessionChange = {\n\titemId: string;\n\tsession: ItemSessionContainer | null;\n\tintent: ItemSessionUpdateIntent;\n\tcomponent?: string;\n\tcomplete?: boolean;\n};\n\nconst DEFAULT_SESSION_ID = \"\";\n\nfunction areSessionContainersEqual(\n\tleft: ItemSessionContainer | null,\n\tright: ItemSessionContainer | null,\n): boolean {\n\tif (!left && !right) return true;\n\tif (!left || !right) return false;\n\tif ((left.id || \"\") !== (right.id || \"\")) return false;\n\ttry {\n\t\treturn JSON.stringify(left.data || []) === JSON.stringify(right.data || []);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport function normalizeItemSessionContainer(\n\tinput: unknown,\n\tfallbackSessionId: string = DEFAULT_SESSION_ID,\n): ItemSessionContainer {\n\tif (input && typeof input === \"object\") {\n\t\tconst candidate = input as Record<string, unknown>;\n\t\tif (Array.isArray(candidate.data)) {\n\t\t\treturn {\n\t\t\t\tid: typeof candidate.id === \"string\" ? candidate.id : fallbackSessionId,\n\t\t\t\tdata: candidate.data,\n\t\t\t};\n\t\t}\n\t\tif (Array.isArray(input)) {\n\t\t\treturn { id: fallbackSessionId, data: input };\n\t\t}\n\t\treturn { id: fallbackSessionId, data: [input] };\n\t}\n\tif (Array.isArray(input)) {\n\t\treturn { id: fallbackSessionId, data: input };\n\t}\n\treturn { id: fallbackSessionId, data: [] };\n}\n\nexport function hasResponseValue(value: unknown): boolean {\n\tif (value == null) return false;\n\tif (Array.isArray(value))\n\t\treturn value.some((entry) => hasResponseValue(entry));\n\tif (typeof value !== \"object\") return false;\n\tfor (const [key, nested] of Object.entries(\n\t\tvalue as Record<string, unknown>,\n\t)) {\n\t\tif (\n\t\t\tkey === \"value\" &&\n\t\t\tnested !== undefined &&\n\t\t\tnested !== null &&\n\t\t\t!(typeof nested === \"string\" && nested.trim() === \"\") &&\n\t\t\t!(Array.isArray(nested) && nested.length === 0)\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tif (hasResponseValue(nested)) return true;\n\t}\n\treturn false;\n}\n\nexport function hasResponseField(value: unknown): boolean {\n\tif (value == null) return false;\n\tif (Array.isArray(value))\n\t\treturn value.some((entry) => hasResponseField(entry));\n\tif (typeof value !== \"object\") return false;\n\tfor (const [key, nested] of Object.entries(\n\t\tvalue as Record<string, unknown>,\n\t)) {\n\t\tif (key === \"value\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (hasResponseField(nested)) return true;\n\t}\n\treturn false;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction mergeSessionEntry(\n\texisting: Record<string, unknown>,\n\tincoming: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst merged = { ...existing };\n\tfor (const [key, value] of Object.entries(incoming)) {\n\t\tconst previous = merged[key];\n\t\tmerged[key] =\n\t\t\tisPlainObject(previous) && isPlainObject(value)\n\t\t\t\t? mergeSessionEntry(previous, value)\n\t\t\t\t: value;\n\t}\n\treturn merged;\n}\n\nexport function mergeElementIntoSession(\n\titemId: string,\n\tpreviousItemSession: unknown,\n\tentryId: string,\n\tentry: Record<string, unknown>,\n): ItemSessionContainer {\n\tconst previous = normalizeItemSessionContainer(previousItemSession, itemId);\n\tconst nextData = [...previous.data];\n\tconst existingIndex = nextData.findIndex((candidate) => {\n\t\tif (!candidate || typeof candidate !== \"object\") return false;\n\t\treturn (candidate as Record<string, unknown>).id === entryId;\n\t});\n\tif (existingIndex >= 0) {\n\t\tconst existing = nextData[existingIndex];\n\t\tnextData[existingIndex] =\n\t\t\texisting && typeof existing === \"object\"\n\t\t\t\t? mergeSessionEntry(existing as Record<string, unknown>, entry)\n\t\t\t\t: entry;\n\t} else {\n\t\tnextData.push(entry);\n\t}\n\treturn {\n\t\tid: previous.id || itemId,\n\t\tdata: nextData,\n\t};\n}\n\nexport function normalizeItemSessionChange(args: {\n\titemId: string;\n\tsessionDetail: unknown;\n\tpreviousItemSession?: unknown;\n}): NormalizedItemSessionChange {\n\tconst sessionDetail = (args.sessionDetail || {}) as Record<string, unknown>;\n\tconst actualSession =\n\t\tsessionDetail &&\n\t\ttypeof sessionDetail === \"object\" &&\n\t\t\"session\" in sessionDetail\n\t\t\t? sessionDetail.session\n\t\t\t: args.sessionDetail;\n\n\tconst safeItemId =\n\t\ttypeof args.itemId === \"string\" && args.itemId\n\t\t\t? args.itemId\n\t\t\t: typeof (actualSession as Record<string, unknown> | null)?.id ===\n\t\t\t\t\t\"string\"\n\t\t\t\t? String((actualSession as Record<string, unknown>).id)\n\t\t\t\t: \"\";\n\n\tif (!actualSession || typeof actualSession !== \"object\") {\n\t\treturn {\n\t\t\titemId: safeItemId,\n\t\t\tsession: null,\n\t\t\tintent: \"metadata-only\",\n\t\t\tcomponent:\n\t\t\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t\t\t? sessionDetail.component\n\t\t\t\t\t: undefined,\n\t\t\tcomplete:\n\t\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t\t? sessionDetail.complete\n\t\t\t\t\t: undefined,\n\t\t};\n\t}\n\n\tconst candidate = actualSession as Record<string, unknown>;\n\tif (Array.isArray(candidate.data)) {\n\t\tconst normalizedCandidate = normalizeItemSessionContainer(\n\t\t\tcandidate,\n\t\t\tsafeItemId,\n\t\t);\n\t\tconst previousNormalized =\n\t\t\targs.previousItemSession !== undefined\n\t\t\t\t? normalizeItemSessionContainer(args.previousItemSession, safeItemId)\n\t\t\t\t: null;\n\t\tconst sessionDetailKeys = Object.keys(sessionDetail);\n\t\tconst metadataWithSessionOnly =\n\t\t\tsessionDetailKeys.length > 0 &&\n\t\t\tsessionDetailKeys.every(\n\t\t\t\t(key) =>\n\t\t\t\t\tkey === \"session\" ||\n\t\t\t\t\tkey === \"complete\" ||\n\t\t\t\t\tkey === \"component\" ||\n\t\t\t\t\tkey === \"timestamp\" ||\n\t\t\t\t\tkey === \"sourceRuntimeId\",\n\t\t\t);\n\t\tif (\n\t\t\tmetadataWithSessionOnly &&\n\t\t\tpreviousNormalized &&\n\t\t\tareSessionContainersEqual(normalizedCandidate, previousNormalized)\n\t\t) {\n\t\t\treturn {\n\t\t\t\titemId: safeItemId,\n\t\t\t\tsession: null,\n\t\t\t\tintent: \"metadata-only\",\n\t\t\t\tcomponent:\n\t\t\t\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t\t\t\t? sessionDetail.component\n\t\t\t\t\t\t: undefined,\n\t\t\t\tcomplete:\n\t\t\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t\t\t? sessionDetail.complete\n\t\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\titemId: safeItemId,\n\t\t\tsession: normalizedCandidate,\n\t\t\tintent: \"replace-item-session\",\n\t\t\tcomponent:\n\t\t\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t\t\t? sessionDetail.component\n\t\t\t\t\t: undefined,\n\t\t\tcomplete:\n\t\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t\t? sessionDetail.complete\n\t\t\t\t\t: undefined,\n\t\t};\n\t}\n\n\tconst candidateKeys = Object.keys(candidate);\n\tconst isMetadataOnlyPayload =\n\t\tcandidateKeys.length > 0 &&\n\t\tcandidateKeys.every(\n\t\t\t(key) =>\n\t\t\t\tkey === \"complete\" ||\n\t\t\t\tkey === \"component\" ||\n\t\t\t\tkey === \"timestamp\" ||\n\t\t\t\tkey === \"sourceRuntimeId\",\n\t\t);\n\tif (isMetadataOnlyPayload) {\n\t\treturn {\n\t\t\titemId: safeItemId,\n\t\t\tsession: null,\n\t\t\tintent: \"metadata-only\",\n\t\t\tcomponent:\n\t\t\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t\t\t? sessionDetail.component\n\t\t\t\t\t: undefined,\n\t\t\tcomplete:\n\t\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t\t? sessionDetail.complete\n\t\t\t\t\t: undefined,\n\t\t};\n\t}\n\n\tconst component =\n\t\ttypeof sessionDetail.component === \"string\"\n\t\t\t? sessionDetail.component\n\t\t\t: typeof candidate.component === \"string\"\n\t\t\t\t? candidate.component\n\t\t\t\t: \"response\";\n\tconst elementEntryId =\n\t\ttypeof candidate.id === \"string\" && candidate.id ? candidate.id : component;\n\tconst merged = mergeElementIntoSession(\n\t\tsafeItemId,\n\t\targs.previousItemSession,\n\t\telementEntryId,\n\t\t{ id: elementEntryId, ...candidate },\n\t);\n\treturn {\n\t\titemId: safeItemId,\n\t\tsession: merged,\n\t\tintent: \"merge-element-session\",\n\t\tcomponent,\n\t\tcomplete:\n\t\t\ttypeof sessionDetail.complete === \"boolean\"\n\t\t\t\t? sessionDetail.complete\n\t\t\t\t: undefined,\n\t};\n}\n"]}
@@ -4,6 +4,7 @@
4
4
  * Functions for updating PIE elements with new models, sessions, and env.
5
5
  */
6
6
  import { mergeObjectsIgnoringNullUndefined } from "../object/index.js";
7
+ import { wrapModelRichContent } from "../security/wrap-model-rich-content.js";
7
8
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
8
9
  import { findPieController } from "./scoring.js";
9
10
  import { defaultPieElementOptions } from "./types.js";
@@ -92,14 +93,15 @@ const applyControllerToElement = async (element, model, elementSession, controll
92
93
  element: model.element,
93
94
  ...controllerResultObject,
94
95
  };
96
+ const wrappedModel = wrapModelRichContent(filteredModel);
95
97
  logger.debug(`${logPrefix} ✅ Controller filtered model:`, {
96
- id: filteredModel.id,
97
- element: filteredModel.element,
98
- hasCorrectResponse: "correctResponse" in filteredModel,
98
+ id: wrappedModel.id,
99
+ element: wrappedModel.element,
100
+ hasCorrectResponse: "correctResponse" in wrappedModel,
99
101
  mode: env.mode,
100
102
  role: env.role,
101
103
  });
102
- element.model = filteredModel;
104
+ element.model = wrappedModel;
103
105
  element.session = elementSession;
104
106
  }
105
107
  catch (err) {
@@ -145,7 +147,7 @@ const updateSinglePieElement = async (pieElement, controllerLookupTag, options,
145
147
  const controller = findPieController(controllerLookupTag);
146
148
  if (!controller) {
147
149
  logger.debug(`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`);
148
- pieElement.model = model;
150
+ pieElement.model = wrapModelRichContent(model);
149
151
  pieElement.session = elementSession;
150
152
  return;
151
153
  }
@@ -176,13 +178,13 @@ const updateSinglePieElement = async (pieElement, controllerLookupTag, options,
176
178
  cause: errorMessage,
177
179
  });
178
180
  // Fall back to raw model on controller error
179
- pieElement.model = model;
181
+ pieElement.model = wrapModelRichContent(model);
180
182
  pieElement.session = elementSession;
181
183
  }
182
184
  }
183
185
  else {
184
186
  logger.debug(`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`);
185
- pieElement.model = model;
187
+ pieElement.model = wrapModelRichContent(model);
186
188
  pieElement.session = elementSession;
187
189
  }
188
190
  };
@@ -1 +1 @@
1
- {"version":3,"file":"updates.js","sourceRoot":"","sources":["../../src/pie/updates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PIE Updates Module\n *\n * Functions for updating PIE elements with new models, sessions, and env.\n */\n\nimport { mergeObjectsIgnoringNullUndefined } from \"../object/index.js\";\nimport type { ConfigEntity, Env, PieModel } from \"../types/index.js\";\nimport { createPieLogger, isGlobalDebugEnabled } from \"./logger.js\";\nimport { findPieController } from \"./scoring.js\";\nimport type { PieElement, UpdatePieElementOptions } from \"./types.js\";\nimport { defaultPieElementOptions } from \"./types.js\";\nimport { findOrAddSession } from \"./utils.js\";\n\n// Create module-level logger (respects global debug flag - pass function for dynamic checking)\nconst logger = createPieLogger(\"pie-updates\", () => isGlobalDebugEnabled());\n\ntype ControllerErrorDetail = {\n\tcode: \"PIE_CONTROLLER_CONTRACT_ERROR\" | \"PIE_CONTROLLER_RUNTIME_ERROR\";\n\tmessage: string;\n\telementName: string;\n\telementId: string;\n\tcontrollerShape?: string;\n\tcause?: string;\n};\n\nconst describeControllerShape = (controller: unknown): string => {\n\tif (!controller) return \"missing\";\n\tif (typeof controller === \"function\") return \"function\";\n\tif (typeof controller !== \"object\") return typeof controller;\n\tconst keys = Object.keys(controller as Record<string, unknown>);\n\tconst defaultValue = (controller as Record<string, unknown>).default;\n\tconst defaultKeys =\n\t\tdefaultValue && typeof defaultValue === \"object\"\n\t\t\t? Object.keys(defaultValue as Record<string, unknown>)\n\t\t\t: [];\n\treturn defaultKeys.length > 0\n\t\t? `object(keys=[${keys.join(\",\")}],defaultKeys=[${defaultKeys.join(\",\")}])`\n\t\t: `object(keys=[${keys.join(\",\")}])`;\n};\n\nconst resolveControllerModelFunction = (\n\tcontroller: unknown,\n):\n\t| ((model: PieModel, session: any, env: Env, updateSession: any) => unknown)\n\t| null => {\n\tif (!controller) return null;\n\tif (typeof controller === \"function\") {\n\t\treturn controller as (\n\t\t\tmodel: PieModel,\n\t\t\tsession: any,\n\t\t\tenv: Env,\n\t\t\tupdateSession: any,\n\t\t) => unknown;\n\t}\n\tif (typeof controller !== \"object\") return null;\n\tconst direct = (controller as Record<string, unknown>).model;\n\tif (typeof direct === \"function\") {\n\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t(direct as Function).call(\n\t\t\t\tcontroller,\n\t\t\t\tmodel,\n\t\t\t\tsessionData,\n\t\t\t\tenv,\n\t\t\t\tupdateSession,\n\t\t\t);\n\t}\n\tconst fromDefault = (controller as Record<string, unknown>).default;\n\tif (fromDefault && typeof fromDefault === \"object\") {\n\t\tconst nested = (fromDefault as Record<string, unknown>).model;\n\t\tif (typeof nested === \"function\") {\n\t\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t\t(nested as Function).call(\n\t\t\t\t\tfromDefault,\n\t\t\t\t\tmodel,\n\t\t\t\t\tsessionData,\n\t\t\t\t\tenv,\n\t\t\t\t\tupdateSession,\n\t\t\t\t);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst emitControllerError = (\n\tpieElement: PieElement,\n\tdetail: ControllerErrorDetail,\n): void => {\n\tpieElement.dispatchEvent(\n\t\tnew CustomEvent(\"pie-controller-error\", {\n\t\t\tdetail,\n\t\t\tbubbles: true,\n\t\t\tcomposed: true,\n\t\t}),\n\t);\n};\n\n/**\n * Helper function to apply controller to element\n * Extracted to eliminate duplication and ensure consistent controller invocation\n */\nconst applyControllerToElement = async (\n\telement: PieElement,\n\tmodel: PieModel,\n\telementSession: any,\n\tcontroller: unknown,\n\tenv: Env,\n\tlogPrefix: string,\n\tonElementSessionUpdate?: (\n\t\telementId: string,\n\t\telementName: string,\n\t\tproperties: Record<string, unknown>,\n\t) => void,\n): Promise<void> => {\n\tlogger.debug(`${logPrefix} Using controller, env:`, env);\n\tlogger.debug(`${logPrefix} Model before filter:`, {\n\t\tid: model.id,\n\t\telement: model.element,\n\t\thasCorrectResponse: \"correctResponse\" in model,\n\t});\n\n\t// Create updateSession callback for controller to save derived state (e.g.\n\t// shuffle order). Mutate the in-flight element session so this render uses it,\n\t// AND propagate the write to the authoritative session via the host callback\n\t// so subsequent renders reuse it instead of regenerating it. The write-back is\n\t// keyed by the canonical model id/element (the entry findOrAddSession resolved),\n\t// which also tolerates controllers that pass an undefined id/element.\n\tconst updateSession = (id: string, _elementName: string, properties: any) => {\n\t\tlogger.debug(\n\t\t\t`${logPrefix} updateSession called for ${id} with:`,\n\t\t\tproperties,\n\t\t);\n\t\tObject.assign(elementSession, properties);\n\t\tonElementSessionUpdate?.(model.id, model.element, properties);\n\t\treturn Promise.resolve();\n\t};\n\n\ttry {\n\t\tconst modelFunction = resolveControllerModelFunction(controller);\n\t\tif (!modelFunction) {\n\t\t\tthrow new Error(\n\t\t\t\t`Controller contract mismatch: expected a model() function but received ${describeControllerShape(\n\t\t\t\t\tcontroller,\n\t\t\t\t)}`,\n\t\t\t);\n\t\t}\n\t\tconst controllerResult = await modelFunction(\n\t\t\tmodel,\n\t\t\telementSession,\n\t\t\tenv,\n\t\t\tupdateSession,\n\t\t);\n\n\t\t// Merge controller result with id and element (like server-side PieControllerExecutor does)\n\t\tconst controllerResultObject =\n\t\t\tcontrollerResult && typeof controllerResult === \"object\"\n\t\t\t\t? (controllerResult as Record<string, unknown>)\n\t\t\t\t: {};\n\t\tconst filteredModel = {\n\t\t\tid: model.id,\n\t\t\telement: model.element,\n\t\t\t...controllerResultObject,\n\t\t};\n\n\t\tlogger.debug(`${logPrefix} ✅ Controller filtered model:`, {\n\t\t\tid: filteredModel.id,\n\t\t\telement: filteredModel.element,\n\t\t\thasCorrectResponse: \"correctResponse\" in filteredModel,\n\t\t\tmode: env.mode,\n\t\t\trole: env.role,\n\t\t});\n\n\t\telement.model = filteredModel;\n\t\telement.session = elementSession;\n\t} catch (err) {\n\t\tlogger.error(`${logPrefix} ❌ Controller error:`, err);\n\t\tthrow err; // Re-throw - controller errors are fatal\n\t}\n};\n\ntype ResolvedUpdateOptions = Pick<\n\tUpdatePieElementOptions,\n\t| \"config\"\n\t| \"session\"\n\t| \"env\"\n\t| \"eventListeners\"\n\t| \"invokeControllerForModel\"\n\t| \"onElementSessionUpdate\"\n>;\n\nconst resolveAndValidateUpdateOptions = (\n\topts: UpdatePieElementOptions,\n): ResolvedUpdateOptions => {\n\tconst {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t} = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);\n\tif (!env) {\n\t\tthrow new Error(\"env is required\");\n\t}\n\tif (!session) {\n\t\tthrow new Error(\"session is required\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"config is required\");\n\t}\n\treturn {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t};\n};\n\nconst updateSinglePieElement = async (\n\tpieElement: PieElement,\n\tcontrollerLookupTag: string,\n\toptions: ResolvedUpdateOptions,\n\tlogContext: string,\n): Promise<void> => {\n\tconst {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t} = options;\n\tconst model = config.models?.find((m) => m.id === pieElement.id) as\n\t\t| PieModel\n\t\t| undefined;\n\tif (!model) {\n\t\tlogger.error(`${logContext} Model not found for`, controllerLookupTag);\n\t\tthrow new Error(`model not found for ${controllerLookupTag}`);\n\t}\n\n\tconst elementSession = findOrAddSession(session, model.id, model.element);\n\n\t// Always attach event listeners (don't skip them for no-controller case)\n\tif (eventListeners) {\n\t\tObject.entries(eventListeners).forEach(([evt, fn]) => {\n\t\t\tpieElement.addEventListener(evt as any, fn);\n\t\t});\n\t}\n\n\tif (env && invokeControllerForModel) {\n\t\tconst controller = findPieController(controllerLookupTag);\n\t\tif (!controller) {\n\t\t\tlogger.debug(\n\t\t\t\t`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`,\n\t\t\t);\n\t\t\tpieElement.model = model;\n\t\t\tpieElement.session = elementSession;\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug(\n\t\t\t`${logContext} Invoking controller for ${controllerLookupTag}#${pieElement.id}`,\n\t\t\t{\n\t\t\t\tmode: env.mode,\n\t\t\t\trole: env.role,\n\t\t\t\thasCorrectResponse: \"correctResponse\" in model,\n\t\t\t},\n\t\t);\n\n\t\t// Await the controller so any updateSession write-back completes before the\n\t\t// caller computes/emits the session signature; otherwise a late, async\n\t\t// shuffle write lands after the cycle that read the session and the order\n\t\t// never round-trips (PIE-631).\n\t\ttry {\n\t\t\tawait applyControllerToElement(\n\t\t\t\tpieElement,\n\t\t\t\tmodel,\n\t\t\t\telementSession,\n\t\t\t\tcontroller,\n\t\t\t\tenv,\n\t\t\t\t`${logContext}(${controllerLookupTag}#${pieElement.id})`,\n\t\t\t\tonElementSessionUpdate,\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tconst errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\tconst isContractError = errorMessage.includes(\n\t\t\t\t\"Controller contract mismatch\",\n\t\t\t);\n\t\t\tlogger.error(\n\t\t\t\t`${logContext} Controller failed for ${controllerLookupTag}#${pieElement.id}:`,\n\t\t\t\terr,\n\t\t\t);\n\t\t\temitControllerError(pieElement, {\n\t\t\t\tcode: isContractError\n\t\t\t\t\t? \"PIE_CONTROLLER_CONTRACT_ERROR\"\n\t\t\t\t\t: \"PIE_CONTROLLER_RUNTIME_ERROR\",\n\t\t\t\tmessage: `${controllerLookupTag} controller failed while applying model for ${pieElement.id}. ${errorMessage}`,\n\t\t\t\telementName: controllerLookupTag,\n\t\t\t\telementId: pieElement.id,\n\t\t\t\tcontrollerShape: describeControllerShape(controller),\n\t\t\t\tcause: errorMessage,\n\t\t\t});\n\t\t\t// Fall back to raw model on controller error\n\t\t\tpieElement.model = model;\n\t\t\tpieElement.session = elementSession;\n\t\t}\n\t} else {\n\t\tlogger.debug(\n\t\t\t`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`,\n\t\t);\n\t\tpieElement.model = model;\n\t\tpieElement.session = elementSession;\n\t}\n};\n\n/**\n * Update a PIE element by ref (direct Element reference)\n */\nexport const updatePieElementWithRef = (\n\tel: Element,\n\topts: UpdatePieElementOptions,\n): Promise<void> => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst pieElement = el as PieElement;\n\tconst elName = pieElement.tagName.toLowerCase();\n\treturn updateSinglePieElement(\n\t\tpieElement,\n\t\telName,\n\t\toptions,\n\t\t\"[updatePieElementWithRef]\",\n\t);\n};\n\n/**\n * Update a PIE element by name (finds all matching elements in DOM)\n */\nexport const updatePieElement = (\n\telName: string,\n\topts: UpdatePieElementOptions,\n): Promise<void> => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst { container } = mergeObjectsIgnoringNullUndefined(\n\t\tdefaultPieElementOptions,\n\t\topts,\n\t);\n\t// Use container for scoped query or fallback to document for global query\n\tconst searchRoot = container || document;\n\tconst pieElements = searchRoot.querySelectorAll(elName);\n\tif (!pieElements || pieElements.length === 0) {\n\t\tlogger.debug(`no elements found for ${elName}`);\n\t\treturn Promise.resolve();\n\t}\n\treturn Promise.all(\n\t\tArray.from(pieElements, (el) =>\n\t\t\tupdateSinglePieElement(\n\t\t\t\tel as PieElement,\n\t\t\t\telName,\n\t\t\t\toptions,\n\t\t\t\t\"[updatePieElement]\",\n\t\t\t),\n\t\t),\n\t).then(() => undefined);\n};\n\n/**\n * Update all PIE elements in a config\n */\nexport const updatePieElements = (\n\tconfig: ConfigEntity,\n\tsession: any[],\n\tenv: Env,\n\tcontainer?: Element | Document,\n\tonElementSessionUpdate?: UpdatePieElementOptions[\"onElementSessionUpdate\"],\n): Promise<void> => {\n\tlogger.debug(\"[updatePieElements] Updating all elements with env:\", env);\n\treturn Promise.all(\n\t\tObject.entries(config.elements).map(([elName, _pkg]) =>\n\t\t\tupdatePieElement(elName, {\n\t\t\t\tconfig,\n\t\t\t\tsession,\n\t\t\t\tenv,\n\t\t\t\tcontainer,\n\t\t\t\tonElementSessionUpdate,\n\t\t\t}),\n\t\t),\n\t).then(() => undefined);\n};\n"]}
1
+ {"version":3,"file":"updates.js","sourceRoot":"","sources":["../../src/pie/updates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PIE Updates Module\n *\n * Functions for updating PIE elements with new models, sessions, and env.\n */\n\nimport { mergeObjectsIgnoringNullUndefined } from \"../object/index.js\";\nimport { wrapModelRichContent } from \"../security/wrap-model-rich-content.js\";\nimport type { ConfigEntity, Env, PieModel } from \"../types/index.js\";\nimport { createPieLogger, isGlobalDebugEnabled } from \"./logger.js\";\nimport { findPieController } from \"./scoring.js\";\nimport type { PieElement, UpdatePieElementOptions } from \"./types.js\";\nimport { defaultPieElementOptions } from \"./types.js\";\nimport { findOrAddSession } from \"./utils.js\";\n\n// Create module-level logger (respects global debug flag - pass function for dynamic checking)\nconst logger = createPieLogger(\"pie-updates\", () => isGlobalDebugEnabled());\n\ntype ControllerErrorDetail = {\n\tcode: \"PIE_CONTROLLER_CONTRACT_ERROR\" | \"PIE_CONTROLLER_RUNTIME_ERROR\";\n\tmessage: string;\n\telementName: string;\n\telementId: string;\n\tcontrollerShape?: string;\n\tcause?: string;\n};\n\nconst describeControllerShape = (controller: unknown): string => {\n\tif (!controller) return \"missing\";\n\tif (typeof controller === \"function\") return \"function\";\n\tif (typeof controller !== \"object\") return typeof controller;\n\tconst keys = Object.keys(controller as Record<string, unknown>);\n\tconst defaultValue = (controller as Record<string, unknown>).default;\n\tconst defaultKeys =\n\t\tdefaultValue && typeof defaultValue === \"object\"\n\t\t\t? Object.keys(defaultValue as Record<string, unknown>)\n\t\t\t: [];\n\treturn defaultKeys.length > 0\n\t\t? `object(keys=[${keys.join(\",\")}],defaultKeys=[${defaultKeys.join(\",\")}])`\n\t\t: `object(keys=[${keys.join(\",\")}])`;\n};\n\nconst resolveControllerModelFunction = (\n\tcontroller: unknown,\n):\n\t| ((model: PieModel, session: any, env: Env, updateSession: any) => unknown)\n\t| null => {\n\tif (!controller) return null;\n\tif (typeof controller === \"function\") {\n\t\treturn controller as (\n\t\t\tmodel: PieModel,\n\t\t\tsession: any,\n\t\t\tenv: Env,\n\t\t\tupdateSession: any,\n\t\t) => unknown;\n\t}\n\tif (typeof controller !== \"object\") return null;\n\tconst direct = (controller as Record<string, unknown>).model;\n\tif (typeof direct === \"function\") {\n\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t(direct as Function).call(\n\t\t\t\tcontroller,\n\t\t\t\tmodel,\n\t\t\t\tsessionData,\n\t\t\t\tenv,\n\t\t\t\tupdateSession,\n\t\t\t);\n\t}\n\tconst fromDefault = (controller as Record<string, unknown>).default;\n\tif (fromDefault && typeof fromDefault === \"object\") {\n\t\tconst nested = (fromDefault as Record<string, unknown>).model;\n\t\tif (typeof nested === \"function\") {\n\t\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t\t(nested as Function).call(\n\t\t\t\t\tfromDefault,\n\t\t\t\t\tmodel,\n\t\t\t\t\tsessionData,\n\t\t\t\t\tenv,\n\t\t\t\t\tupdateSession,\n\t\t\t\t);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst emitControllerError = (\n\tpieElement: PieElement,\n\tdetail: ControllerErrorDetail,\n): void => {\n\tpieElement.dispatchEvent(\n\t\tnew CustomEvent(\"pie-controller-error\", {\n\t\t\tdetail,\n\t\t\tbubbles: true,\n\t\t\tcomposed: true,\n\t\t}),\n\t);\n};\n\n/**\n * Helper function to apply controller to element\n * Extracted to eliminate duplication and ensure consistent controller invocation\n */\nconst applyControllerToElement = async (\n\telement: PieElement,\n\tmodel: PieModel,\n\telementSession: any,\n\tcontroller: unknown,\n\tenv: Env,\n\tlogPrefix: string,\n\tonElementSessionUpdate?: (\n\t\telementId: string,\n\t\telementName: string,\n\t\tproperties: Record<string, unknown>,\n\t) => void,\n): Promise<void> => {\n\tlogger.debug(`${logPrefix} Using controller, env:`, env);\n\tlogger.debug(`${logPrefix} Model before filter:`, {\n\t\tid: model.id,\n\t\telement: model.element,\n\t\thasCorrectResponse: \"correctResponse\" in model,\n\t});\n\n\t// Create updateSession callback for controller to save derived state (e.g.\n\t// shuffle order). Mutate the in-flight element session so this render uses it,\n\t// AND propagate the write to the authoritative session via the host callback\n\t// so subsequent renders reuse it instead of regenerating it. The write-back is\n\t// keyed by the canonical model id/element (the entry findOrAddSession resolved),\n\t// which also tolerates controllers that pass an undefined id/element.\n\tconst updateSession = (id: string, _elementName: string, properties: any) => {\n\t\tlogger.debug(\n\t\t\t`${logPrefix} updateSession called for ${id} with:`,\n\t\t\tproperties,\n\t\t);\n\t\tObject.assign(elementSession, properties);\n\t\tonElementSessionUpdate?.(model.id, model.element, properties);\n\t\treturn Promise.resolve();\n\t};\n\n\ttry {\n\t\tconst modelFunction = resolveControllerModelFunction(controller);\n\t\tif (!modelFunction) {\n\t\t\tthrow new Error(\n\t\t\t\t`Controller contract mismatch: expected a model() function but received ${describeControllerShape(\n\t\t\t\t\tcontroller,\n\t\t\t\t)}`,\n\t\t\t);\n\t\t}\n\t\tconst controllerResult = await modelFunction(\n\t\t\tmodel,\n\t\t\telementSession,\n\t\t\tenv,\n\t\t\tupdateSession,\n\t\t);\n\n\t\t// Merge controller result with id and element (like server-side PieControllerExecutor does)\n\t\tconst controllerResultObject =\n\t\t\tcontrollerResult && typeof controllerResult === \"object\"\n\t\t\t\t? (controllerResult as Record<string, unknown>)\n\t\t\t\t: {};\n\t\tconst filteredModel = {\n\t\t\tid: model.id,\n\t\t\telement: model.element,\n\t\t\t...controllerResultObject,\n\t\t};\n\t\tconst wrappedModel = wrapModelRichContent(filteredModel);\n\n\t\tlogger.debug(`${logPrefix} ✅ Controller filtered model:`, {\n\t\t\tid: wrappedModel.id,\n\t\t\telement: wrappedModel.element,\n\t\t\thasCorrectResponse: \"correctResponse\" in wrappedModel,\n\t\t\tmode: env.mode,\n\t\t\trole: env.role,\n\t\t});\n\n\t\telement.model = wrappedModel;\n\t\telement.session = elementSession;\n\t} catch (err) {\n\t\tlogger.error(`${logPrefix} ❌ Controller error:`, err);\n\t\tthrow err; // Re-throw - controller errors are fatal\n\t}\n};\n\ntype ResolvedUpdateOptions = Pick<\n\tUpdatePieElementOptions,\n\t| \"config\"\n\t| \"session\"\n\t| \"env\"\n\t| \"eventListeners\"\n\t| \"invokeControllerForModel\"\n\t| \"onElementSessionUpdate\"\n>;\n\nconst resolveAndValidateUpdateOptions = (\n\topts: UpdatePieElementOptions,\n): ResolvedUpdateOptions => {\n\tconst {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t} = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);\n\tif (!env) {\n\t\tthrow new Error(\"env is required\");\n\t}\n\tif (!session) {\n\t\tthrow new Error(\"session is required\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"config is required\");\n\t}\n\treturn {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t};\n};\n\nconst updateSinglePieElement = async (\n\tpieElement: PieElement,\n\tcontrollerLookupTag: string,\n\toptions: ResolvedUpdateOptions,\n\tlogContext: string,\n): Promise<void> => {\n\tconst {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t} = options;\n\tconst model = config.models?.find((m) => m.id === pieElement.id) as\n\t\t| PieModel\n\t\t| undefined;\n\tif (!model) {\n\t\tlogger.error(`${logContext} Model not found for`, controllerLookupTag);\n\t\tthrow new Error(`model not found for ${controllerLookupTag}`);\n\t}\n\n\tconst elementSession = findOrAddSession(session, model.id, model.element);\n\n\t// Always attach event listeners (don't skip them for no-controller case)\n\tif (eventListeners) {\n\t\tObject.entries(eventListeners).forEach(([evt, fn]) => {\n\t\t\tpieElement.addEventListener(evt as any, fn);\n\t\t});\n\t}\n\n\tif (env && invokeControllerForModel) {\n\t\tconst controller = findPieController(controllerLookupTag);\n\t\tif (!controller) {\n\t\t\tlogger.debug(\n\t\t\t\t`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`,\n\t\t\t);\n\t\t\tpieElement.model = wrapModelRichContent(model);\n\t\t\tpieElement.session = elementSession;\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug(\n\t\t\t`${logContext} Invoking controller for ${controllerLookupTag}#${pieElement.id}`,\n\t\t\t{\n\t\t\t\tmode: env.mode,\n\t\t\t\trole: env.role,\n\t\t\t\thasCorrectResponse: \"correctResponse\" in model,\n\t\t\t},\n\t\t);\n\n\t\t// Await the controller so any updateSession write-back completes before the\n\t\t// caller computes/emits the session signature; otherwise a late, async\n\t\t// shuffle write lands after the cycle that read the session and the order\n\t\t// never round-trips (PIE-631).\n\t\ttry {\n\t\t\tawait applyControllerToElement(\n\t\t\t\tpieElement,\n\t\t\t\tmodel,\n\t\t\t\telementSession,\n\t\t\t\tcontroller,\n\t\t\t\tenv,\n\t\t\t\t`${logContext}(${controllerLookupTag}#${pieElement.id})`,\n\t\t\t\tonElementSessionUpdate,\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tconst errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\tconst isContractError = errorMessage.includes(\n\t\t\t\t\"Controller contract mismatch\",\n\t\t\t);\n\t\t\tlogger.error(\n\t\t\t\t`${logContext} Controller failed for ${controllerLookupTag}#${pieElement.id}:`,\n\t\t\t\terr,\n\t\t\t);\n\t\t\temitControllerError(pieElement, {\n\t\t\t\tcode: isContractError\n\t\t\t\t\t? \"PIE_CONTROLLER_CONTRACT_ERROR\"\n\t\t\t\t\t: \"PIE_CONTROLLER_RUNTIME_ERROR\",\n\t\t\t\tmessage: `${controllerLookupTag} controller failed while applying model for ${pieElement.id}. ${errorMessage}`,\n\t\t\t\telementName: controllerLookupTag,\n\t\t\t\telementId: pieElement.id,\n\t\t\t\tcontrollerShape: describeControllerShape(controller),\n\t\t\t\tcause: errorMessage,\n\t\t\t});\n\t\t\t// Fall back to raw model on controller error\n\t\t\tpieElement.model = wrapModelRichContent(model);\n\t\t\tpieElement.session = elementSession;\n\t\t}\n\t} else {\n\t\tlogger.debug(\n\t\t\t`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`,\n\t\t);\n\t\tpieElement.model = wrapModelRichContent(model);\n\t\tpieElement.session = elementSession;\n\t}\n};\n\n/**\n * Update a PIE element by ref (direct Element reference)\n */\nexport const updatePieElementWithRef = (\n\tel: Element,\n\topts: UpdatePieElementOptions,\n): Promise<void> => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst pieElement = el as PieElement;\n\tconst elName = pieElement.tagName.toLowerCase();\n\treturn updateSinglePieElement(\n\t\tpieElement,\n\t\telName,\n\t\toptions,\n\t\t\"[updatePieElementWithRef]\",\n\t);\n};\n\n/**\n * Update a PIE element by name (finds all matching elements in DOM)\n */\nexport const updatePieElement = (\n\telName: string,\n\topts: UpdatePieElementOptions,\n): Promise<void> => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst { container } = mergeObjectsIgnoringNullUndefined(\n\t\tdefaultPieElementOptions,\n\t\topts,\n\t);\n\t// Use container for scoped query or fallback to document for global query\n\tconst searchRoot = container || document;\n\tconst pieElements = searchRoot.querySelectorAll(elName);\n\tif (!pieElements || pieElements.length === 0) {\n\t\tlogger.debug(`no elements found for ${elName}`);\n\t\treturn Promise.resolve();\n\t}\n\treturn Promise.all(\n\t\tArray.from(pieElements, (el) =>\n\t\t\tupdateSinglePieElement(\n\t\t\t\tel as PieElement,\n\t\t\t\telName,\n\t\t\t\toptions,\n\t\t\t\t\"[updatePieElement]\",\n\t\t\t),\n\t\t),\n\t).then(() => undefined);\n};\n\n/**\n * Update all PIE elements in a config\n */\nexport const updatePieElements = (\n\tconfig: ConfigEntity,\n\tsession: any[],\n\tenv: Env,\n\tcontainer?: Element | Document,\n\tonElementSessionUpdate?: UpdatePieElementOptions[\"onElementSessionUpdate\"],\n): Promise<void> => {\n\tlogger.debug(\"[updatePieElements] Updating all elements with env:\", env);\n\treturn Promise.all(\n\t\tObject.entries(config.elements).map(([elName, _pkg]) =>\n\t\t\tupdatePieElement(elName, {\n\t\t\t\tconfig,\n\t\t\t\tsession,\n\t\t\t\tenv,\n\t\t\t\tcontainer,\n\t\t\t\tonElementSessionUpdate,\n\t\t\t}),\n\t\t),\n\t).then(() => undefined);\n};\n"]}
@@ -1,5 +1,6 @@
1
1
  export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, resetPurifierForTesting, sanitizeItemMarkup, type ItemMarkupSanitizer, type SanitizeItemMarkupOptions, } from "./sanitize-item-markup.js";
2
2
  export { parseAllowedStyleOrigins, validateExternalStyleUrl, type StyleUrlValidationError, type StyleUrlValidationOk, type StyleUrlValidationOptions, type StyleUrlValidationResult, } from "./validate-style-url.js";
3
3
  export { resetSvgSanitizerForTesting, sanitizeSvgIcon, } from "./sanitize-svg-icon.js";
4
- export { wrapOverwideImages } from "./wrap-overwide-images.js";
5
- export { wrapOverwideTables } from "./wrap-overwide-tables.js";
4
+ export { wrapOverwideImages, wrapOverwideImagesInElement, } from "./wrap-overwide-images.js";
5
+ export { wrapModelRichContent } from "./wrap-model-rich-content.js";
6
+ export { wrapOverwideTables, wrapOverwideTablesInElement, } from "./wrap-overwide-tables.js";
@@ -1,6 +1,7 @@
1
1
  export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, resetPurifierForTesting, sanitizeItemMarkup, } from "./sanitize-item-markup.js";
2
2
  export { parseAllowedStyleOrigins, validateExternalStyleUrl, } from "./validate-style-url.js";
3
3
  export { resetSvgSanitizerForTesting, sanitizeSvgIcon, } from "./sanitize-svg-icon.js";
4
- export { wrapOverwideImages } from "./wrap-overwide-images.js";
5
- export { wrapOverwideTables } from "./wrap-overwide-tables.js";
4
+ export { wrapOverwideImages, wrapOverwideImagesInElement, } from "./wrap-overwide-images.js";
5
+ export { wrapModelRichContent } from "./wrap-model-rich-content.js";
6
+ export { wrapOverwideTables, wrapOverwideTablesInElement, } from "./wrap-overwide-tables.js";
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,uBAAuB,EACvB,gCAAgC,EAChC,uBAAuB,EACvB,kBAAkB,GAGlB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GAKxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,2BAA2B,EAC3B,eAAe,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["export {\n\tbuildAuthoringAllowList,\n\tcreateDefaultItemMarkupSanitizer,\n\tresetPurifierForTesting,\n\tsanitizeItemMarkup,\n\ttype ItemMarkupSanitizer,\n\ttype SanitizeItemMarkupOptions,\n} from \"./sanitize-item-markup.js\";\nexport {\n\tparseAllowedStyleOrigins,\n\tvalidateExternalStyleUrl,\n\ttype StyleUrlValidationError,\n\ttype StyleUrlValidationOk,\n\ttype StyleUrlValidationOptions,\n\ttype StyleUrlValidationResult,\n} from \"./validate-style-url.js\";\nexport {\n\tresetSvgSanitizerForTesting,\n\tsanitizeSvgIcon,\n} from \"./sanitize-svg-icon.js\";\nexport { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nexport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,uBAAuB,EACvB,gCAAgC,EAChC,uBAAuB,EACvB,kBAAkB,GAGlB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GAKxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,2BAA2B,EAC3B,eAAe,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,kBAAkB,EAClB,2BAA2B,GAC3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EACN,kBAAkB,EAClB,2BAA2B,GAC3B,MAAM,2BAA2B,CAAC","sourcesContent":["export {\n\tbuildAuthoringAllowList,\n\tcreateDefaultItemMarkupSanitizer,\n\tresetPurifierForTesting,\n\tsanitizeItemMarkup,\n\ttype ItemMarkupSanitizer,\n\ttype SanitizeItemMarkupOptions,\n} from \"./sanitize-item-markup.js\";\nexport {\n\tparseAllowedStyleOrigins,\n\tvalidateExternalStyleUrl,\n\ttype StyleUrlValidationError,\n\ttype StyleUrlValidationOk,\n\ttype StyleUrlValidationOptions,\n\ttype StyleUrlValidationResult,\n} from \"./validate-style-url.js\";\nexport {\n\tresetSvgSanitizerForTesting,\n\tsanitizeSvgIcon,\n} from \"./sanitize-svg-icon.js\";\nexport {\n\twrapOverwideImages,\n\twrapOverwideImagesInElement,\n} from \"./wrap-overwide-images.js\";\nexport { wrapModelRichContent } from \"./wrap-model-rich-content.js\";\nexport {\n\twrapOverwideTables,\n\twrapOverwideTablesInElement,\n} from \"./wrap-overwide-tables.js\";\n"]}
@@ -0,0 +1 @@
1
+ export declare function wrapModelRichContent<T>(model: T): T;
@@ -0,0 +1,41 @@
1
+ import { wrapOverwideImages } from "./wrap-overwide-images.js";
2
+ import { wrapOverwideTables } from "./wrap-overwide-tables.js";
3
+ const RICH_CONTENT_TAG_REGEX = /<(?:img|table)\b/i;
4
+ const SKIPPED_KEYS = new Set(["accessibilityCatalogs"]);
5
+ function maybeWrapRichContent(value) {
6
+ if (!RICH_CONTENT_TAG_REGEX.test(value))
7
+ return value;
8
+ return wrapOverwideTables(wrapOverwideImages(value));
9
+ }
10
+ function wrapValue(value, parentKey) {
11
+ if (parentKey && SKIPPED_KEYS.has(parentKey))
12
+ return value;
13
+ if (typeof value === "string")
14
+ return maybeWrapRichContent(value);
15
+ if (!value || typeof value !== "object")
16
+ return value;
17
+ if (Array.isArray(value)) {
18
+ let changed = false;
19
+ const next = value.map((entry) => {
20
+ const wrapped = wrapValue(entry);
21
+ if (wrapped !== entry)
22
+ changed = true;
23
+ return wrapped;
24
+ });
25
+ return changed ? next : value;
26
+ }
27
+ const record = value;
28
+ let changed = false;
29
+ const next = {};
30
+ for (const [key, entry] of Object.entries(record)) {
31
+ const wrapped = wrapValue(entry, key);
32
+ if (wrapped !== entry)
33
+ changed = true;
34
+ next[key] = wrapped;
35
+ }
36
+ return changed ? next : value;
37
+ }
38
+ export function wrapModelRichContent(model) {
39
+ return wrapValue(model);
40
+ }
41
+ //# sourceMappingURL=wrap-model-rich-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-model-rich-content.js","sourceRoot":"","sources":["../../src/security/wrap-model-rich-content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AACnD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC;AAExD,SAAS,oBAAoB,CAAC,KAAa;IAC1C,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,kBAAkB,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,SAAS,CAAC,KAAc,EAAE,SAAkB;IACpD,IAAI,SAAS,IAAI,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAEtD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAChC,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YACjC,IAAI,OAAO,KAAK,KAAK;gBAAE,OAAO,GAAG,IAAI,CAAC;YACtC,OAAO,OAAO,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/B,CAAC;IAED,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACtC,IAAI,OAAO,KAAK,KAAK;YAAE,OAAO,GAAG,IAAI,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;IACrB,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAI,KAAQ;IAC/C,OAAO,SAAS,CAAC,KAAK,CAAM,CAAC;AAC9B,CAAC","sourcesContent":["import { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nimport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n\nconst RICH_CONTENT_TAG_REGEX = /<(?:img|table)\\b/i;\nconst SKIPPED_KEYS = new Set([\"accessibilityCatalogs\"]);\n\nfunction maybeWrapRichContent(value: string): string {\n\tif (!RICH_CONTENT_TAG_REGEX.test(value)) return value;\n\treturn wrapOverwideTables(wrapOverwideImages(value));\n}\n\nfunction wrapValue(value: unknown, parentKey?: string): unknown {\n\tif (parentKey && SKIPPED_KEYS.has(parentKey)) return value;\n\tif (typeof value === \"string\") return maybeWrapRichContent(value);\n\tif (!value || typeof value !== \"object\") return value;\n\n\tif (Array.isArray(value)) {\n\t\tlet changed = false;\n\t\tconst next = value.map((entry) => {\n\t\t\tconst wrapped = wrapValue(entry);\n\t\t\tif (wrapped !== entry) changed = true;\n\t\t\treturn wrapped;\n\t\t});\n\t\treturn changed ? next : value;\n\t}\n\n\tconst record = value as Record<string, unknown>;\n\tlet changed = false;\n\tconst next: Record<string, unknown> = {};\n\tfor (const [key, entry] of Object.entries(record)) {\n\t\tconst wrapped = wrapValue(entry, key);\n\t\tif (wrapped !== entry) changed = true;\n\t\tnext[key] = wrapped;\n\t}\n\treturn changed ? next : value;\n}\n\nexport function wrapModelRichContent<T>(model: T): T {\n\treturn wrapValue(model) as T;\n}\n"]}
@@ -11,10 +11,34 @@
11
11
  * space (including at higher browser-zoom levels, which is the original driver
12
12
  * for this change — WCAG 1.4.10 Reflow at 400% zoom).
13
13
  *
14
- * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so
15
- * every host that renders authored markup through the shared
16
- * `pie-item-player` (including the section player) benefits uniformly.
14
+ * Two callable surfaces share the same wrapping logic:
15
+ *
16
+ * - `wrapOverwideImages(markup)` string-in / string-out, used as a
17
+ * post-sanitization step inside `sanitizeItemMarkup`. By design it skips
18
+ * images inside `pie-*` elements; those are the element's own template and
19
+ * should not be restructured by the authored-markup pipeline.
20
+ * - `wrapOverwideImagesInElement(root)` — operates on a live DOM subtree.
21
+ * Used by the post-render pass in `PieItemPlayer.svelte` so that images a
22
+ * PIE element paints into its own light DOM (e.g. a `pie-passage`'s
23
+ * model-driven content) get the same scrollable affordance even though
24
+ * they never appeared in the authored markup string.
25
+ */
26
+ export interface WrapOverwideImagesInElementOptions {
27
+ /**
28
+ * When `true`, images whose nearest `pie-*` ancestor is *strictly between*
29
+ * the image and `root` are left alone. Used by the string pipeline so the
30
+ * authored-markup pass doesn't restructure a PIE element's own template.
31
+ * Defaults to `false` so the live-DOM pass *does* wrap element-rendered
32
+ * images.
33
+ */
34
+ skipPieDescendants?: boolean;
35
+ }
36
+ /**
37
+ * Wrap every unwrapped `<img>` descendant of `root` with the shared
38
+ * horizontal-scroll span. Returns the number of newly-wrapped images so
39
+ * callers can short-circuit when nothing changed. Idempotent.
17
40
  */
41
+ export declare function wrapOverwideImagesInElement(root: Element, options?: WrapOverwideImagesInElementOptions): number;
18
42
  /**
19
43
  * Wrap `<img>` elements in `markup` with a horizontal-scroll container.
20
44
  *
@@ -25,6 +49,8 @@
25
49
  * `pie-image-scroll` class are left alone.
26
50
  * - Leaves images inside PIE custom elements (`<pie-*>`) alone. Those are
27
51
  * rendered by the element's own template / shadow DOM and should not be
28
- * restructured by the authored-markup pipeline.
52
+ * restructured by the authored-markup pipeline. Use
53
+ * `wrapOverwideImagesInElement` for the post-render pass that *does* want
54
+ * to wrap element-rendered images.
29
55
  */
30
56
  export declare function wrapOverwideImages(markup: string): string;
@@ -11,12 +11,20 @@
11
11
  * space (including at higher browser-zoom levels, which is the original driver
12
12
  * for this change — WCAG 1.4.10 Reflow at 400% zoom).
13
13
  *
14
- * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so
15
- * every host that renders authored markup through the shared
16
- * `pie-item-player` (including the section player) benefits uniformly.
14
+ * Two callable surfaces share the same wrapping logic:
15
+ *
16
+ * - `wrapOverwideImages(markup)` string-in / string-out, used as a
17
+ * post-sanitization step inside `sanitizeItemMarkup`. By design it skips
18
+ * images inside `pie-*` elements; those are the element's own template and
19
+ * should not be restructured by the authored-markup pipeline.
20
+ * - `wrapOverwideImagesInElement(root)` — operates on a live DOM subtree.
21
+ * Used by the post-render pass in `PieItemPlayer.svelte` so that images a
22
+ * PIE element paints into its own light DOM (e.g. a `pie-passage`'s
23
+ * model-driven content) get the same scrollable affordance even though
24
+ * they never appeared in the authored markup string.
17
25
  */
18
- const PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;
19
26
  const SCROLL_WRAPPER_CLASS = "pie-image-scroll";
27
+ const PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;
20
28
  function isInsidePieCustomElement(image, root) {
21
29
  let ancestor = image.parentElement;
22
30
  while (ancestor && ancestor !== root) {
@@ -32,6 +40,42 @@ function buildAriaLabel(image) {
32
40
  const trimmed = alt ? alt.trim() : "";
33
41
  return trimmed ? `Scrollable image: ${trimmed}` : "Scrollable image";
34
42
  }
43
+ /**
44
+ * Wrap every unwrapped `<img>` descendant of `root` with the shared
45
+ * horizontal-scroll span. Returns the number of newly-wrapped images so
46
+ * callers can short-circuit when nothing changed. Idempotent.
47
+ */
48
+ export function wrapOverwideImagesInElement(root, options = {}) {
49
+ const { skipPieDescendants = false } = options;
50
+ const images = Array.from(root.querySelectorAll("img"));
51
+ if (images.length === 0)
52
+ return 0;
53
+ const ownerDocument = root.ownerDocument;
54
+ if (!ownerDocument)
55
+ return 0;
56
+ let wrapped = 0;
57
+ for (const image of images) {
58
+ const parent = image.parentElement;
59
+ if (!parent)
60
+ continue;
61
+ // Idempotency — already wrapped.
62
+ if (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
63
+ continue;
64
+ }
65
+ // Authored-markup pass: leave PIE custom-element internals alone.
66
+ if (skipPieDescendants && isInsidePieCustomElement(image, root))
67
+ continue;
68
+ const wrapper = ownerDocument.createElement("span");
69
+ wrapper.className = SCROLL_WRAPPER_CLASS;
70
+ wrapper.setAttribute("tabindex", "0");
71
+ wrapper.setAttribute("role", "region");
72
+ wrapper.setAttribute("aria-label", buildAriaLabel(image));
73
+ parent.insertBefore(wrapper, image);
74
+ wrapper.appendChild(image);
75
+ wrapped += 1;
76
+ }
77
+ return wrapped;
78
+ }
35
79
  /**
36
80
  * Wrap `<img>` elements in `markup` with a horizontal-scroll container.
37
81
  *
@@ -42,7 +86,9 @@ function buildAriaLabel(image) {
42
86
  * `pie-image-scroll` class are left alone.
43
87
  * - Leaves images inside PIE custom elements (`<pie-*>`) alone. Those are
44
88
  * rendered by the element's own template / shadow DOM and should not be
45
- * restructured by the authored-markup pipeline.
89
+ * restructured by the authored-markup pipeline. Use
90
+ * `wrapOverwideImagesInElement` for the post-render pass that *does* want
91
+ * to wrap element-rendered images.
46
92
  */
47
93
  export function wrapOverwideImages(markup) {
48
94
  if (!markup)
@@ -62,30 +108,9 @@ export function wrapOverwideImages(markup) {
62
108
  const body = doc.body;
63
109
  if (!body)
64
110
  return markup;
65
- const images = Array.from(body.querySelectorAll("img"));
66
- if (images.length === 0)
67
- return markup;
68
- let mutated = false;
69
- for (const image of images) {
70
- const parent = image.parentElement;
71
- if (!parent)
72
- continue;
73
- // Idempotency — already wrapped.
74
- if (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
75
- continue;
76
- }
77
- // Leave PIE custom-element internals alone.
78
- if (isInsidePieCustomElement(image, body))
79
- continue;
80
- const wrapper = doc.createElement("span");
81
- wrapper.className = SCROLL_WRAPPER_CLASS;
82
- wrapper.setAttribute("tabindex", "0");
83
- wrapper.setAttribute("role", "region");
84
- wrapper.setAttribute("aria-label", buildAriaLabel(image));
85
- parent.insertBefore(wrapper, image);
86
- wrapper.appendChild(image);
87
- mutated = true;
88
- }
89
- return mutated ? body.innerHTML : markup;
111
+ const wrapped = wrapOverwideImagesInElement(body, {
112
+ skipPieDescendants: true,
113
+ });
114
+ return wrapped > 0 ? body.innerHTML : markup;
90
115
  }
91
116
  //# sourceMappingURL=wrap-overwide-images.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrap-overwide-images.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-images.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps authored `<img>` elements with a horizontally scrollable container so\n * images that are wider than their column surface a scrollbar instead of being\n * clipped by ancestor `overflow-x: hidden` regions (PIE-94).\n *\n * The wrapper is rendered as\n * `<span class=\"pie-image-scroll\" tabindex=\"0\" role=\"region\" aria-label=\"...\">`\n * and receives the accompanying CSS from `@pie-players/pie-theme`. The CSS uses\n * `overflow-x: auto` so small images stay visually unchanged: a scrollbar only\n * appears when the image's intrinsic width exceeds the wrapper's available\n * space (including at higher browser-zoom levels, which is the original driver\n * for this change — WCAG 1.4.10 Reflow at 400% zoom).\n *\n * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so\n * every host that renders authored markup through the shared\n * `pie-item-player` (including the section player) benefits uniformly.\n */\n\nconst PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;\nconst SCROLL_WRAPPER_CLASS = \"pie-image-scroll\";\n\nfunction isInsidePieCustomElement(image: Element, root: Element): boolean {\n\tlet ancestor: Element | null = image.parentElement;\n\twhile (ancestor && ancestor !== root) {\n\t\tif (PIE_CUSTOM_ELEMENT_TAG_REGEX.test(ancestor.tagName)) {\n\t\t\treturn true;\n\t\t}\n\t\tancestor = ancestor.parentElement;\n\t}\n\treturn false;\n}\n\nfunction buildAriaLabel(image: Element): string {\n\tconst alt = image.getAttribute(\"alt\");\n\tconst trimmed = alt ? alt.trim() : \"\";\n\treturn trimmed ? `Scrollable image: ${trimmed}` : \"Scrollable image\";\n}\n\n/**\n * Wrap `<img>` elements in `markup` with a horizontal-scroll container.\n *\n * - No-ops on empty input.\n * - No-ops during SSR (no `window` / `DOMParser`) — the markup is returned\n * unchanged; the browser re-run on hydrate will perform the wrap.\n * - Idempotent: images whose direct parent already carries the\n * `pie-image-scroll` class are left alone.\n * - Leaves images inside PIE custom elements (`<pie-*>`) alone. Those are\n * rendered by the element's own template / shadow DOM and should not be\n * restructured by the authored-markup pipeline.\n */\nexport function wrapOverwideImages(markup: string): string {\n\tif (!markup) return \"\";\n\n\t// Fast path: avoid the DOM round-trip entirely when the markup carries no\n\t// images. Keeps the sanitize pipeline cheap for the common case.\n\tif (!/<img\\b/i.test(markup)) return markup;\n\n\tif (typeof window === \"undefined\" || !window.document) return markup;\n\n\tconst ParserCtor =\n\t\ttypeof DOMParser !== \"undefined\"\n\t\t\t? DOMParser\n\t\t\t: (window as unknown as { DOMParser?: typeof DOMParser }).DOMParser;\n\tif (!ParserCtor) return markup;\n\n\tconst doc = new ParserCtor().parseFromString(\n\t\t`<!DOCTYPE html><html><body>${markup}</body></html>`,\n\t\t\"text/html\",\n\t);\n\tconst body = doc.body;\n\tif (!body) return markup;\n\n\tconst images = Array.from(body.querySelectorAll(\"img\"));\n\tif (images.length === 0) return markup;\n\n\tlet mutated = false;\n\tfor (const image of images) {\n\t\tconst parent = image.parentElement;\n\t\tif (!parent) continue;\n\n\t\t// Idempotency — already wrapped.\n\t\tif (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Leave PIE custom-element internals alone.\n\t\tif (isInsidePieCustomElement(image, body)) continue;\n\n\t\tconst wrapper = doc.createElement(\"span\");\n\t\twrapper.className = SCROLL_WRAPPER_CLASS;\n\t\twrapper.setAttribute(\"tabindex\", \"0\");\n\t\twrapper.setAttribute(\"role\", \"region\");\n\t\twrapper.setAttribute(\"aria-label\", buildAriaLabel(image));\n\n\t\tparent.insertBefore(wrapper, image);\n\t\twrapper.appendChild(image);\n\t\tmutated = true;\n\t}\n\n\treturn mutated ? body.innerHTML : markup;\n}\n"]}
1
+ {"version":3,"file":"wrap-overwide-images.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-images.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps authored `<img>` elements with a horizontally scrollable container so\n * images that are wider than their column surface a scrollbar instead of being\n * clipped by ancestor `overflow-x: hidden` regions (PIE-94).\n *\n * The wrapper is rendered as\n * `<span class=\"pie-image-scroll\" tabindex=\"0\" role=\"region\" aria-label=\"...\">`\n * and receives the accompanying CSS from `@pie-players/pie-theme`. The CSS uses\n * `overflow-x: auto` so small images stay visually unchanged: a scrollbar only\n * appears when the image's intrinsic width exceeds the wrapper's available\n * space (including at higher browser-zoom levels, which is the original driver\n * for this change — WCAG 1.4.10 Reflow at 400% zoom).\n *\n * Two callable surfaces share the same wrapping logic:\n *\n * - `wrapOverwideImages(markup)` — string-in / string-out, used as a\n * post-sanitization step inside `sanitizeItemMarkup`. By design it skips\n * images inside `pie-*` elements; those are the element's own template and\n * should not be restructured by the authored-markup pipeline.\n * - `wrapOverwideImagesInElement(root)` — operates on a live DOM subtree.\n * Used by the post-render pass in `PieItemPlayer.svelte` so that images a\n * PIE element paints into its own light DOM (e.g. a `pie-passage`'s\n * model-driven content) get the same scrollable affordance even though\n * they never appeared in the authored markup string.\n */\n\nconst SCROLL_WRAPPER_CLASS = \"pie-image-scroll\";\nconst PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;\n\nfunction isInsidePieCustomElement(image: Element, root: Element): boolean {\n\tlet ancestor: Element | null = image.parentElement;\n\twhile (ancestor && ancestor !== root) {\n\t\tif (PIE_CUSTOM_ELEMENT_TAG_REGEX.test(ancestor.tagName)) {\n\t\t\treturn true;\n\t\t}\n\t\tancestor = ancestor.parentElement;\n\t}\n\treturn false;\n}\n\nfunction buildAriaLabel(image: Element): string {\n\tconst alt = image.getAttribute(\"alt\");\n\tconst trimmed = alt ? alt.trim() : \"\";\n\treturn trimmed ? `Scrollable image: ${trimmed}` : \"Scrollable image\";\n}\n\nexport interface WrapOverwideImagesInElementOptions {\n\t/**\n\t * When `true`, images whose nearest `pie-*` ancestor is *strictly between*\n\t * the image and `root` are left alone. Used by the string pipeline so the\n\t * authored-markup pass doesn't restructure a PIE element's own template.\n\t * Defaults to `false` so the live-DOM pass *does* wrap element-rendered\n\t * images.\n\t */\n\tskipPieDescendants?: boolean;\n}\n\n/**\n * Wrap every unwrapped `<img>` descendant of `root` with the shared\n * horizontal-scroll span. Returns the number of newly-wrapped images so\n * callers can short-circuit when nothing changed. Idempotent.\n */\nexport function wrapOverwideImagesInElement(\n\troot: Element,\n\toptions: WrapOverwideImagesInElementOptions = {},\n): number {\n\tconst { skipPieDescendants = false } = options;\n\tconst images = Array.from(root.querySelectorAll(\"img\"));\n\tif (images.length === 0) return 0;\n\n\tconst ownerDocument = root.ownerDocument;\n\tif (!ownerDocument) return 0;\n\n\tlet wrapped = 0;\n\tfor (const image of images) {\n\t\tconst parent = image.parentElement;\n\t\tif (!parent) continue;\n\n\t\t// Idempotency — already wrapped.\n\t\tif (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Authored-markup pass: leave PIE custom-element internals alone.\n\t\tif (skipPieDescendants && isInsidePieCustomElement(image, root)) continue;\n\n\t\tconst wrapper = ownerDocument.createElement(\"span\");\n\t\twrapper.className = SCROLL_WRAPPER_CLASS;\n\t\twrapper.setAttribute(\"tabindex\", \"0\");\n\t\twrapper.setAttribute(\"role\", \"region\");\n\t\twrapper.setAttribute(\"aria-label\", buildAriaLabel(image));\n\n\t\tparent.insertBefore(wrapper, image);\n\t\twrapper.appendChild(image);\n\t\twrapped += 1;\n\t}\n\treturn wrapped;\n}\n\n/**\n * Wrap `<img>` elements in `markup` with a horizontal-scroll container.\n *\n * - No-ops on empty input.\n * - No-ops during SSR (no `window` / `DOMParser`) — the markup is returned\n * unchanged; the browser re-run on hydrate will perform the wrap.\n * - Idempotent: images whose direct parent already carries the\n * `pie-image-scroll` class are left alone.\n * - Leaves images inside PIE custom elements (`<pie-*>`) alone. Those are\n * rendered by the element's own template / shadow DOM and should not be\n * restructured by the authored-markup pipeline. Use\n * `wrapOverwideImagesInElement` for the post-render pass that *does* want\n * to wrap element-rendered images.\n */\nexport function wrapOverwideImages(markup: string): string {\n\tif (!markup) return \"\";\n\n\t// Fast path: avoid the DOM round-trip entirely when the markup carries no\n\t// images. Keeps the sanitize pipeline cheap for the common case.\n\tif (!/<img\\b/i.test(markup)) return markup;\n\n\tif (typeof window === \"undefined\" || !window.document) return markup;\n\n\tconst ParserCtor =\n\t\ttypeof DOMParser !== \"undefined\"\n\t\t\t? DOMParser\n\t\t\t: (window as unknown as { DOMParser?: typeof DOMParser }).DOMParser;\n\tif (!ParserCtor) return markup;\n\n\tconst doc = new ParserCtor().parseFromString(\n\t\t`<!DOCTYPE html><html><body>${markup}</body></html>`,\n\t\t\"text/html\",\n\t);\n\tconst body = doc.body;\n\tif (!body) return markup;\n\n\tconst wrapped = wrapOverwideImagesInElement(body, {\n\t\tskipPieDescendants: true,\n\t});\n\treturn wrapped > 0 ? body.innerHTML : markup;\n}\n"]}
@@ -11,8 +11,32 @@
11
11
  * space (including at higher browser-zoom levels — WCAG 1.4.10 Reflow at 400%
12
12
  * zoom is the same driver as for `wrapOverwideImages`).
13
13
  *
14
- * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so
15
- * every host that renders authored markup through the shared
16
- * `pie-item-player` (including the section player) benefits uniformly.
14
+ * Two callable surfaces share the same wrapping logic:
15
+ *
16
+ * - `wrapOverwideTables(markup)` string-in / string-out, used as a
17
+ * post-sanitization step inside `sanitizeItemMarkup`. By design it skips
18
+ * tables inside `pie-*` elements; those are the element's own template and
19
+ * should not be restructured by the authored-markup pipeline.
20
+ * - `wrapOverwideTablesInElement(root)` — operates on a live DOM subtree.
21
+ * Used by the post-render pass in `PieItemPlayer.svelte` so that tables a
22
+ * PIE element paints into its own light DOM (e.g. a `pie-passage`'s
23
+ * model-driven content) get the same scrollable affordance even though
24
+ * they never appeared in the authored markup string.
25
+ */
26
+ export interface WrapOverwideTablesInElementOptions {
27
+ /**
28
+ * When `true`, tables whose nearest `pie-*` ancestor is *strictly between*
29
+ * the table and `root` are left alone. Used by the string pipeline so the
30
+ * authored-markup pass doesn't restructure a PIE element's own template.
31
+ * Defaults to `false` so the live-DOM pass *does* wrap element-rendered
32
+ * tables.
33
+ */
34
+ skipPieDescendants?: boolean;
35
+ }
36
+ /**
37
+ * Wrap every unwrapped `<table>` descendant of `root` with the shared
38
+ * horizontal-scroll div. Returns the number of newly-wrapped tables so
39
+ * callers can short-circuit when nothing changed. Idempotent.
17
40
  */
41
+ export declare function wrapOverwideTablesInElement(root: Element, options?: WrapOverwideTablesInElementOptions): number;
18
42
  export declare function wrapOverwideTables(markup: string): string;