@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,485 @@
1
+ // THE TAP: one AudioWorkletNode hanging off a GainNode, measuring and never
2
+ // sounding.
3
+ //
4
+ // ── WHY A WORKLET AND NOT AN AnalyserNode ───────────────────────────────
5
+ //
6
+ // `AnalyserNode` answers on the MAIN thread, when asked. A meter built on one
7
+ // samples the signal at whatever rate rAF happens to run at, which means every
8
+ // transient between two frames is invisible and a stalled main thread reads as
9
+ // silence. The peak a mixer exists to show is exactly the thing that model
10
+ // drops. A worklet sees every block, so the attack is measured where the audio
11
+ // is and only the RESULT crosses the thread boundary.
12
+ //
13
+ // ── THE OUTPUT IS NEVER CONNECTED, AND THAT IS THE SAFETY RULE ──────────
14
+ //
15
+ // The node is connected FROM the gain node and its own output goes NOWHERE. A
16
+ // meter that joined the graph would sum a second copy of the signal into the
17
+ // destination: every metered channel 6 dB hot, and the export — which mixes
18
+ // offline and has no meters — would disagree with what the author heard. The
19
+ // processor writes nothing to `outputs` either, so even a mis-wiring downstream
20
+ // could only add silence.
21
+ //
22
+ // ── THE PROCESSOR'S SOURCE IS BUILT HERE, FROM meterMath.ts's ───────────
23
+ //
24
+ // A worklet is loaded by URL, and this chrome is served as one bundle with no
25
+ // second entry point to put a processor file at. So the source is assembled as
26
+ // a string and handed over as a Blob URL. The SMOOTHING inside it is not
27
+ // written here: `METER_STEP_SOURCE` is `meterMath.ts`'s own function,
28
+ // stringified, which is what makes the ballistics unit-testable with no
29
+ // AudioContext anywhere. Read that file's header for the constraint that keeps
30
+ // the function portable.
31
+ //
32
+ // THE SOURCE TEXT'S OWN NAME IS NEVER USED. The chrome is minified, so the
33
+ // text reads `function Xe(…)` in production, and the prelude binds each one to
34
+ // the name `process` calls (`const meterStep = (…);`). `audioMeter.test.ts`
35
+ // runs this processor built from a minified bundle; `sourceHygiene.test.ts`
36
+ // fails any evaluated text in the repo that calls a serialised function by a
37
+ // name it does not bind.
38
+ //
39
+ // `__name` IS DEFINED IN THE PRELUDE ON PURPOSE. esbuild's `keepNames` wraps
40
+ // named functions in a module-scope helper that does not survive being turned
41
+ // into text, and this repo has already shipped one page-injected function that
42
+ // threw `ReferenceError: __name is not defined` while every vitest run stayed
43
+ // green (vitest's transform omits `keepNames`). `meterStep` is written with no
44
+ // inner functions so it cannot need the helper today; the prelude is what stops
45
+ // a future edit reintroducing the failure in a place nothing tests.
46
+ import { useCallback, useEffect, useSyncExternalStore } from "react";
47
+ import { useShotEditor } from "../state/context.js";
48
+ import { recordRuntimeError, runtimeRealmRestarted } from "../state/runtimeErrors.js";
49
+ import {
50
+ METER_CHANNELS,
51
+ METER_DIFFERS_SOURCE,
52
+ METER_FIELDS,
53
+ METER_FLOOR,
54
+ METER_MESSAGE_LENGTH,
55
+ METER_POST_MS,
56
+ METER_STEP_SOURCE,
57
+ METER_TUNING,
58
+ METER_VISIBLE_RATIO,
59
+ meterVisiblyDiffers,
60
+ } from "./meterMath.js";
61
+ import { clearMeter, publishMeter, readMeter, subscribeMeter } from "./meterStore.js";
62
+ import type { MeterReading } from "./meterStore.js";
63
+ import type { MeterChannel } from "./meterMath.js";
64
+
65
+ /** The processor's registered name. One string, spelled once: the module text
66
+ * and the node constructor must agree or the construction throws. */
67
+ export const METER_PROCESSOR = "dailies-meter";
68
+
69
+ // --- minimal structural Web Audio surface (fakes stay trivial) ---------------
70
+ //
71
+ // The same posture `preview.ts` takes and for the same reason: no DOM lib types
72
+ // in the signatures, so the jsdom tests drive a hand-rolled fake rather than
73
+ // needing a real audio graph.
74
+
75
+ export interface MeterContextLike {
76
+ readonly sampleRate: number;
77
+ }
78
+
79
+ /**
80
+ * What a tap hangs off. `disconnect` MUST take a destination: calling the
81
+ * no-argument form on a monitored gain node would tear down its real output
82
+ * too, which is the whole mix.
83
+ */
84
+ export interface MeterSourceLike {
85
+ connect(destination: unknown): unknown;
86
+ disconnect(destination?: unknown): void;
87
+ }
88
+
89
+ export interface MeterPortLike {
90
+ onmessage: ((event: { data: unknown }) => void) | null;
91
+ close?(): void;
92
+ }
93
+
94
+ export interface MeterNodeLike {
95
+ readonly port: MeterPortLike;
96
+ disconnect(): void;
97
+ /** Raised once when the processor throws: it is dead for good. */
98
+ onprocessorerror?: ((event: unknown) => void) | null;
99
+ }
100
+
101
+ export interface MeterDeps {
102
+ /** Compile the processor into this context. Called at most once per context;
103
+ * a second call with the same URL is harmless but is not made. */
104
+ addModule(ctx: MeterContextLike, url: string): Promise<void>;
105
+ /** Construct the node. Separate from `addModule` because the construction is
106
+ * what throws when the module has not landed, and a test wants to drive the
107
+ * two independently. */
108
+ createNode(ctx: MeterContextLike): MeterNodeLike & MeterSourceLike;
109
+ /** The module URL. One per page in production. */
110
+ moduleUrl(): string;
111
+ }
112
+
113
+ /** The whole processor, as text. */
114
+ export function meterWorkletSource(): string {
115
+ return [
116
+ // See the header: scoped to this module, never installed on anything else.
117
+ "const __name = (fn) => fn;",
118
+ // BOUND BY NAME HERE, never by the text's own name. The shipped chrome is
119
+ // minified, so `meterStep.toString()` there is `function Xe(…)`, and a bare
120
+ // declaration would leave `process` calling a `meterStep` that does not
121
+ // exist: `ReferenceError: meterStep is not defined` on every quantum. As a
122
+ // parenthesised expression the text's own name no longer matters.
123
+ `const meterStep = (${METER_STEP_SOURCE});`,
124
+ `const meterVisiblyDiffers = (${METER_DIFFERS_SOURCE});`,
125
+ `const TUNING = ${JSON.stringify(METER_TUNING)};`,
126
+ `const POST_MS = ${JSON.stringify(METER_POST_MS)};`,
127
+ `const FLOOR = ${JSON.stringify(METER_FLOOR)};`,
128
+ `const RATIO = ${JSON.stringify(METER_VISIBLE_RATIO)};`,
129
+ `const CHANNELS = ${JSON.stringify(METER_CHANNELS.length)};`,
130
+ `const FIELDS = ${JSON.stringify(METER_FIELDS)};`,
131
+ "class DailiesMeterProcessor extends AudioWorkletProcessor {",
132
+ " constructor() {",
133
+ " super();",
134
+ // ONE STATE PER CHANNEL, each through the same `meterStep`: L and R are
135
+ // two meters that happen to share a quantum, not one meter split.
136
+ " this.states = [];",
137
+ " for (let c = 0; c < CHANNELS; c++) this.states.push({ rms: 0, peak: 0, hold: 0, holdLeftMs: 0 });",
138
+ // What the MAIN THREAD currently believes, per channel, which is what a new
139
+ // post has to be measured against. Born at silence, which is what the
140
+ // store answers for a channel nobody has published to yet, so the first
141
+ // audible block posts and a silent start posts nothing at all. Allocated
142
+ // once; a post overwrites its entries, never the array.
143
+ " this.posted = this.states.slice();",
144
+ " this.sinceMs = 0;",
145
+ " }",
146
+ " process(inputs) {",
147
+ " const input = inputs[0];",
148
+ " const count = input ? input.length : 0;",
149
+ " let frames = 0;",
150
+ " for (let c = 0; c < count; c++) {",
151
+ " const ch = input[c];",
152
+ " if (ch && ch.length > frames) frames = ch.length;",
153
+ " }",
154
+ // A DISCONNECTED input reports no channels at all, which is not the same as
155
+ // a block of zeros: the needle must still decay over the time that passed,
156
+ // so the quantum falls back to the spec's 128 rather than to nothing.
157
+ " if (frames === 0) frames = 128;",
158
+ " const dtMs = (frames / sampleRate) * 1000;",
159
+ " for (let c = 0; c < CHANNELS; c++) {",
160
+ // ONE INPUT CHANNEL FEEDS BOTH. The node asks for two with the speakers
161
+ // rule, so the engine upmixes mono before this runs; an engine (or a
162
+ // test) that hands over one channel anyway still reads as equal L and R,
163
+ // never as a dead right side.
164
+ " const ch = count === 0 ? null : input[c < count ? c : count - 1];",
165
+ " let sum = 0;",
166
+ " let peak = 0;",
167
+ " const n = ch ? ch.length : 0;",
168
+ " for (let i = 0; i < n; i++) {",
169
+ " const v = ch[i];",
170
+ " sum += v * v;",
171
+ " const a = v < 0 ? -v : v;",
172
+ " if (a > peak) peak = a;",
173
+ " }",
174
+ " const rms = n > 0 ? Math.sqrt(sum / n) : 0;",
175
+ " this.states[c] = meterStep(this.states[c], rms, peak, dtMs, TUNING);",
176
+ " }",
177
+ " this.sinceMs += dtMs;",
178
+ " if (this.sinceMs < POST_MS) return true;",
179
+ // EITHER CHANNEL moving is a post, and the post carries both: the main
180
+ // thread gates each channel again on its own, so a still side costs no
181
+ // render there.
182
+ " let differs = false;",
183
+ " for (let c = 0; c < CHANNELS && !differs; c++) {",
184
+ " const s = this.states[c];",
185
+ " differs = meterVisiblyDiffers(this.posted[c], s.rms, s.peak, s.hold, FLOOR, RATIO);",
186
+ " }",
187
+ // ── THE QUIET PATH, AND WHY IT IS HERE AND NOT ON THE MAIN THREAD ──
188
+ //
189
+ // A filter in the `onmessage` handler saves the React commit and nothing
190
+ // else: the structured clone, the port hop and the main thread's wake-up
191
+ // have all already happened by the time it runs. This is where that cost
192
+ // is actually spent, so this is where a silent mix has to stop spending
193
+ // it. The tap outlives the transport, because the preview's AudioContext
194
+ // is built on the first play edge and is not suspended afterwards, so
195
+ // "silent" is the state a metered session sits in for most of its life.
196
+ //
197
+ // RE-ARMING IS INSTANT, and that is what `this.sinceMs = POST_MS` buys.
198
+ // The counter is left ARMED rather than reset, so the gate is re-asked
199
+ // every render quantum (2.7 ms at 48 kHz) instead of every 16 ms, and the
200
+ // first audible block posts from inside the very `process` call that saw
201
+ // it. A meter that misses the attack is worse than a meter that costs 62
202
+ // commits a second.
203
+ " if (!differs) {",
204
+ " this.sinceMs = POST_MS;",
205
+ " return true;",
206
+ " }",
207
+ " this.sinceMs = 0;",
208
+ // A FRESH array per post, copied by the structured clone. Transferring
209
+ // would detach the buffer and cost an allocation anyway; six floats at
210
+ // ~60 Hz is not worth a ring buffer and a SharedArrayBuffer's
211
+ // cross-origin-isolation requirement.
212
+ " const out = new Float32Array(CHANNELS * FIELDS);",
213
+ " for (let c = 0; c < CHANNELS; c++) {",
214
+ " const s = this.states[c];",
215
+ // THE MAIN THREAD'S GATE, MIRRORED. `onmessage` publishes only the
216
+ // channels that visibly differ from the store and drops the rest, so a
217
+ // channel moves its `posted` entry only when it passes the same test.
218
+ // Advancing a dropped channel's entry would let it creep, one sub-threshold
219
+ // post at a time, up to two thresholds from what the face shows, and then
220
+ // never post again.
221
+ " if (meterVisiblyDiffers(this.posted[c], s.rms, s.peak, s.hold, FLOOR, RATIO)) this.posted[c] = s;",
222
+ " out[c * FIELDS] = s.rms;",
223
+ " out[c * FIELDS + 1] = s.peak;",
224
+ " out[c * FIELDS + 2] = s.hold;",
225
+ " }",
226
+ " this.port.postMessage(out);",
227
+ // TRUE FOR EVER while the node lives. Returning false lets the engine
228
+ // collect the processor, and a meter that stopped measuring during a quiet
229
+ // passage would never come back. The quiet path above stops the POSTING,
230
+ // never the measuring: the needle has to keep falling while nobody is
231
+ // being told, or the level it re-arms from is the one it had before the
232
+ // silence.
233
+ " return true;",
234
+ " }",
235
+ "}",
236
+ `registerProcessor(${JSON.stringify(METER_PROCESSOR)}, DailiesMeterProcessor);`,
237
+ ].join("\n");
238
+ }
239
+
240
+ /**
241
+ * ONE Blob URL for the page.
242
+ *
243
+ * ponytail: created on first use and never revoked. It is one small object URL
244
+ * for the life of the document, and revoking it would break the next context's
245
+ * `addModule` — a second AudioContext is born whenever the preview is disposed
246
+ * and re-created. Revoke it from a page-teardown hook if a leak is ever
247
+ * measured; there is nothing else holding it.
248
+ */
249
+ let moduleUrl: string | null = null;
250
+ function browserModuleUrl(): string {
251
+ if (moduleUrl === null) {
252
+ moduleUrl = URL.createObjectURL(new Blob([meterWorkletSource()], { type: "text/javascript" }));
253
+ }
254
+ return moduleUrl;
255
+ }
256
+
257
+ const browserDeps: MeterDeps = {
258
+ addModule: (ctx, url) => (ctx as unknown as BaseAudioContext).audioWorklet.addModule(url),
259
+ createNode: (ctx) => {
260
+ const node = new AudioWorkletNode(ctx as unknown as BaseAudioContext, METER_PROCESSOR, {
261
+ // ONE output, and it is never connected — see the header. Declaring zero
262
+ // outputs would also work in Chrome, but "the output exists and goes
263
+ // nowhere" is the statement the rule is written as, and it is the shape
264
+ // every other analysis worklet in the wild takes.
265
+ numberOfOutputs: 1,
266
+ outputChannelCount: [1],
267
+ // TWO INPUT CHANNELS, ALWAYS, by the speakers rule: the engine upmixes a
268
+ // mono master to L = R and downmixes anything wider, so `process` sees
269
+ // exactly the stereo pair the meter draws. The default ("max") would
270
+ // hand a mono source over as one channel.
271
+ channelCount: METER_CHANNELS.length,
272
+ channelCountMode: "explicit",
273
+ channelInterpretation: "speakers",
274
+ });
275
+ return node as unknown as MeterNodeLike & MeterSourceLike;
276
+ },
277
+ moduleUrl: browserModuleUrl,
278
+ };
279
+
280
+ /** `addModule` is per CONTEXT and must not be repeated: a second compile of the
281
+ * same source is wasted work on the audio thread, and two attaches racing at
282
+ * the first play edge would both do it. Keyed on the context object, so a
283
+ * disposed context's promise is collected with it. */
284
+ const MODULES = new WeakMap<object, Promise<void>>();
285
+
286
+ function moduleFor(ctx: MeterContextLike, deps: MeterDeps): Promise<void> {
287
+ const existing = MODULES.get(ctx);
288
+ if (existing !== undefined) return existing;
289
+ const pending = deps.addModule(ctx, deps.moduleUrl());
290
+ MODULES.set(ctx, pending);
291
+ // A FAILED compile is dropped rather than latched, so the next mount tries
292
+ // again. Latching would silence every meter in the session over one transient
293
+ // failure, which is the posture the preview's buffer cache already rejects.
294
+ void pending.catch(() => {
295
+ if (MODULES.get(ctx) === pending) MODULES.delete(ctx);
296
+ });
297
+ return pending;
298
+ }
299
+
300
+ interface Tap {
301
+ key: string;
302
+ /** How many mounted meters want this node measured. A channel can be on
303
+ * screen in two places (a strip and a master bar) and neither may tear the
304
+ * other's tap down. */
305
+ refs: number;
306
+ node: (MeterNodeLike & MeterSourceLike) | null;
307
+ /** Set by the last `release`, read by the async bring-up: a mount that
308
+ * unmounted while `addModule` was in flight must not leave a node wired to
309
+ * a graph nobody is looking at. */
310
+ dead: boolean;
311
+ }
312
+
313
+ /** ONE tap per GainNode, and the map holds no strong reference to the graph. */
314
+ const TAPS = new WeakMap<MeterSourceLike, Tap>();
315
+
316
+ /** Meter nodes whose processor threw and is dead, until each is torn down:
317
+ * while any is, the worklet error stands in the stage health. */
318
+ const deadProcessors = new Set<object>();
319
+
320
+ /**
321
+ * Measure `source` under `key` until the returned function is called. Each
322
+ * stereo channel publishes under `meterChannelKey(key, "L" | "R")`.
323
+ *
324
+ * Refcounted, so two faces over one bus share one worklet. Idempotent per
325
+ * (node, key): a second attach under a DIFFERENT key on the same node is
326
+ * refused rather than silently re-pointed, because one node cannot honestly
327
+ * publish as two buses and the quiet version of that bug is a master bar
328
+ * showing a clip's level.
329
+ */
330
+ export function attachMeter(
331
+ key: string,
332
+ ctx: MeterContextLike,
333
+ source: MeterSourceLike,
334
+ deps: MeterDeps = browserDeps,
335
+ ): () => void {
336
+ const existing = TAPS.get(source);
337
+ if (existing !== undefined) {
338
+ if (existing.key !== key) {
339
+ console.error(
340
+ `[audio] meter: this node is already published as ${JSON.stringify(existing.key)}; ` +
341
+ `refusing to also publish it as ${JSON.stringify(key)}`,
342
+ );
343
+ return () => {};
344
+ }
345
+ existing.refs += 1;
346
+ return () => release(source, existing);
347
+ }
348
+ const tap: Tap = { key, refs: 1, node: null, dead: false };
349
+ TAPS.set(source, tap);
350
+ void moduleFor(ctx, deps)
351
+ .then(() => {
352
+ if (tap.dead) return;
353
+ const node = deps.createNode(ctx);
354
+ // A processor that throws is dead for good and says so only in the
355
+ // console: kept for the stage health an agent reads (runtimeErrors.ts),
356
+ // STANDING until this node is torn down (`release`).
357
+ node.onprocessorerror = (event) => {
358
+ const message = (event as { message?: unknown } | null)?.message;
359
+ deadProcessors.add(node);
360
+ recordRuntimeError(
361
+ "audio-worklet",
362
+ `the level meter's audio worklet processor threw and stopped${typeof message === "string" && message !== "" ? `: ${message}` : " (the browser console has the error)"}`,
363
+ true,
364
+ );
365
+ };
366
+ // Spelled once per tap, not per post: the handler runs at the post rate.
367
+ const channels = METER_CHANNELS.map((channel) => meterChannelKey(key, channel));
368
+ node.port.onmessage = (event) => {
369
+ const data = event.data;
370
+ if (!(data instanceof Float32Array) || data.length < METER_MESSAGE_LENGTH) return;
371
+ for (let c = 0; c < channels.length; c++) {
372
+ const at = c * METER_FIELDS;
373
+ // THE GATE BEFORE THE ALLOCATION, PER CHANNEL, which is the half
374
+ // `publishMeter` cannot do for us: by the time the store had the
375
+ // object, the object would exist. Same question, same numbers,
376
+ // asked against what the store is already holding, so a post where
377
+ // only L moved leaves R's reading (and R's face) alone. See
378
+ // `meterStore.ts`'s header for why a reading's identity is the
379
+ // whole contract.
380
+ if (!meterVisiblyDiffers(readMeter(channels[c]!), data[at]!, data[at + 1]!, data[at + 2]!, METER_FLOOR, METER_VISIBLE_RATIO)) {
381
+ continue;
382
+ }
383
+ publishMeter(channels[c]!, reading(data, at));
384
+ }
385
+ };
386
+ // The tap's ONE connection, and it is inbound only.
387
+ source.connect(node);
388
+ tap.node = node;
389
+ })
390
+ .catch((e: unknown) => {
391
+ // Decoration, not the mix: a meter that cannot be built must leave the
392
+ // audio alone and say so once, rather than failing the play edge.
393
+ console.error("[audio] meter tap unavailable", e);
394
+ });
395
+ return () => release(source, tap);
396
+ }
397
+
398
+ function reading(data: Float32Array, at: number): MeterReading {
399
+ return { rms: data[at]!, peak: data[at + 1]!, hold: data[at + 2]!, holdLeftMs: 0 };
400
+ }
401
+
402
+ /**
403
+ * The store key one channel of a tap publishes under: `"master:L"`.
404
+ *
405
+ * A TAP IS STILL ONE KEY (`attachMeter`'s refusal is about the node, and one
406
+ * node is one stereo bus), and the store is still one reading per key, so
407
+ * neither contract had to change shape to carry two channels. Each channel is
408
+ * its own entry, gated on its own, so a still side never re-renders.
409
+ */
410
+ export function meterChannelKey(key: string, channel: MeterChannel): string {
411
+ return `${key}:${channel}`;
412
+ }
413
+
414
+ function release(source: MeterSourceLike, tap: Tap): void {
415
+ if (tap.dead) return;
416
+ tap.refs -= 1;
417
+ if (tap.refs > 0) return;
418
+ tap.dead = true;
419
+ TAPS.delete(source);
420
+ const node = tap.node;
421
+ tap.node = null;
422
+ if (node !== null) {
423
+ node.port.onmessage = null;
424
+ node.onprocessorerror = null;
425
+ // THE SELECTIVE FORM. `source.disconnect()` with no argument would drop the
426
+ // monitored node's real output as well, which is the mix.
427
+ try {
428
+ source.disconnect(node);
429
+ } catch {
430
+ // Already torn down with its context; nothing left to unwire.
431
+ }
432
+ node.disconnect();
433
+ node.port.close?.();
434
+ if (deadProcessors.delete(node) && deadProcessors.size === 0) runtimeRealmRestarted("audio-worklet");
435
+ }
436
+ // The face keeps drawing whatever it last saw unless it is told, so the
437
+ // channel falls to silence rather than freezing at -6 dB over nothing.
438
+ for (const channel of METER_CHANNELS) clearMeter(meterChannelKey(tap.key, channel));
439
+ }
440
+
441
+ /**
442
+ * One channel's live reading, for a face.
443
+ *
444
+ * Subscribes to the lane-local store and NOT to the shot store or the hot
445
+ * channel — see `meterStore.ts`'s header for why neither may carry this.
446
+ */
447
+ export function useMeterReading(key: string): MeterReading {
448
+ const subscribe = useCallback((onChange: () => void) => subscribeMeter(key, onChange), [key]);
449
+ const get = useCallback(() => readMeter(key), [key]);
450
+ return useSyncExternalStore(subscribe, get, get);
451
+ }
452
+
453
+ /**
454
+ * Tap a node while this component is mounted AND no export is running.
455
+ *
456
+ * ── BOTH HALVES OF THAT GATE ARE LOAD-BEARING ───────────────────────────
457
+ *
458
+ * MOUNTED, because a tap is per-frame work on the audio thread for a bar
459
+ * nobody is looking at. The effect's cleanup is the detach, so closing the
460
+ * mixer stops the measuring rather than merely hiding it.
461
+ *
462
+ * NO EXPORT LOCK, because an export is the one time this chrome's audio graph
463
+ * is not the author's monitor. A BROWSER export renders through the visible
464
+ * stage and a SERVER render has the machine busy; adding a worklet to either is
465
+ * work competing with the render for the audio thread, and the numbers it would
466
+ * post describe a mix nobody is listening to. `exportLock` is the store's own
467
+ * word for both (state/types.ts), and reading it here is the only thing this
468
+ * lane asks of the shot store.
469
+ *
470
+ * `source` is null until the graph exists — the preview builds its context
471
+ * lazily on the first play edge, which is the user gesture — so a face mounts
472
+ * over silence and starts metering when the node arrives.
473
+ */
474
+ export function useMeterTap(
475
+ key: string,
476
+ ctx: MeterContextLike | null,
477
+ source: MeterSourceLike | null,
478
+ deps?: MeterDeps,
479
+ ): void {
480
+ const exporting = useShotEditor((s) => s.exportLock !== null);
481
+ useEffect(() => {
482
+ if (ctx === null || source === null || exporting) return;
483
+ return attachMeter(key, ctx, source, deps);
484
+ }, [key, ctx, source, exporting, deps]);
485
+ }