@vanillaskyai/video 0.8.8 → 0.10.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 (173) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/PUBLIC-API.md +5 -8
  3. package/dist/{builtin-server-CUYPIDE7.js → builtin-server-W4PLJUZ5.js} +2 -2
  4. package/dist/{catalog-types-BIhSpOWK.d.ts → catalog-types-WTbLP6Jh.d.ts} +5 -1
  5. package/dist/chapter-title-YRLIQY3V.js +7 -0
  6. package/dist/check-runtime.js +5 -14
  7. package/dist/{chunk-AITKH6QT.js → chunk-2HFO22K2.js} +15 -40
  8. package/dist/chunk-4YM2M62S.js +13 -0
  9. package/dist/chunk-4ZJLPHBV.js +684 -0
  10. package/dist/chunk-77VY4O7A.js +390 -0
  11. package/dist/{chunk-MMUXVA47.js → chunk-7M56IUUX.js} +9 -8
  12. package/dist/chunk-7NZOMTAL.js +63 -0
  13. package/dist/{chunk-2DOLF4QN.js → chunk-AUPC6MDK.js} +12 -14
  14. package/dist/{chunk-352ZZCET.js → chunk-EGVQODKU.js} +2 -2
  15. package/dist/{chunk-2USVB3CY.js → chunk-ELVOT2FQ.js} +284 -177
  16. package/dist/chunk-EWWCQFTI.js +18 -0
  17. package/dist/{chunk-KT3M7KDL.js → chunk-HKUI5BTB.js} +3 -3
  18. package/dist/chunk-IIN5M5HW.js +697 -0
  19. package/dist/{chunk-LMG3GHLL.js → chunk-IQMYK5DX.js} +29 -3
  20. package/dist/{chunk-OWBT45EN.js → chunk-IR44XKBI.js} +1 -1
  21. package/dist/{chunk-MXZSDGZQ.js → chunk-NMFDC2W2.js} +4 -6
  22. package/dist/{chunk-5SLENAJW.js → chunk-PAJ44TFW.js} +1 -2
  23. package/dist/{chunk-L7QGGKJ2.js → chunk-PDQFQIQW.js} +1 -1
  24. package/dist/{chunk-WJ6FLKNC.js → chunk-R3XAOMKP.js} +14 -0
  25. package/dist/{chunk-2OKB27TG.js → chunk-RXTN2CW6.js} +2 -2
  26. package/dist/{chunk-XWWLKRNU.js → chunk-TVIU23OM.js} +38 -49
  27. package/dist/chunk-XX4GXKTX.js +35 -0
  28. package/dist/chunk-Z67Q5KUW.js +16 -0
  29. package/dist/{chunk-3VRMKFM7.js → chunk-ZDJNFA52.js} +11 -24
  30. package/dist/cinema-media-C7OUPNSH.js +12 -0
  31. package/dist/cli.js +18 -9
  32. package/dist/comparison-GNZOR7MF.js +37 -0
  33. package/dist/compose-video-TBFTONUC.js +12 -0
  34. package/dist/editorial-timeline-EMV5335S.js +43 -0
  35. package/dist/{events-BQ6z264n.d.ts → events-B4YCc4vc.d.ts} +1 -1
  36. package/dist/index.d.ts +4 -6
  37. package/dist/index.js +3 -7
  38. package/dist/key-figure-P37VK425.js +30 -0
  39. package/dist/{kit-tQnCPMOM.d.ts → kit-DrRpdn0p.d.ts} +2 -2
  40. package/dist/mobile-message-OF7T62B7.js +50 -0
  41. package/dist/quote-T6WYLN7A.js +33 -0
  42. package/dist/react.d.ts +21 -11
  43. package/dist/react.js +585 -826
  44. package/dist/scene-video-backdrop-PCTARAAJ.js +7 -0
  45. package/dist/server.d.ts +7 -8
  46. package/dist/server.js +179 -84
  47. package/dist/{system-prompt-RRXIWDDD.js → system-prompt-SYRT5DWM.js} +1 -1
  48. package/dist/template-catalog.d.ts +392 -16
  49. package/dist/template-catalog.js +1 -1
  50. package/dist/templates.d.ts +5 -5
  51. package/dist/test.d.ts +3 -3
  52. package/dist/test.js +18 -18
  53. package/dist/text-stream-LD364KBI.js +9 -0
  54. package/dist/{types-DWFO6qca.d.ts → types-CdBmNTGD.d.ts} +1 -1
  55. package/dist/{types-CEh9eUFu.d.ts → types-DdZw4GRQ.d.ts} +17 -48
  56. package/dist/validate-4OUD2CLX.js +10 -0
  57. package/docs/architecture.md +1 -1
  58. package/docs/concepts.md +1 -1
  59. package/docs/custom-templates.md +13 -28
  60. package/docs/customization.md +11 -59
  61. package/docs/getting-started.md +2 -2
  62. package/docs/immersive-interface.md +3 -4
  63. package/docs/maintainers/cinematic-migration.md +38 -0
  64. package/docs/media-and-audio.md +14 -14
  65. package/docs/motion-and-effects.md +6 -14
  66. package/docs/persistence.md +3 -3
  67. package/docs/production.md +11 -9
  68. package/docs/provider-integration.md +9 -16
  69. package/docs/reference/design-system.html +1 -1
  70. package/docs/reference/protocol.md +14 -4
  71. package/docs/testing.md +1 -1
  72. package/package.json +6 -3
  73. package/registry/items/backgrounds.json +1 -1
  74. package/registry/items/chapterTitle.json +80 -0
  75. package/registry/items/cinemaMedia.json +136 -0
  76. package/registry/items/comparison.json +168 -0
  77. package/registry/items/editorialTimeline.json +172 -0
  78. package/registry/items/keyFigure.json +162 -0
  79. package/registry/items/mobileMessage.json +153 -0
  80. package/registry/items/quote.json +161 -0
  81. package/registry/items/template-context.json +1 -1
  82. package/registry/items/theme.json +1 -1
  83. package/starters/video-chat/README.md +24 -26
  84. package/starters/video-chat/package.json +1 -1
  85. package/starters/video-chat/providers/video.ts +6 -5
  86. package/starters/video-chat/stock.ts +35 -151
  87. package/styles/fonts/OFL.txt +93 -0
  88. package/styles/fonts/manifest.json +66 -0
  89. package/styles/fonts/roboto-v51-cyrillic-ext.woff2 +0 -0
  90. package/styles/fonts/roboto-v51-cyrillic.woff2 +0 -0
  91. package/styles/fonts/roboto-v51-greek-ext.woff2 +0 -0
  92. package/styles/fonts/roboto-v51-greek.woff2 +0 -0
  93. package/styles/fonts/roboto-v51-latin-ext.woff2 +0 -0
  94. package/styles/fonts/roboto-v51-latin.woff2 +0 -0
  95. package/styles/fonts/roboto-v51-math.woff2 +0 -0
  96. package/styles/fonts/roboto-v51-symbols.woff2 +0 -0
  97. package/styles/fonts/roboto-v51-vietnamese.woff2 +0 -0
  98. package/styles/fonts/roboto.css +91 -0
  99. package/styles/video-chat.css +30 -22
  100. package/dist/bg-confetti-HITCGLGD.js +0 -89
  101. package/dist/bg-emoji-VU4UZYZN.js +0 -150
  102. package/dist/bg-media-L34PDQXJ.js +0 -106
  103. package/dist/brand-message-FUJ4STFO.js +0 -256
  104. package/dist/chart-bar-EGPHJATL.js +0 -310
  105. package/dist/chart-counter-5FFWQNZO.js +0 -306
  106. package/dist/chart-progress-ring-JMXQLMLC.js +0 -302
  107. package/dist/chunk-4UNKZEAT.js +0 -3575
  108. package/dist/chunk-5CDAM24P.js +0 -31
  109. package/dist/chunk-AOWSXU2K.js +0 -18
  110. package/dist/chunk-CJ7F2CE6.js +0 -104
  111. package/dist/chunk-FRN6WKHA.js +0 -185
  112. package/dist/chunk-G5JPHBX7.js +0 -336
  113. package/dist/chunk-L5BTTCZV.js +0 -97
  114. package/dist/chunk-P22HHFDK.js +0 -43
  115. package/dist/chunk-PP3MCPN4.js +0 -1998
  116. package/dist/chunk-QAAO7OAY.js +0 -19
  117. package/dist/chunk-S7HFLYNN.js +0 -35
  118. package/dist/chunk-W3VDIBGR.js +0 -217
  119. package/dist/chunk-XGRA2MUV.js +0 -242
  120. package/dist/chunk-XTOXFMLM.js +0 -105
  121. package/dist/chunk-YJJC4N4D.js +0 -761
  122. package/dist/compose-video-2NFH4DZY.js +0 -13
  123. package/dist/cta-logo-YR7LYUR5.js +0 -212
  124. package/dist/cta-media-DQJBKUO2.js +0 -221
  125. package/dist/incoming-call-54KQ7O5A.js +0 -323
  126. package/dist/infographic-before-after-ROS52GOW.js +0 -460
  127. package/dist/infographic-feature-list-E7MGUDAN.js +0 -272
  128. package/dist/infographic-problem-solution-3S6NGO5N.js +0 -343
  129. package/dist/infographic-stat-row-SYB7AB7H.js +0 -247
  130. package/dist/infographic-steps-KCE6JWE2.js +0 -406
  131. package/dist/prompt-input-MHX4O42G.js +0 -239
  132. package/dist/reaction-SLEGR3BE.js +0 -122
  133. package/dist/scene-video-backdrop-XFPX4O3I.js +0 -10
  134. package/dist/showcase-code-2HLAVKBM.js +0 -357
  135. package/dist/showcase-phone-Q4HEO4LH.js +0 -278
  136. package/dist/showcase-terminal-DL45LUFG.js +0 -312
  137. package/dist/showcase-web-2QZO73FZ.js +0 -396
  138. package/dist/social-conversation-75SD2637.js +0 -695
  139. package/dist/social-milestone-GEFUQSBF.js +0 -296
  140. package/dist/social-notification-FJ5VHVLK.js +0 -247
  141. package/dist/social-review-stack-KXQCKFRP.js +0 -242
  142. package/dist/social-testimonial-JZ7CHC7S.js +0 -287
  143. package/dist/social-tweet-UCDXM25F.js +0 -298
  144. package/dist/text-stream-54LXABJW.js +0 -10
  145. package/dist/validate-T7GBU2YF.js +0 -8
  146. package/registry/items/barChart.json +0 -245
  147. package/registry/items/beforeAfter.json +0 -180
  148. package/registry/items/bigNumber.json +0 -232
  149. package/registry/items/brandMessage.json +0 -164
  150. package/registry/items/cardList.json +0 -214
  151. package/registry/items/chatMessenger.json +0 -151
  152. package/registry/items/chatWhatsapp.json +0 -151
  153. package/registry/items/codeEditor.json +0 -191
  154. package/registry/items/confetti.json +0 -107
  155. package/registry/items/ctaLogo.json +0 -167
  156. package/registry/items/ctaMedia.json +0 -200
  157. package/registry/items/emojiBurst.json +0 -101
  158. package/registry/items/incomingCall.json +0 -177
  159. package/registry/items/media.json +0 -187
  160. package/registry/items/milestone.json +0 -212
  161. package/registry/items/notification.json +0 -189
  162. package/registry/items/phoneMockup.json +0 -261
  163. package/registry/items/problemSolution.json +0 -193
  164. package/registry/items/progressRing.json +0 -206
  165. package/registry/items/promptInput.json +0 -155
  166. package/registry/items/reaction.json +0 -176
  167. package/registry/items/reviewStack.json +0 -231
  168. package/registry/items/steps.json +0 -215
  169. package/registry/items/terminal.json +0 -219
  170. package/registry/items/testimonial.json +0 -171
  171. package/registry/items/tripleStats.json +0 -235
  172. package/registry/items/tweet.json +0 -210
  173. package/registry/items/webMockup.json +0 -280
