@intellectif/lk-core 1.5.0 → 1.7.0

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 (86) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/{ai-BYryFkF9.d.ts → ai-B1wYrqcB.d.cts} +83 -18
  3. package/dist/ai-B1wYrqcB.d.cts.map +1 -0
  4. package/dist/{ai-BYryFkF9.d.cts → ai-B1wYrqcB.d.ts} +83 -18
  5. package/dist/ai-B1wYrqcB.d.ts.map +1 -0
  6. package/dist/ai-check.cjs +13 -6
  7. package/dist/ai-check.cjs.map +1 -1
  8. package/dist/ai-check.d.cts +1 -5
  9. package/dist/ai-check.d.cts.map +1 -1
  10. package/dist/ai-check.d.ts +1 -5
  11. package/dist/ai-check.d.ts.map +1 -1
  12. package/dist/ai-check.js +13 -6
  13. package/dist/ai-check.js.map +1 -1
  14. package/dist/{ai-writing-D8PdhJJz.js → ai-writing-B7oMCt66.js} +311 -66
  15. package/dist/ai-writing-B7oMCt66.js.map +1 -0
  16. package/dist/{ai-writing-Cen7-3qs.cjs → ai-writing-DrPTV3R2.cjs} +313 -50
  17. package/dist/ai-writing-DrPTV3R2.cjs.map +1 -0
  18. package/dist/{index-DUaW1Tj5.d.ts → index-BM8LeDWn.d.cts} +2 -42
  19. package/dist/index-BM8LeDWn.d.cts.map +1 -0
  20. package/dist/{index-Bg0aeWtM.d.cts → index-BTkMQtws.d.ts} +2 -42
  21. package/dist/index-BTkMQtws.d.ts.map +1 -0
  22. package/dist/{index-wMc1E_aM.d.ts → index-BU99GFTl.d.ts} +2 -8
  23. package/dist/index-BU99GFTl.d.ts.map +1 -0
  24. package/dist/{index-CxpiWedW.d.ts → index-BdXs4-en.d.ts} +42 -20
  25. package/dist/index-BdXs4-en.d.ts.map +1 -0
  26. package/dist/{index-qMbxed1B.d.cts → index-DP9rvyOz.d.cts} +42 -20
  27. package/dist/index-DP9rvyOz.d.cts.map +1 -0
  28. package/dist/{index-D7R3Ll8R.d.cts → index-XCCoPoTU.d.cts} +2 -8
  29. package/dist/index-XCCoPoTU.d.cts.map +1 -0
  30. package/dist/index.cjs +70 -21
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +135 -69
  33. package/dist/index.d.cts.map +1 -1
  34. package/dist/index.d.ts +135 -69
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +65 -22
  37. package/dist/index.js.map +1 -1
  38. package/dist/{item-group-DEB7nLID.cjs → item-group-8P3WFuUK.cjs} +5 -6
  39. package/dist/item-group-8P3WFuUK.cjs.map +1 -0
  40. package/dist/{item-group-DdSUCUjM.js → item-group-CeDk7mfM.js} +5 -6
  41. package/dist/item-group-CeDk7mfM.js.map +1 -0
  42. package/dist/{registry-DaZRpqjW.js → registry-D92hz9iA.js} +124 -106
  43. package/dist/registry-D92hz9iA.js.map +1 -0
  44. package/dist/{registry-Ce00A_OW.cjs → registry-hzrkTeIT.cjs} +142 -107
  45. package/dist/registry-hzrkTeIT.cjs.map +1 -0
  46. package/dist/{schemas-DksueYsu.cjs → schemas-BWeskONy.cjs} +79 -18
  47. package/dist/schemas-BWeskONy.cjs.map +1 -0
  48. package/dist/{schemas-BioKFf3F.js → schemas-C7TViWCg.js} +74 -19
  49. package/dist/schemas-C7TViWCg.js.map +1 -0
  50. package/dist/schemas.cjs +5 -2
  51. package/dist/schemas.d.cts +2 -2
  52. package/dist/schemas.d.ts +2 -2
  53. package/dist/schemas.js +4 -3
  54. package/dist/{scoring-lKQVYsxy.cjs → scoring-CtkCFyFT.cjs} +22 -42
  55. package/dist/{scoring-lKQVYsxy.cjs.map → scoring-CtkCFyFT.cjs.map} +1 -1
  56. package/dist/{scoring-BKNNTz-s.js → scoring-DD1GHcLI.js} +22 -42
  57. package/dist/{scoring-BKNNTz-s.js.map → scoring-DD1GHcLI.js.map} +1 -1
  58. package/dist/scoring.cjs +2 -2
  59. package/dist/scoring.d.cts +2 -2
  60. package/dist/scoring.d.ts +2 -2
  61. package/dist/scoring.js +2 -2
  62. package/dist/{xapi-NYrTlpID.cjs → xapi-7btSpx6v.cjs} +5 -8
  63. package/dist/{xapi-NYrTlpID.cjs.map → xapi-7btSpx6v.cjs.map} +1 -1
  64. package/dist/{xapi-wR6gu4qS.js → xapi-Cgicg-zU.js} +5 -8
  65. package/dist/{xapi-wR6gu4qS.js.map → xapi-Cgicg-zU.js.map} +1 -1
  66. package/dist/xapi.cjs +1 -1
  67. package/dist/xapi.d.cts +1 -1
  68. package/dist/xapi.d.ts +1 -1
  69. package/dist/xapi.js +1 -1
  70. package/package.json +2 -2
  71. package/dist/ai-BYryFkF9.d.cts.map +0 -1
  72. package/dist/ai-BYryFkF9.d.ts.map +0 -1
  73. package/dist/ai-writing-Cen7-3qs.cjs.map +0 -1
  74. package/dist/ai-writing-D8PdhJJz.js.map +0 -1
  75. package/dist/index-Bg0aeWtM.d.cts.map +0 -1
  76. package/dist/index-CxpiWedW.d.ts.map +0 -1
  77. package/dist/index-D7R3Ll8R.d.cts.map +0 -1
  78. package/dist/index-DUaW1Tj5.d.ts.map +0 -1
  79. package/dist/index-qMbxed1B.d.cts.map +0 -1
  80. package/dist/index-wMc1E_aM.d.ts.map +0 -1
  81. package/dist/item-group-DEB7nLID.cjs.map +0 -1
  82. package/dist/item-group-DdSUCUjM.js.map +0 -1
  83. package/dist/registry-Ce00A_OW.cjs.map +0 -1
  84. package/dist/registry-DaZRpqjW.js.map +0 -1
  85. package/dist/schemas-BioKFf3F.js.map +0 -1
  86. package/dist/schemas-DksueYsu.cjs.map +0 -1
