@hypit/hypit 0.1.4 → 0.1.6

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 (462) hide show
  1. package/README.md +4 -2
  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 +77 -15
  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 +8 -1
  30. package/examples/semantic-composition/chat.svml +6 -5
  31. package/examples/semantic-composition/hypit.runtime.json +1 -1
  32. package/examples/semantic-composition/packages/chat-scene/README.md +3 -3
  33. package/examples/semantic-composition/packages/chat-scene/src/activation.ts +18 -21
  34. package/examples/semantic-composition/packages/chat-scene/src/render.ts +4 -4
  35. package/examples/semantic-composition/packages/performance-styles/README.md +42 -0
  36. package/examples/semantic-composition/packages/performance-styles/package.json +25 -0
  37. package/examples/semantic-composition/packages/performance-styles/src/activation.ts +57 -0
  38. package/examples/semantic-composition/packages/performance-styles/src/render.ts +54 -0
  39. package/examples/semantic-composition/packages/performance-styles/src/studio.ts +28 -0
  40. package/examples/semantic-composition/packages/performance-styles/tsconfig.json +18 -0
  41. package/examples/semantic-composition/packages/responsive-explainer/README.md +16 -9
  42. package/examples/semantic-composition/packages/responsive-explainer/src/activation.ts +12 -12
  43. package/examples/semantic-composition/packages/responsive-explainer/src/render.ts +5 -5
  44. package/examples/semantic-composition/packages/sound-styles/README.md +36 -0
  45. package/examples/semantic-composition/packages/sound-styles/package.json +25 -0
  46. package/examples/semantic-composition/packages/sound-styles/src/activation.ts +53 -0
  47. package/examples/semantic-composition/packages/sound-styles/src/render.ts +14 -0
  48. package/examples/semantic-composition/packages/sound-styles/tsconfig.json +18 -0
  49. package/package.json +35 -15
  50. package/packages/audio-track/README.md +20 -13
  51. package/packages/audio-track/package.json +1 -1
  52. package/packages/audio-track/src/component.ts +4 -4
  53. package/packages/audio-track/src/fragment.ts +6 -5
  54. package/packages/audio-track/src/index.ts +1 -1
  55. package/packages/audio-track/src/manifest.ts +17 -16
  56. package/packages/audio-track/src/program.ts +21 -25
  57. package/packages/audio-track/src/surface.ts +19 -18
  58. package/packages/audio-track-studio/README.md +23 -0
  59. package/packages/audio-track-studio/src/index.ts +28 -17
  60. package/packages/author-kit/README.md +1 -1
  61. package/packages/background-removal/README.md +4 -7
  62. package/packages/browser-capture/package.json +1 -1
  63. package/packages/build-result/README.md +11 -0
  64. package/packages/build-result/src/decode.ts +2 -0
  65. package/packages/build-result/src/execution-log.ts +17 -0
  66. package/packages/build-result/src/index.ts +1 -0
  67. package/packages/build-result/src/store.ts +14 -1
  68. package/packages/build-result/src/types.ts +3 -0
  69. package/packages/build-result-s3/src/repository.ts +4 -0
  70. package/packages/caption/README.md +61 -73
  71. package/packages/caption/package.json +3 -2
  72. package/packages/caption/src/activation.ts +2 -2
  73. package/packages/caption/src/component.ts +20 -5
  74. package/packages/caption/src/display.ts +3 -18
  75. package/packages/caption/src/fragment.ts +3 -7
  76. package/packages/caption/src/index.ts +6 -5
  77. package/packages/caption/src/manifest.ts +29 -46
  78. package/packages/caption/src/style.ts +24 -135
  79. package/packages/caption/src/surface.ts +12 -92
  80. package/packages/caption/src/temporalize.ts +18 -53
  81. package/packages/caption/src/types.ts +8 -35
  82. package/packages/caption/src/visibility.ts +23 -0
  83. package/packages/caption-fine/README.md +21 -9
  84. package/packages/caption-fine/package.json +4 -2
  85. package/packages/caption-fine/src/component.ts +3 -3
  86. package/packages/caption-fine/src/fragment.ts +7 -19
  87. package/packages/caption-fine/src/index.ts +2 -0
  88. package/packages/caption-fine/src/manifest.ts +26 -17
  89. package/packages/caption-fine/src/recipe.ts +7 -0
  90. package/packages/caption-fine/src/render.ts +14 -13
  91. package/packages/caption-fine/src/schedule.ts +39 -35
  92. package/packages/caption-fine/src/style.ts +10 -9
  93. package/packages/caption-fine/src/surface.ts +62 -35
  94. package/packages/caption-fine/src/types.ts +2 -0
  95. package/packages/caption-fine-studio/README.md +24 -2
  96. package/packages/caption-fine-studio/package.json +3 -1
  97. package/packages/caption-fine-studio/src/activation.ts +3 -3
  98. package/packages/caption-fine-studio/src/index.ts +54 -42
  99. package/packages/cli/README.md +57 -4
  100. package/packages/cli/package.json +1 -0
  101. package/packages/cli/src/arguments.ts +25 -6
  102. package/packages/cli/src/build-planning.ts +3 -2
  103. package/packages/cli/src/command.ts +6 -0
  104. package/packages/cli/src/commands/environment.ts +28 -18
  105. package/packages/cli/src/commands/execution.ts +81 -26
  106. package/packages/cli/src/commands/results.ts +7 -11
  107. package/packages/cli/src/index.ts +2 -2
  108. package/packages/cli/src/machine-view.ts +3 -2
  109. package/packages/cli/src/main.ts +34 -42
  110. package/packages/cli/src/observation.ts +7 -1
  111. package/packages/cli/src/output.ts +65 -23
  112. package/packages/cli/src/view.ts +66 -19
  113. package/packages/comment-sticker/README.md +3 -3
  114. package/packages/comment-sticker/package.json +1 -1
  115. package/packages/comment-sticker/src/component.ts +4 -4
  116. package/packages/comment-sticker/src/fragment.ts +5 -4
  117. package/packages/comment-sticker/src/manifest.ts +7 -6
  118. package/packages/comment-sticker/src/program.ts +9 -9
  119. package/packages/comment-sticker/src/surface.ts +6 -7
  120. package/packages/composition/README.md +21 -0
  121. package/packages/composition/src/audio-presentation.ts +44 -0
  122. package/packages/composition/src/index.ts +3 -0
  123. package/packages/composition/src/schema.ts +8 -3
  124. package/packages/composition/src/track.ts +15 -1
  125. package/packages/credential-store-env/README.md +1 -1
  126. package/packages/deck-track/README.md +3 -3
  127. package/packages/deck-track/package.json +1 -1
  128. package/packages/deck-track/src/component.ts +5 -5
  129. package/packages/deck-track/src/fragment.ts +6 -5
  130. package/packages/deck-track/src/lower.ts +12 -12
  131. package/packages/deck-track/src/manifest.ts +8 -7
  132. package/packages/deck-track/src/program.ts +13 -16
  133. package/packages/deck-track/src/surface.ts +9 -10
  134. package/packages/driver-node/src/driver.ts +41 -3
  135. package/packages/elevenlabs-speech/README.md +27 -0
  136. package/packages/{provider-xiaomi-mimo → elevenlabs-speech}/package.json +5 -8
  137. package/packages/elevenlabs-speech/src/activation.ts +26 -0
  138. package/packages/elevenlabs-speech/src/fragment.ts +11 -0
  139. package/packages/elevenlabs-speech/src/index.ts +111 -0
  140. package/packages/elevenlabs-speech/src/surface.ts +167 -0
  141. package/packages/endpoint-kit/README.md +24 -0
  142. package/packages/endpoint-kit/src/index.ts +7 -1
  143. package/packages/film/README.md +12 -7
  144. package/packages/film/package.json +1 -1
  145. package/packages/film/src/component.ts +4 -4
  146. package/packages/film/src/fragment.ts +6 -5
  147. package/packages/film/src/manifest.ts +11 -10
  148. package/packages/film/src/program.ts +5 -5
  149. package/packages/film/src/recipe.ts +2 -1
  150. package/packages/film/src/surface.ts +10 -7
  151. package/packages/film-studio/package.json +1 -1
  152. package/packages/film-studio/src/index.ts +2 -2
  153. package/packages/fishaudio-speech/README.md +33 -0
  154. package/packages/fishaudio-speech/package.json +21 -0
  155. package/packages/fishaudio-speech/src/activation.ts +32 -0
  156. package/packages/fishaudio-speech/src/fragment.ts +11 -0
  157. package/packages/fishaudio-speech/src/index.ts +156 -0
  158. package/packages/fishaudio-speech/src/surface.ts +192 -0
  159. package/packages/generation/README.md +11 -1
  160. package/packages/generation/src/mapping.ts +28 -30
  161. package/packages/gpt-image/README.md +1 -2
  162. package/packages/grok-imagine/README.md +1 -2
  163. package/packages/hyperframes/README.md +7 -3
  164. package/packages/hyperframes/src/browser-program.ts +11 -2
  165. package/packages/hyperframes/src/document.ts +15 -2
  166. package/packages/interview-emoji-reveal/README.md +4 -4
  167. package/packages/interview-emoji-reveal/package.json +1 -1
  168. package/packages/interview-emoji-reveal/src/component.ts +4 -4
  169. package/packages/interview-emoji-reveal/src/fragment.ts +6 -5
  170. package/packages/interview-emoji-reveal/src/manifest.ts +9 -8
  171. package/packages/interview-emoji-reveal/src/program.ts +14 -14
  172. package/packages/interview-emoji-reveal/src/surface.ts +8 -9
  173. package/packages/media-execution/README.md +7 -3
  174. package/packages/media-execution/src/audio-presentation.ts +23 -0
  175. package/packages/media-execution/src/execute.ts +2 -0
  176. package/packages/media-pipeline/README.md +7 -3
  177. package/packages/media-pipeline/src/audio-plan.ts +4 -1
  178. package/packages/media-pipeline/src/component.ts +16 -2
  179. package/packages/media-pipeline/src/manifest.ts +3 -1
  180. package/packages/media-pipeline/src/types.ts +2 -1
  181. package/packages/media-track/README.md +14 -47
  182. package/packages/media-track/package.json +1 -1
  183. package/packages/media-track/preview/preview.svml +5 -5
  184. package/packages/media-track/src/author.ts +12 -6
  185. package/packages/media-track/src/component.ts +10 -16
  186. package/packages/media-track/src/fragment.ts +8 -7
  187. package/packages/media-track/src/index.ts +3 -1
  188. package/packages/media-track/src/lower.ts +7 -7
  189. package/packages/media-track/src/manifest.ts +16 -38
  190. package/packages/media-track/src/program.ts +62 -67
  191. package/packages/media-track/src/sampling.ts +6 -6
  192. package/packages/media-track/src/sequence.ts +8 -8
  193. package/packages/media-track/src/surface.ts +25 -43
  194. package/packages/media-track-studio/README.md +9 -0
  195. package/packages/media-track-studio/src/index.ts +16 -11
  196. package/packages/mimo-speech/README.md +1 -1
  197. package/packages/minimax-h3/README.md +1 -1
  198. package/packages/nano-banana/README.md +1 -1
  199. package/packages/narrative/README.md +23 -1
  200. package/packages/narrative/src/identity.ts +11 -0
  201. package/packages/narrative/src/index.ts +3 -0
  202. package/packages/narrative/src/schema.ts +28 -27
  203. package/packages/narrative/src/selection.ts +52 -0
  204. package/packages/narrative/src/types.ts +2 -0
  205. package/packages/package-loader-node/README.md +30 -0
  206. package/packages/package-loader-node/package.json +5 -2
  207. package/packages/package-loader-node/src/distribution-resolution.ts +10 -3
  208. package/packages/package-loader-node/src/index.ts +1 -0
  209. package/packages/package-loader-node/src/loader.ts +12 -15
  210. package/packages/package-loader-node/src/location.ts +27 -1
  211. package/packages/package-loader-node/src/module-scope.ts +183 -0
  212. package/packages/package-loader-node/src/types.ts +4 -0
  213. package/packages/performance/README.md +83 -0
  214. package/packages/performance/package.json +30 -0
  215. package/packages/performance/src/activation.ts +11 -0
  216. package/packages/performance/src/component.ts +46 -0
  217. package/packages/performance/src/index.ts +6 -0
  218. package/packages/performance/src/manifest.ts +53 -0
  219. package/packages/{media-track/src/performance.ts → performance/src/media.ts} +9 -10
  220. package/packages/performance/src/program.ts +51 -0
  221. package/packages/performance/src/style.ts +35 -0
  222. package/packages/performance/src/surface.ts +99 -0
  223. package/packages/performance-studio/README.md +25 -0
  224. package/packages/performance-studio/package.json +21 -0
  225. package/packages/performance-studio/src/activation.ts +6 -0
  226. package/packages/performance-studio/src/index.ts +84 -0
  227. package/packages/program-space/README.md +9 -18
  228. package/packages/program-space/src/activation.ts +1 -5
  229. package/packages/program-space/src/index.ts +2 -11
  230. package/packages/program-space/src/surface.ts +1 -20
  231. package/packages/project-context-node/README.md +20 -0
  232. package/packages/project-context-node/package.json +10 -0
  233. package/packages/project-context-node/src/index.ts +3 -0
  234. package/packages/{cli → project-context-node}/src/runtime-selection.ts +7 -2
  235. package/packages/provider-hyperframes-local/README.md +43 -15
  236. package/packages/provider-hyperframes-local/src/activation.ts +4 -3
  237. package/packages/provider-hyperframes-local/src/capture-process.ts +26 -4
  238. package/packages/provider-hyperframes-local/src/capture.ts +96 -26
  239. package/packages/provider-hyperframes-local/src/concurrency.ts +86 -0
  240. package/packages/provider-hyperframes-local/src/opaque-capture.ts +92 -0
  241. package/packages/provider-hyperframes-local/src/options.ts +2 -0
  242. package/packages/provider-hyperframes-local/src/program.ts +6 -3
  243. package/packages/provider-hyperframes-local/src/progress.ts +53 -0
  244. package/packages/provider-hyperframes-local/src/provider.ts +18 -5
  245. package/packages/provider-hyperframes-local/src/render.ts +48 -7
  246. package/packages/provider-hypihub/README.md +48 -17
  247. package/packages/provider-hypihub/package.json +2 -0
  248. package/packages/provider-hypihub/src/mapping.ts +31 -7
  249. package/packages/provider-hypihub/src/oauth.ts +6 -0
  250. package/packages/provider-hypihub/src/provider.ts +25 -13
  251. package/packages/provider-hypihub/src/routes.ts +1 -2
  252. package/packages/provider-hypihub/src/upload.ts +2 -0
  253. package/packages/provider-media-local/README.md +3 -4
  254. package/packages/provider-whisperx-local/src/program.ts +1 -0
  255. package/packages/provider-whisperx-local/src/provider.ts +3 -0
  256. package/packages/ranking/README.md +8 -6
  257. package/packages/ranking/package.json +1 -1
  258. package/packages/ranking/src/component.ts +6 -6
  259. package/packages/ranking/src/fragment.ts +11 -7
  260. package/packages/ranking/src/manifest.ts +21 -14
  261. package/packages/ranking/src/render.ts +22 -26
  262. package/packages/ranking/src/schedule.ts +16 -19
  263. package/packages/ranking/src/surface.ts +12 -13
  264. package/packages/ranking-studio/README.md +18 -0
  265. package/packages/ranking-studio/src/index.ts +61 -9
  266. package/packages/render-hyperframes/README.md +7 -7
  267. package/packages/render-hyperframes/package.json +1 -1
  268. package/packages/render-hyperframes/src/component.ts +11 -1
  269. package/packages/render-hyperframes/src/fragment.ts +7 -4
  270. package/packages/render-hyperframes/src/manifest.ts +4 -4
  271. package/packages/render-hyperframes/src/surface.ts +6 -6
  272. package/packages/runtime/README.md +8 -1
  273. package/packages/runtime/src/execution.ts +15 -4
  274. package/packages/runtime/src/index.ts +2 -1
  275. package/packages/runtime/src/log.ts +51 -0
  276. package/packages/runtime/src/types.ts +5 -8
  277. package/packages/runtime-host-node/README.md +30 -0
  278. package/packages/runtime-host-node/package.json +1 -0
  279. package/packages/runtime-host-node/src/index.ts +24 -9
  280. package/packages/runtime-host-node/src/packages.ts +47 -50
  281. package/packages/runtime-kit/README.md +5 -0
  282. package/packages/runtime-kit/src/index.ts +3 -1
  283. package/packages/runtime-local/README.md +109 -4
  284. package/packages/runtime-local/src/config.ts +202 -56
  285. package/packages/runtime-local/src/control.ts +9 -1
  286. package/packages/runtime-local/src/executor.ts +66 -0
  287. package/packages/runtime-local/src/host.ts +37 -17
  288. package/packages/runtime-local/src/log.ts +48 -0
  289. package/packages/runtime-local/src/programs.ts +2 -1
  290. package/packages/runtime-local/src/result-writer.ts +2 -0
  291. package/packages/runtime-local/src/runtime.ts +8 -6
  292. package/packages/runtime-local/src/supervisor.ts +142 -0
  293. package/packages/runtime-local/src/types.ts +9 -2
  294. package/packages/runtime-local/src/worker-process.ts +7 -24
  295. package/packages/runtime-local/src/worker.ts +55 -83
  296. package/packages/screen-overlay/README.md +3 -3
  297. package/packages/screen-overlay/package.json +1 -1
  298. package/packages/screen-overlay/src/component.ts +3 -3
  299. package/packages/screen-overlay/src/fragment.ts +5 -4
  300. package/packages/screen-overlay/src/manifest.ts +7 -6
  301. package/packages/screen-overlay/src/program.ts +9 -9
  302. package/packages/screen-overlay/src/surface.ts +6 -7
  303. package/packages/script/README.md +30 -5
  304. package/packages/script/src/manifest.ts +4 -3
  305. package/packages/script/src/narrative.ts +1 -0
  306. package/packages/script/src/parser.ts +45 -6
  307. package/packages/script/src/surface.ts +4 -3
  308. package/packages/script/src/types.ts +1 -1
  309. package/packages/script-studio/package.json +9 -3
  310. package/packages/script-studio/src/index.ts +2 -0
  311. package/packages/script-studio/src/projection.ts +105 -0
  312. package/packages/seedance/README.md +30 -1
  313. package/packages/seedance/src/index.ts +18 -7
  314. package/packages/seedance/src/surface.ts +17 -4
  315. package/packages/seedance-kits/README.md +7 -1
  316. package/packages/seedream/README.md +1 -1
  317. package/packages/sound/README.md +81 -0
  318. package/packages/sound/package.json +25 -0
  319. package/packages/sound/src/activation.ts +11 -0
  320. package/packages/sound/src/component.ts +28 -0
  321. package/packages/sound/src/index.ts +5 -0
  322. package/packages/sound/src/manifest.ts +47 -0
  323. package/packages/sound/src/program.ts +84 -0
  324. package/packages/sound/src/style.ts +34 -0
  325. package/packages/sound/src/surface.ts +90 -0
  326. package/packages/sound-studio/README.md +22 -0
  327. package/packages/sound-studio/package.json +20 -0
  328. package/packages/sound-studio/src/activation.ts +6 -0
  329. package/packages/sound-studio/src/index.ts +74 -0
  330. package/packages/speech/README.md +2 -2
  331. package/packages/speech-alignment/src/local.ts +8 -0
  332. package/packages/speech-alignment/src/locate.ts +1 -1
  333. package/packages/store-sqlite/README.md +7 -0
  334. package/packages/store-sqlite/src/store.ts +84 -83
  335. package/packages/studio/INSPECTOR.md +15 -2
  336. package/packages/studio/LOCALIZATION.md +98 -0
  337. package/packages/studio/README.md +101 -14
  338. package/packages/studio/locales/en.json +197 -0
  339. package/packages/studio/locales/zh-CN.json +191 -0
  340. package/packages/studio/package.json +8 -7
  341. package/packages/studio/src/companion-assembly.ts +1 -0
  342. package/packages/studio/src/domain.ts +1 -1
  343. package/packages/studio/src/fallback-companions.ts +3 -3
  344. package/packages/studio/src/feedback-server.ts +51 -0
  345. package/packages/studio/src/feedback-store.ts +66 -0
  346. package/packages/studio/src/feedback.ts +60 -0
  347. package/packages/studio/src/localization-node.ts +46 -0
  348. package/packages/studio/src/localization.ts +85 -0
  349. package/packages/studio/src/parameter-values.ts +43 -1
  350. package/packages/studio/src/parameters.ts +114 -24
  351. package/packages/studio/src/preview/render.ts +5 -1
  352. package/packages/studio/src/preview/runtime-shim.ts +49 -3
  353. package/packages/studio/src/programme.ts +14 -30
  354. package/packages/studio/src/server.ts +10 -6
  355. package/packages/studio/src/shared.ts +4 -2
  356. package/packages/studio/src/snapshot.ts +21 -132
  357. package/packages/studio/src/studio-preflight.ts +3 -3
  358. package/packages/studio/src/studio-registry.ts +50 -6
  359. package/packages/studio/src/style.css +223 -50
  360. package/packages/studio/src/ui/artifact-name.ts +8 -6
  361. package/packages/studio/src/ui/artifact-preview.ts +5 -4
  362. package/packages/studio/src/ui/code.ts +17 -16
  363. package/packages/studio/src/ui/comments.ts +234 -0
  364. package/packages/studio/src/ui/dropdown.ts +68 -0
  365. package/packages/studio/src/ui/i18n.ts +109 -0
  366. package/packages/studio/src/ui/icons.ts +6 -0
  367. package/packages/studio/src/ui/library.ts +76 -65
  368. package/packages/studio/src/ui/main.ts +223 -211
  369. package/packages/studio/src/ui/overlay.ts +12 -8
  370. package/packages/studio/src/ui/scrub-preview.ts +110 -0
  371. package/packages/studio/src/ui/stage.ts +93 -29
  372. package/packages/studio/src/ui/timeline.ts +190 -118
  373. package/packages/studio/start.ts +41 -22
  374. package/packages/studio-adapter/README.md +130 -11
  375. package/packages/studio-adapter/src/index.ts +100 -13
  376. package/packages/temporal/README.md +6 -6
  377. package/packages/temporal/package.json +1 -1
  378. package/packages/temporal/src/component.ts +5 -6
  379. package/packages/temporal/src/index.ts +8 -6
  380. package/packages/temporal/src/location.ts +11 -11
  381. package/packages/temporal/src/projection.ts +19 -19
  382. package/packages/temporal/src/rational.ts +4 -4
  383. package/packages/temporal-markup/EDITING.md +2 -2
  384. package/packages/temporal-markup/README.md +27 -11
  385. package/packages/temporal-markup/package.json +1 -1
  386. package/packages/temporal-markup/src/index.ts +20 -25
  387. package/packages/temporal-markup/src/space.ts +10 -41
  388. package/packages/timeline/README.md +31 -0
  389. package/packages/{semantic-track → timeline}/package.json +1 -1
  390. package/packages/timeline/src/activation.ts +8 -0
  391. package/packages/timeline/src/component.ts +24 -0
  392. package/packages/timeline/src/identity.ts +60 -0
  393. package/packages/timeline/src/index.ts +6 -0
  394. package/packages/{semantic-track → timeline}/src/location.ts +20 -20
  395. package/packages/timeline/src/manifest.ts +39 -0
  396. package/packages/{semantic-track → timeline}/src/projection.ts +14 -14
  397. package/packages/timeline/src/types.ts +24 -0
  398. package/packages/timeline-author/README.md +68 -0
  399. package/packages/{speech-track → timeline-author}/package.json +6 -4
  400. package/packages/timeline-author/src/activation.ts +22 -0
  401. package/packages/{speech-track → timeline-author}/src/component.ts +15 -13
  402. package/packages/{speech-track → timeline-author}/src/fragment.ts +14 -23
  403. package/packages/timeline-author/src/index.ts +7 -0
  404. package/packages/timeline-author/src/manifest.ts +78 -0
  405. package/packages/timeline-author/src/program.ts +107 -0
  406. package/packages/timeline-author/src/surface.ts +46 -0
  407. package/packages/timeline-author/src/types.ts +6 -0
  408. package/packages/typography-track/README.md +4 -4
  409. package/packages/typography-track/package.json +1 -1
  410. package/packages/typography-track/src/component.ts +4 -4
  411. package/packages/typography-track/src/fragment.ts +4 -3
  412. package/packages/typography-track/src/manifest.ts +11 -9
  413. package/packages/typography-track/src/program.ts +15 -15
  414. package/packages/typography-track/src/surface.ts +17 -18
  415. package/packages/video-cli/README.md +4 -1
  416. package/packages/video-cli/package.json +2 -1
  417. package/packages/video-cli/src/creation.ts +14 -4
  418. package/packages/video-cli/src/index.ts +1 -1
  419. package/packages/video-cli/src/studio-distribution.ts +2 -1
  420. package/packages/volcengine-matting/README.md +3 -3
  421. package/packages/whisperx/README.md +1 -1
  422. package/services/whisperx/README.md +14 -0
  423. package/services/whisperx/pyproject.toml +2 -0
  424. package/services/whisperx/uv.lock +20 -20
  425. package/packages/provider-hyperframes-aws-lambda/README.md +0 -30
  426. package/packages/provider-hyperframes-aws-lambda/package.json +0 -30
  427. package/packages/provider-hyperframes-aws-lambda/src/activation.ts +0 -97
  428. package/packages/provider-hyperframes-aws-lambda/src/client.ts +0 -114
  429. package/packages/provider-hyperframes-aws-lambda/src/index.ts +0 -21
  430. package/packages/provider-hyperframes-aws-lambda/src/provider.ts +0 -522
  431. package/packages/provider-kie/README.md +0 -134
  432. package/packages/provider-kie/package.json +0 -43
  433. package/packages/provider-kie/src/activation.ts +0 -71
  434. package/packages/provider-kie/src/index.ts +0 -10
  435. package/packages/provider-kie/src/mapping.ts +0 -182
  436. package/packages/provider-kie/src/provider.ts +0 -657
  437. package/packages/provider-kie/src/routes.ts +0 -150
  438. package/packages/provider-xiaomi-mimo/README.md +0 -32
  439. package/packages/provider-xiaomi-mimo/src/activation.ts +0 -57
  440. package/packages/provider-xiaomi-mimo/src/index.ts +0 -5
  441. package/packages/provider-xiaomi-mimo/src/provider.ts +0 -214
  442. package/packages/runtime/src/environment.ts +0 -6
  443. package/packages/semantic-track/README.md +0 -21
  444. package/packages/semantic-track/src/activation.ts +0 -8
  445. package/packages/semantic-track/src/component.ts +0 -24
  446. package/packages/semantic-track/src/identity.ts +0 -58
  447. package/packages/semantic-track/src/index.ts +0 -6
  448. package/packages/semantic-track/src/manifest.ts +0 -38
  449. package/packages/semantic-track/src/types.ts +0 -26
  450. package/packages/speech-track/README.md +0 -51
  451. package/packages/speech-track/src/activation.ts +0 -19
  452. package/packages/speech-track/src/index.ts +0 -7
  453. package/packages/speech-track/src/manifest.ts +0 -73
  454. package/packages/speech-track/src/program.ts +0 -64
  455. package/packages/speech-track/src/surface.ts +0 -35
  456. package/packages/speech-track/src/types.ts +0 -6
  457. package/packages/speech-track-studio/README.md +0 -3
  458. package/packages/speech-track-studio/package.json +0 -14
  459. package/packages/speech-track-studio/src/activation.ts +0 -7
  460. package/packages/speech-track-studio/src/index.ts +0 -40
  461. /package/packages/{cli → project-context-node}/src/project-context.ts +0 -0
  462. /package/packages/{speech-track → timeline-author}/preview/Track.png +0 -0
