@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
@@ -1,19 +1,45 @@
1
+ import { REDO_ICON, UNDO_ICON } from "../../icons.js";
1
2
  import { useShotEditor, useStore } from "../../state/context.js";
3
+ import { shortcutHint } from "../../state/shortcuts.js";
4
+ import { commandBlocker } from "../../state/workspace.js";
2
5
 
3
6
  /** Undo/redo. The enabled state comes from engine's history, never from a
4
7
  * count this chrome keeps — a second source of truth for "can I undo" is a
5
- * button that lies exactly when it matters. */
8
+ * button that lies exactly when it matters.
9
+ *
10
+ * THE HINTS COME OFF THE TABLE. They were literal "⌘Z"/"⌘⇧Z", which is wrong
11
+ * on every Windows machine the editor runs on — `formatCombo` spells those
12
+ * Ctrl+Z and Ctrl+Shift+Z. `shortcutFor` is the same lookup the menu
13
+ * accelerators use, so the tooltip and the menu row can no longer disagree,
14
+ * and a rebind moves both. */
6
15
  export function HistoryControls() {
7
16
  const store = useStore();
8
17
  const canUndo = useShotEditor((s) => s.canUndo);
9
18
  const canRedo = useShotEditor((s) => s.canRedo);
19
+ const blocked = useShotEditor((s) => commandBlocker(s, "edit.undo"));
20
+ // Under live sync undo is the author's own (Paper page 21, "Undo note").
21
+ const own = useShotEditor((s) => s.syncStatus !== null) ? " · Undo covers only your changes." : "";
10
22
  return (
11
23
  <div className="history-controls">
12
- <button type="button" disabled={!canUndo} title="Undo (⌘Z)" onClick={() => store.undo()}>
13
- ↶
24
+ <button
25
+ type="button"
26
+ className="btn is-icon"
27
+ aria-label="Undo"
28
+ disabled={!canUndo || blocked !== null}
29
+ title={blocked ?? `Undo${shortcutHint("edit.undo")}${own}`}
30
+ onClick={() => store.undo()}
31
+ >
32
+ {UNDO_ICON}
14
33
  </button>
15
- <button type="button" disabled={!canRedo} title="Redo (⌘⇧Z)" onClick={() => store.redo()}>
16
- ↷
34
+ <button
35
+ type="button"
36
+ className="btn is-icon"
37
+ aria-label="Redo"
38
+ disabled={!canRedo || blocked !== null}
39
+ title={blocked ?? `Redo${shortcutHint("edit.redo")}`}
40
+ onClick={() => store.redo()}
41
+ >
42
+ {REDO_ICON}
17
43
  </button>
18
44
  </div>
19
45
  );
@@ -0,0 +1,329 @@
1
+ // WHO ELSE IS HERE (lane R7, Paper page 21): one face per person in the
2
+ // Project's live session, in their peer colour, three faces and then "+N".
3
+ // Hovering a face says who and where; clicking one follows them (their Shot,
4
+ // frame and selection) until Escape, an edit of our own, or they leave.
5
+ //
6
+ // The Agent is a mint face while it runs. It is this tab's own agent, so it
7
+ // has no place to follow.
8
+ //
9
+ // People are live sync only: with sync off the presence store is not live and
10
+ // draws no faces. The local CLI is one user, so it never has a face to draw
11
+ // either. An MCP AGENT working on this Project (state/agentActivity.ts) shows
12
+ // on both hosts, sync or not: a mint face per agent, and a status chip naming
13
+ // its step ("Claude: Writing VolumetricClouds.tsx") with Follow when it is
14
+ // writing a file, which opens Code Studio following it. The view never moves
15
+ // on its own from here.
16
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
17
+ import type { ReactNode } from "react";
18
+ import { initialsOf, useToasts } from "@dailies-studio/ui";
19
+ import type { ToastHandle } from "@dailies-studio/ui";
20
+ import type { Doc3D } from "@dailies-studio/shot/doc";
21
+ import { useShotEditor, useStore } from "../../state/context.js";
22
+ import { AGENT_COLOR } from "../../state/presence.js";
23
+ import type { PresencePerson, PresenceState } from "../../state/presence.js";
24
+ import { shotNameOf, shotNumberOf } from "../../state/storyboardModel.js";
25
+ import { agentsIn } from "../../state/agentActivity.js";
26
+ import type { AgentWork } from "../../state/agentActivity.js";
27
+ import { AgentDots, useAgentActivity } from "../agent/AgentDots.js";
28
+
29
+ /** Faces before the "+N". */
30
+ export const PRESENCE_FACES = 3;
31
+
32
+ export function usePresence(): PresenceState {
33
+ const { presence } = useStore();
34
+ return useSyncExternalStore(presence.subscribe, presence.getState);
35
+ }
36
+
37
+ /** Where a person is, as the hover card says it: the Shot's number and name,
38
+ * and the first layer they hold. Null for a place this document lacks. */
39
+ export function placeOf(
40
+ doc: Doc3D | null,
41
+ person: Pick<PresencePerson, "scene" | "sel">,
42
+ ): { number: string; shot: string; layer: string | null } | null {
43
+ if (doc === null || person.scene === null) return null;
44
+ const scene = doc.scenesById[person.scene];
45
+ if (scene === undefined) return null;
46
+ const number = shotNumberOf(doc, person.scene) ?? String(doc.scenes.indexOf(person.scene) + 1).padStart(2, "0");
47
+ const first = person.sel.find((id) => scene.layersById[id] !== undefined);
48
+ return {
49
+ number,
50
+ shot: shotNameOf(doc, person.scene),
51
+ layer: first === undefined ? null : (scene.layersById[first]?.label ?? null),
52
+ };
53
+ }
54
+
55
+ const AGENT = "agent";
56
+
57
+ export function PresenceStack(): ReactNode {
58
+ const store = useStore();
59
+ const presence = usePresence();
60
+ const agentRunning = useShotEditor((s) => s.agentRunning);
61
+ const doc = useShotEditor((s) => s.doc);
62
+ const projectName = useShotEditor((s) => s.name);
63
+ const activity = useAgentActivity();
64
+ const mcpAgents = agentsIn(activity, projectName);
65
+ const [card, setCard] = useState<string | null>(null);
66
+
67
+ // Escape stops a follow, from anywhere in the editor.
68
+ const following = presence.following;
69
+ useEffect(() => {
70
+ if (following === null) return;
71
+ const onKey = (event: KeyboardEvent) => {
72
+ if (event.key !== "Escape") return;
73
+ store.presence.follow(null);
74
+ };
75
+ window.addEventListener("keydown", onKey);
76
+ return () => window.removeEventListener("keydown", onKey);
77
+ }, [following, store]);
78
+
79
+ // A follow that ended on its own says why, once.
80
+ const { toast, node } = useToasts();
81
+ const shown = useRef<ToastHandle | null>(null);
82
+ useEffect(() => {
83
+ if (presence.notice === null) return;
84
+ shown.current?.dismiss();
85
+ shown.current = toast({ message: presence.notice });
86
+ store.presence.dismissNotice();
87
+ }, [presence.notice, store, toast]);
88
+
89
+ const people = presence.live ? presence.people : [];
90
+ const agent = presence.live && agentRunning;
91
+ const total = people.length + (agent ? 1 : 0) + mcpAgents.length;
92
+ if (total === 0) return node;
93
+
94
+ const shownPeople = people.slice(0, Math.max(0, PRESENCE_FACES - (agent ? 1 : 0) - mcpAgents.length));
95
+ const more = people.length - shownPeople.length;
96
+ const followed = people.find((p) => p.user === following) ?? null;
97
+
98
+ return (
99
+ <>
100
+ {node}
101
+ {mcpAgents.length > 0 ? <AgentStatusChip agent={mcpAgents[mcpAgents.length - 1]!} /> : null}
102
+ <div className="presence-stack" data-testid="presence-stack" role="group" aria-label="People here">
103
+ {mcpAgents.map((a) => (
104
+ <span
105
+ key={a.sessionId}
106
+ className="presence-face presence-face-agent"
107
+ data-testid="presence-mcp-agent"
108
+ style={{ background: AGENT_COLOR }}
109
+ tabIndex={0}
110
+ aria-label={`${a.name}, ${a.step}`}
111
+ onPointerEnter={() => setCard(a.sessionId)}
112
+ onPointerLeave={() => setCard(null)}
113
+ onFocus={() => setCard(a.sessionId)}
114
+ onBlur={() => setCard(null)}
115
+ >
116
+ AI
117
+ {card === a.sessionId ? (
118
+ <span className="presence-card" role="tooltip">
119
+ <span className="presence-card-who">
120
+ <span className="presence-card-avatar" style={{ background: AGENT_COLOR }}>
121
+ AI
122
+ </span>
123
+ <span className="presence-card-name">
124
+ <span>{a.name}</span>
125
+ <span className="presence-card-role">Agent over MCP · {a.step}</span>
126
+ </span>
127
+ </span>
128
+ </span>
129
+ ) : null}
130
+ </span>
131
+ ))}
132
+ {agent ? (
133
+ <span
134
+ className="presence-face presence-face-agent"
135
+ data-testid="presence-agent"
136
+ style={{ background: AGENT_COLOR }}
137
+ tabIndex={0}
138
+ aria-label="Agent, working"
139
+ onPointerEnter={() => setCard(AGENT)}
140
+ onPointerLeave={() => setCard(null)}
141
+ onFocus={() => setCard(AGENT)}
142
+ onBlur={() => setCard(null)}
143
+ >
144
+ AI
145
+ {card === AGENT ? (
146
+ <span className="presence-card" role="tooltip">
147
+ <span className="presence-card-who">
148
+ <span className="presence-card-avatar" style={{ background: AGENT_COLOR }}>
149
+ AI
150
+ </span>
151
+ <span className="presence-card-name">
152
+ <span>Agent</span>
153
+ <span className="presence-card-role">Working in this tab</span>
154
+ </span>
155
+ </span>
156
+ </span>
157
+ ) : null}
158
+ </span>
159
+ ) : null}
160
+ {shownPeople.map((person) => {
161
+ const place = placeOf(doc, person);
162
+ const isFollowed = person.user === following;
163
+ return (
164
+ <button
165
+ key={person.user}
166
+ type="button"
167
+ className={`presence-face${isFollowed ? " presence-face-followed" : ""}`}
168
+ data-testid="presence-face"
169
+ data-user={person.user}
170
+ // The followed face is ringed in its own colour, outside the
171
+ // bar-coloured border every face has.
172
+ style={{
173
+ background: person.color,
174
+ ...(isFollowed ? { boxShadow: `0 0 0 2px ${person.color}` } : {}),
175
+ }}
176
+ aria-pressed={isFollowed}
177
+ aria-label={`${person.name}, ${isFollowed ? "following. Escape stops" : "click to follow"}`}
178
+ onClick={() => store.presence.follow(isFollowed ? null : person.user)}
179
+ onPointerEnter={() => setCard(person.user)}
180
+ onPointerLeave={() => setCard(null)}
181
+ onFocus={() => setCard(person.user)}
182
+ onBlur={() => setCard(null)}
183
+ >
184
+ {person.avatarUrl === null ? initialsOf(person.name) : <img src={person.avatarUrl} alt="" />}
185
+ {card === person.user ? (
186
+ <span className="presence-card" role="tooltip" data-testid="presence-card">
187
+ <span className="presence-card-who">
188
+ <span className="presence-card-avatar" style={{ background: person.color }}>
189
+ {person.avatarUrl === null ? initialsOf(person.name) : <img src={person.avatarUrl} alt="" />}
190
+ </span>
191
+ <span className="presence-card-name">
192
+ <span>{person.name}</span>
193
+ <span className="presence-card-role">
194
+ {person.role === "edit" ? "Editor" : "Viewer"}
195
+ {person.tabs > 1 ? ` · ${person.tabs} tabs` : ""}
196
+ </span>
197
+ </span>
198
+ </span>
199
+ {place === null ? null : (
200
+ <span className="presence-card-where">
201
+ <span className="presence-card-number">{place.number}</span>
202
+ <span className="presence-card-shot">{place.shot}</span>
203
+ {place.layer === null ? null : (
204
+ <>
205
+ <span className="presence-card-slash">/</span>
206
+ <span className="presence-card-swatch" style={{ borderColor: person.color }} />
207
+ <span className="presence-card-layer">{place.layer}</span>
208
+ </>
209
+ )}
210
+ </span>
211
+ )}
212
+ <span className="presence-card-foot">
213
+ <span>{isFollowed ? "Following" : "Click to follow"}</span>
214
+ <span>Esc stops</span>
215
+ </span>
216
+ </span>
217
+ ) : null}
218
+ </button>
219
+ );
220
+ })}
221
+ {more > 0 ? (
222
+ <span
223
+ className="presence-face presence-more"
224
+ data-testid="presence-more"
225
+ title={people
226
+ .slice(shownPeople.length)
227
+ .map((p) => p.name)
228
+ .join(", ")}
229
+ >
230
+ +{more}
231
+ </span>
232
+ ) : null}
233
+ </div>
234
+ {/* An MCP agent editing the Project as a whole (ops naming no layer):
235
+ the canvas cell's frame in the Agent's colour, the follow frame's
236
+ shape. Its layers, when it names some, are outlined on the stage
237
+ instead (setPeerSelections). */}
238
+ {followed === null && mcpAgents.some((a) => a.busy && a.tool === "shot_batch" && a.layers.length === 0) ? (
239
+ <div
240
+ className="presence-follow-frame is-agent"
241
+ data-testid="agent-frame"
242
+ aria-hidden="true"
243
+ />
244
+ ) : null}
245
+ {followed === null ? null : (
246
+ <>
247
+ <div
248
+ className="presence-follow-frame"
249
+ data-testid="presence-follow-frame"
250
+ aria-hidden="true"
251
+ style={{ borderColor: followed.color }}
252
+ />
253
+ <div className="presence-follow-pill" data-testid="presence-follow-pill" role="status">
254
+ <span className="presence-follow-dot" style={{ background: followed.color }} />
255
+ Following {followed.name} · Esc to stop
256
+ </div>
257
+ </>
258
+ )}
259
+ </>
260
+ );
261
+ }
262
+
263
+ /**
264
+ * WHAT THE AGENT IS DOING NOW, beside its face: its step, with dots while a
265
+ * call is in flight, and Follow while it is writing a file this tab is not
266
+ * following yet.
267
+ */
268
+ function AgentStatusChip({ agent }: { agent: AgentWork }): ReactNode {
269
+ const store = useStore();
270
+ const follow = useShotEditor((s) => s.codeFollow);
271
+ const question = useSyncExternalStore(store.agentQuestion.subscribe, store.agentQuestion.getState);
272
+ const docId = useShotEditor((s) => s.doc?.id ?? null);
273
+ const rel = agent.wroteFile;
274
+ // Asking, or waiting on the answer, with this Project's dialog closed:
275
+ // Answer opens it.
276
+ const offerAnswer =
277
+ (agent.tool === "ask_user" || agent.tool === "get_answer") &&
278
+ question.question?.status === "open" &&
279
+ question.question.projectId === docId &&
280
+ !question.open;
281
+ const offerFollow = !offerAnswer && rel !== null && rel !== follow;
282
+ return (
283
+ <span className="agent-status-chip" data-testid="agent-status-chip" role="status">
284
+ {agent.busy ? <AgentDots label={`${agent.name} is working`} /> : <span className="agent-status-dot" aria-hidden="true" />}
285
+ <span className="agent-status-text">
286
+ <span className="agent-status-name">{agent.name}:</span> {agent.step}
287
+ </span>
288
+ {offerAnswer ? (
289
+ <button
290
+ type="button"
291
+ className="agent-status-follow"
292
+ data-testid="agent-status-answer"
293
+ title="Open the agent's question"
294
+ onClick={() => store.agentQuestion.show()}
295
+ >
296
+ Answer
297
+ </button>
298
+ ) : null}
299
+ {offerFollow ? (
300
+ <button
301
+ type="button"
302
+ className="agent-status-follow"
303
+ data-testid="agent-status-follow"
304
+ title={`Open ${rel} in Code Studio and follow each write`}
305
+ onClick={() => void store.followAgentFile(rel)}
306
+ >
307
+ Follow
308
+ </button>
309
+ ) : null}
310
+ </span>
311
+ );
312
+ }
313
+
314
+ /** The people whose place matches `at`, as small dots in their colours after
315
+ * a row's label or a frame's caption (page 21: "Badge lands ●●"). Null with
316
+ * nobody there. */
317
+ export function PeerDots({ at }: { at: (person: PresencePerson) => boolean }): ReactNode {
318
+ const { live, people } = usePresence();
319
+ if (!live) return null;
320
+ const here = people.filter(at);
321
+ if (here.length === 0) return null;
322
+ return (
323
+ <span className="peer-dots" data-testid="peer-dots" title={here.map((p) => p.name).join(", ")}>
324
+ {here.map((p) => (
325
+ <span key={p.user} className="peer-dot" style={{ background: p.color }} />
326
+ ))}
327
+ </span>
328
+ );
329
+ }
@@ -0,0 +1,232 @@
1
+ // The editor has exactly one Project open. Project lifecycle lives at
2
+ // /projects/; this header only names the current one and offers the way back:
3
+ // `Projects › title › shot`, where the root crumb is a link that leaves through
4
+ // `leaveFor` like the app menu's row.
5
+ //
6
+ // ── AND IT NAMES THE LEVEL, NOT JUST THE PROJECT ─────────────────────────
7
+ //
8
+ // The chrome always knew which shot the author was in (`enteredScene`) and
9
+ // never said so up here. The trail that did name it lives in the left rail's
10
+ // head, which is gone the moment the rail is collapsed, and the only other way
11
+ // out was a chip in the timeline dock's head, which is gone with the dock.
12
+ // Collapse both and there was no route back to the project level anywhere on
13
+ // screen. The header never collapses, so the crumb belongs here.
14
+ //
15
+ // A component is not a level of this trail: it opens in the Components
16
+ // workspace, which the workspace switch names, and its canvas bar and left
17
+ // column carry the way back to All components.
18
+ //
19
+ // The crumb is a SWITCHER and not only a back button: "which shot am I in" and
20
+ // "take me to another one" are the same question at this level, and a menu
21
+ // costs the same one click a plain back button would have.
22
+ import { useEffect, useRef, useState, type CSSProperties } from "react";
23
+ import { Popover } from "@dailies-studio/ui";
24
+ import type { Doc3D } from "@dailies-studio/shot/doc";
25
+ import { CHEVRON_DOWN_ICON, CHEVRON_RIGHT_ICON, SCENE_ICON } from "../../icons.js";
26
+ import { useShotEditor, useStore } from "../../state/context.js";
27
+ import { commandBlocker } from "../../state/workspace.js";
28
+ import { leaveFor } from "./leave.js";
29
+
30
+ /** The rows the switcher shows: one per shot, in document order.
31
+ *
32
+ * A free function taking the document rather than a selector, because it
33
+ * ALLOCATES. See the subscription note below: anything this component
34
+ * subscribes to is compared with `Object.is` on every store notification, so a
35
+ * selector shaped like this one would re-render the whole header on each one. */
36
+ function sceneRows(doc: Doc3D | null): ReadonlyArray<{ id: string; name: string }> {
37
+ if (doc === null) return [];
38
+ return doc.scenes.map((id) => ({ id, name: doc.scenesById[id]?.name ?? id }));
39
+ }
40
+
41
+ const NO_ROWS: ReadonlyArray<{ id: string; name: string }> = [];
42
+
43
+ /** THE LABEL OWNS THE CLIP, not the button.
44
+ *
45
+ * `.project-crumb` is an `inline-flex` with `max-width`, so a bare text child
46
+ * becomes an anonymous flex item. `text-overflow` is not inherited and applies
47
+ * to a block container's own inline content, so it never reaches that item:
48
+ * the button clipped a long shot name mid-glyph instead of ellipsizing it.
49
+ * A real element with `min-width: 0` is a flex item that can shrink, and it
50
+ * carries the ellipsis itself.
51
+ *
52
+ * Inline rather than a class because `styles.css` belongs to another lane this
53
+ * round; move it to `.project-crumb-label` whenever that file is next open. */
54
+ const CRUMB_LABEL: CSSProperties = { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" };
55
+
56
+ export function ProjectTitle() {
57
+ const store = useStore();
58
+ const docId = useShotEditor((state) => state.doc?.id ?? null);
59
+ const title = useShotEditor((state) => state.doc?.title ?? state.name ?? "Untitled Project");
60
+ const open = useShotEditor((state) => state.doc !== null);
61
+ // ── SUBSCRIBE TO STRINGS, NEVER TO `doc` ────────────────────────────────
62
+ //
63
+ // This sits in the header, which must survive playback: `useShotEditor`
64
+ // compares with `Object.is` per notification, so a selector returning a
65
+ // computed object or array re-renders every control up here on each one, and
66
+ // a `doc` subscription re-renders them on each keystroke of any edit. Both
67
+ // labels are resolved INSIDE the selector, so what crosses the boundary is a
68
+ // string that changes only when the words change.
69
+ const enteredScene = useShotEditor((state) => state.enteredScene);
70
+ const shotLabel = useShotEditor((state) =>
71
+ state.enteredScene === null
72
+ ? "All shots"
73
+ : (state.doc?.scenesById[state.enteredScene]?.name ?? "Shot"),
74
+ );
75
+ const [draft, setDraft] = useState(title);
76
+ const [menuOpen, setMenuOpen] = useState(false);
77
+ const anchor = useRef<HTMLButtonElement>(null);
78
+ const cancel = useRef(false);
79
+
80
+ useEffect(() => setDraft(title), [docId, title]);
81
+
82
+ const commit = (): void => {
83
+ if (cancel.current) {
84
+ cancel.current = false;
85
+ setDraft(title);
86
+ return;
87
+ }
88
+ store.setProjectTitle(draft);
89
+ const committed = store.getState().doc?.title ?? store.getState().name ?? "Untitled Project";
90
+ if (committed !== draft) setDraft(committed);
91
+ };
92
+
93
+ // THE LIST IS READ WHEN THE MENU OPENS, not subscribed to. `doc.scenesById`
94
+ // is a fresh object after any edit inside any shot, so subscribing to it
95
+ // would cost the header a render per keystroke of a layer rename, for a list
96
+ // nobody is looking at.
97
+ //
98
+ // THE IDS ARE SUBSCRIBED THOUGH, as one joined string, because the menu can
99
+ // outlive the list it drew. `Popover` intercepts Escape only, so ⌘Z reaches
100
+ // the keymap (`state/keymap.ts`) and an undo can delete a shot with the menu
101
+ // still open; none of the four labels above change when some OTHER shot
102
+ // appears or disappears, so nothing would re-render and the row would stay
103
+ // clickable, reaching `enterSceneAt`'s `scene === undefined` refusal as a
104
+ // dead click. A string still crosses the boundary, so the `Object.is` rule
105
+ // holds and the header re-renders only when the shot list itself changes.
106
+ // A rename while the menu is open still shows the old word until the next
107
+ // render, which is cosmetic and does not make the row lie about where it goes.
108
+ // Joined on a comma, which no generated shot id carries (`sc_<slug>`), so two
109
+ // different lists cannot produce the same key.
110
+ const sceneKey = useShotEditor((state) => state.doc?.scenes.join(",") ?? "");
111
+ const switchBlocked = useShotEditor((state) => commandBlocker(state, "ctx.shot"));
112
+ // The Project level, refused on the board: the row says why, as the app
113
+ // menu's does, rather than closing on a leave that never happens.
114
+ const allBlocked = useShotEditor((state) => commandBlocker(state, "ctx.scene"));
115
+ const rows = menuOpen && sceneKey !== "" ? sceneRows(store.getState().doc) : NO_ROWS;
116
+
117
+ /** The same triple the rail's back crumb runs, in the same order: leave the
118
+ * definition and any isolation before leaving the shot, because each of
119
+ * those is scoped by the one after it. */
120
+ const allShots = (): void => {
121
+ store.exitIsolationAll();
122
+ store.editComponent(null);
123
+ store.leaveScene();
124
+ setMenuOpen(false);
125
+ };
126
+
127
+ return (
128
+ <div className="project-identity">
129
+ {/* THE ROOT CRUMB IS BACK (Paper redesign, 2026-10-04): Projects ›
130
+ Project › Shot, the trail the design system's breadcrumb draws. A
131
+ real link, so a middle click opens the list in a new tab; a plain
132
+ click goes through `leaveFor`, which flushes autosave first and
133
+ stays put when the save fails. */}
134
+ <a
135
+ className="project-home"
136
+ href="/projects/"
137
+ onClick={(event) => {
138
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
139
+ event.preventDefault();
140
+ leaveFor(store, "/projects/");
141
+ }}
142
+ >
143
+ Projects
144
+ </a>
145
+ <span className="project-crumb-sep" aria-hidden="true">
146
+ {CHEVRON_RIGHT_ICON}
147
+ </span>
148
+ <input
149
+ className="project-title-input"
150
+ aria-label="Project title"
151
+ value={draft}
152
+ disabled={!open}
153
+ maxLength={80}
154
+ onChange={(event) => setDraft(event.target.value)}
155
+ onBlur={commit}
156
+ onKeyDown={(event) => {
157
+ if (event.key === "Enter") event.currentTarget.blur();
158
+ if (event.key === "Escape") {
159
+ cancel.current = true;
160
+ setDraft(title);
161
+ event.currentTarget.blur();
162
+ }
163
+ }}
164
+ />
165
+ {!open ? null : (
166
+ <>
167
+ <span className="project-crumb-sep" aria-hidden="true">
168
+ {CHEVRON_RIGHT_ICON}
169
+ </span>
170
+ <button
171
+ ref={anchor}
172
+ type="button"
173
+ className="btn project-crumb is-current"
174
+ data-testid="header-shot-crumb"
175
+ aria-haspopup="menu"
176
+ aria-expanded={menuOpen}
177
+ // A mode shows one object or one component: jumping to a shot
178
+ // from inside it would leave the mode showing the wrong thing.
179
+ disabled={switchBlocked !== null}
180
+ title={switchBlocked ?? "Switch shot"}
181
+ onClick={() => setMenuOpen((was) => !was)}
182
+ >
183
+ <span style={CRUMB_LABEL}>{shotLabel}</span>
184
+ {/* AN SVG, NOT "▾". Every glyph button in this header draws a real
185
+ icon, and header.test.tsx holds that line by asserting the
186
+ toggle group's textContent is empty. A text arrow here would be
187
+ the one character in the bar that reads as content. */}
188
+ {CHEVRON_DOWN_ICON}
189
+ </button>
190
+ <Popover open={menuOpen} anchor={anchor} onClose={() => setMenuOpen(false)}>
191
+ <div className="project-crumb-menu" role="menu">
192
+ <button
193
+ type="button"
194
+ role="menuitem"
195
+ className={`project-crumb-item${enteredScene === null ? " is-current" : ""}`}
196
+ disabled={allBlocked !== null}
197
+ title={allBlocked ?? undefined}
198
+ onClick={allShots}
199
+ >
200
+ All shots
201
+ </button>
202
+ {rows.map((row) => (
203
+ <button
204
+ key={row.id}
205
+ type="button"
206
+ role="menuitem"
207
+ className={`project-crumb-item${row.id === enteredScene ? " is-current" : ""}`}
208
+ onClick={() => {
209
+ // LEAVE THE DEFINITION FIRST, the order `allShots` above
210
+ // and the rail's back crumb both use. `editComponentAt`
211
+ // never writes `enteredScene` and `enterSceneAt` never
212
+ // writes `editingDef`, so the two coexist: without this,
213
+ // picking the shot you are already in is swallowed by
214
+ // `enterSceneAt`'s `sceneId === state.enteredScene` early
215
+ // return and picking another one swaps the shot while the
216
+ // stage keeps rendering the definition.
217
+ store.editComponent(null);
218
+ store.enterScene(row.id);
219
+ setMenuOpen(false);
220
+ }}
221
+ >
222
+ {SCENE_ICON}
223
+ {row.name}
224
+ </button>
225
+ ))}
226
+ </div>
227
+ </Popover>
228
+ </>
229
+ )}
230
+ </div>
231
+ );
232
+ }
@@ -0,0 +1,59 @@
1
+ // "Share" in the top bar, beside Export. HOSTED ONLY: the local CLI has no
2
+ // accounts to share between, so `isCloudWorkspace()` keeps it from asking.
3
+ //
4
+ // The editor keeps no rule about who may share. It asks `/auth/me?project=`
5
+ // (worker/src/auth.ts), which answers this Project's actions from the same
6
+ // grants the dashboard list reads, and draws the button only when they hold
7
+ // `project.share`.
8
+ //
9
+ // The access dialog lives in the dashboard (Members.tsx `ProjectAccessDialog`),
10
+ // so the button opens it there, in a new tab so the editor stays open. `ws`
11
+ // lets the dashboard tell when its session sits in another workspace, rather
12
+ // than opening a same-named project there.
13
+ import { useEffect, useState } from "react";
14
+ import { IconShare } from "@dailies-studio/ui";
15
+ import { useShotEditor } from "../../state/context.js";
16
+ import { isCloudWorkspace } from "../../state/posterCapture.js";
17
+
18
+ interface MeProject {
19
+ signedIn?: boolean;
20
+ workspace?: { id?: string } | null;
21
+ projectActions?: string[];
22
+ }
23
+
24
+ export function ShareButton() {
25
+ const name = useShotEditor((s) => s.name);
26
+ const [workspace, setWorkspace] = useState<string | null>(null);
27
+ useEffect(() => {
28
+ setWorkspace(null);
29
+ if (name === null || !isCloudWorkspace()) return;
30
+ let live = true;
31
+ // A failed probe draws nothing: the dashboard's Share stays the way in.
32
+ void fetch(`/auth/me?project=${encodeURIComponent(`shot/${name}`)}`, { credentials: "same-origin" })
33
+ .then((res) => (res.ok ? (res.json() as Promise<MeProject>) : null))
34
+ .then((me) => {
35
+ const id = me?.workspace?.id;
36
+ if (live && me?.signedIn === true && typeof id === "string" && me.projectActions?.includes("project.share") === true) {
37
+ setWorkspace(id);
38
+ }
39
+ })
40
+ .catch(() => {});
41
+ return () => {
42
+ live = false;
43
+ };
44
+ }, [name]);
45
+ if (name === null || workspace === null) return null;
46
+ const href = `/projects/?${new URLSearchParams({ share: `shot/${name}`, ws: workspace }).toString()}`;
47
+ return (
48
+ <button
49
+ type="button"
50
+ className="btn header-share"
51
+ aria-label="Share"
52
+ title="Share this project"
53
+ onClick={() => window.open(href, "_blank", "noopener")}
54
+ >
55
+ <IconShare aria-hidden="true" />
56
+ Share
57
+ </button>
58
+ );
59
+ }