@@ -1,4 +1,4 @@
1
- //#region \0rolldown/runtime.js
1
+ // \0rolldown/runtime.js
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -19,11 +19,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
19
19
  value: mod,
20
20
  enumerable: true
21
21
  }) : target, mod));
22
- //#endregion
23
22
  let zod_v4 = require("zod/v4");
24
23
  zod_v4 = __toESM(zod_v4, 1);
25
24
  let zod_v4_core = require("zod/v4/core");
26
- //#region src/schemas/ai.ts
25
+ // src/schemas/ai.ts
27
26
  /**
28
27
  * What an author allows AI to do for one item: see `ActivityAiPermissions`.
29
28
  * Loose like every content schema, so a key a later release adds survives an
@@ -33,8 +32,7 @@ const AiPermissionsSchema = zod_v4.looseObject({
33
32
  explanations: zod_v4.boolean().optional(),
34
33
  hints: zod_v4.boolean().optional()
35
34
  });
36
- //#endregion
37
- //#region src/schemas/feedback.ts
35
+ // src/schemas/feedback.ts
38
36
  /**
39
37
  * Optional authored "overall feedback" shown after submission, selected by
40
38
  * whether the learner passed (h5p-style overall feedback). Both fields are
@@ -44,8 +42,7 @@ const FeedbackSchema = zod_v4.looseObject({
44
42
  correct: zod_v4.string().min(1).optional(),
45
43
  incorrect: zod_v4.string().min(1).optional()
46
44
  });
47
- //#endregion
48
- //#region src/schemas/text-length.ts
45
+ // src/schemas/text-length.ts
49
46
  /**
50
47
  * `schema`, refusing a string longer than `max` UTF-16 code units — its
51
48
  * JavaScript `length`, the unit these limits have always counted in.
@@ -68,8 +65,7 @@ function maxUnits(schema, max) {
68
65
  });
69
66
  }).meta({ maxLength: max });
70
67
  }
71
- //#endregion
72
- //#region src/schemas/media.ts
68
+ // src/schemas/media.ts
73
69
  /**
74
70
  * Optional media attached to an activity, rendered above the question or
75
71
  * passage — e.g. a recording to listen to, or an embedded video to watch
@@ -157,6 +153,20 @@ const MediaTrackSchema = zod_v4.strictObject({
157
153
  default: zod_v4.boolean().optional()
158
154
  });
159
155
  const MAX_TRACKS = 12;
156
+ /** The most turns a recording's `script` may hold. */
157
+ const SCRIPT_MAX_TURNS = 500;
158
+ /**
159
+ * One speaker's turn in an author-only `script`. Loose, as content is: a host
160
+ * may keep its own fields on a turn (the voice it cast, a direction for it).
161
+ * Unlike a track's, both fields are required, so a misspelt one is still
162
+ * caught.
163
+ */
164
+ const ScriptTurnSchema = zod_v4.looseObject({
165
+ speaker: maxUnits(zod_v4.string(), 60).refine((speaker) => speaker.trim().length > 0, { error: "A script turn needs a speaker." }),
166
+ text: zod_v4.string().refine((text) => text.trim().length > 0, { error: "A script turn needs words." })
167
+ });
168
+ /** An author-only `script`: one turn or more, in the order they are spoken. */
169
+ const ScriptSchema = zod_v4.array(ScriptTurnSchema).min(1).max(500);
160
170
  /** `poster` on anything but a video: an image with nothing to stand in for. */
