@cueframe/kernel 0.2.52 → 0.2.54

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 (71) hide show
  1. package/dist/_members/composition/fonts/bundledFaces.d.ts +9 -15
  2. package/dist/_members/composition/fonts/captionFaces.d.ts +2 -3
  3. package/dist/_members/composition/ops/captionOps.d.ts +11 -0
  4. package/dist/_members/composition/ops/compositionOps.d.ts +20 -19
  5. package/dist/_members/composition/ops/compositionOps.shared.d.ts +1 -0
  6. package/dist/_members/composition/validators/composition.d.ts +724 -0
  7. package/dist/_members/composition/validators/registry.d.ts +1 -1
  8. package/dist/_members/composition/validators/render.d.ts +332 -0
  9. package/dist/_members/composition-ops/index.d.ts +89 -0
  10. package/dist/_members/core/render/compositionAudioAndDepth.d.ts +3 -2
  11. package/dist/_members/core/render/index.d.ts +1 -1
  12. package/dist/_members/core/render/types.d.ts +3 -1
  13. package/dist/_members/plane/contract.d.ts +1 -1
  14. package/dist/_members/primitives/captions/Captions.d.ts +18 -1
  15. package/dist/_members/primitives/captions/index.d.ts +1 -1
  16. package/dist/_members/primitives/lib/text/spec.d.ts +1 -0
  17. package/dist/animate.js +2 -2
  18. package/dist/authoring/build-tools.js +6 -6
  19. package/dist/authoring/context.js +5 -5
  20. package/dist/authoring/tools.js +5 -5
  21. package/dist/authoring.js +9 -9
  22. package/dist/{chunk-MYBK6DKF.js → chunk-2BVKNYRB.js} +1 -1
  23. package/dist/{chunk-AVTEPAWI.js → chunk-7RGDRDAR.js} +17 -13
  24. package/dist/{chunk-7XZW26U2.js → chunk-AONWKKXK.js} +1 -1
  25. package/dist/{chunk-WZ22ZYXD.js → chunk-C3ARYFN6.js} +4 -4
  26. package/dist/{chunk-4B4V3HYW.js → chunk-C6ONUJIO.js} +1 -1
  27. package/dist/{chunk-CZOVEX2D.js → chunk-CR3G2JQP.js} +1 -1
  28. package/dist/{chunk-2PU2TSUG.js → chunk-D5E5XFHK.js} +1 -1
  29. package/dist/{chunk-D2EG7J7X.js → chunk-DFB7UFZD.js} +1 -1
  30. package/dist/{chunk-FSEB4LSU.js → chunk-FYMXVYWS.js} +1 -1
  31. package/dist/{chunk-6WQHVYJD.js → chunk-G7ZSFSQG.js} +1 -1
  32. package/dist/{chunk-5ZQLMPDY.js → chunk-GAACUQAB.js} +1 -1
  33. package/dist/{chunk-3I43W7DP.js → chunk-GZMKTLO3.js} +52 -21
  34. package/dist/{chunk-QVBUYWW6.js → chunk-Q2FWFTDZ.js} +1 -1
  35. package/dist/{chunk-MBSQKRDF.js → chunk-QCGT3M7M.js} +2 -2
  36. package/dist/{chunk-KMDZDH5E.js → chunk-QCJUGKRC.js} +4 -4
  37. package/dist/{chunk-Z6NY6MCL.js → chunk-RMLGMYEJ.js} +1 -1
  38. package/dist/{chunk-IHXMBXXL.js → chunk-SIGKJOSF.js} +30 -4
  39. package/dist/{chunk-5JCLZPMV.js → chunk-T7WKIMTB.js} +63 -44
  40. package/dist/{chunk-ACURZCPN.js → chunk-UKKY7HWN.js} +1 -1
  41. package/dist/{chunk-2M65KQNG.js → chunk-V6GL37RZ.js} +394 -54
  42. package/dist/{chunk-CV3QQUMK.js → chunk-VKZTGD5Z.js} +128 -5
  43. package/dist/{chunk-JWW54N24.js → chunk-VYSBZUXZ.js} +1 -1
  44. package/dist/{chunk-3OG2674K.js → chunk-ZDE5S3DE.js} +1 -1
  45. package/dist/component-runtime/render.js +2 -2
  46. package/dist/component-runtime.js +3 -3
  47. package/dist/compose.js +5 -5
  48. package/dist/composition/fonts.js +1 -1
  49. package/dist/composition/graphics-plan.js +3 -3
  50. package/dist/composition/ops.js +4 -4
  51. package/dist/composition/projection.js +3 -3
  52. package/dist/composition/validators.js +13 -3
  53. package/dist/composition-ops.js +8 -8
  54. package/dist/composition.js +15 -5
  55. package/dist/{context-2MBCDTCZ.js → context-I7SPXR2Z.js} +5 -5
  56. package/dist/core/render.js +4 -4
  57. package/dist/core.js +4 -4
  58. package/dist/mcp/adapter.js +2 -2
  59. package/dist/mcp/core.js +1 -1
  60. package/dist/mcp/scene-shot.js +2 -2
  61. package/dist/mcp/server.js +12 -12
  62. package/dist/mcp.js +12 -12
  63. package/dist/primitives/captions.js +276 -44
  64. package/dist/primitives/effects.js +3 -3
  65. package/dist/primitives/layouts.js +3 -3
  66. package/dist/primitives/lib/text/marker.js +1 -1
  67. package/dist/primitives/scenes.js +3 -3
  68. package/dist/primitives.js +5 -5
  69. package/dist/render-harness/cardFonts.js +2 -2
  70. package/dist/render-harness.js +7 -7
  71. package/package.json +10 -10
