@dailies-studio/shot-editor 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -0,0 +1,628 @@
1
+ // One node's value, drawn as a thumbnail — the stage half of the material
2
+ // workspace's node previews (bridgeTypes: `requestNodePreviews`).
3
+ //
4
+ // ── WHY IT IS STRUCTURALLY UNREACHABLE FROM AN EXPORT ────────────────────
5
+ //
6
+ // `StageApp` mounts this only while `exporting === null` and the material
7
+ // workspace names a preview target, so in export framing it is not a component
8
+ // that declines to run — it is a component that does not exist. The CLI render
9
+ // page never mounts `StageApp` at all, so the whole file is unreachable there.
10
+ // `bridgeClient.requestNodePreviews` refuses on the same two conditions, which
11
+ // is a belt over that structure rather than the only guard.
12
+ //
13
+ // ── A QUAD, NOT THE SPECIMEN MESH ────────────────────────────────────────
14
+ //
15
+ // A thumbnail is a VALUE MAP, not shading. The quad shows the whole UV domain
16
+ // identically for every mesh bound to the graph, which buys three things: the
17
+ // picture answers "what is in this branch" rather than "what does it look like
18
+ // on this model", the rig cache keys on the GRAPH alone, and a 400k-triangle
19
+ // GLB is never drawn 64 times for a wall of 96px pictures. `createDesignPlaneGeometry`
20
+ // is the same quad the plane primitive uses, so uv (0,0) is the top-left corner
21
+ // in both.
22
+ //
23
+ // ── ONE ATLAS, ONE READBACK ──────────────────────────────────────────────
24
+ //
25
+ // Every cell is rendered into ONE render target under a scissor, and the whole
26
+ // target comes back in a single `readRenderTargetPixelsAsync`. That call is a
27
+ // GPU stall — the fence has to settle before the buffer is readable — so the
28
+ // cost of a batch is one stall regardless of how many nodes it holds. Sixty-four
29
+ // separate readbacks would be sixty-four stalls on the canvas the author is
30
+ // still dragging in.
31
+ //
32
+ // ── CELL 0 IS AN ORIENTATION PROBE ───────────────────────────────────────
33
+ //
34
+ // The two backends hand back rows in opposite orders. WebGL reads through
35
+ // `gl.readPixels`, whose origin is bottom-left, so row 0 is the BOTTOM of the
36
+ // target; WebGPU's `copyTextureToBuffer` copies a top-left-origin texture, so
37
+ // row 0 is the TOP. Nothing in the renderer normalises that, and a wrong guess
38
+ // is not a crash — it is every thumbnail silently upside down, on whichever
39
+ // backend nobody happened to test.
40
+ //
41
+ // So the flip is MEASURED, never assumed: cell 0 paints `uv().y` as grey, and
42
+ // the readback is asked which way that ramp runs. Not future-proofing: the
43
+ // preview stage takes the WebGPU-first default and lands on whichever backend
44
+ // the host browser has (packages/shot/src/stageBackend.ts), so both row
45
+ // orders are live today.
46
+ //
47
+ // ── NOT A `useFrame` ─────────────────────────────────────────────────────
48
+ //
49
+ // Work happens inside a request and nowhere else, so playback and scrubbing
50
+ // cost this file exactly nothing. The renderer's viewport is saved and restored
51
+ // around the batch, and the scissor test is turned back off, because a batch
52
+ // that walked away with a 96px viewport would draw the next frame into one
53
+ // corner of the canvas.
54
+ import { Suspense, useEffect, useLayoutEffect, useMemo, useRef } from "react";
55
+ import type { ReactElement } from "react";
56
+ import { useLoader, useThree } from "@react-three/fiber";
57
+ import { Color, FrontSide, Mesh, OrthographicCamera, RenderTarget, Scene, Vector4 } from "three";
58
+ import type { Material } from "three";
59
+ import { MeshBasicNodeMaterial } from "three/webgpu";
60
+ import { uv, vec3, vec4 } from "three/tsl";
61
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
62
+ import {
63
+ compileMaterialGraphNodePreview,
64
+ createDesignPlaneGeometry,
65
+ materialGraphSourceOf,
66
+ primitiveMaterialGraphSource,
67
+ useShotTextures,
68
+ } from "@dailies-studio/shot";
69
+ import type {
70
+ CompiledMaterialGraphPreview,
71
+ MaterialGraphFrame,
72
+ MaterialGraphSourceChannels,
73
+ } from "@dailies-studio/shot";
74
+ import {
75
+ DEFAULT_PRIMITIVE_COLOR,
76
+ isGraphMaterial,
77
+ materialBindingForSlot,
78
+ materialGraphNodeSockets,
79
+ materialGraphSemanticSignature,
80
+ } from "@dailies-studio/shot/doc";
81
+ import type {
82
+ Doc3D,
83
+ Layer3D,
84
+ MaterialGraph3D,
85
+ MaterialGraphBinding,
86
+ } from "@dailies-studio/shot/doc";
87
+ import { useStageAssetResolver } from "@dailies-studio/runtime";
88
+ import type {
89
+ MaterialPreviewTarget,
90
+ NodePreviewBatch,
91
+ NodePreviewRequest,
92
+ NodePreviewResult,
93
+ } from "../bridgeTypes.js";
94
+ import type { StageClient } from "./bridgeClient.js";
95
+
96
+ /**
97
+ * The renderer surface this file uses, as a STRUCTURAL type.
98
+ *
99
+ * Named rather than imported so the list of calls a node preview makes is
100
+ * readable in one place, and so a test can hand in a plain object without
101
+ * standing up a GL context. `useThree` types `gl` as the WebGL renderer while
102
+ * the shot stage runs the node renderer, so a cast happens either way.
103
+ */
104
+ interface PreviewRenderer {
105
+ render(scene: Scene, camera: OrthographicCamera): void;
106
+ setRenderTarget(target: RenderTarget | null): void;
107
+ setViewport(x: number, y: number, width: number, height: number): void;
108
+ setScissor(x: number, y: number, width: number, height: number): void;
109
+ setScissorTest(enabled: boolean): void;
110
+ getViewport(target: Vector4): Vector4;
111
+ readRenderTargetPixelsAsync(
112
+ target: RenderTarget,
113
+ x: number,
114
+ y: number,
115
+ width: number,
116
+ height: number,
117
+ ): Promise<ArrayBufferView>;
118
+ }
119
+
120
+ /** Everything a batch reads, sampled when the batch actually starts rather
121
+ * than when it was requested — a parked request must draw the CURRENT
122
+ * document, not the one that was on screen when the chrome asked. */
123
+ interface PreviewContext {
124
+ doc: Doc3D;
125
+ layer: Layer3D | null;
126
+ /** The surface channels the graph's `source-*` nodes read, or null when the
127
+ * layer, the model or the slot could not be resolved. */
128
+ source: MaterialGraphSourceChannels | null;
129
+ textures: ReturnType<typeof useShotTextures>;
130
+ gl: PreviewRenderer;
131
+ /** Master frame, or def-local under a component context — whichever the
132
+ * stage's clock is on. Time-varying nodes read it. */
133
+ frame: number;
134
+ }
135
+
136
+ export function NodePreviews({
137
+ client,
138
+ doc,
139
+ layers,
140
+ target,
141
+ }: {
142
+ client: StageClient;
143
+ doc: Doc3D;
144
+ /** The container the stage is drawing: the open definition under a component
145
+ * context, the playhead's scene otherwise. The same value `StageTools` gets,
146
+ * so the two can never disagree about which layer an id names. */
147
+ layers: { layersById: Readonly<Record<string, Layer3D>> } | null;
148
+ target: MaterialPreviewTarget;
149
+ }): ReactElement {
150
+ const layer = layers?.layersById[target.layerId] ?? null;
151
+ const mesh = layer?.kind === "mesh" ? layer.mesh : undefined;
152
+ const resolveAsset = useStageAssetResolver();
153
+ const assetPath = mesh?.asset === undefined ? undefined : doc.assets[mesh.asset]?.path;
154
+ const url = assetPath === undefined ? null : resolveAsset(assetPath);
155
+ // A primitive has no source `Material` object to capture, so its channels are
156
+ // synthesised from the one colour it carries — exactly what `mesh.tsx` does
157
+ // for the specimen, so the thumbnail's `source-color` and the specimen's are
158
+ // the same value.
159
+ const primitiveSource = useMemo(
160
+ () =>
161
+ mesh === undefined || mesh.asset !== undefined
162
+ ? null
163
+ : primitiveMaterialGraphSource(
164
+ new Color().setStyle(mesh.color ?? DEFAULT_PRIMITIVE_COLOR),
165
+ FrontSide,
166
+ ),
167
+ [mesh],
168
+ );
169
+
170
+ if (url !== null) {
171
+ return (
172
+ <Suspense fallback={null}>
173
+ <ModelPreviewSource
174
+ client={client}
175
+ doc={doc}
176
+ layer={layer}
177
+ url={url}
178
+ slot={target.slot}
179
+ />
180
+ </Suspense>
181
+ );
182
+ }
183
+ return <PreviewRig client={client} doc={doc} layer={layer} source={primitiveSource} />;
184
+ }
185
+
186
+ /**
187
+ * A model layer's source channels, taken off the loaded glTF.
188
+ *
189
+ * `useLoader` answers from fiber's GLOBAL url cache, which the specimen mesh
190
+ * has already filled — so this suspends for one commit at most on a model that
191
+ * is already on screen, and never re-downloads. While it is suspended nothing
192
+ * registers a preview source and the bridge answers null, which is the same
193
+ * "not ready yet" the chrome already handles.
194
+ */
195
+ function ModelPreviewSource({
196
+ client,
197
+ doc,
198
+ layer,
199
+ url,
200
+ slot,
201
+ }: {
202
+ client: StageClient;
203
+ doc: Doc3D;
204
+ layer: Layer3D | null;
205
+ url: string;
206
+ slot: number | null;
207
+ }): ReactElement {
208
+ const gltf = useLoader(GLTFLoader, url);
209
+ const source = useMemo(() => modelSlotSource(gltf, slot), [gltf, slot]);
210
+ return <PreviewRig client={client} doc={doc} layer={layer} source={source} />;
211
+ }
212
+
213
+ /**
214
+ * The glTF material a slot names, as channels.
215
+ *
216
+ * `parser.associations` is the LOADER's own mapping and the only authority on
217
+ * which source material is index N — a glTF material's name is optional and not
218
+ * unique, so a name match would be the implicit binding this schema refused.
219
+ * The layer-wide binding (`slot: null`) takes the lowest index, because a
220
+ * whole-model preview has no one submesh to speak for.
221
+ */
222
+ function modelSlotSource(gltf: unknown, slot: number | null): MaterialGraphSourceChannels | null {
223
+ const associations = (
224
+ gltf as { parser?: { associations?: Map<object, { materials?: number }> } }
225
+ ).parser?.associations;
226
+ if (associations === undefined) return null;
227
+ let lowest: { index: number; material: Material } | null = null;
228
+ for (const [key, link] of associations) {
229
+ const index = link?.materials;
230
+ // Meshes and nodes ride the same map with their own keys, so "has a
231
+ // material index" is what identifies a material entry.
232
+ if (typeof index !== "number") continue;
233
+ const material = key as Material;
234
+ if (slot !== null) {
235
+ if (index === slot) return materialGraphSourceOf(material);
236
+ continue;
237
+ }
238
+ if (lowest === null || index < lowest.index) lowest = { index, material };
239
+ }
240
+ return lowest === null ? null : materialGraphSourceOf(lowest.material);
241
+ }
242
+
243
+ /** The registration and the atlas. Renders nothing: everything it owns lives in
244
+ * three objects the React tree has no opinion about. */
245
+ function PreviewRig({
246
+ client,
247
+ doc,
248
+ layer,
249
+ source,
250
+ }: {
251
+ client: StageClient;
252
+ doc: Doc3D;
253
+ layer: Layer3D | null;
254
+ source: MaterialGraphSourceChannels | null;
255
+ }): null {
256
+ const gl = useThree((state) => state.gl) as unknown as PreviewRenderer;
257
+ const textures = useShotTextures();
258
+
259
+ // Read through a REF so the registration below stays a mount-only effect.
260
+ // Re-registering on every commit would swap the source out from under a batch
261
+ // that is already awaiting its readback, and the stage commits on every seek.
262
+ const live = useRef<Omit<PreviewContext, "frame">>({ doc, layer, source, textures, gl });
263
+ live.current = { doc, layer, source, textures, gl };
264
+
265
+ const rig = useMemo(createNodePreviewRig, []);
266
+ useEffect(() => () => rig.dispose(), [rig]);
267
+
268
+ useLayoutEffect(() => {
269
+ // THE CLOCK, the playhead's one live answer: the stage's React state
270
+ // holds a frame only for an export's capture loop.
271
+ const context = (): PreviewContext => ({
272
+ ...live.current,
273
+ frame: client.clock.get().frame,
274
+ });
275
+ client.setNodePreviewSource((request) => rig.run(request, context));
276
+ return () => client.setNodePreviewSource(null);
277
+ }, [client, rig]);
278
+
279
+ return null;
280
+ }
281
+
282
+ interface NodePreviewRig {
283
+ run(
284
+ request: NodePreviewRequest,
285
+ context: () => PreviewContext,
286
+ ): Promise<NodePreviewBatch | null>;
287
+ dispose(): void;
288
+ }
289
+
290
+ /** One compiled node head, or the sentence saying why there is none. */
291
+ type CellPlan =
292
+ | { nodeId: string; rig: CompiledMaterialGraphPreview; message?: undefined }
293
+ | { nodeId: string; rig?: undefined; message: string };
294
+
295
+ function createNodePreviewRig(): NodePreviewRig {
296
+ const scene = new Scene();
297
+ // The unit quad exactly fills this frustum, so a cell's picture IS the whole
298
+ // UV domain with no border and no letterboxing to reason about.
299
+ const camera = new OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0, 1);
300
+ const geometry = createDesignPlaneGeometry(1, 1);
301
+ const quad = new Mesh(geometry);
302
+ // Off the near plane. At z = 0 the quad sits exactly ON it, which some drivers
303
+ // clip and some do not — a black thumbnail on one machine and not the next.
304
+ quad.position.z = -0.5;
305
+ quad.frustumCulled = false;
306
+ scene.add(quad);
307
+
308
+ // The orientation probe: grey ramping with uv.y, which the design-space quad
309
+ // puts at 0 along the TOP edge. See the header.
310
+ const probe = new MeshBasicNodeMaterial();
311
+ probe.colorNode = vec4(vec3(uv().y), 1);
312
+ probe.toneMapped = false;
313
+ probe.depthTest = false;
314
+ probe.depthWrite = false;
315
+
316
+ const atlas = new RenderTarget(1, 1, { depthBuffer: false });
317
+ const savedViewport = new Vector4();
318
+
319
+ /** Compiled heads, keyed by node and socket, valid for ONE semantic
320
+ * signature. A literal drag or an override change moves a uniform, so it
321
+ * answers from here; a topology change mints a new signature and every head
322
+ * is disposed. */
323
+ const rigs = new Map<string, CompiledMaterialGraphPreview>();
324
+ let rigsFor: string | null = null;
325
+
326
+ /** Latest wins. At most one request is parked, and a batch whose successor
327
+ * already exists resolves null rather than painting an answer the chrome is
328
+ * about to replace. */
329
+ let running = false;
330
+ let parked: {
331
+ request: NodePreviewRequest;
332
+ context: () => PreviewContext;
333
+ resolve: (batch: NodePreviewBatch | null) => void;
334
+ } | null = null;
335
+
336
+ function disposeRigs(): void {
337
+ for (const head of rigs.values()) head.dispose();
338
+ rigs.clear();
339
+ rigsFor = null;
340
+ }
341
+
342
+ function key(nodeId: string, socket: string): string {
343
+ return `${nodeId}::${socket}`;
344
+ }
345
+
346
+ function headFor(
347
+ graph: MaterialGraph3D,
348
+ binding: MaterialGraphBinding,
349
+ source: MaterialGraphSourceChannels,
350
+ lighting: Doc3D["materialLighting"],
351
+ nodeId: string,
352
+ ): CellPlan {
353
+ const node = graph.nodesById[nodeId];
354
+ if (node === undefined || typeof node.kind !== "string") {
355
+ return { nodeId, message: `Node "${nodeId}" is not in this material graph.` };
356
+ }
357
+ if (node.scope !== undefined) {
358
+ return {
359
+ nodeId,
360
+ message: `Node "${nodeId}" sits inside a graph function, and a function body has no call-site arguments to bind, so it has no value to preview on its own.`,
361
+ };
362
+ }
363
+ const socket = materialGraphNodeSockets(graph, node).outputs[0];
364
+ if (socket === undefined) {
365
+ return { nodeId, message: `Node "${nodeId}" has no output socket to preview.` };
366
+ }
367
+ const cached = rigs.get(key(nodeId, socket.id));
368
+ if (cached !== undefined) return { nodeId, rig: cached };
369
+ try {
370
+ const head = compileMaterialGraphNodePreview({
371
+ graph,
372
+ binding,
373
+ source,
374
+ // No IBL: the preview head is an unlit `MeshBasicNodeMaterial` that
375
+ // paints the socket's value straight out, so an environment map would
376
+ // be built and bound for a material that cannot read it.
377
+ environment: null,
378
+ lighting,
379
+ nodeId,
380
+ socket: socket.id,
381
+ });
382
+ rigs.set(key(nodeId, socket.id), head);
383
+ return { nodeId, rig: head };
384
+ } catch (error) {
385
+ return { nodeId, message: messageOf(error) };
386
+ }
387
+ }
388
+
389
+ async function draw(
390
+ request: NodePreviewRequest,
391
+ context: PreviewContext,
392
+ ): Promise<NodePreviewBatch | null> {
393
+ const graph = context.doc.materials?.[request.materialId];
394
+ if (graph === undefined) return null;
395
+ const signature = materialGraphSemanticSignature(graph);
396
+ if (signature !== rigsFor) {
397
+ disposeRigs();
398
+ rigsFor = signature;
399
+ }
400
+
401
+ const size = request.size;
402
+ const refuse = (message: string): NodePreviewBatch => ({
403
+ materialId: request.materialId,
404
+ semanticSignature: signature,
405
+ size,
406
+ results: request.nodeIds.map((nodeId) => ({ nodeId, bitmap: null, message })),
407
+ });
408
+ const textures = context.textures;
409
+ if (textures === null) {
410
+ return refuse("The shot runtime's texture cache is not mounted, so this graph cannot be sampled.");
411
+ }
412
+ const source = context.source;
413
+ if (source === null) {
414
+ return refuse(
415
+ request.slot === null
416
+ ? "This layer has no source material, so its node previews have no channels to sample."
417
+ : `This model has no material slot ${request.slot}, so its node previews have no channels to sample.`,
418
+ );
419
+ }
420
+
421
+ const binding = bindingFor(context.layer, request);
422
+ const plans = request.nodeIds.map((nodeId) =>
423
+ headFor(graph, binding, source, context.doc.materialLighting, nodeId),
424
+ );
425
+ // Nothing compiled: every answer is already a sentence, so the GPU is never
426
+ // touched and the batch costs no stall at all.
427
+ if (plans.every((plan) => plan.rig === undefined)) {
428
+ return {
429
+ materialId: request.materialId,
430
+ semanticSignature: signature,
431
+ size,
432
+ results: plans.flatMap((plan) =>
433
+ plan.rig === undefined ? [{ nodeId: plan.nodeId, bitmap: null, message: plan.message }] : [],
434
+ ),
435
+ };
436
+ }
437
+
438
+ // Cell 0 is the probe, so the grid is sized for one more picture than the
439
+ // request asks for. Square-ish, because a single row of 65 cells at 256px
440
+ // is 16640 texels wide and past every device's texture limit.
441
+ const cells = plans.length + 1;
442
+ const cols = Math.ceil(Math.sqrt(cells));
443
+ const rows = Math.ceil(cells / cols);
444
+ const width = cols * size;
445
+ const height = rows * size;
446
+ atlas.setSize(width, height);
447
+
448
+ const frame: MaterialGraphFrame = {
449
+ graph,
450
+ binding,
451
+ sceneTimeSeconds: (context.frame * context.doc.fps.den) / context.doc.fps.num,
452
+ // Neutral, and deliberately: a thumbnail shows the NODE's value, so a
453
+ // layer's fade would make two identical branches read differently on two
454
+ // layers.
455
+ opacity: 1,
456
+ shading: context.layer?.kind === "mesh" ? context.layer.mesh.shading : undefined,
457
+ lighting: context.doc.materialLighting,
458
+ // A PRIMITIVE's base colour is a live uniform (its `source-color` node
459
+ // reads this every write), so a colour drag repaints the thumbnails
460
+ // without recompiling. A model's source colours are frozen at load.
461
+ ...(context.layer?.kind === "mesh" && context.layer.mesh.asset === undefined
462
+ ? { sourceColor: source.color }
463
+ : {}),
464
+ textures,
465
+ };
466
+
467
+ const gl = context.gl;
468
+ gl.getViewport(savedViewport);
469
+ gl.setRenderTarget(atlas);
470
+ gl.setScissorTest(true);
471
+ // Visual row-major with row 0 at the TOP; three normalises `setViewport` to
472
+ // a bottom-left origin on both backends, so the y flip is here and only
473
+ // here. Where each cell lands in the READBACK is decided after the probe
474
+ // has been read.
475
+ const drawCell = (index: number, material: Material): void => {
476
+ const x = (index % cols) * size;
477
+ const y = height - (Math.floor(index / cols) + 1) * size;
478
+ gl.setViewport(x, y, size, size);
479
+ gl.setScissor(x, y, size, size);
480
+ quad.material = material;
481
+ gl.render(scene, camera);
482
+ };
483
+ drawCell(0, probe);
484
+ plans.forEach((plan, i) => {
485
+ if (plan.rig === undefined) return;
486
+ plan.rig.write(frame);
487
+ drawCell(i + 1, plan.rig.material);
488
+ });
489
+ gl.setScissorTest(false);
490
+ // The presenter sets a viewport per pane every frame, but only while it is
491
+ // mounted — a camera-only layout leaves whatever was set last standing, so
492
+ // a batch that walked away with a 96px viewport would draw the next frame
493
+ // into one corner of the canvas.
494
+ gl.setViewport(savedViewport.x, savedViewport.y, savedViewport.z, savedViewport.w);
495
+ // LAST, so nothing between here and the next frame can draw into the atlas.
496
+ gl.setRenderTarget(null);
497
+
498
+ const data = await gl.readRenderTargetPixelsAsync(atlas, 0, 0, width, height);
499
+ const bytes = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);
500
+ const image = new ImageData(bytes, width, height);
501
+ const flipY = readsBottomUp(bytes, width, height, size);
502
+
503
+ const results: NodePreviewResult[] = [];
504
+ for (const [i, plan] of plans.entries()) {
505
+ if (plan.rig === undefined) {
506
+ results.push({ nodeId: plan.nodeId, bitmap: null, message: plan.message });
507
+ continue;
508
+ }
509
+ const cell = i + 1;
510
+ const sx = (cell % cols) * size;
511
+ const row = Math.floor(cell / cols);
512
+ const sy = flipY ? height - (row + 1) * size : row * size;
513
+ results.push({
514
+ nodeId: plan.nodeId,
515
+ bitmap: await createImageBitmap(image, sx, sy, size, size, {
516
+ imageOrientation: flipY ? "flipY" : "none",
517
+ // The atlas is opaque (every head writes alpha 1), so premultiplying
518
+ // would be a no-op that costs a copy.
519
+ premultiplyAlpha: "none",
520
+ }),
521
+ });
522
+ }
523
+ return { materialId: request.materialId, semanticSignature: signature, size, results };
524
+ }
525
+
526
+ async function drain(
527
+ request: NodePreviewRequest,
528
+ context: () => PreviewContext,
529
+ resolve: (batch: NodePreviewBatch | null) => void,
530
+ ): Promise<void> {
531
+ let batch: NodePreviewBatch | null = null;
532
+ try {
533
+ batch = await draw(request, context());
534
+ } catch (error) {
535
+ // A batch that throws is a diagnostic, never a stage failure: the author
536
+ // is mid-edit and the thumbnails are the least important thing on screen.
537
+ console.warn("[shot] a node preview batch failed", error);
538
+ }
539
+ const next = parked;
540
+ parked = null;
541
+ if (next === null) {
542
+ running = false;
543
+ resolve(batch);
544
+ return;
545
+ }
546
+ // Superseded while it was drawing, so this answer is already stale. Close
547
+ // the bitmaps rather than hand back pictures nobody will paint.
548
+ closeBatch(batch);
549
+ resolve(null);
550
+ void drain(next.request, next.context, next.resolve);
551
+ }
552
+
553
+ return {
554
+ run(request, context) {
555
+ return new Promise<NodePreviewBatch | null>((resolve) => {
556
+ if (running) {
557
+ parked?.resolve(null);
558
+ parked = { request, context, resolve };
559
+ return;
560
+ }
561
+ running = true;
562
+ void drain(request, context, resolve);
563
+ });
564
+ },
565
+ dispose() {
566
+ parked?.resolve(null);
567
+ parked = null;
568
+ disposeRigs();
569
+ atlas.dispose();
570
+ geometry.dispose();
571
+ probe.dispose();
572
+ },
573
+ };
574
+ }
575
+
576
+ /**
577
+ * Which way the readback runs, measured off cell 0's ramp.
578
+ *
579
+ * The probe is painted into the atlas's visual TOP-LEFT cell, so it lands in
580
+ * buffer rows [0, size) when the backend hands rows back top-down, and in
581
+ * [height - size, height) when it hands them back bottom-up. Both candidate
582
+ * slots are sampled and the one holding the steeper dark-to-light ramp is the
583
+ * probe. Ties fall to top-down, which is the WebGPU answer.
584
+ *
585
+ * ponytail: a node whose own thumbnail happens to be a perfect inverted ramp in
586
+ * the opposite candidate slot could out-vote the probe. Fixing that needs a
587
+ * second probe cell reserved at the grid's other end; the failure mode is one
588
+ * upside-down batch, so it is not worth the layout it would cost.
589
+ */
590
+ function readsBottomUp(
591
+ bytes: Uint8ClampedArray,
592
+ width: number,
593
+ height: number,
594
+ size: number,
595
+ ): boolean {
596
+ const x = Math.floor(size / 2);
597
+ const red = (row: number): number => bytes[(row * width + x) * 4] ?? 0;
598
+ const topDown = red(size - 1) - red(0);
599
+ const bottomUp = red(height - size) - red(height - 1);
600
+ return bottomUp > topDown;
601
+ }
602
+
603
+ /**
604
+ * The binding the request's slot renders through.
605
+ *
606
+ * Falls back to a bare binding on the requested graph whenever the layer no
607
+ * longer names it — a request can outlive a rebind by one round trip, and
608
+ * overrides are keyed by the parameter ids of the graph they were authored
609
+ * against, so carrying the old ones over would write a stale value into an
610
+ * unrelated uniform.
611
+ */
612
+ function bindingFor(layer: Layer3D | null, request: NodePreviewRequest): MaterialGraphBinding {
613
+ const material = layer?.kind === "mesh" ? layer.mesh.material : undefined;
614
+ if (!isGraphMaterial(material)) return { kind: "graph", materialId: request.materialId };
615
+ const bound = materialBindingForSlot(material, request.slot);
616
+ return bound.materialId === request.materialId
617
+ ? bound
618
+ : { kind: "graph", materialId: request.materialId };
619
+ }
620
+
621
+ function closeBatch(batch: NodePreviewBatch | null): void {
622
+ if (batch === null) return;
623
+ for (const result of batch.results) result.bitmap?.close();
624
+ }
625
+
626
+ function messageOf(error: unknown): string {
627
+ return error instanceof Error ? error.message : String(error);
628
+ }