@@ -0,0 +1,10 @@
1
+ import {
2
+ createTemplateSceneValidator,
3
+ validateTemplateSceneStructure
4
+ } from "./chunk-IQMYK5DX.js";
5
+ import "./chunk-EGVQODKU.js";
6
+ import "./chunk-73NTSFFI.js";
7
+ export {
8
+ createTemplateSceneValidator,
9
+ validateTemplateSceneStructure
10
+ };
@@ -67,7 +67,7 @@ prompt/validation registry in `vanillasky/server.ts`.
67
67
  5. The system prompt combines the opening and composition rules with the trusted template
68
68
  catalog, including generated metadata for customer-owned templates. The user
69
69
  prompt serializes the prompt, completed conversation, instructions,
70
- brand, and approved media.
70
+ style, and approved media.
71
71
  6. The model streams one host-consumed opening object followed by NDJSON plan
72
72
  parts. The server emits the opening event, then parses and validates complete
73
73
  scenes before emitting them.
package/docs/concepts.md CHANGED
@@ -31,7 +31,7 @@ or export pipeline when an encoded file is required.
31
31
 
32
32
  The viewer sends a prompt with bounded completed conversation turns. The server
33
33
  adds trusted application `instructions`, template capabilities, and the selected
34
- visual mode. `VideoChat` options control brand, style, orientation, and custom
34
+ cinematic mode. `VideoChat` options control style, orientation, and custom
35
35
  templates. Exact facts belong in the authorized prompt or conversation; secrets
