@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
@@ -21,7 +21,34 @@
21
21
  // `three`. The stage PROJECTS; the chrome receives screen-space numbers as
22
22
  // plain data. test/sourceHygiene.test.ts is the gate, and the temptation it
23
23
  // exists to catch is importing `Vector3` "just for the projection maths".
24
- import type { Doc3D, FramingDelta, GestureDelta, GestureKind, PathData, SnapGuide } from "@dailies-studio/shot/doc";
24
+ import type {
25
+ CameraGate,
26
+ CoverageRequest,
27
+ Doc3D,
28
+ ExportFormat,
29
+ FramingDelta,
30
+ GestureDelta,
31
+ GestureKind,
32
+ MeshMaterialSlotInfo,
33
+ MotionPresetRequest,
34
+ PathData,
35
+ PieceFailure,
36
+ SnapGuide,
37
+ SplinePoint3D,
38
+ StageCoverage,
39
+ StageHealthSnapshot,
40
+ } from "@dailies-studio/shot/doc";
41
+ // Tier-1 export speaks the shared wire protocol's capability shape verbatim —
42
+ // no duplication needed here: both documents resolve the SAME package, so this
43
+ // is one definition, not a copy (the motion editor duplicates because it sits
44
+ // across a package boundary; this file does not).
45
+ import type { Mp4Encoding, Tier1Capability } from "@dailies-studio/shot/tier1";
46
+ // Engine `Value` only — the wire shape of an instance prop. Type-only and
47
+ // three-free, the same import the instance inspector makes.
48
+ import type { Value } from "@dailies-studio/engine";
49
+ // A component's contract types an `instance.props.<key>` key (Count up), and
50
+ // the stage has no registry of its own to read one from.
51
+ import type { Contract } from "@dailies-studio/contract";
25
52
  // ONE definition of the tool union, in the table that also answers what each
26
53
  // tool's key, surface, cursor and draw behaviour are. Re-exported here so the
27
54
  // stage can take the protocol from one import.
@@ -47,9 +74,10 @@ export type { ShadingMode, StageLayout, StagePane, WorkingView };
47
74
  * for a working pane would get a silently unchanged camera pane — exactly the
48
75
  * half-working state the refusal exists for.
49
76
  *
50
- * 3: the camera lane. A v2 stage has no `setCameraFollowsView`, so a v3 chrome
51
- * turning follow on would throw inside the push and, worse, would show its
52
- * armed button over a pane that is not following anything.
77
+ * 3: the camera lane. A v2 stage had no `setCameraFollowsView` (v29's
78
+ * `setPilot` now), so a v3 chrome turning follow on would throw inside the
79
+ * push and, worse, would show its armed button over a pane that is not
80
+ * following anything.
53
81
  *
54
82
  * 4: `onWorkingViewChange` carries a WorkingViewOrigin. A v3 stage sends none,
55
83
  * so every one of its view changes reads as "the author moved the view" — and
@@ -114,8 +142,715 @@ export type { ShadingMode, StageLayout, StagePane, WorkingView };
114
142
  * that never appears on a toon mesh, and an outliner whose triangle budget
115
143
  * reports an empty scene however heavy the models are. Nothing throws and
116
144
  * nothing is wrong on screen; the author simply believes three features are
