@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
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Exists because every performance judgement about this lane used to be made
4
4
  // by reading code: there was no way to SEE where a frame went. The HUD prints
5
- // the five numbers that decide whether the stage is healthy, sampled on a
5
+ // the six numbers that decide whether the stage is healthy, sampled on a
6
6
  // half-second interval so the HUD itself costs nothing worth measuring:
7
7
  //
8
8
  // gl/s GL frames drawn per second. Under `frameloop="never"` a draw IS
@@ -11,26 +11,147 @@
11
11
  // ui ms EMA of the gap between rAF ticks — main-thread health. 16.7 is
12
12
  // a clean 60Hz; spikes mean something synchronous is eating the
13
13
  // thread (a raster storm, a full-dock reconcile).
14
- // draws draw calls in the last GL frame, off `gl.info` (see PerfProbe
15
- // for the drawCalls-vs-calls renderer-class split).
14
+ // gpu the GPU's own time per frame, p50/p95 over the frames resolved
15
+ // since the last sample, off three's timestamp queries
16
+ // (`GpuTimer`, gpuTimer.ts); `n/a` when the renderer has no
17
+ // timer, `-` when nothing drew.
18
+ // scale the playback render scale (renderScaleController.ts): the share
19
+ // of the display's density a played frame renders at, the mode,
20
+ // a step waiting for its cut, the last judged window's GPU p75
21
+ // against its frame period and its frames that came late, and
22
+ // why it last moved.
23
+ // draws draw calls in the last frame, every render of it summed, off
24
+ // `gl.info` (see PerfProbe for the reset and the
25
+ // drawCalls-vs-calls renderer-class split).
16
26
  // raster the module LRU: live MB (refcounted entries included — the
17
27
  // honest live set), entry count, and hits/misses over the last
18
28
  // interval. Misses during steady playback are a regression; see
19
29
  // rasterCache.test.ts.
20
30
  // layers how many layers the open document's current scene holds.
21
31
  //
22
- // Two halves, same file: `PerfProbe` runs INSIDE the canvas (priority 2, after
23
- // the presenters, so its info read sees the finished frame) and writes into a
24
- // mutable box; `PerfHud` is DOM, polling the box and `rasterCacheStats()` on
25
- // an interval. The box is the same feed pattern the picking lane uses — the
26
- // probe must never cause a render, or the HUD would bend the number it shows.
32
+ // Two halves, same file: `PerfProbe` runs INSIDE the canvas (the scheduler's
33
+ // finish phase, after the presenters, so its info read sees the finished
34
+ // frame) and writes into a mutable box, which StageApp's `GpuTimer` feeds
35
+ // too; `PerfHud` is DOM, polling the box and `rasterCacheStats()` on an
36
+ // interval. The box is the same feed pattern the picking lane uses: the probe
37
+ // must never cause a render, or the HUD would bend the number it shows.
27
38
  import { useEffect, useRef, useState } from "react";
28
39
  import { useFrame, useThree } from "@react-three/fiber";
29
- import { rasterCacheStats } from "@dailies-studio/shot";
40
+ import {
41
+ canvasUploadStats,
42
+ enableShotPerf,
43
+ layerMaterialPoolStats,
44
+ planeCacheStats,
45
+ rasterCacheStats,
46
+ rendererCompileBusy,
47
+ shotPerfStats,
48
+ } from "@dailies-studio/shot";
49
+ import type { PresentationQuality } from "./adaptivePresentation.js";
50
+ import {
51
+ createJsSelfProfilerController,
52
+ isLocalJsSelfProfileOptIn,
53
+ } from "./jsSelfProfiler.js";
54
+ import type { JsSelfProfilerController } from "./jsSelfProfiler.js";
55
+ import type { StageInset } from "../bridgeTypes.js";
56
+ import { GpuSamples, NO_GPU_STATS } from "./gpuTimer.js";
57
+ import type { GpuStats, GpuTimerKind } from "./gpuTimer.js";
58
+ import type { RenderScaleController, RenderScaleReading } from "./renderScaleController.js";
59
+ import { stagePerformanceInterval, stagePerformanceLine } from "./stagePerformanceTelemetry.js";
60
+ import type {
61
+ StagePerformanceDebugSnapshot,
62
+ StagePresentationSample,
63
+ StagePerformanceTelemetry,
64
+ } from "./stagePerformanceTelemetry.js";
30
65
 
31
66
  /** Read once: the stage URL is set at iframe mount and never rewritten. */
32
67
  export const PERF_HUD_ENABLED =