36
36
  and provider configuration stay on the server.
37
37
 
@@ -37,7 +37,7 @@ npx vanillasky templates describe customer-health
37
37
  Or copy a built-in when its behavior is already close:
38
38
 
39
39
  ```bash
40
- npx vanillasky templates add bigNumber
40
+ npx vanillasky templates add keyFigure
41
41
  ```
42
42
 
43
43
  Then edit the owned `.tsx` file, regenerate the two small registries, and check
@@ -50,7 +50,7 @@ npx vanillasky templates check
50
50
 
51
51
  For an original template, the source is
52
52
  `vanillasky/templates/customer-health.tsx`. For the copied built-in, it is
53
- `vanillasky/templates/bigNumber.tsx`. These are application source: commit them,
53
+ `vanillasky/templates/keyFigure.tsx`. These are application source: commit them,
54
54
  review them, and change them like any other React component.
55
55
 
56
56
  `create` and `add` run `sync` once, so the generated entry points exist
@@ -62,8 +62,8 @@ registry parity.
62
62
  Preview either operation without applying the proposed file writes:
63
63
 
64
64
  ```bash
65
- npx vanillasky templates add bigNumber --dry-run
66
- npx vanillasky templates add bigNumber --diff
65
+ npx vanillasky templates add keyFigure --dry-run
66
+ npx vanillasky templates add keyFigure --diff
67
67
  ```
68
68
 
69
69
  `--dry-run` lists every proposed file and `--diff` shows its content changes,
@@ -119,8 +119,8 @@ the selected catalog contains a template with one of those two jobs.
119
119
 
120
120
  Customer templates hard-cut by default. Opt into renderer-owned fades only
121
121
  for scenes that use the standard media-background variables, and only after
122
- both timing points are visually tested in portrait and landscape. Shared brand
123
- gradients and unchanged media do not crossfade:
122
+ both timing points are visually tested in portrait and landscape. Shared render
123
+ black backgrounds and unchanged media do not crossfade:
124
124
 
125
125
  ```tsx
126
126
  export default defineTemplate({
@@ -277,7 +277,7 @@ import { VideoPlayer } from "@vanillaskyai/video/react";
277
277
  import { templates } from "../vanillasky";
278
278
 
279
279
  const savedVideo: Video = {
280
- schemaVersion: "0.1",
280
+ schemaVersion: "0.2",
281
281
  orientation: "portrait",
282
282
  scenes: [{
283
283
  id: "customer-health-preview",
@@ -288,21 +288,7 @@ const savedVideo: Video = {
288
288
  },
289
289
  timing: { fixedDuration: 5 },
290
290
  }],
291
- style: {
292
- brand: {
293
- font: "Inter",
294
- scriptFont: "Caveat",
295
- background: { type: "gradient", colors: ["#8711C1", "#2167E3"] },
296
- colors: {
297
- primary: "#00E5A0",
298
- secondary: "#006BE5",
299
- foreground: "#FFFFFF",
300
- surface: "#0A0A14",
301
- surfaceElevated: "#14152A",
302
- muted: "#A7A6B0",
303
- },
304
- },
305
- },
291
+ style: {},
306
292
  };
307
293
 
308
294
  export function TemplatePreview() {
@@ -328,8 +314,8 @@ maps.
328
314
 
329
315
  Useful formats add grounding behavior:
330
316
 
331
- - `grounded-stat` marks numeric statistical evidence for the planner; it does
332
- not compare the value against raw input at runtime;
317
+ - `grounded-stat` accepts numeric or string quantities and requires an exact
318
+ quantity in raw input, preserving its unit;
333
319
  - `grounded-quote` requires the quote to exist verbatim in the input;
334
320
  - `uri` validates approved media URLs restored by the server;
335
321
  - `stock-media-keyword` is only for hosts that resolve stock media before a
@@ -338,14 +324,13 @@ Useful formats add grounding behavior:
338
324
  Templates whose core proof needs a real statistic can add
339
325
  `"x-vanillasky": { "requiresStat": true }`. See the structured-data reference for a complete example.
340
326
 
341
- For media-backed chat scenes, copy the built-in `media` template and keep its
327
+ For media-backed chat scenes, copy the built-in `cinemaMedia` template and keep its
342
328
  `mediaKeyword`, `mediaUrl`, and `mediaType` contract. Configure `searchMedia` on
343
329
  the chat handler so the server resolves semantic searches into approved assets.
344
330
  The chat request does not accept a separate supplied-media input list.
345
331
 
346
- The automatic `opening` uses the built-in `media` variables (`texts` and
347
- `mediaType: "gradient"`). If you replace `media` while using automatic
348
- openings, keep that variable contract.
332
+ The automatic `opening` uses the built-in `chapterTitle` variable `title`.
333
+ If you replace that template while using automatic openings, keep this contract.
349
334
 
350
335
  ## What belongs where
351
336
 
@@ -14,7 +14,6 @@ heading and a root class when the application needs its own copy or chrome color
14
14
  <VideoChat
15
15
  className="acme-chat"
16
16
  welcomeTitle={<>Ask Acme<br />See the answer</>}
17
- options={{ brand: acmeBrand }}
18
17
  />
19
18
  ```
20
19
 
