@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,276 @@
1
+ // The GPU's own time per drawn frame, off three's timestamp queries.
2
+ //
3
+ // WHAT THE NUMBER COVERS IS THREE'S, AND IT IS NARROW. Each render pass is
4
+ // bracketed by a timestamp pair, so a frame's time is the sum of its passes.
5
+ // Texture uploads, mip generation and copies run outside every pass and are
6
+ // not in it, and Chrome quantizes WebGPU timestamps to about 0.07-0.1 ms. That
7
+ // is the right signal for a resolution decision (pixels cost pass time) and
8
+ // the wrong one for a stall an upload or a compile causes.
9
+ //
10
+ // r185 reads `backend.trackTimestamp` per pass (`initTimestampQuery` on both
11
+ // backends), so it can be switched on after the renderer was made. A backend
12
+ // without the feature must never have it on: WebGPU would create a timestamp
13
+ // query set the device cannot validate. The pool holds 2048 queries (1024
14
+ // passes) and only a resolve empties it, so the timer resolves after every
15
+ // draw, or with the next draw while one resolve is still out.
16
+ //
17
+ // EVERY FRAME OF A RESOLVE IS A SAMPLE, AND THE TIMER EMPTIES THREE'S MAP. A
18
+ // resolve answers with the LAST frame it saw, but on WebGPU the frames drawn
19
+ // while the one before it was out are in the same batch. three keeps each
20
+ // pass's ms in the pool's `timestamps`, keyed by a uid that ends in
21
+ // `:f<frame id>`, so the timer sums them per frame. three never deletes from
22
+ // that map and every frame adds new uids, so the timer clears it after each
23
+ // resolve: nothing in the stage reads `getTimestamp`, which is the map's only
24
+ // other reader. WebGL LOSES those frames instead: its pool reads only the
25
+ // queries that had ended when the resolve began, then forgets every query
26
+ // offset, the ones frames drew while it was out included (r185
27
+ // WebGLTimestampQueryPool `resolveQueriesAsync`). There a sample is one frame
28
+ // in two or three, which is why the controller closes a window by time as well
29
+ // as by count (renderScaleController.ts).
30
+ //
31
+ // A SCALED FRAME'S PASSES UP TO THE CANVAS ARE KEPT APART. The copy of P and
32
+ // the present cover the canvas whatever the playback scale, so each frame's
33
+ // sum comes with the share each took, by the uid and kind the presentation
34
+ // child recorded (`takePresentationPass`, packages/shot/src/presentationScale.ts):
35
+ // the present is the output pass a frame at 1 draws too, and the copy one only
36
+ // a scaled frame draws, which the controller prices apart.
37
+ //
38
+ // A frame here is three's rAF tick (`info.frame`), not a stage draw. PerfProbe
39
+ // resets three's Info at the start of every stage draw, so two draws inside
40
+ // one tick (a resize redraw) restart the render-call count in the uid, and
41
+ // three keeps only the second draw's queries for that tick.
42
+ //
43
+ // Known r185 leftover, harmless: WebGPU caches the canvas pass descriptor, so
44
+ // after `stop` it keeps writing its last timestamp pair into the idle query
45
+ // set until three rebuilds it. No pixel depends on it.
46
+ //
47
+ // ── IN PRODUCTION, WHILE THE TRANSPORT PLAYS ─────────────────────────────
48
+ //
49
+ // The playback render scale decides on this number, beside the presented
50
+ // frames' deadlines (renderScaleController.ts: a step down needs both), so
51
+ // `GpuTimer` below is mounted in the editor's non-export branch whenever
52
+ // the stage plays, perf or not (StageApp): the transport, or a preparing
53
+ // pass, whose frames the controller drops. Pause unmounts it. Its cost per
54
+ // drawn frame is a timestamp pair per pass and one resolve: a query-set
55
+ // resolve, a buffer copy and a map, and never a second resolve while one is
56
+ // out. The perf HUD keeps it on while paused too, for its own reading.
57
+ import { useEffect, useRef } from "react";
58
+ import { useFrame, useThree } from "@react-three/fiber";
59
+ import { takePresentationPass } from "@dailies-studio/shot";
60
+ import type { PresentationPass } from "@dailies-studio/shot";
61
+ import { percentile } from "./stagePerformanceTelemetry.js";
62
+
63
+ interface TimestampRenderer {
64
+ backend: {
65
+ isWebGPUBackend?: boolean;
66
+ isWebGLBackend?: boolean;
67
+ /** WebGL: EXT_disjoint_timer_query_webgl2 was found. WebGPU answers true
68
+ * always, so it is no feature test there. */
69
+ hasTimestamp?: boolean;
70
+ hasFeature?: (name: string) => boolean;
71
+ trackTimestamp?: boolean;
72
+ timestampQueryPool?: {
73
+ render?: {
74
+ currentQueryIndex?: number;
75
+ /** Each resolved pass's ms, by uid (`r:<call>:<context>:f<frame id>`). */
76
+ timestamps?: Map<string, number>;
77
+ } | null;
78
+ };
79
+ };
80
+ resolveTimestampsAsync(type: "render"): Promise<number | undefined>;
81
+ }
82
+
83
+ export type GpuTimerKind = "webgpu" | "webgl";
84
+
85
+ export interface GpuTimerHandle {
86
+ readonly kind: GpuTimerKind;
87
+ /** After every draw: resolve the passes it timed. */
88
+ afterDraw(): void;
89
+ /** Drain once, then stop timing passes. */
90
+ stop(): void;
91
+ }
92
+
93
+ /** Which timer this renderer can run, or null when it has none. */
94
+ export function gpuTimerKind(renderer: unknown): GpuTimerKind | null {
95
+ const gl = renderer as Partial<TimestampRenderer> | null | undefined;
96
+ const backend = gl?.backend;
97
+ if (backend === undefined || typeof gl?.resolveTimestampsAsync !== "function") return null;
98
+ if (backend.isWebGPUBackend === true) {
99
+ return backend.hasFeature?.("timestamp-query") === true ? "webgpu" : null;
100
+ }
101
+ if (backend.isWebGLBackend === true) return backend.hasTimestamp === true ? "webgl" : null;
102
+ return null;
103
+ }
104
+
105
+ /** One resolved frame's ms, and the ms its playback-scale copy of P and its
106
+ * present took (0 each for an unscaled frame). */
107
+ export type GpuFrameSink = (ms: number, copyMs: number, presentMs: number) => void;
108
+
109
+ /** Turn the renderer's timestamps on and hand each resolved frame to
110
+ * `onFrame`, oldest first. Null, and nothing switched on, without a timer. */
111
+ export function startGpuTimer(renderer: unknown, onFrame: GpuFrameSink): GpuTimerHandle | null {
112
+ const kind = gpuTimerKind(renderer);
113
+ if (kind === null) return null;
114
+ const gl = renderer as TimestampRenderer;
115
+ const backend = gl.backend;
116
+ backend.trackTimestamp = true;
117
+ let resolving = false;
118
+ let stopped = false;
119
+ const pool = () => backend.timestampQueryPool?.render ?? null;
120
+ // Asked before every resolve: with no pass written since the last one, a
121
+ // resolve has nothing to read.
122
+ const pending = (): boolean => (pool()?.currentQueryIndex ?? 0) > 0;
123
+ const resolve = (): void => {
124
+ resolving = true;
125
+ void gl.resolveTimestampsAsync("render").then(
126
+ () => {
127
+ resolving = false;
128
+ const timestamps = pool()?.timestamps;
129
+ if (timestamps === undefined) return;
130
+ const frames = framesOf(timestamps, (uid) => takePresentationPass(gl, uid));
131
+ timestamps.clear();
132
+ if (stopped) return;
133
+ for (const frame of frames) onFrame(frame.ms, frame.copyMs, frame.presentMs);
134
+ },
135
+ () => {
136
+ resolving = false;
137
+ pool()?.timestamps?.clear();
138
+ },
139
+ );
140
+ };
141
+ return {
142
+ kind,
143
+ afterDraw() {
144
+ if (!stopped && !resolving && pending()) resolve();
145
+ },
146
+ stop() {
147
+ if (stopped) return;
148
+ stopped = true;
149
+ // The resolve reads `trackTimestamp` as it is called, so it goes first.
150
+ if (!resolving && pending()) resolve();
151
+ backend.trackTimestamp = false;
152
+ },
153
+ };
154
+ }
155
+
156
+ /** The stage child that runs the timer on the canvas's renderer: `onTimer`
157
+ * once at mount with the timer it found (null when the renderer has none),
158
+ * then `onFrame` with each resolved frame's ms, its copy's and its
159
+ * present's. Stopped, after one drain, on unmount. Both are read at call
160
+ * time, so a new callback restarts nothing. */
161
+ export function GpuTimer({
162
+ onFrame,
163
+ onTimer,
164
+ }: {
165
+ onFrame: GpuFrameSink;
166
+ onTimer: (kind: GpuTimerKind | null) => void;
167
+ }): null {
168
+ const gl = useThree((s) => s.gl);
169
+ const timer = useRef<GpuTimerHandle | null>(null);
170
+ const sinks = useRef({ onFrame, onTimer });
171
+ sinks.current = { onFrame, onTimer };
172
+ useEffect(() => {
173
+ const started = startGpuTimer(gl, (ms, copyMs, presentMs) => sinks.current.onFrame(ms, copyMs, presentMs));
174
+ timer.current = started;
175
+ sinks.current.onTimer(started?.kind ?? null);
176
+ return () => {
177
+ started?.stop();
178
+ timer.current = null;
179
+ };
180
+ }, [gl]);
181
+ // The scheduler's finish phase, like PerfProbe's: after every render the
182
+ // frame made, the presenters' included. fiber stands its own draw down only
183
+ // for render-phase jobs, so this one can never leave a pane blank.
184
+ useFrame(() => {
185
+ timer.current?.afterDraw();
186
+ }, { phase: "finish" });
187
+ return null;
188
+ }
189
+
190
+ /** One resolve's passes summed per frame, in the order three drew them, each
191
+ * with the shares its copy and present passes took (`passOf`). A frame whose
192
+ * sum is not a time is dropped. */
193
+ function framesOf(
194
+ timestamps: ReadonlyMap<string, number>,
195
+ passOf: (uid: string) => PresentationPass | null,
196
+ ): { ms: number; copyMs: number; presentMs: number }[] {
197
+ const frames = new Map<string, { ms: number; copyMs: number; presentMs: number }>();
198
+ for (const [uid, ms] of timestamps) {
199
+ const at = uid.lastIndexOf(":f");
200
+ const key = at < 0 ? uid : uid.slice(at + 2);
201
+ let frame = frames.get(key);
202
+ if (frame === undefined) {
203
+ frame = { ms: 0, copyMs: 0, presentMs: 0 };
204
+ frames.set(key, frame);
205
+ }
206
+ frame.ms += ms;
207
+ const pass = passOf(uid);
208
+ if (pass === "copy") frame.copyMs += ms;
209
+ else if (pass === "present") frame.presentMs += ms;
210
+ }
211
+ return [...frames.values()].filter((frame) => Number.isFinite(frame.ms) && frame.ms >= 0);
212
+ }
213
+
214
+ /** GPU time per frame over the samples a reader asked for. Every value is
215
+ * null when there are none. */
216
+ export interface GpuStats {
217
+ samples: number;
218
+ p50Ms: number | null;
219
+ p95Ms: number | null;
220
+ maxMs: number | null;
221
+ /** Samples the ring overwrote since it was made, inside the range or not. */
222
+ lost: number;
223
+ }
224
+
225
+ export const NO_GPU_STATS: Readonly<GpuStats> = Object.freeze({
226
+ samples: 0,
227
+ p50Ms: null,
228
+ p95Ms: null,
229
+ maxMs: null,
230
+ lost: 0,
231
+ });
232
+
233
+ /** Over nine minutes of frames at 60 a second. */
234
+ const GPU_SAMPLE_CAPACITY = 1 << 15;
235
+
236
+ /**
237
+ * The frames the timer resolved, newest last, at most `capacity` of them: what
238
+ * the HUD and `__perfProbe.gpu()` read. Each is timed when its resolve landed
239
+ * (the stage's `performance.now()`), a frame or two after its draw.
240
+ */
241
+ export class GpuSamples {
242
+ readonly #at: Float64Array;
243
+ readonly #ms: Float64Array;
244
+ #count = 0;
245
+
246
+ constructor(capacity = GPU_SAMPLE_CAPACITY) {
247
+ this.#at = new Float64Array(capacity);
248
+ this.#ms = new Float64Array(capacity);
249
+ }
250
+
251
+ push(at: number, ms: number): void {
252
+ const slot = this.#count % this.#at.length;
253
+ this.#at[slot] = at;
254
+ this.#ms[slot] = ms;
255
+ this.#count += 1;
256
+ }
257
+
258
+ /** The kept samples that landed in `[fromMs, toMs)`. */
259
+ stats(fromMs = Number.NEGATIVE_INFINITY, toMs = Number.POSITIVE_INFINITY): GpuStats {
260
+ const capacity = this.#at.length;
261
+ const lost = Math.max(0, this.#count - capacity);
262
+ const values: number[] = [];
263
+ for (let index = lost; index < this.#count; index += 1) {
264
+ const slot = index % capacity;
265
+ const at = this.#at[slot]!;
266
+ if (at >= fromMs && at < toMs) values.push(this.#ms[slot]!);
267
+ }
268
+ return {
269
+ samples: values.length,
270
+ p50Ms: percentile(values, 0.5),
271
+ p95Ms: percentile(values, 0.95),
272
+ maxMs: percentile(values, 1),
273
+ lost,
274
+ };
275
+ }
276
+ }
@@ -21,19 +21,33 @@
21
21
  // and needs no network at all.
22
22
  //
23
23
  // FAILURE IS NEVER FATAL. Every load is caught: a component that will not
24
- // resolve is simply absent from the map and its layer draws nothing, with the
25
- // reason travelling up the stage's existing error channel. A stage that threw
26
- // here would blank the whole editor over one broken import.
24
+ // resolve is absent from the map, its id is in `failed`, and the stage
25
+ // marks each placed layer of it as a failed piece (a dashed placeholder on the
26
+ // frame, the reason in the inspector). A stage that threw
27
+ // here would blank the whole editor over one broken import. The reason is the
28
+ // import's own error message: hosted, a module that failed to compile or
29
+ // imports what the stage cannot serve throws one naming the file and the
30
+ // specifier, and that is the line an author needs.
31
+ //
32
+ // THE MODULE EPOCH (moduleEpoch.ts). Hosted, each compile of the user's code
33
+ // is a new epoch, and the registry URL and every project-relative controls URL
34
+ // carry it as `?v=<epoch>`. Locally there is never an epoch, and every URL is
35
+ // byte-for-byte what it always was. Both caches are scoped to the epoch, so a
36
+ // bump re-imports (a failed import included) and an unchanged epoch keeps
37
+ // every promise, hence every module identity.
27
38
  import {
39
+ CODE_PREVIEW_DEF_ID,
40
+ CODE_PREVIEW_LAYER_ID,
28
41
  controlsModuleOf,
29
42
  instanceComponentIdsOf,
30
43
  instanceContractBindings,
31
44
  instanceModuleOf,
32
45
  isBareModuleSpecifier,
33
46
  } from "@dailies-studio/shot/doc";
34
- import type { Doc3D } from "@dailies-studio/shot/doc";
47
+ import type { Doc3D, PieceFailure } from "@dailies-studio/shot/doc";
35
48
  import type { Contract } from "@dailies-studio/contract";
36
49
  import type { ComponentType } from "react";
50
+ import type { ModuleEpoch } from "./moduleEpoch.js";
37
51
 
38
52
  /** The `virtual:studio-components` row shape (cli registry.ts). */
39
53
  interface RegistryEntry {
@@ -51,15 +65,23 @@ export interface InstanceResolution {
51
65
  /** One line per component that could not be resolved. Empty when everything
52
66
  * loaded (or the document has no instances). */
53
67
  errors: string[];
68
+ /** Each component whose module did not load, with its line from `errors`.
69
+ * Its placed layers draw a failed placeholder on the frame instead of
70
+ * nothing. */
71
+ failed: Record<string, string>;
54
72
  }
55
73
 
56
- const EMPTY: InstanceResolution = { instances: {}, contracts: {}, errors: [] };
74
+ const EMPTY: InstanceResolution = { instances: {}, contracts: {}, errors: [], failed: {} };
75
+
76
+ /** A component load's answer: the component, or why there is none (null when
77
+ * the registry simply does not know the id). */
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ type ComponentLoad = { component: ComponentType<any> } | { reason: string | null };
57
80
 
58
81
  /** Module-level caches: the stage re-resolves on every document identity change
59
82
  * (an inspector keystroke), and a dynamic import is idempotent but a REJECTED
60
83
  * one is not cheap to retry in a loop. */
61
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
- const componentCache = new Map<string, Promise<ComponentType<any> | null>>();
84
+ const componentCache = new Map<string, Promise<ComponentLoad>>();
63
85
  const contractCache = new Map<string, Promise<Contract | null>>();
64
86
 
65
87
  /** The registry's URL, not its module ID. `virtual:studio-components` is
@@ -68,63 +90,147 @@ const contractCache = new Map<string, Promise<Contract | null>>();
68
90
  * a const so Vite's import analysis cannot see a literal and try. */
69
91
  const COMPONENT_REGISTRY_URL = "/@id/virtual:studio-components";
70
92
 
71
- async function registry(): Promise<Record<string, RegistryEntry>> {
72
- const mod = (await import(/* @vite-ignore */ COMPONENT_REGISTRY_URL)) as {
93
+ /** The dynamic import every module load goes through. */
94
+ export type ImportModule = (url: string) => Promise<unknown>;
95
+ const dynamicImport: ImportModule = (url) => import(/* @vite-ignore */ url);
96
+
97
+ /** `url` under `epoch`, or `url` untouched when there is none. */
98
+ function versioned(url: string, epoch: ModuleEpoch): string {
99
+ return epoch === null ? url : `${url}?v=${epoch}`;
100
+ }
101
+
102
+ /** A cache key scoped to one epoch. Keys hold no newline, so no two collide. */
103
+ function scoped(epoch: ModuleEpoch, key: string): string {
104
+ return epoch === null ? key : `${epoch}\n${key}`;
105
+ }
106
+
107
+ async function registry(
108
+ epoch: ModuleEpoch,
109
+ importModule: ImportModule,
110
+ ): Promise<Record<string, RegistryEntry>> {
111
+ const mod = (await importModule(versioned(COMPONENT_REGISTRY_URL, epoch))) as {
73
112
  components?: Record<string, RegistryEntry>;
74
113
  };
75
114
  return mod.components ?? {};
76
115
  }
77
116
 
78
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
- function loadComponent(id: string): Promise<ComponentType<any> | null> {
80
- const hit = componentCache.get(id);
117
+ function loadComponent(id: string, epoch: ModuleEpoch, importModule: ImportModule): Promise<ComponentLoad> {
118
+ const key = scoped(epoch, id);
119
+ const hit = componentCache.get(key);
81
120
  if (hit !== undefined) return hit;
82
- const p = (async () => {
83
- const entry = (await registry())[id];
84
- if (entry === undefined) return null;
121
+ const p = (async (): Promise<ComponentLoad> => {
122
+ const entry = (await registry(epoch, importModule))[id];
123
+ if (entry === undefined) return { reason: null };
85
124
  const value = await entry.load();
86
125
  // A vanished export resolves to undefined; treat it as "not resolvable"
87
126
  // rather than rendering `undefined` into the tree.
88
127
  return typeof value === "function" || (typeof value === "object" && value !== null)
89
128
  ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
- (value as ComponentType<any>)
129
+ { component: value as ComponentType<any> }
130
+ : { reason: null };
131
+ })().catch((e: unknown): ComponentLoad => ({ reason: e instanceof Error ? `${e.name}: ${e.message}` : String(e) }));
132
+ componentCache.set(key, p);
133
+ return p;
134
+ }
135
+
136
+ /**
137
+ * The registry's own contract for one component, over HTTP.
138
+ *
139
+ * FOR THE UNPLACED ONLY. Every other path here resolves a contract from the
140
+ * DOCUMENT: a declared one through the controls module the layer's
141
+ * `EmbedContractRef` names, an inferred one through the snapshot the document
142
+ * froze at bind time. The code studio renders a component the document does
143
+ * not place, so it has neither, and without a contract `buildProps` falls
144
+ * through to the raw prop map — which for a preview is empty, so the component
145
+ * would mount with no props at all rather than at its declared defaults.
146
+ *
147
+ * `/api/components/:id` is the one answer that covers both contract sources,
148
+ * and it is the same route the chrome's own `loadContract` reads, so the two
149
+ * sides cannot end up holding different contracts for one id. Same-origin GET:
150
+ * the stage is served by this server, so the bridge pairing gate never applies.
151
+ */
152
+ function loadRegistryContract(id: string, epoch: ModuleEpoch): Promise<Contract | null> {
153
+ const key = scoped(epoch, `registry:${id}`);
154
+ const hit = contractCache.get(key);
155
+ if (hit !== undefined) return hit;
156
+ const p = (async () => {
157
+ const res = await fetch(`/api/components/${encodeURIComponent(id)}`);
158
+ if (!res.ok) return null;
159
+ const detail = (await res.json()) as { contract?: unknown };
160
+ const contract = detail.contract;
161
+ return contract !== null && typeof contract === "object" && "keys" in contract
162
+ ? (contract as Contract)
91
163
  : null;
92
164
  })().catch(() => null);
93
- componentCache.set(id, p);
165
+ contractCache.set(key, p);
94
166
  return p;
95
167
  }
96
168
 
97
- function loadDeclaredContract(specifier: string): Promise<Contract | null> {
98
- const hit = contractCache.get(specifier);
169
+ function loadDeclaredContract(
170
+ specifier: string,
171
+ epoch: ModuleEpoch,
172
+ importModule: ImportModule,
173
+ ): Promise<Contract | null> {
174
+ const key = scoped(epoch, specifier);
175
+ const hit = contractCache.get(key);
99
176
  if (hit !== undefined) return hit;
100
- const url = isBareModuleSpecifier(specifier) ? specifier : `/${specifier}`;
177
+ // A bare specifier is a package the import map (or Vite) resolves, never a
178
+ // project file, so it stays bare.
179
+ const url = isBareModuleSpecifier(specifier) ? specifier : versioned(`/${specifier}`, epoch);
101
180
  const p = (async () => {
102
- const mod = (await import(/* @vite-ignore */ url)) as { controls?: unknown };
181
+ const mod = (await importModule(url)) as { controls?: unknown };
103
182
  const controls = mod.controls;
104
183
  return controls !== null && typeof controls === "object" && "keys" in controls
105
184
  ? (controls as Contract)
106
185
  : null;
107
186
  })().catch(() => null);
108
- contractCache.set(specifier, p);
187
+ contractCache.set(key, p);
109
188
  return p;
110
189
  }
111
190
 
112
- /** Resolve every instance target in `doc`. Pure I/O — no React, no state — so
113
- * the caller owns when it runs and what it does with a partial answer. */
114
- export async function resolveInstanceModules(doc: Doc3D): Promise<InstanceResolution> {
115
- const ids = instanceComponentIdsOf(doc);
191
+ export interface ResolveOptions {
192
+ /** The stage's module epoch. Absent or null: today's URLs exactly. */
193
+ readonly epoch?: ModuleEpoch;
194
+ /** Tests pass a recorder here; the stage never does. */
195
+ readonly importModule?: ImportModule;
196
+ }
197
+
198
+ /**
199
+ * Resolve every instance target in `doc`. Pure I/O — no React, no state — so
200
+ * the caller owns when it runs and what it does with a partial answer.
201
+ *
202
+ * `extraIds` are components to resolve that the DOCUMENT does not place. The
203
+ * code studio is the one caller: it renders a component standalone through a
204
+ * synthetic definition that lives outside the document, so the id set the
205
+ * document walks would never include it and the preview would sit empty
206
+ * forever. Their contracts come from the registry route rather than from a
207
+ * layer's binding, because a component with no placement has no binding.
208
+ */
209
+ export async function resolveInstanceModules(
210
+ doc: Doc3D,
211
+ extraIds: readonly string[] = [],
212
+ options: ResolveOptions = {},
213
+ ): Promise<InstanceResolution> {
214
+ const ids = [...new Set([...instanceComponentIdsOf(doc), ...extraIds])].sort();
116
215
  if (ids.length === 0) return EMPTY;
216
+ const epoch = options.epoch ?? null;
217
+ const importModule = options.importModule ?? dynamicImport;
117
218
 
118
219
  const instances: InstanceModules = {};
119
220
  const errors: string[] = [];
221
+ const failed: Record<string, string> = {};
120
222
  await Promise.all(
121
223
  ids.map(async (id) => {
122
- const comp = await loadComponent(id);
123
- if (comp === null) {
124
- errors.push(`instance "${id}" could not be loaded from the project — its layer is empty.`);
224
+ const loaded = await loadComponent(id, epoch, importModule);
225
+ if ("reason" in loaded) {
226
+ const line =
227
+ `instance "${id}" could not be loaded from the project` +
228
+ (loaded.reason === null ? "." : `: ${loaded.reason}`);
229
+ failed[id] = line;
230
+ errors.push(line);
125
231
  return;
126
232
  }
127
- instances[id] = comp;
233
+ instances[id] = loaded.component;
128
234
  }),
129
235
  );
130
236
 
@@ -136,11 +242,11 @@ export async function resolveInstanceModules(doc: Doc3D): Promise<InstanceResolu
136
242
  instanceContractBindings(doc).map(async ({ component, ref }) => {
137
243
  if (ref.source === "snapshot") return;
138
244
  const specifier = controlsModuleOf(instanceModuleOf(component), ref.source);
139
- const contract = await loadDeclaredContract(specifier);
245
+ const contract = await loadDeclaredContract(specifier, epoch, importModule);
140
246
  if (contract === null) {
141
247
  errors.push(
142
248
  `instance "${component}" declares its contract in ${JSON.stringify(specifier)}, which ` +
143
- `did not load — its props render unmapped here.`,
249
+ `did not load, so its props render unmapped here.`,
144
250
  );
145
251
  return;
146
252
  }
@@ -148,5 +254,60 @@ export async function resolveInstanceModules(doc: Doc3D): Promise<InstanceResolu
148
254
  }),
149
255
  );
150
256
 
151
- return { instances, contracts, errors };
257
+ // The unplaced ones, after the document's own — so a component that is BOTH
258
+ // open in the code studio and placed in the shot keeps the binding the
259
+ // document authored, and the registry answer never shadows it.
260
+ await Promise.all(
261
+ extraIds
262
+ .filter((id) => contracts[id] === undefined)
263
+ .map(async (id) => {
264
+ const contract = await loadRegistryContract(id, epoch);
265
+ // Not an error: a component with no contract at all is legal, and it
266
+ // renders with the props it was given. Only a component the registry
267
+ // does not know is worth saying anything about, and `loadComponent`
268
+ // above has already said it.
269
+ if (contract !== null) contracts[id] = contract;
270
+ }),
271
+ );
272
+
273
+ return { instances, contracts, errors, failed };
274
+ }
275
+
276
+ /**
277
+ * One isolated failure per placed layer whose component did not load, in the
278
+ * runtime's own `PieceFailure` shape, so it rides the same channel a render
279
+ * crash does: the dashed placeholder on the frame, the failure bar, the
280
+ * chrome's badge and inspector row, and the export gate. `codeId` is the code
281
+ * studio's subject, whose one layer lives in no document.
282
+ */
283
+ export function instanceLoadFailures(
284
+ doc: Doc3D | null,
285
+ failed: Readonly<Record<string, string>>,
286
+ codeId: string | null,
287
+ ): PieceFailure[] {
288
+ const out: PieceFailure[] = [];
289
+ const failure = (sceneId: string, layerId: string, message: string): PieceFailure => ({
290
+ kind: "instance",
291
+ sceneId,
292
+ layerId,
293
+ message,
294
+ stack: "",
295
+ firstFrame: 0,
296
+ rev: null,
297
+ });
298
+ if (doc !== null) {
299
+ const homes = [
300
+ ...doc.scenes.map((id) => [id, doc.scenesById[id]] as const),
301
+ ...Object.entries(doc.components ?? {}),
302
+ ];
303
+ for (const [homeId, home] of homes) {
304
+ for (const layer of Object.values(home?.layersById ?? {})) {
305
+ const message = layer.kind === "instance" ? failed[layer.instance.component] : undefined;
306
+ if (message !== undefined) out.push(failure(homeId, layer.id, message));
307
+ }
308
+ }
309
+ }
310
+ const codeMessage = codeId === null ? undefined : failed[codeId];
311
+ if (codeMessage !== undefined) out.push(failure(CODE_PREVIEW_DEF_ID, CODE_PREVIEW_LAYER_ID, codeMessage));
312
+ return out;
152
313
  }
@@ -1,20 +1,20 @@
1
1
  // Turning a pointer into a DESIGN-SPACE delta, which is the one thing the
2
2
  // chrome's document math cannot do for itself.
3
3
  //
4
- // ── THE V-vs-G RULE, WHICH IS THE WHOLE FILE ────────────────────────────
4
+ // ── THE PLAIN-vs-ALT RULE, WHICH IS THE WHOLE FILE ──────────────────────
5
5
  //
6
6
  // The 2D lane could say "screen delta ÷ zoom" because everything was coplanar.
7
7
  // Here each layer sits at its OWN depth under a perspective camera, so a screen
8
8
  // delta maps to a different world delta per layer — and there is no single
9
9
  // correct answer, because two different gestures are being asked for:
10
10
  //
11
- // SELECT (V) unprojects onto the LAYER'S OWN tilted plane. Dragging a tilted
11
+ // A PLAIN DRAG unprojects onto the LAYER'S OWN tilted plane. Dragging a tilted
12
12
  // layer slides it along its own surface, which is what "move this thing"
13
13
  // means once the thing is a plane in space.
14
14
  //
15
- // MOVE (G) unprojects onto the plane through the layer's origin PARALLEL to
16
- // the export frame. Flat screen space: z never changes and tilt is ignored,
17
- // which is what "move it on the canvas" means.
15
+ // AN ALT-DRAG unprojects onto the plane through the layer's origin PARALLEL
16
+ // to the export frame. Flat screen space: z never changes and tilt is
17
+ // ignored, which is what "move it on the canvas" means.
18
18
  //
19
19
  // Two `Ray.intersectPlane` calls. One plane constructor different. That is the
20
20
  // entire distinction, and stating it as two planes rather than as two code
@@ -44,6 +44,25 @@ export function gesturePlane(kind: MovePlane, matrixWorld: { elements: ArrayLike
44
44
  return new Plane().setFromNormalAndCoplanarPoint(normal, origin);
45
45
  }
46
46
 
47
+ /**
48
+ * A plane through a WORLD point with a WORLD normal.
49
+ *
50
+ * `gesturePlane`'s sibling for the two things that have no layer matrix to read
51
+ * a basis out of: the transform gizmo's PLANE PADS (whose normal is a design
52
+ * axis) and its VIEW-PLANE DISC (whose normal is the working camera's own
53
+ * forward). Both then feed the same `designDelta` a body drag does, so there is
54
+ * one unprojection in this lane and not three.
55
+ */
56
+ export function planeAt(
57
+ point: readonly [number, number, number],
58
+ normal: readonly [number, number, number],
59
+ ): Plane {
60
+ return new Plane().setFromNormalAndCoplanarPoint(
61
+ new Vector3(normal[0], normal[1], normal[2]).normalize(),
62
+ new Vector3(point[0], point[1], point[2]),
63
+ );
64
+ }
65
+
47
66
  /** Where a ray meets a plane, or null when it is parallel to it (an edge-on
48
67
  * layer) — which is a real case and must not become a NaN delta. */
49
68
  export function unproject(ray: Ray, plane: Plane): Vector3 | null {