@@ -4,19 +4,19 @@ import {
4
4
  buildPlannerTools,
5
5
  buildToolsFromSchemas,
6
6
  extractProviderError
7
- } from "../chunk-QVBUYWW6.js";
8
- import "../chunk-KMDZDH5E.js";
7
+ } from "../chunk-Q2FWFTDZ.js";
8
+ import "../chunk-QCJUGKRC.js";
9
9
  import "../chunk-JOURGVOR.js";
10
10
  import "../chunk-7AZLFN26.js";
11
- import "../chunk-5JCLZPMV.js";
12
- import "../chunk-MBSQKRDF.js";
13
- import "../chunk-CV3QQUMK.js";
11
+ import "../chunk-T7WKIMTB.js";
12
+ import "../chunk-QCGT3M7M.js";
13
+ import "../chunk-VKZTGD5Z.js";
14
14
  import "../chunk-WPZKOVYF.js";
15
15
  import "../chunk-B7J5UUUV.js";
16
16
  import "../chunk-KYRDKK2D.js";
17
17
  import "../chunk-WREYUH2E.js";
18
18
  import "../chunk-7PA7FGLD.js";
19
- import "../chunk-AVTEPAWI.js";
19
+ import "../chunk-7RGDRDAR.js";
20
20
  import "../chunk-2ESYSVXG.js";
21
21
  export {
22
22
  ZERO_USAGE,
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  buildDirectorContext
3
- } from "../chunk-FSEB4LSU.js";
4
- import "../chunk-5JCLZPMV.js";
5
- import "../chunk-MBSQKRDF.js";
6
- import "../chunk-CV3QQUMK.js";
3
+ } from "../chunk-FYMXVYWS.js";
4
+ import "../chunk-T7WKIMTB.js";
5
+ import "../chunk-QCGT3M7M.js";
6
+ import "../chunk-VKZTGD5Z.js";
7
7
  import "../chunk-WPZKOVYF.js";
8
8
  import "../chunk-B7J5UUUV.js";
9
9
  import "../chunk-KYRDKK2D.js";
10
10
  import "../chunk-WREYUH2E.js";
11
- import "../chunk-AVTEPAWI.js";
11
+ import "../chunk-7RGDRDAR.js";
12
12
  import "../chunk-2ESYSVXG.js";
13
13
  export {
14
14
  buildDirectorContext
@@ -6,18 +6,18 @@ import {
6
6
  COMPOSE_ROLE_TOOLS,
7
7
  COMPOSE_ROLE_TOOL_NAMES,
8
8
  dispatchComposeRoleTool
9
- } from "../chunk-KMDZDH5E.js";
9
+ } from "../chunk-QCJUGKRC.js";
10
10
  import "../chunk-JOURGVOR.js";
11
11
  import "../chunk-7AZLFN26.js";
12
- import "../chunk-5JCLZPMV.js";
13
- import "../chunk-MBSQKRDF.js";
14
- import "../chunk-CV3QQUMK.js";
12
+ import "../chunk-T7WKIMTB.js";
13
+ import "../chunk-QCGT3M7M.js";
14
+ import "../chunk-VKZTGD5Z.js";
15
15
  import "../chunk-WPZKOVYF.js";
16
16
  import "../chunk-B7J5UUUV.js";
17
17
  import "../chunk-KYRDKK2D.js";
18
18
  import "../chunk-WREYUH2E.js";
19
19
  import "../chunk-7PA7FGLD.js";
20
- import "../chunk-AVTEPAWI.js";
20
+ import "../chunk-7RGDRDAR.js";
21
21
  import "../chunk-2ESYSVXG.js";
22
22
  export {
23
23
  COMPOSE_MOTION_TOOLS,
package/dist/authoring.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  buildPlannerTools,
12
12
  buildToolsFromSchemas,
13
13
  extractProviderError
14
- } from "./chunk-QVBUYWW6.js";
14
+ } from "./chunk-Q2FWFTDZ.js";
15
15
  import {
16
16
  COMPOSE_MOTION_TOOLS,
17
17
  COMPOSE_MOTION_VERIFY_TOOLS,
@@ -20,14 +20,14 @@ import {
20
20
  COMPOSE_ROLE_TOOLS,
21
21
  COMPOSE_ROLE_TOOL_NAMES,
22
22
  dispatchComposeRoleTool
23
- } from "./chunk-KMDZDH5E.js";
23
+ } from "./chunk-QCJUGKRC.js";
24
24
  import {
25
25
  AgentFixableCaptureError,
26
26
  getExcludedTools
27
27
  } from "./chunk-JOURGVOR.js";
28
28
  import {
29
29
  buildDirectorContext
30
- } from "./chunk-FSEB4LSU.js";
30
+ } from "./chunk-FYMXVYWS.js";
31
31
  import {
32
32
  buildFastModel,
33
33
  buildModel,
@@ -49,16 +49,16 @@ import {
49
49
  initCodeGenPrompts
50
50
  } from "./chunk-Y4HPN4BC.js";
51
51
  import "./chunk-7AZLFN26.js";
52
- import "./chunk-5JCLZPMV.js";
53
- import "./chunk-MBSQKRDF.js";
54
- import "./chunk-CV3QQUMK.js";
52
+ import "./chunk-T7WKIMTB.js";
53
+ import "./chunk-QCGT3M7M.js";
54
+ import "./chunk-VKZTGD5Z.js";
55
55
  import "./chunk-WPZKOVYF.js";
56
56
  import "./chunk-B7J5UUUV.js";
57
57
  import "./chunk-KYRDKK2D.js";
58
- import "./chunk-6WQHVYJD.js";
58
+ import "./chunk-G7ZSFSQG.js";
59
59
  import {
60
60
  cssLayoutVars
61
- } from "./chunk-Z6NY6MCL.js";
61
+ } from "./chunk-RMLGMYEJ.js";
62
62
  import "./chunk-WREYUH2E.js";
