@hypit/hypit 0.1.3 → 0.1.5

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 (486) hide show
  1. package/README.md +5 -3
  2. package/dist/public/caption.d.ts +206 -183
  3. package/dist/public/composition.d.ts +25 -3
  4. package/dist/public/endpoint-kit.d.ts +24 -2
  5. package/dist/public/generation.d.ts +5 -1
  6. package/dist/public/hyperframes.d.ts +19 -1
  7. package/dist/public/narrative.d.ts +17 -3
  8. package/dist/public/performance.d.ts +1590 -0
  9. package/dist/public/program-space.d.ts +179 -32
  10. package/dist/public/runtime-kit.d.ts +21 -8
  11. package/dist/public/sound.d.ts +822 -0
  12. package/dist/public/speech.d.ts +41 -0
  13. package/dist/public/studio-adapter.d.ts +107 -16
  14. package/dist/public/temporal-markup.d.ts +9 -44
  15. package/dist/public/temporal.d.ts +37 -28
  16. package/dist/public/{semantic-track.d.ts → timeline.d.ts} +62 -49
  17. package/examples/README.md +18 -10
  18. package/examples/minimal-author-package/packages/example-component/preview/preview.svml +19 -12
  19. package/examples/minimal-author-package/packages/example-component/src/component.ts +5 -5
  20. package/examples/minimal-author-package/packages/example-component/src/fragment.ts +3 -5
  21. package/examples/minimal-author-package/packages/example-component/src/manifest.ts +8 -10
  22. package/examples/minimal-author-package/packages/example-component/src/surface.ts +2 -2
  23. package/examples/provider-package/README.md +78 -0
  24. package/examples/provider-package/hypit.runtime.json +12 -0
  25. package/examples/provider-package/packages/provider-images/package.json +11 -0
  26. package/examples/provider-package/packages/provider-images/src/activation.ts +24 -0
  27. package/examples/provider-package/packages/provider-images/src/provider.ts +122 -0
  28. package/examples/provider-package/packages/provider-images/tsconfig.json +9 -0
  29. package/examples/semantic-composition/README.md +6 -0
  30. package/examples/semantic-composition/chat.svml +6 -5
  31. package/examples/semantic-composition/packages/chat-scene/README.md +3 -3
  32. package/examples/semantic-composition/packages/chat-scene/src/activation.ts +18 -21
  33. package/examples/semantic-composition/packages/chat-scene/src/render.ts +4 -4
  34. package/examples/semantic-composition/packages/performance-styles/README.md +42 -0
  35. package/examples/semantic-composition/packages/performance-styles/package.json +25 -0
  36. package/examples/semantic-composition/packages/performance-styles/src/activation.ts +57 -0
  37. package/examples/semantic-composition/packages/performance-styles/src/render.ts +54 -0
  38. package/examples/semantic-composition/packages/performance-styles/src/studio.ts +28 -0
  39. package/examples/semantic-composition/packages/performance-styles/tsconfig.json +18 -0
  40. package/examples/semantic-composition/packages/responsive-explainer/README.md +16 -9
  41. package/examples/semantic-composition/packages/responsive-explainer/src/activation.ts +12 -12
  42. package/examples/semantic-composition/packages/responsive-explainer/src/render.ts +5 -5
  43. package/examples/semantic-composition/packages/sound-styles/README.md +36 -0
  44. package/examples/semantic-composition/packages/sound-styles/package.json +25 -0
  45. package/examples/semantic-composition/packages/sound-styles/src/activation.ts +53 -0
  46. package/examples/semantic-composition/packages/sound-styles/src/render.ts +14 -0
  47. package/examples/semantic-composition/packages/sound-styles/tsconfig.json +18 -0
  48. package/package.json +35 -15
  49. package/packages/audio-track/README.md +20 -13
  50. package/packages/audio-track/package.json +1 -1
  51. package/packages/audio-track/src/component.ts +4 -4
  52. package/packages/audio-track/src/fragment.ts +6 -5
  53. package/packages/audio-track/src/index.ts +1 -1
  54. package/packages/audio-track/src/manifest.ts +17 -16
  55. package/packages/audio-track/src/program.ts +21 -25
  56. package/packages/audio-track/src/surface.ts +19 -18
  57. package/packages/audio-track-studio/README.md +23 -0
  58. package/packages/audio-track-studio/src/index.ts +33 -19
  59. package/packages/author-kit/README.md +1 -1
  60. package/packages/background-removal/README.md +4 -7
  61. package/packages/browser-capture/package.json +1 -1
  62. package/packages/build-result/README.md +69 -0
  63. package/packages/build-result/package.json +2 -1
  64. package/packages/build-result/src/decode.ts +12 -8
  65. package/packages/build-result/src/execution-log.ts +17 -0
  66. package/packages/build-result/src/file-reference.ts +42 -0
  67. package/packages/build-result/src/index.ts +4 -0
  68. package/packages/build-result/src/replace-file-windows.ts +58 -0
  69. package/packages/build-result/src/replace-file.ts +11 -0
  70. package/packages/build-result/src/store.ts +58 -14
  71. package/packages/build-result/src/types.ts +41 -8
  72. package/packages/build-result/src/writer.ts +12 -1
  73. package/packages/build-result-fs/README.md +23 -1
  74. package/packages/build-result-s3/README.md +5 -0
  75. package/packages/build-result-s3/src/repository.ts +23 -5
  76. package/packages/caption/README.md +61 -73
  77. package/packages/caption/package.json +3 -2
  78. package/packages/caption/src/activation.ts +2 -2
  79. package/packages/caption/src/component.ts +20 -5
  80. package/packages/caption/src/display.ts +3 -18
  81. package/packages/caption/src/fragment.ts +3 -7
  82. package/packages/caption/src/index.ts +6 -5
  83. package/packages/caption/src/manifest.ts +29 -46
  84. package/packages/caption/src/style.ts +24 -135
  85. package/packages/caption/src/surface.ts +12 -92
  86. package/packages/caption/src/temporalize.ts +18 -53
  87. package/packages/caption/src/types.ts +8 -35
  88. package/packages/caption/src/visibility.ts +23 -0
  89. package/packages/caption-fine/README.md +31 -11
  90. package/packages/caption-fine/package.json +4 -2
  91. package/packages/caption-fine/src/component.ts +3 -3
  92. package/packages/caption-fine/src/fragment.ts +7 -19
  93. package/packages/caption-fine/src/index.ts +2 -0
  94. package/packages/caption-fine/src/manifest.ts +27 -18
  95. package/packages/caption-fine/src/recipe.ts +7 -0
  96. package/packages/caption-fine/src/render.ts +62 -49
  97. package/packages/caption-fine/src/schedule.ts +39 -35
  98. package/packages/caption-fine/src/style.ts +10 -9
  99. package/packages/caption-fine/src/surface.ts +62 -35
  100. package/packages/caption-fine/src/types.ts +2 -0
  101. package/packages/caption-fine-studio/README.md +24 -2
  102. package/packages/caption-fine-studio/package.json +10 -3
  103. package/packages/caption-fine-studio/src/activation.ts +3 -3
  104. package/packages/caption-fine-studio/src/index.ts +61 -40
  105. package/packages/cli/README.md +81 -3
  106. package/packages/cli/package.json +1 -0
  107. package/packages/cli/src/arguments.ts +39 -15
  108. package/packages/cli/src/build-planning.ts +9 -3
  109. package/packages/cli/src/command.ts +7 -1
  110. package/packages/cli/src/commands/environment.ts +66 -27
  111. package/packages/cli/src/commands/execution.ts +81 -26
  112. package/packages/cli/src/commands/results.ts +7 -11
  113. package/packages/cli/src/index.ts +2 -2
  114. package/packages/cli/src/machine-view.ts +15 -5
  115. package/packages/cli/src/main.ts +38 -42
  116. package/packages/cli/src/observation.ts +7 -1
  117. package/packages/cli/src/output.ts +92 -34
  118. package/packages/cli/src/result-export.ts +22 -13
  119. package/packages/cli/src/run-file.ts +19 -8
  120. package/packages/cli/src/view.ts +64 -18
  121. package/packages/comment-sticker/README.md +3 -3
  122. package/packages/comment-sticker/package.json +1 -1
  123. package/packages/comment-sticker/src/component.ts +4 -4
  124. package/packages/comment-sticker/src/fragment.ts +5 -4
  125. package/packages/comment-sticker/src/manifest.ts +7 -6
  126. package/packages/comment-sticker/src/program.ts +9 -9
  127. package/packages/comment-sticker/src/surface.ts +6 -7
  128. package/packages/comment-sticker-studio/src/index.ts +2 -1
  129. package/packages/compiler-node/src/compiler.ts +1 -0
  130. package/packages/composition/README.md +21 -0
  131. package/packages/composition/src/audio-presentation.ts +44 -0
  132. package/packages/composition/src/index.ts +3 -0
  133. package/packages/composition/src/schema.ts +8 -3
  134. package/packages/composition/src/track.ts +15 -1
  135. package/packages/credential-store-env/README.md +1 -1
  136. package/packages/deck-track/README.md +3 -3
  137. package/packages/deck-track/package.json +1 -1
  138. package/packages/deck-track/src/component.ts +5 -5
  139. package/packages/deck-track/src/fragment.ts +6 -5
  140. package/packages/deck-track/src/lower.ts +12 -12
  141. package/packages/deck-track/src/manifest.ts +8 -7
  142. package/packages/deck-track/src/program.ts +13 -16
  143. package/packages/deck-track/src/surface.ts +9 -10
  144. package/packages/driver-node/src/driver.ts +41 -3
  145. package/packages/elevenlabs-speech/README.md +27 -0
  146. package/packages/{provider-xiaomi-mimo → elevenlabs-speech}/package.json +5 -8
  147. package/packages/elevenlabs-speech/src/activation.ts +26 -0
  148. package/packages/elevenlabs-speech/src/fragment.ts +11 -0
  149. package/packages/elevenlabs-speech/src/index.ts +111 -0
  150. package/packages/elevenlabs-speech/src/surface.ts +167 -0
  151. package/packages/endpoint-kit/README.md +24 -0
  152. package/packages/endpoint-kit/src/index.ts +7 -1
  153. package/packages/estimate/README.md +56 -3
  154. package/packages/estimate/src/program.ts +7 -8
  155. package/packages/film/README.md +12 -7
  156. package/packages/film/package.json +1 -1
  157. package/packages/film/src/component.ts +4 -4
  158. package/packages/film/src/fragment.ts +6 -5
  159. package/packages/film/src/manifest.ts +11 -10
  160. package/packages/film/src/program.ts +5 -5
  161. package/packages/film/src/recipe.ts +2 -1
  162. package/packages/film/src/surface.ts +6 -6
  163. package/packages/film-studio/package.json +1 -1
  164. package/packages/film-studio/src/index.ts +2 -2
  165. package/packages/fishaudio-speech/README.md +33 -0
  166. package/packages/fishaudio-speech/package.json +21 -0
  167. package/packages/fishaudio-speech/src/activation.ts +32 -0
  168. package/packages/fishaudio-speech/src/fragment.ts +11 -0
  169. package/packages/fishaudio-speech/src/index.ts +156 -0
  170. package/packages/fishaudio-speech/src/surface.ts +192 -0
  171. package/packages/fonts-open/README.md +5 -0
  172. package/packages/fonts-open/package.json +5 -1
  173. package/packages/fonts-open/src/studio.ts +21 -0
  174. package/packages/generation/README.md +11 -1
  175. package/packages/generation/src/mapping.ts +28 -30
  176. package/packages/gpt-image/README.md +1 -2
  177. package/packages/grok-imagine/README.md +1 -2
  178. package/packages/hyperframes/README.md +7 -3
  179. package/packages/hyperframes/src/browser-program.ts +11 -2
  180. package/packages/hyperframes/src/document.ts +15 -2
  181. package/packages/interview-emoji-reveal/README.md +4 -4
  182. package/packages/interview-emoji-reveal/package.json +1 -1
  183. package/packages/interview-emoji-reveal/src/component.ts +4 -4
  184. package/packages/interview-emoji-reveal/src/fragment.ts +6 -5
  185. package/packages/interview-emoji-reveal/src/manifest.ts +9 -8
  186. package/packages/interview-emoji-reveal/src/program.ts +14 -14
  187. package/packages/interview-emoji-reveal/src/surface.ts +8 -9
  188. package/packages/media-execution/README.md +7 -3
  189. package/packages/media-execution/src/audio-presentation.ts +23 -0
  190. package/packages/media-execution/src/execute.ts +2 -0
  191. package/packages/media-pipeline/README.md +7 -3
  192. package/packages/media-pipeline/src/audio-plan.ts +4 -1
  193. package/packages/media-pipeline/src/component.ts +16 -2
  194. package/packages/media-pipeline/src/manifest.ts +3 -1
  195. package/packages/media-pipeline/src/types.ts +2 -1
  196. package/packages/media-track/README.md +14 -47
  197. package/packages/media-track/package.json +1 -1
  198. package/packages/media-track/preview/preview.svml +5 -5
  199. package/packages/media-track/src/author.ts +12 -6
  200. package/packages/media-track/src/component.ts +10 -16
  201. package/packages/media-track/src/fragment.ts +8 -7
  202. package/packages/media-track/src/index.ts +3 -1
  203. package/packages/media-track/src/lower.ts +7 -7
  204. package/packages/media-track/src/manifest.ts +16 -38
  205. package/packages/media-track/src/program.ts +62 -67
  206. package/packages/media-track/src/sampling.ts +6 -6
  207. package/packages/media-track/src/sequence.ts +8 -8
  208. package/packages/media-track/src/surface.ts +25 -43
  209. package/packages/media-track-studio/README.md +9 -0
  210. package/packages/media-track-studio/src/index.ts +23 -13
  211. package/packages/mimo-speech/README.md +1 -1
  212. package/packages/minimax-h3/README.md +1 -1
  213. package/packages/nano-banana/README.md +1 -1
  214. package/packages/narrative/README.md +23 -1
  215. package/packages/narrative/src/identity.ts +11 -0
  216. package/packages/narrative/src/index.ts +3 -0
  217. package/packages/narrative/src/schema.ts +28 -27
  218. package/packages/narrative/src/selection.ts +52 -0
  219. package/packages/narrative/src/types.ts +2 -0
  220. package/packages/package-loader-node/README.md +30 -0
  221. package/packages/package-loader-node/package.json +5 -2
  222. package/packages/package-loader-node/src/distribution-resolution.ts +10 -3
  223. package/packages/package-loader-node/src/index.ts +1 -0
  224. package/packages/package-loader-node/src/loader.ts +12 -15
  225. package/packages/package-loader-node/src/location.ts +27 -1
  226. package/packages/package-loader-node/src/module-scope.ts +183 -0
  227. package/packages/package-loader-node/src/types.ts +4 -0
  228. package/packages/performance/README.md +83 -0
  229. package/packages/performance/package.json +30 -0
  230. package/packages/performance/src/activation.ts +11 -0
  231. package/packages/performance/src/component.ts +46 -0
  232. package/packages/performance/src/index.ts +6 -0
  233. package/packages/performance/src/manifest.ts +53 -0
  234. package/packages/{media-track/src/performance.ts → performance/src/media.ts} +9 -10
  235. package/packages/performance/src/program.ts +51 -0
  236. package/packages/performance/src/style.ts +35 -0
  237. package/packages/performance/src/surface.ts +99 -0
  238. package/packages/performance-studio/README.md +25 -0
  239. package/packages/performance-studio/package.json +21 -0
  240. package/packages/performance-studio/src/activation.ts +6 -0
  241. package/packages/performance-studio/src/index.ts +84 -0
  242. package/packages/program-space/README.md +9 -18
  243. package/packages/program-space/src/activation.ts +1 -5
  244. package/packages/program-space/src/index.ts +2 -11
  245. package/packages/program-space/src/surface.ts +1 -20
  246. package/packages/project-context-node/README.md +20 -0
  247. package/packages/project-context-node/package.json +10 -0
  248. package/packages/project-context-node/src/index.ts +3 -0
  249. package/packages/{cli → project-context-node}/src/runtime-selection.ts +7 -2
  250. package/packages/provider-hyperframes-local/README.md +43 -15
  251. package/packages/provider-hyperframes-local/src/activation.ts +4 -3
  252. package/packages/provider-hyperframes-local/src/capture-process.ts +26 -4
  253. package/packages/provider-hyperframes-local/src/capture.ts +96 -26
  254. package/packages/provider-hyperframes-local/src/concurrency.ts +86 -0
  255. package/packages/provider-hyperframes-local/src/opaque-capture.ts +92 -0
  256. package/packages/provider-hyperframes-local/src/options.ts +2 -0
  257. package/packages/provider-hyperframes-local/src/program.ts +6 -3
  258. package/packages/provider-hyperframes-local/src/progress.ts +53 -0
  259. package/packages/provider-hyperframes-local/src/provider.ts +18 -5
  260. package/packages/provider-hyperframes-local/src/render.ts +48 -7
  261. package/packages/provider-hypihub/README.md +48 -17
  262. package/packages/provider-hypihub/package.json +2 -0
  263. package/packages/provider-hypihub/src/mapping.ts +31 -7
  264. package/packages/provider-hypihub/src/oauth.ts +6 -0
  265. package/packages/provider-hypihub/src/provider.ts +25 -13
  266. package/packages/provider-hypihub/src/routes.ts +1 -2
  267. package/packages/provider-hypihub/src/upload.ts +2 -0
  268. package/packages/provider-media-local/README.md +3 -4
  269. package/packages/provider-whisperx-local/README.md +57 -0
  270. package/packages/provider-whisperx-local/src/program.ts +1 -0
  271. package/packages/provider-whisperx-local/src/provider.ts +3 -0
  272. package/packages/ranking/README.md +8 -6
  273. package/packages/ranking/package.json +1 -1
  274. package/packages/ranking/src/component.ts +6 -6
  275. package/packages/ranking/src/fragment.ts +11 -7
  276. package/packages/ranking/src/manifest.ts +21 -14
  277. package/packages/ranking/src/render.ts +33 -35
  278. package/packages/ranking/src/schedule.ts +16 -19
  279. package/packages/ranking/src/surface.ts +12 -13
  280. package/packages/ranking-studio/README.md +29 -0
  281. package/packages/ranking-studio/package.json +8 -3
  282. package/packages/ranking-studio/src/index.ts +71 -12
  283. package/packages/render-hyperframes/README.md +7 -7
  284. package/packages/render-hyperframes/package.json +1 -1
  285. package/packages/render-hyperframes/src/component.ts +11 -1
  286. package/packages/render-hyperframes/src/fragment.ts +7 -4
  287. package/packages/render-hyperframes/src/manifest.ts +4 -4
  288. package/packages/render-hyperframes/src/surface.ts +6 -6
  289. package/packages/runtime/README.md +8 -1
  290. package/packages/runtime/src/catalog.ts +2 -0
  291. package/packages/runtime/src/execution.ts +15 -4
  292. package/packages/runtime/src/index.ts +2 -1
  293. package/packages/runtime/src/log.ts +51 -0
  294. package/packages/runtime/src/types.ts +5 -8
  295. package/packages/runtime-host-node/README.md +30 -0
  296. package/packages/runtime-host-node/package.json +1 -0
  297. package/packages/runtime-host-node/src/index.ts +25 -9
  298. package/packages/runtime-host-node/src/packages.ts +47 -50
  299. package/packages/runtime-kit/README.md +5 -0
  300. package/packages/runtime-kit/src/index.ts +3 -1
  301. package/packages/runtime-local/README.md +111 -4
  302. package/packages/runtime-local/src/config.ts +202 -56
  303. package/packages/runtime-local/src/control.ts +9 -1
  304. package/packages/runtime-local/src/executor.ts +66 -0
  305. package/packages/runtime-local/src/host.ts +37 -17
  306. package/packages/runtime-local/src/log.ts +48 -0
  307. package/packages/runtime-local/src/programs.ts +41 -25
  308. package/packages/runtime-local/src/result-writer.ts +2 -0
  309. package/packages/runtime-local/src/runtime.ts +10 -6
  310. package/packages/runtime-local/src/supervisor.ts +142 -0
  311. package/packages/runtime-local/src/types.ts +9 -2
  312. package/packages/runtime-local/src/worker-process.ts +7 -24
  313. package/packages/runtime-local/src/worker.ts +55 -83
  314. package/packages/screen-overlay/README.md +3 -3
  315. package/packages/screen-overlay/package.json +1 -1
  316. package/packages/screen-overlay/src/component.ts +3 -3
  317. package/packages/screen-overlay/src/fragment.ts +5 -4
  318. package/packages/screen-overlay/src/manifest.ts +7 -6
  319. package/packages/screen-overlay/src/program.ts +9 -9
  320. package/packages/screen-overlay/src/surface.ts +6 -7
  321. package/packages/script/README.md +37 -2
  322. package/packages/script/src/edit.ts +123 -73
  323. package/packages/script/src/lexical.ts +40 -6
  324. package/packages/script/src/manifest.ts +2 -2
  325. package/packages/script/src/narrative.ts +1 -0
  326. package/packages/script/src/parser.ts +36 -8
  327. package/packages/script/src/surface.ts +4 -3
  328. package/packages/script/src/types.ts +5 -1
  329. package/packages/script-studio/package.json +9 -3
  330. package/packages/script-studio/src/index.ts +2 -0
  331. package/packages/script-studio/src/projection.ts +105 -0
  332. package/packages/seedance/README.md +30 -1
  333. package/packages/seedance/src/index.ts +18 -7
  334. package/packages/seedance/src/surface.ts +17 -4
  335. package/packages/seedance-kits/README.md +10 -2
  336. package/packages/seedance-kits/kits/speaker-v1.svs +5 -5
  337. package/packages/seedream/README.md +1 -1
  338. package/packages/sound/README.md +81 -0
  339. package/packages/sound/package.json +25 -0
  340. package/packages/sound/src/activation.ts +11 -0
  341. package/packages/sound/src/component.ts +28 -0
  342. package/packages/sound/src/index.ts +5 -0
  343. package/packages/sound/src/manifest.ts +47 -0
  344. package/packages/sound/src/program.ts +84 -0
  345. package/packages/sound/src/style.ts +34 -0
  346. package/packages/sound/src/surface.ts +90 -0
  347. package/packages/sound-studio/README.md +22 -0
  348. package/packages/sound-studio/package.json +20 -0
  349. package/packages/sound-studio/src/activation.ts +6 -0
  350. package/packages/sound-studio/src/index.ts +74 -0
  351. package/packages/speech/README.md +2 -2
  352. package/packages/speech-alignment/src/align.ts +45 -21
  353. package/packages/speech-alignment/src/local.ts +8 -0
  354. package/packages/speech-alignment/src/locate.ts +1 -1
  355. package/packages/store-sqlite/README.md +7 -0
  356. package/packages/store-sqlite/src/store.ts +84 -83
  357. package/packages/studio/INSPECTOR.md +117 -0
  358. package/packages/studio/README.md +149 -14
  359. package/packages/studio/package.json +8 -7
  360. package/packages/studio/src/build-library.ts +99 -84
  361. package/packages/studio/src/companion-assembly.ts +1 -0
  362. package/packages/studio/src/domain.ts +1 -1
  363. package/packages/studio/src/fallback-companions.ts +3 -3
  364. package/packages/studio/src/feedback-server.ts +51 -0
  365. package/packages/studio/src/feedback-store.ts +66 -0
  366. package/packages/studio/src/feedback.ts +60 -0
  367. package/packages/studio/src/library-media.ts +19 -0
  368. package/packages/studio/src/parameter-values.ts +83 -1
  369. package/packages/studio/src/parameters.ts +132 -43
  370. package/packages/studio/src/preview/render.ts +5 -1
  371. package/packages/studio/src/preview/runtime-shim.ts +49 -3
  372. package/packages/studio/src/programme.ts +14 -30
  373. package/packages/studio/src/server.ts +66 -34
  374. package/packages/studio/src/shared.ts +24 -8
  375. package/packages/studio/src/snapshot.ts +21 -132
  376. package/packages/studio/src/studio-preflight.ts +3 -3
  377. package/packages/studio/src/studio-registry.ts +72 -6
  378. package/packages/studio/src/style.css +342 -190
  379. package/packages/studio/src/temporal-edit.ts +131 -0
  380. package/packages/studio/src/ui/artifact-name.ts +80 -0
  381. package/packages/studio/src/ui/artifact-preview.ts +99 -0
  382. package/packages/studio/src/ui/comments.ts +233 -0
  383. package/packages/studio/src/ui/icons.ts +10 -1
  384. package/packages/studio/src/ui/library.ts +373 -192
  385. package/packages/studio/src/ui/main.ts +291 -110
  386. package/packages/studio/src/ui/material-preview.ts +1 -1
  387. package/packages/studio/src/ui/overlay.ts +31 -26
  388. package/packages/studio/src/ui/scrub-preview.ts +108 -0
  389. package/packages/studio/src/ui/sidebar-panel.ts +31 -0
  390. package/packages/studio/src/ui/stage.ts +188 -9
  391. package/packages/studio/src/ui/timeline.ts +187 -281
  392. package/packages/studio/start.ts +19 -5
  393. package/packages/studio-adapter/README.md +151 -11
  394. package/packages/studio-adapter/src/index.ts +126 -14
  395. package/packages/temporal/README.md +6 -6
  396. package/packages/temporal/package.json +1 -1
  397. package/packages/temporal/src/component.ts +5 -6
  398. package/packages/temporal/src/index.ts +8 -6
  399. package/packages/temporal/src/location.ts +11 -11
  400. package/packages/temporal/src/projection.ts +19 -19
  401. package/packages/temporal/src/rational.ts +4 -4
  402. package/packages/temporal-markup/EDITING.md +183 -0
  403. package/packages/temporal-markup/README.md +31 -11
  404. package/packages/temporal-markup/package.json +1 -1
  405. package/packages/temporal-markup/src/index.ts +20 -25
  406. package/packages/temporal-markup/src/space.ts +10 -41
  407. package/packages/timeline/README.md +31 -0
  408. package/packages/{semantic-track → timeline}/package.json +1 -1
  409. package/packages/timeline/src/activation.ts +8 -0
  410. package/packages/timeline/src/component.ts +24 -0
  411. package/packages/timeline/src/identity.ts +60 -0
  412. package/packages/timeline/src/index.ts +6 -0
  413. package/packages/{semantic-track → timeline}/src/location.ts +20 -20
  414. package/packages/timeline/src/manifest.ts +39 -0
  415. package/packages/{semantic-track → timeline}/src/projection.ts +14 -14
  416. package/packages/timeline/src/types.ts +24 -0
  417. package/packages/timeline-author/README.md +68 -0
  418. package/packages/{speech-track → timeline-author}/package.json +6 -4
  419. package/packages/timeline-author/src/activation.ts +22 -0
  420. package/packages/{speech-track → timeline-author}/src/component.ts +15 -13
  421. package/packages/{speech-track → timeline-author}/src/fragment.ts +14 -23
  422. package/packages/timeline-author/src/index.ts +7 -0
  423. package/packages/timeline-author/src/manifest.ts +78 -0
  424. package/packages/timeline-author/src/program.ts +107 -0
  425. package/packages/timeline-author/src/surface.ts +46 -0
  426. package/packages/timeline-author/src/types.ts +6 -0
  427. package/packages/typography-track/README.md +4 -4
  428. package/packages/typography-track/package.json +1 -1
  429. package/packages/typography-track/src/component.ts +4 -4
  430. package/packages/typography-track/src/fragment.ts +4 -3
  431. package/packages/typography-track/src/manifest.ts +11 -9
  432. package/packages/typography-track/src/program.ts +15 -15
  433. package/packages/typography-track/src/surface.ts +17 -18
  434. package/packages/typography-track-studio/package.json +8 -3
  435. package/packages/typography-track-studio/src/index.ts +5 -1
  436. package/packages/video-cli/README.md +16 -4
  437. package/packages/video-cli/package.json +2 -1
  438. package/packages/video-cli/src/creation.ts +25 -9
  439. package/packages/video-cli/src/index.ts +1 -1
  440. package/packages/video-cli/src/studio-distribution.ts +2 -1
  441. package/packages/volcengine-matting/README.md +3 -3
  442. package/packages/whisperx/README.md +8 -1
  443. package/packages/workspace/src/index.ts +2 -0
  444. package/packages/workspace-fs-node/README.md +4 -1
  445. package/packages/workspace-fs-node/src/workspace.ts +3 -1
  446. package/services/whisperx/README.md +14 -0
  447. package/services/whisperx/pyproject.toml +2 -0
  448. package/services/whisperx/uv.lock +20 -20
  449. package/packages/provider-hyperframes-aws-lambda/README.md +0 -30
  450. package/packages/provider-hyperframes-aws-lambda/package.json +0 -30
  451. package/packages/provider-hyperframes-aws-lambda/src/activation.ts +0 -97
  452. package/packages/provider-hyperframes-aws-lambda/src/client.ts +0 -114
  453. package/packages/provider-hyperframes-aws-lambda/src/index.ts +0 -21
  454. package/packages/provider-hyperframes-aws-lambda/src/provider.ts +0 -522
  455. package/packages/provider-kie/README.md +0 -134
  456. package/packages/provider-kie/package.json +0 -43
  457. package/packages/provider-kie/src/activation.ts +0 -71
  458. package/packages/provider-kie/src/index.ts +0 -10
  459. package/packages/provider-kie/src/mapping.ts +0 -182
  460. package/packages/provider-kie/src/provider.ts +0 -657
  461. package/packages/provider-kie/src/routes.ts +0 -150
  462. package/packages/provider-xiaomi-mimo/README.md +0 -32
  463. package/packages/provider-xiaomi-mimo/src/activation.ts +0 -57
  464. package/packages/provider-xiaomi-mimo/src/index.ts +0 -5
  465. package/packages/provider-xiaomi-mimo/src/provider.ts +0 -214
  466. package/packages/runtime/src/environment.ts +0 -6
  467. package/packages/semantic-track/README.md +0 -21
  468. package/packages/semantic-track/src/activation.ts +0 -8
  469. package/packages/semantic-track/src/component.ts +0 -24
  470. package/packages/semantic-track/src/identity.ts +0 -58
  471. package/packages/semantic-track/src/index.ts +0 -6
  472. package/packages/semantic-track/src/manifest.ts +0 -38
  473. package/packages/semantic-track/src/types.ts +0 -26
  474. package/packages/speech-track/README.md +0 -51
  475. package/packages/speech-track/src/activation.ts +0 -19
  476. package/packages/speech-track/src/index.ts +0 -7
  477. package/packages/speech-track/src/manifest.ts +0 -73
  478. package/packages/speech-track/src/program.ts +0 -64
  479. package/packages/speech-track/src/surface.ts +0 -35
  480. package/packages/speech-track/src/types.ts +0 -6
  481. package/packages/speech-track-studio/README.md +0 -3
  482. package/packages/speech-track-studio/package.json +0 -14
  483. package/packages/speech-track-studio/src/activation.ts +0 -7
  484. package/packages/speech-track-studio/src/index.ts +0 -40
  485. /package/packages/{cli → project-context-node}/src/project-context.ts +0 -0
  486. /package/packages/{speech-track → timeline-author}/preview/Track.png +0 -0
