@dailies-studio/shot-editor 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -0,0 +1,346 @@
1
+ // ONE THREAD (Paper page 20, "Thread"; S5): where it is, Resolve, the
2
+ // assignee, the other place's notes about it, its messages, "Ask agent to
3
+ // fix", and the reply composer. The Comments panel draws every thread inline
4
+ // (a card, a Studio spot, a Frame pin from the old board frame, a removed
5
+ // one).
6
+ import { useState } from "react";
7
+ import type { MouseEvent } from "react";
8
+ import { useContextMenu } from "@dailies-studio/ui";
9
+ import type { MenuEntry } from "@dailies-studio/ui";
10
+ import * as api from "../../api.js";
11
+ import type { CommentMessage as Message, CommentThread as Thread } from "../../api.js";
12
+ import { useShotEditor, useStore } from "../../state/context.js";
13
+ import {
14
+ commentPrompt,
15
+ notesAbout,
16
+ orphanOf,
17
+ personLabel,
18
+ placeOfKind,
19
+ plainText,
20
+ threadWhere,
21
+ } from "../../state/comments.js";
22
+ import type { Doc3D } from "@dailies-studio/shot/doc";
23
+ import { agentInEditor } from "../agent/agentGate.js";
24
+ import { CommentComposer } from "./CommentComposer.js";
25
+ import { CHEVRON_ICON, CommentBody, PersonAvatar, RESOLVE_ICON, shortAgo, sparkle, useComments } from "./parts.js";
26
+
27
+ export function CommentThread(props: { className?: string }) {
28
+ const store = useStore();
29
+ const { comments } = store;
30
+ const { detail, names, threads } = useComments();
31
+ const doc = useShotEditor((s) => s.doc);
32
+ const readOnly = useShotEditor((s) => s.readOnly);
33
+ const { open: openMenu, menu } = useContextMenu();
34
+ const [refusal, setRefusal] = useState<string | null>(null);
35
+ if (detail === null || doc === null) return null;
36
+ const thread = detail;
37
+ const where = threadWhere(doc, thread.anchor);
38
+ const orphan = orphanOf(doc, thread.anchor);
39
+ const resolved = thread.status === "resolved";
40
+ const here = placeOfKind(thread.anchor.kind);
41
+ const notes = notesAbout(doc, threads, thread);
42
+ const linked = new Set(thread.links.map((l) => l.threadId));
43
+ // "Link to Studio" / "Link to board" (S5): the other place's open threads,
44
+ // and any already linked, checked.
45
+ const candidates = threads.filter(
46
+ (t) => placeOfKind(t.anchor.kind) !== here && (t.status === "open" || linked.has(t.id)),
47
+ );
48
+ const linkMenu: MenuEntry = {
49
+ kind: "submenu",
50
+ label: here === "board" ? "Link to Studio" : "Link to board",
51
+ disabled: !thread.can.reply || candidates.length === 0,
52
+ title:
53
+ candidates.length === 0
54
+ ? here === "board"
55
+ ? "No Studio comments to link"
56
+ : "No board comments to link"
57
+ : undefined,
58
+ items: candidates.map(
59
+ (t): MenuEntry => ({
60
+ kind: "action",
61
+ id: t.id,
62
+ label: noteLabel(doc, t, names),
63
+ checked: linked.has(t.id),
64
+ onSelect: () => say(linked.has(t.id) ? comments.unlink(thread.id, t.id) : comments.link(thread.id, t.id)),
65
+ }),
66
+ ),
67
+ };
68
+
69
+ const say = (run: Promise<string | null>): void => {
70
+ void run.then(setRefusal);
71
+ };
72
+ const menuAt = (e: MouseEvent<HTMLElement>, items: MenuEntry[]): void => {
73
+ const r = e.currentTarget.getBoundingClientRect();
74
+ openMenu({ x: r.left, y: r.bottom + 4 }, items);
75
+ };
76
+ const assignMenu = (e: MouseEvent<HTMLElement>): void => {
77
+ const at = e.currentTarget.getBoundingClientRect();
78
+ void comments.mentionable("").then(
79
+ (res) =>
80
+ openMenu({ x: at.left, y: at.bottom + 4 }, [
81
+ ...res.people.map(
82
+ (p): MenuEntry => ({
83
+ kind: "action",
84
+ id: p.id,
85
+ label: personLabel(p),
86
+ checked: thread.assignee?.id === p.id,
87
+ onSelect: () => say(comments.assign(thread.id, p.id)),
88
+ }),
89
+ ),
90
+ { kind: "separator" },
91
+ {
92
+ kind: "action",
93
+ id: "unassign",
94
+ label: "Unassign",
95
+ disabled: thread.assignee === null,
96
+ onSelect: () => say(comments.assign(thread.id, null)),
97
+ },
98
+ ]),
99
+ (err: unknown) => setRefusal(err instanceof Error ? err.message : String(err)),
100
+ );
101
+ };
102
+
103
+ return (
104
+ <div
105
+ className={`comment-thread${props.className === undefined ? "" : ` ${props.className}`}`}
106
+ role="dialog"
107
+ aria-label={`Comment, ${where.time === null ? "" : `${where.time} · `}${where.text}`}
108
+ data-testid="comment-thread"
109
+ data-kind={thread.anchor.kind}
110
+ >
111
+ <div className="comment-thread-head">
112
+ {where.time === null ? null : (
113
+ <>
114
+ <span className="comment-thread-time">{where.time}</span>
115
+ <span className="comment-thread-dot" aria-hidden="true">
116
+ ·
117
+ </span>
118
+ </>
119
+ )}
120
+ <span className="comment-thread-where">{where.text}</span>
121
+ {thread.can.resolve ? (
122
+ <button
123
+ type="button"
124
+ className="comment-thread-resolve"
125
+ data-testid="comment-resolve"
126
+ onClick={() => say(comments.setStatus(thread.id, resolved ? "open" : "resolved"))}
127
+ >
128
+ {RESOLVE_ICON}
129
+ {resolved ? "Reopen" : "Resolve"}
130
+ </button>
131
+ ) : null}
132
+ <button
133
+ type="button"
134
+ className="comment-thread-more"
135
+ aria-label="More"
136
+ onClick={(e) =>
137
+ menuAt(e, [
138
+ {
139
+ kind: "action",
140
+ id: "close",
141
+ label: "Close",
142
+ shortcut: "Esc",
143
+ onSelect: () => comments.close(),
144
+ },
145
+ linkMenu,
146
+ { kind: "separator" },
147
+ {
148
+ kind: "action",
149
+ id: "delete",
150
+ label: "Delete thread",
151
+ danger: true,
152
+ disabled: !thread.can.delete,
153
+ title: thread.can.delete ? undefined : "Only its author or a Project editor can delete a thread",
154
+ onSelect: () => say(comments.remove(thread.id)),
155
+ },
156
+ ])
157
+ }
158
+ >
159
+ ···
160
+ </button>
161
+ </div>
162
+ <div className="comment-thread-meta">
163
+ <span className="comment-thread-meta-label">Assignee</span>
164
+ <button
165
+ type="button"
166
+ className="comment-assignee"
167
+ data-testid="comment-assignee"
168
+ disabled={!thread.can.assign}
169
+ onClick={assignMenu}
170
+ >
171
+ {thread.assignee === null ? (
172
+ <span className="comment-assignee-none">Unassigned</span>
173
+ ) : (
174
+ <>
175
+ <PersonAvatar userId={thread.assignee.id} label={personLabel(thread.assignee)} size={18} />
176
+ <span>{personLabel(thread.assignee)}</span>
177
+ </>
178
+ )}
179
+ {thread.can.assign ? CHEVRON_ICON : null}
180
+ </button>
181
+ <span className="comment-thread-spacer" />
182
+ <span className={`comment-status${resolved ? " is-resolved" : ""}`}>
183
+ <span className="comment-status-dot" aria-hidden="true" />
184
+ {resolved ? "Resolved" : "Open"}
185
+ </span>
186
+ </div>
187
+ {orphan !== null && thread.snapshotKey !== null ? (
188
+ <img className="comment-thread-snapshot" src={api.snapshotUrl(thread.id)} alt={`${where.text}, as it was`} />
189
+ ) : null}
190
+ {notes.map((note) => (
191
+ <NoteStrip key={note.id} doc={doc} note={note} names={names} onOpen={() => void store.openThread(note.id)} />
192
+ ))}
193
+ <div className="comment-thread-messages">
194
+ {(thread.messages ?? (thread.head === null ? [] : [thread.head])).map((m) => (
195
+ <CommentMessage key={m.id} message={m} names={names} />
196
+ ))}
197
+ </div>
198
+ {refusal === null ? null : (
199
+ <p className="comment-refusal" role="alert">
200
+ {refusal}
201
+ </p>
202
+ )}
203
+ {readOnly || resolved || !agentInEditor() ? null : (
204
+ <div className="comment-thread-agent">
205
+ <button
206
+ type="button"
207
+ className="comment-agent-button"
208
+ data-testid="comment-ask-agent"
209
+ onClick={() => store.openAgentWith(commentPrompt(doc, [thread]))}
210
+ >
211
+ {sparkle(13)}
212
+ Ask agent to fix
213
+ </button>
214
+ </div>
215
+ )}
216
+ {thread.can.reply ? (
217
+ <CommentComposer key={thread.id} threadKey={thread.id} variant="reply" onCancel={() => comments.close()} />
218
+ ) : null}
219
+ {menu}
220
+ </div>
221
+ );
222
+ }
223
+
224
+ /** A thread from the other place, in a menu: where it sits and its words. */
225
+ function noteLabel(doc: Doc3D, t: Thread, names: Readonly<Record<string, string>>): string {
226
+ const where = threadWhere(doc, t.anchor);
227
+ const words = plainText(t.head?.body ?? "", names).slice(0, 40) || "Comment";
228
+ return `${where.time === null ? "" : `${where.time} `}${where.text}: ${words}`;
229
+ }
230
+
231
+ /** S5's note strip: a thread from the other place about this one. On the
232
+ * board, "Studio note" with its moment and "Open in Studio"; in the Studio,
233
+ * "Board note" and "Open on board". */
234
+ function NoteStrip(props: { doc: Doc3D; note: Thread; names: Readonly<Record<string, string>>; onOpen: () => void }) {
235
+ const { note } = props;
236
+ const studio = note.anchor.kind === "studio";
237
+ const where = threadWhere(props.doc, note.anchor);
238
+ const words = plainText(note.head?.body ?? "", props.names).slice(0, 80);
239
+ return (
240
+ <div className="comment-note" data-testid={studio ? "comment-studio-note" : "comment-board-note"}>
241
+ {where.time === null ? null : <span className="comment-note-time">{where.time}</span>}
242
+ <span className="comment-note-text">
243
+ {studio ? "Studio note" : "Board note"}: “{words || "Comment"}”
244
+ </span>
245
+ <button type="button" className="comment-note-open" onClick={props.onOpen}>
246
+ {studio ? "Open in Studio" : "Open on board"}
247
+ </button>
248
+ </div>
249
+ );
250
+ }
251
+
252
+ /** One message: who, when, the words with their mention chips, and the
253
+ * marker when an agent wrote it on someone's behalf. Its author edits or
254
+ * deletes it from the "···" on hover. */
255
+ export function CommentMessage(props: { message: Message; names: Readonly<Record<string, string>> }) {
256
+ const { message: m } = props;
257
+ const { comments } = useStore();
258
+ const { open: openMenu, menu } = useContextMenu();
259
+ const [editing, setEditing] = useState<string | null>(null);
260
+ const [refusal, setRefusal] = useState<string | null>(null);
261
+ const save = async (): Promise<void> => {
262
+ if (editing === null) return;
263
+ const said = await comments.editMessage(m.threadId, m.id, editing);
264
+ setRefusal(said);
265
+ if (said === null) setEditing(null);
266
+ };
267
+ return (
268
+ <div className="comment-message" data-testid="comment-message">
269
+ <PersonAvatar userId={m.authorId} label={m.authorLabel} size={24} />
270
+ <div className="comment-message-main">
271
+ <div className="comment-message-head">
272
+ <span className="comment-message-name">{m.authorLabel}</span>
273
+ <span className="comment-message-time" title={new Date(m.createdAt).toLocaleString()}>
274
+ {shortAgo(m.createdAt)}
275
+ {m.editedAt === null ? "" : " · edited"}
276
+ </span>
277
+ {m.viaAgent ? (
278
+ <span className="comment-via-agent" data-testid="comment-via-agent" title="An agent wrote this for them">
279
+ {sparkle(9)}
280
+ via agent
281
+ </span>
282
+ ) : null}
283
+ {(m.can.edit || m.can.delete) && !m.deleted && editing === null ? (
284
+ <button
285
+ type="button"
286
+ className="comment-message-more"
287
+ aria-label="Message actions"
288
+ onClick={(e) => {
289
+ const r = e.currentTarget.getBoundingClientRect();
290
+ openMenu({ x: r.left, y: r.bottom + 4 }, [
291
+ { kind: "action", id: "edit", label: "Edit", disabled: !m.can.edit, onSelect: () => setEditing(m.body) },
292
+ {
293
+ kind: "action",
294
+ id: "delete",
295
+ label: "Delete",
296
+ danger: true,
297
+ disabled: !m.can.delete,
298
+ onSelect: () => void comments.deleteMessage(m.threadId, m.id).then(setRefusal),
299
+ },
300
+ ]);
301
+ }}
302
+ >
303
+ ···
304
+ </button>
305
+ ) : null}
306
+ </div>
307
+ {m.deleted ? (
308
+ <p className="comment-message-body is-deleted">Message deleted</p>
309
+ ) : editing === null ? (
310
+ <CommentBody body={m.body} names={props.names} className="comment-message-body" />
311
+ ) : (
312
+ <div className="comment-message-edit">
313
+ <textarea
314
+ className="comment-field"
315
+ aria-label="Edit message"
316
+ value={editing}
317
+ autoFocus
318
+ onChange={(e) => setEditing(e.target.value)}
319
+ onKeyDown={(e) => {
320
+ if (e.key === "Escape") setEditing(null);
321
+ else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) void save();
322
+ else return;
323
+ e.preventDefault();
324
+ e.stopPropagation();
325
+ }}
326
+ />
327
+ <div className="comment-message-edit-actions">
328
+ <button type="button" className="btn" onClick={() => setEditing(null)}>
329
+ Cancel
330
+ </button>
331
+ <button type="button" className="comment-post" onClick={() => void save()}>
332
+ Save
333
+ </button>
334
+ </div>
335
+ </div>
336
+ )}
337
+ {refusal === null ? null : (
338
+ <p className="comment-refusal" role="alert">
339
+ {refusal}
340
+ </p>
341
+ )}
342
+ </div>
343
+ {menu}
344
+ </div>
345
+ );
346
+ }
@@ -0,0 +1,336 @@
1
+ // THE COMMENTS TAB (Paper page 20, "Comments panel"; S5): one thread list per
2
+ // place, never mixed. On the board: card threads by card, then Frame pins by
3
+ // Shot and Frame. In the Studio: Studio threads by Shot, in frame order. Four
4
+ // filters over either. A row opens its thread where it sits
5
+ // (`store.openThread`), and the thread itself opens inline here. A Frame pin
6
+ // from the old board frame still lists, under its Shot and Frame.
7
+ import type { ReactNode } from "react";
8
+ import { useShotEditor, useStore } from "../../state/context.js";
9
+ import {
10
+ boardThreads,
11
+ commentPrompt,
12
+ filterThreads,
13
+ groupCardThreads,
14
+ groupStudioThreads,
15
+ groupThreads,
16
+ NEW_THREAD,
17
+ personLabel,
18
+ placeOfKind,
19
+ plainText,
20
+ resolvedToday,
21
+ studioPinsFor,
22
+ studioThreads,
23
+ threadWhere,
24
+ } from "../../state/comments.js";
25
+ import type { CommentsFilter } from "../../state/comments.js";
26
+ import type { CommentCardAnchor, CommentStudioAnchor, CommentThread as Thread } from "../../api.js";
27
+ import { shotNameOf, shotNumberOf } from "../../state/storyboardModel.js";
28
+ import { agentInEditor } from "../agent/agentGate.js";
29
+ import { StillPicture } from "../storyboard/StillThumbs.js";
30
+ import { CommentComposer } from "./CommentComposer.js";
31
+ import { CommentThread } from "./CommentThread.js";
32
+ import { CardCommentCounts, PersonAvatar, RESOLVED_ICON, shortAgo, sparkle, useComments } from "./parts.js";
33
+
34
+ const FILTERS: { value: CommentsFilter; label: string }[] = [
35
+ { value: "open", label: "Open" },
36
+ { value: "resolved", label: "Resolved" },
37
+ { value: "mine", label: "Mine" },
38
+ { value: "mentions", label: "Mentions" },
39
+ ];
40
+
41
+ export function CommentsPanel() {
42
+ const store = useStore();
43
+ const state = useComments();
44
+ const doc = useShotEditor((s) => s.doc);
45
+ const readOnly = useShotEditor((s) => s.readOnly);
46
+ const onBoard = useShotEditor((s) => s.storyboardOpen);
47
+ const shotHere = useShotEditor((s) => s.enteredScene ?? s.sceneId);
48
+ if (doc === null) return null;
49
+ const { filter, matched, openId, detail, draft, names, loaded, error } = state;
50
+ // ONE THREAD LIST PER PLACE (S5). Never mixed.
51
+ const threads: readonly Thread[] = onBoard ? boardThreads(state.threads) : studioThreads(state.threads);
52
+ const open = threads.filter((t) => t.status === "open");
53
+ const shown = filterThreads(threads, filter, matched);
54
+ const cardGroups = onBoard ? groupCardThreads(doc, shown) : [];
55
+ const groups = onBoard ? groupThreads(doc, shown) : [];
56
+ const studioGroups = onBoard ? [] : groupStudioThreads(doc, shown);
57
+ const pinNumbers = new Map(studioPinsFor(doc, state.threads, openId).map((p) => [p.threadId, p.initials]));
58
+ const today = filter === "open" ? resolvedToday(threads, Date.now()) : [];
59
+ const studioOpenHere =
60
+ shotHere === null ? 0 : open.filter((t) => t.anchor.kind === "studio" && t.anchor.sceneId === shotHere).length;
61
+ // The place's name and its open count, apart, so a long shot name is the
62
+ // part that gives way in a narrow column and the count always shows.
63
+ const inShot = !onBoard && shotHere !== null && doc.scenesById[shotHere] !== undefined;
64
+ const chipName = inShot ? `${shotNumberOf(doc, shotHere) ?? ""} ${shotNameOf(doc, shotHere)}`.trim() : "Comments";
65
+ const chipOpen = inShot ? studioOpenHere : open.length;
66
+ const panelDraft =
67
+ draft !== null && draft.anchor.kind !== "frame" && placeOfKind(draft.anchor.kind) === (onBoard ? "board" : "studio")
68
+ ? draft.anchor
69
+ : null;
70
+ const mentionsUnread = state.inbox.items.some(
71
+ (item) => item.kind === "mention" && item.readAt === null && item.projectId === state.projectId,
72
+ );
73
+ // A thread carries no "resolved through an agent" bit; the inbox's resolve
74
+ // item does, for the people who follow the thread.
75
+ const agentResolved = new Set(
76
+ state.inbox.items.filter((item) => item.kind === "resolved" && item.viaAgent).map((item) => item.threadId),
77
+ );
78
+ const count = (f: CommentsFilter): number | null =>
79
+ f === "open" ? open.length : f === "resolved" ? threads.length - open.length : f === filter && matched !== null ? shown.length : null;
80
+ const inline = (t: Thread): boolean => t.id === openId && detail !== null;
81
+ const row = (t: Thread, orphan: boolean, lead?: ReactNode) => (
82
+ <div key={t.id}>
83
+ <ThreadRow
84
+ thread={t}
85
+ names={names}
86
+ active={t.id === openId}
87
+ orphan={orphan}
88
+ lead={lead}
89
+ onOpen={() => void store.openThread(t.id)}
90
+ />
91
+ {inline(t) ? <CommentThread className="is-inline" /> : null}
92
+ </div>
93
+ );
94
+ const empty =
95
+ filter === "open"
96
+ ? onBoard
97
+ ? "No open comments. Select a card and press C to comment on it."
98
+ : "No open Studio comments. Press C, then click the picture to leave one at the playhead."
99
+ : filter === "resolved"
100
+ ? "Nothing resolved yet."
101
+ : filter === "mine"
102
+ ? "No threads you started or were assigned."
103
+ : "No one has mentioned you here.";
104
+
105
+ return (
106
+ <div className="comments-panel" data-testid="comments-panel" data-place={onBoard ? "board" : "studio"}>
107
+ <div className="comments-place-bar" data-testid="comments-place-bar">
108
+ <span className="comments-place-title">{onBoard ? "Storyboard" : "Studio"}</span>
109
+ <span className="comments-place-sub">{onBoard ? "talks about the plan" : "talks about the picture at a moment"}</span>
110
+ <span className="comment-thread-spacer" />
111
+ <span className="comments-place-chip" title={`${chipName} · ${chipOpen} open`}>
112
+ <span className="comments-place-chip-name">{chipName}</span>
113
+ <span className="comments-place-chip-count"> · {chipOpen} open</span>
114
+ </span>
115
+ </div>
116
+ <div className="comments-filters" role="radiogroup" aria-label="Show">
117
+ {FILTERS.map((f) => {
118
+ const n = count(f.value);
119
+ return (
120
+ <button
121
+ key={f.value}
122
+ type="button"
123
+ role="radio"
124
+ aria-checked={filter === f.value}
125
+ className={`comments-filter${filter === f.value ? " is-active" : ""}`}
126
+ data-testid={`comments-filter-${f.value}`}
127
+ onClick={() => void store.comments.setFilter(f.value)}
128
+ >
129
+ {f.label}
130
+ {n === null ? null : <span className="comments-filter-count">{n}</span>}
131
+ {f.value === "mentions" && mentionsUnread ? <span className="comments-unread-dot" aria-label="New" /> : null}
132
+ </button>
133
+ );
134
+ })}
135
+ </div>
136
+ <div className="comments-list">
137
+ {panelDraft === null ? null : <PanelDraft anchor={panelDraft} />}
138
+ {error !== null ? (
139
+ <p className="comments-empty" role="alert">
140
+ {error}
141
+ </p>
142
+ ) : !loaded || (filter !== "open" && filter !== "resolved" && matched === null) ? (
143
+ <p className="comments-empty">Loading comments…</p>
144
+ ) : shown.length === 0 && today.length === 0 && panelDraft === null ? (
145
+ <p className="comments-empty" data-testid="comments-empty">
146
+ {empty}
147
+ </p>
148
+ ) : null}
149
+ {cardGroups.map((group, g) => (
150
+ <section key={group.id} className={`comments-shot${g > 0 ? " is-later" : ""}`} data-testid="comments-card">
151
+ <header className="comments-shot-head">
152
+ <span className="comments-card-label">
153
+ {group.orphan === null ? `On card · ${group.label}` : group.label}
154
+ </span>
155
+ <span className="comment-thread-spacer" />
156
+ {group.orphan === null ? (
157
+ <CardCommentCounts target={group.target} />
158
+ ) : null}
159
+ </header>
160
+ {group.threads.map((t) => row(t, group.orphan !== null))}
161
+ </section>
162
+ ))}
163
+ {studioGroups.map((group, g) => (
164
+ <section key={group.sceneId} className={`comments-shot${g > 0 ? " is-later" : ""}`} data-testid="comments-studio-shot">
165
+ <header className="comments-shot-head">
166
+ {group.number === null ? null : <span className="comments-shot-number">{group.number}</span>}
167
+ <span className="comments-shot-name">{group.name}</span>
168
+ <span className="comment-thread-spacer" />
169
+ {group.open > 0 ? <span className="comments-shot-open">{group.open} open</span> : null}
170
+ </header>
171
+ {group.threads.map((t) => {
172
+ const where = threadWhere(doc, t.anchor);
173
+ const n = pinNumbers.get(t.id);
174
+ return row(
175
+ t,
176
+ group.number === null,
177
+ <span className="comments-thread-where">
178
+ {n === undefined ? null : <span className="comments-studio-pin">{n}</span>}
179
+ <span className="comments-studio-time">{where.time}</span>
180
+ <span className="comments-thread-where-text">{where.text}</span>
181
+ </span>,
182
+ );
183
+ })}
184
+ </section>
185
+ ))}
186
+ {groups.map((group, g) => (
187
+ <section
188
+ key={`${group.number ?? "gone"}:${group.sceneId}`}
189
+ className={`comments-shot${g > 0 || cardGroups.length > 0 ? " is-later" : ""}`}
190
+ >
191
+ <header className="comments-shot-head">
192
+ {group.number === null ? null : <span className="comments-shot-number">{group.number}</span>}
193
+ <span className="comments-shot-name">{group.name}</span>
194
+ <span className="comment-thread-spacer" />
195
+ {group.open > 0 ? <span className="comments-shot-open">{group.open} open</span> : null}
196
+ </header>
197
+ {group.frames.map((frame, f) => (
198
+ <div key={frame.stillId} className="comments-frame">
199
+ <div className={`comments-frame-head${f > 0 ? " is-later" : ""}`}>
200
+ {frame.orphan === null ? (
201
+ <>
202
+ <StillPicture
203
+ sceneId={group.sceneId}
204
+ frame={frame.frame}
205
+ width={32}
206
+ height={18}
207
+ className="comments-frame-thumb"
208
+ />
209
+ <span className="comments-frame-label">
210
+ {frame.label} · {(frame.frame / (doc.fps.num / doc.fps.den)).toFixed(1)} s
211
+ </span>
212
+ </>
213
+ ) : (
214
+ <>
215
+ <span className="comments-frame-thumb is-removed" aria-hidden="true" />
216
+ <span className="comments-frame-removed">Frame removed</span>
217
+ <span className="comments-frame-was">was {frame.label}</span>
218
+ </>
219
+ )}
220
+ </div>
221
+ {frame.threads.map((t) => row(t, frame.orphan !== null))}
222
+ </div>
223
+ ))}
224
+ </section>
225
+ ))}
226
+ {today.length > 0 ? (
227
+ <section className="comments-today" data-testid="comments-resolved-today">
228
+ <div className="comments-today-label">Resolved today</div>
229
+ {today.map((t) => (
230
+ <button key={t.id} type="button" className="comments-today-row" onClick={() => void store.openThread(t.id)}>
231
+ {RESOLVED_ICON}
232
+ <span className="comments-today-title">{plainText(t.head?.body ?? "", names) || "Comment"}</span>
233
+ {agentResolved.has(t.id) ? (
234
+ <span className="comment-via-agent">
235
+ {sparkle(9)}
236
+ via agent
237
+ </span>
238
+ ) : null}
239
+ </button>
240
+ ))}
241
+ </section>
242
+ ) : null}
243
+ </div>
244
+ {readOnly || open.length === 0 || !agentInEditor() ? null : (
245
+ <div className="comments-foot">
246
+ <button
247
+ type="button"
248
+ className="comments-agent-all"
249
+ data-testid="comments-ask-agent"
250
+ onClick={() => store.openAgentWith(commentPrompt(doc, open))}
251
+ >
252
+ {sparkle(13)}
253
+ Ask agent to fix {open.length} open
254
+ </button>
255
+ </div>
256
+ )}
257
+ </div>
258
+ );
259
+ }
260
+
261
+ /** A card or Studio thread being written, at the top of its place's list:
262
+ * where it goes, then the composer. */
263
+ function PanelDraft(props: { anchor: CommentCardAnchor | CommentStudioAnchor }) {
264
+ const store = useStore();
265
+ const doc = useShotEditor((s) => s.doc);
266
+ if (doc === null) return null;
267
+ const where = threadWhere(doc, props.anchor);
268
+ return (
269
+ <div className="comment-thread is-inline comments-draft" data-testid="comments-draft">
270
+ <CommentComposer
271
+ threadKey={NEW_THREAD}
272
+ variant="new"
273
+ onCancel={() => store.comments.cancelDraft()}
274
+ head={
275
+ <div className="comment-composer-head">
276
+ {where.time === null ? null : <span className="comments-studio-time">{where.time}</span>}
277
+ <span className="comment-composer-shot">{where.text}</span>
278
+ </div>
279
+ }
280
+ />
281
+ </div>
282
+ );
283
+ }
284
+
285
+ function ThreadRow(props: {
286
+ thread: Thread;
287
+ names: Readonly<Record<string, string>>;
288
+ active: boolean;
289
+ orphan: boolean;
290
+ /** A line above the author: the Studio's pin number, time and layer. */
291
+ lead?: ReactNode;
292
+ onOpen: () => void;
293
+ }) {
294
+ const { thread: t } = props;
295
+ const author = t.head?.authorLabel ?? "Someone";
296
+ const replies = Math.max(0, t.messageCount - 1);
297
+ return (
298
+ <button
299
+ type="button"
300
+ className={`comments-thread${props.active ? " is-active" : ""}`}
301
+ data-testid="comments-thread"
302
+ aria-current={props.active ? "true" : undefined}
303
+ onClick={props.onOpen}
304
+ >
305
+ <PersonAvatar userId={t.createdBy} label={author} size={22} className="is-tailed" />
306
+ <span className="comments-thread-main">
307
+ {props.lead}
308
+ <span className="comments-thread-head">
309
+ <span className="comments-thread-name">{author}</span>
310
+ <span className="comments-thread-time">{shortAgo(t.lastMessageAt)}</span>
311
+ <span className="comment-thread-spacer" />
312
+ {t.viewer.unread ? <span className="comments-unread-dot" aria-label="Unread" data-testid="comments-unread" /> : null}
313
+ </span>
314
+ <span className="comments-thread-body">{plainText(t.head?.body ?? "", props.names)}</span>
315
+ <span className="comments-thread-meta">
316
+ <span>{replies === 0 ? "No replies" : replies === 1 ? "1 reply" : `${replies} replies`}</span>
317
+ {t.assignee === null ? (
318
+ <span>Unassigned</span>
319
+ ) : (
320
+ <span className="comments-thread-assignee">
321
+ <PersonAvatar userId={t.assignee.id} label={personLabel(t.assignee)} size={14} />
322
+ {personLabel(t.assignee)}
323
+ </span>
324
+ )}
325
+ </span>
326
+ {props.orphan ? (
327
+ <span className="comments-thread-orphan">
328
+ {t.anchor.kind === "frame"
329
+ ? "Shows the picture you saw. Undo the delete and the pin comes back."
330
+ : "Undo the delete and it comes back where it was."}
331
+ </span>
332
+ ) : null}
333
+ </span>
334
+ </button>
335
+ );
336
+ }