117
- * broken and one number is true. */
118
- export const SHOT_BRIDGE_VERSION = 11;
145
+ * broken and one number is true.
146
+ *
147
+ * 12: `exportCapability` and `exportMovie` — Tier-1 in-browser export, and
148
+ * the bridge's first Promise-returning members (same-origin, so the promise
149
+ * and the finished Blob cross as plain references). A v11 stage under a v12
150
+ * chrome throws "exportMovie is not a function" the moment the dialog's
151
+ * browser path runs; the gate turns that into the rebuild message at
152
+ * connect.
153
+ *
154
+ * 13: `capturePoster` — the dashboard's card thumbnail, read off the live GL
155
+ * canvas. A v12 stage under a v13 chrome would simply never produce a
156
+ * poster, which is the quietest possible failure (a gradient placeholder
157
+ * forever, and nothing in the console); the version gate turns it into the
158
+ * rebuild message instead.
159
+ *
160
+ * 14: `setComponentContext` — the component context (unify P2). A v13 stage
161
+ * has no verb, so a v14 chrome entering a definition would throw inside the
162
+ * push; and the quiet direction is worse: the chrome's outline, dock and
163
+ * inspector would all address the definition while the stage kept rendering
164
+ * the SCENE at master time — every def-local seek the dock issues would
165
+ * scrub the scene instead, and a pick would land on layers the chrome
166
+ * considers out of scope. `seek` also changes MEANING under a context
167
+ * (def-local frames), which no optional-feature fallback can express.
168
+ *
169
+ * 15: 3D spline authoring. Work-plane state, spline drafts, point-edit
170
+ * gestures and projected non-quad selection shapes all cross the bridge. A
171
+ * v14 stage would leave Shift+P armed over an orbiting pane and a v14 chrome
172
+ * would ignore the point edits a v15 stage emits, so neither direction has a
173
+ * truthful optional fallback.
174
+ *
175
+ * 16: material-workspace preview ownership and shader compile diagnostics.
176
+ * The stage must stop scene picking/transforms while the graph owns the
177
+ * pointer, and the chrome must distinguish a last-good preview from a graph
178
+ * the GPU rejected. A v15 half would silently violate one of those claims.
179
+ *
180
+ * 17: material SLOTS and node PREVIEWS. `setMaterialPreview` addresses a
181
+ * submesh slot rather than a whole layer, `onMaterialCompile` reports per
182
+ * slot, `onMeshMaterialSlots` pushes the parsed glTF material list, and
183
+ * `requestNodePreviews` answers with thumbnails. Neither half degrades
184
+ * honestly: a v16 stage under a v17 chrome would leave the slot rail empty
185
+ * forever and throw on a preview request, and a v17 stage under a v16 chrome
186
+ * would call a slot handler that is not there from inside the model-load
187
+ * effect.
188
+ *
189
+ * 18: `onFitRequest` — the stage asking the chrome to hand the camera back.
190
+ * In the material workspace every drag orbits, and an orbited camera stays the
191
+ * author's forever by design, so a double-click on the object is the gesture
192
+ * that re-derives the head-on frame. It is stage -> chrome and fires from
193
+ * inside a pointer lane, so a v18 stage under a v17 chrome throws mid-gesture
194
+ * exactly as the version-11 handlers did.
195
+ *
196
+ * 19: `GestureDelta.suppressGuides` — the stage telling the chrome that CTRL
197
+ * is held, so the smart guides step aside for a delta that is already on a
198
+ * round number. Both halves fail silently without the bump, in opposite
199
+ * directions and both invisibly: a v19 stage under a v18 chrome quantises the
200
+ * emit and then has the guides pull it back off, so holding ctrl looks like it
201
+ * does nothing at all; a v18 stage under a v19 chrome never sets the flag, so
202
+ * the guides stay on and the same thing happens. There is no wrong PIXEL to
203
+ * see either way — only a modifier that appears not to work — which is exactly
204
+ * the class of mismatch a version gate exists to turn into a refusal.
205
+ *
206
+ * 20: `setPlaying` — the chrome telling the stage the transport is running, so
207
+ * the stage can defer work that the next seek is about to redo anyway. A v19
208
+ * stage under a v20 chrome simply never learns it is playing and behaves
209
+ * exactly as it did at v19, which is why this bump is about honesty rather
210
+ * than corruption: the mismatch costs performance, never pixels.
211
+ *
212
+ * 21: `ShotComponentContext.zoom` — the Studio's zoom over the definition's
213
+ * head-on render. An additive optional field, and bumped anyway on version
214
+ * 19's rule: a v20 stage ignores it, so the chrome's zoom control moves a
215
+ * number nothing on screen answers to. There is no wrong PIXEL to see, only a
216
+ * control that appears not to work, which is exactly the class of mismatch
217
+ * this gate exists to turn into a refusal rather than a support question.
218
+ *
219
+ * 22: `hoverLayer` and `setGridVisible`, one release. `hoverLayer` is the
220
+ * chrome telling the stage which layer a timeline row under the cursor names,
221
+ * so the stage lights that layer's outline without a click (and without
222
+ * changing the selection) — imperative on the stage side (a feed write and
223
+ * one SVG leaf repaint, zero GL draws — the reason it is NOT a state push).
224
+ * `setGridVisible` toggles the working pane's grid floor, workspace state on
225
+ * `setShadingMode`'s pattern. A v21 stage under a v22 chrome would throw on
226
+ * either missing verb — hover on the first row crossing, grid on the first
227
+ * toggle — which is version 18's mid-gesture failure class and exactly what
228
+ * the gate refuses up front.
229
+ *
230
+ * 23: `ShotComponentContext` becomes a union, so the context can name a CODE
231
+ * component ("<module>#<Export>") and not only an authored definition. A v22
232
+ * stage handed a code context reads `ctx.def` as undefined, fails its own
233
+ * "is this definition in the document" check and stores null: the chrome
234
+ * enters the code studio, the stage keeps rendering the shot behind the hole,
235
+ * and the author sees a panel over the wrong picture. A wrong PIXEL, which is
236
+ * version 18's class and the reason this is a bump rather than an additive
237
+ * field.
238
+ *
239
+ * 24: `onWaypointGesture`, the camera lane's second verb. A path move's
240
+ * waypoints became grabbable in the working pane, and the drag rides the
241
+ * EXISTING `onFramingGestureStart`/`End` bracket — one scrub, one undo entry —
242
+ * but it writes a WAYPOINT rather than the framing, so it needs a message of
243
+ * its own. A v23 chrome under a v24 stage would throw on the first waypoint
244
+ * drag: version 22's missing-verb class, refused up front instead.
245
+ *
246
+ * 25: `onStageBusy`, the readiness lane. The chrome holds playback while the
247
+ * stage is still loading (renderer init, fonts, textures, models), because
248
+ * play against a scene that has not landed runs the clock over a blank
249
+ * picture. A v24 stage under a v25 chrome never calls the handler, so the
250
+ * chrome would read "ready" forever and the hold would silently not exist —
251
+ * no wrong pixel, but a guard that is present in the source and absent in the
252
+ * product, which is the worst of both. Refused up front instead.
253
+ *
254
+ * 26: `onPerformance`, the presentation readout. The stage already measures
255
+ * what it PRESENTS, once a second, because the adaptive quality policy is
256
+ * driven by it; the chrome had no way to read it, so the only number it could
257
+ * have shown was its own seek cadence, and the stage coalesces requests and
258
+ * drops publications precisely when the two diverge. A number that reads 60
259
+ * while the picture runs at 18 is worse than no number. A v25 stage under a
260
+ * v26 chrome never calls the handler, so the badge would simply never appear:
261
+ * silent in the direction that looks like it works, which is version 25's
262
+ * class exactly. Refused up front instead.
263
+ *
264
+ * 27: `ShotCodeContext.props`, the defaults rail's live preview. The code
265
+ * studio's component face scrubs a contract default and the stage must show
266
+ * it before the commit lands in source, so the override rides the context —
267
+ * additive and optional, but bumped on v21's rule: a v26 stage's
268
+ * `validComponentContext` REBUILDS the code arm as `{ code, zoom }` and
269
+ * silently strips the field, so the rail's scrub would move a control
270
+ * nothing on screen answers to. Refused up front instead.
271
+ *
272
+ * 28: `setVideo`, the watch-only frame. Video stopped being a pane layout and
273
+ * became a flag over the picture, and the stage is where the flag does its
274
+ * work: it empties the selection it draws furniture for, refuses every grab
275
+ * and draw gesture, drops the hover raycast, and abandons a live modal, pen
276
+ * or spline draft. A v27 stage under a v28 chrome never receives the message,
277
+ * so it would keep drawing handles over a finished picture and keep taking
278
+ * transform drags on it. An author "watching" would be editing by accident,
279
+ * and nothing anywhere would say so. Silent in the direction that looks like
280
+ * it works, which is version 25's class exactly. Refused up front instead.
281
+ *
282
+ * 29: the camera lane, made legible. `setPilot` replaces
283
+ * `setCameraFollowsView`: piloting drives the shot camera from the CAMERA
284
+ * pane's own navigation (orbit, pan, wheel) through the framing gesture lane,
285
+ * where follow projected 3D-view orbits onto a camera that cannot tilt and
286
+ * left the two panes disagreeing mid-drag. `setCameraGate` tells the stage
287
+ * what a camera write would do at the playhead, so the gizmo dims and the
288
+ * press refuses BEFORE the drag instead of after it. `onFramingGesture` grew
289
+ * an origin (a wheel has no release, so the chrome brackets it with silence)
290
+ * and answers whether it wrote, so the badge can flash a refusal.
291
+ * `onLeavePilot` is the badge's own EJECT, and `onCameraDriver` names the
292
+ * component driving the render camera. A v28 stage under a v29 chrome throws
293
+ * on the attach push (`setPilot`), and the quiet direction is worse: a v29
294
+ * stage under a v28 chrome would keep the pane's wheel and drags on the
295
+ * viewport while the badge said the camera was being piloted. Version 25's
296
+ * class exactly. Refused up front instead.
297
+ *
298
+ * 30: `setStoryboardPreview`, the board's hole. The Storyboard used to be
299
+ * chrome over an unchanged stage; it now leaves a `.stage-window` in its
300
+ * bottom strip, and `measureInset` hands that rect down as the whole visible
301
+ * stage. A v29 stage under a v30 chrome never receives the flag, so it would
302
+ * keep the layout the author left behind it and draw selection handles,
303
+ * gizmos and the grid into a 300px monitor. Worse, the picture there would be
304
+ * whatever the stage was last framed at rather than the shot: the derived fit
305
+ * rides this bit too. Silent in the direction that looks like it works, which
306
+ * is version 28's class exactly. Refused up front instead.
307
+ *
308
+ * 31: `preparePlayback`, the preparing pass before Play. A v30 stage under a
309
+ * v31 chrome has no such method, and a chrome that called it would throw on
310
+ * every Play. Refused up front instead.
311
+ *
312
+ * 32: `captureFrame`, the storyboard's "Frame from Shot" drawn by the stage
313
+ * itself, because the hosted editor has no server renderer to ask, and the
314
+ * GIF export kind (`ShotExportMovieRequest.format`, `Tier1Capability.gif`).
315
+ * A v31 stage has no `captureFrame`, so a v32 chrome would throw on every
316
+ * capture; and it ignores `format`, so a GIF export would come back an MP4.
317
+ * Refused up front instead.
318
+ *
319
+ * 33: `capturePoster` YIELDS and REMEMBERS. The chrome takes no lock for a
320
+ * poster, so the stage now aborts one for anything the author starts on it
321
+ * (Play, an export or `captureFrame`, a scrub, an edit, a press), and answers
322
+ * null at once for a document whose capture failed. The chrome relies on
323
+ * both: it asks again on every null. A v32 stage under it would re-run a
324
+ * failing capture, up to a minute in export framing, on every ask, and an
325
+ * export started mid-poster would refuse "already running". Refused up
326
+ * front instead.
327
+ *
328
+ * 34: `onPieceFailures`, one broken piece isolated. The stage now draws the
329
+ * rest of a Shot when a layer, a background, a post stack or a transition
330
+ * throws, and sends the chrome the whole list of what it isolated (and
331
+ * `loadDoc` carries the document's revision, stamped on each failure). A v33
332
+ * chrome under a v34 stage has no such handler, so the one message that says
333
+ * a piece is missing would throw from inside the stage's apply pass; a v33
334
+ * stage under a v34 chrome never sends it, so a broken layer would read as a
335
+ * healthy stage. Silent in the direction that looks like it works, version
336
+ * 25's class. Refused up front instead.
337
+ *
338
+ * 35: `health()` and `onStageHealth`, the stage reporting on itself to an
339
+ * agent that cannot see it. `health()` answers what the stage holds (the
340
+ * crash, a lost context, the isolated pieces, the render fallbacks, the
341
+ * component failures, the uncaught errors of its document and its export
342
+ * worker, and what it last PRESENTED: the saved revision, the module epoch,
343
+ * the frame), and `onStageHealth` says it changed, never per frame. `loadDoc`
344
+ * of the document already on stage now only records its revision, which is
345
+ * how the chrome's own save marks the picture as that revision. A v34 stage
346
+ * under a v35 chrome has no `health()`, so every MCP write would wait out
347
+ * its check and answer "pending" over a stage that drew it; a v35 stage
348
+ * under a v34 chrome calls a handler that is not there from inside its
349
+ * presentation. Refused up front instead.
350
+ *
351
+ * 36: `coverage()`, which mesh-like layers put a pixel in the frame. A
352
+ * write whose every piece drew can still show nothing of a model: a
353
+ * gradient background drawn over it, a layer that paints over it, a camera
354
+ * that does not see it. The stage now draws its frame offscreen, whole and
355
+ * without each mesh, device or placed component layer, and says which of
356
+ * them changed no pixel (shot stageCoverage.ts). A v35 stage under a v36
357
+ * chrome has no `coverage()`, so every health report would say the layers
358
+ * were not checked and an agent would learn to ignore it; a v36 stage under
359
+ * a v35 chrome is never asked, which reads as a stage whose layers all
360
+ * draw. Silent in the direction that looks like it works, version 25's
361
+ * class. Refused up front instead.
362
+ *
363
+ * 37: `setBoardFrame`, the storyboard's Frame view framing the live stage:
364
+ * a tag, a numbered dot on each pinned beat's layer, and an onion skin of
365
+ * the previous still. Unlike `setStoryboardPreview` it keeps the stage
366
+ * editable, so it fits the shot into the hole without hiding furniture or
367
+ * refusing a click, and it parks every view gesture so the fit is the only
368
+ * framing on screen. A v36 stage under a v37 chrome throws "setBoardFrame
369
+ * is not a function" the moment the Frame view opens; a v37 stage under a
370
+ * v36 chrome is never framed, so the board's window would show the
371
+ * author's pan and zoom instead of the shot. Refused up front instead.
372
+ *
373
+ * 38: `editText` and `onTextEdit`, the stage's inline text editor. A Text
374
+ * click inserts a layer and opens it for typing; a double-click on a text
375
+ * layer opens it again. A v37 stage under a v38 chrome throws "editText is
376
+ * not a function" on the first Text click; a v38 stage under a v37 chrome
377
+ * calls a handler that is not there when the editor closes, and the words
378
+ * are lost. Refused up front instead. Also `designPointAt`, the query a
379
+ * drop on the frame converts its point through: a v37 stage under a v38
380
+ * chrome throws "designPointAt is not a function" on the first drop. And
381
+ * `onInstanceNaturalSize`, what a box-ignoring instance module draws: a v38
382
+ * stage under a v37 chrome calls a handler that is not there on the first
383
+ * placed component.
384
+ *
385
+ * 39: `exportMovie` takes a master `window`, the work area. A v38 stage
386
+ * under a v39 chrome ignores the field and exports the whole film into a
387
+ * file named for the work area, with audio cut to the window: silent in the
388
+ * direction that looks like it works. Refused up front instead.
389
+ *
390
+ * 40: `onTextAutoSize`, an auto-width text box's measured size (`TextBox`).
391
+ * A v40 stage under a v39 chrome calls a handler that is not there on the
392
+ * first auto text box; a v39 stage under a v40 chrome never measures, so the
393
+ * box keeps its click estimate. Refused up front instead.
394
+ *
395
+ * 41: `setReadOnly`, a viewer's stage. A v40 stage under a v41 chrome throws
396
+ * "setReadOnly is not a function" on attach; a v41 stage under a v40 chrome
397
+ * is never told, which is the editable stage it always was. Refused up front
398
+ * instead.
399
+ *
400
+ * 42: Storyboard comments. `ShotBoardFrame` gains `pins` and `comment`, and
401
+ * the handlers gain `onCommentAt` and `onCommentPin`. A v41 stage under a
402
+ * v42 chrome draws no pins and never reports a comment click; a v42 stage
403
+ * under a v41 chrome calls handlers that are not there. Refused up front
404
+ * instead.
405
+ *
406
+ * 43: `setPeerSelections`, other people's selections (live collaboration,
407
+ * R7). A v42 stage under a v43 chrome throws "setPeerSelections is not a
408
+ * function" on the first peer; a v43 stage under a v42 chrome is never
409
+ * told, so it draws no peer outlines. Refused up front instead.
410
+ *
411
+ * 44: Rulers, guides and the motion path key drag. The stage gains
412
+ * `setEditorChrome` and `setMotionPaths`; the handlers gain the guide
413
+ * gestures (`onGuideGestureStart`, `onGuideGesture`, `onGuideGestureEnd`,
414
+ * `onGuideSet`, `onGuideRemove`) and the path key drag
415
+ * (`onPathKeyGestureStart`, `onPathKeyGesture`, `onPathKeyGestureEnd`,
416
+ * plus `onPathKeyDoubleClick` and `onContextMenu`'s optional `pathKey`), whose
417
+ * `part` already names the Bezier handles so schema v40 needs no second
418
+ * bump. A v43 stage under a v44 chrome throws "setEditorChrome is not a
419
+ * function" on attach; a v44 stage under a v43 chrome calls guide handlers
420
+ * that are not there on the first ruler drag. Refused up front instead.
421
+ *
422
+ * 45: The Components workspace's preview and Studio comments. Both arms of
423
+ * `ShotComponentContext` gain `background` (dark, light or brand, drawn as
424
+ * the working pane's clear colour) and `loopFrames` (the stage wraps every
425
+ * seek inside the context to that length). The stage gains
426
+ * `setStudioComments`, pins anchored to a Shot and a frame with no still,
427
+ * and the handlers gain `onStudioCommentAt`. A v44 stage under a v45 chrome
428
+ * throws "setStudioComments is not a function" on attach and ignores the
429
+ * background and the loop; a v45 stage under a v44 chrome calls a handler
430
+ * that is not there on the first Studio comment. Refused up front instead.
431
+ * The board no longer frames the stage, so v45 also drops `setBoardFrame`
432
+ * (v37) and `onCommentAt` (v42); the pins `ShotBoardPin` describes are the
433
+ * Studio's now.
434
+ *
435
+ * 46: The Components props panel tries values without saving them.
436
+ * `ShotDefContext` gains `props` (exposed key -> Value), drawn the way a
437
+ * placement's overrides are. A v45 stage under a v46 chrome ignores them, so
438
+ * the panel would show a tried value the preview never draws. Refused up
439
+ * front instead.
440
+ *
441
+ * 47: The Studio's sketch guide. The stage gains `setSketchGuide`, one
442
+ * storyboard sketch as plain shapes in design px, drawn as dashed outlines
443
+ * over the camera frame. A v46 stage under a v47 chrome throws
444
+ * "setSketchGuide is not a function" on attach. Refused up front instead.
445
+ *
446
+ * 48: A definition is authored only on the Components Edit tab.
447
+ * `ShotDefContext` gains `edit`, and only then does the definition's one
448
+ * pane take the authoring lanes (the draw tools, the pen, drops and the
449
+ * Select grammar). The Preview tab shows the same definition and orbits.
450
+ * A v47 stage under a v48 chrome would author on Preview too, so a drag
451
+ * there moved the master definition. Refused up front instead.
452
+ *
453
+ * 49: `exportMovie` takes a `quantizer`, the quality's CRF as a fixed H.264
454
+ * QP, and its result says how the encoder ran (`encoding`). A bitrate encode
455
+ * can open soft (KAN-23), so the stage encodes at the QP wherever a hardware
456
+ * encoder honours one. A v48 stage under a v49 chrome ignores the field and
457
+ * silently ships the bitrate encode, and the dialog never learns which
458
+ * encoder ran: silent in the direction that looks like it works, version
459
+ * 25's class. Refused up front instead.
460
+ *
461
+ * 50: The font picker's stage preview. The stage gains `setFontPreview`, a
462
+ * font shown on some text layers without a document write. A v49 stage
463
+ * under a v50 chrome throws "setFontPreview is not a function" on the first
464
+ * hover in the picker. Refused up front instead.
465
+ *
466
+ * 51: Honest readiness, the queued Play, and background preparation that
467
+ * runs. `onStageBusy` now says the stage owes the open subject its first
468
+ * whole presentation, not that a load is open. `preparePlayback` loses
469
+ * `background` and takes a `pace`, reports each frame's outcome, ends with
470
+ * a reason (`yielded`, `refused`) and hands over on the start frame
471
+ * (`handedOver`). The stage gains `preparationKey()` and `takePlayed()`. A
472
+ * v50 stage under a v51 chrome would report busy for ordinary loads,
473
+ * resolve before the start frame is presented, and ignore `pace`; a v51
474
+ * stage under a v50 chrome would call `pace()` on a request that has none.
475
+ * Refused up front instead.
476
+ *
477
+ * 52: The playback render scale's report and its preference. A played frame
478
+ * may render below the display's density while the GPU cannot keep up (the
479
+ * stage's renderScaleController.ts decides, on stage-local inputs), so
480
+ * `StagePerformanceReport` gains `gpuMs` (the second's GPU p95, null without
481
+ * a timer) and `renderScale` (the share of the display's density its last
482
+ * frame rendered at), and `EditorChrome` gains `playbackResolution` (the
483
+ * Preferences row: `auto`, `full` or `fast`). A v51 stage under a v52 chrome
484
+ * sends neither field, and the fps chip reads both; a v52 stage under a v51
485
+ * chrome is never told a mode, so it runs Auto, and the chip never says the
486
+ * picture renders below the display's density: silent in the direction that
487
+ * looks like it works, version 25's class. Refused up front instead.
488
+ *
489
+ * 53: The motion preset picker's stage preview. The stage gains
490
+ * `setMotionPreview`, a preset's keys shown on some layers without a
491
+ * document write, while the chrome seeks the stage through the preset's
492
+ * frames. A v52 stage under a v53 chrome throws "setMotionPreview is not a
493
+ * function" on the first hover in the Motion section. Refused up front
494
+ * instead.
495
+ */
496
+ export const SHOT_BRIDGE_VERSION = 53;
497
+
498
+ /**
499
+ * A motion preset the Motion section shows on some layers before it is
500
+ * applied (bridge v53). Never written to a Doc3D. The stage stamps it on its
501
+ * own copy with the same `stampMotionPreset` a commit runs, so the preview is
502
+ * exactly the keys an apply writes.
503
+ */
504
+ export interface ShotMotionPreview {
505
+ /** The container that holds the layers: the Shot's scene id, or the open
506
+ * definition's id. */
507
+ containerId: string;
508
+ layerIds: readonly string[];
509
+ /** The stamp, `at` included (the chrome's playhead, container-local). */
510
+ request: MotionPresetRequest;
511
+ /** The registry's contracts for the placed components among `layerIds`,
512
+ * by component id: what types a Count up's `instance.props.progress`. */
513
+ contracts?: Readonly<Record<string, Contract>>;
514
+ }
515
+
516
+ /** One file of a previewed face: a font url the stage loads itself, and the
517
+ * characters it covers (a CSS `unicode-range`; absent, the whole file). */
518
+ export interface ShotFontPreviewFace {
519
+ url: string;
520
+ unicodeRange?: string;
521
+ }
522
+
523
+ /** A font the font picker shows on some text layers before it is applied
524
+ * (bridge v50). Never written to a Doc3D. */
525
+ export interface ShotFontPreview {
526
+ /** The container that holds the layers: the Shot's scene id, or the open
527
+ * definition's id. */
528
+ containerId: string;
529
+ /** The text layers that show the font. Other layers, and other kinds, are
530
+ * left as they are. */
531
+ layerIds: readonly string[];
532
+ /** The family's display name, kept in the stack after the preview face. */
533
+ family: string;
534
+ /** The generic family drawn while the faces load, and where they have no
535
+ * glyph (`sans-serif`, `serif`, `monospace`, `cursive`). */
536
+ fallback: string;
537
+ /** The style the faces are, which the layers draw in for the preview. */
538
+ style: "normal" | "italic";
539
+ /** The faces' `font-weight` descriptor range: a variable file's `wght`
540
+ * axis, or `[1, 1000]` for one static file. Each layer keeps its own
541
+ * weight, and the browser never synthesises bold over the range. */
542
+ weightRange: readonly [number, number];
543
+ /** The faces, the family's default subset first. The stage fetches every
544
+ * one as soon as the preview arrives (a face joins the stage's font set
545
+ * only once it has loaded), so list the files the layers' text needs
546
+ * (`subsetsForText`), not each of the family's subsets. The same files,
547
+ * style and weight range sent again keep the faces already loaded. */
548
+ faces: readonly ShotFontPreviewFace[];
549
+ }
550
+
551
+ /** The component preview's backdrop (bridge v45). `brand` is the Project's
552
+ * first brand colour, and dark when it has none. */
553
+ export type ShotComponentBackground = "dark" | "light" | "brand";
554
+
555
+ /** The colour `brand` resolves to: the first colour row of the PROJECT's
556
+ * branding, or null when it has none (the caller then draws dark). One rule
557
+ * for both documents, so the stage and the board's card stage cannot pick
558
+ * different rows. */
559
+ export function brandBackdrop(doc: Pick<Doc3D, "resources"> | null): string | null {
560
+ for (const item of doc?.resources?.branding ?? []) {
561
+ if (item.type === "color" && /^#[0-9a-fA-F]{6}$/.test(item.value)) return item.value;
562
+ }
563
+ return null;
564
+ }
565
+
566
+ /** The most frames a preview loop may hold: ten minutes at 60 fps. A bound,
567
+ * so a junk number cannot make the wrap a no-op that reads as a loop. */
568
+ export const MAX_LOOP_FRAMES = 36_000;
569
+
570
+ /** A Studio comment's place (bridge v45): a Shot, a Shot-local frame, the
571
+ * topmost layer under the click, and the point as 0..1 of the frame rect.
572
+ * No still: a Studio comment is left on the moving picture at a timecode. */
573
+ export interface ShotStudioAnchor {
574
+ kind: "studio";
575
+ sceneId: string;
576
+ /** Shot-local, frames from the Shot's start. */
577
+ frame: number;
578
+ layerId: string | null;
579
+ u: number;
580
+ v: number;
581
+ }
582
+
583
+ /** One Studio comment pin (bridge v45). The stage draws it while stopped on
584
+ * its anchor's Shot and frame, so the chrome sends every pin once rather
585
+ * than re-pushing per frame. */
586
+ export interface ShotStudioPin {
587
+ threadId: string;
588
+ anchor: ShotStudioAnchor;
589
+ /** As `ShotBoardPin`. */
590
+ initials: string;
591
+ color: string;
592
+ active: boolean;
593
+ label: string;
594
+ }
595
+
596
+ /** What the Studio's comment layer lays over the camera pane (bridge v45).
597
+ * See `setStudioComments`. */
598
+ export interface ShotStudioComments {
599
+ pins: readonly ShotStudioPin[];
600
+ /** The comment tool is armed: a primary click on the camera frame is
601
+ * `onStudioCommentAt` instead of a pick. */
602
+ armed: boolean;
603
+ }
604
+
605
+ /** One outline of the Studio's sketch guide (bridge v47): a sketch item's
606
+ * box in design px, `at` its centre (origin at the canvas centre, +y down,
607
+ * the layer units), with its corner radius and its label chip. */
608
+ export interface ShotSketchGuideRect {
609
+ at: readonly [number, number];
610
+ size: readonly [number, number];
611
+ /** Corner radius, design px; absent is square. */
612
+ radius?: number;
613
+ label?: string;
614
+ }
615
+
616
+ /** A sketch arrow (bridge v47): a motion note, drawn from `from` to `to`. */
617
+ export interface ShotSketchGuideArrow {
618
+ from: readonly [number, number];
619
+ to: readonly [number, number];
620
+ label?: string;
621
+ }
622
+
623
+ /** THE SKETCH GUIDE (bridge v47): what "Guide: Sketch N" draws over the
624
+ * camera frame. Plain shapes, so the stage needs no storyboard code. */
625
+ export interface ShotSketchGuide {
626
+ rects: readonly ShotSketchGuideRect[];
627
+ arrows: readonly ShotSketchGuideArrow[];
628
+ }
629
+
630
+ /** The most shapes a guide carries: a sketch's own item limit, with room
631
+ * for nothing more. A bound, so a junk message cannot flood the overlay. */
632
+ export const MAX_SKETCH_GUIDE_SHAPES = 32;
633
+
634
+ /** One other person's selection (bridge v43): outlined in their colour with
635
+ * their name, under this tab's own selection, and never clickable. */
636
+ export interface ShotPeerSelection {
637
+ ids: readonly string[];
638
+ name: string;
639
+ /** `#rrggbb`. */
640
+ color: string;
641
+ }
642
+
643
+ /** One comment pin as the stage draws it on the camera pane's frame
644
+ * (bridge v42; the Studio's pins from v45). */
645
+ export interface ShotBoardPin {
646
+ threadId: string;
647
+ /** Where on the picture, 0..1 across and down the frame rect. */
648
+ u: number;
649
+ v: number;
650
+ /** Up to two letters, the thread author's initials. */
651
+ initials: string;
652
+ /** The author's colour, a hex string: the stage has no chrome tokens. */
653
+ color: string;
654
+ /** The open thread's pin, drawn with a ring. */
655
+ active: boolean;
656
+ /** The pin's accessible name. */
657
+ label: string;
658
+ }
659
+
660
+ /**
661
+ * One piece the stage isolated: which kind (a layer, a placed code component,
662
+ * a layer inside a component definition, a Shot's background, its post stack,
663
+ * a transition), where (the Shot, the layer, the field when known), what it
664
+ * threw, and the frame and document revision it was first seen at. The
665
+ * runtime's own record (`PieceFailure`, pieceFailures.ts), verbatim.
666
+ */
667
+ export type StagePieceFailure = PieceFailure;
668
+
669
+ /** What the stage's shader compiler most recently established for one graph.
670
+ * It is live runtime state, never persisted with the material definition.
671
+ *
672
+ * `slot` is the glTF material index the report is about, or null for the
673
+ * layer-wide binding. A model with a Glass slot over a Studio base compiles two
674
+ * materials and can fail at exactly one of them, so a diagnostic that named
675
+ * only the layer would put a red badge on a graph that is fine. */
676
+ export type MaterialCompileDiagnostic =
677
+ | { status: "compiling"; materialId: string; layerId: string; slot: number | null }
678
+ | { status: "ready"; materialId: string; layerId: string; slot: number | null }
679
+ | { status: "error"; materialId: string; layerId: string; slot: number | null; message: string };
680
+
681
+ /**
682
+ * WHICH SURFACE the material workspace is editing.
683
+ *
684
+ * `slot: null` is the layer-wide binding — the whole model, and the only
685
+ * possibility for a primitive. A number is a glTF material index, stable
686
+ * because an asset id is a content hash (see `materialSlots.ts`).
687
+ */
688
+ export interface MaterialPreviewTarget {
689
+ layerId: string;
690
+ slot: number | null;
691
+ }
692
+
693
+ /** The chrome's ask for a batch of node thumbnails. */
694
+ export interface NodePreviewRequest {
695
+ materialId: string;
696
+ layerId: string;
697
+ slot: number | null;
698
+ /** The VIEWPORT-VISIBLE nodes only, chosen by the chrome. The stage clamps
699
+ * to `MAX_NODE_PREVIEWS` rather than refusing, because a request that is one
700
+ * node too long is a scroll position, not a bug. */
701
+ nodeIds: readonly string[];
702
+ /** Thumbnail edge in device px, clamped to [32, 256]. */
703
+ size: number;
704
+ }
705
+
706
+ export type NodePreviewResult =
707
+ | { nodeId: string; bitmap: ImageBitmap; message?: undefined }
708
+ | { nodeId: string; bitmap: null; message: string };
709
+
710
+ export interface NodePreviewBatch {
711
+ materialId: string;
712
+ /** The graph the stage actually drew. The chrome drops a batch whose
713
+ * signature is no longer current rather than painting a stale thumbnail
714
+ * over a graph the author has since edited. */
715
+ semanticSignature: string;
716
+ size: number;
717
+ results: readonly NodePreviewResult[];
718
+ }
719
+
720
+ /** One atlas readback is one GPU stall, so the batch is bounded. 64 cells at
721
+ * 96px is a 768x768 target, which is cheaper than the graph it describes. */
722
+ export const MAX_NODE_PREVIEWS = 64;
723
+
724
+ /**
725
+ * The stage's component context: render ONE component standalone.
726
+ *
727
+ * The subject mounts on its own canvas (drawn as a plate over the working
728
+ * pane's clay backdrop, no scene, no post stack, no scene camera), and while a
729
+ * context is set the frames the chrome pushes through `seek` are SUBJECT-LOCAL
730
+ * (0 = the subject's own start).
731
+ *
732
+ * TWO SUBJECTS, ONE CONTEXT (v23). A shot places two things that are both
733
+ * called "component": an authored definition in `doc.components`, and a code
734
+ * component from the author's repo placed as an `instance` layer. Only the
735
+ * first could ever be opened, because only the first had something for the
736
+ * stage to render. The `code` arm gives the second one a subject too, and it
737
+ * does so WITHOUT a second render path: the stage builds a synthetic
738
+ * definition holding one instance of that component at contract defaults
739
+ * (`codePreviewDef`), so both arms land in the same mount.
740
+ *
741
+ * The zoom is on both arms; everything else differs, which is why this is a
742
+ * union and not one interface with optional halves.
743
+ */
744
+ export type ShotComponentContext = ShotDefContext | ShotCodeContext;
745
+
746
+ /** A definition the loaded document holds. */
747
+ export interface ShotDefContext {
748
+ /** A `cmp_` id in the loaded document. */
749
+ def: string;
750
+ /**
751
+ * The chrome's zoom over the head-on render: 1 fits the whole canvas, 2
752
+ * shows it twice as large. Absent = 1.
753
+ *
754
+ * IT CROSSES AS A NUMBER AND NOT AS A CAMERA, because the frame is derived
755
+ * on the stage side (`componentFrame(size)`) and only the stage knows the
756
+ * pane it has to fit into. The zoom scales that frame's half-extents before
757
+ * the fit, so the "honest visible rect" promise the derived rig makes holds
758
+ * at every magnification instead of only at 1.
759
+ */
760
+ zoom?: number;
761
+ /** The preview's backdrop (v45), the working pane's clear colour. Absent =
762
+ * the working pane's own token, as before v45. */
763
+ background?: ShotComponentBackground;
764
+ /** The preview loop's length in frames (v45): every seek inside the
765
+ * context wraps to `frame % loopFrames`. Absent = no wrap. */
766
+ loopFrames?: number;
767
+ /** TRIED values from the Components props panel (v46), exposed key ->
768
+ * Value, drawn exactly as a placement's overrides are. Editor-only and
769
+ * never persisted: "Apply to all instances" is what writes the definition.
770
+ * Absent or empty = the definition's own values. */
771
+ props?: Readonly<Record<string, Value>>;
772
+ /** True on the Components Edit tab (v48): the definition is being authored,
773
+ * so its one pane is the frame the draw tools, the pen, drops and the
774
+ * Select grammar map through. Absent on the Preview tab, which only views
775
+ * the definition: a drag there orbits and nothing on it writes the master. */
776
+ edit?: true;
777
+ }
778
+
779
+ /**
780
+ * A code component from the author's repo, by REGISTRY ID.
781
+ *
782
+ * The id is all that crosses. The stage rebuilds the subject from it with the
783
+ * same pure function the chrome would, so the two sides cannot hold different
784
+ * subjects for one id, and a document is not involved at all: a code component
785
+ * can be opened from any context, including one where nothing places it.
786
+ */
787
+ export interface ShotCodeContext {
788
+ /** "<module>#<Export>", the registry id and `ComponentRow.id`. */
789
+ code: string;
790
+ /** As `ShotDefContext.zoom`. */
791
+ zoom?: number;
792
+ /** As `ShotDefContext.background` (v45). */
793
+ background?: ShotComponentBackground;
794
+ /** As `ShotDefContext.loopFrames` (v45). */
795
+ loopFrames?: number;
796
+ /**
797
+ * Live default OVERRIDES from the defaults rail, contract KEY -> engine
798
+ * Value (curve/gradient as their encoded strings, `instance.props`' own
799
+ * form — `buildProps` decodes). Absent or empty = every prop at its
800
+ * declared default, exactly the v23 behaviour.
801
+ *
802
+ * EDITOR-ONLY, NEVER PERSISTED: the stage merges these over the synthetic
803
+ * preview instance's props for THIS render; committing a value goes through
804
+ * the defaults-persist API and lands in source, at which point the chrome
805
+ * drops the override. The def identity stays stable — only the props map
806
+ * changes — so nothing remounts under a scrub.
807
+ */
808
+ props?: Readonly<Record<string, Value>>;
809
+ }
810
+
811
+ /** `ShotCodeContext.props` values a stage may accept from a chrome: finite
812
+ * numbers, strings, booleans, and finite numeric tuples of 2..4 — the wire
813
+ * shapes of every animatable contract kind. Anything else is DROPPED, not
814
+ * refused: one malformed key must not blank an otherwise valid preview.
815
+ * Returns undefined when nothing survives, so the caller can omit the field
816
+ * entirely and keep the v23 object shape.
817
+ *
818
+ * KNOWN CEILING: a string on a curve/gradient KEY is decoded by `buildProps`
819
+ * inside the render, and an undecodable one throws there — the same refusal
820
+ * a corrupt document value has always produced. This validator cannot see
821
+ * contract kinds, so it cannot close that; the shipped rail closes it by
822
+ * construction (it validates + encodes composite values before they reach
823
+ * the bridge), and any other chrome owes the same care. */
824
+ export function validPreviewProps(raw: unknown): Record<string, Value> | undefined {
825
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return undefined;
826
+ const out: Record<string, Value> = {};
827
+ for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
828
+ if (typeof value === "string" || typeof value === "boolean") {
829
+ out[key] = value;
830
+ continue;
831
+ }
832
+ if (typeof value === "number" && Number.isFinite(value)) {
833
+ out[key] = value;
834
+ continue;
835
+ }
836
+ if (
837
+ Array.isArray(value) &&
838
+ value.length >= 2 &&
839
+ value.length <= 4 &&
840
+ value.every((n) => typeof n === "number" && Number.isFinite(n))
841
+ ) {
842
+ out[key] = value as Value;
843
+ }
844
+ }
845
+ return Object.keys(out).length === 0 ? undefined : out;
846
+ }
847
+
848
+ /** The subject a context names, for callers that only need to tell one context
849
+ * from another: the fake bridge's log line, and the stage's own re-push
850
+ * equality check. */
851
+ export function componentContextSubject(ctx: ShotComponentContext): string {
852
+ return "code" in ctx ? ctx.code : ctx.def;
853
+ }
119
854
 
120
855
  /** What the stage reports once it has a renderer and a document. */
121
856
  export interface ShotStageInfo {
@@ -130,6 +865,37 @@ export interface ShotStageInfo {
130
865
  backend: string;
131
866
  }
132
867
 
868
+ /** One second of presentation, as the stage measured it.
869
+ *
870
+ * `expectedFrames` is what the document's rate asked for over the same window,
871
+ * so a reader can say "28 of 30" rather than only "28" — the pair is what makes
872
+ * the number judgeable. */
873
+ export interface StagePerformanceReport {
874
+ presentedFps: number;
875
+ expectedFps: number;
876
+ droppedPublications: number;
877
+ /** The GPU's own time per frame over the same second, its p95 in ms (v52),
878
+ * off three's timestamp queries: render passes only, so an upload or a
879
+ * compile is not in it (the stage's gpuTimer.ts). Null when the renderer
880
+ * has no timer or no frame's time landed in the window. */
881
+ gpuMs: number | null;
882
+ /** The share of the display's density the window's last frame rendered at
883
+ * (v52): 1, or a step below it while the playback render scale holds one
884
+ * (the stage's renderScaleController.ts). The canvas itself never resizes. */
885
+ renderScale: number;
886
+ }
887
+
888
+ /** The Preferences row "Playback resolution" (v52). `auto` lets the stage
889
+ * render a played frame below the display's density while the GPU cannot
890
+ * keep up, `full` never does, and `fast` starts at 0.7 of it and never goes
891
+ * above. Paused frames, stills and exports are always at full density. */
892
+ export const PLAYBACK_RESOLUTIONS = ["auto", "full", "fast"] as const;
893
+ export type PlaybackResolution = (typeof PLAYBACK_RESOLUTIONS)[number];
894
+
895
+ export function isPlaybackResolution(value: unknown): value is PlaybackResolution {
896
+ return (PLAYBACK_RESOLUTIONS as readonly unknown[]).includes(value);
897
+ }
898
+
133
899
  /** A screen-space quad for one layer: TL, TR, BR, BL as x,y pairs, in CSS
134
900
  * pixels relative to the stage viewport's top-left.
135
901
  *
@@ -141,6 +907,82 @@ export interface ShotScreenQuad {
141
907
  pts: readonly [number, number, number, number, number, number, number, number];
142
908
  }
143
909
 
910
+ /** Editor-only construction plane. `selection` means the named layer's local
911
+ * XY plane, and falls back visibly to world XY when the layer disappears. */
912
+ export type WorkPlaneTarget =
913
+ | { kind: "xy" | "xz" | "yz" | "view" }
914
+ | { kind: "selection"; layerId: string };
915
+
916
+ /** A work plane resolved into design coordinates (+Y down, +Z away). */
917
+ export interface ResolvedWorkPlane {
918
+ target: WorkPlaneTarget;
919
+ origin: readonly [number, number, number];
920
+ xAxis: readonly [number, number, number];
921
+ yAxis: readonly [number, number, number];
922
+ normal: readonly [number, number, number];
923
+ fallback: boolean;
924
+ }
925
+
926
+ /** The stage's world-space draft. The chrome recentres it into one spline
927
+ * layer at commit, assigning stable point ids in the process. */
928
+ export interface SplineCreateDraft {
929
+ basis: SplineBasis;
930
+ closed: boolean;
931
+ points: readonly SplinePoint3D[];
932
+ }
933
+
934
+ /** The interpolation new spline drafts use. Workspace state, never document data. */
935
+ export type SplineBasis = "bezier" | "catmull-rom";
936
+
937
+ export type SplinePointPart = "anchor" | "in" | "out";
938
+
939
+ /** Which part of a motion path key a drag holds (v44): the key's own dot, or
940
+ * one of its spatial Bezier handles (schema v40). The chrome refuses `"in"`
941
+ * and `"out"` off a bezier path, and on the side no segment uses. */
942
+ export type PathKeyPart = "key" | "in" | "out";
943
+
944
+ /** A path key drag's offset from where it started, in the layer's stored
945
+ * position units: parent space, +y DOWN, +z away, the numbers the
946
+ * inspector's Position row shows. The stage measures
947
+ * the whole drag, never a step, so a dropped event loses nothing. */
948
+ export interface PathKeyDelta {
949
+ dx: number;
950
+ dy: number;
951
+ dz: number;
952
+ /** A handle drag only (Alt): move this handle alone. Without it the chrome
953
+ * turns the opposite handle to stay in line, keeping its length. Read per
954
+ * move, so the pair can break mid-drag. */
955
+ breakPair?: boolean;
956
+ }
957
+
958
+ /** The per-viewer preferences the stage acts on: its furniture (v44) and the
959
+ * playback resolution (v52). */
960
+ export interface EditorChrome {
961
+ /** Rulers along the camera pane's top and left edges. */
962
+ rulers: boolean;
963
+ /** Smart guides: a dragged guide snaps to the frame's edges and centre. */
964
+ smartGuides: boolean;
965
+ /** What a played frame may render at (v52, `PlaybackResolution`). It moves
966
+ * no furniture and ends no preparing pass, which draws at full density. */
967
+ playbackResolution: PlaybackResolution;
968
+ }
969
+
970
+ /** How a guide drag ended (v44). `"remove"` is a guide dragged back onto its
971
+ * ruler; a guide created from the ruler and dropped there is never added. */
972
+ export type GuideGestureOutcome = "commit" | "remove" | "cancel";
973
+
974
+ export interface SplineEditContext {
975
+ layerId: string;
976
+ selectedPointIds: readonly string[];
977
+ }
978
+
979
+ export interface SplinePointPatch {
980
+ layerId: string;
981
+ pointId: string;
982
+ part: SplinePointPart;
983
+ value: readonly [number, number, number];
984
+ }
985
+
144
986
  /** The stage's view of the working area: how much of the frame is on screen and
145
987
  * where. Owned by the stage (it is a camera `setViewOffset`), mirrored to the
146
988
  * chrome so the zoom readout and "fit" have something to read and write. */
@@ -178,6 +1020,71 @@ export interface ShotBridgeHandlers {
178
1020
  onReady(info: ShotStageInfo): void;
179
1021
  /** The stage failed in a way the author needs to see. */
180
1022
  onError(message: string): void;
1023
+ /**
1024
+ * The stage still OWES THE OPEN SUBJECT its first whole presentation, or no
1025
+ * longer does (v51).
1026
+ *
1027
+ * The subject is what the stage shows: the Project (`doc.id`), a component
1028
+ * context, or a material preview's layer and slot. A whole presentation is
1029
+ * a clock echo of the current subject, drawn with no external readiness
1030
+ * claim open and every placed component's module installed. Once this
1031
+ * renderer has drawn one, the subject is latched and loads no longer make
1032
+ * the stage busy: a font, a texture, a model or a paused video seek arriving
1033
+ * later holds nothing. A new subject, a new renderer (a new export canvas)
1034
+ * or a new stage document owes one again.
1035
+ *
1036
+ * A PAUSED VIDEO SEEK holds the subject's FIRST whole presentation only. It
1037
+ * never holds again after that, and the latch never re-arms on Pause.
1038
+ *
1039
+ * False with no document, and false while the stage cannot present at all
1040
+ * (a fatal error, a lost context, a renderer compile failure), so nothing
1041
+ * waits on it for ever. A standing fallback (a texture, html, font or glyph
1042
+ * failure) does not release the hold: the stage still presents, so the
1043
+ * latch closes anyway.
1044
+ *
1045
+ * Sent on connect and then on every CHANGE of the value, coalesced to a
1046
+ * microtask and deduped stage-side.
1047
+ *
1048
+ * THIS IS NOT THE EXPORT GATE'S QUESTION. A capture waits on every external
1049
+ * claim at every frame it takes, a paused video seek included. See
1050
+ * `bridgeClient.ts`.
1051
+ */
1052
+ onStageBusy(pending: boolean): void;
1053
+ /**
1054
+ * The pieces the stage isolated (v34), WHOLE, on every change: a layer, a
1055
+ * background, a post stack or a transition that threw while the rest of the
1056
+ * Shot kept drawing. Empty once every piece renders again. Sent from inside
1057
+ * the stage's apply pass, so it must not throw.
1058
+ *
1059
+ * The same list refuses an exact export or still of any frame a listed piece
1060
+ * belongs to, by name (the stage's own gate, `bridgeClient.ts`).
1061
+ */
1062
+ onPieceFailures(failures: readonly StagePieceFailure[]): void;
1063
+ /**
1064
+ * What `health()` answers changed (v35): a presentation of another
1065
+ * document, revision or module epoch, a piece, a render fallback, a
1066
+ * component failure, the crash, or an uncaught error. Also sent when
1067
+ * `preparationKey().base` moves (v51), so a chrome with nothing left to
1068
+ * prepare looks again. No payload: the chrome reads `health()` when it
1069
+ * wants the answer, which is when an agent asks. Never per frame. Sent from
1070
+ * inside the stage's presentation, so it must not throw.
1071
+ */
1072
+ onStageHealth(): void;
1073
+ /**
1074
+ * What the stage PRESENTED over the last second, or null once it stops.
1075
+ *
1076
+ * Sent once per closed telemetry window, and windows roll only during
1077
+ * playback, so this is 1Hz while playing and silent otherwise. Null on pause
1078
+ * is a retraction rather than a zero: no frames are expected of a stopped
1079
+ * stage, and 0 fps would read as a stall.
1080
+ *
1081
+ * It has to come from the stage. The chrome knows what it ASKED for, and the
1082
+ * stage coalesces requests and drops publications exactly when the ask and
1083
+ * the picture come apart, which is the only time the number is interesting.
1084
+ */
1085
+ onPerformance(report: StagePerformanceReport | null): void;
1086
+ /** The real stage is the material specimen; report what its compiler did. */
1087
+ onMaterialCompile(diagnostic: MaterialCompileDiagnostic): void;
181
1088
  /**
182
1089
  * A pick, in DOCUMENT order front-to-back — the WHOLE ordered hit list, not
183
1090
  * just the nearest.
@@ -202,6 +1109,10 @@ export interface ShotBridgeHandlers {
202
1109
  * isolation stack, and then two places would decide what entering means.
203
1110
  */
204
1111
  onEnterLayer(hits: readonly string[], pane: StagePane): void;
1112
+ /** The inline text editor closed with this content (v38). One edit, by id:
1113
+ * a click outside can move the selection before the editor's blur lands.
1114
+ * Empty (or blank) content removes the layer. */
1115
+ onTextEdit(layerId: string, content: string): void;
205
1116
  /**
206
1117
  * A RIGHT PRESS: the same ordered hit stack `onPick` sends, plus the point to
207
1118
  * open a menu at.
@@ -214,8 +1125,16 @@ export interface ShotBridgeHandlers {
214
1125
  * `at` is in CLIENT coordinates and needs no translation: the stage iframe is
215
1126
  * `position: fixed; inset: 0`, so a point in the stage's document is the same
216
1127
  * point in the chrome's.
1128
+ *
1129
+ * `pathKey` (v44) is the motion path key dot under the press, when there is
1130
+ * one: the menu is then that key's (seek, curved path, delete).
217
1131
  */
218
- onContextMenu(hits: readonly string[], pane: StagePane, at: { x: number; y: number }): void;
1132
+ onContextMenu(
1133
+ hits: readonly string[],
1134
+ pane: StagePane,
1135
+ at: { x: number; y: number },
1136
+ pathKey?: { layerId: string; keyId: string },
1137
+ ): void;
219
1138
  /**
220
1139
  * A mesh layer's triangle count, once its model has loaded — or null when the
221
1140
  * layer unmounts or its model is swapped.
@@ -229,6 +1148,37 @@ export interface ShotBridgeHandlers {
229
1148
  * heavy scene, and neither half can read a `BufferGeometry`.
230
1149
  */
231
1150
  onMeshStats(layerId: string, triangles: number | null): void;
1151
+ /**
1152
+ * The glTF materials a model actually ships, pushed once when it parses.
1153
+ *
1154
+ * The same "known exactly once, at parse" argument `onMeshStats` makes: a
1155
+ * material's index, name, colour and map set live in the FILE, not in the
1156
+ * document, so no amount of reading `Doc3D` tells the chrome that slot 3 is
1157
+ * called "Screen" and carries a base-colour map. Without it the slot rail
1158
+ * could only offer bare indices.
1159
+ *
1160
+ * `null` on unmount or a model swap, so a stale list never outlives the
1161
+ * model it described.
1162
+ */
1163
+ onMeshMaterialSlots(layerId: string, slots: readonly MeshMaterialSlotInfo[] | null): void;
1164
+ /**
1165
+ * What an instance module that never read its box draws at scale 1, design
1166
+ * px (v38): pushed once after its first commit while the layer has no
1167
+ * `instance.naturalSize`. The chrome stores it only for a component it just
1168
+ * placed, and sets the box to it in the same history entry (F41).
1169
+ */
1170
+ onInstanceNaturalSize(layerId: string, size: readonly [number, number]): void;
1171
+ /** An auto-width text box's measured size, design px (v40, `autoTextSize`):
1172
+ * pushed when the stored `transform.size` is off. The chrome writes it in
1173
+ * the current history entry, keeping the box's aligned edge. */
1174
+ onTextAutoSize(layerId: string, size: readonly [number, number]): void;
1175
+ /** A comment pin was clicked (v42): from v45, a Studio pin on the camera
1176
+ * pane. */
1177
+ onCommentPin(threadId: string): void;
1178
+ /** A click on the camera frame with the Studio comment tool armed (v45):
1179
+ * the Shot and Shot-local frame on screen, the point, and the topmost
1180
+ * layer under it. */
1181
+ onStudioCommentAt(anchor: ShotStudioAnchor): void;
232
1182
  /**
233
1183
  * The key-light gizmo's drag, as the same three-call shape every other
234
1184
  * gesture uses: open a scrub, re-derive from its base on every emit, commit
@@ -271,6 +1221,23 @@ export interface ShotBridgeHandlers {
271
1221
  * view change is a mirror the chrome is free to throttle.
272
1222
  */
273
1223
  onIsolationOrbitTaken(): void;
1224
+ /**
1225
+ * GIVE THE CAMERA BACK: re-derive the head-on rig for whatever this pane is
1226
+ * looking at. The exact inverse of `onIsolationOrbitTaken`.
1227
+ *
1228
+ * The hand-over above is one-way by design, and Home is the key that undoes
1229
+ * it — but the material workspace covers the panel that key belongs to, and
1230
+ * in that mode EVERY drag orbits, so the first accidental nudge left the
1231
+ * object crooked with no visible way back. A double-click on the object is
1232
+ * the gesture, and it starts on the stage because the chrome cannot receive
1233
+ * it: the workspace body is `pointer-events: none` so its drags reach the
1234
+ * stage, which also means nothing there is clickable.
1235
+ *
1236
+ * It carries no argument on purpose. WHAT to re-derive is the chrome's
1237
+ * question — it owns the isolation and the component context that decide it —
1238
+ * and this is only the news that the author asked.
1239
+ */
1240
+ onFitRequest(): void;
274
1241
  /** The stage's own pan/zoom changed. Camera pane only: the working pane's
275
1242
  * view is a `WorkingView`, and the two are not the same shape. */
276
1243
  onViewChange(view: ShotView): void;
@@ -319,18 +1286,46 @@ export interface ShotBridgeHandlers {
319
1286
  // for being under a shot. One scrub is open at a time either way (engine's
320
1287
  // history throws on a second), which the pointer already guarantees.
321
1288
  //
322
- // It brackets TWO gestures: a drag on the frustum gizmo, which sends deltas;
323
- // and — while "camera follows this view" is armed — a working-pane orbit or
324
- // pan, which sends none at all. The view change IS the payload there, and it
325
- // already crosses as `onWorkingViewChange`.
1289
+ // It brackets the camera gizmo's drags in the working pane and, while
1290
+ // PILOTING (v29), the Camera pane's own navigation: an orbit, a pan and a
1291
+ // wheel there are framing deltas like any other.
326
1292
  /** Answers whether the scrub opened, for `onGestureStart`'s reason. */
327
1293
  onFramingGestureStart(): boolean;
328
- /** Measured FROM THE PRESS, same rule as `onGesture`. `dzoom` is a factor. */
329
- onFramingGesture(delta: FramingDelta): void;
1294
+ /**
1295
+ * Measured FROM THE PRESS, same rule as `onGesture`. `dzoom` is a factor.
1296
+ *
1297
+ * `origin: "wheel"` (v29) is a notch with no press and no release around
1298
+ * it: the chrome opens the scrub itself and closes it over silence, the way
1299
+ * follow's wheel used to be bracketed. ANSWERS whether the document took
1300
+ * the write — false is the fork's refusal (a held camera, a path) — so the
1301
+ * pilot badge can flash rather than the pane silently not moving.
1302
+ */
1303
+ onFramingGesture(delta: FramingDelta, origin?: "wheel"): boolean;
330
1304
  onFramingGestureEnd(commit: boolean): void;
1305
+ /** The pilot badge's own EJECT (v29): hands off the camera, which stays
1306
+ * where it is. The chrome owns the mode, so this is only the news. */
1307
+ onLeavePilot(): void;
1308
+ /** Which component drives the render camera this frame, by name, or null
1309
+ * when the document's rig does (v29). Pushed on change, never per frame. */
1310
+ onCameraDriver(name: string | null): void;
1311
+ /**
1312
+ * A PATH MOVE'S WAYPOINT, dragged in the working pane.
1313
+ *
1314
+ * The same bracket as the three above — the scrub is already open and the
1315
+ * camera lane is already the one that owns it — but a different write: this
1316
+ * moves one pose in a list rather than the framing the camera resolves to.
1317
+ * `writeFraming` REFUSES a drag over a path for exactly that reason (there is
1318
+ * no single end for a gesture to land on), and this is the answer to the
1319
+ * refusal: name the pose.
1320
+ *
1321
+ * `dx`/`dy` are design px MEASURED FROM THE PRESS, `onGesture`'s rule, so a
1322
+ * coalesced move cannot make the drag drift.
1323
+ */
1324
+ onWaypointGesture(shotId: string, index: number, delta: { dx: number; dy: number }): void;
331
1325
 
332
1326
  /**
333
- * INSERT A LAYER. The camera pane's draw lane, and the pen's commit.
1327
+ * INSERT A LAYER. The frame pane's draw lane (the camera pane over a Shot,
1328
+ * an open definition's one pane inside it), and the pen's commit.
334
1329
  *
335
1330
  * ── THE POINTS ARE DESIGN COORDINATES, NOT SCREEN ONES ─────────────────
336
1331
  *
@@ -353,8 +1348,16 @@ export interface ShotBridgeHandlers {
353
1348
  * carries handles now and crosses as `onPenCommit` below — but `onDraw`
354
1349
  * still accepts `"path"`, because `layerFromDraw` remains the way an
355
1350
  * agent-free caller mints a polyline path.
1351
+ *
1352
+ * `under` is the pick under a TEXT tool CLICK (no drag), front first. A
1353
+ * click on a text layer edits it rather than stacking a new one, the
1354
+ * Figma and Paper rule; the chrome decides, since it owns the layer kinds.
356
1355
  */
357
- onDraw(tool: InsertTool, points: readonly (readonly [number, number])[]): void;
1356
+ onDraw(
1357
+ tool: InsertTool,
1358
+ points: readonly (readonly [number, number])[],
1359
+ under?: readonly string[],
1360
+ ): void;
358
1361
 
359
1362
  /**
360
1363
  * COMMIT THE PEN'S PATH: anchors, their bezier handles, and whether the loop
@@ -374,6 +1377,60 @@ export interface ShotBridgeHandlers {
374
1377
  */
375
1378
  onPenCommit(data: PathData): void;
376
1379
 
1380
+ /** Commit a working-pane 3D spline draft as one layer and one undo entry. */
1381
+ onSplineCreateCommit(draft: SplineCreateDraft): void;
1382
+ /** Point-mode selection remains chrome-owned, just like layer selection. */
1383
+ onSplinePointPick(layerId: string, pointId: string, additive: boolean): void;
1384
+ /** A point/handle drag, bracketed into one document history entry. */
1385
+ onSplinePointGestureStart(layerId: string, pointId: string, part: SplinePointPart): boolean;
1386
+ onSplinePointGesture(patch: SplinePointPatch): void;
1387
+ onSplinePointGestureEnd(commit: boolean): void;
1388
+ /**
1389
+ * A ruler guide drag, bracketed into one history entry (v44).
1390
+ *
1391
+ * `guideId` null CREATES a guide on `axis`, pulled out of a ruler; an id
1392
+ * moves that guide. Returns false when the chrome refuses (a viewer, an
1393
+ * unknown id), and the stage then draws nothing. `at` is the guide's
1394
+ * position in the inspector's units: centre origin, y UP, which is what the
1395
+ * ruler under it reads.
1396
+ */
1397
+ onGuideGestureStart(guideId: string | null, axis: "x" | "y"): boolean;
1398
+ onGuideGesture(at: number): void;
1399
+ onGuideGestureEnd(outcome: GuideGestureOutcome): void;
1400
+ /** Set one guide's position from the numeric entry: one history entry. */
1401
+ onGuideSet(guideId: string, at: number): void;
1402
+ /** Delete one guide (a selected guide and Delete). */
1403
+ onGuideRemove(guideId: string): void;
1404
+ /**
1405
+ * A motion path key drag, bracketed into one history entry (v44).
1406
+ *
1407
+ * `keyId` is a key on the layer's `transform.position` track. Each
1408
+ * `onPathKeyGesture` carries the WHOLE offset since the start, in position
1409
+ * units, and the chrome writes start + offset. `part` is `"key"` for the
1410
+ * dot, or a Bezier handle (`"in"`, `"out"`) on a bezier path: the chrome
1411
+ * then writes the handle's offset, mirroring the other one unless the delta
1412
+ * says `breakPair`.
1413
+ */
1414
+ onPathKeyGestureStart(layerId: string, keyId: string, part: PathKeyPart): boolean;
1415
+ onPathKeyGesture(delta: PathKeyDelta): void;
1416
+ onPathKeyGestureEnd(commit: boolean): void;
1417
+ /** A double-click on a motion path key dot (v44). The chrome toggles the
1418
+ * key between corner and smooth on a bezier path, and seeks to it on any
1419
+ * other. */
1420
+ onPathKeyDoubleClick(layerId: string, keyId: string): void;
1421
+ /** Insert an anchor after a segment's leading point. */
1422
+ onSplineSegmentInsert(
1423
+ layerId: string,
1424
+ afterPointId: string,
1425
+ point: SplinePoint3D,
1426
+ /** Local progress in the authored span, used for a shape-preserving Bezier split. */
1427
+ spanProgress?: number,
1428
+ ): void;
1429
+ /** Delete selected anchors in point mode, subject to the valid-curve floor. */
1430
+ onSplinePointDelete(layerId: string, pointIds: readonly string[]): void;
1431
+ /** Enter confirms point mode and Escape leaves it when no drag is live. */
1432
+ onSplinePointModeEnd(layerId: string): void;
1433
+
377
1434
  /** A key the STAGE received. Returns true when the chrome consumed it, so the
378
1435
  * stage knows whether to preventDefault. Keyboard events go to ONE document,
379
1436
  * and once the canvas has focus that document is the stage. */
@@ -440,12 +1497,36 @@ export interface ShotStageBridge {
440
1497
  * called: there is nothing worth painting before a chrome exists, and the
441
1498
  * document arrives immediately after. */
442
1499
  connect(handlers: ShotBridgeHandlers): void;
443
- /** Replace the document. Cheap when the reference is unchanged. */
444
- loadDoc(doc: Doc3D): void;
1500
+ /** Replace the document. Cheap when the reference is unchanged. `rev` is
1501
+ * the saved revision it is (v34), or null for an edit not yet saved: it is
1502
+ * stamped on a piece failure first seen while this document is on stage. */
1503
+ loadDoc(doc: Doc3D, rev?: string | null): void;
445
1504
  /** Park the studio clock on an integer MASTER frame and repaint. */
446
1505
  seek(frame: number): void;
1506
+ /**
1507
+ * The transport is running, or has stopped.
1508
+ *
1509
+ * SCHEDULING ONLY. The stage may use this to decide WHEN work happens — a
1510
+ * repaint left for the next seek, a resolution held down until the transport
1511
+ * stops — and may never let it reach a cache key or a sampled value. Every
1512
+ * frame that is rested on, captured or exported is drawn by the same code
1513
+ * whether or not it was played through, and two machines playing one document
1514
+ * still ask for the same pixels.
1515
+ *
1516
+ * A stage that ignores this call is CORRECT, only slower, which is what makes
1517
+ * the v20 gate a soft one.
1518
+ *
1519
+ * `true` also lets a freeze a preparing pass left standing lift on the first
1520
+ * complete presentation of the current frame in EITHER presentation (v51),
1521
+ * not only a paused one. Never at once: the canvas may still hold another
1522
+ * Shot's picture.
1523
+ */
1524
+ setPlaying(playing: boolean): void;
447
1525
  /** Which layers are selected, for the overlays. */
448
1526
  setSelection(ids: readonly string[]): void;
1527
+ /** Other people's selections, drawn as outlines in their colours (v43).
1528
+ * Ids the drawn shot does not hold draw nothing. An empty list clears. */
1529
+ setPeerSelections(peers: readonly ShotPeerSelection[]): void;
449
1530
  /** Screen quads for the current selection at the current frame, or null when
450
1531
  * nothing is selected. The chrome polls this from its own render rather than
451
1532
  * being pushed at, so an overlay re-render is never scheduled by the stage.
@@ -455,21 +1536,46 @@ export interface ShotStageBridge {
455
1536
  * keeps getting the answer it always got. */
456
1537
  selectionQuads(pane?: StagePane): readonly ShotScreenQuad[] | null;
457
1538
  /**
458
- * Which tool is armed, because it decides the PLANE a move unprojects onto:
459
- * "select" slides a tilted layer along its own surface, "move" slides it flat
460
- * with z untouched. See stage/interactions/move.ts — that distinction is the
461
- * whole difference between the two, and it only exists once layers can tilt.
1539
+ * Which tool is armed, because it decides what a press does (pick, draw, pen)
1540
+ * and the cursor. The plane a move unprojects onto is not a tool: a plain
1541
+ * drag slides along the layer's own plane and an Alt-drag keeps it flat (see
1542
+ * stage/interactions/move.ts).
462
1543
  */
463
1544
  setTool(tool: ShotTool): void;
1545
+ /** Editor-only construction plane, re-pushed on attach and scene change. */
1546
+ setWorkPlane(target: WorkPlaneTarget): void;
1547
+ /** Interpolation for the next spline draft. A draft freezes it on its first anchor. */
1548
+ setSplineBasis(basis: SplineBasis): void;
1549
+ /** Enter/leave spline point mode and mirror the selected anchors. */
1550
+ setSplineEditContext(context: SplineEditContext | null): void;
464
1551
  /** Smart-guide lines the chrome's snapping landed on, in DESIGN frame
465
1552
  * coordinates. Pushed rather than polled because they change only while a
466
1553
  * gesture is running and must vanish the instant it ends. */
467
1554
  setSnapGuides(guides: readonly SnapGuide[]): void;
1555
+ /**
1556
+ * The layer a CHROME surface is pointing at (a timeline row under the
1557
+ * cursor), or null when it left. The stage lights that layer's outline
1558
+ * through the same hover path its own pointer uses — pointing at the row
1559
+ * answers "which one is this" without a click and without touching the
1560
+ * selection.
1561
+ *
1562
+ * Imperative on the stage side by design: under `frameloop="never"` every
1563
+ * state push is a React commit and therefore a GL draw, and a hover must
1564
+ * cost one SVG leaf repaint and zero draws (v22).
1565
+ */
1566
+ hoverLayer(layerId: string | null): void;
468
1567
  /** Drive the working-area view. */
469
1568
  setView(view: ShotView): void;
470
1569
  /** Fit the design frame to the CAMERA PANE with a margin. Returns the view it
471
1570
  * landed on so the chrome's readout stays in step. */
472
1571
  fitView(): ShotView;
1572
+ /** A point of the STAGE DOCUMENT (its client px, what a `drop` event on it
1573
+ * reports) as design px from the canvas centre, the coordinates `onDraw`
1574
+ * hands over, or null when it is not over the frame pane (the camera pane
1575
+ * over a Shot, an open definition's one pane inside it, where the point is
1576
+ * from the definition's canvas centre). A drop onto the frame places at
1577
+ * the point the author let go (v38). */
1578
+ designPointAt(clientX: number, clientY: number): readonly [number, number] | null;
473
1579
  /**
474
1580
  * How many views are on screen, and how the width is divided.
475
1581
  *
@@ -478,6 +1584,115 @@ export interface ShotStageBridge {
478
1584
  * is re-pushed on every attach.
479
1585
  */
480
1586
  setStageLayout(layout: StageLayout): void;
1587
+ /**
1588
+ * THE AUTHOR IS WATCHING: draw the picture and nothing else.
1589
+ *
1590
+ * Video is the export's frame with every piece of authoring furniture taken
1591
+ * off it: no selection handles, no union box, no hover outline, no marquee,
1592
+ * no draw or transform gestures, no pen or spline draft. What stays is the
1593
+ * frame border, navigation (wheel, space-drag) and the plain click, which is
1594
+ * how an author picks the layer they want to work on and lands back in Edit.
1595
+ *
1596
+ * A FLAG AND NOT A LAYOUT, deliberately. The stage forces the camera-only
1597
+ * panes while this is on and hands the author's real layout back untouched
1598
+ * when it goes off, so `setStageLayout` keeps saying which EDITOR is waiting
1599
+ * underneath and this says whether anything is drawn on top of it.
1600
+ *
1601
+ * WORKSPACE STATE, like the layout: never written to a Doc3D, never in an
1602
+ * export, and re-pushed on every attach.
1603
+ */
1604
+ setVideo(on: boolean): void;
1605
+ /**
1606
+ * The Project is VIEW ONLY for this person (v41): their role lets them look
1607
+ * but not edit. The stage keeps what looking needs (hover, click-select, the
1608
+ * marquee, the outlines, navigation) and drops what editing needs: no
1609
+ * handles, no drag gestures, no draw tools, no double-click text edit, no
1610
+ * camera framing drag, no key light drag. The chrome refuses every write as
1611
+ * well, so this only spares a viewer gestures that would be refused anyway.
1612
+ *
1613
+ * WORKSPACE STATE, like `setVideo`: never written to a Doc3D, re-pushed on
1614
+ * every attach.
1615
+ */
1616
+ setReadOnly(on: boolean): void;
1617
+ /**
1618
+ * THE BOARD IS WATCHING, through a hole the size of a postcard.
1619
+ *
1620
+ * The same picture `setVideo` asks for (the camera pane, no furniture,
1621
+ * `watching` in StageApp answers for both), and a SECOND flag rather than a
1622
+ * second caller of the first: `setVideo` is the author choosing Video, it
1623
+ * writes the focus layout, empties the selection through the chrome's own
1624
+ * `setVideo` verb and collapses the inspector the board needs, and it is
1625
+ * refused outright while a component definition is open. None of that is
1626
+ * true of a board that simply wants to see the frame it is planning.
1627
+ *
1628
+ * It also carries the FRAMING, which `setVideo` does not: while this is on
1629
+ * the camera pane's view is derived from `fit()` every render instead of
1630
+ * read from `state.view`, so the shot lands whole inside the hole and
1631
+ * nothing has to be put back when the board closes.
1632
+ *
1633
+ * WORKSPACE STATE, like the layout: never written to a Doc3D and never in
1634
+ * an export. Only the component stage (ComponentStage) sends it, on its own
1635
+ * bridge: the shot stage starts with it off and the chrome never turns it on.
1636
+ */
1637
+ setStoryboardPreview(on: boolean): void;
1638
+ /**
1639
+ * THE STUDIO'S COMMENT LAYER (bridge v45), or null for none.
1640
+ *
1641
+ * Pins draw over the camera pane's frame while stopped on their anchor's
1642
+ * Shot and frame; a click on one calls `onCommentPin`. Armed, a primary
1643
+ * press on the frame calls `onStudioCommentAt` instead of picking. Not
1644
+ * drawn inside a component context or in the material workspace.
1645
+ *
1646
+ * WORKSPACE STATE: never written to a Doc3D, re-pushed on every attach.
1647
+ */
1648
+ setStudioComments(comments: ShotStudioComments | null): void;
1649
+ /**
1650
+ * THE SKETCH GUIDE (bridge v47), or null for none: one storyboard sketch
1651
+ * drawn as dashed outlines and labels in the camera frame, so it follows
1652
+ * the camera pane's view. Not drawn while watching, inside a component
1653
+ * context or in the material workspace.
1654
+ *
1655
+ * WORKSPACE STATE: never written to a Doc3D, re-pushed on every attach.
1656
+ */
1657
+ setSketchGuide(guide: ShotSketchGuide | null): void;
1658
+ /**
1659
+ * THE FONT PREVIEW (bridge v50), or null for none: the font picker's
1660
+ * hovered or arrowed-to family drawn on the given text layers. Only the
1661
+ * picture changes: picking, overlays, auto-width measuring, stills,
1662
+ * coverage, health and every export keep the real document, and a face
1663
+ * that fails to load reports nothing and holds no readiness claim. While a
1664
+ * preview changes a text layer of the open document, `coverage()` refuses
1665
+ * (its picture is not the Shot's) and `capturePoster` answers null; a
1666
+ * preview arriving mid-poster aborts the poster. An export or a capture
1667
+ * draws the real document and the preview shows again when it ends. Until
1668
+ * the faces land the layers draw `family`, then `fallback`.
1669
+ *
1670
+ * WORKSPACE STATE: never written to a Doc3D, cleared on every attach and
1671
+ * when the document closes.
1672
+ */
1673
+ setFontPreview(preview: ShotFontPreview | null): void;
1674
+ /**
1675
+ * THE MOTION PREVIEW (bridge v53), or null for none: the Motion section's
1676
+ * active preset stamped on the given layers. Only the picture changes, as
1677
+ * with the font preview: picking, overlays, health and every export keep
1678
+ * the real document, and a presentation drawn under it is never reported
1679
+ * as the Project's (`health()` reads pending until it clears). The chrome
1680
+ * moves through the preset's frames with ordinary seeks and seeks back to
1681
+ * the playhead when it clears. While it changes the open document,
1682
+ * `coverage()` refuses, `capturePoster` answers null and a preparing pass
1683
+ * gives way.
1684
+ *
1685
+ * WORKSPACE STATE: never written to a Doc3D, cleared on every attach and
1686
+ * when the document closes.
1687
+ */
1688
+ setMotionPreview(preview: ShotMotionPreview | null): void;
1689
+ /**
1690
+ * OPEN THE INLINE TEXT EDITOR on one text layer, or null to close it
1691
+ * without committing (v38). The stage draws the editor over the layer, with
1692
+ * the content selected, and answers `onTextEdit` on Escape or a click
1693
+ * outside. Workspace state: never written to a Doc3D.
1694
+ */
1695
+ editText(layerId: string | null): void;
481
1696
  /**
482
1697
  * ISOLATE A SUBTREE, or null for the whole scene.
483
1698
  *
@@ -493,6 +1708,26 @@ export interface ShotStageBridge {
493
1708
  * "the shot", which is exactly the split that already exists.
494
1709
  */
495
1710
  setIsolation(layerId: string | null): void;
1711
+ /** Give the working pane to the material workspace. The target remains
1712
+ * rendered and navigable, but scene picking, transforms and draw tools stop.
1713
+ *
1714
+ * v17 addresses a SLOT rather than a layer, because a model's submeshes each
1715
+ * carry their own graph and the workspace edits exactly one of them. */
1716
+ setMaterialPreview(target: MaterialPreviewTarget | null): void;
1717
+ /**
1718
+ * ENTER OR LEAVE THE COMPONENT CONTEXT (v14): render one definition
1719
+ * standalone, or null for the ordinary scene render.
1720
+ *
1721
+ * WORKSPACE STATE like isolation: never written to a Doc3D, never in an
1722
+ * export, re-pushed on every attach. The stage is handed the def id and
1723
+ * derives everything from the document it already has. While a context is
1724
+ * set, `seek` frames are DEF-LOCAL; `setIsolation` and the camera/split
1725
+ * machinery are inert (the context owns the whole viewport). The chrome
1726
+ * clears isolation before setting a context — the two are mutually
1727
+ * exclusive views, and the stage refuses nothing here because the chrome
1728
+ * owns the mode.
1729
+ */
1730
+ setComponentContext(ctx: ShotComponentContext | null): void;
496
1731
  /**
497
1732
  * Inside isolation, does the AUTHOR own the working camera?
498
1733
  *
@@ -509,6 +1744,10 @@ export interface ShotStageBridge {
509
1744
  *
510
1745
  * `setIsolation` resets it to false on its own, so entering a subtree never
511
1746
  * inherits the last one's mode.
1747
+ *
1748
+ * `false` while isolated is also Home: the stage re-fits the held isolation
1749
+ * frame from the current playhead. That frame otherwise stays still across
1750
+ * edits and playback.
512
1751
  */
513
1752
  setIsolationOrbit(taken: boolean): void;
514
1753
  /**
@@ -517,11 +1756,12 @@ export interface ShotStageBridge {
517
1756
  *
518
1757
  * ── WHY THE STAGE CANNOT MEASURE THIS ITSELF ────────────────────────────
519
1758
  *
520
- * A pane is the WHOLE WINDOW (`paneRects`), because the stage iframe is
521
- * pinned `position: fixed; inset: 0` and the chrome's panels float ON TOP of
522
- * it. So "the top-right corner of the working pane" is behind the shell
523
- * header, and there is no measurement the stage's document can take to
524
- * discover that — the panels are in the other document.
1759
+ * A pane runs to the WINDOW'S OWN EDGES — `paneRects` insets nothing for the
1760
+ * chrome, and in a split the two panes divide the window between them —
1761
+ * because the stage iframe is pinned `position: fixed; inset: 0` and the
1762
+ * chrome's panels are docked ON TOP of it. So the top of the working pane is
1763
+ * behind the shell header, and there is no measurement the stage's document
1764
+ * can take to discover that — the panels are in the other document.
525
1765
  *
526
1766
  * `Overlays` carried a hardcoded 116 for the top edge and a note saying the
527
1767
  * real fix was this message, "worth it when a second overlay needs the same
@@ -530,9 +1770,23 @@ export interface ShotStageBridge {
530
1770
  * thing that says a modal transform is live, has never been visible in the
531
1771
  * default layout at all.
532
1772
  *
533
- * PAINT ONLY. `paneRects` must not change: insetting the pane rects would
534
- * move the scissor and the hit test together and shrink the rendered picture
535
- * to fit the chrome. This positions overlay FURNITURE and nothing else.
1773
+ * ── WHAT IT MOVES, AND WHAT IT MUST NEVER MOVE ──────────────────────────
1774
+ *
1775
+ * It moves the things that have to be SEEN: the overlay's furniture, the
1776
+ * derived head-on framing, the camera pane's zoom-to-fit, and where the split
1777
+ * SEAM falls (the ratio is the working pane's share of the width, and the
1778
+ * width it divides has to be the width the author can see — dividing the
1779
+ * whole window put the seam at 683 on a 1366 laptop whose visible canvas ran
1780
+ * 212..1066, handing the working pane 471 visible px against 383 with a fifth
1781
+ * of it behind the rail).
1782
+ *
1783
+ * It moves NEITHER THE SCISSOR NOR THE HIT TEST. `paneRects` takes the inset
1784
+ * for the seam alone; the rects it returns still partition the WHOLE
1785
+ * viewport, so the stage keeps drawing full-bleed under the docked chrome
1786
+ * and `paneAt` keeps answering for every pixel of it. Insetting those rects
1787
+ * instead would shrink the rendered picture to fit the chrome and leave dead
1788
+ * gutters where the panels sit. Which pixels are visible is a separate
1789
+ * question, asked separately, by `insetRect`.
536
1790
  *
537
1791
  * Editor state, never a document field.
538
1792
  */
@@ -545,6 +1799,27 @@ export interface ShotStageBridge {
545
1799
  * here — it is always the graded render, by construction.
546
1800
  */
547
1801
  setShadingMode(mode: ShadingMode): void;
1802
+ /**
1803
+ * The working pane's grid floor, on or off (v22).
1804
+ *
1805
+ * WORKSPACE STATE by `setShadingMode`'s rule: never written to a Doc3D,
1806
+ * never in an export (the grid is an EDITOR_LAYER object the shot camera
1807
+ * cannot see), re-pushed on every attach. The spline work-plane aid is NOT
1808
+ * gated by this — a drawing aid for an open gesture is not furniture.
1809
+ */
1810
+ setGridVisible(visible: boolean): void;
1811
+ /**
1812
+ * The rulers and smart guides, on or off (v44), and the playback
1813
+ * resolution (v52).
1814
+ *
1815
+ * WORKSPACE STATE by `setGridVisible`'s rule: a per-viewer preference,
1816
+ * never written to a Doc3D, never in an export, re-pushed on every attach.
1817
+ * The guides themselves are document data and arrive with `loadDoc`.
1818
+ */
1819
+ setEditorChrome(chrome: EditorChrome): void;
1820
+ /** Motion paths for the selected layers, on or off (v44). Workspace state
1821
+ * by `setGridVisible`'s rule, re-pushed on every attach. */
1822
+ setMotionPaths(on: boolean): void;
548
1823
  /** Drive the working pane's orbit camera. */
549
1824
  setWorkingView(view: WorkingView): void;
550
1825
  /** Frame the whole canvas in the working pane. Returns the view it landed on,
@@ -552,15 +1827,28 @@ export interface ShotStageBridge {
552
1827
  * owns the lens the distance is derived from. */
553
1828
  fitWorkingView(): WorkingView;
554
1829
  /**
555
- * "Camera follows this view" is armed.
1830
+ * PILOTING is armed, or not (v29).
556
1831
  *
557
- * The stage needs it for two things it is the only half able to do: bracket
558
- * an orbit with the framing gesture lane, so one drag is one undo entry; and
559
- * draw the affordance INSIDE the working pane. Blender's long-running
560
- * complaint about its equivalent is that it is easy to forget it is on, and a
561
- * checkbox in a panel the author is not looking at is exactly how.
1832
+ * The stage needs it for two things it is the only half able to do: route
1833
+ * the Camera pane's navigation (middle-drag orbit, shift-drag pan, wheel
1834
+ * zoom) down the framing gesture lane instead of the viewport, so one drag
1835
+ * is one undo entry; and draw the affordance INSIDE the pane. Blender's
1836
+ * long-running complaint about its "Lock Camera to View" is that it is easy
1837
+ * to forget it is on, and a checkbox in a panel the author is not looking at
1838
+ * is exactly how.
1839
+ *
1840
+ * `text` is the badge's words when something holds the camera (a finished
1841
+ * move with Animate off, a path, a component driving it), composed by the
1842
+ * chrome because the chrome knows why; null draws the plain badge.
1843
+ */
1844
+ setPilot(on: boolean, text: string | null): void;
1845
+ /**
1846
+ * What a camera write would do at the playhead (v29), or null with no
1847
+ * document. The gizmo dims when it is not `free`, the pointer lane refuses
1848
+ * the press with the gate's own sentence, and the hover names the move —
1849
+ * all BEFORE a drag, where the error strip used to speak after one.
562
1850
  */
563
- setCameraFollowsView(on: boolean): void;
1851
+ setCameraGate(gate: CameraGate | null): void;
564
1852
  /** Drop the document: the stage renders nothing and every layer plane
565
1853
  * unmounts, releasing its raster pins and texture bindings. The close
566
1854
  * half of `loadDoc`. */
@@ -571,10 +1859,297 @@ export interface ShotStageBridge {
571
1859
  * closed a panel. Not called on scene switch: surviving a switch warm is
572
1860
  * the whole reason the cache is module-level. */
573
1861
  clearCaches(): void;
1862
+ /**
1863
+ * Tier-1 export: what THIS browser can encode at the document's dimensions.
1864
+ * Empirical (a VideoEncoder/AudioEncoder probe inside the encode worker),
1865
+ * never UA sniffing. Cached per dimensions on the stage side.
1866
+ */
1867
+ exportCapability(): Promise<Tier1Capability>;
1868
+ /**
1869
+ * Render the shot in place and encode an MP4, or a GIF (v32), in THIS
1870
+ * browser.
1871
+ *
1872
+ * The stage flips into export framing — design viewport, no view offset,
1873
+ * dpr 1, raster scale 1, overlays unmounted: the same numbers a Tier-2
1874
+ * render pins — drives every frame through the same draw latch the still
1875
+ * lane uses, and restores the author's framing and playhead when done.
1876
+ * While it runs, chrome `seek`/`loadDoc` pushes are DEFERRED (applied on
1877
+ * completion), never interleaved: a mid-capture re-render would let the
1878
+ * encoder stamp one frame's timestamp on another frame's pixels.
1879
+ *
1880
+ * One at a time — a second call rejects while one runs. Cancel via
1881
+ * `signal`; cancellation restores the stage and resolves nothing.
1882
+ */
1883
+ exportMovie(request: ShotExportMovieRequest): Promise<ShotExportMovieResult>;
1884
+ /**
1885
+ * The Project's poster frame (`posterFrame`, the frame the local CLI
1886
+ * renders) as a JPEG at most `maxWidth` wide: the dashboard's card
1887
+ * thumbnail.
1888
+ *
1889
+ * Captured the way an export frame is: export framing, then the in-draw
1890
+ * snapshot from the matching presentation, then the author's stage back.
1891
+ * The playback framebuffer is unpreserved, so the live canvas is never read.
1892
+ *
1893
+ * Null (never a throw) when there is nothing to capture or the stage is
1894
+ * busy: no document, an export in flight, playback or its preparing pass
1895
+ * running, a component or material being edited, or a capture that refused.
1896
+ * Null uploads nothing: an existing poster remains, and a card that never
1897
+ * had one keeps its deterministic gradient, so this is never worth an error.
1898
+ *
1899
+ * The chrome takes no lock for it, so the poster YIELDS: anything the author
1900
+ * starts on the stage mid-capture (Play, an export or `captureFrame`, a
1901
+ * scrub, an edit, a component, a press, a font preview) aborts it, runs
1902
+ * once the stage has left its export framing, and the poster answers null.
1903
+ * A document whose
1904
+ * capture failed answers null at once until it changes, so the chrome may
1905
+ * ask again whenever it likes.
1906
+ */
1907
+ capturePoster(maxWidth: number): Promise<Blob | null>;
1908
+ /**
1909
+ * One exact still of the whole timeline (v32): the storyboard's "Frame
1910
+ * from Shot". Captured the way `capturePoster` is (export framing, the
1911
+ * in-draw snapshot at `frame`, the author's stage back), then scaled to the
1912
+ * asked size and encoded.
1913
+ *
1914
+ * Rejects with the reason, unlike the poster: the author asked for this
1915
+ * picture, and a silent nothing would read as a dead menu row. The chrome
1916
+ * holds its export lock around the call, which pauses the transport.
1917
+ */
1918
+ captureFrame(request: ShotCaptureFrameRequest): Promise<Blob>;
1919
+ /**
1920
+ * THUMBNAILS for a batch of nodes in the open material graph.
1921
+ *
1922
+ * The same request/response shape `capturePoster` established, and for the
1923
+ * same reason: the chrome is three-free by test, so it cannot render a node's
1924
+ * value itself. The stage compiles one unlit preview material per requested
1925
+ * node, draws them into ONE atlas render target and reads it back once —
1926
+ * one GPU stall per batch rather than per thumbnail.
1927
+ *
1928
+ * Null (never a throw) when there is nothing to draw: no stage, an export in
1929
+ * flight, no material preview target set, or a newer request superseded this
1930
+ * one. A missing thumbnail is a blank card, which is worth no error.
1931
+ *
1932
+ * EDITOR ONLY. The component that answers this is mounted only while the
1933
+ * stage is not exporting, and the CLI's render page never mounts it at all,
1934
+ * so no export can be reached from here.
1935
+ */
1936
+ requestNodePreviews(request: NodePreviewRequest): Promise<NodePreviewBatch | null>;
1937
+ /**
1938
+ * DRAW EACH OF `frames` ONCE, so playback does not pay a first-use cost on
1939
+ * any of them (`playbackPreparation.ts` says what that cost is and which
1940
+ * frames carry it). One pass serves a queued Play and the background alike:
1941
+ * `pace()` says which it is, and is asked again at every hop and at the end
1942
+ * of the list, so a Play pressed during a background pass upgrades it.
1943
+ *
1944
+ * EVERY PASS DRAWS UNDER A FROZEN PICTURE. The frame on screen is presented
1945
+ * again, copied inside that presentation and laid over the canvas with a
1946
+ * shield above every overlay; a freeze an earlier pass left standing is
1947
+ * adopted instead. A press or a wheel on the shield ends the pass
1948
+ * (`"press"`). The stage then switches to its PLAYING presentation (the
1949
+ * warm-up's playing roles), because that is what playback will draw with,
1950
+ * and publishes each frame with a fresh token, the frame already on screen
1951
+ * included. It waits for that frame's playing presentation with a deadline,
1952
+ * so one frame that never lands cannot hold Play for ever, and reports the
1953
+ * frame's outcome to `onProgress`.
1954
+ *
1955
+ * At the end of the list under a `"play"` pace it HANDS OVER: the start
1956
+ * frame is presented again in the playing presentation, the freeze lifts
1957
+ * inside that presentation, and it resolves `handedOver` with the stage
1958
+ * left playing, for the chrome to start the transport at once (a start
1959
+ * frame that is not presented within 5 s restores instead). Under an
1960
+ * `"idle"` pace, and on a cancel, a yield or an export, it restores the
1961
+ * stage's own playing flag and the start frame, and the freeze lifts on the
1962
+ * first complete presentation of the current frame (a later seek moves the
1963
+ * lift to the newer frame, and a scrub lifts it within 150 ms). A
1964
+ * press on the shield after the restore takes the freeze down at once.
1965
+ *
1966
+ * Refused (`refused`, none of `frames` drawn) with no document, no canvas
1967
+ * or a component context, and when the freeze itself times out. Under
1968
+ * export framing a `"play"` pace waits for a poster to step aside and an
1969
+ * `"idle"` pace yields (`"export"`). A stage interaction live at the start,
1970
+ * or begun during the pass, yields (`"interaction"`); the other early ends
1971
+ * are the rest of `ShotPreparePlaybackYield`. Never rejects: preparation is
1972
+ * an optimisation, and a stage that cannot do it is only slower.
1973
+ *
1974
+ * One at a time: a second call cancels the first.
1975
+ */
1976
+ preparePlayback(request: ShotPreparePlaybackRequest): Promise<ShotPreparePlaybackResult>;
1977
+ /**
1978
+ * What the GPU state behind prepared frames stands on (v51), for the
1979
+ * chrome's preparation keys. Synchronous and cheap. When `base` moves the
1980
+ * chrome drops every key; `modules` joins only the keys of Shots that place
1981
+ * code components.
1982
+ */
1983
+ preparationKey(): ShotPreparationKey;
1984
+ /**
1985
+ * The `(frame, document)` pairs of every whole presentation the TRANSPORT
1986
+ * drew in the playing presentation since it started, then cleared (v51),
1987
+ * so the chrome can credit what playback drew as prepared at Pause.
1988
+ *
1989
+ * Never a preparing pass's frames, and none while a component context or a
1990
+ * font preview shows. Reset when the transport starts and on `loadDoc` of
1991
+ * another document id.
1992
+ */
1993
+ takePlayed(): ReadonlyArray<{ frame: number; document: Doc3D }>;
1994
+ /**
1995
+ * What the stage knows about itself (v35), for an agent that cannot see it:
1996
+ * the crash, a lost context, the isolated pieces, the render fallbacks, the
1997
+ * component failures, the uncaught errors of this document and its export
1998
+ * worker, and what it last presented (a complete picture, readiness clear)
1999
+ * with the saved revision and module epoch it drew. Synchronous and cheap:
2000
+ * a copy of what the stage already keeps for its export gate.
2001
+ */
2002
+ health(): StageHealthSnapshot;
2003
+ /**
2004
+ * Which mesh-like layers put a pixel in the frame on stage (v36): for the
2005
+ * Shot under the playhead, at the frame last drawn, each mesh, device and
2006
+ * placed component layer the document says shows there (in the Shot or in
2007
+ * a placed component definition, at most `cap`, the rest listed as
2008
+ * skipped) is drawn away once and the frame compared with itself drawn
2009
+ * whole, offscreen, through the Shot's camera in export framing, its bins,
2010
+ * its post chain and its colour boundary. Nothing is presented, and the
2011
+ * author's picture, playhead and every flag it flips are as they were
2012
+ * after, a throw included. Works in a hidden tab.
2013
+ *
2014
+ * Rejects with the reason it could not check: no Shot under the playhead,
2015
+ * a component shown alone, an isolated layer or a material preview (the
2016
+ * picture is not the Shot's then), an export, Play or its preparing pass,
2017
+ * a playhead that moved. A preparing pass under an `"idle"` pace is ended
2018
+ * first and resolves `yielded: "coverage"`.
2019
+ */
2020
+ coverage(request?: CoverageRequest): Promise<StageCoverage>;
574
2021
  /** Tear the stage down (a chrome unmount, or a bridge version mismatch). */
575
2022
  dispose(): void;
576
2023
  }
577
2024
 
2025
+ /** Who is waiting on a preparing pass (v51), asked at every hop. `"play"`: a
2026
+ * queued Play. `"idle"`: nobody, the author sits paused. */
2027
+ export type ShotPreparePace = "play" | "idle";
2028
+
2029
+ /** How one frame of a preparing pass ended (v51). `"presented"`: its own
2030
+ * publication was presented in the playing presentation. `"timeout"`: its
2031
+ * deadline passed with no external readiness claim pending.
2032
+ * `"timeout-pending"`: its deadline passed while an external claim (a font,
2033
+ * a texture, an html layer, a video seek) was still pending. */
2034
+ export type ShotPreparedOutcome = "presented" | "timeout" | "timeout-pending";
2035
+
2036
+ /** Why a preparing pass ended before its list did (v51). */
2037
+ export type ShotPreparePlaybackYield =
2038
+ | "press" // a press or wheel on the shield (cancels a queued Play)
2039
+ | "input" // author input while pace() was "idle": the request's signal aborted with the reason "input"
2040
+ | "interaction" // a live stage interaction started (or was live at the start)
2041
+ | "push" // a stage push that changes the picture or the overlays
2042
+ | "resize" // a window resize
2043
+ | "hidden" // the document became hidden
2044
+ | "coverage" // a coverage() check needed the author's frame
2045
+ | "export"; // export framing (an export, a poster, a still, a capture)
2046
+
2047
+ /** The chrome's request for the preparing pass (`preparePlayback`). */
2048
+ export interface ShotPreparePlaybackRequest {
2049
+ /** MASTER frames, in the order to draw them. */
2050
+ frames: readonly number[];
2051
+ /** After each frame: presented, or its deadline passed (with or without an
2052
+ * external claim pending at the deadline). */
2053
+ onProgress?: ((framesDone: number, totalFrames: number, outcome: ShotPreparedOutcome) => void) | undefined;
2054
+ /** Aborting it cancels the pass, and the stage restores. Aborted with the
2055
+ * reason `"input"` (author input under an `"idle"` pace), the pass
2056
+ * resolves `yielded: "input"`. */
2057
+ signal?: AbortSignal | undefined;
2058
+ /** Asked at every hop and at the end of the list. `"play"`: a queued Play
2059
+ * is waiting (hop through a MessageChannel; hand over at the end).
2060
+ * `"idle"`: nothing is waiting (hop through requestIdleCallback with a
2061
+ * 100 ms timeout; restore at the end). */
2062
+ pace: () => ShotPreparePace;
2063
+ /** How long `frames[index]` may take to present, in ms, asked as it is
2064
+ * published: the chrome's second-timeout rule asks for 500 after a
2065
+ * timeout in the same Shot. Absent, or not a positive number, it is
2066
+ * `PREPARE_FRAME_DEADLINE_MS` (5 s), which is also the ceiling. */
2067
+ deadlineMs?: ((index: number) => number) | undefined;
2068
+ }
2069
+
2070
+ export interface ShotPreparePlaybackResult {
2071
+ /** Every frame was presented or timed out. */
2072
+ completed: boolean;
2073
+ /** Frames whose own publication was presented in the playing presentation. */
2074
+ presented: number;
2075
+ /** Ended early, for this reason. */
2076
+ yielded?: ShotPreparePlaybackYield;
2077
+ /** Could not run at all, and why: no document, no canvas, a component
2078
+ * context, or a freeze that timed out. None of `frames` was drawn. */
2079
+ refused?: string;
2080
+ /** Ended in the playing presentation on the start frame with the freeze
2081
+ * lifted: start the transport now. */
2082
+ handedOver?: true;
2083
+ }
2084
+
2085
+ /** What the GPU state behind prepared frames stands on (v51
2086
+ * `preparationKey()`). */
2087
+ export interface ShotPreparationKey {
2088
+ /** Moves when the GPU state prepared frames stand for is replaced:
2089
+ * `performance.timeOrigin : gpuEpoch : canvas CSS size @ resolved pixel
2090
+ * ratio : raster rung`. The GPU epoch moves on a new renderer, at the END
2091
+ * of export framing (an export, a poster, a still, `captureFrame`), on
2092
+ * `clearCaches` and on `closeDoc`, and each move is announced through
2093
+ * `onStageHealth`. A coverage check moves nothing: it draws into targets
2094
+ * of its own. */
2095
+ base: string;
2096
+ /** The placed code components' module epoch : hot-update count. */
2097
+ modules: string;
2098
+ }
2099
+
2100
+ /** The chrome's request for one exact still (`captureFrame`, v32). */
2101
+ export interface ShotCaptureFrameRequest {
2102
+ /** A MASTER frame of the whole timeline. */
2103
+ frame: number;
2104
+ /** The delivered size, in px. The frame is drawn at the design size, as an
2105
+ * export frame is, and scaled to this. */
2106
+ width: number;
2107
+ height: number;
2108
+ format: "png" | "jpeg" | "webp";
2109
+ signal?: AbortSignal | undefined;
2110
+ }
2111
+
2112
+ /** The chrome's request for a Tier-1 (in-browser) movie export. */
2113
+ export interface ShotExportMovieRequest {
2114
+ /** Absent = mp4. A GIF (v32) takes no audio, bitrate or quantizer, and
2115
+ * matches the server GIF's palette, timing and loop rules (tier1/gifSink.ts). */
2116
+ format?: ExportFormat | undefined;
2117
+ /** Scene id to export; absent or "" = the whole timeline (the same
2118
+ * WHOLE_TIMELINE convention the render route takes). */
2119
+ sceneId?: string | undefined;
2120
+ /** Master frames of the whole timeline to export, the work area (v39).
2121
+ * Refused with a `sceneId`. Absent = the whole scope. */
2122
+ window?: { start: number; frames: number } | undefined;
2123
+ /** Target video bitrate, bits/s — derived by the chrome from the shared
2124
+ * quality table (bitsPerPixel × w × h × fps) so the browser and server
2125
+ * paths honour the same quality choice. Absent = the encoder default. */
2126
+ videoBitrate?: number | undefined;
2127
+ /** The H.264 QP for every P frame, a whole number 0..51 (v49): the quality's
2128
+ * CRF from the same table. The stage encodes at it where a hardware encoder
2129
+ * honours a fixed QP, and at `videoBitrate` everywhere else. Absent =
2130
+ * `videoBitrate` only. */
2131
+ quantizer?: number | undefined;
2132
+ /** The document's audio, already mixed OFFLINE by the chrome (planar f32,
2133
+ * 48 kHz stereo). Absent = export silent. The chrome owns the mix because
2134
+ * audio was never part of the stage: the preview's clips play from the
2135
+ * chrome document, and the stage neither draws nor samples them. */
2136
+ audio?: { sampleRate: number; channels: Float32Array[] } | undefined;
2137
+ onProgress?: ((framesDone: number, totalFrames: number) => void) | undefined;
2138
+ signal?: AbortSignal | undefined;
2139
+ }
2140
+
2141
+ export interface ShotExportMovieResult {
2142
+ blob: Blob;
2143
+ extension: string;
2144
+ /** Set when the document HAS audio but this browser could not encode it —
2145
+ * the export shipped silent and the dialog says why (the motion lane's
2146
+ * exact affordance). */
2147
+ audioSkippedReason?: string;
2148
+ /** How the MP4's encoder ran (v49): a fixed QP or a bitrate, on a hardware
2149
+ * or a software encoder. Absent for a GIF. */
2150
+ encoding?: Mp4Encoding;
2151
+ }
2152
+
578
2153
  declare global {
579
2154
  interface Window {
580
2155
  __shotStudio?: ShotStageBridge;