@@ -129,7 +129,7 @@ async function audioWaveform(url: string): Promise<string | undefined> {
129
129
  }
130
130
  }
131
131
 
132
- function audioPreview(url: string): Promise<string | undefined> {
132
+ export function audioPreview(url: string): Promise<string | undefined> {
133
133
  const held = generatedAudio.get(url);
134
134
  if (held !== undefined) return held;
135
135
  const pending = audioWaveform(url).catch(() => undefined).then((waveform) => {
@@ -14,12 +14,13 @@ export type Box = {
14
14
  readonly widthPx: number; readonly heightPx: number;
15
15
  };
16
16
 
17
- export type Measure = (clipId: string) => Box | undefined;
17
+ export type Measure = (clipId: string) => (Box & { readonly stackOrder: number }) | undefined;
18
18
 
19
19
  export type Overlay = {
20
20
  readonly element: SVGSVGElement;
21
21
  /** The topmost clip drawn at a point, in client coordinates. */
22
22
  hitTest(clientX: number, clientY: number): Clip | undefined;
23
+ hitsAt(clientX: number, clientY: number): readonly Clip[];
23
24
  /** Redraw against the picture, once it has mounted or moved. */
24
25
  refresh(): void;
25
26
  };
@@ -62,21 +63,24 @@ export function createOverlay(store: Store, measure: Measure): Overlay {
62
63
  element.setAttribute("class", "stage-overlay");
63
64
  element.setAttribute("preserveAspectRatio", "none");
64
65
 
65
- let live: readonly Clip[] = [];
66
+ let entities: readonly Clip[] = [];
66
67
  let canvas = { width: 1, height: 1 };
67
- let last: { snapshot: StudioSnapshot; selected: Clip | undefined; frame: number } | undefined;
68
+ let last: { snapshot: StudioSnapshot; selected: Clip | undefined } | undefined;
68
69
 
69
70
  /**
70
71
  * The box is measured off the picture rather than restated from the Source:
71
72
  * motion moves an element across its span, so only what was drawn knows where
72
73
  * it ended up. A Present that has not mounted has no box to draw.
73
74
  */
74
- const drawnBox = (clip: Clip): Box | undefined => {
75
- const ids = clip.presentId === undefined ? clip.renderIds : [clip.presentId];
76
- const boxes = ids.flatMap((id) => {
75
+ const drawnParts = (clip: Clip) => {
76
+ const ids = clip.renderIds.length > 0 ? clip.renderIds : clip.presentId === undefined ? [] : [clip.presentId];
77
+ return ids.flatMap((id) => {
77
78
  const found = measure(id);
78
79
  return found === undefined ? [] : [found];
79
80
  });
81
+ };
82
+ const drawnBox = (clip: Clip): Box | undefined => {
83
+ const boxes = drawnParts(clip);
80
84
  if (boxes.length === 0) return undefined;
81
85
  const left = Math.min(...boxes.map((box) => box.xPx));
82
86
  const top = Math.min(...boxes.map((box) => box.yPx));
@@ -87,23 +91,23 @@ export function createOverlay(store: Store, measure: Measure): Overlay {
87
91
 
88
92
  const draw = (): void => {
89
93
  if (last === undefined) return;
90
- const { snapshot, selected, frame } = last;
94
+ const { snapshot, selected } = last;
91
95
  canvas = { width: snapshot.space.canvasWidth, height: snapshot.space.canvasHeight };
92
96
  element.setAttribute("viewBox", `0 0 ${canvas.width} ${canvas.height}`);
93
97
  element.replaceChildren();
94
- // Only clips actually on screen can be picked out of the picture.
95
- live = store.clipsAt(frame);
98
+ // An editing interval can end before its visual representation disappears.
99
+ // The renderer decides which associated parts are visible at this frame.
100
+ entities = snapshot.tracks.flatMap((track) => track.clips);
96
101
 
97
102
  if (selected === undefined) return;
98
103
  const tones = intentTones(snapshot);
99
104
  const tone = tones.get(selected.authoredId);
100
- const onScreen = frame >= selected.startFrame && frame < selected.endFrameExclusive;
101
105
  const box = drawnBox(selected);
102
106
  // A Present that is not on screen at this frame was never drawn, so there
103
107
  // is nothing to put a box around.
104
108
  if (box === undefined) return;
105
109
  const group = document.createElementNS(SVG_NS, "g");
106
- group.setAttribute("class", `box${onScreen ? "" : " box-elsewhere"}${tone === undefined ? "" : ` tone-${tone}`}`);
110
+ group.setAttribute("class", `box${tone === undefined ? "" : ` tone-${tone}`}`);
107
111
 
108
112
  group.append(rect(box, "box-content"));
109
113
 
@@ -111,34 +115,35 @@ export function createOverlay(store: Store, measure: Measure): Overlay {
111
115
  label.setAttribute("x", String(box.xPx + 10));
112
116
  label.setAttribute("y", String(Math.max(28, box.yPx - 12)));
113
117
  label.setAttribute("class", "box-label");
114
- label.textContent = onScreen ? selected.display.title : `${selected.display.title} (not at this frame)`;
118
+ label.textContent = selected.display.title;
115
119
  group.append(label);
116
120
  element.append(group);
117
121
  };
118
122
 
119
- store.subscribe(({ snapshot, selection, playhead }) => {
123
+ store.subscribe(({ snapshot, selection }) => {
120
124
  last = {
121
125
  snapshot,
122
126
  selected: selection.kind === "clip" ? store.clip(selection.clipId) : undefined,
123
- frame: playhead.frame,
124
127
  };
125
128
  draw();
126
129
  });
127
130
 
128
- return {
129
- element,
130
- refresh: draw,
131
- hitTest(clientX, clientY) {
131
+ const hitsAt = (clientX: number, clientY: number): readonly Clip[] => {
132
132
  const box = element.getBoundingClientRect();
133
- if (box.width === 0 || box.height === 0) return undefined;
133
+ if (box.width === 0 || box.height === 0) return [];
134
134
  const x = (clientX - box.left) / box.width * canvas.width;
135
135
  const y = (clientY - box.top) / box.height * canvas.height;
136
- // clipsAt already returns topmost first, so the first hit is the one an
137
- // author would say they clicked on.
138
- return live.find((clip) => {
139
- const box = drawnBox(clip);
140
- return box !== undefined && inside(box, x, y);
141
- });
142
- },
136
+ // Hit individual parts, not the empty space inside a group's union box.
137
+ const hits = entities.flatMap((clip) => drawnParts(clip)
138
+ .filter((part) => inside(part, x, y))
139
+ .map((part) => ({ clip, order: part.stackOrder })))
140
+ .sort((left, right) => right.order - left.order);
141
+ return [...new Map(hits.map(({ clip }) => [clip.id, clip])).values()];
142
+ };
143
+ return {
144
+ element,
145
+ refresh: draw,
146
+ hitsAt,
147
+ hitTest: (x, y) => hitsAt(x, y)[0],
143
148
  };
144
149
  }
@@ -0,0 +1,108 @@
1
+ import type { StudioSnapshot } from "../shared.js";
2
+ import { feedbackClock } from "../feedback.js";
3
+
4
+ type PreviewWindow = Window & {
5
+ __hypitSeekFrame?: (frame: number) => Promise<boolean>;
6
+ __hypitSetMuted?: (muted: boolean) => Promise<boolean>;
7
+ };
8
+
9
+ /** One lazy, muted picture for scrubbing; it never changes the main playhead. */
10
+ export function createScrubPreview(container: HTMLElement) {
11
+ const element = document.createElement("div");
12
+ element.className = "scrub-preview";
13
+ element.hidden = true;
14
+ element.innerHTML = '<div class="scrub-preview-picture"><span>Loading frame…</span></div><div class="scrub-preview-time"></div>';
15
+ element.setAttribute("aria-hidden", "true");
16
+ const picture = element.querySelector<HTMLElement>(".scrub-preview-picture")!;
17
+ const status = picture.querySelector<HTMLElement>("span")!;
18
+ const time = element.querySelector<HTMLElement>(".scrub-preview-time")!;
19
+ container.append(element);
20
+ let iframe: HTMLIFrameElement | undefined;
21
+ let revision: number | undefined;
22
+ let ready = false;
23
+ let seeking = false;
24
+ let requested: number | undefined;
25
+ let displayed: number | undefined;
26
+
27
+ const seek = async (): Promise<void> => {
28
+ const current = iframe;
29
+ if (!ready || seeking || current === undefined || requested === undefined || requested === displayed) return;
30
+ seeking = true;
31
+ try {
32
+ while (iframe === current && requested !== undefined && requested !== displayed) {
33
+ const frame: number = requested;
34
+ const target = current.contentWindow as PreviewWindow | null;
35
+ const placed = await target?.__hypitSeekFrame?.(frame);
36
+ if (iframe !== current) return;
37
+ if (!placed) throw new Error("Frame unavailable");
38
+ displayed = frame;
39
+ if (requested === frame) {
40
+ current.style.visibility = "visible";
41
+ status.hidden = true;
42
+ element.dataset.frame = String(frame);
43
+ }
44
+ }
45
+ } catch {
46
+ if (iframe === current) status.textContent = "Frame unavailable";
47
+ } finally {
48
+ if (iframe === current) seeking = false;
49
+ }
50
+ };
51
+ const hide = (): void => { element.hidden = true; requested = undefined; };
52
+ const clear = (): void => {
53
+ hide(); iframe?.remove(); iframe = undefined; revision = undefined;
54
+ ready = false; seeking = false; displayed = undefined;
55
+ delete element.dataset.frame;
56
+ };
57
+ return {
58
+ hide, clear,
59
+ show(snapshot: StudioSnapshot, frame: number, clientX: number): void {
60
+ if (snapshot.revision !== revision) clear();
61
+ if (iframe === undefined) {
62
+ revision = snapshot.revision;
63
+ const { canvasWidth, canvasHeight } = snapshot.space;
64
+ const scale = Math.min(200 / canvasWidth, 170 / canvasHeight);
65
+ picture.style.width = `${canvasWidth * scale}px`;
66
+ picture.style.height = `${canvasHeight * scale}px`;
67
+ const created = document.createElement("iframe");
68
+ created.title = "Hovered frame";
69
+ created.tabIndex = -1;
70
+ created.setAttribute("sandbox", "allow-scripts allow-same-origin");
71
+ created.setAttribute("allow", "autoplay 'none'");
72
+ created.style.width = `${canvasWidth}px`;
73
+ created.style.height = `${canvasHeight}px`;
74
+ created.style.transform = `scale(${scale})`;
75
+ created.style.visibility = "hidden";
76
+ created.addEventListener("load", () => {
77
+ void (async () => {
78
+ const target = created.contentWindow as PreviewWindow | null;
79
+ await target?.__hypitSetMuted?.(true);
80
+ if (iframe !== created) return;
81
+ ready = true;
82
+ await seek();
83
+ })().catch(() => { if (iframe === created) status.textContent = "Frame unavailable"; });
84
+ });
85
+ created.srcdoc = snapshot.preview.srcdoc;
86
+ iframe = created;
87
+ picture.prepend(created);
88
+ }
89
+ requested = frame;
90
+ element.hidden = false;
91
+ const room = container.getBoundingClientRect();
92
+ const left = Math.max(0, Math.min(clientX - room.left - element.offsetWidth / 2, room.width - element.offsetWidth));
93
+ element.style.left = `${left}px`;
94
+ time.textContent = feedbackClock(frame * snapshot.space.frameRate.denominator / snapshot.space.frameRate.numerator);
95
+ if (frame !== displayed) {
96
+ iframe.style.visibility = "hidden";
97
+ status.textContent = "Loading frame…";
98
+ status.hidden = false;
99
+ delete element.dataset.frame;
100
+ } else {
101
+ iframe.style.visibility = "visible";
102
+ status.hidden = true;
103
+ element.dataset.frame = String(frame);
104
+ }
105
+ void seek();
106
+ },
107
+ };
108
+ }
@@ -0,0 +1,31 @@
1
+ import { icon } from "./icons.js";
2
+
3
+ /** Shared chrome; each view owns its navigation choices, toolbar and content. */
4
+ export function createSidebarPanel(mark: string, navigationLabel: string) {
5
+ const element = document.createElement("div");
6
+ element.className = "sidebar-panel";
7
+ element.innerHTML = `
8
+ <div class="sidebar-panel-heading"><span class="sidebar-panel-mark" aria-hidden="true">${icon(mark)}</span></div>
9
+ <div class="sidebar-panel-toolbar"></div>
10
+ <nav class="sidebar-panel-nav"></nav>
11
+ <div class="sidebar-panel-content"></div>`;
12
+ const navigation = element.querySelector<HTMLElement>("nav")!;
13
+ navigation.setAttribute("aria-label", navigationLabel);
14
+ return {
15
+ element, navigation,
16
+ toolbar: element.querySelector<HTMLElement>(".sidebar-panel-toolbar")!,
17
+ content: element.querySelector<HTMLElement>(".sidebar-panel-content")!,
18
+ };
19
+ }
20
+
21
+ export function sidebarItem(input: { label: string; icon: string; selected: boolean; select(): void }) {
22
+ const button = document.createElement("button");
23
+ button.type = "button";
24
+ button.className = `sidebar-panel-item${input.selected ? " active" : ""}`;
25
+ button.setAttribute("aria-label", input.label);
26
+ button.setAttribute("aria-pressed", String(input.selected));
27
+ button.title = input.label;
28
+ button.innerHTML = icon(input.icon);
29
+ button.addEventListener("click", input.select);
30
+ return button;
31
+ }
@@ -1,12 +1,21 @@
1
- import type { StudioSnapshot } from "../shared.js";
1
+ import { createArtifactPreview } from "./artifact-preview.js";
2
+ import type { StudioArtifactView, StudioSnapshot } from "../shared.js";
2
3
  import { createOverlay } from "./overlay.js";
3
4
  import { icon, setIcon } from "./icons.js";
5
+ import { createScrubPreview } from "./scrub-preview.js";
4
6
  import type { State, Store } from "./selection.js";
5
7
 
6
8
  export type Stage = {
7
9
  readonly element: HTMLElement;
8
10
  /** Start or stop playback, however the request arrived. */
9
11
  toggle(): void;
12
+ pause(): void;
13
+ /** Change picture interaction while preserving Studio's selection. */
14
+ setReviewMode(enabled: boolean): void;
15
+ /** Return from a Result artifact to the composition player. */
16
+ showComposition(): void;
17
+ openArtifact(artifact: StudioArtifactView): void;
18
+ renameArtifact(artifact: StudioArtifactView): void;
10
19
  };
11
20
 
12
21
  /**
@@ -17,19 +26,25 @@ export type Stage = {
17
26
  * drives frames, not seconds, and lands on exactly the frame the renderer would
18
27
  * photograph.
19
28
  */
20
- export function createStage(store: Store): Stage {
29
+ export function createStage(store: Store, selectedArtifact: (id: string | undefined) => void): Stage {
21
30
  const element = document.createElement("section");
22
31
  element.className = "stage";
23
32
  element.innerHTML = `
24
33
  <div class="stage-heading">
25
34
  <div class="stage-title">${icon("preview")}<strong>Preview</strong></div>
35
+ <button type="button" class="stage-return" hidden>Back to composition</button>
26
36
  </div>
27
37
  <div class="stage-viewport">
28
38
  <div class="stage-scaler">
29
39
  <iframe title="Preview" sandbox="allow-scripts allow-same-origin" allow="autoplay"></iframe>
30
40
  </div>
31
41
  </div>
42
+ <div class="stage-progress" hidden>
43
+ <input class="stage-scrubber" type="range" min="0" max="0" value="0" step="0.01" aria-label="Media time" hidden>
44
+ </div>
32
45
  <div class="stage-bar">
46
+ <span class="stage-time" hidden></span>
47
+ <div class="stage-transport">
33
48
  <button type="button" data-previous aria-label="Previous frame" title="Previous frame">
34
49
  ${icon("previous")}
35
50
  </button>
@@ -39,6 +54,7 @@ export function createStage(store: Store): Stage {
39
54
  <button type="button" data-next aria-label="Next frame" title="Next frame">
40
55
  ${icon("next")}
41
56
  </button>
57
+ </div>
42
58
  <button type="button" data-mute aria-label="Mute" class="stage-mute">
43
59
  <span data-mute-icon>${icon("volume")}</span>
44
60
  </button>
@@ -47,6 +63,7 @@ export function createStage(store: Store): Stage {
47
63
  const viewport = element.querySelector<HTMLElement>(".stage-viewport")!;
48
64
  const scaler = element.querySelector<HTMLElement>(".stage-scaler")!;
49
65
  const iframe = element.querySelector<HTMLIFrameElement>("iframe")!;
66
+ let reviewMode = false;
50
67
  /**
51
68
  * Measure a clip in the rendered picture.
52
69
  *
@@ -57,17 +74,30 @@ export function createStage(store: Store): Stage {
57
74
  * of its enter and exit, and selecting a clip lands the playhead on exactly
58
75
  * the frame where that displacement is largest.
59
76
  */
60
- const measure = (clipId: string): { xPx: number; yPx: number; widthPx: number; heightPx: number } | undefined => {
77
+ const measure = (clipId: string): { xPx: number; yPx: number; widthPx: number; heightPx: number; stackOrder: number } | undefined => {
61
78
  const document_ = iframe.contentDocument;
62
79
  if (document_ === null) return undefined;
63
80
  const present = document_.querySelector(`[data-hypit-present-id="${CSS.escape(clipId)}"]`);
81
+ if (present === null) return undefined;
82
+ const current = store.current();
83
+ if (current === undefined) return undefined;
84
+ const frameRate = fps(current.snapshot);
85
+ const start = Number(present.getAttribute("data-start"));
86
+ const end = start + Number(present.getAttribute("data-duration"));
87
+ if (current.playhead.frame < Math.round(start * frameRate)
88
+ || current.playhead.frame >= Math.round(end * frameRate)) return undefined;
64
89
  // The present spans the whole canvas; the drawn box is its frame element.
65
90
  const drawn = present?.querySelector(`[data-hypit-element-id="${CSS.escape(`${clipId}:frame`)}"]`)
66
91
  ?? present?.querySelector("[data-hypit-element-id]");
67
92
  if (drawn === null || drawn === undefined) return undefined;
93
+ for (let ancestor: Element | null = drawn; ancestor !== null; ancestor = ancestor.parentElement) {
94
+ const style = document_.defaultView!.getComputedStyle(ancestor);
95
+ if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) return undefined;
96
+ }
68
97
  const box = drawn.getBoundingClientRect();
69
98
  if (box.width === 0 && box.height === 0) return undefined;
70
- return { xPx: box.left, yPx: box.top, widthPx: box.width, heightPx: box.height };
99
+ return { xPx: box.left, yPx: box.top, widthPx: box.width, heightPx: box.height,
100
+ stackOrder: Number(document_.defaultView!.getComputedStyle(present).zIndex) || 0 };
71
101
  };
72
102
 
73
103
  // The overlay is a sibling of the iframe inside the scaler, so it inherits the
@@ -78,13 +108,45 @@ export function createStage(store: Store): Stage {
78
108
  // Clicking the picture selects whatever is drawn under the pointer, which is
79
109
  // the third way into the same selection.
80
110
  scaler.addEventListener("click", (event) => {
111
+ if (reviewMode) { toggle(); return; }
81
112
  const clip = overlay.hitTest(event.clientX, event.clientY);
82
113
  if (clip !== undefined) store.select(clip.id, "video");
83
114
  else store.clearSelection();
84
115
  });
116
+ // A broad component frame can cover smaller elements. Offer all actual
117
+ // Companion hits without special knowledge of captions or any other family.
118
+ let selectionMenu: HTMLElement | undefined;
119
+ const closeSelectionMenu = (): void => { selectionMenu?.remove(); selectionMenu = undefined; };
120
+ scaler.addEventListener("contextmenu", (event) => {
121
+ if (reviewMode) { event.preventDefault(); return; }
122
+ const hits = overlay.hitsAt(event.clientX, event.clientY);
123
+ if (hits.length === 0) return;
124
+ event.preventDefault(); stop(); closeSelectionMenu();
125
+ const menu = document.createElement("div");
126
+ menu.className = "selection-menu";
127
+ menu.setAttribute("role", "menu"); menu.setAttribute("aria-label", "Elements at this point");
128
+ for (const hit of hits) {
129
+ const option = document.createElement("button"); option.type = "button";
130
+ option.setAttribute("role", "menuitem"); option.textContent = hit.display.title;
131
+ option.title = hit.id;
132
+ option.addEventListener("click", () => { store.select(hit.id, "video"); closeSelectionMenu(); });
133
+ menu.append(option);
134
+ }
135
+ element.append(menu);
136
+ menu.style.left = `${Math.max(0, Math.min(event.clientX, window.innerWidth - menu.offsetWidth - 8))}px`;
137
+ menu.style.top = `${Math.max(0, Math.min(event.clientY, window.innerHeight - menu.offsetHeight - 8))}px`;
138
+ selectionMenu = menu; menu.querySelector("button")?.focus();
139
+ });
140
+ document.addEventListener("pointerdown", (event) => {
141
+ if (event.target instanceof Node && !selectionMenu?.contains(event.target)) closeSelectionMenu();
142
+ });
143
+ document.addEventListener("keydown", (event) => { if (event.key === "Escape") closeSelectionMenu(); });
85
144
  // The room around the picture is empty in the plainest sense.
86
145
  viewport.addEventListener("click", (event) => {
87
- if (event.target === viewport) store.clearSelection();
146
+ if (event.target === viewport) {
147
+ if (reviewMode) toggle();
148
+ else store.clearSelection();
149
+ }
88
150
  });
89
151
  const play = element.querySelector<HTMLButtonElement>("[data-play]")!;
90
152
  const previous = element.querySelector<HTMLButtonElement>("[data-previous]")!;
@@ -152,6 +214,7 @@ export function createStage(store: Store): Stage {
152
214
  };
153
215
 
154
216
  const toggle = (): void => {
217
+ if (artifactPreview.selected !== undefined) { artifactPreview.toggle(); return; }
155
218
  if (state === undefined) return;
156
219
  if (playing) { stop(); return; }
157
220
  playing = true;
@@ -172,21 +235,119 @@ export function createStage(store: Store): Stage {
172
235
  };
173
236
  play.addEventListener("click", toggle);
174
237
  previous.addEventListener("click", () => {
175
- if (state !== undefined) store.seek(state.playhead.frame - 1, "video");
238
+ if (artifactPreview.selected !== undefined) artifactPreview.seek((artifactPreview.media?.currentTime ?? 0) - 5);
239
+ else if (state !== undefined) store.seek(state.playhead.frame - 1, "video");
176
240
  });
177
241
  next.addEventListener("click", () => {
178
- if (state !== undefined) store.seek(state.playhead.frame + 1, "video");
242
+ if (artifactPreview.selected !== undefined) artifactPreview.seek((artifactPreview.media?.currentTime ?? 0) + 5);
243
+ else if (state !== undefined) store.seek(state.playhead.frame + 1, "video");
179
244
  });
180
245
 
181
246
  const applyMuted = (): void => {
182
247
  setIcon(muteIcon, muted ? "muted" : "volume");
183
248
  mute.setAttribute("aria-label", muted ? "Unmute" : "Mute");
249
+ if (artifactPreview.media !== undefined) artifactPreview.media.muted = muted;
184
250
  (iframe.contentWindow as SeekWindow | null)?.__hypitSetMuted?.(muted);
185
251
  };
186
252
  mute.addEventListener("click", () => { muted = !muted; applyMuted(); });
187
253
 
254
+ const title = element.querySelector<HTMLElement>(".stage-title strong")!;
255
+ const back = element.querySelector<HTMLButtonElement>(".stage-return")!;
256
+ const scrubber = element.querySelector<HTMLInputElement>(".stage-scrubber")!;
257
+ const progress = element.querySelector<HTMLElement>(".stage-progress")!;
258
+ const scrubPreview = createScrubPreview(progress);
259
+ scrubber.addEventListener("pointermove", (event) => {
260
+ if (!reviewMode || state === undefined || scrubber.disabled || artifactPreview.selected !== undefined) return;
261
+ const box = scrubber.getBoundingClientRect();
262
+ // Native range thumbs travel between their centers, not the input edges.
263
+ const fraction = Math.max(0, Math.min(1, (event.clientX - box.left - 6) / Math.max(1, box.width - 12)));
264
+ const frame = Math.round(fraction * Math.max(0, state.snapshot.space.frameCount - 1));
265
+ scrubPreview.show(state.snapshot, frame, event.clientX);
266
+ });
267
+ scrubber.addEventListener("pointerleave", scrubPreview.hide);
268
+ scrubber.addEventListener("blur", scrubPreview.hide);
269
+ const time = element.querySelector<HTMLElement>(".stage-time")!;
270
+ const transport = element.querySelector<HTMLElement>(".stage-transport")!;
271
+ const clock = (seconds: number): string => {
272
+ const ticks = Math.max(0, Math.round(seconds * 100));
273
+ const whole = Math.floor(ticks / 100);
274
+ return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}.${String(ticks % 100).padStart(2, "0")}`;
275
+ };
276
+ const artifactPreview = createArtifactPreview(() => {
277
+ if (artifactPreview.selected === undefined) return;
278
+ const media = artifactPreview.media;
279
+ const timed = media !== undefined;
280
+ const duration = media !== undefined && Number.isFinite(media.duration) ? media.duration : 0;
281
+ transport.hidden = !timed;
282
+ mute.hidden = !timed;
283
+ progress.hidden = scrubber.hidden = !timed;
284
+ time.hidden = !timed;
285
+ play.disabled = duration <= 0 || media?.error != null;
286
+ previous.disabled = next.disabled = play.disabled;
287
+ scrubber.disabled = duration <= 0;
288
+ scrubber.step = "0.01";
289
+ scrubber.setAttribute("aria-label", "Media time");
290
+ scrubber.max = String(duration);
291
+ scrubber.value = String(media?.currentTime ?? 0);
292
+ time.textContent = `${clock(media?.currentTime ?? 0)} / ${clock(duration)}`;
293
+ setIcon(playIcon, media !== undefined && !media.paused ? "pause" : "play");
294
+ play.setAttribute("aria-label", media !== undefined && !media.paused ? "Pause" : "Play");
295
+ });
296
+ viewport.append(artifactPreview.element);
297
+ const compositionTime = (): void => {
298
+ if (state === undefined || artifactPreview.selected !== undefined) return;
299
+ const rate = fps(state.snapshot);
300
+ progress.hidden = scrubber.hidden = time.hidden = false;
301
+ scrubber.disabled = !ready;
302
+ scrubber.step = String(1 / rate);
303
+ scrubber.max = String(Math.max(0, state.snapshot.space.frameCount - 1) / rate);
304
+ scrubber.value = String(state.playhead.frame / rate);
305
+ scrubber.setAttribute("aria-label", "Composition time");
306
+ time.textContent = `${clock(state.playhead.frame / rate)} / ${clock(state.snapshot.space.frameCount / rate)}`;
307
+ };
308
+ scrubber.addEventListener("input", () => {
309
+ if (artifactPreview.selected !== undefined) artifactPreview.seek(Number(scrubber.value));
310
+ else if (state !== undefined) store.seek(Math.round(Number(scrubber.value) * fps(state.snapshot)), "video");
311
+ });
312
+
313
+ const returnToComposition = (): void => {
314
+ artifactPreview.close();
315
+ selectedArtifact(undefined);
316
+ scaler.hidden = false;
317
+ back.hidden = true;
318
+ compositionTime();
319
+ transport.hidden = mute.hidden = false;
320
+ play.disabled = previous.disabled = next.disabled = false;
321
+ previous.title = "Previous frame";
322
+ previous.setAttribute("aria-label", previous.title);
323
+ next.title = "Next frame";
324
+ next.setAttribute("aria-label", next.title);
325
+ title.textContent = "Preview";
326
+ title.removeAttribute("title");
327
+ setIcon(playIcon, "play");
328
+ play.setAttribute("aria-label", "Play");
329
+ fit();
330
+ if (state !== undefined && ready) seekPicture(state.playhead.frame);
331
+ };
332
+ back.addEventListener("click", returnToComposition);
333
+
334
+ const openArtifact = (artifact: StudioArtifactView): void => {
335
+ stop();
336
+ scaler.hidden = true;
337
+ back.hidden = false;
338
+ title.textContent = artifact.displayName ?? artifact.output;
339
+ title.title = artifact.output;
340
+ previous.title = "Back 5 seconds";
341
+ next.title = "Forward 5 seconds";
342
+ previous.setAttribute("aria-label", previous.title);
343
+ next.setAttribute("aria-label", next.title);
344
+ artifactPreview.open(artifact, muted);
345
+ selectedArtifact(artifact.id);
346
+ };
347
+
188
348
  iframe.addEventListener("load", () => {
189
349
  ready = true;
350
+ compositionTime();
190
351
  if (state !== undefined) seekPicture(state.playhead.frame);
191
352
  applyMuted();
192
353
  // The box could not be measured until now.
@@ -197,16 +358,20 @@ export function createStage(store: Store): Stage {
197
358
 
198
359
  store.subscribe((value) => {
199
360
  const first = state === undefined;
361
+ const moved = state !== undefined && (state.playhead.frame !== value.playhead.frame || state.selection !== value.selection);
200
362
  state = value;
363
+ if (moved && value.playhead.origin !== "play" && artifactPreview.selected !== undefined) returnToComposition();
201
364
  if (value.snapshot.revision !== mounted) {
365
+ scrubPreview.clear();
202
366
  mounted = value.snapshot.revision;
203
- stop();
367
+ if (artifactPreview.selected === undefined) stop();
204
368
  ready = false;
205
369
  const preview = value.snapshot.preview;
206
370
  iframe.srcdoc = preview.srcdoc;
207
371
  fit();
208
372
  }
209
373
  if (first) fit();
374
+ compositionTime();
210
375
 
211
376
  // Playing and scrubbing are different requests: one lets material run on
212
377
  // its own clock, the other moves it. Asking a decoder for a fresh seek
@@ -227,5 +392,19 @@ export function createStage(store: Store): Stage {
227
392
  if (playing) overlay.refresh();
228
393
  });
229
394
 
230
- return { element, toggle };
395
+ return { element, toggle, openArtifact,
396
+ setReviewMode(enabled) {
397
+ reviewMode = enabled;
398
+ element.classList.toggle("stage-review", enabled);
399
+ overlay.element.style.display = enabled ? "none" : "";
400
+ closeSelectionMenu();
401
+ if (!enabled) scrubPreview.clear();
402
+ },
403
+ pause() { stop(); artifactPreview.media?.pause(); },
404
+ showComposition() { if (artifactPreview.selected !== undefined) returnToComposition(); },
405
+ renameArtifact(artifact) {
406
+ const selected = artifactPreview.selected;
407
+ if (selected?.build === artifact.build && selected.output === artifact.output) title.textContent = artifact.displayName ?? artifact.output;
408
+ },
409
+ };
231
410
  }