@@ -32,10 +31,10 @@ that instance:
32
31
  ```
33
32
 
34
33
  The built-in navigation carries the VanillaSky logo. `welcomeTitle` changes
35
- the welcome heading; it does not replace the navigation logo. `options.brand`
36
- styles generated video content independently of the surrounding controls.
34
+ the welcome heading; it does not replace the navigation logo. Graphic scenes
35
+ use fixed black backgrounds and white/neutral system typography.
37
36
 
38
- Use `options` for the endpoint, templates, orientation, visual brand, request
37
+ Use `options` for the endpoint, templates, orientation, request
39
38
  headers, and an optional custom voice. Provider capabilities are discovered
40
39
  from the server. Use `useVideoChat()` only when the application needs to own the
41
40
  entire interface.
@@ -44,63 +43,16 @@ Pass the following visual settings through `VideoChat` or `useVideoChat` options
44
43
  Keep viewer context in the prompt and completed conversation turns; use the
45
44
  server handler’s `instructions` for trusted product guidance.
46
45
 
47
- ## Background and semantic brand
46
+ ## Cinematic visual direction
48
47
 
49
- Omit brand configuration to use the standard `cosmic` background. Prefer a
50
- named curated choice over raw color work:
48
+ Graphics use black backgrounds and white/neutral typography. Full-bleed media
49
+ and Reach out can show naturally colored footage; the other six templates
50
+ explain with their own composition and motion. There is no brand-kit option.
51
51
 
52
- ```ts
53
- const brand = {
54
- name: "Acme",
55
- logoUrl: "https://cdn.acme.com/logo.svg",
56
- font: "Inter",
57
- scriptFont: "Caveat",
58
- background: "twilight",
59
- colors: {
60
- primary: "#6D5EF5",
61
- secondary: "#3D2A78",
62
- foreground: "#FFFFFF",
63
- surface: "#17122F",
64
- surfaceElevated: "#231B42",
65
- muted: "#A7A6B0",
66
- },
67
- };
68
- ```
69
-
70
- Gradient presets: `cosmic`, `horizon`, `twilight`, `meadow`, `velvet`,
71
- `flamingo`, `peach`, `saffron`. Solid presets: `black`, `midnight`,
72
- `aubergine`, `coal`, `navy`. When a named choice genuinely cannot express the
73
- brand, use `{ colors: ["#112233", "#334455"] }` for a custom gradient or
74
- `{ color: "#070B20" }` for a custom solid.
75
-
76
- `colors` may be partial; the resolver fills every semantic token before the
77
- video is emitted. With no foreground, named and custom backgrounds
78
- deterministically select black or white for at least 4.5:1 contrast across the
79
- full rendered sRGB ramp, including gradient interiors. A ramp that neither can
80
- cover is rejected. An explicit foreground is preserved and validated by the
81
- same invariant during input resolution and replay; low-contrast values are
82
- rejected with the failing path and minimum ratio. Elevated surfaces derive an
83
- accessible internal text treatment without changing the semantic foreground.
84
- Use an approved public or signed URL for logos, and never put a private storage
85
- credential in the config.
86
-
87
- ## Global visual direction
88
-
89
- Leave visual direction unset to use VanillaSky's defaults, or set one coherent
90
- look for the completed video:
91
-
92
- ```ts
93
- style: {
94
- density: "airy", // airy | normal | packed
95
- motion: "calm", // calm | normal | punchy
96
- textArchetype: "cinematic",
97
- backgroundEffect: "slow-zoom-out",
98
- }
99
- ```
100
-
101
- These are defaults, not generated CSS. A validated scene may still select a
102
- more appropriate text or background treatment when its trusted template allows
103
- it.
52
+ The host can provide a shared `generatedLook` description for media preparation.
53
+ Custom source-owned templates can define their own visual language in code.
54
+ Do not rely on old global brand, text-effect, or gradient controls to restyle
55
+ the seven cinematic templates.
104
56
 
105
57
  ## Opening
106
58
 
@@ -96,8 +96,8 @@ spoken hook from the response stream before the full plan is complete, holds its
96
96
  scene is ready, reaches its final frame, and leaves the composer ready for
97
97
  another turn. With stock media enabled, click a welcome or follow-up card and
98
98
  confirm its footage carries directly into that opening. With generated video
99
- enabled, confirm the only choices are Templates and Full AI video and that the
100
- first generated shot continues the spoken hook without repeating it.
99
+ enabled, confirm that relevant generated shots and editorial templates form one
100
+ cinematic response, and the first shot continues the hook without repeating it.
101
101
 
102
102
  The generated local authorization accepts localhost only. Replace it with your
103
103
  real session check, rate limits, and usage policy before deploying.
@@ -67,8 +67,7 @@ hover movement remain inside the rail's available space.
67
67
  ## Layout and access
68
68
 
69
69
  The foreground video preserves its scene ratio, so diagrams and text remain
70
- visible without cropping or distortion. Ambient opening media fills the
71
- surrounding viewport, with a gradient fallback when media is unavailable.
70
+ visible without cropping or distortion. The surrounding viewport remains black during playback.
72
71
  Display layout does not rewrite the saved response's orientation. See
73
72
  [responsive orientation](responsive-orientation.md) for saved videos and custom
74
73
  players.
@@ -79,9 +78,9 @@ radio behavior, and popovers support Escape and outside-click dismissal.
79
78
  Reduced motion removes movement and fades; increased contrast strengthens
80
79
  surface opacity and borders.
81
80
 
82
- ## Branding
81
+ ## Customization
83
82
 
84
83
  Use the existing `welcomeTitle`, `className` and `options` props to customize
85
- copy, scoped CSS properties and generated video branding. There is no separate
84
+ copy, scoped CSS properties and generated footage direction. There is no separate
86
85
  light/dark appearance picker. See [customization](customization.md) for examples.
87
86
  Use `useVideoChat` when the application needs to own the whole interface.
@@ -0,0 +1,38 @@
1
+ # Cinematic contract migration
2
+
3
+ The eight-template replacement is an approved breaking pre-1.0 change. Persisted Video schema is now `0.2`; event protocol is `0.6`.
4
+
5
+ ## Breaking changes
6
+
7
+ - Removed `VideoInput.brand`, `VideoStyle.brand`, `VideoBrand`, `VideoBrandInput`, `VideoBackground`, and `resolveVideoBrand`. Graphics use black, white/neutral text and system typography. Naturally colored media is unaffected.
8
+ - Removed all 28 old built-in IDs. Eight new IDs describe different schemas: `cinemaMedia`, `chapterTitle`, `focusCards`, `editorialTimeline`, `mobileMessage`, `comparison`, `quote`, `keyFigure`.
9
+ - `parseVideo` rejects the old persisted `0.1` fixture with `unsupported_video_version`. The parser also rejects `style.brand` if a caller merely stamps the new version onto an old object.
10
+ - Scene source intent uses `mediaKeyword` (up to 80 characters) and optional `mediaSource`. The host owns `mediaUrl`, `mediaPoster` and resolved `mediaType`. No gradient fallback mode. Full-bleed requires a resolvable intent or asset; Reach out can remain on black when media is unavailable.
11
+
12
+ ## Adoption
13
+
14
+ Regenerate videos from the retained source and narration using the new catalog. Do not automatically relabel old IDs: before/after emojis, count-ups and factual quote/stat schemas have different meanings. Retain an older published SDK in a separate legacy playback boundary if historical exports must continue to render; do not pass those payloads into the new parser.
15
+
16
+ Before (old persisted format):
17
+
18
+ ```json
19
+ {"schemaVersion":"0.1","scenes":[{"id":"one","templateId":"media","variables":{"texts":"A new perspective","mediaType":"gradient"},"timing":{"fixedDuration":4}}],"style":{"brand":{"font":"Inter"}}}
20
+ ```
21
+
22
+ After re-authoring that title as a chapter:
23
+
24
+ ```json
25
+ {"schemaVersion":"0.2","scenes":[{"id":"one","templateId":"chapterTitle","variables":{"title":"A new perspective"},"timing":{"fixedDuration":4}}],"style":{}}
26
+ ```
27
+
28
+ Evidence: persistence tests preserve the untouched old release fixture and assert rejection, accept current-style round trips, reject hidden/non-JSON input as before, and pin new checksums. Template tests cover both orientations, deterministic seeking, exact evidence text and single-decoder backdrop ownership. Final packed consumer and mobile playback checks remain release gates.
29
+
30
+ ## Render fonts
31
+
32
+ The cinematic templates use `-apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif` at regular and medium weights. Apple devices keep native system typography. The existing `@vanillaskyai/video/video-chat.css` entry registers packaged Roboto v51 WOFF2 subsets as the fallback for environments without those fonts. Standalone player and source-owned template integrations should also import that stylesheet. No font is fetched from Google at runtime; browsers fetch local packaged subsets only when Roboto is selected for the rendered glyphs. The font assets include the SIL Open Font License and a source/hash manifest.
33
+
34
+ Live native fonts and a Linux renderer’s Roboto have slightly different metrics. For repeatable exports, keep the browser, installed fonts, viewport and package version fixed, and await `document.fonts.ready` after mounting the final scene before capturing frames. This is a host export responsibility; the SDK does not add a separate export API or force downloaded fonts onto Apple devices.
35
+
36
+ ## Media-led follow-up
37
+
38
+ The owner requested removing `focusCards`. Regenerate persisted videos using that ID; migrate parallel explanations to narration over footage rather than another bullet layout. The remaining seven templates retain their IDs. Comparison, quote, key figure and timeline accept optional standard media variables and remain readable on black when assets are absent. No provider callback signatures or host limits change.
@@ -6,20 +6,19 @@ VanillaSky keeps provider choice in the application. The SDK defines small
6
6
  server callbacks, advertises only the capabilities you configure, and keeps
7
7
  all credentials out of React and the browser bundle.
8
8
 
9
- ## Two visual modes
9
+ ## One cinematic mode
10
10
 
11
- The chat exposes only two clear choices:
11
+ The director combines relevant footage and seven editorial templates. Graphics
12
+ use black and white; footage carries atmosphere and concrete action. Each media
13
+ scene declares `mediaSource: "stock"` or `"generate"`. Provider availability and
14
+ host budgets constrain those choices. There is no all-stock or all-AI switch.
12
15
 
13
- | Mode | Visual source | Required callback |
14
- | --- | --- | --- |
15
- | `templates` | Trusted rendered templates, optionally with stock footage | none; `searchMedia` is optional |
16
- | `full` | A generated clip for every visual beat | `generateVideo` |
16
+ The canonical templates are `cinemaMedia`, `chapterTitle`,
17
+ `editorialTimeline`, `mobileMessage`, `comparison`, `quote`, and `keyFigure`.
18
+ All except `chapterTitle` accept optional media backgrounds. Editorial overlays use soft text shadows and a dark contrast scrim over relevant media, and fall back to black. Use at most one explanatory overlay per typical thirty-second answer. Full-bleed
19
+ footage has no headline; narration and subtitles carry the explanation.
17
20
 
18
- Templates are always available and are the fast, inexpensive fallback. The
19
- `full` mode is advertised only when `generateVideo` exists. VanillaSky does not
20
- offer a mixed mode that generates only some scenes.
21
-
22
- ## Stock media for templates
21
+ ## Reviewed stock
23
22
 
24
23
  Add `searchMedia` when template answers, the welcome screen, and follow-up
25
24
  cards should use approved photography or footage:
@@ -46,15 +45,16 @@ createVideoChatHandler({
46
45
  The planner emits a short semantic keyword, not a URL. The callback returns an
47
46
  application-approved image or video URL, and the SDK validates it before it
48
47
  reaches a scene. Return `null` when no licensed, safe, relevant asset exists;
49
- the template falls back to its built-in treatment.
48
+ `cinemaMedia` becomes a chapter using its grounded `fallbackText`; a message can
49
+ keep its content on black. Missing fallback copy produces an explicit error.
50
50
 
51
51
  For Pexels, keep `PEXELS_API_KEY` on the server, enforce a deadline, filter for
52
52
  orientation, and return only validated Pexels asset domains. Licensing,
53
53
  attribution, caching, MIME checks, and byte limits remain application-owned.
54
54
 
55
- ## Full generated video
55
+ ## Planned generated shots
56
56
 
57
- Add `generateVideo` to enable full AI video. It receives the planned visual
57
+ Add `generateVideo` to enable generated shots within cinematic responses. It receives the planned visual
58
58
  subject plus the generated look so every clip can follow the same direction:
59
59
 
60
60
  ```ts
