@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
@@ -8,6 +8,7 @@ import { mountMaterialPreview } from "./material-preview.js";
8
8
  import type { State, Store } from "./selection.js";
9
9
  import { createHandle } from "./resize.js";
10
10
  import { createZoom } from "./zoom.js";
11
+ import { chooseSemanticGesture, semanticGestureSpan } from "../temporal-edit.js";
11
12
  import { applyStudioMutation } from "./writeback.js";
12
13
 
13
14
  export type Timeline = {
@@ -17,7 +18,6 @@ export type Timeline = {
17
18
  readonly fit: () => void;
18
19
  };
19
20
 
20
- const opened = new Set<string>();
21
21
  const itemMetrics = {
22
22
  // Ordinary items fill their rows. Only the semantic lane owns compact word cells.
23
23
  insetYPx: 1,
@@ -147,11 +147,20 @@ export function createTimeline(store: Store): Timeline {
147
147
  let paintFrame = 0;
148
148
  let rebuildFrame = 0;
149
149
  const readableWordLabels = new Set<string>();
150
+ let selectedWord: string | undefined;
151
+ let overlapMenu: HTMLElement | undefined;
152
+ const closeOverlapMenu = () => { overlapMenu?.remove(); overlapMenu = undefined; };
153
+ element.addEventListener("pointerdown", event => {
154
+ if (!overlapMenu?.contains(event.target as Node)) closeOverlapMenu();
155
+ });
156
+ element.addEventListener("keydown", event => { if (event.key === "Escape") closeOverlapMenu(); });
157
+ body.addEventListener("scroll", closeOverlapMenu);
150
158
  let clipNodes: readonly {
151
159
  readonly node: HTMLElement;
152
160
  readonly start: number;
153
161
  readonly end: number;
154
162
  readonly id: string;
163
+ readonly selectionGroup?: string;
155
164
  }[] = [];
156
165
  let semanticNodes: readonly {
157
166
  readonly node: HTMLElement;
@@ -221,107 +230,17 @@ export function createTimeline(store: Store): Timeline {
221
230
  readonly clip: StudioSnapshot["tracks"][number]["clips"][number];
222
231
  readonly handle: StudioEditHandle;
223
232
  readonly startFrame: number;
224
- readonly pointerAnchorIndex?: number;
225
233
  readonly pointerId: number;
226
234
  readonly node: HTMLElement;
227
235
  readonly originalLeft: string;
228
236
  readonly originalWidth: string;
229
237
  } | undefined;
230
238
 
231
- const nearestAnchorIndex = (
232
- frame: number,
233
- indices: readonly number[],
234
- preferredIndex?: number,
235
- ): number | undefined => {
236
- if (state === undefined) return undefined;
237
- const anchors = state.snapshot.semantic?.anchors ?? [];
238
- const preferredKind = preferredIndex === undefined ? undefined : anchors[preferredIndex]?.kind;
239
- return [...indices].sort((left, right) => {
240
- const leftAnchor = anchors[left]!;
241
- const rightAnchor = anchors[right]!;
242
- return Math.abs(leftAnchor.frame - frame) - Math.abs(rightAnchor.frame - frame)
243
- || Number(leftAnchor.kind !== preferredKind) - Number(rightAnchor.kind !== preferredKind)
244
- || Math.abs(left - (preferredIndex ?? left)) - Math.abs(right - (preferredIndex ?? right))
245
- || left - right;
246
- })[0];
247
- };
248
-
249
- const semanticTarget = (
250
- edit: NonNullable<typeof activeEdit>,
251
- nextFrame: number,
252
- ): { readonly kind: "selection"; readonly startAnchorId: string; readonly endAnchorId: string }
253
- | { readonly kind: "moment"; readonly anchorId: string }
254
- | undefined => {
255
- if (state === undefined || edit.handle.semantic === undefined) return undefined;
256
- const semantic = edit.handle.semantic;
257
- const anchors = state.snapshot.semantic?.anchors ?? [];
258
- if (semantic.kind === "moment") {
259
- const currentIndex = anchors.findIndex((anchor) => anchor.id === semantic.anchorId);
260
- const anchorIndex = nearestAnchorIndex(
261
- nextFrame,
262
- anchors.map((_, index) => index),
263
- currentIndex < 0 ? undefined : currentIndex,
264
- );
265
- return anchorIndex === undefined ? undefined : { kind: "moment", anchorId: anchors[anchorIndex]!.id };
266
- }
267
- const startIndex = anchors.findIndex((anchor) => anchor.id === semantic.startAnchorId);
268
- const endIndex = anchors.findIndex((anchor) => anchor.id === semantic.endAnchorId);
269
- if (startIndex < 0 || endIndex < 0 || startIndex >= endIndex) return undefined;
270
- let nextStart = startIndex;
271
- let nextEnd = endIndex;
272
- if (edit.handle.gesture === "trim-start") {
273
- const allowed = anchors.flatMap((anchor, index) =>
274
- index < endIndex && anchor.frame < anchors[endIndex]!.frame ? [index] : []);
275
- nextStart = nearestAnchorIndex(nextFrame, allowed, startIndex) ?? startIndex;
276
- } else if (edit.handle.gesture === "trim-end") {
277
- const allowed = anchors.flatMap((anchor, index) =>
278
- index > startIndex && anchor.frame > anchors[startIndex]!.frame ? [index] : []);
279
- nextEnd = nearestAnchorIndex(nextFrame, allowed, endIndex) ?? endIndex;
280
- } else if (edit.handle.gesture === "move") {
281
- const instantBoundary = edit.handle.temporal?.kind === "instant"
282
- && edit.handle.temporal.authority.kind === "semantic"
283
- && edit.handle.temporal.authority.source.kind === "selection"
284
- ? edit.handle.temporal.authority.boundary
285
- : undefined;
286
- if (instantBoundary === "start") {
287
- const allowed = anchors.flatMap((anchor, index) =>
288
- index < endIndex && anchor.frame < anchors[endIndex]!.frame ? [index] : []);
289
- nextStart = nearestAnchorIndex(nextFrame, allowed, startIndex) ?? startIndex;
290
- return { kind: "selection", startAnchorId: anchors[nextStart]!.id, endAnchorId: anchors[nextEnd]!.id };
291
- }
292
- if (instantBoundary === "end") {
293
- const allowed = anchors.flatMap((anchor, index) =>
294
- index > startIndex && anchor.frame > anchors[startIndex]!.frame ? [index] : []);
295
- nextEnd = nearestAnchorIndex(nextFrame, allowed, endIndex) ?? endIndex;
296
- return { kind: "selection", startAnchorId: anchors[nextStart]!.id, endAnchorId: anchors[nextEnd]!.id };
297
- }
298
- const pointerIndex = edit.pointerAnchorIndex ?? startIndex;
299
- const deltas = anchors.flatMap((_, index) => {
300
- const delta = index - pointerIndex;
301
- const movedStart = startIndex + delta;
302
- const movedEnd = endIndex + delta;
303
- return movedStart >= 0 && movedEnd < anchors.length
304
- && anchors[movedStart]!.frame < anchors[movedEnd]!.frame
305
- ? [delta]
306
- : [];
307
- });
308
- const targetPointer = nearestAnchorIndex(
309
- nextFrame,
310
- deltas.map((delta) => pointerIndex + delta),
311
- pointerIndex,
312
- );
313
- const delta = targetPointer === undefined ? 0 : targetPointer - pointerIndex;
314
- nextStart += delta;
315
- nextEnd += delta;
316
- } else {
317
- return undefined;
318
- }
319
- return {
320
- kind: "selection",
321
- startAnchorId: anchors[nextStart]!.id,
322
- endAnchorId: anchors[nextEnd]!.id,
323
- };
324
- };
239
+ const semanticTarget = (edit: NonNullable<typeof activeEdit>, nextFrame: number) =>
240
+ state === undefined ? undefined : chooseSemanticGesture({
241
+ anchors: state.snapshot.semantic?.anchors ?? [], handle: edit.handle,
242
+ pointerStart: edit.startFrame, pointerNow: nextFrame, frameCount: state.snapshot.space.frameCount,
243
+ });
325
244
 
326
245
  const absoluteWindowTarget = (
327
246
  edit: NonNullable<typeof activeEdit>,
@@ -354,40 +273,8 @@ export function createTimeline(store: Store): Timeline {
354
273
  ): { readonly startFrame: number; readonly endFrameExclusive: number } | undefined => {
355
274
  if (state === undefined) return undefined;
356
275
  const target = semanticTarget(edit, nextFrame);
357
- if (target?.kind === "selection" && edit.handle.semantic?.kind === "selection") {
358
- const current = state.snapshot.semantic?.selections.find((selection) => selection.id === edit.handle.semantic!.id);
359
- const nextStart = state.snapshot.semantic?.anchors.find((anchor) => anchor.id === target.startAnchorId)?.frame;
360
- const nextEnd = state.snapshot.semantic?.anchors.find((anchor) => anchor.id === target.endAnchorId)?.frame;
361
- if (current === undefined || nextStart === undefined || nextEnd === undefined) return undefined;
362
- const instantBoundary = edit.handle.temporal?.kind === "instant"
363
- && edit.handle.temporal.authority.kind === "semantic"
364
- && edit.handle.temporal.authority.source.kind === "selection"
365
- ? edit.handle.temporal.authority.boundary
366
- : undefined;
367
- if (instantBoundary === "start" || instantBoundary === "end") {
368
- const frame = instantBoundary === "start" ? nextStart : nextEnd;
369
- return { startFrame: frame, endFrameExclusive: frame + 1 };
370
- }
371
- return {
372
- startFrame: nextStart + edit.clip.startFrame - current.startFrame,
373
- endFrameExclusive: nextEnd + edit.clip.endFrameExclusive - current.endFrameExclusive,
374
- };
375
- }
376
- if (target?.kind === "moment" && edit.handle.semantic?.kind === "moment") {
377
- const current = state.snapshot.semantic?.moments.find((moment) => moment.id === edit.handle.semantic!.id);
378
- const next = state.snapshot.semantic?.anchors.find((anchor) => anchor.id === target.anchorId)?.frame;
379
- if (current === undefined || next === undefined) return undefined;
380
- const delta = next - current.frame;
381
- if (edit.handle.gesture === "trim-start") {
382
- return { startFrame: next, endFrameExclusive: edit.clip.endFrameExclusive };
383
- }
384
- if (edit.handle.gesture === "trim-end") {
385
- return { startFrame: edit.clip.startFrame, endFrameExclusive: next };
386
- }
387
- return edit.handle.moveEffect === "move-start"
388
- ? { startFrame: edit.clip.startFrame + delta, endFrameExclusive: edit.clip.endFrameExclusive }
389
- : { startFrame: edit.clip.startFrame + delta, endFrameExclusive: edit.clip.endFrameExclusive + delta };
390
- }
276
+ if (target !== undefined) return semanticGestureSpan(state.snapshot.semantic?.anchors ?? [], edit.handle, target);
277
+ if (edit.handle.semantic !== undefined) return undefined;
391
278
  return edit.handle.temporal === undefined ? undefined : absoluteWindowTarget(edit, nextFrame);
392
279
  };
393
280
  lanes.addEventListener("pointerdown", (event) => {
@@ -423,7 +310,7 @@ export function createTimeline(store: Store): Timeline {
423
310
  const from = place(preview.startFrame, state.snapshot.space.frameCount, zoom.window());
424
311
  const to = place(preview.endFrameExclusive, state.snapshot.space.frameCount, zoom.window());
425
312
  activeEdit.node.style.left = `${from * 100}%`;
426
- activeEdit.node.style.width = `max(2px, calc(${Math.max(0, to - from) * 100}% - ${itemMetrics.gapPx}px))`;
313
+ activeEdit.node.style.width = `max(2px, ${Math.max(0, to - from) * 100}%)`;
427
314
  }
428
315
  return;
429
316
  }
@@ -577,18 +464,15 @@ export function createTimeline(store: Store): Timeline {
577
464
  detail: string,
578
465
  height: number,
579
466
  attached = false,
580
- hasState = false,
581
467
  ): HTMLElement => {
582
468
  const label = document.createElement("div");
583
- label.className = `track-label track-${kind}${hasState ? " track-label-has-state" : ""}`;
469
+ label.className = `track-label track-${kind}`;
584
470
  if (attached) label.classList.add("track-label-attached");
585
471
  label.style.height = `${height}px`;
586
472
  label.title = `${name} · ${detail}`;
587
473
  label.innerHTML = `${attached
588
474
  ? '<span class="track-attachment-mark" aria-hidden="true"></span>'
589
- : '<span class="track-icon"></span>'}<span class="track-copy"><strong></strong></span>${hasState
590
- ? '<span class="track-state"></span>'
591
- : ""}`;
475
+ : '<span class="track-icon"></span>'}<span class="track-copy"><strong></strong></span>`;
592
476
  if (!attached) setIcon(label.querySelector(".track-icon")!, iconName);
593
477
  label.querySelector("strong")!.textContent = name;
594
478
  return label;
@@ -608,25 +492,28 @@ export function createTimeline(store: Store): Timeline {
608
492
  return;
609
493
  }
610
494
  const presentation = snapshot.semantic.presentation;
611
- // This is one semantic ruler group, not a regular Film lane. Its three
612
- // bands share the same inner inset as ordinary items, so the first pixel
613
- // of the semantic content aligns with the first pixel of the label card.
614
- const laneHeight = presentation.lane.heightPx;
615
- const bandHeight = Math.max(1, (laneHeight - itemMetrics.insetYPx * 2) / 3);
495
+ const bandHeight = Math.max(1, (presentation.lane.heightPx - itemMetrics.insetYPx * 2) / 3);
496
+ const bands = [
497
+ { kind: "segment", height: bandHeight },
498
+ ...(snapshot.semantic.tokens.length > 0 ? [{ kind: "word", height: bandHeight }] : []),
499
+ ...(snapshot.semantic.selections.length + snapshot.semantic.moments.length > 0
500
+ ? [{ kind: "intent", height: bandHeight }] : []),
501
+ ];
502
+ const laneHeight = bands.reduce((height, band) => height + band.height, itemMetrics.insetYPx * 2);
616
503
  const label = createTrackLabel(
617
- presentation.label ?? "Semantic",
504
+ presentation.label ?? "Timeline",
618
505
  presentation.tone,
619
506
  presentation.icon,
620
- `${snapshot.semantic.segments.length} take${snapshot.semantic.segments.length === 1 ? "" : "s"}`,
507
+ `${snapshot.semantic.segments.length} segment${snapshot.semantic.segments.length === 1 ? "" : "s"}`,
621
508
  laneHeight,
622
509
  );
623
510
  label.classList.add("track-label-semantic");
511
+ label.style.height = `calc(var(--timeline-ruler-height) + ${laneHeight}px)`;
624
512
  labels.append(label);
625
513
 
626
514
  const lane = document.createElement("div");
627
515
  lane.className = `lane semantic-lane track-tone-${presentation.tone}`;
628
516
  lane.style.height = `${laneHeight}px`;
629
- lane.style.setProperty("--semantic-band-height", `${bandHeight}px`);
630
517
  const laneWidth = lanes.clientWidth;
631
518
  const nextSemanticNodes: {
632
519
  node: HTMLElement;
@@ -636,12 +523,15 @@ export function createTimeline(store: Store): Timeline {
636
523
  kind: "segment" | "word" | "selection" | "moment";
637
524
  }[] = [];
638
525
 
639
- const bands = ["segment", "word", "intent"] as const;
640
- for (const [index, kind] of bands.entries()) {
526
+ let bandTop = itemMetrics.insetYPx;
527
+ for (const { kind, height } of bands) {
641
528
  const band = document.createElement("div");
642
529
  band.className = `semantic-band semantic-band-${kind}`;
643
- band.style.top = `${itemMetrics.insetYPx + index * bandHeight}px`;
530
+ band.style.top = `${bandTop}px`;
531
+ band.style.setProperty("--semantic-band-height", `${height}px`);
532
+ band.setAttribute("aria-label", kind === "intent" ? "Selections and Moments" : kind === "word" ? "Words" : "Segments");
644
533
  lane.append(band);
534
+ bandTop += height;
645
535
  }
646
536
  const segmentBand = lane.querySelector<HTMLElement>(".semantic-band-segment")!;
647
537
  const wordBand = lane.querySelector<HTMLElement>(".semantic-band-word")!;
@@ -660,7 +550,7 @@ export function createTimeline(store: Store): Timeline {
660
550
  node.dataset.semanticSegment = segment.id;
661
551
  node.style.left = `${from * 100}%`;
662
552
  const segmentWidth = Math.max(0, to - from) * 100;
663
- node.style.width = `max(2px, calc(${segmentWidth}% - ${itemMetrics.gapPx}px))`;
553
+ node.style.width = `max(2px, ${segmentWidth}%)`;
664
554
  node.title = segment.id;
665
555
  const segmentLabel = document.createElement("span");
666
556
  segmentLabel.className = "semantic-segment-label";
@@ -700,12 +590,12 @@ export function createTimeline(store: Store): Timeline {
700
590
  word.setAttribute("aria-label", token.text);
701
591
  word.style.left = `${wordFrom * 100}%`;
702
592
  const wordWidth = Math.max(0, wordTo - wordFrom);
703
- word.style.width = `max(1px, calc(${wordWidth * 100}% - ${itemMetrics.gapPx}px))`;
593
+ word.style.width = `max(1px, ${wordWidth * 100}%)`;
704
594
  word.title = token.text;
705
595
  const text = document.createElement("span");
706
596
  text.className = "semantic-word-label";
707
597
  text.textContent = token.text;
708
- const wordWidthPx = Math.max(1, wordWidth * laneWidth - itemMetrics.gapPx);
598
+ const wordWidthPx = Math.max(1, wordWidth * laneWidth);
709
599
  const textWidthPx = measuredText(token.text);
710
600
  const contentWidthPx = Math.max(0, wordWidthPx - wordLabelPaddingPx * 2);
711
601
  const wasReadable = readableWordLabels.has(token.id);
@@ -721,6 +611,8 @@ export function createTimeline(store: Store): Timeline {
721
611
  word.append(wordContent);
722
612
  word.addEventListener("click", (event) => {
723
613
  event.stopPropagation();
614
+ store.clearSelection();
615
+ selectedWord = token.id;
724
616
  store.seek(token.startFrame, "timeline");
725
617
  });
726
618
  nextSemanticNodes.push({ node: word, id: token.id, start: token.startFrame, end: token.endFrameExclusive, kind: "word" });
@@ -736,7 +628,7 @@ export function createTimeline(store: Store): Timeline {
736
628
  node.tabIndex = 0;
737
629
  node.setAttribute("aria-label", `Selection ${selection.id}`);
738
630
  node.style.left = `${from * 100}%`;
739
- node.style.width = `max(2px, calc(${Math.max(0, to - from) * 100}% - ${itemMetrics.gapPx}px))`;
631
+ node.style.width = `max(2px, ${Math.max(0, to - from) * 100}%)`;
740
632
  node.title = selection.id;
741
633
  const selectionLabel = document.createElement("span");
742
634
  selectionLabel.className = "semantic-selection-label";
@@ -770,6 +662,33 @@ export function createTimeline(store: Store): Timeline {
770
662
  intentBand.append(node);
771
663
  nextSemanticNodes.push({ node, id: moment.id, start: moment.frame, end: moment.frame + 1, kind: "moment" });
772
664
  }
665
+ lane.addEventListener("contextmenu", event => {
666
+ const hits = nextSemanticNodes.filter(({ node }) => {
667
+ const rect = node.getBoundingClientRect();
668
+ return event.clientX >= rect.left && event.clientX < rect.right
669
+ && event.clientY >= rect.top && event.clientY < rect.bottom;
670
+ });
671
+ if (hits.length < 2) return;
672
+ event.preventDefault();
673
+ closeOverlapMenu();
674
+ const menu = document.createElement("div");
675
+ menu.className = "timeline-overlap-menu";
676
+ menu.setAttribute("role", "menu");
677
+ menu.setAttribute("aria-label", "Overlapping timeline objects");
678
+ for (const hit of [...hits].reverse()) {
679
+ const option = document.createElement("button");
680
+ option.type = "button";
681
+ option.setAttribute("role", "menuitem");
682
+ option.textContent = hit.node.getAttribute("aria-label") ?? hit.id;
683
+ option.addEventListener("click", () => { closeOverlapMenu(); hit.node.click(); });
684
+ menu.append(option);
685
+ }
686
+ element.append(menu);
687
+ menu.style.left = `${Math.max(0, Math.min(event.clientX, window.innerWidth - menu.offsetWidth - 8))}px`;
688
+ menu.style.top = `${Math.max(0, Math.min(event.clientY, window.innerHeight - menu.offsetHeight - 8))}px`;
689
+ overlapMenu = menu;
690
+ menu.querySelector("button")?.focus();
691
+ });
773
692
  rows.append(lane);
774
693
  semanticNodes = nextSemanticNodes;
775
694
  };
@@ -777,155 +696,139 @@ export function createTimeline(store: Store): Timeline {
777
696
  const buildTrack = (
778
697
  snapshot: StudioSnapshot,
779
698
  track: StudioSnapshot["tracks"][number],
780
- nextClipNodes: { node: HTMLElement; start: number; end: number; id: string }[],
699
+ nextClipNodes: { node: HTMLElement; start: number; end: number; id: string; selectionGroup?: string }[],
781
700
  attached = false,
782
701
  ): void => {
702
+ const trackBands = track.binding.bands ?? [];
703
+ const banded = trackBands.length > 0;
704
+ const layout = [
705
+ ...trackBands.filter(band => band.placement === "before"),
706
+ { id: undefined, heightPx: track.binding.lane.heightPx, display: "content" as const, tone: track.binding.tone },
707
+ ...trackBands.filter(band => band.placement === "after"),
708
+ ];
709
+ const totalHeight = layout.reduce((sum, band) => sum + band.heightPx, 0)
710
+ + (banded ? 2 * itemMetrics.insetYPx : 0);
783
711
  const tone = track.binding.tone;
784
- const freeFrom: number[] = [];
785
- const rowOf = new Map<string, number>();
786
- for (const clip of [...track.clips].sort((a, b) => b.stackOrder - a.stackOrder || a.startFrame - b.startFrame)) {
787
- let row = freeFrom.findIndex((free) => free <= clip.startFrame);
788
- if (row < 0) { row = freeFrom.length; freeFrom.push(0); }
789
- freeFrom[row] = clip.endFrameExclusive;
790
- rowOf.set(clip.id, row);
791
- }
792
- const depth = Math.max(1, freeFrom.length);
793
- const folded = depth > 1 && !opened.has(track.id);
794
- const shownRows = folded ? 1 : depth;
795
- const laneHeight = track.binding.lane.heightPx;
796
712
  const displayedItems = track.clips.length;
797
713
  const label = createTrackLabel(
798
714
  displayTrackName(track),
799
715
  tone,
800
716
  track.binding.icon,
801
717
  `${displayedItems} item${displayedItems === 1 ? "" : "s"}`,
802
- shownRows * laneHeight,
718
+ totalHeight,
803
719
  attached,
804
- depth > 1,
805
720
  );
806
721
  label.classList.add(`track-facet-${track.binding.facet}`);
807
- if (depth > 1) {
808
- const fold = document.createElement("button");
809
- fold.type = "button";
810
- fold.className = "track-fold";
811
- fold.textContent = folded ? `+${depth - 1}` : "−";
812
- fold.title = folded ? `Show ${depth} rows` : "Collapse rows";
813
- fold.setAttribute("aria-expanded", String(!folded));
814
- fold.addEventListener("click", (event) => {
815
- event.stopPropagation();
816
- if (opened.has(track.id)) opened.delete(track.id);
817
- else opened.add(track.id);
818
- build(snapshot);
819
- paint();
820
- });
821
- label.querySelector(".track-state")!.append(fold);
822
- }
823
722
  labels.append(label);
824
723
 
825
- const lane = document.createElement("div");
826
- lane.className = `lane track-tone-${tone} track-facet-${track.binding.facet}${attached ? " lane-attached" : ""}`;
827
- lane.style.height = `${shownRows * laneHeight}px`;
828
- lane.style.setProperty("--lane-height", `${laneHeight}px`);
829
- const laneWidth = lanes.clientWidth;
724
+ const trackBody = document.createElement("div");
725
+ trackBody.className = banded ? "timeline-track timeline-track-banded" : "timeline-track";
726
+ trackBody.dataset.track = track.id;
727
+ trackBody.style.height = `${totalHeight}px`;
830
728
  const materialMounts: {
831
729
  readonly target: HTMLElement;
832
730
  readonly preview: Extract<StudioSnapshot["tracks"][number]["clips"][number]["display"]["layers"][number], { readonly kind: "preview" }>["preview"];
833
731
  }[] = [];
834
- for (const clip of track.clips) {
835
- const from = place(clip.startFrame, snapshot.space.frameCount, zoom.window());
836
- const to = place(clip.endFrameExclusive, snapshot.space.frameCount, zoom.window());
837
- if (to <= 0 || from >= 1) continue;
838
- const row = rowOf.get(clip.id) ?? 0;
839
- if (folded && row > 0) continue;
840
- const node = document.createElement("button");
841
- node.type = "button";
842
- node.className = `clip clip-tone-${tone} clip-facet-${track.binding.facet} clip-chrome-${clip.presentation.chrome}`;
843
- node.classList.toggle("clip-editable", clip.editHandles.some((handle) => handle.enabled));
844
- node.dataset.clip = clip.id;
845
- node.setAttribute("aria-label", clip.display.title);
846
- node.style.left = `${from * 100}%`;
847
- node.style.width = `max(2px, calc(${Math.max(0, to - from) * 100}% - ${itemMetrics.gapPx}px))`;
848
- const visibleWidthPx = visibleItemWidth(from, to, laneWidth);
849
- node.classList.toggle("clip-preview-wide", visibleWidthPx >= 92);
850
- node.style.top = `${row * laneHeight}px`;
851
- node.title = `${clip.display.title} · ${clip.startFrame}-${clip.endFrameExclusive}f`;
852
- node.innerHTML = `<span class="clip-head"><span class="clip-name"></span><span class="clip-meta"></span></span><span class="clip-body"><span class="clip-layers" aria-hidden="true"></span><span class="clip-phases"></span></span><span class="clip-selection" aria-hidden="true"></span>`;
853
- const layers = node.querySelector<HTMLElement>(".clip-layers")!;
854
- clip.display.layers.forEach((layer, index) => {
855
- const layerNode = document.createElement("span");
856
- layerNode.className = `clip-layer clip-layer-${layer.kind} clip-layer-role-${layer.role}`;
857
- layerNode.style.zIndex = String(index + 1);
858
- if (layer.kind === "text") {
859
- layerNode.textContent = layer.text;
860
- } else {
861
- layerNode.classList.add(`clip-layer-layout-${layer.layout}`);
862
- materialMounts.push({ target: layerNode, preview: layer.preview });
732
+ for (const band of layout) {
733
+ const tone = band.tone ?? track.binding.tone;
734
+ const laneHeight = band.heightPx;
735
+ const lane = document.createElement("div");
736
+ lane.className = `lane track-tone-${tone} track-facet-${track.binding.facet}${attached ? " lane-attached" : ""}`;
737
+ lane.style.height = `${laneHeight}px`;
738
+ if (banded) lane.classList.add("track-band", `track-band-${band.display}`);
739
+ if (band.id !== undefined) lane.dataset.band = band.id;
740
+ lane.style.setProperty("--lane-height", `${laneHeight}px`);
741
+ const laneWidth = lanes.clientWidth;
742
+ // Stable order preserves Companion projection order when stack levels tie.
743
+ for (const clip of track.clips.filter(clip => clip.band === band.id).sort((a, b) => a.stackOrder - b.stackOrder)) {
744
+ const from = place(clip.startFrame, snapshot.space.frameCount, zoom.window());
745
+ const to = place(clip.endFrameExclusive, snapshot.space.frameCount, zoom.window());
746
+ if (to <= 0 || from >= 1) continue;
747
+ const node = document.createElement("button");
748
+ node.type = "button";
749
+ node.className = `clip clip-tone-${tone} clip-facet-${track.binding.facet} clip-chrome-${clip.presentation.chrome}`;
750
+ node.classList.toggle("clip-editable", clip.editHandles.some((handle) => handle.enabled));
751
+ node.dataset.clip = clip.id;
752
+ node.setAttribute("aria-label", clip.display.title);
753
+ node.style.left = `${from * 100}%`;
754
+ node.style.width = `max(2px, ${Math.max(0, to - from) * 100}%)`;
755
+ const visibleWidthPx = visibleItemWidth(from, to, laneWidth);
756
+ node.classList.toggle("clip-preview-wide", visibleWidthPx >= 92);
757
+ node.title = `${clip.display.title} · ${clip.startFrame}-${clip.endFrameExclusive}f`;
758
+ node.innerHTML = `<span class="clip-head"><span class="clip-name"></span><span class="clip-meta"></span></span><span class="clip-body"><span class="clip-layers" aria-hidden="true"></span><span class="clip-phases"></span></span><span class="clip-boundary" aria-hidden="true"></span><span class="clip-selection" aria-hidden="true"></span>`;
759
+ const layers = node.querySelector<HTMLElement>(".clip-layers")!;
760
+ clip.display.layers.forEach((layer, index) => {
761
+ const layerNode = document.createElement("span");
762
+ layerNode.className = `clip-layer clip-layer-${layer.kind} clip-layer-role-${layer.role}`;
763
+ layerNode.style.zIndex = String(index + 1);
764
+ if (layer.kind === "text") {
765
+ layerNode.textContent = layer.text;
766
+ } else {
767
+ layerNode.classList.add(`clip-layer-layout-${layer.layout}`);
768
+ materialMounts.push({ target: layerNode, preview: layer.preview });
769
+ }
770
+ layers.append(layerNode);
771
+ });
772
+ const phaseLayer = node.querySelector<HTMLElement>(".clip-phases")!;
773
+ for (const phase of clip.temporal?.phases ?? []) {
774
+ if (phase.endFrameExclusive <= clip.startFrame || phase.startFrame >= clip.endFrameExclusive) continue;
775
+ const phaseNode = document.createElement("span");
776
+ phaseNode.className = `clip-phase clip-phase-${phase.role}`;
777
+ phaseNode.style.left = `${(phase.startFrame - clip.startFrame) / Math.max(1, clip.endFrameExclusive - clip.startFrame) * 100}%`;
778
+ phaseNode.style.width = `${(phase.endFrameExclusive - phase.startFrame) / Math.max(1, clip.endFrameExclusive - clip.startFrame) * 100}%`;
779
+ phaseLayer.append(phaseNode);
863
780
  }
864
- layers.append(layerNode);
865
- });
866
- const phaseLayer = node.querySelector<HTMLElement>(".clip-phases")!;
867
- for (const phase of clip.temporal?.phases ?? []) {
868
- if (phase.endFrameExclusive <= clip.startFrame || phase.startFrame >= clip.endFrameExclusive) continue;
869
- const phaseNode = document.createElement("span");
870
- phaseNode.className = `clip-phase clip-phase-${phase.role}`;
871
- phaseNode.style.left = `${(phase.startFrame - clip.startFrame) / Math.max(1, clip.endFrameExclusive - clip.startFrame) * 100}%`;
872
- phaseNode.style.width = `${(phase.endFrameExclusive - phase.startFrame) / Math.max(1, clip.endFrameExclusive - clip.startFrame) * 100}%`;
873
- phaseLayer.append(phaseNode);
874
- }
875
- const metaText = clip.presentation.chrome === "point"
876
- ? `${(clip.startFrame / fps(snapshot)).toFixed(2)}s`
877
- : `${((clip.endFrameExclusive - clip.startFrame) / fps(snapshot)).toFixed(2)}s`;
878
- const headerLabel = node.querySelector<HTMLElement>(".clip-name")!;
879
- node.querySelector(".clip-meta")!.textContent = metaText;
880
- headerLabel.textContent = clip.display.title;
881
- node.addEventListener("pointerdown", (event) => {
882
- const rect = node.getBoundingClientRect();
883
- const edge = Math.min(8, Math.max(4, rect.width / 3));
884
- const nearStart = event.clientX - rect.left <= edge;
885
- const nearEnd = rect.right - event.clientX <= edge;
886
- const handle = clip.editHandles.find((candidate) =>
887
- candidate.enabled
888
- && ((candidate.gesture === "trim-start" && nearStart)
889
- || (candidate.gesture === "trim-end" && nearEnd)
890
- || (candidate.gesture === "move" && !nearStart && !nearEnd)));
891
- if (handle !== undefined
892
- && (handle.gesture === "move" || handle.gesture === "trim-start" || handle.gesture === "trim-end")) {
893
- event.stopPropagation();
894
- const pointerFrame = handle.coordinate === "semantic-anchor"
895
- ? rawFrameAt(event.clientX)
896
- : frameAt(event.clientX);
897
- const pointerAnchorIndex = handle.semantic !== undefined
898
- ? nearestAnchorIndex(
899
- pointerFrame,
900
- state?.snapshot.semantic?.anchors.map((_, index) => index) ?? [],
901
- )
902
- : undefined;
903
- activeEdit = {
904
- clip,
905
- handle,
906
- startFrame: pointerFrame,
907
- ...(pointerAnchorIndex === undefined ? {} : { pointerAnchorIndex }),
908
- pointerId: event.pointerId,
909
- node,
910
- originalLeft: node.style.left,
911
- originalWidth: node.style.width,
912
- };
913
- node.classList.add("editing");
914
- try { lanes.setPointerCapture(event.pointerId); } catch { /* local pointer */ }
781
+ const metaText = clip.presentation.chrome === "point"
782
+ ? `${(clip.startFrame / fps(snapshot)).toFixed(2)}s`
783
+ : `${((clip.endFrameExclusive - clip.startFrame) / fps(snapshot)).toFixed(2)}s`;
784
+ const headerLabel = node.querySelector<HTMLElement>(".clip-name")!;
785
+ node.querySelector(".clip-meta")!.textContent = metaText;
786
+ headerLabel.textContent = clip.display.title;
787
+ node.addEventListener("pointerdown", (event) => {
788
+ const rect = node.getBoundingClientRect();
789
+ const edge = Math.min(8, Math.max(4, rect.width / 3));
790
+ const nearStart = event.clientX - rect.left <= edge;
791
+ const nearEnd = rect.right - event.clientX <= edge;
792
+ const handle = clip.editHandles.find((candidate) =>
793
+ candidate.enabled
794
+ && ((candidate.gesture === "trim-start" && nearStart)
795
+ || (candidate.gesture === "trim-end" && nearEnd)
796
+ || (candidate.gesture === "move" && !nearStart && !nearEnd)));
797
+ if (handle !== undefined
798
+ && (handle.gesture === "move" || handle.gesture === "trim-start" || handle.gesture === "trim-end")) {
799
+ event.stopPropagation();
800
+ const pointerFrame = handle.coordinate === "semantic-anchor"
801
+ ? rawFrameAt(event.clientX)
802
+ : frameAt(event.clientX);
803
+ activeEdit = {
804
+ clip,
805
+ handle,
806
+ startFrame: pointerFrame,
807
+ pointerId: event.pointerId,
808
+ node,
809
+ originalLeft: node.style.left,
810
+ originalWidth: node.style.width,
811
+ };
812
+ node.classList.add("editing");
813
+ try { lanes.setPointerCapture(event.pointerId); } catch { /* local pointer */ }
814
+ store.select(clip.id, "timeline");
815
+ return;
816
+ }
915
817
  store.select(clip.id, "timeline");
916
- return;
917
- }
918
- store.select(clip.id, "timeline");
919
- });
920
- node.addEventListener("dblclick", () => store.seek(clip.startFrame, "timeline"));
921
- nextClipNodes.push({ node, start: clip.startFrame, end: clip.endFrameExclusive, id: clip.id });
922
- lane.append(node);
818
+ });
819
+ node.addEventListener("dblclick", () => store.seek(clip.startFrame, "timeline"));
820
+ nextClipNodes.push({ node, start: clip.startFrame, end: clip.endFrameExclusive, id: clip.id, ...(clip.selectionGroup === undefined ? {} : { selectionGroup: clip.selectionGroup }) });
821
+ lane.append(node);
822
+ }
823
+ trackBody.append(lane);
923
824
  }
924
- rows.append(lane);
825
+ rows.append(trackBody);
826
+ // Cached storyboards render synchronously and need connected, measurable targets.
925
827
  for (const item of materialMounts) mountMaterialPreview(item.target, item.preview);
926
828
  };
927
829
 
928
830
  const build = (snapshot: StudioSnapshot): void => {
831
+ closeOverlapMenu();
929
832
  const scrollTop = body.scrollTop;
930
833
  labels.replaceChildren();
931
834
  rows.replaceChildren();
@@ -933,15 +836,14 @@ export function createTimeline(store: Store): Timeline {
933
836
  const corner = document.createElement("div");
934
837
  corner.className = "label-corner";
935
838
  corner.textContent = "";
936
- labels.append(corner);
839
+ if (snapshot.semantic === undefined || snapshot.semantic.segments.length === 0) labels.append(corner);
937
840
  buildSemanticLane(snapshot);
938
- const nextClipNodes: { node: HTMLElement; start: number; end: number; id: string }[] = [];
841
+ const nextClipNodes: { node: HTMLElement; start: number; end: number; id: string; selectionGroup?: string }[] = [];
939
842
  for (const track of snapshot.tracks) {
940
843
  const attachedTo = track.binding.lane.attachedTo;
941
844
  if (attachedTo !== undefined) continue;
942
845
  buildTrack(snapshot, track, nextClipNodes);
943
- const slot = track.binding.lane.groupId;
944
- if (slot === undefined) continue;
846
+ const slot = track.binding.lane.groupId ?? track.binding.groupId;
945
847
  const attachments = attachedTracks(snapshot, slot, track.binding.groupId);
946
848
  for (const attachment of attachments) {
947
849
  buildTrack(snapshot, attachment, nextClipNodes, true);
@@ -967,8 +869,10 @@ export function createTimeline(store: Store): Timeline {
967
869
  playhead.classList.toggle("outside", position < 0 || position > lanes.clientWidth);
968
870
  playheadGrip.classList.toggle("outside", position < 0 || position > lanes.clientWidth);
969
871
  timelineTime.textContent = frameTimecode(snapshot, head.frame);
872
+ const chosen = selection.kind === "clip" ? store.clip(selection.clipId) : undefined;
970
873
  for (const item of clipNodes) {
971
- const selected = selection.kind === "clip" && selection.clipId === item.id;
874
+ const selected = chosen !== undefined && (chosen.id === item.id
875
+ || (chosen.selectionGroup !== undefined && chosen.selectionGroup === item.selectionGroup));
972
876
  item.node.classList.toggle("selected", selected);
973
877
  item.node.setAttribute("aria-pressed", String(selected));
974
878
  item.node.classList.toggle("live", head.frame >= item.start && head.frame < item.end);
@@ -978,7 +882,8 @@ export function createTimeline(store: Store): Timeline {
978
882
  (item.kind === "segment" || item.kind === "selection")
979
883
  && head.frame >= item.start && head.frame < item.end);
980
884
  item.node.classList.toggle("current", item.kind === "word" && head.frame >= item.start && head.frame < item.end);
981
- const selected = (item.kind === "segment"
885
+ const selected = (item.kind === "word" && selectedWord === item.id && selection.kind === "none")
886
+ || (item.kind === "segment"
982
887
  && selection.kind === "semantic-segment" && selection.segmentId === item.id)
983
888
  || (item.kind === "selection"
984
889
  && selection.kind === "semantic-selection" && selection.selectionId === item.id)
@@ -1025,6 +930,7 @@ export function createTimeline(store: Store): Timeline {
1025
930
  zoom.subscribe(scheduleBuild);
1026
931
  store.subscribe((value) => {
1027
932
  state = value;
933
+ if (value.selection.kind !== "none") selectedWord = undefined;
1028
934
  if (value.snapshot.revision !== built) {
1029
935
  built = value.snapshot.revision;
1030
936
  build(value.snapshot);