@dailies-studio/shot-editor 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -0,0 +1,641 @@
1
+ // Tier-1 export for the shot stage (the browser-side sibling of the motion
2
+ // harness's stageExport.ts, a fifth of its size because a shot is ONE canvas):
3
+ // per frame, arm a snapshot, commit the frame on the live ShotRuntime, capture
4
+ // synchronously from its onPresented call stack, and feed the encode worker.
5
+ // whose frameDone ack IS the encoder's backpressure, so memory stays bounded
6
+ // at ~one frame in flight.
7
+ //
8
+ // There is no SnapDOM, no compositing pass and no per-layer split here:
9
+ // ShotRuntime draws every layer kind, text included, onto one WebGL canvas.
10
+ // The default framebuffer is deliberately UNPRESERVED for playback speed, so
11
+ // capture must begin in the same task as the draw, before Chromium presents
12
+ // and retires it. A later canvas read can be a valid-sized all-black image.
13
+ //
14
+ // The loop is a plain awaited while, never rAF. A hidden tab still stops the
15
+ // stage itself (rAF, ResizeObserver and WebGL's rAF-polled program links all
16
+ // halt), so each frame's deadline counts visible time only and the export
17
+ // pauses rather than fails while the tab is behind another. It does not poll
18
+ // either: ClockDriver
19
+ // calls onPresented only after the draw and only when non-draw readiness claims
20
+ // are clear. The exact frame, marker and backing size are checked in that same
21
+ // callback before the armed snapshot is invoked.
22
+ import { CAPABILITY_PROBE_TIMED_OUT, converse, gifFrameStride, gifRate } from "@dailies-studio/shot/tier1";
23
+ import type { Mp4Encoding, Tier1Capability, WorkerLike } from "@dailies-studio/shot/tier1";
24
+ import type { Rational } from "@dailies-studio/engine";
25
+
26
+ /** What the exporter needs from the stage. Everything here must be honest
27
+ * about the CURRENT stage. The exporter never touches stage internals, and
28
+ * the tests fake this seam whole. */
29
+ export interface ShotExportHost {
30
+ /** The export framing's numbers, or null before a document is loaded. */
31
+ composition(): { width: number; height: number; fps: Rational } | null;
32
+ /** Frames the requested scope runs, resolved against the CURRENT document
33
+ * (the slice the runtime will render), or null for an unknown scene id. */
34
+ scopeFrames(sceneId: string | undefined): number | null;
35
+ /** The export-local frames on which a scene begins (always including 0).
36
+ * An export never warms the next scene, so each of these is a cold mount:
37
+ * models to parse, rasters to draw, programs to link. */
38
+ sceneStartFrames(sceneId: string | undefined): readonly number[];
39
+ /** Flip the stage into export framing for `sceneId` (undefined = whole
40
+ * timeline) at an uncovered sentinel frame. Remembers what to restore; the
41
+ * first armed seek to frame 0 is therefore always a real presentation. */
42
+ begin(sceneId: string | undefined): void;
43
+ /** Commit `frame` on the export stage. */
44
+ seek(frame: number): void;
45
+ /** Restore the author's framing, playhead and any deferred chrome pushes. */
46
+ end(): void;
47
+ /** The live GL canvas, or null before the runtime mounted. */
48
+ canvas(): HTMLCanvasElement | null;
49
+ /** True when the stage's LAST COMMIT was the export framing at `frame`.
50
+ * the DOM marker StageApp stamps in the same commit that claims the draw
51
+ * latch, which is what makes the in-presentation capture race-free. */
52
+ frameCommitted(frame: number): boolean;
53
+ /** What the outstanding readiness claims are waiting on, for the timeout
54
+ * message: each label with its claim's own description. */
55
+ waitingOn(): readonly string[];
56
+ /**
57
+ * Arm one exact in-draw snapshot before `seek(frame)`.
58
+ *
59
+ * The stage resolves it only from ShotRuntime's matching onPresented call,
60
+ * after advance() drew and while the unpreserved default framebuffer is
61
+ * still readable. It also requires export framing, the matching DOM commit
62
+ * marker, and the exact composition-sized backing store. A second arm is a
63
+ * refusal rather than two consumers racing for one framebuffer.
64
+ */
65
+ armCapture(
66
+ frame: number,
67
+ size: { width: number; height: number },
68
+ snapshot: (canvas: HTMLCanvasElement) => Promise<ImageBitmap>,
69
+ ): Promise<ImageBitmap>;
70
+ /** Reject the currently armed capture, if any. */
71
+ cancelCapture(error: Error): void;
72
+ }
73
+
74
+ export interface ShotExportDeps {
75
+ createWorker(): WorkerLike;
76
+ toBitmap(canvas: HTMLCanvasElement): Promise<ImageBitmap>;
77
+ /** Run `fn` after `ms`, returning a cancel. The only timer this lane needs:
78
+ * one refusal deadline per armed frame. */
79
+ after(ms: number, fn: () => void): () => void;
80
+ /** A monotonic clock, for charging a frame's deadline visible time only. */
81
+ now(): number;
82
+ /** Whether the page is hidden (another tab in front, minimised). */
83
+ hidden(): boolean;
84
+ /** Subscribe to visibility changes; returns the unsubscribe. */
85
+ onVisibilityChange(listener: () => void): () => void;
86
+ /** An encode worker failed (its `error` event): recorded beside the
87
+ * export's own refusal for the stage's health (bridge v35). */
88
+ onWorkerError?(message: string): void;
89
+ /** Scale a captured frame to `width`x`height` and encode it as `type`. The
90
+ * caller closes the bitmap. */
91
+ encodeStill(
92
+ bitmap: ImageBitmap,
93
+ width: number,
94
+ height: number,
95
+ type: string,
96
+ quality: number | undefined,
97
+ ): Promise<Blob>;
98
+ }
99
+
100
+ export interface ShotExportMovieOptions {
101
+ /** Absent = mp4. A GIF carries no audio and takes no bitrate or quantizer. */
102
+ format?: "mp4" | "gif" | undefined;
103
+ sceneId?: string | undefined;
104
+ /** Master frames of the whole timeline to export (the work area). Never
105
+ * with `sceneId`. Absent = the whole scope. */
106
+ window?: { start: number; frames: number } | undefined;
107
+ videoBitrate?: number | undefined;
108
+ /** The fixed H.264 QP the worker encodes at where a hardware encoder
109
+ * honours one (`MovieJob.quantizer`). */
110
+ quantizer?: number | undefined;
111
+ audio?: { sampleRate: number; channels: Float32Array[] } | undefined;
112
+ onProgress?: ((framesDone: number, totalFrames: number) => void) | undefined;
113
+ signal?: AbortSignal | undefined;
114
+ }
115
+
116
+ /** One exact still of the whole timeline, delivered at `width`x`height`. */
117
+ export interface ShotStillOptions {
118
+ /** A MASTER frame of the whole timeline. */
119
+ frame: number;
120
+ width: number;
121
+ height: number;
122
+ type: "image/png" | "image/jpeg" | "image/webp";
123
+ /** Lossy encoders only. Absent = the browser's default. */
124
+ quality?: number | undefined;
125
+ signal?: AbortSignal | undefined;
126
+ }
127
+
128
+ export interface ShotStageExporter {
129
+ capability(): Promise<Tier1Capability>;
130
+ exportMovie(
131
+ opts: ShotExportMovieOptions,
132
+ ): Promise<{ blob: Blob; extension: string; audioSkippedReason?: string; encoding?: Mp4Encoding }>;
133
+ /** The same export framing and in-draw capture as one movie frame, for the
134
+ * dashboard poster and the storyboard's "frame from shot". Rejects with the
135
+ * reason, and restores the stage on every path. */
136
+ captureStill(opts: ShotStillOptions): Promise<Blob>;
137
+ }
138
+
139
+ /** The first frame, and the first frame of every later scene, may re-raster
140
+ * every text layer at the export scale, parse models, link programs, decode
141
+ * images and settle video seeks (on frame 0, on top of the framing swap).
142
+ * This uses the still lane's cold-start budget (settleShotStill's 60 s), not a
143
+ * per-frame one. */
144
+ const FIRST_FRAME_TIMEOUT_MS = 60_000;
145
+ const FRAME_TIMEOUT_MS = 20_000;
146
+ const CAPABILITY_TIMEOUT_MS = 20_000;
147
+ /** One inactivity window for worker startup, one encoded frame, or finalize.
148
+ * The worker may encode a long audio mix during startup and a software-backed
149
+ * 4K frame can be legitimately slow, so this is deliberately much larger
150
+ * than the presentation deadline. It is still finite: a codec promise that
151
+ * never settles must restore the editor rather than own it forever. A GIF's
152
+ * finish encodes the whole film and reports each frame ("finishing"), and
153
+ * every report starts the window again, so a long GIF is not refused for
154
+ * being long. */
155
+ const WORKER_OPERATION_TIMEOUT_MS = 10 * 60_000;
156
+
157
+ function defaultDeps(): ShotExportDeps {
158
+ return {
159
+ createWorker: () =>
160
+ new Worker(new URL("./exportWorker.ts", import.meta.url), {
161
+ type: "module",
162
+ }) as unknown as WorkerLike,
163
+ toBitmap: (canvas) => createImageBitmap(canvas),
164
+ after: (ms, fn) => {
165
+ const timer = setTimeout(fn, ms);
166
+ return () => clearTimeout(timer);
167
+ },
168
+ now: () => performance.now(),
169
+ // "hidden" only: jsdom and prerendered pages report other states, and a
170
+ // deadline that never ran would be a hang rather than a pause.
171
+ hidden: () => typeof document !== "undefined" && document.visibilityState === "hidden",
172
+ onVisibilityChange: (listener) => {
173
+ if (typeof document === "undefined") return () => undefined;
174
+ document.addEventListener("visibilitychange", listener);
175
+ return () => document.removeEventListener("visibilitychange", listener);
176
+ },
177
+ encodeStill: async (bitmap, width, height, type, quality) => {
178
+ const canvas = new OffscreenCanvas(width, height);
179
+ const ctx = canvas.getContext("2d");
180
+ if (ctx === null) throw new Error("a 2d canvas is unavailable, so the still could not be encoded");
181
+ // A poster is a third of the design width: the default low-quality
182
+ // (bilinear) filter aliases at that ratio.
183
+ ctx.imageSmoothingQuality = "high";
184
+ ctx.drawImage(bitmap, 0, 0, width, height);
185
+ return canvas.convertToBlob(quality === undefined ? { type } : { type, quality });
186
+ },
187
+ };
188
+ }
189
+
190
+ /** A channel's exact bytes as a standalone ArrayBuffer for the transfer
191
+ * list. The mix's Float32Array may be a view into a larger buffer.
192
+ *
193
+ * The mix is made in the CHROME's realm and this runs in the stage iframe's,
194
+ * so `instanceof ArrayBuffer` (this realm's constructor) was always false and
195
+ * every export copied its whole mix first. The brand check works across
196
+ * realms, and still refuses a SharedArrayBuffer, which cannot be transferred.
197
+ * The mix is made fresh for each export and not read after it is handed
198
+ * over, so transferring it (which detaches it in the chrome) is safe. */
199
+ export function transferableChannel(ch: Float32Array): ArrayBuffer {
200
+ if (
201
+ ch.byteOffset === 0 &&
202
+ ch.byteLength === ch.buffer.byteLength &&
203
+ Object.prototype.toString.call(ch.buffer) === "[object ArrayBuffer]"
204
+ ) {
205
+ return ch.buffer as ArrayBuffer;
206
+ }
207
+ return ch.slice().buffer as ArrayBuffer;
208
+ }
209
+
210
+ export function createShotStageExporter(
211
+ host: ShotExportHost,
212
+ overrides: Partial<ShotExportDeps> = {},
213
+ ): ShotStageExporter {
214
+ const deps: ShotExportDeps = { ...defaultDeps(), ...overrides };
215
+ const capabilityCache = new Map<string, Tier1Capability>();
216
+ /** A started encode worker, its failures reported beside the refusal the
217
+ * conversation raises (`onWorkerError`). A fake without events is left
218
+ * as it is. */
219
+ const startWorker = (): WorkerLike => {
220
+ const worker = deps.createWorker();
221
+ const target = worker as unknown as { addEventListener?: (type: string, listener: (event: unknown) => void) => void };
222
+ target.addEventListener?.("error", (event) => {
223
+ const message = (event as { message?: unknown }).message;
224
+ deps.onWorkerError?.(`the export worker failed: ${typeof message === "string" && message !== "" ? message : "its script did not load or threw"}`);
225
+ });
226
+ return worker;
227
+ };
228
+ let active = false;
229
+
230
+ function requireComposition(): { width: number; height: number; fps: Rational } {
231
+ const comp = host.composition();
232
+ if (!comp) throw new Error("export needs a loaded shot");
233
+ return comp;
234
+ }
235
+
236
+ /**
237
+ * `after`, charging VISIBLE time only. A hidden tab stops rAF, the
238
+ * ResizeObserver the framing swap waits on, and the rAF-polled completion of
239
+ * a WebGL program link, so no frame can present while the author has another
240
+ * tab in front. Counting that time refused exports the author had merely
241
+ * stopped watching; paused, the export simply resumes when they come back.
242
+ */
243
+ function visibleDeadline(ms: number, fn: () => void): () => void {
244
+ let spent = 0;
245
+ let visibleSince: number | null = deps.hidden() ? null : deps.now();
246
+ let cancelTimer = (): void => {};
247
+ let finished = false;
248
+ const schedule = (): void => {
249
+ cancelTimer();
250
+ cancelTimer = () => {};
251
+ if (visibleSince === null) return;
252
+ cancelTimer = deps.after(Math.max(0, ms - spent), () => {
253
+ if (finished) return;
254
+ finished = true;
255
+ unsubscribe();
256
+ fn();
257
+ });
258
+ };
259
+ const unsubscribe = deps.onVisibilityChange(() => {
260
+ if (finished) return;
261
+ if (deps.hidden()) {
262
+ if (visibleSince === null) return;
263
+ spent += deps.now() - visibleSince;
264
+ visibleSince = null;
265
+ } else {
266
+ if (visibleSince !== null) return;
267
+ visibleSince = deps.now();
268
+ }
269
+ schedule();
270
+ });
271
+ schedule();
272
+ return () => {
273
+ finished = true;
274
+ cancelTimer();
275
+ unsubscribe();
276
+ };
277
+ }
278
+
279
+ /** Arm a bounded capture and retain the old gate's actionable timeout. */
280
+ function captureFrame(
281
+ frame: number,
282
+ timeoutMs: number,
283
+ comp: { width: number; height: number },
284
+ signal: AbortSignal | undefined,
285
+ ): Promise<ImageBitmap> {
286
+ return new Promise<ImageBitmap>((resolve, reject) => {
287
+ let done = false;
288
+ const capture = host.armCapture(frame, comp, deps.toBitmap);
289
+ let cancelDeadline = (): void => {};
290
+ const cleanup = (): void => {
291
+ cancelDeadline();
292
+ signal?.removeEventListener("abort", onAbort);
293
+ };
294
+ const cancel = (error: Error): void => {
295
+ if (done) return;
296
+ done = true;
297
+ cleanup();
298
+ host.cancelCapture(error);
299
+ reject(error);
300
+ };
301
+ const onAbort = (): void => cancel(new Error("export cancelled"));
302
+ cancelDeadline = visibleDeadline(timeoutMs, () => {
303
+ const canvas = host.canvas();
304
+ const size =
305
+ canvas !== null && (canvas.width !== comp.width || canvas.height !== comp.height)
306
+ ? `the canvas to reach ${comp.width}x${comp.height} (it is ${canvas.width}x${canvas.height})`
307
+ : "";
308
+ const claims = host.waitingOn().join(", ");
309
+ const commit = host.frameCommitted(frame) ? "" : "the stage to commit";
310
+ const parts = [claims, size, commit].filter(Boolean);
311
+ const error = new Error(
312
+ `export frame ${frame} was not captured within ${Math.round(timeoutMs / 1000)}s of visible time: ` +
313
+ `waiting on ${parts.join("; ") || "the matching presentation"}`,
314
+ );
315
+ cancel(error);
316
+ });
317
+ signal?.addEventListener("abort", onAbort, { once: true });
318
+ if (signal?.aborted) onAbort();
319
+ void capture.then(
320
+ (bitmap) => {
321
+ if (done) {
322
+ bitmap.close();
323
+ return;
324
+ }
325
+ done = true;
326
+ cleanup();
327
+ resolve(bitmap);
328
+ },
329
+ (error: unknown) => {
330
+ if (done) return;
331
+ done = true;
332
+ cleanup();
333
+ reject(error);
334
+ },
335
+ );
336
+ });
337
+ }
338
+
339
+ /** Bound one worker request without weakening converse's single-pending
340
+ * protocol. The caller still owns teardown: a deadline rejects into the
341
+ * existing cancel/terminate/finally path, while a late worker reply is
342
+ * ignored after talk.dispose(). `start` gets `alive`, which starts the
343
+ * window again: the request's own progress, when it reports any. */
344
+ function workerDeadline<T>(start: (alive: () => void) => Promise<T>, message: string): Promise<T> {
345
+ return new Promise<T>((resolve, reject) => {
346
+ let settled = false;
347
+ let cancelDeadline = (): void => {};
348
+ const arm = (): void => {
349
+ cancelDeadline();
350
+ cancelDeadline = deps.after(WORKER_OPERATION_TIMEOUT_MS, () => {
351
+ if (settled) return;
352
+ settled = true;
353
+ reject(new Error(message));
354
+ });
355
+ };
356
+ arm();
357
+ const promise = start(() => {
358
+ if (!settled) arm();
359
+ });
360
+ void promise.then(
361
+ (value) => {
362
+ if (settled) return;
363
+ settled = true;
364
+ cancelDeadline();
365
+ resolve(value);
366
+ },
367
+ (error: unknown) => {
368
+ if (settled) return;
369
+ settled = true;
370
+ cancelDeadline();
371
+ reject(error);
372
+ },
373
+ );
374
+ });
375
+ }
376
+
377
+ /** Reserve the lane and flip the stage into export framing for `scope`. */
378
+ function enter(scope: string | undefined): void {
379
+ active = true;
380
+ try {
381
+ host.begin(scope);
382
+ } catch (e) {
383
+ active = false;
384
+ throw e;
385
+ }
386
+ }
387
+
388
+ /** Restore the author's stage and free the lane, on every path. */
389
+ function leave(): void {
390
+ try {
391
+ host.end();
392
+ } catch (endError) {
393
+ // A restore that fails must not replace the capture's own outcome: a
394
+ // finished movie or still is still returned, and a failure still reports
395
+ // its own cause. The stage has left export framing regardless (`end`
396
+ // sets that in a finally).
397
+ console.error("[export] restoring the stage after the export failed", endError);
398
+ } finally {
399
+ active = false;
400
+ }
401
+ }
402
+
403
+ async function capability(signal?: AbortSignal): Promise<Tier1Capability> {
404
+ const comp = requireComposition();
405
+ const key = `${comp.width}x${comp.height}`;
406
+ const cached = capabilityCache.get(key);
407
+ if (cached) return cached;
408
+ let worker: WorkerLike;
409
+ try {
410
+ worker = startWorker();
411
+ } catch (e) {
412
+ const reason = `export worker could not start: ${e instanceof Error ? e.message : String(e)}`;
413
+ return {
414
+ mp4: { ok: false, reason },
415
+ pngSequence: { ok: false, reason },
416
+ gif: { ok: false, reason },
417
+ audio: { ok: false, reason },
418
+ };
419
+ }
420
+ const controller = new AbortController();
421
+ let timedOut = false;
422
+ const relayAbort = (): void => controller.abort();
423
+ const cancelDeadline = deps.after(CAPABILITY_TIMEOUT_MS, () => {
424
+ timedOut = true;
425
+ controller.abort();
426
+ });
427
+ signal?.addEventListener("abort", relayAbort, { once: true });
428
+ if (signal?.aborted) relayAbort();
429
+ const talk = converse(worker, controller.signal);
430
+ try {
431
+ const res = await talk.request(
432
+ { type: "probe", width: comp.width, height: comp.height },
433
+ ["capability"],
434
+ );
435
+ capabilityCache.set(key, res.capability);
436
+ return res.capability;
437
+ } catch (e) {
438
+ const reason = timedOut
439
+ ? CAPABILITY_PROBE_TIMED_OUT
440
+ : signal?.aborted
441
+ ? "export cancelled"
442
+ : e instanceof Error
443
+ ? e.message
444
+ : String(e);
445
+ return {
446
+ mp4: { ok: false, reason },
447
+ pngSequence: { ok: false, reason },
448
+ gif: { ok: false, reason },
449
+ audio: { ok: false, reason },
450
+ };
451
+ } finally {
452
+ cancelDeadline();
453
+ signal?.removeEventListener("abort", relayAbort);
454
+ talk.dispose();
455
+ worker.terminate();
456
+ }
457
+ }
458
+
459
+ return {
460
+ capability,
461
+
462
+ async exportMovie({ format, sceneId, window, videoBitrate, quantizer, audio, onProgress, signal }) {
463
+ const gif = format === "gif";
464
+ if (active) throw new Error("an export is already running");
465
+ const comp = requireComposition();
466
+ const scope = sceneId === undefined || sceneId === "" ? undefined : sceneId;
467
+ const scopeTotal = host.scopeFrames(scope);
468
+ if (scopeTotal === null) throw new Error(`unknown scene '${sceneId}'`);
469
+ if (window !== undefined) {
470
+ if (scope !== undefined) throw new Error("a frame window is for the whole Project, not one Shot");
471
+ const { start, frames } = window;
472
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(frames) || start < 0 || frames < 1 || start + frames > scopeTotal) {
473
+ throw new Error(`frames ${start}..${start + frames} are outside this Project's timeline (0..${scopeTotal})`);
474
+ }
475
+ }
476
+ // The first master frame and how many follow it.
477
+ const first = window?.start ?? 0;
478
+ const total = window?.frames ?? scopeTotal;
479
+ if (total <= 0) throw new Error("the export scope has no frames");
480
+
481
+ // Reserve the lane and enter export framing BEFORE the optional async
482
+ // audio probe. That defers chrome document/playhead pushes while the
483
+ // sampled composition and duration are in use, and a second export
484
+ // cannot pass the guard during the probe.
485
+ enter(scope);
486
+ try {
487
+ // Audio is gated on the probe BEFORE any capture work: an unencodable-
488
+ // audio browser ships silently WITH the reason (the dialog renders it),
489
+ // never silently-silently, and never crashes mid-encode instead.
490
+ let audioJob: { sampleRate: number; channels: ArrayBuffer[] } | undefined;
491
+ let audioSkippedReason: string | undefined;
492
+ // A GIF has no audio track: the dialog never offers one, and a mix
493
+ // handed here anyway has nowhere to go.
494
+ if (audio && !gif) {
495
+ const cap = await capability(signal);
496
+ if (signal?.aborted) throw new Error("export cancelled");
497
+ if (!cap.mp4.ok) {
498
+ throw new Error(cap.mp4.reason ?? "this browser cannot encode MP4");
499
+ }
500
+ if (!cap.audio.ok) {
501
+ audioSkippedReason = cap.audio.reason;
502
+ } else {
503
+ audioJob = {
504
+ sampleRate: audio.sampleRate,
505
+ channels: audio.channels.map(transferableChannel),
506
+ };
507
+ }
508
+ }
509
+
510
+ // A GIF faster than 50 fps is written at a fraction of the rate,
511
+ // every `stride`-th frame (`gifFrameStride`): only those are captured,
512
+ // and the encoder is handed an ordinary film at the GIF's rate.
513
+ const stride = gif ? gifFrameStride(comp.fps) : 1;
514
+ const count = Math.ceil(total / stride);
515
+ const worker = startWorker();
516
+ const talk = converse(worker, signal);
517
+ try {
518
+ const started = await workerDeadline(
519
+ () =>
520
+ talk.request(
521
+ {
522
+ type: "start",
523
+ kind: gif ? "gif" : "mp4",
524
+ width: comp.width,
525
+ height: comp.height,
526
+ fps: gif ? gifRate(comp.fps) : comp.fps,
527
+ totalFrames: count,
528
+ ...(audioJob ? { audio: audioJob } : {}),
529
+ ...(videoBitrate !== undefined && !gif ? { videoBitrate } : {}),
530
+ ...(quantizer !== undefined && !gif ? { quantizer } : {}),
531
+ },
532
+ ["started"],
533
+ audioJob?.channels,
534
+ ),
535
+ "export encoder did not start within 600 seconds",
536
+ );
537
+ // Every scene's first frame is a cold mount (an export never warms
538
+ // the next scene), so each gets the cold budget, not only frame 0.
539
+ // Decimated, that is the first captured frame at or after it.
540
+ const coldFrames = new Set(
541
+ host
542
+ .sceneStartFrames(scope)
543
+ .filter((start) => start >= first && start < first + total)
544
+ .map((start) => Math.ceil((start - first) / stride)),
545
+ );
546
+ coldFrames.add(0);
547
+ for (let index = 0; index < count; index++) {
548
+ if (signal?.aborted) throw new Error("export cancelled");
549
+ const frame = first + index * stride;
550
+ const bitmapPromise = captureFrame(
551
+ frame,
552
+ coldFrames.has(index) ? FIRST_FRAME_TIMEOUT_MS : FRAME_TIMEOUT_MS,
553
+ comp,
554
+ signal,
555
+ );
556
+ host.seek(frame);
557
+ const bitmap = await bitmapPromise;
558
+ try {
559
+ // The ack resolves when the encoder can take more, providing backpressure.
560
+ const ack = await workerDeadline(
561
+ () =>
562
+ talk.request(
563
+ { type: "frame", index, bitmap },
564
+ ["frameDone"],
565
+ [bitmap],
566
+ ),
567
+ `export frame ${frame} was captured, but the encoder did not acknowledge it within 600 seconds`,
568
+ );
569
+ if (ack.index !== index) {
570
+ throw new Error(
571
+ `export worker protocol violation: frame ${index} was acknowledged as ${ack.index}`,
572
+ );
573
+ }
574
+ } finally {
575
+ // If converse refuses before postMessage (an already-aborted
576
+ // signal or dead channel), ownership never left this thread.
577
+ // close() is also harmless after a successful transfer.
578
+ bitmap.close();
579
+ }
580
+ onProgress?.(index + 1, count);
581
+ }
582
+ const done = await workerDeadline(
583
+ (alive) => talk.request({ type: "finish" }, ["done"], undefined, () => alive()),
584
+ "export encoder made no progress finishing the file for 600 seconds",
585
+ );
586
+ return {
587
+ blob: new Blob([done.data], { type: done.mimeType }),
588
+ extension: done.extension,
589
+ ...(audioSkippedReason !== undefined ? { audioSkippedReason } : {}),
590
+ ...(started.encoding !== undefined ? { encoding: started.encoding } : {}),
591
+ };
592
+ } catch (e) {
593
+ // Best-effort worker-side teardown; terminate() below is the backstop.
594
+ try {
595
+ worker.postMessage({ type: "cancel" });
596
+ } catch {
597
+ // worker already dead
598
+ }
599
+ throw e;
600
+ } finally {
601
+ talk.dispose();
602
+ worker.terminate();
603
+ }
604
+ } finally {
605
+ leave();
606
+ }
607
+ },
608
+
609
+ async captureStill({ frame, width, height, type, quality, signal }) {
610
+ if (active) throw new Error("an export is already running");
611
+ const comp = requireComposition();
612
+ const total = host.scopeFrames(undefined) ?? 0;
613
+ if (!Number.isSafeInteger(frame) || frame < 0 || frame >= total) {
614
+ throw new Error(`frame ${frame} is outside this Project's timeline (0..${total - 1})`);
615
+ }
616
+ for (const [name, px] of [["width", width], ["height", height]] as const) {
617
+ if (!Number.isSafeInteger(px) || px < 1) {
618
+ throw new Error(`still ${name} must be a positive whole number of pixels, got ${px}`);
619
+ }
620
+ }
621
+ let bitmap: ImageBitmap;
622
+ enter(undefined);
623
+ try {
624
+ // Always the cold budget: the still's scene is usually not the one
625
+ // on screen, and an export never warms ahead.
626
+ const captured = captureFrame(frame, FIRST_FRAME_TIMEOUT_MS, comp, signal);
627
+ host.seek(frame);
628
+ bitmap = await captured;
629
+ } finally {
630
+ // The stage goes back to the author before the encode: scaling and
631
+ // compressing the picture needs the bitmap, not export framing.
632
+ leave();
633
+ }
634
+ try {
635
+ return await deps.encodeStill(bitmap, width, height, type, quality);
636
+ } finally {
637
+ bitmap.close();
638
+ }
639
+ },
640
+ };
641
+ }
@@ -2,19 +2,24 @@
2
2
  //