63
63
  import "./chunk-ILO4NEZ3.js";
64
64
  import "./chunk-E63UEO5C.js";
@@ -69,7 +69,7 @@ import {
69
69
  isSceneShotClip
70
70
  } from "./chunk-CL5Y6V6K.js";
71
71
  import "./chunk-2L7PYYJK.js";
72
- import "./chunk-AVTEPAWI.js";
72
+ import "./chunk-7RGDRDAR.js";
73
73
  import "./chunk-2ESYSVXG.js";
74
74
 
75
75
  // ../authoring/src/agent.ts
@@ -3,7 +3,7 @@ import {
3
3
  assertSceneShotParamsAdmitted,
4
4
  materializeComponentSource,
5
5
  renderComponentAlpha
6
- } from "./chunk-7XZW26U2.js";
6
+ } from "./chunk-AONWKKXK.js";
7
7
  import {
8
8
  componentArtifactId,
9
9
  componentBundleId,
@@ -75,7 +75,7 @@ var BUNDLED_FONT_FACES = Object.freeze({
75
75
  },
76
76
  "Instrument Serif": {
77
77
  slug: "instrument-serif",
78
- faces: [{ weight: "400", file: "400" }],
78
+ faces: [{ weight: "400", file: "400" }, { weight: "400", file: "400-italic", style: "italic" }],
79
79
  captionDefaults: { base: 400, emphasis: 400 }
80
80
  },
81
81
  "National Park": {
@@ -102,19 +102,19 @@ function bundledFaceCoversWeight(face, weight) {
102
102
  return weight >= lo && weight <= hi;
103
103
  }
104
104
  function bundledFontFaceExists(family, weight, style) {
105
- if (style !== BUNDLED_FONT_STYLE) return false;
106
105
  const bundled = bundledFontFamilyFaces(family);
107
106
  if (!bundled) return false;
108
- return bundled.faces.some((face) => bundledFaceCoversWeight(face, weight));
107
+ return bundled.faces.some((face) => (face.style ?? BUNDLED_FONT_STYLE) === style && bundledFaceCoversWeight(face, weight));
109
108
  }
110
109
  function describeBundledFontWeights(family) {
111
110
  const bundled = bundledFontFamilyFaces(family);
112
111
  if (!bundled) return "";
113
- return bundled.faces.map((face) => face.weight.split(/\s+/).join("-")).join(", ");
112
+ return [...new Set(bundled.faces.map((face) => face.weight.split(/\s+/).join("-")))].join(", ");
114
113
  }
115
114
  function unrenderableFaceField(family, weight, style, sealed) {
116
- if (bundledFontFamilyFaces(family)) {
117
- return style === BUNDLED_FONT_STYLE ? "fontWeight" : "fontStyle";
115
+ const bundled = bundledFontFamilyFaces(family);
116
+ if (bundled) {
117
+ return bundled.faces.some((face) => (face.style ?? BUNDLED_FONT_STYLE) === style) ? "fontWeight" : "fontStyle";
118
118
  }
119
119
  if (sealed.hasFamily(family)) {
120
120
  return sealed.hasWeight(family, weight) ? "fontStyle" : "fontWeight";
@@ -165,7 +165,7 @@ function admissibleFamilyForStack(stack) {
165
165
  return resolved.kind === "bundled" ? resolved.family : stack;
166
166
  }
167
167
  function weightRanges(faces) {
168
- return faces.map((face) => {
168
+ return [...new Map(faces.map((face) => [face.weight, face])).values()].map((face) => {
169
169
  const parts = face.weight.split(/\s+/).map(Number);
170
170
  const min = parts[0];
171
171
  const max = parts.length > 1 ? parts[1] : min;
@@ -188,7 +188,7 @@ function vendoredFontCatalog() {
188
188
  return {
189
189
  family,
190
190
  weights: weightRanges(entry.faces),
191
- styles: [BUNDLED_FONT_STYLE],
191
+ styles: [...new Set(entry.faces.map((face) => face.style ?? BUNDLED_FONT_STYLE))],
192
192
  captionDefaults: entry.captionDefaults,
193
193
  origin: aliased.has(family) ? "aliased" : "bundled"
194
194
  };
@@ -261,12 +261,12 @@ function faceIssue(use) {
261
261
  return {
262
262
  field: use.field,
263
263
  face: { family: use.stack, weight: use.weight, style: use.style },
264
- message: `font_not_reproducible \u2014 ${because(use)}: "${use.stack}" names no font file the render bundle carries, and it ships no italic binary at all, so the browser would slant whatever upright face it matched and the render refuses it before capture. Emphasise with emphasisFontWeight, emphasisColor or emphasisScale, or name an uploaded brand font that is sealed in italic.`
264
+ message: `font_not_reproducible \u2014 ${because(use)}: "${use.stack}" names no font file the render bundle carries, so this stack ships no italic binary to select and the browser would slant whatever upright face it matched and the render refuses it before capture. Emphasise with emphasisFontWeight, emphasisColor or emphasisScale, or name an uploaded brand font that is sealed in italic.`
265
265
  };
266
266
  }
267
267
  if (use.resolved.kind !== "bundled") return null;
268
268
  const face = { family: use.resolved.family, weight: use.weight, style: use.style };
269
- if (use.style === "italic") {
269
+ if (use.style === "italic" && !bundledFontFamilyFaces(use.resolved.family).faces.some((face2) => face2.style === "italic")) {
270
270
  return {
271
271
  field: use.field,
272
272
  face,
@@ -274,10 +274,11 @@ function faceIssue(use) {
274
274
  };
275
275
  }
276
276
  if (bundledFontFaceExists(use.resolved.family, use.weight, use.style)) return null;
277
+ const weightField = use.role === "emphasis" ? "emphasisFontWeight" : "fontWeight";
277
278
  return {
278
- field: use.field,
279
+ field: weightField,
279
280
  face,
280
- message: `font_not_reproducible \u2014 ${because(use)}: ${use.resolved.family} ships ${describeBundledFontWeights(use.resolved.family)}. The render refuses a weight outside that range before capture rather than let the browser substitute a nearby file, so set ${use.field} to a weight the family covers, or pick a family whose files do.`
281
+ message: `font_not_reproducible \u2014 ${weightField} ${use.weight} (${use.style}) has no file behind it: ${use.resolved.family} ships ${describeBundledFontWeights(use.resolved.family)}. The render refuses a weight outside that range before capture rather than let the browser substitute a nearby file, so set ${weightField} to a weight the family covers, or pick a family whose files do.`
281
282
  };
282
283
  }
283
284
  function captionFaceIssues(style) {
@@ -377,7 +378,10 @@ function captionWeightChoices(stack) {
377
378
  );
378
379
  }
379
380
  function captionStyleChoices(stack) {
380
- return captionStackFamily(stack).kind === "unknown" ? ["normal", "italic"] : ["normal"];
381
+ const resolved = captionStackFamily(stack);
382
+ if (resolved.kind === "unknown") return ["normal", "italic"];
383
+ if (resolved.kind !== "bundled") return ["normal"];
384
+ return [...new Set(bundledFontFamilyFaces(resolved.family).faces.map((face) => face.style ?? BUNDLED_FONT_STYLE))];
381
385
  }
382
386
 
383
387
  // ../composition/src/fonts.ts
@@ -31,7 +31,7 @@ import {
31
31
  } from "./chunk-CL5Y6V6K.js";
32
32
  import {
33
33
  BUNDLED_FONT_FACES
34
- } from "./chunk-AVTEPAWI.js";
34
+ } from "./chunk-7RGDRDAR.js";
35
35
 
36
36
  // ../execution/component-runtime/src/sceneAdmission.ts
37
37
  import { ZodError } from "zod";
@@ -2,12 +2,12 @@ import {
2
2
  SCENE_SHOT_RESOURCE_DESCRIPTION,
3
3
  SCENE_SHOT_RESOURCE_URI,
4
4
  sceneShotContract
5
- } from "./chunk-3OG2674K.js";
5
+ } from "./chunk-ZDE5S3DE.js";
6
6
  import {
7
7
  querySchemaShape,
8
8
  serializeQuery,
9
9
  waitForJob
10
- } from "./chunk-JWW54N24.js";
10
+ } from "./chunk-VYSBZUXZ.js";
11
11
  import {
12
12
  CURATED_TOOLS
13
13
  } from "./chunk-4WBSXHQA.js";
@@ -28,10 +28,10 @@ import {
28
28
  text,
29
29
  textWithData,
30
30
  withMedia
31
- } from "./chunk-2M65KQNG.js";
31
+ } from "./chunk-V6GL37RZ.js";
32
32
  import {
33
33
  CompositionOp
34
- } from "./chunk-IHXMBXXL.js";
34
+ } from "./chunk-SIGKJOSF.js";
35
35
 
36
36
  // ../mcp/src/prompts.ts
37
37
  var skillListCache = null;
@@ -43,7 +43,7 @@ import {
43
43
  makeDefaultScreenTexture,
44
44
  resolveCamera,
45
45
  useImageTexture
46
- } from "./chunk-2PU2TSUG.js";
46
+ } from "./chunk-D5E5XFHK.js";
47
47
  import {
48
48
  DEVICE_GLASS_OPACITY_ENCODED,
49
49
  DeviceMesh,
@@ -24,7 +24,7 @@ import {
24
24
  } from "./chunk-W2245F6G.js";
25
25
  import {
26
26
  FlexLayout
27
- } from "./chunk-2PU2TSUG.js";
27
+ } from "./chunk-D5E5XFHK.js";
28
28
 
29
29
  // ../execution/primitives/src/layouts/chat-to-preview-layout/index.tsx
30
30
  import { Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion";
@@ -34,7 +34,7 @@ import {
34
34
  } from "./chunk-CL5Y6V6K.js";
35
35
  import {
36
36
  resolveCaptionFaces
37
- } from "./chunk-AVTEPAWI.js";
37
+ } from "./chunk-7RGDRDAR.js";
38
38
 
39
39
  // ../execution/animate/src/easing/smooth.ts
40
40
  var sigmoid = (x) => 1 / (1 + Math.exp(-x));
@@ -15,7 +15,7 @@ function Marker({ spec, progress }) {
15
15
  d: path,
16
16
  fill: "none",
17
17
  stroke: spec.color,
18
- strokeWidth: 4,
18
+ strokeWidth: spec.strokeWidth ?? 4,
19
19
  strokeLinecap: "round",
20
20
  strokeLinejoin: "round",
21
21
  pathLength: 1,
@@ -2,7 +2,7 @@ import {
2
2
  selectAllClips,
3
3
  selectAllOverlays,
4
4
  selectSceneMarkers
5
- } from "./chunk-5JCLZPMV.js";
5
+ } from "./chunk-T7WKIMTB.js";
6
6
 
7
7
  // ../authoring/src/context/transcript.ts
8
8
  var MAX_TRANSCRIPT_CHARS = 6e4;
@@ -2,7 +2,7 @@ import {
2
2
  activeFaceKeepOutSeries,
3
3
  boundingBox,
4
4
  resolveBrandRefsDeep
5
- } from "./chunk-Z6NY6MCL.js";
5
+ } from "./chunk-RMLGMYEJ.js";
6
6
  import {
7
7
  CueframeError,
8
8
  encodeError
@@ -26,7 +26,7 @@ import {
26
26
  perspectiveFor,
27
27
  resolveCamera,
28
28
  useStage
29
- } from "./chunk-2PU2TSUG.js";
29
+ } from "./chunk-D5E5XFHK.js";
30
30
 
31
31
  // ../execution/primitives/src/effects/blur/index.tsx
32
32
  import React2 from "react";
@@ -31,7 +31,7 @@ import {
31
31
  } from "./chunk-VZ7TYX6Z.js";
32
32
  import {
33
33
  resolveBrandRef
34
- } from "./chunk-Z6NY6MCL.js";
34
+ } from "./chunk-RMLGMYEJ.js";
35
35
  import {
36
36
  SHOT_SCALE_TARGET,
37
37
  shotScaleZoom
@@ -64,7 +64,7 @@ import {
64
64
  bundledFontFaceExists,
65
65
  mergedCaptionFaceIssues,
66
66
  unrenderableFaceField
67
- } from "./chunk-AVTEPAWI.js";
67
+ } from "./chunk-7RGDRDAR.js";
68
68
 
69
69
  // ../execution/core/src/render/sceneCaptureIdentity.ts
70
70
  var INTENT_FIELDS = [
@@ -1226,22 +1226,33 @@ function bindBehindSubjectHosts(overlays, clips) {
1226
1226
  );
1227
1227
  }
1228
1228
  }
1229
- function assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, output, effects = []) {
1230
- for (const segment of captionSegments ?? []) {
1231
- for (const word of segment) {
1232
- const startFrame = Math.round(word.startMs / 1e3 * fps);
1233
- const endFrame = Math.max(startFrame + 1, Math.round(word.endMs / 1e3 * fps));
1234
- for (const window of visiblePackedHostWindows(clips, startFrame, endFrame, output, effects)) {
1235
- if (!window.captionVisible) continue;
1236
- if (window.hostClipId) continue;
1237
- const code = window.candidateClipIds.length > 1 ? "behind_subject_caption_host_ambiguous" : "behind_subject_caption_host_missing";
1238
- throw renderInputError(
1239
- code,
1240
- `Behind-subject caption word '${word.text}' is not fully covered by a unique visible packed host during frames [${window.startFrame}, ${window.endFrame}); an exact matte is required for the visible picture.`,
1241
- { candidateClipIds: window.candidateClipIds, startFrame: window.startFrame, endFrame: window.endFrame }
1242
- );
1243
- }
1229
+ function assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, output, effects = [], captionLayouts = []) {
1230
+ const requireCoverage = (label, startMs, endMs) => {
1231
+ const startFrame = Math.round(startMs / 1e3 * fps);
1232
+ const endFrame = Math.max(startFrame + 1, Math.round(endMs / 1e3 * fps));
1233
+ for (const window of visiblePackedHostWindows(clips, startFrame, endFrame, output, effects)) {
1234
+ if (!window.captionVisible || window.hostClipId) continue;
1235
+ const code = window.candidateClipIds.length > 1 ? "behind_subject_caption_host_ambiguous" : "behind_subject_caption_host_missing";
1236
+ throw renderInputError(
1237
+ code,
1238
+ `Behind-subject caption ${label} is not fully covered by a unique visible packed host during frames [${window.startFrame}, ${window.endFrame}); an exact matte is required for the visible picture.`,
1239
+ { candidateClipIds: window.candidateClipIds, startFrame: window.startFrame, endFrame: window.endFrame }
1240
+ );
1244
1241
  }
1242
+ };
1243
+ for (const segment of captionSegments ?? []) {
1244
+ for (const word of segment) requireCoverage(`word '${word.text}'`, word.startMs, word.endMs);
1245
+ }
1246
+ for (const layout of captionLayouts ?? []) {
1247
+ const segment = captionSegments?.[layout.segmentIndex];
1248
+ const first = segment?.[layout.wordStartIndex];
1249
+ const last = segment?.[layout.holdThroughWordIndex ?? layout.wordEndIndex - 1];
1250
+ if (!first || !last) throw new Error("Behind-subject caption layout references missing saved transcript words");
1251
+ requireCoverage(
1252
+ `layout segment ${layout.segmentIndex} words ${layout.wordStartIndex}..${layout.wordEndIndex - 1}`,
1253
+ first.startMs,
1254
+ last.endMs
1255
+ );
1245
1256
  }
1246
1257
  }
1247
1258
  var FULL_RECT = { left: 0, top: 0, right: 1, bottom: 1 };
@@ -1272,6 +1283,11 @@ function coversLowerPictures(upper, lower, output, effects) {
1272
1283
  return upperRect.left <= rect.left + epsilon && upperRect.top <= rect.top + epsilon && upperRect.right + epsilon >= rect.right && upperRect.bottom + epsilon >= rect.bottom;
1273
1284
  });
1274
1285
  }
1286
+ function explicitlyHidesCaptions(clip, effects) {
1287
+ if (!clip || clip.captionVisibility !== "hidden" || clip.mediaType === "image") return false;
1288
+ const region = clip.region;
1289
+ return !!region && region.x === 0 && region.y === 0 && region.w === 1 && region.h === 1 && (clip.opacity ?? 1) === 1 && (clip.scale ?? 1) === 1 && (clip.rotation ?? 0) === 0 && !clip.animate && !clip.cropTrajectory && !clip.cropTrajectoryUrl && !clip.hasCropIntent && !hasClipEffect(clip, effects) && !clip.hasTransition && (!clip.transitionIn || clip.transitionIn === "none");
1290
+ }
1275
1291
  function visiblePackedHostWindows(clips, startFrame, endFrame, output, effects = [], hostMode = "packed-only") {
1276
1292
  if (endFrame <= startFrame) return [];
1277
1293
  const boundaries = /* @__PURE__ */ new Set([startFrame, endFrame]);
@@ -1307,7 +1323,7 @@ function visiblePackedHostWindows(clips, startFrame, endFrame, output, effects =
1307
1323
  effects
1308
1324
  ));
1309
1325
  const suppressesCaption = provesOcclusion && !globalEffectActive && pictureRect(top[0], output, effects) !== void 0;
1310
- const captionVisible = !(suppressesCaption && top[0].captionVisibility === "hidden");
1326
+ const captionVisible = !(top.length === 1 && !globalEffectActive && (suppressesCaption && top[0].captionVisibility === "hidden" || explicitlyHidesCaptions(top[0], effects)));
1311
1327
  const candidateHost = (clip) => hostMode === "all-video-candidates" ? clip.mediaType !== "image" : !!clip.packedUrl;
1312
1328
  const host = provesOcclusion && candidateHost(top[0]) ? top[0] : void 0;
1313
1329
  const unresolvedOverlap = top.length > 1 || top.length === 1 && active.length > 1 && !provesOcclusion;
@@ -1879,6 +1895,11 @@ function captionVisibilityWindowsForComposition(composition, sourceDimensions) {
1879
1895
  ...item.region ? { region: item.region } : {},
1880
1896
  ...item.fit ? { fit: item.fit } : {},
1881
1897
  ...track.kind === "video" && item.captionVisibility ? { captionVisibility: item.captionVisibility } : {},
1898
+ ...item.opacity !== void 0 ? { opacity: item.opacity } : {},
1899
+ ...item.scale !== void 0 ? { scale: item.scale } : {},
1900
+ ...item.rotation !== void 0 ? { rotation: item.rotation } : {},
1901
+ ...item.animate !== void 0 ? { animate: item.animate } : {},
1902
+ ...spanIndex === 0 && item.transitionIn?.type !== void 0 && item.transitionIn.type !== "cut" && item.transitionIn.type !== "none" ? { hasTransition: true } : {},
1882
1903
  ...item.source.reframe?.segments.length ? { hasCropIntent: true } : {},
1883
1904
  ...item.effects?.length ? { hasClipEffects: true } : {}
1884
1905
  });
@@ -1976,6 +1997,11 @@ function assertMergedCaptionFacesDrawable(composition) {
1976
1997
  if (!captions?.segments.some((segment) => segment.words.length > 0)) return;
1977
1998
  const brandCaption = composition.brandKit?.caption;
1978
1999
  const issues = mergedCaptionFaceIssues(captions.style, brandCaption);
2000
+ for (const [index, layout] of (captions.layouts ?? []).entries()) {
2001
+ for (const issue of mergedCaptionFaceIssues({ ...captions.style, ...layout.style }, brandCaption)) {
2002
+ issues.push({ ...issue, path: `captions.layouts[${index}].style.${issue.path.split(".").at(-1)}` });
2003
+ }
2004
+ }
1979
2005
  if (issues.length === 0) return;
1980
2006
  throw renderInputError(
1981
2007
  "font_not_reproducible",
@@ -2078,9 +2104,9 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
2078
2104
  if (end > maxBackgroundEndFrames) maxBackgroundEndFrames = end;
2079
2105
  }
2080
2106
  const bodyDurationInFrames = maxBackgroundEndFrames > 0 ? Math.min(maxEndFrames, maxBackgroundEndFrames) : maxEndFrames;
2081
- const captionSegments = composition.captions?.segments.map((seg) => seg.words).filter((words) => words.length > 0);
2107
+ const captionSegments = composition.captions?.segments.map((seg) => seg.words);
2082
2108
  if (composition.captions?.zPlane === "behind-subject") {
2083
- assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, { width, height }, effects);
2109
+ assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, { width, height }, effects, composition.captions.layouts);
2084
2110
  }
2085
2111
  const brandKit = composition.brandKit;
2086
2112
  const { totalFrames: durationInFrames } = resolveBookendTimeline(
@@ -2102,7 +2128,7 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
2102
2128
  ...mot.exitMs != null ? { exitMs: mot.exitMs } : {}
2103
2129
  } : void 0;
2104
2130
  const baseStyle = composition.captions?.style;
2105
- const hasCaptions = captionSegments != null && captionSegments.length > 0;
2131
+ const hasCaptions = captionSegments?.some((words) => words.length > 0) ?? false;
2106
2132
  const captionStyle = hasCaptions && (cap || baseStyle || captionMotion) ? {
2107
2133
  ...baseStyle ?? {},
2108
2134
  ...cap?.fontFamily ? { fontFamily: cap.fontFamily } : {},
@@ -2127,6 +2153,10 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
2127
2153
  ...cap?.exitMs != null ? { exitMs: cap.exitMs } : {}
2128
2154
  } : void 0;
2129
2155
  const resolvedCaptionStyle = captionStyle ? resolveCaptionColorRefs(captionStyle, brandKit) : void 0;
2156
+ const captionLayouts = composition.captions?.layouts?.map((layout) => ({
2157
+ ...layout,
2158
+ ...layout.style ? { style: resolveCaptionColorRefs(layout.style, brandKit) } : {}
2159
+ }));
2130
2160
  return {
2131
2161
  clips,
2132
2162
  overlays,
@@ -2138,6 +2168,7 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
2138
2168
  ...audioTracks.length > 0 ? { audioTracks } : {},
2139
2169
  ...effects.length > 0 ? { effects } : {},
2140
2170
  ...hasCaptions ? { captionSegments } : {},
2171
+ ...hasCaptions && captionLayouts?.length ? { captionLayouts } : {},
2141
2172
  ...hasCaptions && composition.captions?.zPlane === "behind-subject" ? { captionsBehind: true } : {},
2142
2173
  ...resolvedCaptionStyle ? { captionStyle: resolvedCaptionStyle } : {},
2143
2174
  ...brandKit ? { brandKit } : {}
@@ -2,7 +2,7 @@ import {
2
2
  COMPOSE_PLANNER_TOOLS,
3
3
  COMPOSE_ROLE_TOOLS,
4
4
  dispatchComposeRoleTool
5
- } from "./chunk-KMDZDH5E.js";
5
+ } from "./chunk-QCJUGKRC.js";
6
6
 
7
7
  // ../authoring/src/agent/buildTools.ts
8
8
  import { tool } from "ai";
@@ -4,10 +4,10 @@ import {
4
4
  Format,
5
5
  GraphicRole,
6
6
  OpaqueId
7
- } from "./chunk-CV3QQUMK.js";
7
+ } from "./chunk-VKZTGD5Z.js";
8
8
  import {
9
9
  brandKitCaptionFaceIssues
10
- } from "./chunk-AVTEPAWI.js";
10
+ } from "./chunk-7RGDRDAR.js";
11
11
 