@@ -47,22 +47,14 @@ only when two ranges are contiguous (allowing floating-point arithmetic noise)
47
47
  and both templates declare `usesGlobalTransition: true` with valid
48
48
  `transitionTiming` metadata. It is also conditional on the effective backdrop:
49
49
  the 300 ms outer crossfade runs only when the resolved background media changes.
50
- Scenes that share the brand gradient, or the same resolved media backdrop, do
50
+ Scenes that share the black base, or the same resolved media backdrop, do
51
51
  not crossfade. This keeps one stable background visible while each template
52
52
  plays its own entrance, hold, and exit choreography.
53
53
 
54
- Animated brand gradients use a closed, eased loop per scene. Every gradient
55
- family reaches the exact same zero-velocity frame at progress `0` and `1`, and
56
- content-seeded variation converges before the boundary. A same-gradient cut
57
- therefore changes only the foreground template; the backdrop cannot jump or
58
- flash between scene-specific phases.
59
-
60
- The player owns a persistent brand-color backdrop beneath every scene. Built-in
61
- renderers preload through the same component state used for playback, so their
62
- first frame does not suspend when a new template type appears. If a genuinely
63
- cold custom renderer does suspend, its transparent loading frame reveals the
64
- brand backdrop rather than a black canvas. Scene media still belongs to the
65
- scene and covers that base only while the media scene is active.
54
+ The player owns a fixed black backdrop beneath the scenes. Built-in renderers
55
+ preload through the same component state used for playback. A cold custom
56
+ renderer reveals this black base while loading. Resolved scene media covers it
57
+ only while the media scene is active.
66
58
 
67
59
  During a changed-media overlap, the current scene continues to its exact end.
68
60
  The incoming component may be pre-mounted for media readiness, but remains
@@ -98,7 +90,7 @@ also protects intentional hard cuts; it does not pause or otherwise alter the
98
90
  visual or soundtrack clocks.
99
91
 
100
92
  Undefined or unknown transition names preserve a hard cut and unmodified local
101
- motion. Overlapping ranges also hard-cut. A timeline gap renders the owned brand
93
+ motion. Overlapping ranges also hard-cut. A timeline gap renders the fixed black
102
94
  background instead of replaying an earlier scene.
