frameyard 0.8.0 → 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 (327) hide show
  1. package/CHANGELOG.md +177 -14
  2. package/GLOSSARY.md +112 -2
  3. package/README.md +302 -265
  4. package/dist/assets/integrations.d.ts +8 -4
  5. package/dist/assets/integrations.d.ts.map +1 -1
  6. package/dist/assets/integrations.js +8 -4
  7. package/dist/assets/integrations.js.map +1 -1
  8. package/dist/assets/provenance.d.ts +2 -2
  9. package/dist/assets/provenance.d.ts.map +1 -1
  10. package/dist/assets/provenance.js +5 -3
  11. package/dist/assets/provenance.js.map +1 -1
  12. package/dist/assets/tts-supertonic.d.ts +28 -0
  13. package/dist/assets/tts-supertonic.d.ts.map +1 -0
  14. package/dist/assets/tts-supertonic.js +156 -0
  15. package/dist/assets/tts-supertonic.js.map +1 -0
  16. package/dist/assets/tts.d.ts +18 -6
  17. package/dist/assets/tts.d.ts.map +1 -1
  18. package/dist/assets/tts.js +219 -97
  19. package/dist/assets/tts.js.map +1 -1
  20. package/dist/cli.js +1685 -93
  21. package/dist/cli.js.map +1 -1
  22. package/dist/composition/format.d.ts +1 -1
  23. package/dist/composition/format.d.ts.map +1 -1
  24. package/dist/composition/format.js +1 -1
  25. package/dist/composition/format.js.map +1 -1
  26. package/dist/context.d.ts +82 -0
  27. package/dist/context.d.ts.map +1 -0
  28. package/dist/context.js +277 -0
  29. package/dist/context.js.map +1 -0
  30. package/dist/contracts.d.ts +25 -0
  31. package/dist/contracts.d.ts.map +1 -1
  32. package/dist/contracts.js.map +1 -1
  33. package/dist/doctor.d.ts +14 -1
  34. package/dist/doctor.d.ts.map +1 -1
  35. package/dist/doctor.js +166 -7
  36. package/dist/doctor.js.map +1 -1
  37. package/dist/errors.d.ts +1 -1
  38. package/dist/errors.d.ts.map +1 -1
  39. package/dist/errors.js.map +1 -1
  40. package/dist/fonts/add.d.ts +41 -0
  41. package/dist/fonts/add.d.ts.map +1 -0
  42. package/dist/fonts/add.js +259 -0
  43. package/dist/fonts/add.js.map +1 -0
  44. package/dist/fonts/binary.d.ts +4 -0
  45. package/dist/fonts/binary.d.ts.map +1 -0
  46. package/dist/fonts/binary.js +21 -0
  47. package/dist/fonts/binary.js.map +1 -0
  48. package/dist/fonts/css.d.ts +4 -0
  49. package/dist/fonts/css.d.ts.map +1 -0
  50. package/dist/fonts/css.js +50 -0
  51. package/dist/fonts/css.js.map +1 -0
  52. package/dist/fonts/fontsource.d.ts +107 -0
  53. package/dist/fonts/fontsource.d.ts.map +1 -0
  54. package/dist/fonts/fontsource.js +565 -0
  55. package/dist/fonts/fontsource.js.map +1 -0
  56. package/dist/fonts/index.d.ts +11 -0
  57. package/dist/fonts/index.d.ts.map +1 -0
  58. package/dist/fonts/index.js +11 -0
  59. package/dist/fonts/index.js.map +1 -0
  60. package/dist/fonts/install.d.ts +24 -0
  61. package/dist/fonts/install.d.ts.map +1 -0
  62. package/dist/fonts/install.js +145 -0
  63. package/dist/fonts/install.js.map +1 -0
  64. package/dist/fonts/manifest.d.ts +21 -0
  65. package/dist/fonts/manifest.d.ts.map +1 -0
  66. package/dist/fonts/manifest.js +96 -0
  67. package/dist/fonts/manifest.js.map +1 -0
  68. package/dist/fonts/options.d.ts +9 -0
  69. package/dist/fonts/options.d.ts.map +1 -0
  70. package/dist/fonts/options.js +66 -0
  71. package/dist/fonts/options.js.map +1 -0
  72. package/dist/fonts/schema.d.ts +4 -0
  73. package/dist/fonts/schema.d.ts.map +1 -0
  74. package/dist/fonts/schema.js +316 -0
  75. package/dist/fonts/schema.js.map +1 -0
  76. package/dist/fonts/summary.d.ts +16 -0
  77. package/dist/fonts/summary.d.ts.map +1 -0
  78. package/dist/fonts/summary.js +22 -0
  79. package/dist/fonts/summary.js.map +1 -0
  80. package/dist/fonts/types.d.ts +56 -0
  81. package/dist/fonts/types.d.ts.map +1 -0
  82. package/dist/fonts/types.js +8 -0
  83. package/dist/fonts/types.js.map +1 -0
  84. package/dist/fonts/verify.d.ts +33 -0
  85. package/dist/fonts/verify.d.ts.map +1 -0
  86. package/dist/fonts/verify.js +200 -0
  87. package/dist/fonts/verify.js.map +1 -0
  88. package/dist/html/validate.d.ts.map +1 -1
  89. package/dist/html/validate.js +59 -0
  90. package/dist/html/validate.js.map +1 -1
  91. package/dist/index.d.ts +3 -3
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +1 -1
  94. package/dist/index.js.map +1 -1
  95. package/dist/inspect.d.ts +61 -0
  96. package/dist/inspect.d.ts.map +1 -0
  97. package/dist/inspect.js +325 -0
  98. package/dist/inspect.js.map +1 -0
  99. package/dist/lint.d.ts +63 -0
  100. package/dist/lint.d.ts.map +1 -0
  101. package/dist/lint.js +265 -0
  102. package/dist/lint.js.map +1 -0
  103. package/dist/policy.d.ts +69 -0
  104. package/dist/policy.d.ts.map +1 -0
  105. package/dist/policy.js +361 -0
  106. package/dist/policy.js.map +1 -0
  107. package/dist/preview/fonts/LICENSE-figtree.txt +93 -0
  108. package/dist/preview/fonts/LICENSE-space-mono.txt +93 -0
  109. package/dist/preview/fonts/README.md +23 -0
  110. package/dist/preview/fonts/figtree-latin-wght-normal.woff2 +0 -0
  111. package/dist/preview/fonts/space-mono-latin-400-normal.woff2 +0 -0
  112. package/dist/preview/fonts/space-mono-latin-700-normal.woff2 +0 -0
  113. package/dist/preview/main.js +5 -2
  114. package/dist/preview/main.js.map +1 -1
  115. package/dist/preview/timeline-data.d.ts +17 -69
  116. package/dist/preview/timeline-data.d.ts.map +1 -1
  117. package/dist/preview/timeline-host.d.ts.map +1 -1
  118. package/dist/preview/timeline-host.js +70 -45
  119. package/dist/preview/timeline-host.js.map +1 -1
  120. package/dist/preview/timeline-main.d.ts.map +1 -1
  121. package/dist/preview/timeline-main.js +88 -27
  122. package/dist/preview/timeline-main.js.map +1 -1
  123. package/dist/project.d.ts +10 -3
  124. package/dist/project.d.ts.map +1 -1
  125. package/dist/project.js +20 -17
  126. package/dist/project.js.map +1 -1
  127. package/dist/renderer/audio.d.ts +6 -0
  128. package/dist/renderer/audio.d.ts.map +1 -1
  129. package/dist/renderer/audio.js +3 -1
  130. package/dist/renderer/audio.js.map +1 -1
  131. package/dist/renderer/browser.d.ts.map +1 -1
  132. package/dist/renderer/browser.js +13 -0
  133. package/dist/renderer/browser.js.map +1 -1
  134. package/dist/renderer/concurrency.d.ts +6 -0
  135. package/dist/renderer/concurrency.d.ts.map +1 -0
  136. package/dist/renderer/concurrency.js +27 -0
  137. package/dist/renderer/concurrency.js.map +1 -0
  138. package/dist/renderer/ffmpeg.d.ts +104 -5
  139. package/dist/renderer/ffmpeg.d.ts.map +1 -1
  140. package/dist/renderer/ffmpeg.js +580 -67
  141. package/dist/renderer/ffmpeg.js.map +1 -1
  142. package/dist/renderer/index.js +1 -1
  143. package/dist/renderer/index.js.map +1 -1
  144. package/dist/renderer/keyframe-expressions.d.ts +39 -0
  145. package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
  146. package/dist/renderer/keyframe-expressions.js +180 -0
  147. package/dist/renderer/keyframe-expressions.js.map +1 -0
  148. package/dist/renderer/session-pool.d.ts +28 -0
  149. package/dist/renderer/session-pool.d.ts.map +1 -0
  150. package/dist/renderer/session-pool.js +91 -0
  151. package/dist/renderer/session-pool.js.map +1 -0
  152. package/dist/renderer/timeline.d.ts +2 -0
  153. package/dist/renderer/timeline.d.ts.map +1 -1
  154. package/dist/renderer/timeline.js +600 -147
  155. package/dist/renderer/timeline.js.map +1 -1
  156. package/dist/renderer/timings.d.ts +13 -0
  157. package/dist/renderer/timings.d.ts.map +1 -0
  158. package/dist/renderer/timings.js +29 -0
  159. package/dist/renderer/timings.js.map +1 -0
  160. package/dist/runtime/host.d.ts.map +1 -1
  161. package/dist/runtime/host.js +4 -0
  162. package/dist/runtime/host.js.map +1 -1
  163. package/dist/scaffold.d.ts +3 -0
  164. package/dist/scaffold.d.ts.map +1 -1
  165. package/dist/scaffold.js +48 -320
  166. package/dist/scaffold.js.map +1 -1
  167. package/dist/server/composition-html.d.ts +2 -0
  168. package/dist/server/composition-html.d.ts.map +1 -1
  169. package/dist/server/composition-html.js +7 -0
  170. package/dist/server/composition-html.js.map +1 -1
  171. package/dist/server/preview-html.d.ts.map +1 -1
  172. package/dist/server/preview-html.js +74 -188
  173. package/dist/server/preview-html.js.map +1 -1
  174. package/dist/server/preview-style.d.ts +11 -0
  175. package/dist/server/preview-style.d.ts.map +1 -0
  176. package/dist/server/preview-style.js +298 -0
  177. package/dist/server/preview-style.js.map +1 -0
  178. package/dist/server/server.d.ts.map +1 -1
  179. package/dist/server/server.js +31 -1
  180. package/dist/server/server.js.map +1 -1
  181. package/dist/server/timeline-preview-data.d.ts.map +1 -1
  182. package/dist/server/timeline-preview-data.js +12 -34
  183. package/dist/server/timeline-preview-data.js.map +1 -1
  184. package/dist/server/timeline-preview-html.d.ts.map +1 -1
  185. package/dist/server/timeline-preview-html.js +140 -148
  186. package/dist/server/timeline-preview-html.js.map +1 -1
  187. package/dist/templates.d.ts +16 -0
  188. package/dist/templates.d.ts.map +1 -0
  189. package/dist/templates.js +186 -0
  190. package/dist/templates.js.map +1 -0
  191. package/dist/timeline/easing.d.ts +36 -0
  192. package/dist/timeline/easing.d.ts.map +1 -0
  193. package/dist/timeline/easing.js +225 -0
  194. package/dist/timeline/easing.js.map +1 -0
  195. package/dist/timeline/keyframes.d.ts +23 -0
  196. package/dist/timeline/keyframes.d.ts.map +1 -0
  197. package/dist/timeline/keyframes.js +95 -0
  198. package/dist/timeline/keyframes.js.map +1 -0
  199. package/dist/timeline/plan.d.ts +6 -12
  200. package/dist/timeline/plan.d.ts.map +1 -1
  201. package/dist/timeline/plan.js +17 -30
  202. package/dist/timeline/plan.js.map +1 -1
  203. package/dist/timeline/resolve.d.ts +12 -0
  204. package/dist/timeline/resolve.d.ts.map +1 -1
  205. package/dist/timeline/resolve.js +105 -9
  206. package/dist/timeline/resolve.js.map +1 -1
  207. package/dist/timeline/scene.d.ts +126 -0
  208. package/dist/timeline/scene.d.ts.map +1 -0
  209. package/dist/timeline/scene.js +109 -0
  210. package/dist/timeline/scene.js.map +1 -0
  211. package/dist/timeline/schema.d.ts +31 -0
  212. package/dist/timeline/schema.d.ts.map +1 -1
  213. package/dist/timeline/schema.js +240 -9
  214. package/dist/timeline/schema.js.map +1 -1
  215. package/dist/timeline/transitions.d.ts +21 -0
  216. package/dist/timeline/transitions.d.ts.map +1 -0
  217. package/dist/timeline/transitions.js +23 -0
  218. package/dist/timeline/transitions.js.map +1 -0
  219. package/dist/version.d.ts +1 -1
  220. package/dist/version.d.ts.map +1 -1
  221. package/dist/version.js +1 -1
  222. package/dist/version.js.map +1 -1
  223. package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
  224. package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
  225. package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +1 -1
  226. package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +2 -1
  227. package/docs/adr/0015-inherit-composition-format-from-the-project.md +2 -2
  228. package/docs/adr/0016-author-compositions-as-html-fragments.md +1 -1
  229. package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
  230. package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
  231. package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
  232. package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
  233. package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
  234. package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
  235. package/docs/adr/0024-stream-composition-captures-and-skip-trivial-composites.md +86 -0
  236. package/docs/adr/0025-capture-timeline-layers-in-parallel-with-a-global-limit.md +67 -0
  237. package/docs/adr/0026-reuse-loaded-composition-sessions-across-segments.md +70 -0
  238. package/docs/adr/0027-encode-two-full-frame-layers-through-dual-pipes.md +76 -0
  239. package/docs/adr/0028-encode-full-frame-video-segments-directly.md +65 -0
  240. package/docs/migration-0.4.md +2 -2
  241. package/docs/preview-testing.md +27 -6
  242. package/docs/{v0.1-design.md → v0.x-design.md} +57 -32
  243. package/examples/blend/README.md +3 -3
  244. package/examples/demo/README.md +2 -2
  245. package/examples/intro/README.md +71 -0
  246. package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
  247. package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
  248. package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
  249. package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
  250. package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
  251. package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
  252. package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
  253. package/examples/intro/assets/grain.png +0 -0
  254. package/examples/intro/assets/score.ogg +0 -0
  255. package/examples/intro/compositions/brand.html +412 -0
  256. package/examples/intro/compositions/chrome.controller.js +63 -0
  257. package/examples/intro/compositions/chrome.html +256 -0
  258. package/examples/intro/compositions/lattice.controller.js +148 -0
  259. package/examples/intro/compositions/lattice.html +308 -0
  260. package/examples/intro/compositions/opening.html +504 -0
  261. package/examples/intro/compositions/studio.html +930 -0
  262. package/examples/intro/compositions/theme.css +218 -0
  263. package/examples/intro/fy.config.js +13 -0
  264. package/examples/intro/fy.fonts.json +100 -0
  265. package/examples/intro/scripts/make-grain.mjs +79 -0
  266. package/examples/intro/scripts/make-score.mjs +702 -0
  267. package/examples/intro/timelines/intro.timeline.json +60 -0
  268. package/examples/keyframes/README.md +76 -0
  269. package/examples/keyframes/compositions/backdrop.html +60 -0
  270. package/examples/keyframes/compositions/card.html +99 -0
  271. package/examples/keyframes/fy.config.js +10 -0
  272. package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
  273. package/examples/nested/README.md +112 -0
  274. package/examples/nested/assets/pulse.wav +0 -0
  275. package/examples/nested/compositions/backdrop.html +84 -0
  276. package/examples/nested/compositions/card.html +275 -0
  277. package/examples/nested/compositions/overlay.html +307 -0
  278. package/examples/nested/fy.config.js +13 -0
  279. package/examples/nested/scripts/make-pulse.sh +20 -0
  280. package/examples/nested/timelines/chapter-a.timeline.json +39 -0
  281. package/examples/nested/timelines/chapter-b.timeline.json +39 -0
  282. package/examples/nested/timelines/nested-reel.timeline.json +52 -0
  283. package/examples/static/README.md +2 -2
  284. package/examples/transitions/README.md +128 -0
  285. package/examples/transitions/assets/alpha.wav +0 -0
  286. package/examples/transitions/assets/beta.wav +0 -0
  287. package/examples/transitions/assets/gamma.wav +0 -0
  288. package/examples/transitions/compositions/alpha.html +56 -0
  289. package/examples/transitions/compositions/beta.html +56 -0
  290. package/examples/transitions/compositions/gamma.html +56 -0
  291. package/examples/transitions/fy.config.js +12 -0
  292. package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
  293. package/examples/transitions/timelines/transitions.timeline.json +31 -0
  294. package/examples/video/README.md +4 -4
  295. package/examples/webgl/README.md +1 -1
  296. package/package.json +13 -6
  297. package/scaffolds/init/AGENTS.md +131 -0
  298. package/scaffolds/init/README.md +56 -0
  299. package/scaffolds/init/compositions/hello.html +79 -0
  300. package/scaffolds/init/fy.config.js +9 -0
  301. package/scaffolds/init/gitignore.template +1 -0
  302. package/scaffolds/init/package.json +11 -0
  303. package/scaffolds/init/props/hello.json +4 -0
  304. package/scaffolds/init/props/main.json +6 -0
  305. package/scaffolds/init/timelines/main.timeline.json +20 -0
  306. package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
  307. package/scaffolds/local-models/fy.policy.json +5 -0
  308. package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
  309. package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
  310. package/scaffolds/local-models/pi/sandbox.json +15 -0
  311. package/scaffolds/local-models/pi/settings.json +15 -0
  312. package/skills/frameyard/README.md +1 -0
  313. package/skills/frameyard/SKILL.md +59 -61
  314. package/skills/frameyard/references/audio-assets.md +32 -3
  315. package/skills/frameyard/references/common-mistakes.md +53 -2
  316. package/skills/frameyard/references/deterministic-authoring.md +10 -3
  317. package/skills/frameyard/references/diagnostics.md +31 -1
  318. package/skills/frameyard/references/failure-recovery.md +32 -6
  319. package/skills/frameyard/references/fonts.md +132 -0
  320. package/skills/frameyard/references/json-results.md +119 -6
  321. package/skills/frameyard/references/quick-reference.md +222 -0
  322. package/skills/frameyard/references/timelines.md +97 -7
  323. /package/examples/blend/{video.config.js → fy.config.js} +0 -0
  324. /package/examples/demo/{video.config.js → fy.config.js} +0 -0
  325. /package/examples/static/{video.config.js → fy.config.js} +0 -0
  326. /package/examples/video/{video.config.js → fy.config.js} +0 -0
  327. /package/examples/webgl/{video.config.js → fy.config.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -8,6 +8,180 @@ Frameyard uses `0.x` versions until the composition, configuration, CLI,
8
8
  JSON-result, transcript, and skill contracts stabilize. Before `1.0.0`, a
9
9
  contract change may ship in a minor release and is called out explicitly below.
10
10
 
11
+ ## [Unreleased]
12
+
13
+ ## [0.10.0] - 2026-11-07
14
+
15
+ ### Added
16
+
17
+ - `FRAMEYARD_TIMINGS=1` emits one JSON line per render stage to stderr without
18
+ changing the `--json` result on stdout. `bench/summarize-timings.mjs`
19
+ aggregates session, capture, PNG-write, FFV1, composite, concat, audio, and
20
+ publish timings for production-path profiling,
21
+ `bench/timeline-60min-gate.mjs` generates the 60-minute composition-only
22
+ production gate, and `bench/timeline-60min-representative.mjs` generates the
23
+ representative 60-minute 50/50 gate. The representative gate rendered 108,000
24
+ frames in 16.82 minutes with eight workers, clean scratch, and video
25
+ re-encoded rather than stream-copied. `FRAMEYARD_FFV1_THREADS` bounds the streaming FFV1 encoder.
26
+ - Composition clips now stream captured PNGs directly into FFV1 instead of
27
+ writing a PNG sequence to scratch first. A segment with one full-frame,
28
+ untransformed composition skips the FFV1 intermediate and the FFmpeg
29
+ composite, encoding the capture directly to the delivery segment over the
30
+ timeline background. See
31
+ `docs/adr/0024-stream-composition-captures-and-skip-trivial-composites.md`.
32
+ - Independent layer captures run concurrently under a FIFO semaphore, bounded
33
+ by `FRAMEYARD_CAPTURE_CONCURRENCY` or `min(workers * 2, 12)` by default. The
34
+ two-layer 60-second gate fell from 59.60 s to 43.78 s. See
35
+ `docs/adr/0025-capture-timeline-layers-in-parallel-with-a-global-limit.md`.
36
+ - The timeline renderer reuses loaded composition sessions across segments,
37
+ keyed by entry, props, and transparency, with a bounded idle pool. The
38
+ 10-minute one-layer gate fell from 211.8 s to 192.6 s, and the 60-minute gate
39
+ fell from 19.83 to 18.78 minutes with eight sessions instead of 360. See
40
+ `docs/adr/0026-reuse-loaded-composition-sessions-across-segments.md`.
41
+ - A full-frame composition base and a static composition overlay (including a
42
+ static PIP box) encode through dual image pipes into one delivery segment,
43
+ skipping FFV1 and the separate composite. The full-frame two-layer 60-second
44
+ workload fell from 53.27 s to 34.62 s with byte-identical output, the PIP
45
+ 60-second workload from 43.78 s to 33.90 s with pixel-identical frames, and
46
+ the full two-layer 60-minute gates completed in 29.69 minutes (full-frame) and
47
+ 25.75 minutes (PIP) with eight workers. Windows keeps the segmented fallback. See
48
+ `docs/adr/0027-encode-two-full-frame-layers-through-dual-pipes.md`.
49
+ - A single full-frame video with an identity transform re-encodes its source
50
+ range directly into the delivery segment, skipping the composite filter
51
+ graph while keeping the normal encoder settings and the RGBA color round trip
52
+ for pixel parity. A 60-second video-only gate fell from 10.65 s to 7.61 s
53
+ with identical decoded frames, and the representative 60-minute 50/50 gate
54
+ fell from 16.82 to 15.57 minutes with a byte-identical file. See
55
+ `docs/adr/0028-encode-full-frame-video-segments-directly.md`.
56
+
57
+ ### Changed
58
+
59
+ - The composition and timeline preview pages share one visual language:
60
+ ink-black surfaces, paper-white type, a single lime signal, monospace
61
+ instrument labels, and a tick-marked progress rail. The timeline strip adds a
62
+ frame ruler that tracks the playhead. The chrome bundles its own Figtree and
63
+ Space Mono faces and no longer links the project font sheet; project fonts
64
+ still apply inside compositions. See `src/server/preview-style.ts` and
65
+ `src/preview/fonts/`.
66
+
67
+ ### Fixed
68
+
69
+ - Timeline preview plays composition audio once instead of restarting it when
70
+ the mapped clip window is longer than the source file. An embedded `<audio>`
71
+ without `data-fy-duration` now stops at the end of the source instead of
72
+ looping for the rest of the composition clip.
73
+ - Timeline inspection no longer reports an overrun when a composition's
74
+ natural-duration audio ends before its mapped clip window. The clip is still
75
+ mixed and padded with silence, while explicit trim ranges beyond the source
76
+ keep warning.
77
+
78
+ [0.10.0]: https://github.com/vladiantio/frameyard/compare/v0.9.0...v0.10.0
79
+
80
+ ## [0.9.0] - 2026-11-06
81
+
82
+ ### Added
83
+
84
+ - `fy.fonts.json` records vendored font families, files, licenses, and
85
+ SHA-256 values. `frameyard font search` filters the Fontsource catalog, and
86
+ `font add <id>` pins a Fontsource `npmVersion`, vendors its WOFF2 files and
87
+ license, and supports variable `wght` or explicit static weights. `font add
88
+ --file` copies a local WOFF2/WOFF/TTF/OTF font, `font add --url` vendors an
89
+ HTTPS source with an optional SHA-256, and `font list`/`verify`/`remove`
90
+ manage the manifest. The server injects the generated `@font-face` sheet into
91
+ every composition, the host fails with `FONT_LOAD_FAILED` when a used face
92
+ does not load, and static validation warns with `FONT_REMOTE` for remote font
93
+ URLs. See
94
+ `docs/adr/0023-pin-fonts-as-project-assets.md`.
95
+ - `fy.policy.json` is a workspace resource policy discovered from the
96
+ working directory upward. It gates `tts`, `transcribe`, and `render`, caps
97
+ `still`, denies `--force`/`--overwrite`, and supports the `shared-cpu`
98
+ profile. `FRAMEYARD_POLICY` overrides the file. See
99
+ `docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md`.
100
+ - `frameyard lint <id>` runs static validation, timeline resolution, scene and
101
+ keyframe projection for every frame, a controller determinism scan, and media
102
+ metadata probing without Chromium. It is the edit-loop gate; `check` remains
103
+ the full gate.
104
+ - `frameyard context --json` prints a compact project digest with resolved
105
+ composition metadata, prop keys and types, timeline clip summaries, unique
106
+ assets, diagnostics, the active policy, and next commands.
107
+ - `frameyard inspect <image> --json` reports dimensions, alpha coverage, blank
108
+ detection, background and dominant colors, a content bounding box, a coarse
109
+ luminance grid, and edge density. `frameyard compare <a> <b> --json` reports
110
+ pixel and 3×3 region differences and exits 1 when the images differ.
111
+ - `frameyard add --list` and `frameyard add <composition|timeline> <id>
112
+ --template <name>` write `static-poster`, `title-card`, `lower-third`, or
113
+ `slideshow` entries and print the exact `fy.config.js` registration
114
+ snippet. `add local-models` and `init --local-models` install the optional
115
+ policy, OpenCode permissions, and Pi settings. `add` never edits the config
116
+ and never overwrites an existing entry. The skill adds a compact quick
117
+ reference for limited-context hosts.
118
+ - `init` templates now live under `scaffolds/init/`; `src/scaffold.ts` reads
119
+ them and renders the project-name and version placeholders.
120
+ - `pnpm eval:local` runs an opt-in local-model authoring eval against a
121
+ llama.cpp router. It covers eight tasks, feeds `quick-reference.md` into the
122
+ system prompt, repairs lint failures, and reports first-pass status, turns,
123
+ tokens, wall time, sampled server CPU/RSS/GPU, and optional budgets. It never
124
+ renders and stays outside `pnpm check`.
125
+ - `tts`, `transcribe`, and `render` accept `--dry-run`, which validates inputs
126
+ and returns `plan`, `commandLine`, `prerequisites`, and `blockers` without
127
+ writing outputs, loading a model, launching Chromium, or encoding.
128
+ `--profile shared-cpu` caps timeline workers at 2 and composition concurrency
129
+ at 1; `--low-priority` lowers process priority without the caps.
130
+ - `doctor` reports the active resource policy and basic CPU, load, and memory
131
+ resources. Local-model host setup is opt-in through
132
+ `scaffolds/local-models/`, which contains the `authoring` policy, OpenCode
133
+ permissions, and Pi settings; the repository does not enable it by default.
134
+ - `tts --engine supertonic` synthesizes 31-language speech through
135
+ `sherpa-onnx-offline-tts` and the Supertonic 3 model archive. It normalizes
136
+ the engine output to the frozen 24 kHz mono WAV contract, records the seven
137
+ model files in the provenance manifest, and adds `doctor --sherpa-onnx-tts`
138
+ and `doctor --model-dir` inspection. The default engine remains `qwen`.
139
+ - Timeline clips on one video track may dissolve across their overlap through a
140
+ timeline-level `transitions` array. The window is `to.start .. from.end`; the
141
+ incoming clip composites above the outgoing clip with `opacity × eased
142
+ progress`, contributed audio crossfades with equal power, and preview draws a
143
+ read-only band. Undeclared overlap stays an error. See
144
+ `docs/adr/0019-make-same-track-overlap-the-transition-window.md` and
145
+ `examples/transitions/`.
146
+ - Audio clips on an audio track may crossfade through the same `transitions`
147
+ array with `kind: "crossfade"`. The kind/track matrix is explicit: `dissolve`
148
+ is video-only, `crossfade` is audio-only. See
149
+ `docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md` and the
150
+ `audio-crossfade` timeline in `examples/transitions/`.
151
+ - Timeline clips animate `x`, `y`, `scale`, `rotation`, and `opacity` with
152
+ clip-local `keyframes`, CSS easing, preview markers, and FFmpeg expressions.
153
+ Animated opacity composes through blend modes. See
154
+ `docs/adr/0018-animate-timeline-clips-with-keyframes.md` and
155
+ `examples/keyframes/`.
156
+
157
+ ### Changed
158
+
159
+ - `frameyard init` without a directory prompts for a project name (default
160
+ `my-video`) on an interactive terminal and creates a directory with that
161
+ name. Non-interactive and `--json` runs use `my-video` without prompting.
162
+ Pass a directory to scaffold into an explicit path or the current directory.
163
+ - The project configuration file is `fy.config.js` (previously
164
+ `video.config.js`), the assets manifest is `fy.assets.json` (previously
165
+ `frameyard.assets.json`), and the workspace resource policy is
166
+ `fy.policy.json` (previously `frameyard.policy.json`). Rename existing files
167
+ in place; the loaders read only the new names. `FRAMEYARD_POLICY` keeps
168
+ its name.
169
+
170
+ ### Fixed
171
+
172
+ - Timeline preview layers pin `color-scheme: light` on the embedded document
173
+ root. A composition that declared a dark scheme could paint an opaque canvas
174
+ base and hide every layer beneath it, so a timeline with a transparent
175
+ overlay composition appeared to show only the overlay.
176
+ - Rotated timeline clips keep their full corners. Keyed rotation uses a
177
+ segment-stable diagonal bound; static rotation uses its exact rotated
178
+ bounding box.
179
+ - Animated-opacity masks use a 2×2 FFmpeg source instead of 1×1. FFmpeg 6
180
+ rejects a 1×1 `yuv420p` frame, which broke keyframe, transition, and example
181
+ renders on Ubuntu 24.04 runners.
182
+
183
+ [0.9.0]: https://github.com/vladiantio/frameyard/compare/v0.8.0...v0.9.0
184
+
11
185
  ## [0.8.0] - 2026-11-03
12
186
 
13
187
  ### Changed
@@ -61,12 +235,9 @@ contract change may ship in a minor release and is called out explicitly below.
61
235
  applies `mix-blend-mode` and stacks layers by track; production uses an
62
236
  alpha-aware FFmpeg composite. Nested timelines blend as flattened groups.
63
237
  See ADR 0014 and `examples/blend/`.
64
- - `examples/static/` demonstrates a timeless composition rendered to PNG and
65
- reused as a timeline image.
66
- - `examples/blend/compositions/backdrop.html` demonstrates an inherited
67
- composition format.
68
- - Static composition validation, host, CLI, timeline, preview, and example
69
- coverage.
238
+ - Static composition validation, host, CLI, timeline, and preview coverage.
239
+
240
+ [0.8.0]: https://github.com/vladiantio/frameyard/compare/v0.7.0...v0.8.0
70
241
 
71
242
  ## [0.7.0] - 2026-11-02
72
243
 
@@ -111,7 +282,6 @@ contract change may ship in a minor release and is called out explicitly below.
111
282
  layer iframes reach the preview transport, while text entry does not step
112
283
  frames across iframe boundaries.
113
284
 
114
- [0.8.0]: https://github.com/vladiantio/frameyard/compare/v0.7.0...v0.8.0
115
285
  [0.7.0]: https://github.com/vladiantio/frameyard/compare/v0.6.0...v0.7.0
116
286
 
117
287
  ## [0.6.0] - 2026-11-01
@@ -142,8 +312,6 @@ contract change may ship in a minor release and is called out explicitly below.
142
312
  `timelines` map. `project.json` publishes `format` and `timelines`.
143
313
  - Timeline-first `init` scaffold with a composition clip, timeline override
144
314
  props, and `main` commands.
145
- - The video example adds WebM and MP4 timelines that place video clips under a
146
- transparent HTML overlay.
147
315
  - Official skill reference for timelines, clips, tracks, nested timelines,
148
316
  props overrides, commands, preview, and determinism.
149
317
  - Timeline config, schema, resolution, planning, rendering, preview, and
@@ -173,11 +341,6 @@ contract change may ship in a minor release and is called out explicitly below.
173
341
 
174
342
  ### Added
175
343
 
176
- - `examples/webgl/` demonstrates a frame-seeked WebGL shader driven by an
177
- external controller.
178
- - `examples/video/` demonstrates two-pass embedded video with the same
179
- composition rendered from WebM and MP4 sources, plus a script that
180
- regenerates both clips with FFmpeg.
181
344
  - `test/examples.test.ts` captures a still from every bundled example.
182
345
  - `pnpm compare-stills <a.png> <b.png>` reports an exact decoded-pixel diff and
183
346
  exits nonzero on any difference, for checking determinism at the capture
package/GLOSSARY.md CHANGED
@@ -151,17 +151,79 @@ How a clip source's natural frame is fitted into the project frame or an explici
151
151
  _Avoid_: Resize mode, object-fit
152
152
 
153
153
  **Clip transform**:
154
- The static size, position, rotation, and opacity applied to a visual clip. Size is either an explicit `width`/`height` box or a `scale` multiplier.
154
+ The size, position, rotation, and opacity applied to a visual clip. Size is either an explicit `width`/`height` box or a `scale` multiplier. Static transform values and keyframe tracks are separate; a keyed channel cannot also be a static scalar.
155
155
  _Avoid_: Effect, animation
156
156
 
157
+ **Clip keyframes**:
158
+ A per-channel animation on a visual clip's presentation: `x`, `y`, `scale`, `rotation`, or `opacity`. Keys use clip-local integer frames and hold before the first and after the last key.
159
+ _Avoid_: CSS keyframes, composition animation
160
+
161
+ **Keyframe**:
162
+ One `{ frame, value, easing? }` entry in a clip keyframe track. `frame` is clip-local; `easing` applies to the interval after it.
163
+ _Avoid_: Marker, cue
164
+
165
+ **Keyframe track**:
166
+ The ordered keyframes for one presentation channel. Keys are strictly increasing and stay within the clip's `0..duration` range.
167
+ _Avoid_: Animation curve, property lane
168
+
169
+ **Easing**:
170
+ A CSS timing function that shapes the interval between two timeline keyframes. Preview evaluates it exactly; production approximates it in the FFmpeg filter graph.
171
+ _Avoid_: Interpolation, curve
172
+
157
173
  **Blend mode**:
158
174
  The static CSS `mix-blend-mode` a visual clip applies against the accumulated backdrop of all lower layers and the timeline background. It respects the clip's source alpha and is not animated.
159
175
  _Avoid_: Filter, effect, composite mode
160
176
 
177
+ **Transition**:
178
+ A declared handoff between two clips on one track, written as `{ from, to, kind, easing? }` in the timeline's `transitions` array. `dissolve` is a visual fade on a video track; `crossfade` is an audio gain ramp on an audio track. The clips overlap; an overlap without a declared transition is still an error.
179
+ _Avoid_: Overlap
180
+
181
+ **Crossfade**:
182
+ The audio-track transition kind. It ramps the outgoing audio clip down and the incoming clip up with equal power and changes no pixels.
183
+ _Avoid_: Dissolve, fade
184
+
185
+ **Transition window**:
186
+ The overlap `to.start .. from.end` where both clips are active. During a dissolve the incoming clip composites above the outgoing clip with `opacity × eased progress`, evaluated at absolute timeline frames.
187
+ _Avoid_: Handle, overlap region
188
+
189
+ **Transition role**:
190
+ The side a clip takes in a transition: `out` for the clip being replaced, `in` for the clip replacing it.
191
+ _Avoid_: Transition direction, fade side
192
+
193
+ **Equal-power crossfade**:
194
+ The audio gain curve inside either transition kind: the outgoing clip ramps as `sqrt(1 - t)` and the incoming clip as `sqrt(t)` after easing, keeping perceived loudness steady.
195
+ _Avoid_: Linear fade
196
+
161
197
  **Nested timeline**:
162
198
  A timeline used as a clip source inside another timeline.
163
199
  _Avoid_: Compound composition
164
200
 
201
+ ### Fonts
202
+
203
+ **Font manifest**:
204
+ The versioned `fy.fonts.json` file that records vendored font families, their CSS family names, providers, licenses, and file hashes. It is the source of truth for the generated `@font-face` sheet.
205
+ _Avoid_: Font config, font cache
206
+
207
+ **Font provider**:
208
+ The acquisition source recorded for a font family: `local` for a user-supplied file, `fontsource` for a pinned Fontsource download, or `url` for an explicit HTTPS source.
209
+ _Avoid_: Font catalog, font CDN
210
+
211
+ **Font family entry**:
212
+ One manifest entry keyed by a stable id. It contains the CSS family name, provider metadata, license, optional variable axes, and one or more font files.
213
+ _Avoid_: Font face, font package
214
+
215
+ **Font face**:
216
+ One file entry inside a family: a subset, style, weight or weight range, format, optional unicode range, SHA-256, and byte size.
217
+ _Avoid_: Font variant, font weight
218
+
219
+ **Font subset**:
220
+ A provider-defined slice of a family's glyph coverage, such as `latin` or `latin-ext`. Subsets are recorded per face and may carry a CSS `unicode-range`.
221
+ _Avoid_: Language, charset
222
+
223
+ **Variable font**:
224
+ A font file that carries a continuous range for one or more axes instead of separate static weights. Frameyard records the axes and emits a CSS weight range; the first version installs the `wght` axis only.
225
+ _Avoid_: Dynamic font, multiple weight font
226
+
165
227
  ### Deterministic execution
166
228
 
167
229
  **Frame**:
@@ -172,6 +234,10 @@ _Avoid_: Timestamp, animation tick
172
234
  A clip whose timeline range contains the current timeline frame.
173
235
  _Avoid_: Visible clip
174
236
 
237
+ **Timeline scene**:
238
+ The ordered active clips and their presentation at one timeline frame or over one render range, including transition roles and composite order. It is the timeline-level arrangement, not a composition.
239
+ _Avoid_: Timeline composition, frame snapshot
240
+
175
241
  **Clip capture**:
176
242
  The pixel frames a composition produces for a timeline clip before the timeline compositor combines layers.
177
243
  _Avoid_: Screenshot, render
@@ -312,6 +378,46 @@ _Avoid_: Frame export, image sequence
312
378
  The guarantee that, for the same timeline or composition revision, props, assets, and frame under a pinned runtime/browser environment, the visual state is identical. It does not imply identical encoded bytes, even on the same machine; encoded output depends on the encoder and its runtime.
313
379
  _Avoid_: Bit-for-bit encoding equality
314
380
 
381
+ ### Resource policy
382
+
383
+ **Resource policy**:
384
+ A workspace-local `fy.policy.json` declaration of which CLI operations are allowed and which resource limits apply. It is discovered from the working directory upward, independent of `fy.config.js`, and can only tighten its tier.
385
+ _Avoid_: Project config, agent runtime
386
+
387
+ **Policy tier**:
388
+ One of `light`, `authoring`, or `full`. A tier fixes the allowed heavy operations, the still-frame cap (1, 4, or the library cap of 32), and overwrite permission. `FRAMEYARD_POLICY` replaces the policy file for a human override.
389
+ _Avoid_: Permission mode, sandbox
390
+
391
+ **Resource profile**:
392
+ A named set of runtime caps and priority settings for a shared machine. `shared-cpu` caps timeline workers at two, composition concurrency at one, and lowers process priority.
393
+ _Avoid_: Encoder preset, render mode
394
+
395
+ **Lint**:
396
+ A Node-only validation pass that resolves a composition or timeline, projects every timeline scene and keyframe channel, scans controller source for wall-clock and randomness calls, and probes media metadata without launching Chromium. Lint is not a substitute for `check`.
397
+ _Avoid_: Static check, fast render
398
+
399
+ **Dry run**:
400
+ A heavy command mode that validates inputs and returns a plan, handoff command, prerequisites, and blockers without writing outputs, loading a model, launching Chromium, or encoding.
401
+ _Avoid_: Preview, partial render
402
+
403
+ ### Inspection and templates
404
+
405
+ **Project context**:
406
+ A compact, read-only digest of a project's format, composition metadata, prop types, timeline clips, assets, installed fonts, diagnostics, active policy, and next commands.
407
+ _Avoid_: Project dump, repo summary
408
+
409
+ **Image inspection**:
410
+ Text facts extracted from a decoded still: dimensions, alpha coverage, blank detection, background and dominant colors, content bounding box, luminance grid, and edge density.
411
+ _Avoid_: Visual review, AI vision
412
+
413
+ **Image comparison**:
414
+ A pixel and region diff between two decoded images. It reports differing pixels, channel deltas, a bounding box, and a 3×3 region breakdown.
415
+ _Avoid_: Pixel hash, visual diff
416
+
417
+ **Project template**:
418
+ A known-good composition or timeline entry that `add` writes while printing the explicit `fy.config.js` registration snippet.
419
+ _Avoid_: Generator, project scaffold
420
+
315
421
  ### Agent operation
316
422
 
317
423
  **Agent-assisted authoring and operation**:
@@ -333,9 +439,13 @@ A versioned, project-local record of generated assets, their content hashes, and
333
439
  _Avoid_: Render cache
334
440
 
335
441
  **Speech synthesis** (TTS):
336
- The local production of a spoken audio asset from text using a named speaker.
442
+ The local production of a spoken audio asset from text using a named speaker or numeric speaker id.
337
443
  _Avoid_: Voice generation, voice cloning, voice design
338
444
 
445
+ **Speech engine**:
446
+ A local executable that turns text into a speech asset. Frameyard supports qwentts.cpp CustomVoice and sherpa-onnx Supertonic 3.
447
+ _Avoid_: TTS provider, cloud voice
448
+
339
449
  **Transcription**:
340
450
  The local production of timed text from source audio or video.
341
451
  _Avoid_: Subtitle rendering