12
12
  // ../composition/src/validators/composePlan.ts
13
13
  import { z } from "zod";
@@ -7,13 +7,13 @@ import {
7
7
  } from "./chunk-7AZLFN26.js";
8
8
  import {
9
9
  selectAllOverlays
10
- } from "./chunk-5JCLZPMV.js";
10
+ } from "./chunk-T7WKIMTB.js";
11
11
  import {
12
12
  ComposePlan
13
- } from "./chunk-MBSQKRDF.js";
13
+ } from "./chunk-QCGT3M7M.js";
14
14
  import {
15
15
  OverlayClipSource
16
- } from "./chunk-CV3QQUMK.js";
16
+ } from "./chunk-VKZTGD5Z.js";
17
17
 
18
18
  // ../authoring/src/tools/toolSets.ts
19
19
  var COMPOSE_PLANNER_TOOLS = [
@@ -63,7 +63,7 @@ var COMPOSE_ROLE_TOOLS = Object.freeze({
63
63
  schema: z.object({}),
64
64
  metadata: { label: "Get Context" },
65
65
  handler: async (_args, capabilities) => {
66
- const { buildDirectorContext } = await import("./context-2MBCDTCZ.js");
66
+ const { buildDirectorContext } = await import("./context-I7SPXR2Z.js");
67
67
  return buildDirectorContext(capabilities);
68
68
  }
69
69
  },
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  captionFontSource,
12
12
  effectiveCaptionFamily
13
- } from "./chunk-AVTEPAWI.js";
13
+ } from "./chunk-7RGDRDAR.js";
14
14
 