103
95
 
104
96
  ## Reduced motion
@@ -10,7 +10,7 @@ SDK serializer. An interrupted turn can remain visible without qualifying as
10
10
  completed conversation history. The default `VideoChat` keeps history in memory;
11
11
  use the headless hook when the application needs durable storage.
12
12
 
13
- Every stored video has `schemaVersion: "0.1"`. This storage version is separate
13
+ Every stored video has `schemaVersion: "0.2"`. This storage version is separate
14
14
  from streaming protocol `0.5`. The 0.1 policy supports the current storage
15
15
  schema only: there are no compatibility aliases or implicit coercions.
16
16
 
@@ -39,12 +39,12 @@ The release verifier compiles this exact documented snippet against the packed
39
39
  SDK artifact, including its root and React subpath imports.
40
40
 
41
41
  `parseVideo(value: unknown)` validates the complete shape, known fields,
42
- resolved brand and style, audio, metadata, unique scenes, timing, and JSON-safe
42
+ style, audio, metadata, unique scenes, timing, and JSON-safe
43
43
  template variables. It returns a detached, deeply frozen `Video`, so later
44
44
  changes to the loaded object cannot mutate player state.
45
45
 
46
46
  Invalid data throws `VideoValidationError` with `code: "invalid_video"`.
47
- Unknown or future storage versions throw the same error class with
47
+ Previous, unknown, or future storage versions throw the same error class with
48
48
  `code: "unsupported_video_version"`. `<VideoPlayer video={value} />` repeats
49
49
  this boundary validation and rejects the entire value before any renderer
50
50
  runs; it never renders a partial future document.
@@ -20,12 +20,14 @@ The handler rejects unknown templates and fields, invalid variables, unsafe
20
20
  media, and fabricated quote-template content before a scene reaches the
21
21
  player. Read the [security guide](security.md) for the complete controls.
22
22
 
23
- ## Visual modes and providers
23
+ ## Cinematic direction and providers
24
24
 
25
- Keep templates available on every deployment. They are the reliable fallback
26
- when stock, speech, or generated-video providers are missing or late. Add
27
- `searchMedia` for approved stock footage and `generateVideo` for the separate
28
- full AI video mode. Do not expose a partial generated-video mode.
25
+ One cinematic mode combines footage and editorial graphics according to the
26
+ story. Keep templates available as grounded fallbacks. Add `searchMedia` for
27
+ reviewed stock and `generateVideo` for selected illustrative shots. The planner
28
+ sets `mediaSource` per media scene; the host enforces the generation budget.
29
+ A stock candidate must match the subject, action and permitted crop. Return
30
+ `null` for uncertainty rather than broadening an essential detail.
29
31
 
30
32
  Use explicit provider deadlines. Generated video should use idempotency keys
31
33
  and `maxRetries: 0` so one visible action cannot silently create several
@@ -36,13 +38,13 @@ licensing before use.
36
38
 
37
39
  The planner's first streamed object supplies the spoken hook and media keyword.
38
40
  Start speech immediately, resolve stock in parallel, and keep the opening
39
- playing until the first narrated scene and its media are ready. Welcome cards
41
+ playing until the contiguous preparation cushion and first frame are ready. Welcome cards
40
42
  should carry a prepared hook and preloaded footage so they can start without a
41
43
  model round trip.
42
44
 
43
45
  Do not wait for the complete plan before showing the first validated scene.
44
46
  Preload upcoming assets and keep the current visual if the next one is late.
45
- For full AI video, reserve the first shot in the opening object so generation
47
+ When generated footage is useful, reserve the first shot in the opening object so generation
46
48
  can begin while the planner streams later scenes.
47
49
 
48
50
  ## Data and privacy
@@ -101,8 +103,8 @@ npm test
101
103
  - [ ] Keys exist only in the server secret store.
102
104
  - [ ] Authentication, tenant policy, rate limits, and origin allowlist are live.
103
105
  - [ ] Cancellation, timeouts, fallbacks, and safe errors are tested.
104
- - [ ] Template mode completes when every optional provider is unavailable.
105
- - [ ] Full mode appears only when generated video is configured.
106
+ - [ ] Grounded graphic recovery works when every optional provider is unavailable.
107
+ - [ ] Per-scene media choices obey provider availability and spending limits.
106
108
  - [ ] Both orientations render and narration stays synchronized.
107
109
  - [ ] A packed-artifact consumer and deterministic browser chat pass.
108
110
  - [ ] One bounded real-provider run meets the product's latency and quality target.
@@ -61,11 +61,10 @@ dependencies and never enter the browser bundle.
61
61
  The planner emits a 6-9 word hook first, then continues into the scenes in the
62
62
  same stream. The stock lookup is a separate cancellable request, so it cannot
63
63
  delay speech or planning. A welcome card can carry a prewritten `opening`, which
64
- starts immediately with its already-loaded media. For a full AI response, the
64
+ starts immediately with its already-loaded media. When generated shots are available, the
65
65
  same first streamed object reserves the exact first body scene. Its clip starts
66
- generating while the planner continues with scenes two through five. Template
67
- responses stream their first resolved scene without waiting for the rest of the
68
- plan, and normal narration is written in that same planner call rather than
66
+ generating while the planner continues the story. Playback begins after its
67
+ contiguous preparation cushion, without requiring the entire plan, and normal narration is written in that same planner call rather than
69
68
  through a second model round trip.
70
69
 
71
70
  The matching complete React interface is one component and one scoped style
@@ -121,14 +120,10 @@ streamText: ({ systemPrompt, userPrompt, signal }) => streamText({
121
120
  }),
