@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,717 @@
1
+ // Correlated stage frame telemetry and the one-second window sampler behind
2
+ // the editor's fps readout and the playback gate.
3
+ //
4
+ // The minimal sampler is product-always WHILE PLAYING: one timestamp when a
5
+ // frame is requested and one when the runtime confirms that revision was
6
+ // presented. Those readings used to control backing-store DPR; the ladder is
7
+ // gone (adaptivePresentation.ts), so a closed window is reported, with the
8
+ // GPU frame times that landed in it (`noteGpu`). Each presented frame of the
9
+ // transport goes to the playback render scale (`subscribePresented`), whose
10
+ // deadlines it is (renderScaleController.ts), timed by its request; its
11
+ // windows are its own, and this one's rate is a readout.
12
+ // Rich history is opt-in under `?perf=1`; when it is off, paused scrubs and
13
+ // incidental redraws perform no telemetry-only wall-clock reads or
14
+ // ring-buffer work.
15
+ import { ADAPTIVE_WINDOW_MS } from "./adaptivePresentation.js";
16
+ import type { AdaptivePresentationLevel, PresentationQuality } from "./adaptivePresentation.js";
17
+ import { canonicalJSON } from "@dailies-studio/contract";
18
+ import { DEFAULT_SHOT_FPS } from "@dailies-studio/shot/doc";
19
+ import type { Doc3D } from "@dailies-studio/shot/doc";
20
+
21
+ export interface StageFrameRequest {
22
+ requestSequence: number;
23
+ frame: number;
24
+ requestedAt: number | null;
25
+ }
26
+
27
+ interface PublishedFrame extends StageFrameRequest {
28
+ publicationToken: number;
29
+ }
30
+
31
+ export interface StagePresentedFrameLike {
32
+ revision: number;
33
+ frame: number;
34
+ /** Editor publication identity echoed by ShotRuntime. */
35
+ presentationToken?: number;
36
+ }
37
+
38
+ export interface StagePerformanceSnapshot {
39
+ requestedFrames: number;
40
+ publishedFrames: number;
41
+ presentedFrames: number;
42
+ matchedPresentations: number;
43
+ unmatchedPresentations: number;
44
+ droppedPublications: number;
45
+ requestCoalesced: number;
46
+ publicationCoalesced: number;
47
+ latencySamples: number;
48
+ latencyP50Ms: number | null;
49
+ latencyP95Ms: number | null;
50
+ latencyP99Ms: number | null;
51
+ lastWindowP95Ms: number | null;
52
+ lastWindowPresentedFrames: number;
53
+ lastWindowExpectedFrames: number;
54
+ lastWindowDroppedPublications: number;
55
+ lastPresentedRevision: number;
56
+ qualityLevel: AdaptivePresentationLevel;
57
+ }
58
+
59
+ /** One closed adaptive window: what the stage actually put on screen against
60
+ * what the document's cadence asked of it. `durationMs` is carried rather
61
+ * than assumed so a reader can turn frames into a rate without importing
62
+ * `ADAPTIVE_WINDOW_MS`. */
63
+ export interface StagePerformanceWindow {
64
+ presentedFrames: number;
65
+ expectedFrames: number;
66
+ droppedPublications: number;
67
+ durationMs: number;
68
+ /** The p95 of the GPU frame times that landed in the window (`noteGpu`),
69
+ * or null when none did. */
70
+ gpuP95Ms: number | null;
71
+ }
72
+
73
+ export interface StagePerformanceInterval {
74
+ requestedFrames: number;
75
+ publishedFrames: number;
76
+ presentedFrames: number;
77
+ matchedPresentations: number;
78
+ unmatchedPresentations: number;
79
+ droppedPublications: number;
80
+ requestCoalesced: number;
81
+ publicationCoalesced: number;
82
+ renderPasses: number;
83
+ effectivePasses: number;
84
+ }
85
+
86
+ export interface StagePresentationSample {
87
+ sequence: number;
88
+ publicationToken: number;
89
+ requestSequence: number;
90
+ frame: number;
91
+ revision: number;
92
+ requestedAt: number;
93
+ presentedAt: number;
94
+ latencyMs: number;
95
+ }
96
+
97
+ export interface StageDebugDocumentIdentity {
98
+ id: string;
99
+ name: string;
100
+ title: string | null;
101
+ size: [number, number];
102
+ fps: [number, number];
103
+ scenes: Array<[string, number, number]>;
104
+ }
105
+
106
+ export interface StageDebugDocumentPresentation {
107
+ frame: number;
108
+ revision: number;
109
+ presentedAt: number;
110
+ }
111
+
112
+ /**
113
+ * The document that crossed the stage bridge, plus proof that this exact
114
+ * object reached ShotRuntime's presentation callback.
115
+ *
116
+ * Object identity stays stage-local. Only the compact structural identity is
117
+ * published, so a diagnostics poll never serializes the authored document or
118
+ * puts its size on the measured frame path.
119
+ */
120
+ export interface StageDebugDocumentSnapshot {
121
+ identity: StageDebugDocumentIdentity;
122
+ fingerprint: string | null;
123
+ fingerprintError: string | null;
124
+ loadSequence: number;
125
+ loadedAtSampleSequence: number;
126
+ presentationCount: number;
127
+ lastPresentation: StageDebugDocumentPresentation | null;
128
+ }
129
+
130
+ const DEBUG_DOCUMENT_FINGERPRINT_PREFIX = "sha256-canonical-json-v1:";
131
+
132
+ /**
133
+ * Whole-value document fingerprint shared by the stage and browser gate.
134
+ *
135
+ * `canonicalJSON` sorts object keys and preserves array order, so equivalent
136
+ * values do not depend on insertion order while every authored array remains
137
+ * order-sensitive. SHA-256 makes accidental acceptance of different values
138
+ * computationally negligible. This is an audit identity, not an authenticity
139
+ * claim: both sides run in the same local process and no secret is involved.
140
+ */
141
+ export async function debugDocumentFingerprint(doc: Doc3D): Promise<string> {
142
+ if (globalThis.crypto?.subtle === undefined) {
143
+ throw new Error("Web Crypto SHA-256 is unavailable");
144
+ }
145
+ const bytes = new TextEncoder().encode(canonicalJSON(doc));
146
+ const digest = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", bytes));
147
+ return (
148
+ DEBUG_DOCUMENT_FINGERPRINT_PREFIX +
149
+ Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")
150
+ );
151
+ }
152
+
153
+ /** Read-only opt-in query used by the browser benchmark. */
154
+ export interface StagePerformanceDebugSnapshot {
155
+ counters: StagePerformanceSnapshot;
156
+ samples: readonly StagePresentationSample[];
157
+ latestSampleSequence: number;
158
+ /** First row retained. A cursor is stale when `cursor + 1` is below it. */
159
+ oldestSampleSequence: number;
160
+ /** Present only in rich diagnostics, and only while a document is loaded. */
161
+ document: StageDebugDocumentSnapshot | null;
162
+ }
163
+
164
+ export interface StagePerformanceTelemetryOptions {
165
+ rich?: boolean;
166
+ now?: () => number;
167
+ richLatencyCapacity?: number;
168
+ /** Whether the stage is on screen at all. A hidden tab has its rAF throttled
169
+ * to seconds apart, so the transport requests almost nothing and the window
170
+ * reads as a scene in deep trouble — which it is not. Defaults to the
171
+ * document's own visibility; a test passes its own. */
172
+ visible?: () => boolean;
173
+ }
174
+
175
+ const DEFAULT_RICH_LATENCY_CAPACITY = 512;
176
+ const MAX_PENDING_PUBLICATIONS = 512;
177
+
178
+ export class StagePerformanceTelemetry {
179
+ readonly #now: () => number;
180
+ readonly #visible: () => boolean;
181
+ readonly #richLatencyCapacity: number;
182
+ readonly #qualityListeners = new Set<() => void>();
183
+ /** Told once per CLOSED window, with what that window actually held. The
184
+ * quality listeners above fire only when the policy changes rung, which is
185
+ * rare by design; a readout of the current rate needs every window. */
186
+ readonly #windowListeners = new Set<(window: StagePerformanceWindow) => void>();
187
+ /** Told of every frame of the transport that reached the canvas, with the
188
+ * clock read its request took and its frame (`subscribePresented`). */
189
+ readonly #presentedListeners = new Set<(requestedAt: number, frame: number) => void>();
190
+ readonly #debugSampleListeners = new Set<(sample: StagePresentationSample) => void>();
191
+ readonly #pendingByToken = new Map<number, PublishedFrame>();
192
+ readonly #windowLatencies: number[] = [];
193
+ readonly #windowGpu: number[] = [];
194
+ readonly #richSamples: StagePresentationSample[] = [];
195
+
196
+ #rich: boolean;
197
+ #debugDocumentSource: Doc3D | null = null;
198
+ #debugDocument: StageDebugDocumentSnapshot | null = null;
199
+ #debugDocumentLoadSequence = 0;
200
+ #playing = false;
201
+ // The lane's own default rate until `setFrameRate` hears from a document,
202
+ // with the period derived exactly as that setter derives it.
203
+ #fpsNumerator = DEFAULT_SHOT_FPS.num;
204
+ #fpsDenominator = DEFAULT_SHOT_FPS.den;
205
+ #expectedFrameRemainder = 0;
206
+ #windowStartedAt = 0;
207
+ #requestSequence = 0;
208
+ #publishSequence = 0;
209
+ #sampleSequence = 0;
210
+ #lastPresentedRevision = 0;
211
+ #requestedFrames = 0;
212
+ #publishedFrames = 0;
213
+ #presentedFrames = 0;
214
+ #matchedPresentations = 0;
215
+ #unmatchedPresentations = 0;
216
+ #droppedPublications = 0;
217
+ #requestCoalesced = 0;
218
+ #publicationCoalesced = 0;
219
+ #lastWindowP95Ms: number | null = null;
220
+ #windowPresentedFrames = 0;
221
+ #windowDroppedPublications = 0;
222
+ #lastWindowPresentedFrames = 0;
223
+ #lastWindowExpectedFrames = 0;
224
+ #lastWindowDroppedPublications = 0;
225
+
226
+ constructor(options: StagePerformanceTelemetryOptions = {}) {
227
+ this.#rich = options.rich === true;
228
+ this.#now = options.now ?? (() => performance.now());
229
+ this.#visible =
230
+ options.visible ??
231
+ (() => typeof document === "undefined" || document.visibilityState === "visible");
232
+ const requestedCapacity = options.richLatencyCapacity ?? DEFAULT_RICH_LATENCY_CAPACITY;
233
+ this.#richLatencyCapacity = Number.isFinite(requestedCapacity)
234
+ ? Math.max(1, Math.round(requestedCapacity))
235
+ : DEFAULT_RICH_LATENCY_CAPACITY;
236
+ }
237
+
238
+ setRichEnabled(enabled: boolean): void {
239
+ this.#rich = enabled;
240
+ if (enabled) return;
241
+ this.#richSamples.length = 0;
242
+ this.#debugSampleListeners.clear();
243
+ this.#debugDocumentSource = null;
244
+ this.#debugDocument = null;
245
+ }
246
+
247
+ /**
248
+ * Records the bridge's accepted document for local rich diagnostics.
249
+ *
250
+ * The source reference never leaves this class. `present` receives the
251
+ * document rendered by StageApp and only confirms a presentation when the
252
+ * references are identical. A late callback from a replaced document,
253
+ * including one that reused the same persisted id, therefore cannot satisfy
254
+ * the browser gate.
255
+ */
256
+ setDebugDocument(doc: Doc3D | null): void {
257
+ if (!this.#rich || this.#debugDocumentSource === doc) return;
258
+ this.#debugDocumentSource = doc;
259
+ if (doc === null) {
260
+ this.#debugDocument = null;
261
+ return;
262
+ }
263
+ this.#debugDocumentLoadSequence += 1;
264
+ const debugDocument: StageDebugDocumentSnapshot = {
265
+ identity: {
266
+ id: doc.id,
267
+ name: doc.name,
268
+ title: doc.title ?? null,
269
+ size: [doc.size.width, doc.size.height],
270
+ fps: [doc.fps.num, doc.fps.den],
271
+ scenes: doc.scenes.map((id) => {
272
+ const scene = doc.scenesById[id]!;
273
+ return [id, scene.durationFrames, Object.keys(scene.layersById).length];
274
+ }),
275
+ },
276
+ fingerprint: null,
277
+ fingerprintError: null,
278
+ loadSequence: this.#debugDocumentLoadSequence,
279
+ loadedAtSampleSequence: this.#sampleSequence,
280
+ presentationCount: 0,
281
+ lastPresentation: null,
282
+ };
283
+ this.#debugDocument = debugDocument;
284
+ // The canonical walk and digest exist only under rich diagnostics. The
285
+ // generation checks keep a late digest from labelling a replacement doc.
286
+ void debugDocumentFingerprint(doc).then(
287
+ (fingerprint) => {
288
+ if (this.#debugDocumentSource !== doc || this.#debugDocument !== debugDocument) return;
289
+ debugDocument.fingerprint = fingerprint;
290
+ },
291
+ (error: unknown) => {
292
+ if (this.#debugDocumentSource !== doc || this.#debugDocument !== debugDocument) return;
293
+ debugDocument.fingerprintError = error instanceof Error ? error.message : String(error);
294
+ },
295
+ );
296
+ }
297
+
298
+ setFrameRate(fps: { num: number; den: number } | null): void {
299
+ if (
300
+ fps === null ||
301
+ !Number.isFinite(fps.num) ||
302
+ !Number.isFinite(fps.den) ||
303
+ fps.num <= 0 ||
304
+ fps.den <= 0
305
+ ) {
306
+ return;
307
+ }
308
+ this.#fpsNumerator = fps.num;
309
+ this.#fpsDenominator = fps.den;
310
+ this.#expectedFrameRemainder = 0;
311
+ }
312
+
313
+ /** A document/topology replacement invalidates every outstanding token. */
314
+ resetPublications(): void {
315
+ this.#pendingByToken.clear();
316
+ }
317
+
318
+ setPlaying(playing: boolean): void {
319
+ if (playing === this.#playing) return;
320
+ this.#playing = playing;
321
+ this.#windowLatencies.length = 0;
322
+ this.#windowGpu.length = 0;
323
+ this.#expectedFrameRemainder = 0;
324
+ this.#lastWindowP95Ms = null;
325
+ this.#pendingByToken.clear();
326
+ this.#windowPresentedFrames = 0;
327
+ this.#windowDroppedPublications = 0;
328
+ this.#lastWindowPresentedFrames = 0;
329
+ this.#lastWindowExpectedFrames = 0;
330
+ this.#lastWindowDroppedPublications = 0;
331
+ if (playing) {
332
+ this.#windowStartedAt = this.#now();
333
+ return;
334
+ }
335
+ this.#windowStartedAt = 0;
336
+ }
337
+
338
+ request(frame: number): StageFrameRequest | null {
339
+ // A paused stage without the opt-in diagnostics has no latency policy to
340
+ // drive. Return the null token before allocating, reading the clock, or
341
+ // touching diagnostic counters.
342
+ if (!this.#playing && !this.#rich) return null;
343
+ const requestedAt = this.#now();
344
+ if (this.#playing) this.#rollWindowsBefore(requestedAt);
345
+ this.#requestedFrames += 1;
346
+ this.#requestSequence += 1;
347
+ return { requestSequence: this.#requestSequence, frame, requestedAt };
348
+ }
349
+
350
+ coalesceRequest(request: StageFrameRequest | null): void {
351
+ if (request === null) return;
352
+ this.#requestCoalesced += 1;
353
+ }
354
+
355
+ publish(request: StageFrameRequest | null): number | null {
356
+ if (request === null) return null;
357
+ this.#publishedFrames += 1;
358
+ this.#publishSequence += 1;
359
+ const publicationToken = this.#publishSequence;
360
+ this.#pendingByToken.set(publicationToken, { ...request, publicationToken });
361
+ if (this.#pendingByToken.size > MAX_PENDING_PUBLICATIONS) {
362
+ const oldest = this.#pendingByToken.keys().next().value as number | undefined;
363
+ if (oldest !== undefined) {
364
+ this.#pendingByToken.delete(oldest);
365
+ this.#publicationCoalesced += 1;
366
+ this.#droppedPublications += 1;
367
+ if (this.#playing) this.#windowDroppedPublications += 1;
368
+ }
369
+ }
370
+ return publicationToken;
371
+ }
372
+
373
+ present(info: StagePresentedFrameLike, document: Doc3D | null = null): void {
374
+ if (!this.#playing && !this.#rich) return;
375
+ const presentedAt = this.#now();
376
+ if (
377
+ this.#rich &&
378
+ document !== null &&
379
+ document === this.#debugDocumentSource &&
380
+ this.#debugDocument !== null
381
+ ) {
382
+ this.#debugDocument.presentationCount += 1;
383
+ this.#debugDocument.lastPresentation = {
384
+ frame: info.frame,
385
+ revision: info.revision,
386
+ presentedAt,
387
+ };
388
+ }
389
+ if (this.#playing) this.#rollWindowsBefore(presentedAt);
390
+ this.#presentedFrames += 1;
391
+ this.#lastPresentedRevision = Math.max(this.#lastPresentedRevision, info.revision);
392
+ const token = info.presentationToken;
393
+ const pending = token === undefined ? undefined : this.#pendingByToken.get(token);
394
+ if (pending === undefined || pending.frame !== info.frame) {
395
+ this.#unmatchedPresentations += 1;
396
+ if (this.#playing) this.#rollWindowsThrough(presentedAt);
397
+ return;
398
+ }
399
+
400
+ // Any older pending publication was superseded before it reached the
401
+ // canvas. Identity is the publication token, never the authored frame: a
402
+ // seek away and back may publish the same frame several times.
403
+ for (const [pendingToken, row] of this.#pendingByToken) {
404
+ if (row.publicationToken < pending.publicationToken) {
405
+ this.#publicationCoalesced += 1;
406
+ this.#droppedPublications += 1;
407
+ if (this.#playing) this.#windowDroppedPublications += 1;
408
+ }
409
+ if (row.publicationToken <= pending.publicationToken) this.#pendingByToken.delete(pendingToken);
410
+ }
411
+ this.#matchedPresentations += 1;
412
+ if (this.#playing) {
413
+ this.#windowPresentedFrames += 1;
414
+ this.#notifyPresented(pending.requestedAt ?? presentedAt, pending.frame);
415
+ }
416
+
417
+ if (pending.requestedAt === null) {
418
+ if (this.#playing) this.#rollWindowsThrough(presentedAt);
419
+ return;
420
+ }
421
+ const latency = Math.max(0, presentedAt - pending.requestedAt);
422
+ if (this.#rich) {
423
+ this.#pushRichSample({
424
+ publicationToken: pending.publicationToken,
425
+ requestSequence: pending.requestSequence,
426
+ frame: pending.frame,
427
+ revision: info.revision,
428
+ requestedAt: pending.requestedAt,
429
+ presentedAt,
430
+ latencyMs: latency,
431
+ });
432
+ }
433
+ if (!this.#playing) return;
434
+ this.#windowLatencies.push(latency);
435
+ this.#rollWindowsThrough(presentedAt);
436
+ }
437
+
438
+ /** One frame's GPU time, ms, as the stage's `GpuTimer` resolved it: into
439
+ * the open window while playing, for the report's `gpuMs`. A paused
440
+ * stage's draws are dropped, as its presentations are. */
441
+ noteGpu(ms: number): void {
442
+ if (this.#playing && Number.isFinite(ms)) this.#windowGpu.push(ms);
443
+ }
444
+
445
+ /** Always 0. The DPR ladder is gone; the method survives for the HUD, the
446
+ * gate and the counters that print it. The playback render scale is not a
447
+ * rung of it: the canvas keeps its density (renderScaleController.ts). */
448
+ qualityLevel(): AdaptivePresentationLevel {
449
+ return 0;
450
+ }
451
+
452
+ /** Never fires. Kept so a caller written against the ladder still type
453
+ * checks; nothing changes rung any more. */
454
+ subscribeQuality(listener: () => void): () => void {
455
+ this.#qualityListeners.add(listener);
456
+ return () => this.#qualityListeners.delete(listener);
457
+ }
458
+
459
+ /**
460
+ * Every closed, VISIBLE window: what was presented against what the cadence
461
+ * asked for.
462
+ *
463
+ * Windows roll only while playing (`#rollWindowsBefore`/`Through` both guard
464
+ * on `#playing`), so this is one call a second during playback and silent
465
+ * otherwise. A window nobody could see returns early above without notifying:
466
+ * a hidden tab presents nothing, and reporting 0 fps for it would be a
467
+ * measurement of the tab rather than of the scene.
468
+ */
469
+ subscribeWindow(listener: (window: StagePerformanceWindow) => void): () => void {
470
+ this.#windowListeners.add(listener);
471
+ return () => this.#windowListeners.delete(listener);
472
+ }
473
+
474
+ /**
475
+ * Every frame of the transport that reached the canvas while playing, with
476
+ * its document frame, timed by the clock read its request took (`request`,
477
+ * in the transport's tick before the frame was drawn): the playback render
478
+ * scale's deadlines (renderScaleController.ts `presented`). Timed by the
479
+ * echo, after the draw in the same task, a frame drawn 10 ms slower than the
480
+ * one before it, in time for its refresh, followed it by more than a
481
+ * refresh and a half at 60 fps, late.
482
+ *
483
+ * A publication the transport made and the runtime presented, so neither a
484
+ * preparing pass's frames (the stage is not playing for this sampler), nor a
485
+ * superseded publication, nor a redraw of a frame already on screen, nor a
486
+ * frame the playback scale held off the canvas, which the runtime never
487
+ * echoes. One call per presented frame and nothing else while it plays.
488
+ */
489
+ subscribePresented(listener: (requestedAt: number, frame: number) => void): () => void {
490
+ this.#presentedListeners.add(listener);
491
+ return () => this.#presentedListeners.delete(listener);
492
+ }
493
+
494
+ snapshot(): StagePerformanceSnapshot {
495
+ const richLatencies = this.#richSamples.map((sample) => sample.latencyMs);
496
+ return {
497
+ requestedFrames: this.#requestedFrames,
498
+ publishedFrames: this.#publishedFrames,
499
+ presentedFrames: this.#presentedFrames,
500
+ matchedPresentations: this.#matchedPresentations,
501
+ unmatchedPresentations: this.#unmatchedPresentations,
502
+ droppedPublications: this.#droppedPublications,
503
+ requestCoalesced: this.#requestCoalesced,
504
+ publicationCoalesced: this.#publicationCoalesced,
505
+ latencySamples: this.#richSamples.length,
506
+ latencyP50Ms: percentile(richLatencies, 0.5),
507
+ latencyP95Ms: percentile(richLatencies, 0.95),
508
+ latencyP99Ms: percentile(richLatencies, 0.99),
509
+ lastWindowP95Ms: this.#lastWindowP95Ms,
510
+ lastWindowPresentedFrames: this.#lastWindowPresentedFrames,
511
+ lastWindowExpectedFrames: this.#lastWindowExpectedFrames,
512
+ lastWindowDroppedPublications: this.#lastWindowDroppedPublications,
513
+ lastPresentedRevision: this.#lastPresentedRevision,
514
+ qualityLevel: 0,
515
+ };
516
+ }
517
+
518
+ /**
519
+ * Exact samples after a caller-owned cursor. Null while rich diagnostics are
520
+ * disabled, so production callers cannot accidentally turn the ring on by
521
+ * reading it.
522
+ */
523
+ debugSnapshot(afterSequence = 0): StagePerformanceDebugSnapshot | null {
524
+ if (!this.#rich) return null;
525
+ const cursor = Number.isFinite(afterSequence) ? Math.max(0, Math.floor(afterSequence)) : 0;
526
+ const oldestSampleSequence = this.#richSamples[0]?.sequence ?? this.#sampleSequence + 1;
527
+ return {
528
+ counters: this.snapshot(),
529
+ samples: this.#richSamples
530
+ .filter((sample) => sample.sequence > cursor)
531
+ .map((sample) => ({ ...sample })),
532
+ latestSampleSequence: this.#sampleSequence,
533
+ oldestSampleSequence,
534
+ document:
535
+ this.#debugDocument === null
536
+ ? null
537
+ : {
538
+ ...this.#debugDocument,
539
+ identity: {
540
+ ...this.#debugDocument.identity,
541
+ size: [...this.#debugDocument.identity.size],
542
+ fps: [...this.#debugDocument.identity.fps],
543
+ scenes: this.#debugDocument.identity.scenes.map((scene) => [...scene]),
544
+ },
545
+ lastPresentation:
546
+ this.#debugDocument.lastPresentation === null
547
+ ? null
548
+ : { ...this.#debugDocument.lastPresentation },
549
+ },
550
+ };
551
+ }
552
+
553
+ /**
554
+ * O(1) cursor for callers that only need to ignore presentations that
555
+ * happened before they subscribed. Null keeps the rich-mode boundary
556
+ * explicit without copying or walking the retained sample ring.
557
+ */
558
+ debugSampleSequence(): number | null {
559
+ return this.#rich ? this.#sampleSequence : null;
560
+ }
561
+
562
+ /**
563
+ * Event-shaped access for the local profiler harness. Polling this ring once
564
+ * per animation frame would put the harness itself into the sampled stacks.
565
+ * Normal stages cannot subscribe because rich diagnostics are off, and an
566
+ * empty listener set adds no per-presentation work.
567
+ */
568
+ subscribeDebugSample(
569
+ listener: (sample: StagePresentationSample) => void,
570
+ ): (() => void) | null {
571
+ if (!this.#rich) return null;
572
+ this.#debugSampleListeners.add(listener);
573
+ return () => this.#debugSampleListeners.delete(listener);
574
+ }
575
+
576
+ #pushRichSample(sample: Omit<StagePresentationSample, "sequence">): void {
577
+ if (this.#richSamples.length >= this.#richLatencyCapacity) this.#richSamples.shift();
578
+ this.#sampleSequence += 1;
579
+ const row = { sequence: this.#sampleSequence, ...sample };
580
+ this.#richSamples.push(row);
581
+ for (const listener of this.#debugSampleListeners) {
582
+ try {
583
+ listener({ ...row });
584
+ } catch {
585
+ // Diagnostics cannot break the runtime's onPresented callback.
586
+ }
587
+ }
588
+ }
589
+
590
+ /** Close buckets strictly before an event, so a presentation exactly on the
591
+ * boundary completes the bucket whose cadence it satisfies. */
592
+ #rollWindowsBefore(now: number): void {
593
+ while (this.#playing && now > this.#windowStartedAt + ADAPTIVE_WINDOW_MS) {
594
+ this.#finishWindow();
595
+ }
596
+ }
597
+
598
+ /** Close the boundary bucket after its presentation has been attributed. */
599
+ #rollWindowsThrough(now: number): void {
600
+ while (this.#playing && now >= this.#windowStartedAt + ADAPTIVE_WINDOW_MS) {
601
+ this.#finishWindow();
602
+ }
603
+ }
604
+
605
+ #finishWindow(): void {
606
+ const durationMs = ADAPTIVE_WINDOW_MS;
607
+ const cadenceTotal = this.#expectedFrameRemainder + this.#fpsNumerator;
608
+ const wholeExpectedFrames = Math.floor(cadenceTotal / this.#fpsDenominator + 1e-9);
609
+ this.#expectedFrameRemainder =
610
+ cadenceTotal - wholeExpectedFrames * this.#fpsDenominator;
611
+ // The rational accumulator above is exact, so the reported cadence stays
612
+ // honest across fractional rates. Nothing judges it any more: the window
613
+ // is a readout, and the authored expectation is the honest denominator.
614
+ const expectedFrames = Math.max(1, wholeExpectedFrames);
615
+ // A window nobody could see is not evidence about this scene. Close the
616
+ // bucket and forget what it held.
617
+ if (!this.#visible()) {
618
+ this.#windowLatencies.length = 0;
619
+ this.#windowGpu.length = 0;
620
+ this.#windowPresentedFrames = 0;
621
+ this.#windowDroppedPublications = 0;
622
+ this.#windowStartedAt += ADAPTIVE_WINDOW_MS;
623
+ return;
624
+ }
625
+ const p95 = percentile(this.#windowLatencies, 0.95);
626
+ this.#lastWindowP95Ms = p95;
627
+ this.#lastWindowPresentedFrames = this.#windowPresentedFrames;
628
+ this.#lastWindowExpectedFrames = expectedFrames;
629
+ this.#lastWindowDroppedPublications = this.#windowDroppedPublications;
630
+ // READ BEFORE THE RESET: the three counters below are zeroed on the next
631
+ // line, and a listener that read them from the instance afterwards would
632
+ // report an empty window.
633
+ const closed: StagePerformanceWindow = {
634
+ presentedFrames: this.#windowPresentedFrames,
635
+ expectedFrames,
636
+ droppedPublications: this.#windowDroppedPublications,
637
+ durationMs,
638
+ gpuP95Ms: percentile(this.#windowGpu, 0.95),
639
+ };
640
+ this.#windowLatencies.length = 0;
641
+ this.#windowGpu.length = 0;
642
+ this.#windowPresentedFrames = 0;
643
+ this.#windowDroppedPublications = 0;
644
+ this.#windowStartedAt += ADAPTIVE_WINDOW_MS;
645
+ this.#notifyWindow(closed);
646
+ }
647
+
648
+ #notifyPresented(requestedAt: number, frame: number): void {
649
+ for (const listener of this.#presentedListeners) {
650
+ try {
651
+ listener(requestedAt, frame);
652
+ } catch {
653
+ // The window listeners' rule: a reader never breaks the runtime's
654
+ // presentation path.
655
+ }
656
+ }
657
+ }
658
+
659
+ #notifyWindow(window: StagePerformanceWindow): void {
660
+ for (const listener of this.#windowListeners) {
661
+ try {
662
+ listener(window);
663
+ } catch {
664
+ // A readout cannot break the runtime's presentation path, which is the
665
+ // same rule the sample listeners above follow.
666
+ }
667
+ }
668
+ }
669
+ }
670
+
671
+ /** Nearest-rank percentile, total over an empty or non-finite sample set. */
672
+ export function percentile(values: readonly number[], quantile: number): number | null {
673
+ const finite = values.filter(Number.isFinite).sort((a, b) => a - b);
674
+ if (finite.length === 0) return null;
675
+ const q = Math.max(0, Math.min(1, quantile));
676
+ const index = Math.max(0, Math.ceil(q * finite.length) - 1);
677
+ return finite[index] ?? null;
678
+ }
679
+
680
+ export function stagePerformanceInterval(
681
+ before: StagePerformanceSnapshot,
682
+ after: StagePerformanceSnapshot,
683
+ renderPasses: number,
684
+ ): StagePerformanceInterval {
685
+ const presentedFrames = Math.max(0, after.presentedFrames - before.presentedFrames);
686
+ return {
687
+ requestedFrames: Math.max(0, after.requestedFrames - before.requestedFrames),
688
+ publishedFrames: Math.max(0, after.publishedFrames - before.publishedFrames),
689
+ presentedFrames,
690
+ matchedPresentations: Math.max(0, after.matchedPresentations - before.matchedPresentations),
691
+ unmatchedPresentations: Math.max(0, after.unmatchedPresentations - before.unmatchedPresentations),
692
+ droppedPublications: Math.max(0, after.droppedPublications - before.droppedPublications),
693
+ requestCoalesced: Math.max(0, after.requestCoalesced - before.requestCoalesced),
694
+ publicationCoalesced: Math.max(0, after.publicationCoalesced - before.publicationCoalesced),
695
+ renderPasses: Math.max(0, renderPasses),
696
+ effectivePasses: presentedFrames > 0 ? Math.max(0, renderPasses) / presentedFrames : 0,
697
+ };
698
+ }
699
+
700
+ const latencyText = (value: number | null): string => (value === null ? "-" : value.toFixed(1));
701
+
702
+ /** One compact diagnostic segment consumed by PerfHud and benchmark capture. */
703
+ export function stagePerformanceLine(
704
+ snapshot: StagePerformanceSnapshot,
705
+ interval: StagePerformanceInterval,
706
+ quality: PresentationQuality,
707
+ ): string {
708
+ const dpr = quality.dpr.toFixed(2);
709
+ return (
710
+ `frames req ${interval.requestedFrames}/pub ${interval.publishedFrames}/pres ${interval.presentedFrames}` +
711
+ ` · match ${interval.matchedPresentations}/unmatched ${interval.unmatchedPresentations}` +
712
+ ` · coal req ${interval.requestCoalesced}/pub ${interval.publicationCoalesced}/drop ${interval.droppedPublications}` +
713
+ ` · lat p50 ${latencyText(snapshot.latencyP50Ms)}/p95 ${latencyText(snapshot.latencyP95Ms)}/p99 ${latencyText(snapshot.latencyP99Ms)}ms` +
714
+ ` · passes ${interval.renderPasses} · effective ${interval.effectivePasses.toFixed(2)}` +
715
+ ` · q L${quality.level} dpr ${dpr} raster ${quality.rasterScale}`
716
+ );
717
+ }