33
- typeof window !== "undefined" && new URLSearchParams(window.location.search).get("perf") === "1";
68
+ typeof window !== "undefined" && new URLSearchParams(window.location.search).get("perf") !== null;
69
+
70
+ /** `?perf=log` as well as the HUD: echo every sample to the console.
71
+ *
72
+ * OPT-IN SEPARATELY, because it is two lines a second forever and the console
73
+ * is also where three's own deprecation warnings and any real error land. The
74
+ * overlay and `window.__perf` cover reading a number; this covers watching one
75
+ * move over time, which is the rarer want. */
76
+ export const PERF_LOG_ENABLED =
77
+ typeof window !== "undefined" && new URLSearchParams(window.location.search).get("perf") === "log";
78
+
79
+ /** The stack sampler is stricter than the counter HUD: exact query value and
80
+ * a loopback document. A hosted page carrying a copied query remains inert. */
81
+ export const PERF_SELF_PROFILE_ENABLED =
82
+ typeof window !== "undefined" && isLocalJsSelfProfileOptIn(window.location.href);
83
+
84
+ // The runtime's own counters cost a `performance.now()` per draw, so they stay
85
+ // off until something asks. This is the only caller, and asking once at module
86
+ // evaluation means the very first frames are measured too — a compile storm on
87
+ // load is exactly the kind of thing that would hide behind a later switch-on.
88
+ if (PERF_HUD_ENABLED && !PERF_SELF_PROFILE_ENABLED) enableShotPerf();
89
+
90
+ /**
91
+ * The counters themselves, not the line: `window.__perfProbe`.
92
+ *
93
+ * `window.__perf` is a STRING sampled on an interval, and a hidden tab clamps
94
+ * that interval to about a second — useless for asking what one frame cost.
95
+ * The obvious answer, importing `@dailies-studio/shot` from the console and
96
+ * calling `shotPerfStats()`, does not work: Vite hands an `?t=` -stamped URL
97
+ * to the module graph and a console import of the bare path gets a SECOND
98
+ * instance, whose counters are all zero while the real ones move. That failure
99
+ * is silent and reads exactly like "the runtime is not drawing".
100
+ *
101
+ * So the HUD, which is already inside the stage and already holds the live
102
+ * bindings, publishes them. Behind the same flag as everything else here, so a
103
+ * shipped stage has no such property.
104
+ */
105
+ /** The live renderer, parked by `PerfProbe` on its first frame.
106
+ *
107
+ * Module scope rather than a field on the box: `PerfProbe` is the only thing
108
+ * in the stage that already holds `gl`, and every question worth asking of it
109
+ * is a one-off from a console. Publishing the OBJECT rather than a chosen
110
+ * statistic is deliberate — the last three diagnostics each needed a different
111
+ * number off it, and each one cost a source edit and a reload to get. */
112
+ let probeRenderer: unknown = null;
113
+
114
+ interface WindowPerfProbe {
115
+ shot: typeof shotPerfStats;
116
+ raster: typeof rasterCacheStats;
117
+ planes: typeof planeCacheStats;
118
+ mats: typeof layerMaterialPoolStats;
119
+ uploads: typeof canvasUploadStats;
120
+ renderer: () => unknown;
121
+ /** A compile is queued or running on the live renderer
122
+ * (`rendererCompileBusy`): what a proof that steps frames one at a time
123
+ * waits out, so a pipeline made in a draw means nothing warmed it, whatever
124
+ * the host's speed. */
125
+ compileBusy: () => boolean;
126
+ stage?: (afterSequence?: number) => StagePerformanceDebugSnapshot | null;
127
+ stageSequence?: () => number | null;
128
+ subscribeStage?: (
129
+ listener: (sample: StagePresentationSample) => void,
130
+ ) => (() => void) | null;
131
+ /** GPU time per frame over the frames whose resolve landed in
132
+ * `[fromMs, toMs)` (this document's `performance.now()`, every frame kept
133
+ * when absent), and which timer measured it (`pending` before the renderer
134
+ * was asked, `n/a` without one). */
135
+ gpu?: (fromMs?: number, toMs?: number) => GpuStats & { timer: PerfBox["gpuTimer"] };
136
+ profile?: JsSelfProfilerController;
137
+ }
138
+
139
+ const windowPerfProbe: WindowPerfProbe | null = PERF_HUD_ENABLED
140
+ ? {
141
+ shot: shotPerfStats,
142
+ raster: rasterCacheStats,
143
+ planes: planeCacheStats,
144
+ mats: layerMaterialPoolStats,
145
+ uploads: canvasUploadStats,
146
+ renderer: () => probeRenderer,
147
+ compileBusy: () => probeRenderer !== null && typeof probeRenderer === "object" && rendererCompileBusy(probeRenderer),
148
+ ...(PERF_SELF_PROFILE_ENABLED ? { profile: createJsSelfProfilerController() } : {}),
149
+ }
150
+ : null;
151
+
152
+ if (windowPerfProbe !== null) {
153
+ (window as unknown as { __perfProbe?: WindowPerfProbe }).__perfProbe = windowPerfProbe;
154
+ }
34
155
 