15
15
  // ../composition/src/projection/brandKit.ts
16
16
  var LOGO_DEFAULT_PLACEMENT = "br";
@@ -3,25 +3,29 @@ import {
3
3
  projectMediaPlaybackSpans,
4
4
  sliceTranscriptWords,
5
5
  splitIntoSentences
6
- } from "./chunk-3I43W7DP.js";
6
+ } from "./chunk-GZMKTLO3.js";
7
7
  import {
8
8
  createHeadlessStore,
9
9
  findClipById,
10
10
  findMarkerById,
11
11
  findTrackContainingClipId
12
- } from "./chunk-5JCLZPMV.js";
12
+ } from "./chunk-T7WKIMTB.js";
13
13
  import {
14
+ CaptionLayoutInput,
14
15
  CaptionSegment,
15
16
  CaptionStyle,
16
17
  Composition,
18
+ CompositionCaptions,
17
19
  Format,
18
20
  Marker,
19
21
  PerClipEffect,
20
22
  TimelineClip,
21
23
  Track,
22
24
  Transition,
25
+ captionLayoutIssues,
26
+ captionSegmentSignature,
23
27
  captionVisibilityIssues
24
- } from "./chunk-CV3QQUMK.js";
28
+ } from "./chunk-VKZTGD5Z.js";
25
29
  import {
26
30
  ReframeSegment
27
31
  } from "./chunk-B7J5UUUV.js";