@@ -1,6 +1,8 @@
1
+ import { bindDropdown } from "./dropdown.js";
2
+ import { uiLabel, uiAttribute, uiText, uiAttr, userText, languageMenu, initializeI18n, type Message } from "./i18n.js";
1
3
  import type { Clip, StudioFailure, StudioInspectorDomain, StudioSnapshot } from "../shared.js";
2
4
  import type { CanonicalValue, ValueSchema } from "@hypit/protocol";
3
- import { parameterAuthorValue, parameterNumber, parameterOption, validateParameterValue } from "../parameter-values.js";
5
+ import { parameterAuthorValue, parameterControlForSchema, parameterNumber, parameterOption, parameterRecordSchema, parameterRecordVariants, validateParameterValue } from "../parameter-values.js";
4
6
  import { createCodePane } from "./code.js";
5
7
  import { icon } from "./icons.js";
6
8
  import { createLibraryPane } from "./library.js";
@@ -13,8 +15,11 @@ import { semanticGestureSpan } from "../temporal-edit.js";
13
15
  import type { SemanticTarget } from "../temporal-edit.js";
14
16
  import { applyStudioMutation } from "./writeback.js";
15
17
  import { createTimeline } from "./timeline.js";
18
+ import { createComments } from "./comments.js";
16
19
  import "../style.css";