122
121
  ```
123
122
 
124
- Measured on one grounded chat answer with the 28 built-in templates, leaving
125
- the Anthropic default in place cost roughly twenty seconds before the first
126
- scene; disabling reasoning brought the same plan to a few seconds. Other
127
- providers expose equivalent controls under their own names. Treat the exact
128
- values as host-owned tuning: the lowest effort setting is the fastest, but a
129
- weaker plan misses schema limits more often, which shows up as rejected scenes
130
- in `onComplete`. Compare `timeToFirstSceneMs` and `rejectedSceneCount` across
131
- settings before fixing one.
123
+ Reasoning settings can substantially affect startup latency. Measure them with
124
+ your installed catalog and representative requests. Compare first-scene timing,
125
+ rejected scenes and factual accuracy; the fastest token stream is not useful if
126
+ its scenes cannot be rendered. Keep these settings in the provider adapter.
132
127
 
133
128
  VanillaSky never sets these controls. Provider selection, sampling parameters,
134
129
  and credentials stay with the application.
@@ -185,10 +180,8 @@ is intentionally substantial. It is stable for the same SDK version, template
185
180
  kit, media policy, and base prompt. Record input-token usage, keep the selected
186
181
  kit no broader than the product needs, and enable provider-side prompt caching
187
182
  where the chosen provider/model supports it. VanillaSky does not assume one
188
- provider's cache controls in its provider-neutral adapter. With the 28 built-in
189
- templates, the current catalog prompt is roughly 28,000 characters (about
190
- 7,000 tokens before user input; tokenizer-dependent); provider-reported usage
191
- is the authoritative measurement.
183
+ provider's cache controls in its provider-neutral adapter. The cinematic catalog contains seven templates; use provider-reported token
184
+ usage as the authoritative measurement rather than a character estimate.
192
185
 
193
186
  Provider finish reasons `error` and `tool-calls` are terminal failures.
194
187
  `length` and `content-filter` may complete with already accepted scenes; a
@@ -104,7 +104,7 @@ body { background: #080b16; }
104
104
  <section class="doc-section" id="conversation"><div class="section-heading"><span class="number">03</span><h2>One field. Every follow-up.</h2></div><p class="section-copy">Speak or type directly over the picture. Beginning a question pauses playback. Sending starts the next answer; canceling returns to the previous playback state.</p><div class="doc-preview"><div class="demo-toolbar" aria-label="Composer preview states"><button type="button" data-state="ready" aria-pressed="true">Ready</button><button type="button" data-state="listening" aria-pressed="false">Listening</button><button type="button" data-state="thinking" aria-pressed="false">Transcribing</button></div><div class="conversation-composer"><div class="composer-meta"><span id="composer-status" role="status">Interactive preview · no microphone or model call</span></div><form class="composer" id="composer-demo"><button class="ghost transport" type="button" aria-label="Play preview" id="play-demo"><svg data-icon="play"></svg></button><label class="sr-only" for="demo-question">Question</label><textarea id="demo-question" rows="1" placeholder="Ask a follow-up…"></textarea><button class="ghost" type="button" id="mic-demo" aria-label="Preview listening state" aria-pressed="false"><svg data-icon="mic"></svg></button><button class="send" type="submit" aria-label="Preview send" disabled><svg data-icon="send"></svg></button></form></div></div><p class="doc-note">44px targets. 10px between microphone and send. The field grows to 120px and keeps the controls within reach.</p></section>
105
105
  <section class="doc-section" id="captions"><div class="section-heading"><span class="number">04</span><h2>Words stay with the picture</h2></div><p class="section-copy">The first subtitle takes the input’s bottom baseline. The complete cue remains readable without truncation. Reveal the input to move subtitles upward; expand for the transcript or restore hidden subtitles in Settings.</p><div class="doc-preview"><div class="demo-toolbar"><button type="button" id="restore-caption">Show subtitles</button><button type="button" id="toggle-transcript">Expand transcript</button></div><div class="line-row" id="caption-demo" data-expanded="false"><div class="caption-actions"><button class="caption-action" type="button" id="expand-action" aria-label="Expand subtitles" aria-expanded="false">Expand <svg data-icon="up"></svg></button><button class="caption-action" type="button" id="hide-action" aria-label="Hide subtitles"><svg data-icon="close"></svg></button></div><p class="line" id="compact-caption">Water absorbs the warm colors of sunlight,<br>leaving more blue light to reach your eyes.</p><div class="expanded-captions" id="transcript-demo" tabindex="0" aria-label="Expanded subtitles" hidden><span class="eyebrow">Full answer</span><p>The ocean’s color begins with sunlight.</p><p>Water absorbs the warm colors of sunlight, leaving more blue light to reach your eyes.</p><p>Depth, particles and the sky change what we see. That is why a shallow lagoon and the open ocean can look so different.</p></div></div></div></section>
106
106
  <section class="doc-section" id="suggestions"><div class="section-heading"><span class="number">05</span><h2>A little more curiosity</h2></div><p class="section-copy">A horizontal rail offers the next question. Video cards show their poster image before playback, including when autoplay is blocked. Hover or keyboard focus selects a card. Its outline and lift have room to breathe.</p><div class="doc-preview"><ul class="cards" aria-label="Example suggested questions"><li><button type="button" data-active><span class="card-art"></span><span class="card-wash"></span><span class="card-prompt">What is hiding in the deep ocean?</span></button></li><li><button type="button"><span class="card-art space"></span><span class="card-wash"></span><span class="card-prompt">Why do planets stay in orbit?</span></button></li><li><button type="button"><span class="card-art mind"></span><span class="card-wash"></span><span class="card-prompt">How does a memory begin?</span></button></li></ul></div><p class="doc-note">Reference artwork uses abstract color fields. The live rail uses the answer’s resolved media. Select a card here to place its question in the preview composer.</p></section>
107
- <section class="doc-section" id="foundations"><div class="section-heading"><span class="number">06</span><h2>Small decisions, shared everywhere</h2></div><p class="section-copy">System typography, generous touch targets and a compact spacing scale. The video carries the color; the interface carries meaning.</p><div class="doc-preview"><div class="type-row"><span style="font-size:40px;letter-spacing:-.055em;line-height:1.08">An answer worth watching.</span><code>Display · responsive / 1.08</code></div><div class="type-row"><span style="font-size:22px;letter-spacing:-.02em;line-height:1.45">A clear line over a moving picture.</span><code>Subtitles · 17–22px / 1.45</code></div><div class="type-row"><span style="font-size:16px;letter-spacing:-.015em;line-height:1.625">Ask a follow-up…</span><code>Input · 16px / 26px</code></div><div class="type-row"><span style="font-size:13px;color:var(--vs-media-muted)">One answer leads to another.</span><code>Context · 13px</code></div><div class="metrics"><div class="metric"><strong>44<span style="display:inline;font-size:15px"> px</span></strong><span>Minimum primary control target</span></div><div class="metric"><strong>180<span style="display:inline;font-size:15px"> ms</span></strong><span>Navigation and opacity fade</span></div><div class="metric"><strong>280<span style="display:inline;font-size:15px"> ms</span></strong><span>Input and subtitle movement</span></div><div class="metric"><strong>2<span style="display:inline;font-size:15px"> s</span></strong><span>Idle delay after revealing controls</span></div></div></div><p class="doc-note">First subtitles hide the input immediately unless the viewer is editing, listening or using keyboard focus. Reduced motion removes movement and fades. Increased contrast strengthens the surfaces and borders. The input respects bottom safe areas. Subtitle actions fade while inactive and return on hover, touch or keyboard focus. The foreground preserves the scene ratio and readable text. Ambient opening media or a gradient fills the surrounding viewport.</p></section>
107
+ <section class="doc-section" id="foundations"><div class="section-heading"><span class="number">06</span><h2>Small decisions, shared everywhere</h2></div><p class="section-copy">System typography, generous touch targets and a compact spacing scale. The video carries the color; the interface carries meaning.</p><div class="doc-preview"><div class="type-row"><span style="font-size:40px;letter-spacing:-.055em;line-height:1.08">An answer worth watching.</span><code>Display · responsive / 1.08</code></div><div class="type-row"><span style="font-size:22px;letter-spacing:-.02em;line-height:1.45">A clear line over a moving picture.</span><code>Subtitles · 17–22px / 1.45</code></div><div class="type-row"><span style="font-size:16px;letter-spacing:-.015em;line-height:1.625">Ask a follow-up…</span><code>Input · 16px / 26px</code></div><div class="type-row"><span style="font-size:13px;color:var(--vs-media-muted)">One answer leads to another.</span><code>Context · 13px</code></div><div class="metrics"><div class="metric"><strong>44<span style="display:inline;font-size:15px"> px</span></strong><span>Minimum primary control target</span></div><div class="metric"><strong>180<span style="display:inline;font-size:15px"> ms</span></strong><span>Navigation and opacity fade</span></div><div class="metric"><strong>280<span style="display:inline;font-size:15px"> ms</span></strong><span>Input and subtitle movement</span></div><div class="metric"><strong>2<span style="display:inline;font-size:15px"> s</span></strong><span>Idle delay after revealing controls</span></div></div></div><p class="doc-note">First subtitles hide the input immediately unless the viewer is editing, listening or using keyboard focus. Reduced motion removes movement and fades. Increased contrast strengthens the surfaces and borders. The input respects bottom safe areas. Subtitle actions fade while inactive and return on hover, touch or keyboard focus. The foreground preserves the scene ratio and readable text. The surrounding viewport remains black during playback.</p></section>
108
108
  <footer class="doc-footer"><span>VanillaSky · VideoChat immersive interface</span><span>Rendered with the same tokens and component CSS as the experience.</span></footer>
109
109
  </div></main>
110
110
  <script>
@@ -1,10 +1,10 @@
1
- # Video Response Protocol 0.5
1
+ # Video Response Protocol 0.6
2
2
 
3
3
  ## Transport
4
4
 
5
5
  The public transport is UTF-8 Server-Sent Events returned from a `POST` request.
6
6
  Responses use `Content-Type: text/event-stream`,
7
- `x-vanillasky-video-stream: 0.5`, `Cache-Control: no-cache, no-transform`, and
7
+ `x-vanillasky-video-stream: 0.6`, `Cache-Control: no-cache, no-transform`, and
8
8
  `X-Accel-Buffering: no`. Each block has an SSE `id`, the event name
9
9
  `video`, and one JSON envelope in `data`. A final `data: [DONE]`
10
10
  closes the transport after a terminal protocol event. Comment heartbeats do not
@@ -14,7 +14,7 @@ change protocol state.
14
14
 
15
15
  ```ts