@@ -31,7 +35,7 @@ import {
31
35
  } from "./chunk-CL5Y6V6K.js";
32
36
  import {
33
37
  introducedCaptionFaceIssues
34
- } from "./chunk-AVTEPAWI.js";
38
+ } from "./chunk-7RGDRDAR.js";
35
39
 
36
40
  // ../composition-ops/src/index.ts
37
41
  import { z } from "zod";
@@ -480,6 +484,9 @@ var CompositionOp = z.discriminatedUnion("type", [
480
484
  z.object({ type: z.literal("setCaptions"), segments: z.array(CaptionSegment), style: CaptionStyle.partial().optional() }).strict().describe(
481
485
  "Replace the caption SEGMENTS wholesale \u2014 author the word-timed caption layer directly (each segment = one sentence/page of `words` with timeline-ms `startMs`/`endMs`, optional `emphasis`). This is how a client with its OWN word timings (e.g. a generated VO take) gets word-synced captions without a source transcript; the compose lane's transcript slice writes the same layer. `style`, when given, merges with setCaptionStyle semantics; an omitted style keeps the existing one. Renderer paginates within a segment and never pools words across two \u2014 keep segments sentence-bounded."
482
486
  ),
487
+ z.object({ type: z.literal("setCaptionLayouts"), layouts: z.array(CaptionLayoutInput) }).strict().describe(
488
+ "Replace native caption placements without changing transcript words or timings. Each layout references a half-open word-index range in one saved segment; a one-word range is valid. region places that group independently, optional bounded scale/rotation/style changes its look, and holdThroughWordIndex keeps it visible through a later saved word in the same segment. All words in a placed range occupy one page; rendering refuses a range that cannot fit its box, so enlarge region or split the range. The box reserves horizontal space of 1.6 font sizes and vertical space of 0.8 font sizes for padding, with at most three lines. Unreferenced words keep the default caption layout. Word ranges must not overlap. Reapply after caption regeneration or timeline cuts."
489
+ ),
483
490
  z.object({ type: z.literal("applyMotionPreset"), clipId: z.string(), preset: z.string(), params: z.object({ enterMs: z.number().optional(), exitMs: z.number().optional(), staggerMs: z.number().optional() }).strict().optional() }).strict().describe(
484
491
  "Shell a STATIC `card` clip with a frame-driven MOTION preset \u2014 the public card-shelling op (the layout renders VERBATIM; only the container animates in/out). The server wraps the card in the chosen `preset` (fade-up / slide-in-left / slide-in-right / scale-pop / stagger-reveal), transpiles it, mints it as a LIVE component, and swaps the clip source to that component (the bakeless live lane \u2014 no box). `params` tunes timing (`enterMs`/`exitMs`, `staggerMs` for stagger-reveal). The target must be a static card (components already carry motion; library overlays already move); re-run overwrites idempotently ONLY while the target is still a card (an uncompilable shell); once shelled, the clip is a COMPONENT \u2014 to change its motion, remove and re-add the card, then re-shell. Honored under `dry_run` (validates preset + target without transpiling or persisting). ORDERING: the preset resolves its target against the PERSISTED composition \u2014 a card added in the SAME batch is not yet visible to it. Persist the clip.add batch first, then shell in a following call."
485
492
  ),
@@ -665,6 +672,20 @@ function applyOpToStore(store, op) {
665
672
  case "setCaptions":
666
673
  applyCaptionOpToStore(store, op);
667
674
  break;
675
+ case "setCaptionLayouts": {
676
+ const captions = composition.captions;
677
+ if (!captions) throw new Error("setCaptionLayouts requires saved transcript captions");
678
+ const issue = captionLayoutIssues(captions.segments, op.layouts)[0];
679
+ if (issue) throw new Error(`setCaptionLayouts.layouts[${issue.index}].${issue.field}: ${issue.message}`);
680
+ const layouts = op.layouts.map((layout) => ({
681
+ ...layout,
682
+ wordSignature: captionSegmentSignature(captions.segments[layout.segmentIndex].words)
683
+ }));
684
+ const parsed = CompositionCaptions.safeParse({ ...captions, layouts });
685
+ if (!parsed.success) throw new Error(`setCaptionLayouts: ${parsed.error.issues[0]?.message}`);
686
+ store.setCaptionLayouts(layouts);
687
+ break;
688
+ }
668
689
  case "applyMotionPreset":
669
690
  throw new Error(
670
691
  `applyOp: 'applyMotionPreset' is route-materialized \u2014 it needs a director transpile of the generated motion shell and is rewritten to a clip.update at /composition/apply before reaching the pure engine.`
@@ -801,6 +822,11 @@ function invertOp(before, op) {
801
822
  }
802
823
  case "setCaptionDepth":
803
824
  return { type: "setCaptionDepth", zPlane: before.captions?.zPlane ?? "front" };
825
+ case "setCaptionLayouts":
826
+ return {
827
+ type: "setCaptionLayouts",
828
+ layouts: (before.captions?.layouts ?? []).map(({ wordSignature: _signature, ...layout }) => layout)
829
+ };
804
830
  case "addTrack":
805
831
  if (op.id === void 0) {
806
832
  throw new Error("invertOp(addTrack): no pinned `id` \u2014 cannot target the minted track");