17
20
 
21
+ await initializeI18n();
22
+
18
23
  const app = document.querySelector<HTMLElement>("#app")!;
19
24
  app.innerHTML = `
20
25
  <header class="topbar">
@@ -39,6 +44,11 @@ app.innerHTML = `
39
44
  <div class="topbar-right">
40
45
  <div class="meta" data-meta></div>
41
46
  <div class="status" data-status></div>
47
+ <div data-language-menu></div>
48
+ <div class="view-tabs" role="tablist" ${uiAttribute("aria-label", "app.studio-view")}>
49
+ <button type="button" role="tab" data-view="studio" aria-selected="true">${icon("studio")}${uiLabel("app.studio")}</button>
50
+ <button type="button" role="tab" data-view="comments" aria-selected="false">${icon("comments")}${uiLabel("app.comments")}</button>
51
+ </div>
42
52
  </div>
43
53
  </header>
44
54
  <main class="studio-shell">
@@ -47,7 +57,7 @@ app.innerHTML = `
47
57
  <div class="preview-panel" data-stage></div>
48
58
  <aside class="workspace-panel">
49
59
  <div class="pane-heading workspace-heading" data-workspace-heading>
50
- <div class="pane-title">${icon("tune")}<h2>Properties</h2></div>
60
+ <div class="pane-title">${icon("tune")}<h2>${uiLabel("inspector.properties")}</h2></div>
51
61
  </div>
52
62
  <div class="workspace-scroll">
53
63
  <section class="workspace-section">
@@ -60,11 +70,14 @@ app.innerHTML = `
60
70
  </main>
