@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
@@ -30,13 +30,43 @@ import type { PointerEvent as ReactPointerEvent } from "react";
30
30
  import { frameToPx, startDrag } from "@dailies-studio/timeline";
31
31
  import type { Rational } from "@dailies-studio/engine";
32
32
  import type { MarkerRef } from "@dailies-studio/shot/doc";
33
- import { useShotEditor, useStore } from "../../state/context.js";
33
+ import { useShotEditor, useShotEditorHot, useStore } from "../../state/context.js";
34
34
  import { frameDelta, timecode } from "./model.js";
35
35
  import { isSnapSuppressed } from "./snap.js";
36
36
  import type { SnapPress } from "./snap.js";
37
+ import { formatCombo } from "../../state/shortcuts.js";
37
38
 
38
- export function MarkerLane(props: { ppf: number; labelWidth: number; beginSnap?: SnapPress }) {
39
- const { ppf, labelWidth, beginSnap } = props;
39
+ export function MarkerLane(props: {
40
+ ppf: number;
41
+ labelWidth: number;
42
+ beginSnap?: SnapPress;
43
+ /**
44
+ * The SHOT CONTEXT's window, or absent in the scene context.
45
+ *
46
+ * A marker's frame is a MASTER frame and stays one; this only RE-ZEROES the
47
+ * lane on the shot, exactly as every other row in the dock is re-zeroed.
48
+ *
49
+ * ── EVERY MARKER IS STILL RENDERED, AND CLIPPED BY THE TRACK BOX ────────
50
+ *
51
+ * Narrowing the list to the window was the obvious thing and it is wrong,
52
+ * because the pin is the thing being DRAGGED: `dragMarker` writes an
53
+ * unclamped master frame, so pulling a mark one frame past the shot's edge
54
+ * unmounted its own pin mid-gesture — the pointer capture died with the node,
55
+ * the drag was silently discarded, and the beat was left wherever the last
56
+ * committed move had put it. The same filter also made the snap guide point
57
+ * at a mark the lane refused to draw. Drawing them all and letting
58
+ * `.marker-track`'s clip hide what is outside costs a handful of spans and
59
+ * has neither failure.
60
+ *
61
+ * DRAWN AT ALL, unlike the component context which prunes both master lanes:
62
+ * a definition runs on a second clock with no master position, so its ruler
63
+ * cannot place a master frame. A shot IS a window of master time, and
64
+ * beat-synced authoring inside one is the whole point of the tool.
65
+ */
66
+ shotWindow?: { origin: number; len: number } | undefined;
67
+ }) {
68
+ const { ppf, labelWidth, beginSnap, shotWindow } = props;
69
+ const origin = shotWindow?.origin ?? 0;
40
70
  const store = useStore();
41
71
  const doc = useShotEditor((s) => s.doc);
42
72
  const selected = useShotEditor((s) => s.selectedMarker);
@@ -67,6 +97,7 @@ export function MarkerLane(props: { ppf: number; labelWidth: number; beginSnap?:
67
97
  marker={marker}
68
98
  selected={marker.id === selected}
69
99
  ppf={ppf}
100
+ origin={origin}
70
101
  fps={doc.fps}
71
102
  beginSnap={beginSnap}
72
103
  />
@@ -81,16 +112,23 @@ function MarkerPin(props: {
81
112
  marker: MarkerRef;
82
113
  selected: boolean;
83
114
  ppf: number;
115
+ /** The shot context's re-zero. DRAWING ONLY: the drag below is a signed
116
+ * delta on a master frame, which no offset touches. */
117
+ origin: number;
84
118
  fps: Rational;
85
119
  beginSnap: SnapPress | undefined;
86
120
  }) {
87
- const { marker, selected, ppf, fps, beginSnap } = props;
121
+ const { marker, selected, ppf, origin, fps, beginSnap } = props;
122
+ /** What the ruler above this lane is reading. Master time outside a shot,
123
+ * and the shot's own zero inside one — a tooltip that said 0:42 under a
124
+ * ruler reading 0:02 is two clocks in one panel. */
125
+ const shown = marker.frame - origin;
88
126
  const store = useStore();
89
127
  // Subscribed to the BOOLEAN, not the frame: useSyncExternalStore only
90
128
  // notifies when the selected value changes, so a pin re-renders exactly
91
129
  // twice per playhead pass-through instead of once per playback frame — and
92
130
  // the lane above it never re-renders at all.
93
- const atPlayhead = useShotEditor((s) => s.frame === marker.frame);
131
+ const atPlayhead = useShotEditorHot((s) => s.frame === marker.frame);
94
132
 
95
133
  const drag = (e: ReactPointerEvent<HTMLElement>): void => {
96
134
  e.preventDefault();
@@ -120,12 +158,12 @@ function MarkerPin(props: {
120
158
  <span
121
159
  className={`marker-pin${selected ? " is-selected" : ""}${atPlayhead ? " is-at-playhead" : ""}`}
122
160
  data-marker={marker.id}
123
- style={{ left: frameToPx(marker.frame, ppf) }}
161
+ style={{ left: frameToPx(shown, ppf) }}
124
162
  role="button"
125
163
  tabIndex={-1}
126
- aria-label={`${label} at ${timecode(marker.frame, fps)}`}
164
+ aria-label={`${label} at ${timecode(shown, fps)}`}
127
165
  aria-pressed={selected}
128
- title={`${label} · ${timecode(marker.frame, fps)} · drag to move it, ⌘drag to ignore snapping`}
166
+ title={formatCombo(`${label} · ${timecode(shown, fps)} · drag to move it, ⌘drag to ignore snapping`)}
129
167
  onPointerDown={drag}
130
168
  >
131
169
  {/* The diamond is a rotated square rather than an SVG: it is 8px of
@@ -0,0 +1,91 @@
1
+ // THE PLAY CONTROL, one for the dock (`dock-play`) and Code Studio's preview
2
+ // strip (`components-play`), so the two cannot disagree about what Play is
3
+ // doing. Each caller passes its own class; the sheets place the look.
4
+ //
5
+ // idle the Play glyph, "Play (Space)".
6
+ // pending a queued Play (`playIntent`): the cancel glyph, `aria-busy`,
7
+ // and after 300ms a ring around the glyph. The ring turns while
8
+ // the scene loads or the stage hands over, and counts the
9
+ // percent while a Play pass draws. The title says which.
10
+ // playing the Pause glyph, "Pause (Space)".
11
+ // refused a start `playHold` refuses (the board, an export lock): the
12
+ // Play glyph, disabled, with the refusal itself as its title, so
13
+ // it names which hold this is. `setPlaying` refuses on the same
14
+ // `playHold`, so the greyed control is the guard agreeing with
15
+ // itself, not a second rule.
16
+ //
17
+ // A press is `setPlaying(!playOn(state))`, as Space is, so a second press
18
+ // cancels a queued Play. Only a start is ever refused: a running transport
19
+ // and a queued Play can always be called off, whatever opens meanwhile.
20
+ //
21
+ // A leaf with its own subscriptions, so a play edge, and each percent of a
22
+ // Play pass, re-renders this button and not the dock around it.
23
+ import { IconClose, IconPause, IconPlay } from "@dailies-studio/ui";
24
+ import { useShotEditor, useStore } from "../../state/context.js";
25
+ import { SCENE_WAITING, playHold, playOn, preparingPercent, preparingTitle } from "../../state/playHold.js";
26
+
27
+ /** Every Play control, whoever places it: input aimed at one does not end a
28
+ * background preparing pass, which its click upgrades (`StagePanel`). */
29
+ export const PLAY_CONTROL = "[data-play-control]";
30
+
31
+ export function PlayButton(props: { className: string; testId?: string }) {
32
+ const store = useStore();
33
+ const playing = useShotEditor((s) => s.playing);
34
+ const intent = useShotEditor((s) => s.playIntent);
35
+ // The refusal and its wording. A string selector is compared by value, so
36
+ // this is as cold a subscription as a boolean.
37
+ const refusal = useShotEditor(playHold);
38
+ const pending = !playing && intent !== null;
39
+ const title = playing
40
+ ? "Pause (Space)"
41
+ : intent === null
42
+ ? (refusal ?? "Play (Space)")
43
+ : intent.phase === "preparing"
44
+ ? preparingTitle(intent)
45
+ : SCENE_WAITING;
46
+ return (
47
+ <button
48
+ type="button"
49
+ className={props.className}
50
+ data-testid={props.testId}
51
+ data-play-control=""
52
+ data-state={playing ? "playing" : pending ? "pending" : "idle"}
53
+ aria-label={playing ? "Pause" : pending ? "Cancel play" : "Play"}
54
+ aria-busy={pending ? true : undefined}
55
+ disabled={!playing && !pending && refusal !== null}
56
+ title={title}
57
+ // Read at the press, not from this render: the store may have moved on.
58
+ onClick={() => store.setPlaying(!playOn(store.getState()))}
59
+ >
60
+ {playing ? <IconPause /> : pending ? <IconClose /> : <IconPlay />}
61
+ {pending ? <PlayRing percent={intent.phase === "preparing" ? preparingPercent(intent) : null} /> : null}
62
+ </button>
63
+ );
64
+ }
65
+
66
+ /** The queued Play's ring: the font field's write-in-flight ring
67
+ * (`.font-field-ring`, inspector-object.css), so the product has one ring. A
68
+ * level while a Play pass counts; with nothing to count, a short arc that
69
+ * turns, or a still dashed ring under reduced motion. Each context's sheet
70
+ * lays it over the control and holds it back 300ms, so a start that is
71
+ * ready at once never flashes it. Drawn on a 36 grid: the dock's ring is
72
+ * 36px, so the ring's 1.5 stroke draws 1.5px there. */
73
+ function PlayRing(props: { percent: number | null }) {
74
+ const { percent } = props;
75
+ return (
76
+ <span className="play-ring" aria-hidden="true">
77
+ <svg className={`font-field-ring${percent === null ? " is-indeterminate" : ""}`} viewBox="0 0 36 36">
78
+ <circle className="font-field-ring-track" cx="18" cy="18" r="17" />
79
+ <circle
80
+ className="font-field-ring-level"
81
+ cx="18"
82
+ cy="18"
83
+ r="17"
84
+ pathLength={100}
85
+ {...(percent === null ? {} : { strokeDasharray: `${percent} 100` })}
86
+ transform="rotate(-90 18 18)"
87
+ />
88
+ </svg>
89
+ </span>
90
+ );
91
+ }
@@ -0,0 +1,254 @@
1
+ // "+ property": THE SECOND DOOR INTO KEYFRAMING.
2
+ //
3
+ // ── WHY IT EXISTS AT ALL ────────────────────────────────────────────────────
4
+ //
5
+ // A channel row appears once a property carries animation and not before, which
6
+ // is Lottie's rule (animated properties, not every property) and is right: a
7
+ // layer has thirty trackable paths and nobody wants thirty rows. But it left
8
+ // the inspector as the ONLY way to start one — an author who wanted to animate
9
+ // the stroke width had to know which section holds it and find the diamond in
10
+ // it. The timeline is where you go to animate something, so the timeline is
11
+ // where "animate something else" belongs too.
12
+ //
13
+ // ── THE LIST IS THE TABLE, AND SO ARE THE REFUSALS ──────────────────────────
14
+ //
15
+ // Every entry comes from `TRACKABLE_PATHS` / `SCENE_TRACKABLE_PATHS` — the same
16
+ // single source validate, the agent ops and the inspector diamond read — so a
17
+ // path this offers is a path the gate accepts, by construction rather than by
18
+ // two lists agreeing.
19
+ //
20
+ // A path this layer cannot carry is shown GREYED WITH ITS REASON rather than
21
+ // hidden. `layerTrackRefusal` already returns the sentence ("needs a stroke",
22
+ // "needs a solid fill"), and "why can I not animate the stroke width" is a
23
+ // question the author is entitled to have answered where they asked it. That is
24
+ // the one place this deliberately differs from `KeyframeDot`, which is ABSENT on
25
+ // a refused path: a diamond sits in a row the author is already looking at, and
26
+ // this is a list they opened to go looking.
27
+ import { Popover } from "@dailies-studio/ui";
28
+ import {
29
+ INSTANCE_PROP_PREFIX,
30
+ SCENE_TRACKABLE_PATHS,
31
+ TRACKABLE_PATHS,
32
+ contractTrackablePath,
33
+ layerSnapshotContract,
34
+ layerTrackRefusal,
35
+ pathAllowsKind,
36
+ sceneTrackRefusal,
37
+ } from "@dailies-studio/shot/doc";
38
+ import type { Layer3D, Scene3D } from "@dailies-studio/shot/doc";
39
+ import { controlLabel } from "@dailies-studio/contract";
40
+ import type { Contract } from "@dailies-studio/contract";
41
+ import { useMemo, useRef, useState } from "react";
42
+ import type { CSSProperties } from "react";
43
+ import { useShotEditor, useStore } from "../../state/context.js";
44
+
45
+ /** One offer. `refusal` non-null means the row is disabled and says why. */
46
+ interface Offer {
47
+ path: string;
48
+ label: string;
49
+ group: string;
50
+ tracked: boolean;
51
+ refusal: string | null;
52
+ }
53
+
54
+ /** The first path segment, title-cased, is the section an author already knows
55
+ * the property by: "transform.opacity" is under Transform, "paint.fill.color"
56
+ * under Paint. Derived rather than tabled, because a second column naming the
57
+ * group is a second thing to keep in step with the path. A one-segment path
58
+ * is a field of the layer itself (`blurPx`, schema v47), which the inspector
59
+ * edits in Appearance, so it groups there rather than under its own name. */
60
+ export function groupOf(path: string): string {
61
+ if (!path.includes(".")) return "Appearance";
62
+ const head = path.split(".")[0] ?? "";
63
+ return head.charAt(0).toUpperCase() + head.slice(1);
64
+ }
65
+
66
+ /**
67
+ * The array-indexed paths (`paint.shadows.<i>.blurPx`) are offered ONCE PER
68
+ * SHADOW the layer actually has. The table key carries the placeholder, so a
69
+ * literal offer would be a path that resolves to nothing and a keyframe that
70
+ * writes nowhere.
71
+ */
72
+ function expandIndexed(path: string, layer: Layer3D): string[] {
73
+ if (!path.includes(".<i>.")) return [path];
74
+ const shadows = "paint" in layer ? (layer.paint?.shadows ?? []) : [];
75
+ return shadows.map((_, i) => path.replace(".<i>.", `.${i}.`));
76
+ }
77
+
78
+ function layerOffers(layer: Layer3D, live: Contract | undefined): Offer[] {
79
+ const out: Offer[] = [];
80
+ for (const [key, spec] of Object.entries(TRACKABLE_PATHS)) {
81
+ // A path that is not a property of this KIND is not a refusal to explain,
82
+ // it is a property of something else. Those are the only ones left out.
83
+ if (!pathAllowsKind(spec, layer.kind)) continue;
84
+ for (const path of expandIndexed(key, layer)) {
85
+ out.push({
86
+ path,
87
+ label: spec.label,
88
+ group: groupOf(path),
89
+ tracked: layer.tracks?.[path] !== undefined,
90
+ refusal: layerTrackRefusal(layer, path),
91
+ });
92
+ }
93
+ }
94
+ out.push(...instanceOffers(layer, live));
95
+ return out;
96
+ }
97
+
98
+ /**
99
+ * An instance's props, from the CONTRACT rather than from the table — the one
100
+ * offer family whose list is a property of the component and not of this lane.
101
+ *
102
+ * Grouped under "Component" and not under `groupOf`'s "Instance": the inspector
103
+ * section that edits these props is titled Component, and two names for one
104
+ * group is what `groupOf`'s own comment is guarding against.
105
+ *
106
+ * A non-animatable spec (`slot`, `event`) is LEFT OUT rather than greyed. Every
107
+ * other refusal in this list is something the author can fix by adding a stroke
108
+ * or a shadow; "this prop is a slot" is a fact about the component's source, so
109
+ * a row explaining it would be a row that never becomes pressable.
110
+ */
111
+ function instanceOffers(layer: Layer3D, live: Contract | undefined): Offer[] {
112
+ if (layer.kind !== "instance") return [];
113
+ const contract = live ?? layerSnapshotContract(layer);
114
+ if (contract === null || contract === undefined) return [];
115
+ const out: Offer[] = [];
116
+ for (const spec of Object.values(contract.keys)) {
117
+ if (contractTrackablePath(spec) === null) continue;
118
+ const path = `${INSTANCE_PROP_PREFIX}${spec.key}`;
119
+ out.push({
120
+ path,
121
+ label: controlLabel(spec),
122
+ group: "Component",
123
+ tracked: layer.tracks?.[path] !== undefined,
124
+ refusal: layerTrackRefusal(layer, path, live),
125
+ });
126
+ }
127
+ return out;
128
+ }
129
+
130
+ function sceneOffers(scene: Scene3D): Offer[] {
131
+ return Object.entries(SCENE_TRACKABLE_PATHS).map(([path, spec]) => ({
132
+ path,
133
+ label: spec.label,
134
+ group: groupOf(path),
135
+ tracked: scene.tracks?.[path] !== undefined,
136
+ refusal: sceneTrackRefusal(scene, path),
137
+ }));
138
+ }
139
+
140
+ /**
141
+ * The button and its list. `subject` decides which table is read and which verb
142
+ * the pick routes through; everything else is one component because every pixel
143
+ * of it is shared — the `KeyframeDot`/`SceneKeyframeDot` split's own reasoning.
144
+ */
145
+ export function PropertyPicker({
146
+ subject,
147
+ containerId,
148
+ labelWidth,
149
+ depth,
150
+ status = null,
151
+ }: {
152
+ subject: { kind: "layer"; layer: Layer3D } | { kind: "shot"; scene: Scene3D };
153
+ containerId: string;
154
+ labelWidth: number;
155
+ depth: number;
156
+ /** The selected-key readout drawn beside the button, from the rows above
157
+ * (ChannelRow's `selectedKeyStatus`). Null draws nothing. */
158
+ status?: string | null;
159
+ }) {
160
+ const store = useStore();
161
+ const anchor = useRef<HTMLButtonElement>(null);
162
+ const [open, setOpen] = useState(false);
163
+ // The registry's contracts, for the instance-prop offers. `InstanceSection` is
164
+ // what loads one; until it has, the layer's snapshot answers.
165
+ const contracts = useShotEditor((s) => s.contracts);
166
+ const live =
167
+ subject.kind === "layer" && subject.layer.kind === "instance"
168
+ ? contracts[subject.layer.instance.component]
169
+ : undefined;
170
+
171
+ const offers = useMemo(
172
+ () => (subject.kind === "layer" ? layerOffers(subject.layer, live) : sceneOffers(subject.scene)),
173
+ [subject.kind, subject.kind === "layer" ? subject.layer : subject.scene, live],
174
+ );
175
+ const groups = useMemo(() => {
176
+ const byGroup = new Map<string, Offer[]>();
177
+ for (const offer of offers) {
178
+ const at = byGroup.get(offer.group);
179
+ if (at === undefined) byGroup.set(offer.group, [offer]);
180
+ else at.push(offer);
181
+ }
182
+ return [...byGroup.entries()];
183
+ }, [offers]);
184
+
185
+ const testId = subject.kind === "layer" ? subject.layer.id : containerId;
186
+
187
+ const pick = (offer: Offer): void => {
188
+ setOpen(false);
189
+ if (offer.refusal !== null) return;
190
+ // THE ROW'S SUBJECT, NOT THE SELECTION. Both verbs fan out over what is
191
+ // selected (a diamond in the inspector keys every selected layer), so the
192
+ // row makes its own layer the selection first — which is what clicking any
193
+ // row in this dock does anyway.
194
+ store.focusScene(containerId);
195
+ if (subject.kind === "layer") {
196
+ store.select([subject.layer.id]);
197
+ store.toggleKeyframe([offer.path]);
198
+ } else {
199
+ store.toggleSceneKeyframe([offer.path]);
200
+ }
201
+ };
202
+
203
+ return (
204
+ <div className="channel-row channel-add-row">
205
+ <div className="channel-label-cell" style={{ width: labelWidth, "--depth": depth } as CSSProperties}>
206
+ <button
207
+ ref={anchor}
208
+ type="button"
209
+ className="channel-add"
210
+ data-testid={`property-add-${testId}`}
211
+ aria-haspopup="true"
212
+ aria-expanded={open}
213
+ title="Animate another property: the first keyframe takes the value it already has, so nothing moves"
214
+ onClick={() => setOpen((was) => !was)}
215
+ >
216
+ + property
217
+ </button>
218
+ </div>
219
+ {/* In the LANE, not the label cell: the label column is one rail track
220
+ wide and the button already fills its share, while the lane has room
221
+ for "Opacity 0.42 · frame 24 · Ease Out". */}
222
+ <div className="channel-lane channel-add-status" data-testid={`property-add-status-${testId}`}>
223
+ {status}
224
+ </div>
225
+ <Popover open={open} anchor={anchor} onClose={() => setOpen(false)} className="property-pop">
226
+ {groups.map(([group, list]) => (
227
+ <div key={group} className="property-group">
228
+ <div className="property-group-label">{group}</div>
229
+ {list.map((offer) => (
230
+ <button
231
+ key={offer.path}
232
+ type="button"
233
+ className={`property-item${offer.tracked ? " is-tracked" : ""}`}
234
+ data-testid={`property-item-${offer.path}`}
235
+ disabled={offer.refusal !== null}
236
+ // The refusal IS the title on a disabled row, and the promise on
237
+ // an available one.
238
+ title={offer.refusal ?? `Keyframe ${offer.label} at the playhead`}
239
+ onClick={() => pick(offer)}
240
+ >
241
+ <span className="property-item-name">{offer.label}</span>
242
+ {offer.refusal !== null ? (
243
+ <span className="property-item-note">{offer.refusal.replace(/^"[^"]*"\s*/, "")}</span>
244
+ ) : offer.tracked ? (
245
+ <span className="property-item-note">animated</span>
246
+ ) : null}
247
+ </button>
248
+ ))}
249
+ </div>
250
+ ))}
251
+ </Popover>
252
+ </div>
253
+ );
254
+ }