161
171
  function posterFits(m) {
162
172
  return m.poster === void 0 || m.type === "video";
@@ -199,7 +209,9 @@ const MediaSchema = zod_v4.looseObject({
199
209
  captionsUrl: MediaUrlSchema.optional(),
200
210
  tracks: zod_v4.array(MediaTrackSchema).min(1).max(MAX_TRACKS).optional(),
201
211
  poster: MediaUrlSchema.optional(),
202
- playback: MediaPlaybackSchema.optional()
212
+ playback: MediaPlaybackSchema.optional(),
213
+ transcript: zod_v4.string().optional(),
214
+ script: ScriptSchema.optional()
203
215
  }).refine((m) => m.type !== "image" && m.type !== "embed" || typeof m.alt === "string" && m.alt.length > 0, {
204
216
  error: "image and embed media require non-empty alt text (WCAG 1.1.1 / 4.1.2).",
205
217
  path: ["alt"]
@@ -251,8 +263,7 @@ const RedactedMediaSchema = zod_v4.strictObject({
251
263
  error: "embed media requires an absolute http(s) provider URL. data:, blob:, and relative URLs are not allowed for embeds — the embed iframe runs with allow-scripts, and a data:/same-origin document there is an XSS vector.",
252
264
  path: ["url"]
253
265
  }).refine(posterFits, POSTER_FITS).refine(tracksFit, TRACKS_FIT).refine(tracksAreDistinct, TRACKS_DISTINCT);
254
- //#endregion
255
- //#region src/schemas/fill-in-the-blanks.ts
266
+ // src/schemas/fill-in-the-blanks.ts
256
267
  /**
257
268
  * Matches `{{ blank_id }}` placeholders in a passage, capturing the trimmed id.
258
269
  * Exported for the draft checks in `authoring/`, which must read a passage
@@ -260,6 +271,12 @@ const RedactedMediaSchema = zod_v4.strictObject({
260
271
  */
261
272
  const PLACEHOLDER_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
262
273
  /**
274
+ * The instructions of an item with a passage: plain text, with no `{{id}}`
275
+ * placeholder. Blanks and gaps live in the passage only, and one written here
276
+ * would be shown to the learner as it stands. Shared by Gap Select.
277
+ */
278
+ const PassageInstructionsSchema = zod_v4.string().refine((text) => text.match(PLACEHOLDER_RE) === null, { error: "The instructions are plain text: a {{id}} placeholder belongs in the passage, where its blank or gap is." });
279
+ /**
263
280
  * Zod schema for a `TextMatchPolicy` — the opt-in matching tolerances a blank
264
281
  * may declare. Every default reproduces the v1 trim + case-fold semantics.
265
282
  */
@@ -307,6 +324,7 @@ const FillInTheBlanksDataSchema = zod_v4.looseObject({
307
324
  type: zod_v4.literal("fill-in-the-blanks"),
308
325
  id: zod_v4.string().min(1),
309
326
  title: zod_v4.string().min(1),
327
+ instructions: PassageInstructionsSchema.optional(),
310
328
  passage: zod_v4.string().min(1),
311
329
  passageHtml: zod_v4.string().optional(),
312
330
  blanks: zod_v4.array(BlankConfigSchema).min(1),
@@ -338,8 +356,7 @@ const FillInTheBlanksDataSchema = zod_v4.looseObject({
338
356
  error: "Each blank id must appear exactly once in blanks[] and have exactly one matching {{id}} placeholder in the passage, and vice versa.",
339
357
  path: ["passage"]
340
358
  });
341
- //#endregion
342
- //#region src/answer-leak.ts
359
+ // src/answer-leak.ts
343
360
  /**
344
361
  * Whether a text gives an answer away: the rule an AI hint is held to, and the
345
362
  * one the item critic holds an author's own hint, title and passage to.
@@ -426,8 +443,7 @@ function revealsAnswer(text, answer, passage, position) {
426
443
  if (before === void 0 && after === void 0) return containsWords(words, needle);
427
444
  return before !== void 0 && containsWords(words, [before, ...needle]) || after !== void 0 && containsWords(words, [...needle, after]);
428
445
  }
429
- //#endregion
430
- //#region src/built-in-types.ts
446
+ // src/built-in-types.ts
431
447
  /**
432
448
  * The activity types the SDK ships, in one closed list.
433
449
  *
@@ -463,8 +479,7 @@ function isBuiltInActivityType(type) {
463
479
  * @internal
464
480
  */
465
481
  function everyBuiltInTypeHandled(_value) {}
466
- //#endregion
467
- //#region src/scoring/dictation/edit-distance.ts
482
+ // src/scoring/dictation/edit-distance.ts
468
483
  /**
469
484
  * The code points of `text`, as numbers. A surrogate pair is one code point; a
470
485
  * lone surrogate iterates as one code point too — deterministic, never thrown.
@@ -516,8 +531,7 @@ function similarity(a, b) {
516
531
  const max = Math.max(codePoints(a).length, codePoints(b).length);
517
532
  return max === 0 ? 0 : (max - editDistance(a, b)) / max;
518
533
  }
519
- //#endregion
520
- //#region src/validation-scope.ts
534
+ // src/validation-scope.ts
521
535
  /**
522
536
  * Work shared for the length of one outermost validation call.
523
537
  *
@@ -556,8 +570,7 @@ function scopeCache(key, create) {
556
570
  }
557
571
  return value;
558
572
  }
559
- //#endregion
560
- //#region src/scoring/dictation/unicode-joining.ts
573
+ // src/scoring/dictation/unicode-joining.ts
561
574
  /**
562
575
  * Unicode data the dictation normaliser reads when it decides whether a joiner
563
576
  * changes how the text is drawn (see `normalize.ts`): the viramas, and which
@@ -775,8 +788,7 @@ const JOINS_BACKWARD = [
775
788
  [69577, 69578],
776
789
  [125184, 125251]
777
790
  ];
778
- //#endregion
779
- //#region src/scoring/dictation/normalize.ts
791
+ // src/scoring/dictation/normalize.ts
780
792
  /**
781
793
  * The longest transcript a dictation accepts, in code points, before and after
782
794
  * its equivalences are applied — and its rules may not grow it past twice
@@ -1736,8 +1748,7 @@ function searchReveal(forms, candidate) {
1736
1748
  return fields.some((field) => containsPhrase(field, phrase));
1737
1749
  });
1738
1750
  }
1739
- //#endregion
1740
- //#region src/scoring/dictation/align.ts
1751
+ // src/scoring/dictation/align.ts
1741
1752
  /**
1742
1753
  * The words the scorer can emit a detail for, one list per candidate — index 0
1743
1754
  * is the transcript, index n is `acceptedTranscripts[n − 1]` — each word in the
@@ -1936,8 +1947,7 @@ function alignWords(reference, attempt) {
1936
1947
  }
1937
1948
  return words;
1938
1949
  }
1939
- //#endregion
1940
- //#region src/errors.ts
1950
+ // src/errors.ts
1941
1951
  /** Thrown when activity data fails schema validation at a component boundary. */
1942
1952
  var ActivitySchemaError = class extends Error {
1943
1953
  activityType;
@@ -1991,8 +2001,7 @@ var DeferredScoringError = class extends Error {
1991
2001
  this.name = "DeferredScoringError";
1992
2002
  }
1993
2003
  };
1994
- //#endregion
1995
- //#region src/schemas/after-issues.ts
2004
+ // src/schemas/after-issues.ts
1996
2005
  /**
1997
2006
  * A check zod runs even when another field was refused. An ordinary check is
1998
2007
  * skipped once any issue has been reported, so an author fixing a recording's
@@ -2029,8 +2038,7 @@ function parsedFields(payload) {
2029
2038
  refused
2030
2039
  };
2031
2040
  }
2032
- //#endregion
2033
- //#region src/schemas/dictation.ts
2041
+ // src/schemas/dictation.ts
2034
2042
  /**
2035
2043
  * Zod schema for the slower second recording of a dictation. Loose like every
2036
2044
  * content schema (a sidecar survives), audio only, and — deliberately — with no
@@ -2212,8 +2220,7 @@ function groupCaptionsField(group, written) {
2212
2220
  }
2213
2221
  /** The message every validator gives for {@link groupCaptionsRevealDictation}. */
2214
2222
  const GROUP_CAPTIONS_REVEAL_DICTATION = "The stimulus recording carries captions, and a dictation in this group plays that recording: the captions are the answer. Remove the captions, or give the dictation its own recording.";
2215
- //#endregion
2216
- //#region src/authoring/issues.ts
2223
+ // src/authoring/issues.ts
2217
2224
  /**
2218
2225
  * Every issue code the SDK's draft checks can report, with its severity.
2219
2226
  *
@@ -2549,8 +2556,7 @@ function coveredPathsOf(reported) {
2549
2556
  }
2550
2557
  return covered;
2551
2558
  }
2552
- //#endregion
2553
- //#region src/authoring/dictation.ts
2559
+ // src/authoring/dictation.ts
2554
2560
  /**
2555
2561
  * Draft support for `dictation`.
2556
2562
  *
@@ -2740,8 +2746,7 @@ function checkTolerance(tolerance) {
2740
2746
  }
2741
2747
  return issues;
2742
2748
  }
2743
- //#endregion
2744
- //#region src/count-words.ts
2749
+ // src/count-words.ts
2745
2750
  /**
2746
2751
  * Canonical word counter for written-response bounds: tokens are
2747
2752
  * maximal runs of non-whitespace (split on `\s+`), so hyphenated forms
@@ -2846,8 +2851,7 @@ function levelDistribution(values, count, chosen) {
2846
2851
  const sum = read.reduce((total, p) => total + p, 0);
2847
2852
  return Math.abs(sum - 1) <= 1e-6 && read[chosen] === Math.max(...read) ? read : null;
2848
2853
  }
2849
- //#endregion
2850
- //#region src/scoring/strategies/all-or-nothing.ts
2854
+ // src/scoring/strategies/all-or-nothing.ts
2851
2855
  /**
2852
2856
  * All-or-nothing scoring: full credit only when every item is correct.
2853
2857
  *
@@ -2857,8 +2861,7 @@ function levelDistribution(values, count, chosen) {
2857
2861
  function allOrNothingStrategy(correctItems) {
2858
2862
  return correctItems.every((isCorrect) => isCorrect) ? 1 : 0;
2859
2863
  }
2860
- //#endregion
2861
- //#region src/scoring/strategies/partial.ts
2864
+ // src/scoring/strategies/partial.ts
2862
2865
  /**
2863
2866
  * Partial-credit scoring for Multiple Choice — balanced/symmetric scheme.
2864
2867
  *
@@ -2898,8 +2901,7 @@ function partialStrategy(correctSelected, incorrectSelected, totalCorrect, total
2898
2901
  function partialBlankStrategy(correctBlanks, totalBlanks) {
2899
2902
  return correctBlanks / totalBlanks;
2900
2903
  }
2901
- //#endregion
2902
- //#region src/scoring/text-match.ts
2904
+ // src/scoring/text-match.ts
2903
2905
  /** Combining marks (any script) removed for diacritic folding after NFD decomposition. */
2904
2906
  const COMBINING_MARKS_RE = /\p{M}/gu;
2905
2907
  /**
@@ -2992,8 +2994,7 @@ function matchText(input, accepted, policy = {}) {
2992
2994
  via: "none"
2993
2995
  };
2994
2996
  }
2995
- //#endregion
2996
- //#region src/scoring/activity-scorers/fill-in-the-blanks.ts
2997
+ // src/scoring/activity-scorers/fill-in-the-blanks.ts
2997
2998
  /**
2998
2999
  * Resolves the effective match policy for a blank. The legacy
2999
3000
  * `caseSensitive` / `trimWhitespace` flags map onto the baseline policy
@@ -3037,8 +3038,7 @@ function scoreFillInTheBlanks(data, response) {
3037
3038
  details
3038
3039
  };
3039
3040
  }
3040
- //#endregion
3041
- //#region src/authoring/findings.ts
3041
+ // src/authoring/findings.ts
3042
3042
  /**
3043
3043
  * Every finding code the SDK's item critic can report, with its severity.
3044
3044
  *
@@ -3064,6 +3064,9 @@ const ITEM_FINDING_SEVERITY = {
3064
3064
  wr_criterion_name_duplicate: "warning",
3065
3065
  wr_levels_unreadable: "warning",
3066
3066
  ra_text_long_for_time: "warning",
3067
+ media_transcript_unused: "advice",
3068
+ media_script_differs: "warning",
3069
+ media_transcript_differs: "warning",
3067
3070
  set_key_position_same: "warning"
3068
3071
  };
3069
3072
  function finding(code, path, message) {
@@ -3088,8 +3091,7 @@ function normaliseFinding(found) {
3088
3091
  severity: documented ?? (found.severity === "advice" ? "advice" : "warning")
3089
3092
  };
3090
3093
  }
3091
- //#endregion
3092
- //#region src/authoring/critique.ts
3094
+ // src/authoring/critique.ts
3093
3095
  /**
3094
3096
  * The built-in item critic: what an experienced item writer points out on
3095
3097
  * review, as far as a rule can see it without a model.
@@ -3372,8 +3374,7 @@ function keyPositionFinding(items, path) {
3372
3374
  if (positions.length < KEY_POSITION_MINIMUM || !positions.every((at) => at === positions[0])) return [];
3373
3375
  return [finding("set_key_position_same", path, `In all ${positions.length} single-answer questions the right option is option ${ordinal(positions[0])}. Vary its position, or shuffle the options.`)];
3374
3376
  }
3375
- //#endregion
3376
- //#region src/authoring/fill-in-the-blanks.ts
3377
+ // src/authoring/fill-in-the-blanks.ts
3377
3378
  /**
3378
3379
  * Draft support for `fill-in-the-blanks`.
3379
3380
  *
@@ -3505,8 +3506,7 @@ function pairsOneToOne$1(placeholders, blanks) {
3505
3506
  if (placeholders.size !== blankIds.length) return false;
3506
3507
  return blankIds.every((id) => typeof id === "string" && placeholders.get(id) === 1);
3507
3508
  }
3508
- //#endregion
3509
- //#region src/authoring/gap-select.ts
3509
+ // src/authoring/gap-select.ts
3510
3510
  /** Mirrors `GapSelectChoiceSchema` arrays: `.min(2)` on both a gap's own list and a bank. */
3511
3511
  const MIN_CHOICES = 2;
3512
3512
  /**
@@ -3704,8 +3704,7 @@ function pairsOneToOne(placeholders, gaps) {
3704
3704
  if (new Set(ids).size !== ids.length || placeholders.size !== ids.length) return false;
3705
3705
  return ids.every((id) => typeof id === "string" && placeholders.get(id) === 1);
3706
3706
  }
3707
- //#endregion
3708
- //#region src/schemas/multiple-choice.ts
3707
+ // src/schemas/multiple-choice.ts
3709
3708
  /**
3710
3709
  * Zod schema for a single Multiple Choice option. Loose: unknown keys are
3711
3710
  * preserved through validation (forward-compat / consumer sidecars — B7).
@@ -3732,7 +3731,9 @@ const MultipleChoiceOptionMediaSchema = zod_v4.looseObject({
3732
3731
  type: zod_v4.enum(["image", "audio"]),
3733
3732
  url: MediaUrlSchema,
3734
3733
  alt: zod_v4.string().min(1).optional(),
3735
- captionsUrl: MediaUrlSchema.optional()
3734
+ captionsUrl: MediaUrlSchema.optional(),
3735
+ transcript: zod_v4.string().optional(),
3736
+ script: ScriptSchema.optional()
3736
3737
  }).refine((media) => media.type !== "image" || media.alt !== void 0 && media.alt !== "", {
3737
3738
  error: "An image option requires a non-empty `alt`.",
3738
3739
  path: ["alt"]
@@ -3767,6 +3768,7 @@ const MultipleChoiceDataSchema = zod_v4.looseObject({
3767
3768
  type: zod_v4.literal("multiple-choice"),
3768
3769
  id: zod_v4.string().min(1),
3769
3770
  title: zod_v4.string().min(1),
3771
+ instructions: zod_v4.string().optional(),
3770
3772
  question: zod_v4.string().min(1),
3771
3773
  questionHtml: zod_v4.string().optional(),
3772
3774
  mode: zod_v4.enum(["single", "multi"]),
@@ -3796,8 +3798,7 @@ const MultipleChoiceDataSchema = zod_v4.looseObject({
3796
3798
  error: "Option ids must be unique within the activity.",
3797
3799
  path: ["options"]
3798
3800
  });
3799
- //#endregion
3800
- //#region src/authoring/multiple-choice.ts
3801
+ // src/authoring/multiple-choice.ts
3801
3802
  /** Mirrors `MultipleChoiceDataSchema`: `options` is `.min(2).max(26)`. */
3802
3803
  const MIN_OPTIONS = 2;
3803
3804
  const MAX_OPTIONS = 26;
@@ -3900,8 +3901,7 @@ function checkOptionMedia(media, index, ordinal) {
3900
3901
  if (kind === "video" || kind === "embed") return [issue("mc_option_media_kind", [...path, "type"], `Option ${ordinal} carries ${kind === "embed" ? "an embedded player" : "a video"}, which cannot be an option: its controls swallow the click that selects the answer. Use a picture or a recording.`)];
3901
3902
  return checkMedia(media, path, MultipleChoiceOptionMediaSchema);
3902
3903
  }
3903
- //#endregion
3904
- //#region src/scoring/speech/limits.ts
3904
+ // src/scoring/speech/limits.ts
3905
3905
  /**
3906
3906
  * The longest reference text a read-aloud item accepts, in code points, before
3907
3907
  * and after normalisation. It is the dictation transcript cap on purpose: the
@@ -3951,8 +3951,7 @@ const SPEECH_ASSESSMENT_MAX_WORDS = 1e3;
3951
3951
  * read-aloud item comes near it.
3952
3952
  */
3953
3953
  const SPEECH_ASSESSMENT_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH;
3954
- //#endregion
3955
- //#region src/scoring/speech/locale.ts
3954
+ // src/scoring/speech/locale.ts
3956
3955
  /**
3957
3956
  * A BCP 47 tag in the one spelling read-aloud accepts: a lowercase language of
3958
3957
  * two or three letters, an optional title-case script, and a region of two
@@ -3968,8 +3967,7 @@ const SPEECH_ASSESSMENT_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH;
3968
3967
  * Internal: the content schema and the assessment validator share it.
3969
3968
  */
3970
3969
  const CANONICAL_LOCALE_RE = /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?-(?:[A-Z]{2}|[0-9]{3})$/;
3971
- //#endregion
3972
- //#region src/schemas/read-aloud.ts
3970
+ // src/schemas/read-aloud.ts
3973
3971
  /** The dimensions a read-aloud grade can be made of, in the order they are documented. */
3974
3972
  const READ_ALOUD_DIMENSIONS = [
3975
3973
  "accuracy",
@@ -4122,8 +4120,7 @@ const ReadAloudDataSchema = zod_v4.looseObject({
4122
4120
  if (minSeconds !== void 0 && minSeconds >= maxSeconds) report(["recording", "minSeconds"], minSeconds, "The shortest take must be shorter than the longest.");
4123
4121
  }
4124
4122
  }));
4125
- //#endregion
4126
- //#region src/authoring/read-aloud.ts
4123
+ // src/authoring/read-aloud.ts
4127
4124
  /**
4128
4125
  * Draft support for `read-aloud`.
4129
4126
  *
@@ -4286,8 +4283,7 @@ function checkSlowRecording(draft) {
4286
4283
  if (typeof slow.url === "string" && slow.url !== "" && slow.url === media.url) issues.push(issue("ra_slow_media_same_recording", ["slowMedia", "url"], "The slow model recording is the same file as the recording. Point it at the slower reading."));
4287
4284
  return issues;
4288
4285
  }
4289
- //#endregion
4290
- //#region src/authoring/written-response.ts
4286
+ // src/authoring/written-response.ts
4291
4287
  /**
4292
4288
  * Draft support for `written-response`.
4293
4289
  *
@@ -4375,8 +4371,7 @@ function checkCriteria(criteria) {
4375
4371
  else if (totalWeight === Number.POSITIVE_INFINITY) issues.push(issue("wr_rubric_weights_too_large", ["rubric", "criteria"], "The rubric weights add up to more than can be calculated with. Use smaller weights."));
4376
4372
  return issues;
4377
4373
  }
4378
- //#endregion
4379
- //#region src/schemas/gap-select.ts
4374
+ // src/schemas/gap-select.ts
4380
4375
  /**
4381
4376
  * Zod schema for one selectable choice. Loose, like every other schema here:
4382
4377
  * unknown keys are preserved so a consumer sidecar survives validation.
@@ -4440,6 +4435,7 @@ const GapSelectDataSchema = zod_v4.looseObject({
4440
4435
  type: zod_v4.literal("gap-select"),
4441
4436
  id: zod_v4.string().min(1),
4442
4437
  title: zod_v4.string().min(1),
4438
+ instructions: PassageInstructionsSchema.optional(),
4443
4439
  passage: zod_v4.string().min(1),
4444
4440
  passageHtml: zod_v4.string().optional(),
4445
4441
  gaps: zod_v4.array(GapSelectGapSchema).min(1),
@@ -4500,8 +4496,7 @@ const GapSelectDataSchema = zod_v4.looseObject({
4500
4496
  error: "Each gap needs unique choice ids and a `correctChoiceId` that is one of the choices it offers.",
4501
4497
  path: ["gaps"]
4502
4498
  });
4503
- //#endregion
4504
- //#region src/schemas/written-response.ts
4499
+ // src/schemas/written-response.ts
4505
4500
  /** Zod schema for a single rubric criterion. Loose: unknown keys preserved. */
4506
4501
  const WrittenResponseRubricCriterionSchema = zod_v4.looseObject({
4507
4502
  name: zod_v4.string().min(1),
@@ -4572,8 +4567,7 @@ const WrittenResponseDataSchema = zod_v4.looseObject({
4572
4567
  error: "maxWords must be greater than or equal to minWords.",
4573
4568
  path: ["maxWords"]
4574
4569
  });
4575
- //#endregion
4576
- //#region src/schemas/redacted.ts
4570
+ // src/schemas/redacted.ts
4577
4571
  /**
4578
4572
  * Schemas for REDACTED activity data — the learner-safe projection `redact()`
4579
4573
  * produces (R7). Deliberately STRICT (`z.strictObject`), the opposite of the
@@ -4637,6 +4631,7 @@ const RedactedMultipleChoiceOptionSchema = zod_v4.strictObject({
4637
4631
  const RedactedMultipleChoiceDataSchema = zod_v4.strictObject({
4638
4632
  ...redactedBase,
4639
4633
  type: zod_v4.literal("multiple-choice"),
4634
+ instructions: zod_v4.string().optional(),
4640
4635
  question: zod_v4.string().min(1),
4641
4636
  questionHtml: zod_v4.string().optional(),
4642
4637
  mode: zod_v4.enum(["single", "multi"]),
@@ -4652,6 +4647,7 @@ const RedactedBlankConfigSchema = zod_v4.strictObject({
4652
4647
  const RedactedFillInTheBlanksDataSchema = zod_v4.strictObject({
4653
4648
  ...redactedBase,
4654
4649
  type: zod_v4.literal("fill-in-the-blanks"),
4650
+ instructions: zod_v4.string().optional(),
4655
4651
  passage: zod_v4.string().min(1),
4656
4652
  passageHtml: zod_v4.string().optional(),
4657
4653
  blanks: zod_v4.array(RedactedBlankConfigSchema).min(1)
@@ -4690,6 +4686,7 @@ const RedactedGapSelectGapSchema = zod_v4.strictObject({
4690
4686
  const RedactedGapSelectDataSchema = zod_v4.strictObject({
4691
4687
  ...redactedBase,
4692
4688
  type: zod_v4.literal("gap-select"),
4689
+ instructions: zod_v4.string().optional(),
4693
4690
  passage: zod_v4.string().min(1),
4694
4691
  passageHtml: zod_v4.string().optional(),
4695
4692
  gaps: zod_v4.array(RedactedGapSelectGapSchema).min(1),
@@ -4815,8 +4812,7 @@ const RedactedReadAloudDataSchema = zod_v4.strictObject({
4815
4812
  path: ["slowMedia", "url"]
4816
4813
  });
4817
4814
  });
4818
- //#endregion
4819
- //#region src/scoring/activity-scorers/dictation.ts
4815
+ // src/scoring/activity-scorers/dictation.ts
4820
4816
  /**
4821
4817
  * Scores a Dictation response: the character-level similarity of what the
4822
4818
  * learner typed to the transcript, over the whole sentence.
@@ -4855,8 +4851,7 @@ function scoreDictation(data, response) {
4855
4851
  details
4856
4852
  };
4857
4853
  }
4858
- //#endregion
4859
- //#region src/scoring/activity-scorers/gap-select.ts
4854
+ // src/scoring/activity-scorers/gap-select.ts
4860
4855
  /**
4861
4856
  * The choices a gap offers: its own list, or the bank it names.
4862
4857
  *
@@ -4910,8 +4905,7 @@ function scoreGapSelect(data, response) {
4910
4905
  details
4911
4906
  };
4912
4907
  }
4913
- //#endregion
4914
- //#region src/scoring/activity-scorers/multiple-choice.ts
4908
+ // src/scoring/activity-scorers/multiple-choice.ts
4915
4909
  /**
4916
4910
  * Scores a Multiple Choice response. Pure; trusts its typed inputs (validation
4917
4911
  * is the component boundary's job). Options are looked up by id; an unknown
@@ -4955,8 +4949,7 @@ function scoreMultipleChoice(data, response) {
4955
4949
  details
4956
4950
  };
4957
4951
  }
4958
- //#endregion
4959
- //#region src/registry/define.ts
4952
+ // src/registry/define.ts
4960
4953
  /**
4961
4954
  * Identity helper that gives full type inference when authoring a descriptor:
4962
4955
  *
@@ -4971,8 +4964,7 @@ function scoreMultipleChoice(data, response) {
4971
4964
  function defineActivityType(descriptor) {
4972
4965
  return descriptor;
4973
4966
  }
4974
- //#endregion
4975
- //#region src/registry/keep-when.ts
4967
+ // src/registry/keep-when.ts
4976
4968
  /**
4977
4969
  * A nested field policy that keeps its field only when the field's value
4978
4970
  * passes a test. Internal: the symbol is never exported, so no host policy can
@@ -4997,8 +4989,22 @@ function keepWhen(policy, test) {
4997
4989
  function guardOf(policy) {
4998
4990
  return policy[KEEP_WHEN];
4999
4991
  }
5000
- //#endregion
5001
- //#region src/registry/builtins.ts
4992
+ // src/registry/builtins.ts
4993
+ /**
4994
+ * Where the built-in types keep their files (`mediaSlots`). Every type has its
4995
+ * `media`; dictation and read-aloud add a slower recording; a multiple-choice
4996
+ * option may carry a picture or a recording of its own.
4997
+ */
4998
+ const MAIN_MEDIA = [{ path: ["media"] }];
4999
+ const MAIN_AND_SLOW_MEDIA = [{ path: ["media"] }, { path: ["slowMedia"] }];
5000
+ function multipleChoiceMediaSlots(data) {
5001
+ const options = Array.isArray(data.options) ? data.options : [];
5002
+ return [...MAIN_MEDIA, ...options.map((_, index) => ({ path: [
5003
+ "options",
5004
+ index,
5005
+ "media"
5006
+ ] }))];
5007
+ }
5002
5008
  /**
5003
5009
  * Field-sensitivity policies for the built-in types (R7). Fail-closed:
5004
5010
  * anything not listed here is dropped by `redact()`. `scoringStrategy` is
@@ -5019,10 +5025,11 @@ function guardOf(policy) {
5019
5025
  * documents ("a field the policy does not classify is removed") stopped at the
5020
5026
  * media boundary. It no longer does.
5021
5027
  *
5022
- * Everything here is public by necessity: the client is the thing that renders
5023
- * and enforces the policy, and "1 play remaining" is text the learner has to
5024
- * read. None of it is an answer key — a listening paper's answer key is
5025
- * `stimulus.transcript`, which stays `author-only`.
5028
+ * Public by necessity, except what the recording says: the client is the thing
5029
+ * that renders and enforces the policy, and "1 play remaining" is text the
5030
+ * learner has to read. `transcript` and `script` are the exception, and
5031
+ * `author-only`: what a listening question's recording says is that question's
5032
+ * answer, as `stimulus.transcript` is a listening paper's.
5026
5033
  */
5027
5034
  const MEDIA_FIELD_POLICY = {
5028
5035
  type: "public",
@@ -5043,7 +5050,9 @@ const MEDIA_FIELD_POLICY = {
5043
5050
  seek: "public",
5044
5051
  rate: "public",
5045
5052
  nativeControlHints: "public"
5046
- }
5053
+ },
5054
+ transcript: "author-only",
5055
+ script: "author-only"
5047
5056
  };
5048
5057
  const SHARED_PUBLIC_FIELDS = {
5049
5058
  schemaVersion: "public",
@@ -5098,6 +5107,7 @@ const TEXT_MATCH_FIELD_POLICY = {
5098
5107
  };
5099
5108
  const MULTIPLE_CHOICE_FIELD_POLICY = {
5100
5109
  ...SHARED_PUBLIC_FIELDS,
5110
+ instructions: "public",
5101
5111
  question: "public",
5102
5112
  questionHtml: "public",
5103
5113
  mode: "public",
@@ -5113,7 +5123,9 @@ const MULTIPLE_CHOICE_FIELD_POLICY = {
5113
5123
  type: "public",
5114
5124
  url: "public",
5115
5125
  alt: "public",
5116
- captionsUrl: "public"
5126
+ captionsUrl: "public",
5127
+ transcript: "author-only",
5128
+ script: "author-only"
5117
5129
  }
5118
5130
  }
5119
5131
  };
@@ -5130,6 +5142,7 @@ const MULTIPLE_CHOICE_FIELD_POLICY = {
5130
5142
  */
5131
5143
  const GAP_SELECT_FIELD_POLICY = {
5132
5144
  ...SHARED_PUBLIC_FIELDS,
5145
+ instructions: "public",
5133
5146
  passage: "public",
5134
5147
  passageHtml: "public",
5135
5148
  presentation: "public",
@@ -5156,6 +5169,7 @@ const GAP_SELECT_FIELD_POLICY = {
5156
5169
  };
5157
5170
  const FILL_IN_THE_BLANKS_FIELD_POLICY = {
5158
5171
  ...SHARED_PUBLIC_FIELDS,
5172
+ instructions: "public",
5159
5173
  passage: "public",
5160
5174
  passageHtml: "public",
5161
5175
  scoringStrategy: "answer-key",
@@ -5253,7 +5267,8 @@ const multipleChoiceType = defineActivityType({
5253
5267
  "option-deselected",
5254
5268
  "submitted"
5255
5269
  ],
5256
- authoring: multipleChoiceAuthoring
5270
+ authoring: multipleChoiceAuthoring,
5271
+ mediaSlots: multipleChoiceMediaSlots
5257
5272
  });
5258
5273
  /** Built-in Fill-in-the-Blanks descriptor. */
5259
5274
  const fillInTheBlanksType = defineActivityType({
@@ -5276,7 +5291,8 @@ const fillInTheBlanksType = defineActivityType({
5276
5291
  "hint-requested",
5277
5292
  "submitted"
5278
5293
  ],
5279
- authoring: fillInTheBlanksAuthoring
5294
+ authoring: fillInTheBlanksAuthoring,
5295
+ mediaSlots: () => MAIN_MEDIA
5280
5296
  });
5281
5297
  /**
5282
5298
  * Built-in Written Response descriptor. Grading is DEFERRED: submissions are
@@ -5308,7 +5324,8 @@ const writtenResponseType = defineActivityType({
5308
5324
  correctResponsesPattern: () => []
5309
5325
  },
5310
5326
  interactions: ["text-changed", "submitted"],
5311
- authoring: writtenResponseAuthoring
5327
+ authoring: writtenResponseAuthoring,
5328
+ mediaSlots: () => MAIN_MEDIA
5312
5329
  });
5313
5330
  /** Built-in Gap Select descriptor. */
5314
5331
  const gapSelectType = defineActivityType({
@@ -5327,7 +5344,8 @@ const gapSelectType = defineActivityType({
5327
5344
  correctResponsesPattern: (data) => [data.gaps.map((gap) => `${gap.id}[.]${gap.correctChoiceId}`).join("[,]")]
5328
5345
  },
5329
5346
  interactions: ["gap-selected", "submitted"],
5330
- authoring: gapSelectAuthoring
5347
+ authoring: gapSelectAuthoring,
5348
+ mediaSlots: () => MAIN_MEDIA
5331
5349
  });
5332
5350
  /**
5333
5351
  * Built-in Dictation descriptor. Graded synchronously: the score is the
@@ -5354,7 +5372,8 @@ const dictationType = defineActivityType({
5354
5372
  "hint-requested",
5355
5373
  "submitted"
5356
5374
  ],
5357
- authoring: dictationAuthoring
5375
+ authoring: dictationAuthoring,
5376
+ mediaSlots: () => MAIN_AND_SLOW_MEDIA
5358
5377
  });
5359
5378
  /**
5360
5379
  * Read Aloud, the type with no answer key at all.
@@ -5435,7 +5454,8 @@ const readAloudType = defineActivityType({
5435
5454
  "recording-uploaded",
5436
5455
  "submitted"
5437
5456
  ],
5438
- authoring: readAloudAuthoring
5457
+ authoring: readAloudAuthoring,
5458
+ mediaSlots: () => MAIN_AND_SLOW_MEDIA
5439
5459
  });
5440
5460
  /**
5441
5461
  * The SDK's own types, by type: what the registry is created holding
@@ -5452,8 +5472,7 @@ const BUILT_IN_DESCRIPTORS = {
5452
5472
  dictation: dictationType,
5453
5473
  "read-aloud": readAloudType
5454
5474
  };
5455
- //#endregion
5456
- //#region src/schemas/read-schema.ts
5475
+ // src/schemas/read-schema.ts
5457
5476
  /**
5458
5477
  * How every entry point reads a schema — `validateActivity`, `validateDraft`,
5459
5478
  * `validateItemGroup`, `redact`, `assertRedacted` — so a schema is read one way
@@ -5563,14 +5582,13 @@ function isStandardSchema(value) {
5563
5582
  * A schema's own JSON Schema, when it implements the Standard JSON Schema
5564
5583
  * converter (`'~standard'.jsonSchema`) — zod 4.6 does — or `undefined`.
5565
5584
  */
5566
- function standardJsonSchemaOf(schema) {
5585
+ function standardJsonSchemaOf(schema, target = "draft-07") {
5567
5586
  const converter = schema["~standard"].jsonSchema;
5568
5587
  if (typeof converter?.output !== "function") return;
5569
- const generated = converter.output({ target: "draft-07" });
5588
+ const generated = converter.output({ target });
5570
5589
  return generated !== null && typeof generated === "object" && !Array.isArray(generated) ? generated : void 0;
5571
5590
  }
5572
- //#endregion
5573
- //#region src/registry/registry.ts
5591
+ // src/registry/registry.ts
5574
5592
  /**
5575
5593
  * Module-scoped default registry (deliberate — no registry instances until a
5576
5594
  * second consumer exists), created holding the SDK's own types.
@@ -5616,7 +5634,6 @@ function getActivityTypeDescriptor(type) {
5616
5634
  function registeredActivityTypes() {
5617
5635
  return [...registry.keys()];
5618
5636
  }
5619
- //#endregion
5620
5637
  Object.defineProperty(exports, "ActivitySchemaError", {
5621
5638
  enumerable: true,
5622
5639
  get: function() {
@@ -5773,6 +5790,12 @@ Object.defineProperty(exports, "RedactedScoringError", {
5773
5790
  return RedactedScoringError;
5774
5791
  }
5775
5792
  });
5793
+ Object.defineProperty(exports, "SCRIPT_MAX_TURNS", {
5794
+ enumerable: true,
5795
+ get: function() {
5796
+ return SCRIPT_MAX_TURNS;
5797
+ }
5798
+ });
5776
5799
  Object.defineProperty(exports, "SPEECH_ASSESSMENT_MAX_TEXT_LENGTH", {
5777
5800
  enumerable: true,
5778
5801
  get: function() {
@@ -5791,6 +5814,12 @@ Object.defineProperty(exports, "STEP", {
5791
5814
  return STEP;
5792
5815
  }
5793
5816
  });
5817
+ Object.defineProperty(exports, "ScriptSchema", {
5818
+ enumerable: true,
5819
+ get: function() {
5820
+ return ScriptSchema;
5821
+ }
5822
+ });
5794
5823
  Object.defineProperty(exports, "UnknownActivityTypeError", {
5795
5824
  enumerable: true,
5796
5825
  get: function() {
@@ -5887,6 +5916,12 @@ Object.defineProperty(exports, "fillInTheBlanksType", {
5887
5916
  return fillInTheBlanksType;
5888
5917
  }
5889
5918
  });
5919
+ Object.defineProperty(exports, "finding", {
5920
+ enumerable: true,
5921
+ get: function() {
5922
+ return finding;
5923
+ }
5924
+ });
5890
5925
  Object.defineProperty(exports, "fold", {
5891
5926
  enumerable: true,
5892
5927
  get: function() {
@@ -6092,4 +6127,4 @@ Object.defineProperty(exports, "writtenResponseType", {
6092
6127
  }
6093
6128
  });
6094
6129
 
6095
- //# sourceMappingURL=registry-Ce00A_OW.cjs.map
6130
+ //# sourceMappingURL=registry-hzrkTeIT.cjs.map