16
16
  type VideoEvent<T extends string, D> = {
17
- protocolVersion: "0.5";
17
+ protocolVersion: "0.6";
18
18
  runId: string;
19
19
  sequence: number;
20
20
  eventId: string; // exactly `${runId}:${sequence}`
@@ -42,7 +42,7 @@ finish reason, and a deterministic checksum. The reducer verifies that the
42
42
  snapshot equals the state produced by all prior events. The checksum detects
43
43
  accidental drift; it is not a cryptographic signature.
44
44
 
45
- The terminal snapshot carries persisted `schemaVersion: "0.1"`. That storage
45
+ The terminal snapshot carries persisted `schemaVersion: "0.2"`. That storage
46
46
  version is independent from this streaming protocol version. Load stored
47
47
  snapshots through the universal `parseVideo` boundary described in the
48
48
  [persistence guide](../persistence.md).
@@ -100,3 +100,13 @@ SDK does not prescribe or operate a persistence service.
100
100
  Validate adapter output and persisted replay logs against this protocol before
101
101
  accepting them. A replay log must preserve ordering, checksums, and a terminal
102
102
  event.
103
+
104
+ ## Host-authored narration across cuts
105
+
106
+ `VideoScene.narrationGroup` optionally joins adjacent shots to one prepared spoken paragraph. A host supplies the shared `id`, `text`, measured `totalSeconds`, and each segment's `offsetSeconds` and `durationSeconds`. Each scene retains its narration fragment and a matching `timing.fixedDuration`; fragments must exactly cover the paragraph in order. The planner cannot emit this field or guess speech timings.
107
+
108
+ Grouped playback requires prepared speech with `supportsOffsets: true`. The generated-audio voice supports offsets; browser speech synthesis does not. The chat hook prepares and validates a complete group before showing its first scene, including saved replay. Hosts using a standalone player must prepare the paragraph and coordinate narration themselves before starting playback. Unsupported voices and mismatched measured durations fail explicitly. Default planner responses still prepare narration per scene.
109
+
110
+ For standalone playback, provide a synchronous `narrationReady` callback alongside your `onSceneChange` narration handler. Return false while a new grouped paragraph awaits actual audio onset, then true from the voice's `onStart` callback; also release readiness on completion, failure, or interruption. Abort pending narration from the player's `onError` handler. `VideoChat` wires this automatically through its internal narration hook. For grouped paragraphs, the voice must invoke `onStart` when audio actually begins, not when audio is prepared or `play()` is requested. The first visual cue starts narration, then the playhead waits for that onset without pausing the voice. A missing onset stops the player with an error after eight seconds of active waiting. For prepared audio, also provide `narrationTime(scene)` using the active voice's optional `getCurrentTime()`: return paragraph-relative seconds (including the seek offset) for a group, or scene-relative seconds otherwise. This makes the actual audio clock authoritative through cold-start delays and mid-speech stalls. Return `undefined` for silent scenes or unavailable clocks; after ordinary narration completes, release to wall time so the authored reading hold can finish. `VideoChat` coordinates these callbacks automatically. A clock that stops advancing for eight active seconds produces an error; visual-readiness holds and deliberate pauses do not consume that timeout. Voices without an observable playback clock retain wall-time playback.
111
+
112
+ Readiness holds pause narration together with the picture. The same audio continues across adjacent group scenes; replay starts a new playback session. This preserves words through delayed media rather than promising uninterrupted playback on every network.
package/docs/testing.md CHANGED
@@ -34,7 +34,7 @@ describe("POST /api/video-chat", () => {
34
34
  body: JSON.stringify({
35
35
  prompt: "Explain why the Moon shows one face",
36
36
  opening: "The Moon turns, perfectly matching its orbit.",
37
- mode: "templates",
37
+ mode: "cinematic",
38
38
  orientation: "landscape",
39
39
  }),
40
40
  },