3
3
  // Every editor-only object in the working pane is drawn with a MATERIAL colour,
4
4
  // and a material colour cannot read a CSS custom property the way an SVG fill
5
- // can. Resolving one at mount, inside the stage document — which imports
6
- // shotTokens.css — is what lets the gizmo, the grid and the SVG overlay share
7
- // the single definition in that file instead of each carrying its own literal.
8
- //
9
- // Not for GL CLEAR colours: SplitRenderer's clay backdrop is hard-coded because
10
- // it is written before any style has resolved.
5
+ // can. Resolving one when the appearance-keyed material is built inside the
6
+ // stage document is what lets the gizmo, grid, clear colours and SVG overlay
7
+ // share the single definitions in shotTokens.css instead of carrying literals.
11
8
  import { Color } from "three";
12
9
 
10
+ export const STAGE_THEME_TOKENS_ID = "shot-stage-theme-tokens";
11
+
12
+ /** Read from the editor-only probe when the real stage boot installed it.
13
+ * Tests and isolated component mounts keep the original root fallback. */
14
+ export function tokenValue(name: string, fallback: string): string {
15
+ if (typeof document === "undefined") return fallback;
16
+ const source = document.getElementById(STAGE_THEME_TOKENS_ID) ?? document.documentElement;
17
+ return getComputedStyle(source).getPropertyValue(name).trim() || fallback;
18
+ }
19
+
13
20
  export function tokenColor(name: string, fallback: string): Color {
14
21
  const backstop = new Color(fallback);
15
- if (typeof document === "undefined") return backstop;
16
- const token = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
17
- if (token.length === 0) return backstop;
22
+ const token = tokenValue(name, fallback);
18
23
  try {
19
24
  return new Color(token);
20
25
  } catch {