61
71
  <pre class="failure" data-failure></pre>`;
62
72
 
73
+ app.querySelector("[data-language-menu]")!.replaceWith(languageMenu());
74
+
63
75
  const store = createStore();
64
76
  const code = createCodePane();
65
77
  const stage = createStage(store, (id) => library.selectArtifact(id));
66
78
  const library = createLibraryPane(code, (artifact) => stage.openArtifact(artifact), (artifact) => stage.renameArtifact(artifact));
67
79
  const timeline = createTimeline(store);
80
+ const comments = createComments(store, stage);
68
81
  app.querySelector<HTMLElement>("[data-library]")!.append(library.element);
69
82
  app.querySelector<HTMLElement>("[data-timeline]")!.append(timeline.element);
70
83
  app.querySelector<HTMLElement>("[data-stage]")!.append(stage.element);
@@ -91,6 +104,14 @@ const workspaceHandle = createHandle({
91
104
  });
92
105
  workspaceHandle.classList.add("workspace-handle");
93
106
  upperShell.insertBefore(workspaceHandle, workspacePanel);
107
+ const commentsHandle = createHandle({
108
+ axis: "column", initial: 360, minimum: 280, invert: true,
109
+ maximum: () => Math.max(280, upperShell.clientWidth - 360),
110
+ apply: (size) => { upperShell.style.setProperty("--comments-width", `${size}px`); },
111
+ remember: "hypit-studio.comments-width",
112
+ });
113
+ commentsHandle.classList.add("comments-handle");
114
+ upperShell.append(commentsHandle, comments.element);
94
115
  shell.insertBefore(createHandle({
95
116
  axis: "row", initial: Math.round(window.innerHeight * 0.45), minimum: 220, invert: true,
96
117
  maximum: () => Math.max(260, shell.clientHeight - 260),
@@ -104,10 +125,33 @@ const project = app.querySelector<HTMLElement>("[data-project]")!;
104
125
  const status = app.querySelector<HTMLElement>("[data-status]")!;
105
126
  const failureView = app.querySelector<HTMLElement>("[data-failure]")!;
106
127
 
128
+ const changeView = (): void => {
129
+ const current = window.location.hash === "#comments" ? "comments" : "studio";
130
+ shell.dataset.view = current;
131
+ app.querySelectorAll<HTMLButtonElement>(".view-tabs [data-view]").forEach((button) => {
132
+ const selected = button.dataset.view === current;
133
+ button.setAttribute("aria-selected", String(selected)); button.tabIndex = selected ? 0 : -1;
134
+ });
135
+ comments.activate(current === "comments");
136
+ };
137
+ app.querySelectorAll<HTMLButtonElement>(".view-tabs [data-view]").forEach((button) => {
138
+ button.addEventListener("click", () => { window.location.hash = button.dataset.view!; });
139
+ button.addEventListener("keydown", (event) => {
140
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
141
+ event.preventDefault();
142
+ event.stopPropagation();
143
+ const next = button.dataset.view === "studio" ? "comments" : "studio";
144
+ window.location.hash = next;
145
+ app.querySelector<HTMLButtonElement>(`.view-tabs [data-view="${next}"]`)!.focus();
146
+ });
147
+ });
148
+ window.addEventListener("hashchange", changeView);
149
+ changeView();
150
+
107
151
  timeline.element.addEventListener("studio:write", (event) => {
108
152
  const state = (event as CustomEvent<{ readonly state?: string }>).detail.state;
109
153
  status.className = state === "error" ? "status error" : state === "saved" ? "status saved" : "status saving";
110
- status.textContent = state === "error" ? "Save failed" : state === "saved" ? "Saved" : "Saving";
154
+ uiText(status, state === "error" ? "common.save-failed" : state === "saved" ? "common.saved" : "common.saving");
111
155
  });
112
156
 
113
157
  // Program-level facts never change while a Source is being read, so they live in
@@ -117,14 +161,14 @@ function renderMeta(snapshot: StudioSnapshot): void {
117
161
  project.title = snapshot.source.path;
118
162
  // Sequence facts belong to the project/inspector, not the application chrome.
119
163
  // The timeline transport is the single persistent time readout.
120
- meta.textContent = "";
164
+ userText(meta, "");
121
165
  }
122
166
 
123
- function property(label: string, value: string, tone?: string): HTMLElement {
167
+ function property(label: Message, value: string, tone?: string): HTMLElement {
124
168
  const node = document.createElement("div");
125
169
  node.className = `property${tone === undefined ? "" : ` ${tone}`}`;
126
170
  node.innerHTML = "<span></span><strong></strong>";
127
- node.querySelector("span")!.textContent = label;
171
+ uiText(node.querySelector("span")!, label);
128
172
  node.querySelector("strong")!.textContent = value;
129
173
  node.querySelector("strong")!.title = value;
130
174
  return node;
@@ -142,6 +186,12 @@ function group(label: string, items: readonly HTMLElement[], className = ""): HT
142
186
  return node;
143
187
  }
144
188
 
189
+ function uiGroup(label: Message, items: readonly HTMLElement[]): HTMLElement {
190
+ const node = group(label, items);
191
+ uiText(node.querySelector("h3")!, label);
192
+ return node;
193
+ }
194
+
145
195
  function aspectRatio(width: number, height: number): string {
146
196
  let a = width;
147
197
  let b = height;
@@ -149,18 +199,18 @@ function aspectRatio(width: number, height: number): string {
149
199
  return `${width / a}:${height / a}`;
150
200
  }
151
201
 
152
- const domainPresentation: Readonly<Record<StudioInspectorDomain, { readonly label: string; readonly icon: string }>> = {
153
- where: { label: "Where", icon: "where" },
154
- how: { label: "How", icon: "how" },
155
- when: { label: "When", icon: "when" },
202
+ const domainPresentation: Readonly<Record<StudioInspectorDomain, { readonly label: Message; readonly icon: string }>> = {
203
+ where: { label: "inspector.where", icon: "where" },
204
+ how: { label: "inspector.how", icon: "how" },
205
+ when: { label: "inspector.when", icon: "when" },
156
206
  };
157
- const domainOrder: readonly StudioInspectorDomain[] = ["where", "how", "when"];
207
+ const domainOrder: readonly StudioInspectorDomain[] = ["where", "when", "how"];
158
208
  const inspectorDomainByEntity = new Map<string, StudioInspectorDomain>();
159
209
  const inspectorPageByEntity = new Map<string, string>();
160
210
 
161
211
  function defaultWorkspaceHeading(): void {
162
212
  workspaceHeading.className = "pane-heading workspace-heading";
163
- workspaceHeading.innerHTML = `<div class="pane-title">${icon("tune")}<h2>Properties</h2></div>`;
213
+ workspaceHeading.innerHTML = `<div class="pane-title">${icon("tune")}<h2>${uiLabel("inspector.properties")}</h2></div>`;
164
214
  }
165
215
 
166
216
  function inspectorHeading(
@@ -176,7 +226,7 @@ function inspectorHeading(
176
226
  button.type = "button";
177
227
  button.className = `inspector-domain-tab${domain === active ? " active" : ""}`;
178
228
  button.dataset.domain = domain;
179
- button.innerHTML = `${icon(presentation.icon)}<strong>${presentation.label}</strong>`;
229
+ button.innerHTML = `${icon(presentation.icon)}<strong>${uiLabel(presentation.label)}</strong>`;
180
230
  button.setAttribute("aria-pressed", String(domain === active));
181
231
  button.addEventListener("click", () => {
182
232
  inspectorDomainByEntity.set(entityId, domain);
@@ -207,7 +257,7 @@ function commitControl(entityId: string, parameter: Clip["inspector"][number], r
207
257
  void writeParameter(entityId, parameter, replacement);
208
258
  } catch (error) {
209
259
  restoreParameterControls(entityId);
210
- status.textContent = "Invalid value"; status.className = "status error";
260
+ uiText(status, "common.invalid-value"); status.className = "status error";
211
261
  status.title = error instanceof Error ? error.message : String(error);
212
262
  }
213
263
  }
@@ -230,8 +280,8 @@ document.addEventListener("keydown", (event) => {
230
280
  }, true);
231
281
 
232
282
  function selectControl(
233
- entityId: string,
234
283
  parameter: Clip["inspector"][number],
284
+ change: (value: CanonicalValue) => void,
235
285
  ): HTMLElement {
236
286
  const control = document.createElement("div");
237
287
  control.className = "parameter-select parameter-control";
@@ -286,76 +336,13 @@ function selectControl(
286
336
  }
287
337
  close(false);
288
338
  trigger.focus();
289
- commitControl(entityId, parameter, option.value);
339
+ change(option.value);
290
340
  });
291
341
  return item;
292
342
  });
293
343
  menu.append(...options);
294
344
 
295
- const close = (restoreFocus: boolean): void => {
296
- control.classList.remove("open", "open-up");
297
- trigger.setAttribute("aria-expanded", "false");
298
- menu.hidden = true;
299
- if (restoreFocus) trigger.focus();
300
- };
301
- const open = (focus: "selected" | "first" | "last" = "selected"): void => {
302
- control.classList.add("open");
303
- trigger.setAttribute("aria-expanded", "true");
304
- menu.hidden = false;
305
- control.classList.remove("open-up");
306
- const scroll = control.closest<HTMLElement>(".workspace-scroll");
307
- if (scroll !== null) {
308
- const menuBox = menu.getBoundingClientRect();
309
- const scrollBox = scroll.getBoundingClientRect();
310
- if (menuBox.bottom > scrollBox.bottom && trigger.getBoundingClientRect().top - menuBox.height >= scrollBox.top) {
311
- control.classList.add("open-up");
312
- }
313
- }
314
- const target = focus === "first" ? options[0]
315
- : focus === "last" ? options.at(-1)
316
- : options.find((option) => option.classList.contains("active")) ?? options[0];
317
- target?.focus();
318
- };
319
- const moveOptionFocus = (offset: number): void => {
320
- const current = options.indexOf(document.activeElement as HTMLButtonElement);
321
- const next = current < 0 ? 0 : (current + offset + options.length) % options.length;
322
- options[next]?.focus();
323
- };
324
-
325
- trigger.addEventListener("click", () => {
326
- if (control.classList.contains("open")) close(false);
327
- else open();
328
- });
329
- trigger.addEventListener("keydown", (event) => {
330
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
331
- event.preventDefault();
332
- event.stopPropagation();
333
- open(event.key === "ArrowDown" ? "first" : "last");
334
- } else if (event.key === "Escape" && control.classList.contains("open")) {
335
- event.preventDefault();
336
- event.stopPropagation();
337
- close(false);
338
- }
339
- });
340
- menu.addEventListener("keydown", (event) => {
341
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
342
- event.preventDefault();
343
- event.stopPropagation();
344
- moveOptionFocus(event.key === "ArrowDown" ? 1 : -1);
345
- } else if (event.key === "Home" || event.key === "End") {
346
- event.preventDefault();
347
- event.stopPropagation();
348
- options[event.key === "Home" ? 0 : options.length - 1]?.focus();
349
- } else if (event.key === "Escape") {
350
- event.preventDefault();
351
- event.stopPropagation();
352
- close(true);
353
- }
354
- });
355
- control.addEventListener("focusout", (event) => {
356
- if (event.relatedTarget instanceof Node && control.contains(event.relatedTarget)) return;
357
- close(false);
358
- });
345
+ const { close } = bindDropdown(control, trigger, menu, options);
359
346
  control.append(trigger, menu);
360
347
  return control;
361
348
  }
@@ -376,7 +363,7 @@ function colorValueControl(
376
363
  const picker = document.createElement("input");
377
364
  picker.type = "color";
378
365
  picker.className = "parameter-color-native";
379
- picker.setAttribute("aria-label", `${label} picker`);
366
+ uiAttr(picker, "aria-label", "inspector.color-picker", { label });
380
367
  picker.title = label;
381
368
  const exact = /^#[0-9a-f]{6}(?:[0-9a-f]{2})?$/iu.test(initial);
382
369
  picker.value = exact ? initial.slice(0, 7) : "#000000";
@@ -437,7 +424,8 @@ function fieldLabel(name: string): string {
437
424
 
438
425
  function blankValue(schema: ValueSchema): CanonicalValue {
439
426
  if (schema.kind === "string") return "";
440
- if (schema.kind === "number" || schema.kind === "null" || schema.kind === "literal" || schema.kind === "oneOf") return null;
427
+ if (schema.kind === "literal") return schema.value;
428
+ if (schema.kind === "number" || schema.kind === "null" || schema.kind === "oneOf") return null;
441
429
  if (schema.kind === "boolean") return false;
442
430
  if (schema.kind === "array") return [];
443
431
  if (schema.kind === "object") return Object.fromEntries(Object.entries(schema.fields)
@@ -452,31 +440,12 @@ function scalarDraftControl(
452
440
  held: CanonicalValue | undefined,
453
441
  change: (value: CanonicalValue) => void,
454
442
  ): HTMLElement {
455
- if (schema.kind === "string" && schema.format === "color") {
456
- return colorValueControl(label, typeof held === "string" ? held : "", change, true);
457
- }
458
- const input = document.createElement("input");
459
- input.className = "parameter-value parameter-structured-value";
460
- input.setAttribute("aria-label", label);
461
- input.spellcheck = false;
462
- if (schema.kind === "boolean") {
463
- input.type = "checkbox";
464
- input.checked = held === true;
465
- input.addEventListener("change", () => change(input.checked));
466
- return input;
467
- }
468
- input.type = "text";
469
- if (schema.kind === "number") input.inputMode = "decimal";
470
- input.value = held === undefined || held === null ? "" : textValue(held);
471
- input.addEventListener("input", () => {
472
- if (schema.kind === "number") {
473
- const number = Number(input.value);
474
- change(input.value.trim().length === 0 || !Number.isFinite(number) ? null : number);
475
- } else {
476
- change(input.value);
477
- }
478
- });
479
- return input;
443
+ return parameterControl("", {
444
+ id: label, label, domain: "how", section: { id: "record", label: "" },
445
+ control: parameterControlForSchema(schema) ?? "text",
446
+ ...(schema.kind === "string" && schema.enum !== undefined ? { options: schema.enum } : {}),
447
+ schema, value: held ?? null,
448
+ }, change);
480
449
  }
481
450
 
482
451
  function recordDraftControl(
@@ -490,30 +459,34 @@ function recordDraftControl(
490
459
  const fields = document.createElement("div");
491
460
  fields.className = "parameter-record-fields";
492
461
  for (const [name, field] of Object.entries(schema.fields)) {
493
- const row = document.createElement("label");
494
- row.className = "parameter-record-field";
495
- const copy = document.createElement("span");
496
- copy.textContent = fieldLabel(name);
497
- row.append(copy, scalarDraftControl(fieldLabel(name), field.schema, record[name], (next) => {
462
+ if (field.schema.kind === "literal") continue;
463
+ fields.append(scalarDraftControl(fieldLabel(name), field.schema, record[name], (next) => {
498
464
  record = { ...record, [name]: next };
499
465
  change(record);
500
466
  }));
501
- fields.append(row);
502
467
  }
503
468
  return fields;
504
469
  }
505
470
 
506
- function structuredControl(entityId: string, parameter: Clip["inspector"][number]): HTMLElement {
507
- const schema = parameter.schema;
471
+ function structuredControl(entityId: string, parameter: Clip["inspector"][number], change: (value: CanonicalValue) => void): HTMLElement {
472
+ const declaredSchema = parameter.schema;
508
473
  const shell = document.createElement("div");
509
474
  shell.className = "parameter-structured";
510
- if (schema === undefined || (schema.kind !== "array" && schema.kind !== "object")) return shell;
475
+ if (declaredSchema === undefined || (declaredSchema.kind !== "array" && declaredSchema.kind !== "object" && !parameterRecordVariants(declaredSchema).length)) return shell;
511
476
  let draft = structuredClone(parameter.value);
512
- let refreshDecisions = (): void => {};
477
+ const notice = document.createElement("div");
478
+ notice.className = "parameter-structured-notice";
479
+ notice.setAttribute("role", "status");
480
+ const save = (): void => {
481
+ const changed = !sameValue(draft, parameter.value);
482
+ notice.hidden = !changed || valid();
483
+ uiText(notice, "inspector.invalid-fields");
484
+ if (changed && valid()) change(draft);
485
+ };
513
486
 
514
487
  const valid = (): boolean => {
515
488
  try {
516
- validateParameterValue(draft, schema, parameter.label);
489
+ validateParameterValue(draft, declaredSchema, parameter.label);
517
490
  return true;
518
491
  } catch {
519
492
  return false;
@@ -522,14 +495,28 @@ function structuredControl(entityId: string, parameter: Clip["inspector"][number
522
495
 
523
496
  const render = (): void => {
524
497
  shell.replaceChildren();
498
+ const variants = parameterRecordVariants(declaredSchema);
499
+ const schema = declaredSchema.kind === "array" ? declaredSchema : parameterRecordSchema(declaredSchema, draft);
500
+ if (schema === undefined) return;
501
+ if (variants.length) {
502
+ const active = variants.find(variant => variant.schema === schema)!;
503
+ shell.append(parameterControl(entityId, {
504
+ ...parameter, control: "select", value: variants.indexOf(active),
505
+ options: variants.map((variant, index) => ({ value: index, label: textValue(variant.value) })),
506
+ }, (next) => {
507
+ if (next === variants.indexOf(active)) return;
508
+ draft = blankValue(variants[Number(next)]!.schema);
509
+ render();
510
+ save();
511
+ }));
512
+ }
525
513
  const summary = document.createElement("div");
526
514
  summary.className = "parameter-structured-summary";
527
515
  const count = document.createElement("span");
528
- count.textContent = schema.kind === "array" && Array.isArray(draft)
529
- ? `${draft.length} ${draft.length === 1 ? "item" : "items"}`
530
- : "Structured value";
516
+ if (schema.kind === "array" && Array.isArray(draft)) uiText(count, "inspector.item-count", { count: draft.length });
517
+ else uiText(count, "inspector.structured-value");
531
518
  summary.append(count);
532
- shell.append(summary);
519
+ if (schema.kind === "array") shell.append(summary);
533
520
 
534
521
  if (schema.kind === "array") {
535
522
  const list = Array.isArray(draft) ? draft : [];
@@ -542,12 +529,12 @@ function structuredControl(entityId: string, parameter: Clip["inspector"][number
542
529
  ? recordDraftControl(schema.items, item, (next) => {
543
530
  const current = Array.isArray(draft) ? draft : [];
544
531
  draft = current.map((candidate, heldIndex) => heldIndex === index ? next : candidate);
545
- refreshDecisions();
532
+ save();
546
533
  })
547
534
  : scalarDraftControl(`${parameter.label} ${index + 1}`, schema.items, item, (next) => {
548
535
  const current = Array.isArray(draft) ? draft : [];
549
536
  draft = current.map((candidate, heldIndex) => heldIndex === index ? next : candidate);
550
- refreshDecisions();
537
+ save();
551
538
  });
552
539
  const actions = document.createElement("span");
553
540
  actions.className = "parameter-list-actions";
@@ -557,28 +544,30 @@ function structuredControl(entityId: string, parameter: Clip["inspector"][number
557
544
  reordered.splice(index + offset, 0, moving!);
558
545
  draft = reordered;
559
546
  render();
547
+ save();
560
548
  };
561
549
  const up = document.createElement("button");
562
550
  up.type = "button";
563
551
  up.textContent = "↑";
564
- up.title = "Move up";
552
+ uiAttr(up, "title", "inspector.move-up");
565
553
  up.disabled = index === 0;
566
554
  up.addEventListener("click", () => move(-1));
567
555
  const down = document.createElement("button");
568
556
  down.type = "button";
569
557
  down.textContent = "↓";
570
- down.title = "Move down";
558
+ uiAttr(down, "title", "inspector.move-down");
571
559
  down.disabled = index === list.length - 1;
572
560
  down.addEventListener("click", () => move(1));
573
561
  const remove = document.createElement("button");
574
562
  remove.type = "button";
575
563
  remove.innerHTML = icon("minus");
576
- remove.title = "Remove item";
564
+ uiAttr(remove, "title", "inspector.remove-item");
577
565
  remove.disabled = schema.minItems !== undefined && list.length <= schema.minItems;
578
566
  remove.addEventListener("click", () => {
579
567
  const current = Array.isArray(draft) ? draft : [];
580
568
  draft = current.filter((_, heldIndex) => heldIndex !== index);
581
569
  render();
570
+ save();
582
571
  });
583
572
  actions.append(up, down, remove);
584
573
  row.append(editor, actions);
@@ -588,7 +577,7 @@ function structuredControl(entityId: string, parameter: Clip["inspector"][number
588
577
  } else {
589
578
  shell.append(recordDraftControl(schema, draft, (next) => {
590
579
  draft = next;
591
- refreshDecisions();
580
+ save();
592
581
  }));
593
582
  }
594
583
 
@@ -599,75 +588,87 @@ function structuredControl(entityId: string, parameter: Clip["inspector"][number
599
588
  const add = document.createElement("button");
600
589
  add.type = "button";
601
590
  add.className = "parameter-structured-add";
602
- add.innerHTML = `${icon("plus")}<span>Add ${schema.items.kind === "string" && schema.items.format === "color" ? "color" : "item"}</span>`;
591
+ add.innerHTML = `${icon("plus")}${uiLabel(schema.items.kind === "string" && schema.items.format === "color" ? "inspector.add-color" : "inspector.add-item")}`;
603
592
  add.disabled = schema.maxItems !== undefined && list.length >= schema.maxItems;
604
593
  add.addEventListener("click", () => {
605
594
  const current = Array.isArray(draft) ? draft : [];
606
595
  draft = [...current, blankValue(schema.items)];
607
596
  render();
597
+ save();
608
598
  });
609
599
  footer.append(add);
610
600
  }
611
- const decisions = document.createElement("span");
612
- decisions.className = "parameter-structured-decisions";
613
- const reset = document.createElement("button");
614
- reset.type = "button";
615
- reset.textContent = "Reset";
616
- reset.disabled = sameValue(draft, parameter.value);
617
- reset.addEventListener("click", () => {
618
- draft = structuredClone(parameter.value);
619
- render();
620
- });
621
- const apply = document.createElement("button");
622
- apply.type = "button";
623
- apply.className = "primary";
624
- apply.textContent = "Apply";
625
- apply.addEventListener("click", () => commitControl(entityId, parameter, draft));
626
- refreshDecisions = () => {
627
- reset.disabled = sameValue(draft, parameter.value);
628
- apply.disabled = reset.disabled || !valid();
629
- };
630
- refreshDecisions();
631
- decisions.append(reset, apply);
632
- footer.append(decisions);
633
- shell.append(footer);
601
+ if (schema.kind === "array") shell.append(footer);
602
+ notice.hidden = sameValue(draft, parameter.value) || valid();
603
+ shell.append(notice);
634
604
  };
635
605
  render();
636
606
  return shell;
637
607
  }
638
608
 
639
- function parameterControl(entityId: string, parameter: Clip["inspector"][number]): HTMLElement {
609
+ function parameterControl(
610
+ entityId: string,
611
+ parameter: Clip["inspector"][number],
612
+ change?: (value: CanonicalValue) => void,
613
+ ): HTMLElement {
614
+ const editable = parameter.edit !== undefined || change !== undefined;
615
+ const commit = change ?? ((value: CanonicalValue) => commitControl(entityId, parameter, value));
616
+ if (editable && parameter.control === "record" && parameter.schema !== undefined && parameterRecordVariants(parameter.schema).length) {
617
+ return structuredControl(entityId, parameter, commit);
618
+ }
640
619
  const row = document.createElement("div");
641
- row.className = `parameter-row parameter-editable control-${parameter.control}`;
620
+ row.className = `parameter-row ${!editable ? "parameter-readonly" : "parameter-editable"} control-${parameter.control}`;
642
621
  const name = document.createElement("span");
643
622
  name.className = "parameter-label";
644
623
  name.textContent = parameter.label;
645
624
  name.title = parameter.summary ?? parameter.label;
646
625
  const right = document.createElement("span");
647
626
  right.className = "parameter-right parameter-control";
627
+ if (!editable) {
628
+ const value = document.createElement("span");
629
+ value.className = "parameter-display";
630
+ let displayValue = textValue(parameter.value);
631
+ let unitLabel = parameter.unit;
632
+ if (parameter.control === "number") {
633
+ try {
634
+ const numeric = parameterNumber(change && parameter.value === null ? 0 : parameter.value, parameter.number);
635
+ displayValue = String(numeric.value);
636
+ unitLabel = numeric.suffix || parameter.unit;
637
+ } catch { /* Keep the actual value visible when it is not a scalar. */ }
638
+ } else if (parameter.control === "select") {
639
+ displayValue = parameter.options?.map(parameterOption).find(option => option.value === parameter.value)?.label ?? displayValue;
640
+ }
641
+ value.textContent = displayValue;
642
+ right.append(value);
643
+ if (unitLabel !== undefined) {
644
+ const unit = document.createElement("small"); unit.textContent = unitLabel; right.append(unit);
645
+ }
646
+ row.append(name, right);
647
+ return row;
648
+ }
648
649
  let unitLabel = parameter.unit;
649
650
  if (parameter.control === "select") {
650
- right.append(selectControl(entityId, parameter));
651
+ right.append(selectControl(parameter, commit));
651
652
  } else if (parameter.control === "color") {
652
653
  right.append(colorValueControl(parameter.label, textValue(parameter.value), (next) => {
653
- commitControl(entityId, parameter, next);
654
+ commit(next);
654
655
  }));
655
656
  if (parameter.swatches?.length) {
656
657
  const palette = document.createElement("span"); palette.className = "parameter-swatches";
657
658
  for (const color of parameter.swatches) {
658
659
  const swatch = document.createElement("button"); swatch.type = "button";
659
- swatch.style.backgroundColor = color; swatch.title = color; swatch.setAttribute("aria-label", `Use ${color}`);
660
- swatch.addEventListener("click", () => commitControl(entityId, parameter, color)); palette.append(swatch);
660
+ swatch.style.backgroundColor = color; swatch.title = color; uiAttr(swatch, "aria-label", "inspector.use-color", { color });
661
+ swatch.addEventListener("click", () => commit(color)); palette.append(swatch);
661
662
  }
662
663
  right.append(palette);
663
664
  }
664
665
  } else if (parameter.control === "list" || parameter.control === "record") {
665
- right.append(structuredControl(entityId, parameter));
666
+ right.append(structuredControl(entityId, parameter, commit));
666
667
  } else {
667
668
  if (parameter.multiline && parameter.control === "text") {
668
669
  const value = document.createElement("textarea"); value.className = "parameter-value parameter-multiline";
669
670
  value.value = textValue(parameter.value); value.setAttribute("aria-label", parameter.label);
670
- value.addEventListener("change", () => commitControl(entityId, parameter, value.value)); right.append(value);
671
+ value.addEventListener("change", () => commit(value.value)); right.append(value);
671
672
  row.append(name, right); return row;
672
673
  }
673
674
  const value = document.createElement("input");
@@ -678,8 +679,8 @@ function parameterControl(entityId: string, parameter: Clip["inspector"][number]
678
679
  if (value.type === "checkbox") value.checked = parameter.value === true || parameter.value === "true";
679
680
  else if (parameter.control === "number") {
680
681
  try {
681
- const numeric = parameterNumber(parameter.value, parameter.number);
682
- value.type = "number"; value.inputMode = "decimal"; value.value = String(numeric.value);
682
+ const numeric = parameterNumber(change && parameter.value === null ? 0 : parameter.value, parameter.number);
683
+ value.type = "number"; value.inputMode = "decimal"; value.value = change && parameter.value === null ? "" : String(numeric.value);
683
684
  const schema = parameter.schema?.kind === "number" ? parameter.schema : undefined;
684
685
  const scale = parameter.number?.scale ?? 1;
685
686
  const minimum = parameter.number?.minimum ?? (schema?.minimum === undefined ? undefined : schema.minimum * scale);
@@ -698,11 +699,12 @@ function parameterControl(entityId: string, parameter: Clip["inspector"][number]
698
699
  if (!value.title) value.title = parameter.summary ?? parameter.label;
699
700
  value.addEventListener("change", () => {
700
701
  if (value.type === "checkbox") {
701
- commitControl(entityId, parameter, value.checked);
702
+ commit(value.checked);
702
703
  } else if (parameter.control === "number") {
703
- if (value.reportValidity()) commitControl(entityId, parameter, value.valueAsNumber);
704
+ if (change) commit(Number.isFinite(value.valueAsNumber) ? value.valueAsNumber : null);
705
+ else if (value.reportValidity()) commit(value.valueAsNumber);
704
706
  } else {
705
- commitControl(entityId, parameter, value.value);
707
+ commit(value.value);
706
708
  }
707
709
  });
708
710
  right.append(value);
@@ -731,13 +733,13 @@ function parameterGroups(entityId: string, fields: readonly Clip["inspector"][nu
731
733
  });
732
734
  }
733
735
 
734
- let parameterWriteState: "" | "Saving" | "Saved" | "Failed" = "";
736
+ let parameterWriteState: "" | "common.saving" | "common.saved" | "common.failed" = "";
735
737
  async function writeParameter(entityId: string, parameter: Clip["inspector"][number], replacement: CanonicalValue): Promise<void> {
736
738
  const state = store.current();
737
739
  if (state === undefined) return;
738
- parameterWriteState = "Saving";
740
+ parameterWriteState = "common.saving";
739
741
  status.title = "";
740
- status.textContent = parameterWriteState;
742
+ uiText(status, parameterWriteState);
741
743
  status.className = "status saving";
742
744
  try {
743
745
  await applyStudioMutation({
@@ -747,15 +749,15 @@ async function writeParameter(entityId: string, parameter: Clip["inspector"][num
747
749
  parameterId: parameter.id,
748
750
  value: replacement,
749
751
  });
750
- parameterWriteState = "Saved";
751
- status.textContent = parameterWriteState;
752
+ parameterWriteState = "common.saved";
753
+ uiText(status, parameterWriteState);
752
754
  status.className = "status saved";
753
755
  } catch (error) {
754
756
  // Validation and stale-revision rejections do not publish a new snapshot.
755
757
  // Restore the accepted value just as a rejected compilation does.
756
758
  restoreParameterControls(entityId);
757
- parameterWriteState = "Failed";
758
- status.textContent = error instanceof Error ? "Save failed" : parameterWriteState;
759
+ parameterWriteState = "common.failed";
760
+ uiText(status, error instanceof Error ? "common.save-failed" : parameterWriteState);
759
761
  status.className = "status error";
760
762
  status.title = error instanceof Error ? error.message : String(error);
761
763
  }
@@ -773,26 +775,26 @@ function renderInspector(snapshot: StudioSnapshot, clipId: string | undefined):
773
775
  defaultWorkspaceHeading();
774
776
  const fps = snapshot.space.frameRate.numerator / snapshot.space.frameRate.denominator;
775
777
  inspector.replaceChildren(
776
- group("Project", [
777
- property("Author", snapshot.source.path, "property-code"),
778
- property("Run", snapshot.run.path, "property-code"),
779
- property("Sources", `${snapshot.source.files.length} referenced files`),
780
- property("Tracks", String(snapshot.tracks.length), "property-number"),
778
+ uiGroup("inspector.project", [
779
+ property("inspector.author", snapshot.source.path, "property-code"),
780
+ property("inspector.run", snapshot.run.path, "property-code"),
781
+ property("inspector.sources", String(snapshot.source.files.length)),
782
+ property("inspector.tracks", String(snapshot.tracks.length), "property-number"),
781
783
  ]),
782
- group("Canvas", [
783
- property("Resolution", `${snapshot.space.canvasWidth} × ${snapshot.space.canvasHeight}`, "property-number"),
784
- property("Aspect ratio", aspectRatio(snapshot.space.canvasWidth, snapshot.space.canvasHeight), "property-number"),
784
+ uiGroup("inspector.canvas", [
785
+ property("inspector.resolution", `${snapshot.space.canvasWidth} × ${snapshot.space.canvasHeight}`, "property-number"),
786
+ property("inspector.aspect-ratio", aspectRatio(snapshot.space.canvasWidth, snapshot.space.canvasHeight), "property-number"),
785
787
  ]),
786
- group("Timeline", [
787
- property("Duration", `${snapshot.space.durationSec.toFixed(2)} s`, "property-number"),
788
- property("Frame rate", `${fps.toFixed(Number.isInteger(fps) ? 0 : 2)} fps`, "property-number"),
789
- property("Frames", String(snapshot.space.frameCount), "property-number"),
788
+ uiGroup("inspector.timeline", [
789
+ property("inspector.duration", `${snapshot.space.durationSec.toFixed(2)} s`, "property-number"),
790
+ property("inspector.frame-rate", `${fps.toFixed(Number.isInteger(fps) ? 0 : 2)} fps`, "property-number"),
791
+ property("inspector.frames", String(snapshot.space.frameCount), "property-number"),
790
792
  ]),
791
- group("Build", [
792
- property("Targets", snapshot.run.targets
793
+ uiGroup("inspector.build", [
794
+ property("inspector.targets", snapshot.run.targets
793
795
  .map((target) => target.split("::output::").at(-1) ?? target)
794
796
  .join(", ") || "—", "property-code"),
795
- property("Candidates", String(snapshot.run.satisfactions.length), "property-number"),
797
+ property("inspector.candidates", String(snapshot.run.satisfactions.length), "property-number"),
796
798
  ]),
797
799
  );
798
800
  return;
@@ -802,7 +804,7 @@ function renderInspector(snapshot: StudioSnapshot, clipId: string | undefined):
802
804
  defaultWorkspaceHeading();
803
805
  const empty = document.createElement("div");
804
806
  empty.className = "inspector-empty";
805
- empty.textContent = "No adjustable parameters";
807
+ uiText(empty, "inspector.empty");
806
808
  inspector.replaceChildren(empty);
807
809
  return;
808
810
  }
@@ -813,17 +815,22 @@ function renderInspector(snapshot: StudioSnapshot, clipId: string | undefined):
813
815
 
814
816
  const domainFields = clip.inspector.filter((field) => field.domain === activeDomain);
815
817
  const pages = new Map<string, { readonly label: string; readonly fields: typeof domainFields }>();
816
- for (const field of domainFields) {
817
- const id = field.page?.id ?? "default";
818
+ const commonFields = domainFields.filter(field => field.page === undefined);
819
+ for (const field of domainFields.filter(field => field.page !== undefined)) {
820
+ const id = field.page!.id;
818
821
  const page = pages.get(id) ?? { label: field.page?.label ?? "", fields: [] };
819
822
  pages.set(id, { ...page, fields: [...page.fields, field] });
820
823
  }
821
824
  const pageIds = [...pages.keys()];
825
+ if (pageIds.length === 0) {
826
+ inspector.replaceChildren(...parameterGroups(clip.id, commonFields));
827
+ return;
828
+ }
822
829
  const memoryKey = `${clip.id}:${activeDomain}`;
823
830
  const rememberedPage = inspectorPageByEntity.get(memoryKey);
824
831
  const activePage = rememberedPage !== undefined && pages.has(rememberedPage) ? rememberedPage : pageIds[0]!;
825
832
  inspectorPageByEntity.set(memoryKey, activePage);
826
- const page = pages.get(activePage)!;
833
+ const page = pages.get(activePage);
827
834
  const subtabs = document.createElement("div");
828
835
  subtabs.className = "inspector-subtabs";
829
836
  if (pageIds.length > 1) {
@@ -841,7 +848,7 @@ function renderInspector(snapshot: StudioSnapshot, clipId: string | undefined):
841
848
  return button;
842
849
  }));
843
850
  }
844
- inspector.replaceChildren(...(pageIds.length > 1 ? [subtabs] : []), ...parameterGroups(clip.id, page.fields));
851
+ inspector.replaceChildren(...(pageIds.length > 1 ? [subtabs] : []), ...parameterGroups(clip.id, [...commonFields, ...(page?.fields ?? [])]));
845
852
  }
846
853
 
847
854
  function renderSemanticInspector(snapshot: StudioSnapshot, segmentId: string): void {
@@ -850,7 +857,7 @@ function renderSemanticInspector(snapshot: StudioSnapshot, segmentId: string): v
850
857
  defaultWorkspaceHeading();
851
858
  const empty = document.createElement("div");
852
859
  empty.className = "inspector-empty";
853
- empty.textContent = "No adjustable parameters";
860
+ uiText(empty, "inspector.empty");
854
861
  inspector.replaceChildren(empty);
855
862
  }
856
863
 
@@ -862,32 +869,36 @@ function semanticAnchorInspector(snapshot: StudioSnapshot, kind: "selection" | "
862
869
  .map((handle) => ({ clip, handle })));
863
870
  const current = kind === "selection" ? semantic?.selections.find((item) => item.id === id)
864
871
  : semantic?.moments.find((item) => item.id === id);
865
- if (!semantic || !current) return group("Timing", []);
866
- const endpoints = "anchorId" in current ? [["Moment", current.anchorId]]
867
- : [["Start", current.startAnchorId], ["End", current.endAnchorId]];
868
- return group("Semantic anchors", endpoints.map(([label, anchorId]) => {
872
+ if (!semantic || !current) return uiGroup("inspector.timing", []);
873
+ const endpoints: readonly (readonly [Message, string])[] = "anchorId" in current ? [["inspector.moment", current.anchorId]]
874
+ : [["inspector.start", current.startAnchorId], ["inspector.end", current.endAnchorId]];
875
+ return uiGroup("inspector.semantic-anchors", endpoints.map(([label, anchorId]) => {
869
876
  const anchor = semantic.anchors.find((item) => item.id === anchorId)!;
870
- const describe = (item: typeof anchor) => {
877
+ const describe = (node: Element, item: typeof anchor) => {
871
878
  const word = semantic.tokens.find((token) => token.id === item.tokenId)?.text;
872
- return `${item.kind.replaceAll("-", " ")}${word ? ` · ${word}` : ""}${item.segmentId ? ` · ${item.segmentId}` : ""}`;
879
+ uiText(node, `inspector.anchor.${item.kind}`, { detail: `${word ? ` · ${word}` : ""}${item.segmentId ? ` · ${item.segmentId}` : ""}` });
873
880
  };
874
881
  const candidates = semantic.anchors.filter((item) => item.frame === anchor.frame).flatMap((item) => {
875
882
  const target: SemanticTarget = "anchorId" in current ? { kind: "moment", anchorId: item.id }
876
- : { kind: "selection", startAnchorId: label === "Start" ? item.id : current.startAnchorId,
877
- endAnchorId: label === "End" ? item.id : current.endAnchorId };
883
+ : { kind: "selection", startAnchorId: label === "inspector.start" ? item.id : current.startAnchorId,
884
+ endAnchorId: label === "inspector.end" ? item.id : current.endAnchorId };
878
885
  const owner = consumers.find(({ handle }) => semanticGestureSpan(semantic.anchors, handle, target) !== undefined);
879
886
  return owner ? [{ item, target, ...owner }] : [];
880
887
  });
881
- if (candidates.length < 2) return property(label!, describe(anchor));
888
+ if (candidates.length < 2) {
889
+ const row = property(label!, "");
890
+ describe(row.querySelector("strong")!, anchor);
891
+ return row;
892
+ }
882
893
  const row = document.createElement("label");
883
894
  row.className = "property";
884
895
  const name = document.createElement("span");
885
- name.textContent = label!;
896
+ uiText(name, label!);
886
897
  const control = document.createElement("select");
887
898
  control.className = "parameter-value";
888
- control.setAttribute("aria-label", `${label} semantic anchor`);
899
+ uiAttr(control, "aria-label", label === "inspector.start" ? "inspector.start-anchor" : label === "inspector.end" ? "inspector.end-anchor" : "inspector.moment-anchor");
889
900
  for (const { item } of candidates) {
890
- const option = document.createElement("option"); option.value = item.id; option.textContent = describe(item);
901
+ const option = document.createElement("option"); option.value = item.id; describe(option, item);
891
902
  control.append(option);
892
903
  }
893
904
  control.value = anchorId!;
@@ -896,14 +907,14 @@ function semanticAnchorInspector(snapshot: StudioSnapshot, kind: "selection" | "
896
907
  const span = semanticGestureSpan(semantic.anchors, choice.handle, choice.target)!;
897
908
  const temporal = choice.handle.temporal!;
898
909
  control.disabled = true;
899
- status.textContent = "Saving"; status.className = "status saving";
910
+ uiText(status, "common.saving"); status.className = "status saving";
900
911
  void applyStudioMutation({ type: "timeline.adjust", revision: snapshot.revision,
901
912
  entityId: choice.clip.id, gesture: choice.handle.gesture,
902
913
  target: temporal.kind === "instant" ? { kind: "instant", frame: span.startFrame, semantic: choice.target }
903
914
  : { kind: "window", ...span, semantic: choice.target },
904
- }).then(() => { status.textContent = "Saved"; status.className = "status saved"; })
915
+ }).then(() => { uiText(status, "common.saved"); status.className = "status saved"; })
905
916
  .catch((error: unknown) => {
906
- control.value = anchorId!; status.textContent = "Save failed"; status.className = "status error";
917
+ control.value = anchorId!; uiText(status, "common.save-failed"); status.className = "status error";
907
918
  status.title = error instanceof Error ? error.message : String(error);
908
919
  }).finally(() => { control.disabled = false; });
909
920
  });
@@ -1060,6 +1071,7 @@ window.addEventListener("keydown", (event) => {
1060
1071
  event.preventDefault();
1061
1072
  return;
1062
1073
  }
1074
+ if (shell.dataset.view === "comments" && ["-", "_", "=", "+", "\\", "f", "F"].includes(event.key)) return;
1063
1075
  if (event.key === "-" || event.key === "_") {
1064
1076
  timeline.zoomOut();
1065
1077
  event.preventDefault();
@@ -1086,10 +1098,10 @@ window.addEventListener("keydown", (event) => {
1086
1098
  });
1087
1099
 
1088
1100
  function applySnapshot(snapshot: StudioSnapshot): void {
1089
- failureView.textContent = "";
1101
+ userText(failureView, "");
1090
1102
  status.className = "status";
1091
1103
  status.title = "";
1092
- status.textContent = "";
1104
+ userText(status, "");
1093
1105
  renderMeta(snapshot);
1094
1106
  library.show(snapshot);
1095
1107
  store.load(snapshot);
@@ -1097,7 +1109,7 @@ function applySnapshot(snapshot: StudioSnapshot): void {
1097
1109
 
1098
1110
  function applyFailure(failure: StudioFailure): void {
1099
1111
  status.className = "status error";
1100
- status.textContent = "Compile failed";
1112
+ uiText(status, "common.compile-failed");
1101
1113
  failureView.textContent = failure.error;
1102
1114
  if (failure.range !== undefined) code.highlight([{ range: failure.range, tone: "element" }], true);
1103
1115
  // A parameter control changes immediately in the browser, but the source