35
156
  export interface PerfBox {
36
157
  glFrames: number;
@@ -42,21 +163,53 @@ export interface PerfBox {
42
163
  /**
43
164
  * Triangles the renderer submitted, off `info.render.triangles`.
44
165
  *
45
- * LAST BIN ONLY when a scene has more than one. `renderBins` calls
46
- * `gl.render` once per bin and the info resets per render, so this reports
47
- * the final pass rather than the frame — exactly the caveat `drawCalls`
48
- * beside it already carries, and for exactly the same reason. The outliner's
49
- * per-layer badges are the number to trust for authoring decisions; this one
50
- * is for watching a pass get heavier while you drag.
166
+ * EVERY RENDER OF THE LAST FRAME, like `drawCalls` beside it: each bin, each
167
+ * post pass and both split panes, summed (PerfProbe owns the reset). The
168
+ * outliner's per-layer badges are the number to trust for authoring
169
+ * decisions; this one is for watching a frame get heavier while you drag.
51
170
  */
52
171
  triangles: number;
172
+ /**
173
+ * Textures and geometries the renderer is HOLDING, off `info.memory`.
174
+ *
175
+ * The counterpart to `programs`, and the number that finally made a
176
+ * progressive slowdown visible. Every cache this lane owns reports itself
177
+ * (raster MB/entries, planes, mats), so when a document got slower on each
178
+ * successive pass while all of them stayed flat, there was nothing left to
179
+ * look at. These two are what an INSTANCE component's own textures and
180
+ * geometries land in, and nothing else on this line can see those.
181
+ */
182
+ textures: number;
183
+ geometries: number;
53
184
  /** Wall-time spans from a `markPerf()` call to the NEXT presented frame,
54
185
  * keyed by the mark's name. The probe settles them; the HUD prints them. */
55
186
  spans: Record<string, number>;
187
+ /** What `GpuTimer` found: `pending` until it has asked the renderer, then
188
+ * the backend it times, or `n/a` when the renderer cannot time a pass. */
189
+ gpuTimer: GpuTimerKind | "pending" | "n/a";
190
+ /** The frames `GpuTimer` resolved (`perfGpuFrame`), made at the first, so
191
+ * a stage without the HUD never allocates it. */
192
+ gpu: GpuSamples | null;
193
+ }
194
+
195
+ /** One frame `GpuTimer` resolved, into the box's ring: what the HUD's `gpu`
196
+ * reading and `__perfProbe.gpu()` read. Nothing without the HUD. */
197
+ export function perfGpuFrame(box: PerfBox, ms: number): void {
198
+ if (PERF_HUD_ENABLED) (box.gpu ??= new GpuSamples()).push(performance.now(), ms);
56
199
  }
57
200
 
58
201
  export function createPerfBox(): PerfBox {
59
- return { glFrames: 0, drawCalls: 0, programs: 0, triangles: 0, spans: {} };
202
+ return {
203
+ glFrames: 0,
204
+ drawCalls: 0,
205
+ programs: 0,
206
+ triangles: 0,
207
+ textures: 0,
208
+ geometries: 0,
209
+ spans: {},
210
+ gpuTimer: "pending",
211
+ gpu: null,
212
+ };
60
213
  }
61
214
 
62
215
  /**
@@ -72,38 +225,57 @@ const pendingMarks = new Map<string, number>();
72
225
  /** Record "X happened; measure to the next presented frame". No-op unless the
73
226
  * HUD is on, so callers do not need their own gate. */
74
227
  export function markPerf(name: string): void {
75
- if (!PERF_HUD_ENABLED) return;
228
+ if (!PERF_HUD_ENABLED || PERF_SELF_PROFILE_ENABLED) return;
76
229
  pendingMarks.set(name, performance.now());
77
230
  }
78
231
 
79
232
  /** In-canvas half: counts GL frames and copies the renderer's draw-call count.
80
233
  *
81
- * THE PRIORITY IS CONDITIONAL AND THAT IS LOAD-BEARING. Any useFrame with
82
- * priority > 0 makes fiber skip its own render (`!state.internal.priority`
83
- * gates the default draw), on the theory that a positive-priority subscriber
84
- * IS the renderer. In the camera-only layout nothing else takes over the
85
- * present, so a probe at priority 2 there would be the only "renderer" on
86
- * the canvas — and it draws nothing: a blank stage, only under ?perf=1, only
87
- * in the default layout. So: priority 2 only when a presenter is mounted
88
- * (SplitRenderer or ScenePresenter, both priority 1) so the probe reads the
89
- * FINISHED frame; priority 0 otherwise, where `drawCalls` is one frame stale
90
- * — fine for a 500ms-sampled HUD, and gl/s stays exact. */
91
- export function PerfProbe({ box, presenterMounted }: { box: PerfBox; presenterMounted: boolean }): null {
234
+ * THE PHASE IS LOAD-BEARING. The probe subscribes in the scheduler's
235
+ * `finish` phase, which runs after the `render` phase, so it reads the
236
+ * FINISHED frame in every layout. fiber only stands its own draw down for
237
+ * jobs in the `render` phase (`hasUserJobsInPhase`) or a legacy positive
238
+ * priority, so a finish-phase probe can never leave the camera-only layout
239
+ * blank the way a positive-priority one could in v9. This replaces the v9
240
+ * conditional (priority 2 with a presenter mounted, else a one-frame-stale
241
+ * priority 0): the v10 phase gives the fresh reading unconditionally.
242
+ *
243
+ * AND THE PROBE OWNS THE RESET. three's Info clears its per-frame counters on
244
+ * its own rAF tick (`autoReset`), which has nothing to do with when this
245
+ * stage draws: two draws inside one tick are read as one frame, and a
246
+ * hidden tab, which gets no ticks, never resets at all. So the probe turns
247
+ * that off and clears the counters at the START of every frame, and the
248
+ * finish phase reads every render the frame made. At the start rather than
249
+ * after the read, because a reader that runs after the frame (the
250
+ * benchmark's resource rows, from the presentation callback) must still
251
+ * find it. */
252
+ export function PerfProbe({ box }: { box: PerfBox }): null {
92
253
  const gl = useThree((s) => s.gl);
254
+ useEffect(() => {
255
+ const info = infoOf(gl);
256
+ if (PERF_SELF_PROFILE_ENABLED || info === undefined) return;
257
+ const previous = info.autoReset;
258
+ info.autoReset = false;
259
+ return () => {
260
+ info.autoReset = previous ?? true;
261
+ };
262
+ }, [gl]);
93
263
  useFrame(() => {
264
+ if (!PERF_SELF_PROFILE_ENABLED) infoOf(gl)?.reset?.();
265
+ }, { phase: "start" });
266
+ useFrame(() => {
267
+ if (PERF_HUD_ENABLED && probeRenderer === null) probeRenderer = gl;
268
+ if (PERF_SELF_PROFILE_ENABLED) return;
94
269
  box.glFrames += 1;
95
- const info = (gl as {
96
- info?: {
97
- render?: { calls?: number; drawCalls?: number; triangles?: number };
98
- memory?: { programs?: number };
99
- };
100
- }).info;
270
+ const info = infoOf(gl);
101
271
  // The common WebGPURenderer Info spells the per-frame count `drawCalls`
102
272
  // (its `calls` is a LIFETIME total); a classic WebGLRenderer has no
103
273
  // `drawCalls` and its `calls` is per-frame. Hence this fallback order —
104
274
  // the split is renderer class, not backend.
105
275
  box.drawCalls = info?.render?.drawCalls ?? info?.render?.calls ?? 0;
106
276
  box.programs = info?.memory?.programs ?? 0;
277
+ box.textures = info?.memory?.textures ?? 0;
278
+ box.geometries = info?.memory?.geometries ?? 0;
107
279
  // Spelled the same on both renderer classes, so no fallback is needed here.
108
280
  box.triangles = info?.render?.triangles ?? 0;
109
281
  // Settle pending marks: this frame is the first one PRESENTED after the
@@ -113,19 +285,97 @@ export function PerfProbe({ box, presenterMounted }: { box: PerfBox; presenterMo
113
285
  for (const [name, at] of pendingMarks) box.spans[name] = now - at;
114
286
  pendingMarks.clear();
115
287
  }
116
- }, presenterMounted ? 2 : 0);
288
+ }, { phase: "finish" });
117
289
  return null;
118
290
  }
119
291
 
292
+ /** The slice of three's renderer `Info` the probe reads and resets. */
293
+ interface RendererInfo {
294
+ autoReset?: boolean;
295
+ reset?: () => void;
296
+ render?: { calls?: number; drawCalls?: number; triangles?: number };
297
+ memory?: { programs?: number; textures?: number; geometries?: number };
298
+ }
299
+
300
+ function infoOf(gl: unknown): RendererInfo | undefined {
301
+ return (gl as { info?: RendererInfo }).info;
302
+ }
303
+
120
304
  const SAMPLE_MS = 500;
121
305
 
306
+ /** The HUD's `scale` segment. */
307
+ export function renderScaleText(reading: RenderScaleReading): string {
308
+ const next = reading.next === null ? "" : `→${reading.next.toFixed(2)}`;
309
+ const gpu = !reading.timer
310
+ ? "gpu n/a"
311
+ : reading.gpuP75Ms === null
312
+ ? "p75 -"
313
+ : `p75 ${reading.gpuP75Ms.toFixed(1)}/${reading.periodMs.toFixed(1)}ms`;
314
+ const late = reading.late === null ? "" : ` late ${reading.late.frames}/${reading.late.of}`;
315
+ const why = reading.reason === null ? "" : ` (${reading.reason})`;
316
+ return `scale ${reading.scale.toFixed(2)}${next} ${reading.mode} ${gpu}${late}${why}`;
317
+ }
318
+
122
319
  /** DOM half: the readout. `layers` is passed in because only the caller knows
123
- * which scene is current; everything else is sampled here. */
124
- export function PerfHud({ box, layers }: { box: PerfBox; layers: number }) {
320
+ * which scene is current; everything else is sampled here.
321
+ *
322
+ * `quality` and the render scale are printed, and mirrored onto data
323
+ * attributes, so a drop in either is VISIBLE while it happens: a downgrade
324
+ * nobody can see is the kind that gets blamed on the renderer. */
325
+ export function PerfHud({
326
+ box,
327
+ layers,
328
+ telemetry,
329
+ quality,
330
+ renderScale,
331
+ inset,
332
+ }: {
333
+ box: PerfBox;
334
+ layers: number;
335
+ telemetry: StagePerformanceTelemetry;
336
+ quality: PresentationQuality;
337
+ /** The playback render scale's controller, read inside the interval. */
338
+ renderScale: RenderScaleController;
339
+ /** Where the chrome covers the stage; the HUD sits just inside it. */
340
+ inset: StageInset;
341
+ }) {
125
342
  const [text, setText] = useState("perf…");
343
+ const [scale, setScale] = useState(1);
344
+ useEffect(() => {
345
+ if (windowPerfProbe === null) return;
346
+ const readStage = (afterSequence = 0): StagePerformanceDebugSnapshot | null =>
347
+ telemetry.debugSnapshot(afterSequence);
348
+ const readStageSequence = (): number | null => telemetry.debugSampleSequence();
349
+ windowPerfProbe.stage = readStage;
350
+ windowPerfProbe.stageSequence = readStageSequence;
351
+ const subscribeStage = (listener: (sample: StagePresentationSample) => void) =>
352
+ telemetry.subscribeDebugSample(listener);
353
+ // Event-shaped diagnostics are useful to pixel gates as well as to the
354
+ // sampling profiler. Publishing the function under the existing `?perf`
355
+ // boundary adds no per-frame work until a caller actually subscribes.
356
+ windowPerfProbe.subscribeStage = subscribeStage;
357
+ const readGpu = (fromMs?: number, toMs?: number): GpuStats & { timer: PerfBox["gpuTimer"] } => ({
358
+ timer: box.gpuTimer,
359
+ ...(box.gpu?.stats(fromMs, toMs) ?? NO_GPU_STATS),
360
+ });
361
+ windowPerfProbe.gpu = readGpu;
362
+ return () => {
363
+ if (windowPerfProbe.stage === readStage) delete windowPerfProbe.stage;
364
+ if (windowPerfProbe.stageSequence === readStageSequence) {
365
+ delete windowPerfProbe.stageSequence;
366
+ }
367
+ if (windowPerfProbe.subscribeStage === subscribeStage) delete windowPerfProbe.subscribeStage;
368
+ if (windowPerfProbe.gpu === readGpu) delete windowPerfProbe.gpu;
369
+ };
370
+ }, [box, telemetry]);
371
+ // A ref, not a dependency: the rung changes on its own schedule and must not
372
+ // restart the sampling interval (which would reset every delta baseline).
373
+ const qualityRef = useRef(quality);
374
+ qualityRef.current = quality;
126
375
  // rAF gap EMA, sampled continuously but folded into state only per interval.
127
376
  const uiMs = useRef(16.7);
128
377
  useEffect(() => {
378
+ if (PERF_SELF_PROFILE_ENABLED) return;
129
379
  let raf = 0;
130
380
  let last = performance.now();
131
381
  const tick = (): void => {
@@ -142,6 +392,7 @@ export function PerfHud({ box, layers }: { box: PerfBox; layers: number }) {
142
392
  // rAF-gap EMA has already recovered by sample time.
143
393
  const longTasks = useRef({ count: 0, maxMs: 0 });
144
394
  useEffect(() => {
395
+ if (PERF_SELF_PROFILE_ENABLED) return;
145
396
  if (typeof PerformanceObserver === "undefined") return;
146
397
  const observer = new PerformanceObserver((list) => {
147
398
  for (const entry of list.getEntries()) {
@@ -157,38 +408,173 @@ export function PerfHud({ box, layers }: { box: PerfBox; layers: number }) {
157
408
  return () => observer.disconnect();
158
409
  }, []);
159
410
  useEffect(() => {
411
+ if (PERF_SELF_PROFILE_ENABLED) return;
160
412
  let lastFrames = box.glFrames;
161
413
  let lastHits = rasterCacheStats().hits;
162
414
  let lastMisses = rasterCacheStats().misses;
415
+ let lastPerf = shotPerfStats();
416
+ let lastUploads = canvasUploadStats();
417
+ let lastStage = telemetry.snapshot();
163
418
  const timer = setInterval(() => {
164
419
  const stats = rasterCacheStats();
420
+ const perf = shotPerfStats();
421
+ const stage = telemetry.snapshot();
165
422
  const gps = ((box.glFrames - lastFrames) * 1000) / SAMPLE_MS;
166
423
  const hits = stats.hits - lastHits;
167
424
  const misses = stats.misses - lastMisses;
425
+ // Per-INTERVAL deltas, so the numbers describe the last half second
426
+ // rather than the session. `passes` is the double-draw detector: GL
427
+ // passes divided by the distinct playhead frames they were drawn for.
428
+ // 1.0 is one draw per frame; 2.0 means every frame is rendered twice,
429
+ // which is what a live video layer used to cost.
430
+ const drawDelta = perf.draws - lastPerf.draws;
431
+ const frameDelta = perf.frames - lastPerf.frames;
432
+ const passes = frameDelta > 0 ? drawDelta / frameDelta : 0;
433
+ // Splits a doubling into its two possible causes, which is the whole
434
+ // reason `sceneRenders` is counted: renders-per-frame above 1 means a
435
+ // second COMMIT upstream, while passes above renders means one render
436
+ // drawing twice. Printing only `passes` left the two indistinguishable,
437
+ // so every judgement about a doubling was made by reading code.
438
+ const renderDelta = perf.sceneRenders - lastPerf.sceneRenders;
439
+ const renders = frameDelta > 0 ? renderDelta / frameDelta : 0;
440
+ // Where those renders CAME FROM. `host` rising with `renders` means the
441
+ // editor pushed the commits down; `host` flat while `renders` climbs
442
+ // means state inside the canvas subtree is doing it.
443
+ const hostDelta = perf.runtimeRenders - lastPerf.runtimeRenders;
444
+ const hostRenders = frameDelta > 0 ? hostDelta / frameDelta : 0;
445
+ const rootDelta = perf.rootRenders - lastPerf.rootRenders;
446
+ const rootRenders = frameDelta > 0 ? rootDelta / frameDelta : 0;
447
+ // TRUE renderer entries per playhead frame, from the call sites rather
448
+ // than from ClockDriver advances. A plain scene is 1; a bin list, a post
449
+ // chain or a transition legitimately draw more, and this is the only
450
+ // number that says how much the GPU was actually asked to do.
451
+ const passDelta = perf.renderPasses - lastPerf.renderPasses;
452
+ const glPasses = frameDelta > 0 ? passDelta / frameDelta : 0;
453
+ const armDelta = perf.mediaArms - lastPerf.mediaArms;
454
+ const repaintDelta = perf.repaints - lastPerf.repaints;
455
+ // Which setter inside the canvas asked for the extra scene render.
456
+ const bumps: Array<[string, number]> = [
457
+ ["f", perf.bumpFonts - lastPerf.bumpFonts],
458
+ ["h", perf.bumpHtml - lastPerf.bumpHtml],
459
+ ["m", perf.bumpMedia - lastPerf.bumpMedia],
460
+ ["e", perf.bumpEnv - lastPerf.bumpEnv],
461
+ ];
462
+ const bumpText = bumps
463
+ .filter((row) => row[1] > 0)
464
+ .map((row) => `${row[0]}${row[1]}`)
465
+ .join("");
466
+ const compilePer = drawDelta > 0 ? (perf.compileMs - lastPerf.compileMs) / drawDelta : 0;
467
+ const advancePer = drawDelta > 0 ? (perf.advanceMs - lastPerf.advanceMs) / drawDelta : 0;
468
+ const watchPer = drawDelta > 0 ? (perf.watchMs - lastPerf.watchMs) / drawDelta : 0;
469
+ // The runtime subtree's whole render and commit — see `treeMs`. Printed
470
+ // beside `adv` because the pair is the split that matters: a seek is
471
+ // `tree` plus what the host component above it costs, and until this
472
+ // number existed the two were one unattributed remainder.
473
+ const treePer = drawDelta > 0 ? (perf.treeMs - lastPerf.treeMs) / drawDelta : 0;
474
+ // The frame program's two halves per draw, and how the transport's
475
+ // frames split between the clock listener (no commit) and a structure
476
+ // commit (with its whole cost per commit). `tree` above covers only the
477
+ // commits that rendered the runtime; `commit` is the one to read during
478
+ // playback.
479
+ const evalPer = drawDelta > 0 ? (perf.evaluateMs - lastPerf.evaluateMs) / drawDelta : 0;
480
+ const applyPer = drawDelta > 0 ? (perf.applyMs - lastPerf.applyMs) / drawDelta : 0;
481
+ const clockDelta = perf.clockFrames - lastPerf.clockFrames;
482
+ const commitDelta = perf.commitFrames - lastPerf.commitFrames;
483
+ const commitPer = commitDelta > 0 ? (perf.commitMs - lastPerf.commitMs) / commitDelta : 0;
484
+ const clockShare = drawDelta > 0 ? (100 * clockDelta) / drawDelta : 0;
485
+ // Request → publish → present, and everything lost between them. This
486
+ // is the signal the quality policy actually acts on, so it is worth
487
+ // seeing beside the numbers that explain WHY a window was slow.
488
+ //
489
+ // `renderPasses` is the runtime's ClockDriver advances here. The lane
490
+ // this came from counted true renderer entry points instead, which also
491
+ // sees the bins, transition and split presenters; without that counter
492
+ // `effective` reads as advances-per-presentation rather than
493
+ // passes-per-presentation.
494
+ const stageInterval = stagePerformanceInterval(lastStage, stage, drawDelta);
495
+ const stageText = stagePerformanceLine(stage, stageInterval, qualityRef.current);
496
+ // CanvasTexture upload requests: the one cost `gl.info` cannot see. Per
497
+ // PRESENTED frame when frames moved; raw interval totals otherwise,
498
+ // because uploads on a paused stage are themselves the anomaly worth
499
+ // printing (the budget for them is zero — see docs/shot.md).
500
+ const uploadsNow = canvasUploadStats();
501
+ const uploadDelta = uploadsNow.uploads - lastUploads.uploads;
502
+ const uploadMpx = uploadsNow.mpx - lastUploads.mpx;
503
+ lastUploads = uploadsNow;
504
+ const upText =
505
+ uploadDelta > 0
506
+ ? frameDelta > 0
507
+ ? ` · up ${(uploadDelta / frameDelta).toFixed(1)}/${(uploadMpx / frameDelta).toFixed(1)}MPx`
508
+ : ` · up ${uploadDelta}/${uploadMpx.toFixed(1)}MPx paused`
509
+ : "";
510
+ lastStage = stage;
168
511
  lastFrames = box.glFrames;
169
512
  lastHits = stats.hits;
170
513
  lastMisses = stats.misses;
514
+ lastPerf = perf;
171
515
  const lt = longTasks.current;
172
516
  const ltText = lt.count > 0 ? ` · lt ${lt.count}/${lt.maxMs.toFixed(0)}ms` : "";
173
517
  longTasks.current = { count: 0, maxMs: 0 };
174
518
  const spans = Object.entries(box.spans)
175
519
  .map(([name, ms]) => ` · ${name} ${ms.toFixed(0)}ms`)
176
520
  .join("");
177
- setText(
178
- `gl/s ${gps.toFixed(0)} · ui ${uiMs.current.toFixed(1)}ms · draws ${box.drawCalls} · ` +
179
- `tris ${box.triangles} · progs ${box.programs} · ` +
180
- `raster ${(stats.bytes / 1e6).toFixed(0)}MB/${stats.entries} (+${hits}h ${misses}m) · ` +
181
- `layers ${layers}${ltText}${spans}`,
182
- );
521
+ // GPU time per frame over the frames resolved since the last sample; a
522
+ // dash when nothing drew.
523
+ const sampledAt = performance.now();
524
+ const gpu = box.gpu?.stats(sampledAt - SAMPLE_MS, sampledAt) ?? NO_GPU_STATS;
525
+ const gpuText =
526
+ box.gpuTimer === "n/a"
527
+ ? "n/a"
528
+ : gpu.p50Ms === null || gpu.p95Ms === null
529
+ ? "-"
530
+ : `${gpu.p50Ms.toFixed(1)}/${gpu.p95Ms.toFixed(1)}ms`;
531
+ // The render scale, off the controller in this interval: no React state
532
+ // above the runtime moves per window.
533
+ const reading = renderScale.reading();
534
+ // Only while something is actually drawing: a paused stage draws nothing,
535
+ // and printing "passes 0.0 · compile 0.0ms" there would read as a broken
536
+ // renderer rather than an idle one.
537
+ const drawText =
538
+ drawDelta > 0
539
+ ? ` · passes ${passes.toFixed(2)} · renders ${renders.toFixed(2)} · host ${hostRenders.toFixed(2)} · root ${rootRenders.toFixed(2)} · gl ${glPasses.toFixed(2)} · arm ${armDelta}/rep ${repaintDelta}${bumpText === "" ? "" : ` · bump ${bumpText}`}${repaintDelta > 0 ? ` (${perf.lastRepaintTrigger})` : ""} · compile ${compilePer.toFixed(1)}ms · adv ${advancePer.toFixed(1)}ms · watch ${watchPer.toFixed(1)}ms · tree ${treePer.toFixed(1)}ms · eval ${evalPer.toFixed(2)}ms · apply ${applyPer.toFixed(2)}ms · clock ${clockShare.toFixed(0)}% · commit ${commitDelta}/${commitPer.toFixed(1)}ms`
540
+ : "";
541
+ const line =
542
+ `gl/s ${gps.toFixed(0)} · ui ${uiMs.current.toFixed(1)}ms · gpu ${gpuText} · draws ${box.drawCalls} · ` +
543
+ `tris ${box.triangles} · progs ${box.programs} · ` +
544
+ `raster ${(stats.bytes / 1e6).toFixed(0)}MB/${stats.entries} (+${hits}h ${misses}m) · ` +
545
+ `planes ${planeCacheStats().entries} · ` +
546
+ `mats ${layerMaterialPoolStats().parked} · ` +
547
+ `tex ${box.textures}/geo ${box.geometries}${upText} · ` +
548
+ `layers ${layers} · ${renderScaleText(reading)} · ${stageText}${drawText}${ltText}${spans}`;
549
+ setText(line);
550
+ setScale(reading.scale);
551
+ // ── AND SOMEWHERE THE DOCK CANNOT COVER ─────────────────────────────
552
+ //
553
+ // The overlay below is inside the stage IFRAME, and `.stage-frame` is
554
+ // `position: fixed; inset: 0`: the iframe fills the whole window and
555
+ // every chrome panel is docked above it. The iframe is one element in the
556
+ // chrome's stacking order, so no z-index in here can beat a docked cell:
557
+ // the HUD sits in the visible canvas's corner, but a long line still
558
+ // runs under the right column. A reading that has to be transcribed off
559
+ // a translucent overlay is also the one thing nobody does accurately.
560
+ //
561
+ // So the line also goes to `window.__perf`, which can be grabbed at any
562
+ // instant from the console, and under `?perf=log` to the console itself
563
+ // for watching a number move over a whole playback.
564
+ (window as unknown as { __perf?: string }).__perf = line;
565
+ if (PERF_LOG_ENABLED) console.log("[perf] " + line);
183
566
  }, SAMPLE_MS);
184
567
  return () => clearInterval(timer);
185
- }, [box, layers]);
568
+ }, [box, layers, telemetry, renderScale]);
569
+ if (PERF_SELF_PROFILE_ENABLED) return null;
186
570
  return (
187
571
  <div
188
572
  style={{
189
573
  position: "fixed",
190
- left: 8,
191
- bottom: 8,
574
+ // Just inside the docked cells, in the corner of the visible canvas.
575
+ // A literal once put it under the timeline, where it was unreadable.
576
+ left: inset.left + 8,
577
+ top: inset.top + 8,
192
578
  zIndex: 40,
193
579
  padding: "4px 8px",
194
580
  borderRadius: 4,
@@ -199,6 +585,10 @@ export function PerfHud({ box, layers }: { box: PerfBox; layers: number }) {
199
585
  whiteSpace: "nowrap",
200
586
  }}
201
587
  data-testid="perf-hud"
588
+ data-quality-level={quality.level}
589
+ data-quality-dpr={quality.dpr}
590
+ data-quality-raster={quality.rasterScale}
591
+ data-render-scale={scale}
202
592
  >
203
593
  {text}
204
594
  </div>