@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,251 @@
1
+ // ONE SHOT CARD on the board (Paper SB1): one planned shot, or one Shot no
2
+ // plan links, read in playing order (`storyEntries`). A storyboard panel:
3
+ //
4
+ // picture the built frame once "Mark shot built" ran (with a Built
5
+ // badge), else Sketch 1 as the still stage draws it (`MiniSketch`
6
+ // with `useSketchThumbs`, drawn in the DOM until that is ready)
7
+ // with one dot per sketch to peek at the others, else the Shot's
8
+ // first still, else empty paper.
9
+ // body the number, the name and the status badge (`shotStatusOf`:
10
+ // Built, Building while this session's agent run is out for it,
11
+ // else Planned; never stored), then one line: the voice in curly
12
+ // quotes, else What we see.
13
+ // footer how many sketches and pinned cards, and the length.
14
+ //
15
+ // A PRESS SELECTS (the Plan panel opens on the right), A DOUBLE PRESS OR
16
+ // ENTER OPENS THE SHOT BOARD, never the Studio. The board owns the double
17
+ // press (BoardCanvas), because the first press can rewrap the board under the
18
+ // pointer. A DRAG MOVES IT (`SHOT_MIME`):
19
+ // the board draws the insertion line and writes the order (`moveBoardEntry`).
20
+ // Its open comment counts show while it is selected (S5: the plan never
21
+ // hides work).
22
+ //
23
+ // A DROP PINS STRAIGHT TO THIS SHOT (S2 note 3): a board card moves onto it,
24
+ // files upload onto it, no dialog. A Shot no plan links gets its planned shot
25
+ // in the same edit (`scene`, `pinPartFor`).
26
+ //
27
+ // `data-comment-target` names the card for the comment tools (lane M).
28
+ import { useState } from "react";
29
+ import type { DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent } from "react";
30
+ import { framesToSeconds } from "@dailies-studio/engine";
31
+ import type { Doc3D } from "@dailies-studio/shot/doc";
32
+ import { useShotEditor, useStore } from "../../state/context.js";
33
+ import { SHOT_STATUS_LABEL, isReferenceId, shotLengthOf, shotStatusOf } from "../../state/storyboardModel.js";
34
+ import type { ShotStatus, StoryEntry } from "../../state/storyboardModel.js";
35
+ import { CHECK_ICON } from "../../icons.js";
36
+ import { ASSET_MIME, readAssetDrag } from "../files/assetDrag.js";
37
+ import { CardCommentCounts } from "../comments/parts.js";
38
+ import { formatDuration } from "../timeline/model.js";
39
+ import type { Press } from "./boardRows.js";
40
+ import { entryIdOf } from "./boardOrder.js";
41
+ import { MiniSketch } from "./MiniSketch.js";
42
+ import { useSketchThumbs, useStillThumb } from "./StillThumbs.js";
43
+ import { STORYBOARD_MIME, acceptedFiles, readDragPayload, refusedFilesMessage } from "./storyboardDrop.js";
44
+ import { useBuildingParts } from "../agent/AgentDots.js";
45
+
46
+ export { entryIdOf } from "./boardOrder.js";
47
+
48
+ /** A shot card dragged to a new place on the board: its entry id. Its own
49
+ * type, so a card drop (`STORYBOARD_MIME`) never takes it. */
50
+ export const SHOT_MIME = "application/x-dailies-shot";
51
+ /** What a drop on a card can take. */
52
+ const CARD_DROP_TYPES = [STORYBOARD_MIME, ASSET_MIME, "Files"];
53
+ /** The card's picture width, CSS px (Paper 346): what its images fetch for. */
54
+ const PICTURE_PX = 346;
55
+
56
+ /** The badge tone each status reads in (G1 `.badge`). */
57
+ const STATUS_TONE: Record<ShotStatus, string> = { planned: "", building: " is-building", built: " is-success" };
58
+
59
+ /** "2 sketches · 1 pinned". */
60
+ function sketchPinWords(sketches: number, pinned: number): string {
61
+ return `${sketches} ${sketches === 1 ? "sketch" : "sketches"} · ${pinned} pinned`;
62
+ }
63
+
64
+ /** Six dots: the drag grip (Paper SB1, on hover). */
65
+ const GRIP_ICON = (
66
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
67
+ <circle cx="6" cy="4" r="1.25" />
68
+ <circle cx="10" cy="4" r="1.25" />
69
+ <circle cx="6" cy="8" r="1.25" />
70
+ <circle cx="10" cy="8" r="1.25" />
71
+ <circle cx="6" cy="12" r="1.25" />
72
+ <circle cx="10" cy="12" r="1.25" />
73
+ </svg>
74
+ );
75
+
76
+ export function ShotCard(props: {
77
+ doc: Doc3D;
78
+ entry: StoryEntry;
79
+ /** Its place in `storyEntries`, from 0. */
80
+ at: number;
81
+ selected: boolean;
82
+ onPress: (id: string, press: Press) => void;
83
+ onOpen: (entry: StoryEntry) => void;
84
+ onMenu: (entry: StoryEntry, event: ReactMouseEvent) => void;
85
+ /** A drag of this card began or ended; the board draws the insertion line. */
86
+ onDragMove?: (id: string | null) => void;
87
+ }) {
88
+ const { doc, entry, at, selected } = props;
89
+ const store = useStore();
90
+ const building = useBuildingParts();
91
+ const readOnly = useShotEditor((s) => s.readOnly);
92
+ const part = entry.kind === "shot" ? entry.shot.part : entry.part;
93
+ const shot = entry.kind === "shot" ? entry.shot : null;
94
+ const id = entryIdOf(entry);
95
+ const name = entry.kind === "shot" ? entry.name : entry.part.label;
96
+ const status = shotStatusOf(part, building);
97
+ const length = shotLengthOf(doc, part, shot?.scene ?? null);
98
+ const pins = part === null ? 0 : (doc.resources?.references ?? []).filter((r) => r.part === part.id).length;
99
+ const number = String(at + 1).padStart(2, "0");
100
+ const line = part?.voice !== undefined ? `“${part.voice}”` : part?.notes;
101
+
102
+ const onDragOver = (event: ReactDragEvent): void => {
103
+ const types = Array.from(event.dataTransfer.types ?? []);
104
+ if (!types.some((type) => CARD_DROP_TYPES.includes(type))) return;
105
+ event.preventDefault();
106
+ event.stopPropagation();
107
+ event.dataTransfer.dropEffect = types.includes("Files") ? "copy" : "move";
108
+ event.currentTarget.classList.add("is-drop");
109
+ };
110
+ const onDragLeave = (event: ReactDragEvent<HTMLElement>): void => {
111
+ if (!event.currentTarget.contains(event.relatedTarget as Node | null)) event.currentTarget.classList.remove("is-drop");
112
+ };
113
+ const onDrop = (event: ReactDragEvent<HTMLElement>): void => {
114
+ event.currentTarget.classList.remove("is-drop");
115
+ const data = event.dataTransfer;
116
+ const lane = part?.id ?? null;
117
+ const opts = part === null && shot !== null ? { scene: shot.sceneId } : {};
118
+ const payload = readDragPayload(data);
119
+ const asset = payload === null ? readAssetDrag(data) : null;
120
+ const files = Array.from(data.files ?? []);
121
+ if (payload === null && asset === null && files.length === 0) return;
122
+ event.preventDefault();
123
+ event.stopPropagation();
124
+ if (payload?.kind === "reference") {
125
+ const live = store.getState().storyboardSelection;
126
+ const ids = live.includes(payload.id) ? live.filter(isReferenceId) : [payload.id];
127
+ store.attachStoryboardItems(ids, lane, opts);
128
+ } else if (asset !== null) {
129
+ store.addReferenceForAsset(asset.asset, lane, { ...opts, select: false });
130
+ } else if (files.length > 0) {
131
+ const taken = acceptedFiles(files);
132
+ if (taken.length > 0) void store.addStoryboardFiles(taken, lane, { ...opts, select: false });
133
+ const refused = refusedFilesMessage(files);
134
+ if (refused !== null) store.reportError(refused);
135
+ }
136
+ };
137
+
138
+ return (
139
+ <article
140
+ className={`shot-card${selected ? " is-selected" : ""}`}
141
+ data-testid={`shot-card-${id}`}
142
+ data-comment-target={id}
143
+ data-status={status}
144
+ role="option"
145
+ aria-selected={selected}
146
+ aria-label={`${number} ${name}, ${SHOT_STATUS_LABEL[status]}`}
147
+ tabIndex={0}
148
+ draggable={!readOnly}
149
+ onDragStart={(event) => {
150
+ if (event.target !== event.currentTarget) return;
151
+ event.dataTransfer.setData(SHOT_MIME, id);
152
+ event.dataTransfer.effectAllowed = "move";
153
+ props.onDragMove?.(id);
154
+ }}
155
+ onDragEnd={() => props.onDragMove?.(null)}
156
+ onClick={(event) => props.onPress(id, event)}
157
+ onKeyDown={(event) => {
158
+ if (event.target !== event.currentTarget) return;
159
+ if (event.key === "Enter") props.onOpen(entry);
160
+ else if (event.key === " ") {
161
+ event.preventDefault();
162
+ props.onPress(id, event);
163
+ }
164
+ }}
165
+ onContextMenu={(event) => props.onMenu(entry, event)}
166
+ onDragOver={onDragOver}
167
+ onDragLeave={onDragLeave}
168
+ onDrop={onDrop}
169
+ >
170
+ <ShotPicture doc={doc} entry={entry} />
171
+ {readOnly ? null : (
172
+ <span className="shot-card-grip" aria-hidden="true" title="Drag to move">
173
+ {GRIP_ICON}
174
+ </span>
175
+ )}
176
+ <div className="shot-card-body">
177
+ <div className="shot-card-title">
178
+ <span className="shot-card-number">{number}</span>
179
+ <span className="shot-card-name">{name}</span>
180
+ <span className={`badge${STATUS_TONE[status]}`} data-status={status} data-testid="shot-status">
181
+ {SHOT_STATUS_LABEL[status]}
182
+ </span>
183
+ </div>
184
+ {line === undefined ? null : (
185
+ <p className={part?.voice !== undefined ? "shot-card-voice" : "shot-card-notes"} title={line}>
186
+ {line}
187
+ </p>
188
+ )}
189
+ </div>
190
+ <footer className="shot-card-foot">
191
+ <span className="shot-card-counts">{sketchPinWords(part?.sketches?.length ?? 0, pins)}</span>
192
+ {/* The counts show on the selected card only (S5). */}
193
+ {part === null || !selected ? null : <CardCommentCounts target={{ partId: part.id }} />}
194
+ <span className="shot-card-length">{formatDuration(framesToSeconds(length, doc.fps))}</span>
195
+ </footer>
196
+ </article>
197
+ );
198
+ }
199
+
200
+ /** The card's picture: the built frame, Sketch 1 (and a peek at the others),
201
+ * the Shot's first still, or empty paper. */
202
+ function ShotPicture({ doc, entry }: { doc: Doc3D; entry: StoryEntry }) {
203
+ const part = entry.kind === "shot" ? entry.shot.part : entry.part;
204
+ const shot = entry.kind === "shot" ? entry.shot : null;
205
+ const sketches = part?.sketches ?? [];
206
+ const [peek, setPeek] = useState(0);
207
+ const built = shot !== null && part?.built !== undefined;
208
+ // Both stills draw through the board's still queue, so a picture shown here
209
+ // and in the Studio is drawn once.
210
+ const frame = shot === null ? null : built ? part!.built!.frame : sketches.length > 0 ? null : (shot.stills[0]?.frame ?? null);
211
+ const thumb = useStillThumb(shot?.sceneId ?? "", frame ?? 0);
212
+ const shown = sketches[Math.min(peek, sketches.length - 1)];
213
+ // The shown sketch as the still stage draws it, asked for only when the
214
+ // card shows a sketch.
215
+ const sketchPicture = useSketchThumbs(part?.id ?? "", (shot !== null && frame !== null) || shown === undefined ? [] : [shown.id]);
216
+ if (shot !== null && frame !== null) {
217
+ return (
218
+ <div className="shot-card-picture" data-picture={built ? "built" : "frame"} data-status={thumb?.status ?? "pending"}>
219
+ {thumb?.url === undefined ? null : <img src={thumb.url} alt="" draggable={false} />}
220
+ {built ? (
221
+ <span className="shot-card-built">
222
+ {CHECK_ICON}
223
+ Built
224
+ </span>
225
+ ) : null}
226
+ </div>
227
+ );
228
+ }
229
+ if (shown === undefined) {
230
+ return (
231
+ <div className="shot-card-picture is-paper" data-picture="empty">
232
+ <span className="shot-card-sketch-empty">No sketch yet</span>
233
+ </div>
234
+ );
235
+ }
236
+ return (
237
+ <div className="shot-card-picture is-paper" data-picture="sketch" onPointerLeave={() => setPeek(0)}>
238
+ <MiniSketch doc={doc} sketch={shown} width={PICTURE_PX} picture={sketchPicture.get(shown.id) ?? null} />
239
+ <span className="shot-card-dots">
240
+ {sketches.map((sketch, index) => (
241
+ <span
242
+ key={sketch.id}
243
+ className={`shot-card-dot${sketch === shown ? " is-shown" : ""}`}
244
+ title={sketch.label ?? `Sketch ${index + 1}`}
245
+ onPointerEnter={() => setPeek(index)}
246
+ />
247
+ ))}
248
+ </span>
249
+ </div>
250
+ );
251
+ }
@@ -0,0 +1,348 @@
1
+ // THE ONE LIVE PREVIEW STAGE: any in-memory `Doc3D`, drawn by a second
2
+ // `/shot-stage.html`, looped or held on one frame. The component card's
3
+ // preview (`ComponentStage`, which builds its one-instance document) and the
4
+ // shot board's Play (`sketchStageDoc`) both mount it.
5
+ //
6
+ // WHY A SECOND STAGE AND NOT THE HARNESS. `/preview.html?component=<id>`
7
+ // frames a three host through `DefaultRig` (a generic fov-50 camera at z 5),
8
+ // while a shot-lane component is authored in stage pixels on the Project's
9
+ // frame box, so the harness showed a CROP of the middle of the design (9 of
10
+ // the 20 shipped components one flat colour, measured 2026-09-25). The stage
11
+ // takes any in-memory `Doc3D` through `loadDoc` (it never fetches one), so
12
+ // this mounts `/shot-stage.html` a second time and hands it the caller's
13
+ // document. Nothing is saved, and no bridge message is new.
14
+ //
15
+ // LOOPING, OR ONE FRAME. The stage owns no clock (the chrome drives every
16
+ // seek), so the preview plays by seeking its own stage from a rAF loop, at the
17
+ // document's fps, over `lengthFrames`. A caller that names a `frame` shows
18
+ // that frame and holds it.
19
+ //
20
+ // HOSTED, StagePanel's rule: no src until `__dailiesHost.ready` settles. But
21
+ // NOT `host.stageLoaded`: the host tracks ONE stage (the main one, which it
22
+ // reloads when the Service Worker refuses it a module), so this preview does
23
+ // that call's two jobs itself: a document the Service Worker does not control
24
+ // is reloaded once, and the current epoch is posted again on every load,
25
+ // because the epoch channel does not replay. Locally there is no host and none
26
+ // of this runs.
27
+ //
28
+ // IN VIEW AND IN BUDGET. The stage mounts only while it is on screen and holds
29
+ // a slot of the shared budget (liveCap.ts), keyed by the caller's `slotKey`,
30
+ // so a card that already holds that slot shares it rather than taking a
31
+ // second. A caller may hold it back (`live`): the board's cards go live only
32
+ // on hover, focus or selection, and show their POSTER at rest (posterCache.ts),
33
+ // which a live stage captures under the caller's `posterKey` once it is ready.
34
+ //
35
+ // NEVER `.stage-frame` or `.stage-window`: the first is pinned full-window by
36
+ // shell.css, and `measureInset` reads the second as the main stage's hole.
37
+ // The classes stay `component-stage*`, where storyboard.css styles them.
38
+ import { useEffect, useRef, useState } from "react";
39
+ import type { ReactNode } from "react";
40
+ import type { Doc3D } from "@dailies-studio/shot/doc";
41
+ import { readStudioThemePreference, resolveStudioTheme } from "@dailies-studio/ui/theme";
42
+ import { connectStage } from "../../bridge.js";
43
+ import { MODULE_EPOCH_CHANNEL, dailiesHost } from "../../hostApi.js";
44
+ import type { ShotBridgeHandlers, ShotStageBridge } from "../../bridgeTypes.js";
45
+ import { stageSrc } from "../stage/StagePanel.js";
46
+ import { useInView, useLiveSlot } from "./liveCap.js";
47
+ import { usePoster, writePoster } from "./posterCache.js";
48
+
49
+ /** The poster's width in px: twice the widest card that shows it. */
50
+ export const POSTER_WIDTH = 480;
51
+
52
+ /** The poster's size for a frame box: `POSTER_WIDTH` wide, the box's aspect. */
53
+ export function posterSize(size: { width: number; height: number }): { width: number; height: number } {
54
+ return { width: POSTER_WIDTH, height: Math.max(1, Math.round((POSTER_WIDTH * size.height) / size.width)) };
55
+ }
56
+
57
+ /** The frame a loop that started `elapsedMs` ago shows: the document's fps,
58
+ * wrapped over `lengthFrames` (at least one). */
59
+ export function loopFrameAt(elapsedMs: number, fps: number, lengthFrames: number): number {
60
+ return Math.floor((Math.max(0, elapsedMs) / 1000) * fps) % Math.max(1, Math.round(lengthFrames));
61
+ }
62
+
63
+ export interface StagePreviewProps {
64
+ /** What the stage draws. Null holds it on its loading line. A new identity
65
+ * reloads the stage document, so a caller memoizes it on what it reads. */
66
+ doc: Doc3D | null;
67
+ /** The frame box: the aspect ratio and the poster's. */
68
+ size: { width: number; height: number };
69
+ /** How many frames the loop runs over. */
70
+ lengthFrames: number;
71
+ /** Shows this frame and holds it: no loop runs. */
72
+ frame?: number;
73
+ /** Whether the stage may go live. False shows the poster (default true). */
74
+ live?: boolean;
75
+ /** The live budget slot this preview takes (liveCap.ts). */
76
+ slotKey: string;
77
+ /** The poster cache key (posterCache.ts). Null captures and shows none. */
78
+ posterKey: string | null;
79
+ /** The iframe's accessible name. */
80
+ title: string;
81
+ className?: string;
82
+ /** What shows while the stage is not live and no poster is cached yet. */
83
+ fallback?: ReactNode;
84
+ /** The stage said it drew the document. */
85
+ onReady?: () => void;
86
+ }
87
+
88
+ export function StagePreview(props: StagePreviewProps) {
89
+ const { doc, size, lengthFrames, frame } = props;
90
+ const iframeRef = useRef<HTMLIFrameElement | null>(null);
91
+ const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null);
92
+ const inView = useInView(wrapper);
93
+ const wantLive = props.live ?? true;
94
+ const live = useLiveSlot(props.slotKey, inView && wantLive);
95
+ // THE SRC IS FIXED AT MOUNT, StagePanel's rule: a src that followed the
96
+ // theme would reboot the stage on every appearance change. Read from the
97
+ // provider's own source, not its context, so a preview mounted outside the
98
+ // provider (a suite) draws rather than taking the inspector down with it.
99
+ const src = useRef(stageSrc(resolveStudioTheme(readStudioThemePreference()))).current;
100
+ // Read once: the boot script installs it before the chrome runs, or never.
101
+ const [host] = useState(dailiesHost);
102
+ const [hostSettled, setHostSettled] = useState(host === undefined);
103
+ const reloaded = useRef(false);
104
+ const [bridge, setBridge] = useState<ShotStageBridge | null>(null);
105
+ const [status, setStatus] = useState<"loading" | "ready" | string>("loading");
106
+ // A ref, so a caller's fresh callback never reconnects the stage.
107
+ const onReady = useRef(props.onReady);
108
+ onReady.current = props.onReady;
109
+ const fps = doc === null ? 0 : doc.fps.num / doc.fps.den;
110
+ const poster = usePoster(props.posterKey);
111
+ // TRUE WHILE A POSTER CAPTURE IS OUT: the loop holds its seeks, which would
112
+ // move the stage under the capture.
113
+ const capturing = useRef(false);
114
+
115
+ // ── CONNECT, AND AGAIN ON EVERY LOAD ────────────────────────────────────
116
+ //
117
+ // Saving a component's source reloads every stage iframe, this one too, and
118
+ // a bridge from the old document is dead. So every load event reconnects.
119
+ useEffect(() => {
120
+ if (host === undefined) return;
121
+ let alive = true;
122
+ // A host that failed says so on the main stage; this one still loads.
123
+ const settle = (): void => {
124
+ if (alive) setHostSettled(true);
125
+ };
126
+ host.ready.then(settle, settle);
127
+ return () => {
128
+ alive = false;
129
+ };
130
+ }, [host]);
131
+
132
+ useEffect(() => {
133
+ const iframe = iframeRef.current;
134
+ if (iframe === null || !hostSettled || !live) return;
135
+ const aborter = new AbortController();
136
+ const handlers = previewHandlers({
137
+ onReady: () => {
138
+ setStatus("ready");
139
+ onReady.current?.();
140
+ },
141
+ onError: (message) => setStatus(message),
142
+ });
143
+ const connect = (): void => {
144
+ setBridge(null);
145
+ setStatus("loading");
146
+ void connectStage(iframe, handlers, { signal: aborter.signal })
147
+ .then((next) => {
148
+ if (aborter.signal.aborted) return;
149
+ // THE CAMERA ONLY, no furniture: the storyboard preview's own mode.
150
+ next.setStoryboardPreview(true);
151
+ setBridge(next);
152
+ })
153
+ .catch((e: unknown) => {
154
+ if (!aborter.signal.aborted) setStatus(e instanceof Error ? e.message : String(e));
155
+ });
156
+ };
157
+ const onLoad = (): void => {
158
+ const stage = iframe.contentWindow;
159
+ if (host !== undefined && stage !== null) {
160
+ if (stage.navigator.serviceWorker?.controller == null && !reloaded.current) {
161
+ reloaded.current = true;
162
+ stage.location.reload();
163
+ return;
164
+ }
165
+ const version = host.compileVersion();
166
+ if (version !== null) {
167
+ const channel = new BroadcastChannel(MODULE_EPOCH_CHANNEL);
168
+ channel.postMessage(version);
169
+ channel.close();
170
+ }
171
+ }
172
+ connect();
173
+ };
174
+ iframe.addEventListener("load", onLoad);
175
+ // Hosted, the src was set in the commit this effect follows, so the load
176
+ // is still ahead.
177
+ if (host === undefined) connect();
178
+ return () => {
179
+ aborter.abort();
180
+ iframe.removeEventListener("load", onLoad);
181
+ // A slot handed on unmounts the iframe, and its bridge with it: drop
182
+ // it, or the loop below keeps seeking a dead document.
183
+ setBridge(null);
184
+ setStatus("loading");
185
+ };
186
+ }, [host, hostSettled, live]);
187
+
188
+ // ── THE DOCUMENT, then the frame ────────────────────────────────────────
189
+ useEffect(() => {
190
+ if (bridge === null || doc === null) return;
191
+ bridge.loadDoc(doc);
192
+ bridge.seek(frame ?? 0);
193
+ }, [bridge, doc, frame]);
194
+
195
+ // ── THE POSTER, once the stage is ready ─────────────────────────────────
196
+ //
197
+ // Each live session captures again, so a poster made before a code change
198
+ // the key does not see is replaced the next time the card goes live.
199
+ const posterKey = props.posterKey;
200
+ const { width, height } = size;
201
+ useEffect(() => {
202
+ if (bridge === null || status !== "ready" || posterKey === null) return;
203
+ const aborter = new AbortController();
204
+ capturing.current = true;
205
+ const out = posterSize({ width, height });
206
+ void bridge
207
+ .captureFrame({ frame: frame ?? 0, width: out.width, height: out.height, format: "webp", signal: aborter.signal })
208
+ .then((blob) => {
209
+ if (!aborter.signal.aborted) writePoster(posterKey, blob);
210
+ })
211
+ // A failed poster leaves the caller's fallback; the live stage still plays.
212
+ .catch(() => {})
213
+ .finally(() => {
214
+ capturing.current = false;
215
+ });
216
+ return () => {
217
+ aborter.abort();
218
+ capturing.current = false;
219
+ };
220
+ }, [bridge, status, posterKey, frame, width, height]);
221
+
222
+ // ── THE LOOP, when no frame is named ────────────────────────────────────
223
+ //
224
+ // One seek per NEW frame, at the document's fps: a rAF that seeked every
225
+ // tick would ask the stage for the same picture twice at 60 Hz.
226
+ useEffect(() => {
227
+ if (bridge === null || frame !== undefined || fps <= 0) return;
228
+ let raf = 0;
229
+ let last = -1;
230
+ const start = performance.now();
231
+ const tick = (now: number): void => {
232
+ const next = loopFrameAt(now - start, fps, lengthFrames);
233
+ if (next !== last && !capturing.current) {
234
+ last = next;
235
+ bridge.seek(next);
236
+ }
237
+ raf = requestAnimationFrame(tick);
238
+ };
239
+ raf = requestAnimationFrame(tick);
240
+ return () => cancelAnimationFrame(raf);
241
+ }, [bridge, frame, fps, lengthFrames]);
242
+
243
+ const ready = live && status === "ready";
244
+ const aspectRatio = `${width} / ${height}`;
245
+ return (
246
+ <div
247
+ ref={setWrapper}
248
+ className={props.className === undefined ? "component-stage" : `component-stage ${props.className}`}
249
+ data-live={ready ? "true" : undefined}
250
+ style={{ aspectRatio }}
251
+ >
252
+ {/* THE POSTER UNDER A STAGE THAT IS NOT READY, so going live never
253
+ flashes an empty ground. */}
254
+ {!ready && poster !== null ? <img className="component-stage-poster" src={poster} alt="" draggable={false} /> : null}
255
+ {!live && poster === null ? props.fallback : null}
256
+ {live ? (
257
+ <iframe
258
+ ref={iframeRef}
259
+ className="component-stage-frame"
260
+ src={hostSettled ? src : undefined}
261
+ title={props.title}
262
+ tabIndex={-1}
263
+ style={{ aspectRatio, opacity: ready || poster === null ? 1 : 0 }}
264
+ />
265
+ ) : null}
266
+ {!live ? (
267
+ inView && wantLive && poster === null ? (
268
+ <span className="component-stage-status" role="status">
269
+ Preview paused
270
+ </span>
271
+ ) : null
272
+ ) : status === "ready" || poster !== null ? null : (
273
+ <span className="component-stage-status" role="status">
274
+ {status === "loading" ? "Loading the preview…" : status}
275
+ </span>
276
+ )}
277
+ </div>
278
+ );
279
+ }
280
+
281
+ /**
282
+ * Every bridge handler, as a no-op except the two this preview reads. An
283
+ * EXPLICIT object, so a handler the bridge grows later is a compile error
284
+ * here rather than a silent `undefined` the stage calls. The boolean ones
285
+ * answer false: the preview takes no gesture, no key and no key light.
286
+ */
287
+ function previewHandlers(on: { onReady(): void; onError(message: string): void }): ShotBridgeHandlers {
288
+ const none = (): void => {};
289
+ const refuse = (): boolean => false;
290
+ return {
291
+ onReady: () => on.onReady(),
292
+ onError: (message) => on.onError(message),
293
+ onStageBusy: none,
294
+ onPerformance: none,
295
+ onMaterialCompile: none,
296
+ onPick: none,
297
+ onEnterLayer: none,
298
+ onTextEdit: none,
299
+ onContextMenu: none,
300
+ onMeshStats: none,
301
+ onMeshMaterialSlots: none,
302
+ onInstanceNaturalSize: none,
303
+ onTextAutoSize: none,
304
+ onCommentPin: none,
305
+ onStudioCommentAt: none,
306
+ onKeyLightStart: refuse,
307
+ onKeyLight: none,
308
+ onKeyLightEnd: none,
309
+ onIsolationOrbitTaken: none,
310
+ onFitRequest: none,
311
+ onViewChange: none,
312
+ onWorkingViewChange: none,
313
+ onActivePane: none,
314
+ onGestureStart: refuse,
315
+ onGesture: none,
316
+ onGestureEnd: none,
317
+ onFramingGestureStart: refuse,
318
+ onFramingGesture: refuse,
319
+ onFramingGestureEnd: none,
320
+ onLeavePilot: none,
321
+ onCameraDriver: none,
322
+ onWaypointGesture: none,
323
+ onDraw: none,
324
+ onPenCommit: none,
325
+ onSplineCreateCommit: none,
326
+ onSplinePointPick: none,
327
+ onSplinePointGestureStart: refuse,
328
+ onSplinePointGesture: none,
329
+ onSplinePointGestureEnd: none,
330
+ onSplineSegmentInsert: none,
331
+ onSplinePointDelete: none,
332
+ onSplinePointModeEnd: none,
333
+ onGuideGestureStart: refuse,
334
+ onGuideGesture: none,
335
+ onGuideGestureEnd: none,
336
+ onGuideSet: none,
337
+ onGuideRemove: none,
338
+ onPathKeyGestureStart: refuse,
339
+ onPathKeyGesture: none,
340
+ onPathKeyGestureEnd: none,
341
+ onPathKeyDoubleClick: none,
342
+ onKey: refuse,
343
+ // The preview's own failures show in its status line. Stage health is
344
+ // the main stage's report to an agent, never this one's.
345
+ onPieceFailures: none,
346
+ onStageHealth: none,
347
+ };
348
+ }