@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,1606 @@
1
+ // EVERY BINDING THIS CHROME HAS, as a table — and the one place a combo string
2
+ // is written down.
3
+ //
4
+ // It lived inside HelpSection.tsx, which was fine while the manual was its only
5
+ // reader. The app menu is the second, and a data table reached through a React
6
+ // component is a data table that drags a Dialog into `state/menu.ts` (which is
7
+ // deliberately JSX-free). So it moved here, beside `keymap.ts`, which is what
8
+ // it documents. The manual imports it and re-exports it; nothing else changed.
9
+ //
10
+ // ── WHY IT IS HAND-WRITTEN AND NOT DERIVED ──────────────────────────────
11
+ //
12
+ // `resolveKey` is a FUNCTION with seven ordered lanes, not a table: the same
13
+ // letter is two commands depending on where the pointer is, Escape is a
14
+ // four-rung ladder, and one lane blanks every other key on purpose. There is no
15
+ // inverse to invert.
16
+ //
17
+ // So each row carries the EXACT EVENT it claims to describe plus the action
18
+ // kind the keymap must answer for it, and test/helpSection.test.tsx walks every
19
+ // row through the real `resolveKey`. A binding change without a table edit is a
20
+ // red test, not a stale manual — and now, not a stale menu either.
21
+ //
22
+ // The SHAPE of every row is still written out here. What some rows no longer
23
+ // write is their WORDS: the three layout keys take combo, prose and menu id
24
+ // from `state/layouts.ts`, which is the table the toolbar and the View menu
25
+ // already read, so a rename cannot reach two of those three surfaces and stop.
26
+ import type { KeyAction, KeyContext, KeyEventLike, SketchKeyAction } from "./keymap.js";
27
+ import { cameraVerb } from "./cameraVerbs.js";
28
+ import { LAYOUT_MODES, layoutSpec } from "./layouts.js";
29
+ import { SHADING_MODES, shadingLabel } from "../stage/shading.js";
30
+ import { VIEW_MODE_SPECS } from "./viewMode.js";
31
+ import { specOf } from "../tools.js";
32
+ import type { ShotTool } from "../tools.js";
33
+ import { ALIGN_LABELS } from "@dailies-studio/shot/doc";
34
+
35
+ /** The manual's sections, in the order it prints them. It reads this to render
36
+ * them, so a new group is one edit here rather than a union the manual has to
37
+ * be taught a second time. */
38
+ export const SHORTCUT_GROUPS = [
39
+ "View",
40
+ // ITS OWN GROUP, not rows folded into "View". Every one of these moves the
41
+ // PLAYHEAD or the clock that drives it, which is the one subject an author
42
+ // looks for as a set — and the set is now large enough (a shuttle, three
43
+ // kinds of jump, a work area) that scattering it through the view keys would
44
+ // hide it. `HelpSection` derives its sections from this list, so the heading
45
+ // costs one word here and nothing there.
46
+ "Transport",
47
+ "3D view",
48
+ "Edit",
49
+ "Animate & camera",
50
+ "Material nodes",
51
+ "Storyboard",
52
+ ] as const;
53
+
54
+ export type ShortcutGroup = (typeof SHORTCUT_GROUPS)[number];
55
+
56
+ export interface ShortcutRow {
57
+ /** What the manual shows, house glyph style (⌘ ⇧ ⌥ — ⌘ is Ctrl on Windows). */
58
+ combo: string;
59
+ /**
60
+ * The Mac's chord, for the rows whose Mac keys are NOT the same keys under
61
+ * the Mac's glyphs, with the event it sends. Arrange's front and back are
62
+ * Ctrl+⇧] on Windows and Linux and ⌥⌘] on a Mac, because ⌘⇧] and ⌘⇧[ are
63
+ * the Mac's tab switch (founder decision 3, critic 5). `comboOf` prints this
64
+ * on a Mac and `combo` everywhere else; the manual's test walks both probes.
65
+ */
66
+ mac?: { combo: string; probe: KeyEventLike };
67
+ does: string;
68
+ group: ShortcutGroup;
69
+ /** The event this combo produces, so the test can ask the keymap itself. */
70
+ probe: KeyEventLike;
71
+ /**
72
+ * The app-menu row this binding belongs to, when one exists.
73
+ *
74
+ * ── WHY THE MENU READS THIS TABLE RATHER THAN TYPING ITS OWN GLYPHS ────
75
+ *
76
+ * `ZoomControls.tsx` typed its own hint and it drifted (it still claimed ⇧Z
77
+ * for a fit that moved to Home). The menu is forty-odd more chances to do
78
+ * that. Pointing a row at a table entry costs one field and inherits that
79
+ * entry's test: rebind the key, and the walk-through fails on the row whose
80
+ * accelerator the menu was about to render.
81
+ *
82
+ * A row that covers SEVERAL keys in one line of prose ("Num2 / 4 / 6 / 8")
83
+ * carries no id — its `combo` is not a single accelerator, and a menu row
84
+ * showing it would be lying about which key it is. Where a menu wanted the
85
+ * digits individually the prose row was SPLIT instead (the three layout
86
+ * digits, the two zoom directions, the two clear-transform channels), so
87
+ * every id maps to exactly one key and the manual gained the detail too.
88
+ */
89
+ menuId?: string;
90
+ /** Arrows answer differently with a selection; everything else omits it. */
91
+ hasSelection?: boolean;
92
+ /** The pen's two rows answer differently with a path open, and ONLY with one
93
+ * open — Escape means deselect the rest of the time, which is why the
94
+ * context carries "holds points" and not "the pen tool is armed". */
95
+ penActive?: boolean;
96
+ /** Escape's third meaning: leave one level of isolation. It only answers
97
+ * that with NOTHING selected, so the row carries the bit and the test
98
+ * probes with `hasSelection` false. */
99
+ isolation?: boolean;
100
+ /** Escape's FIRST meaning, above all the others: put the editor back. Only
101
+ * while Video is on screen, so the row carries the bit. */
102
+ video?: boolean;
103
+ /** The modal starters (G/R/S) answer only with the cursor over the working
104
+ * pane — the same letters arm tools everywhere else, and both readings
105
+ * have rows. */
106
+ workingPane?: boolean;
107
+ /** The axis and terminator rows answer only while a transform is LIVE. */
108
+ transformModal?: boolean;
109
+ /** The material-graph rows answer only inside the node canvas — and answer
110
+ * something ELSE outside it, which is the whole reason the lane exists. */
111
+ materialGraph?: boolean;
112
+ /** The storyboard rows answer only while the board is open, and answer
113
+ * something ELSE outside it: Delete removes a layer there. */
114
+ storyboard?: boolean;
115
+ /** A shot board's sketch keys (`resolveShotBoardKey`). */
116
+ shotBoard?: boolean;
117
+ /** The rest of the context a row answers under, for the bits only a row or
118
+ * two read (Escape's lower rungs, the clipboards). The manual's test spreads
119
+ * it into the probe's context. */
120
+ context?: Omit<KeyContext, "editing" | "hasSelection">;
121
+ /** The action `resolveKey(probe)` must return (`resolveShotBoardKey` for a
122
+ * shot board row). */
123
+ action: KeyAction["kind"] | SketchKeyAction["kind"];
124
+ }
125
+
126
+ const probe = (
127
+ key: string,
128
+ m: { shift?: boolean; ctrl?: boolean; alt?: boolean; meta?: boolean; code?: string } = {},
129
+ ): KeyEventLike => ({
130
+ key,
131
+ shiftKey: m.shift === true,
132
+ altKey: m.alt === true,
133
+ ctrlKey: m.ctrl === true,
134
+ metaKey: m.meta === true,
135
+ ...(m.code === undefined ? {} : { code: m.code }),
136
+ });
137
+
138
+ export const SHORTCUTS: readonly ShortcutRow[] = [
139
+ {
140
+ group: "View",
141
+ combo: "⇧S",
142
+ does: "Open or close the Storyboard",
143
+ probe: probe("s", { shift: true }),
144
+ action: "storyboardToggle",
145
+ menuId: "ctx.storyboard",
146
+ },
147
+ // ── THE TRANSPORT ────────────────────────────────────────────────────────
148
+ //
149
+ // Space and the step keys were rows of "View" when the playhead had three
150
+ // bindings between them. It has eleven now, so they moved into the group the
151
+ // rest of them are in rather than leaving the set split across two headings.
152
+ {
153
+ group: "Transport",
154
+ combo: "Space",
155
+ does: "Play / pause",
156
+ probe: probe(" "),
157
+ action: "playToggle",
158
+ menuId: "time.play",
159
+ },
160
+ {
161
+ group: "Transport",
162
+ combo: "← →",
163
+ does: "Step one frame (nothing selected)",
164
+ probe: probe("ArrowRight"),
165
+ action: "step",
166
+ },
167
+ {
168
+ group: "Transport",
169
+ combo: "⇧← ⇧→",
170
+ does: "Step one second (nothing selected)",
171
+ probe: probe("ArrowRight", { shift: true }),
172
+ action: "stepSeconds",
173
+ },
174
+ {
175
+ group: "Transport",
176
+ combo: "↑ ↓",
177
+ does: "To the previous / next edit point: a shot boundary, a lifespan edge, a marker",
178
+ probe: probe("ArrowUp"),
179
+ action: "jumpEdit",
180
+ },
181
+ // ── THE SHUTTLE, AND WHY IT IS NOT J / K / L ─────────────────────────────
182
+ //
183
+ // `L` is the Line tool and `⇧L` is the Arrow tool (`TOOL_SPECS`), and ⌥L is
184
+ // the lock. A shuttle on any of the three would shadow a binding that has no
185
+ // other key, so the run's third rung is ⇧K. See the block in keymap.ts.
186
+ {
187
+ group: "Transport",
188
+ combo: "J",
189
+ does: "Shuttle backwards: 1x, then 2x, then 4x in reverse",
190
+ probe: probe("j"),
191
+ action: "shuttle",
192
+ },
193
+ {
194
+ group: "Transport",
195
+ combo: "K",
196
+ does: "Stop, and put the speed back to 1x",
197
+ probe: probe("k"),
198
+ action: "shuttleStop",
199
+ },
200
+ {
201
+ group: "Transport",
202
+ combo: "⇧K",
203
+ does: "Shuttle forwards: 1x, then 2x, then 4x (L is the Line tool)",
204
+ probe: probe("k", { shift: true }),
205
+ action: "shuttle",
206
+ },
207
+ {
208
+ group: "Transport",
209
+ combo: ";",
210
+ does: "To the selection's in point",
211
+ probe: probe(";"),
212
+ action: "seekSelectionEdge",
213
+ },
214
+ {
215
+ group: "Transport",
216
+ combo: "'",
217
+ does: "To the selection's out point",
218
+ probe: probe("'"),
219
+ action: "seekSelectionEdge",
220
+ },
221
+ // ── THE WORK AREA ────────────────────────────────────────────────────────
222
+ //
223
+ // Session state, never the document: it is what playback loops inside while
224
+ // an author watches one stretch over and over. See `ShotEditorState.workArea`
225
+ // for why it is not a document field.
226
+ {
227
+ group: "Transport",
228
+ combo: "⌥I",
229
+ does: "Work area in at the playhead (playback loops inside it)",
230
+ probe: probe("i", { alt: true }),
231
+ action: "setWorkArea",
232
+ menuId: "time.workArea.in",
233
+ },
234
+ {
235
+ group: "Transport",
236
+ combo: "⌥O",
237
+ does: "Work area out at the playhead",
238
+ probe: probe("o", { alt: true }),
239
+ action: "setWorkArea",
240
+ menuId: "time.workArea.out",
241
+ },
242
+ {
243
+ group: "Transport",
244
+ combo: "⌥X",
245
+ does: "Clear the work area: loop the whole film again",
246
+ probe: probe("x", { alt: true }),
247
+ action: "clearWorkArea",
248
+ menuId: "time.workArea.clear",
249
+ },
250
+ // THE KEYFRAME JUMPS, on the alt lane since the shuttle took J and K. They
251
+ // had no row at all before, so the manual was silent about a working pair of
252
+ // keys — which is the state this table exists to prevent.
253
+ {
254
+ group: "Transport",
255
+ combo: "⌥J",
256
+ does: "To the previous keyframe on the focused track",
257
+ probe: probe("j", { alt: true }),
258
+ action: "jumpKeyframe",
259
+ },
260
+ {
261
+ group: "Transport",
262
+ combo: "⌥K",
263
+ does: "To the next keyframe on the focused track",
264
+ probe: probe("k", { alt: true }),
265
+ action: "jumpKeyframe",
266
+ },
267
+ // THREE ROWS, one per layout, in key order. Still one row per key, so the
268
+ // View ▸ Layout entries can each name their own and each is walked through
269
+ // `resolveKey` on its own, which a single "⇧1 / ⇧2 / ⇧3" line could not do.
270
+ // The words, the keys and the menu ids come out of `state/layouts.ts`, so a
271
+ // rename or a rebind reaches the manual with no edit here. `probe` is
272
+ // module-private, which is why the derivation lives in this file rather than
273
+ // beside the table. The probe's `key` is the digit (what AZERTY reports);
274
+ // the keymap reads `code`, so a US board's "!" answers the same.
275
+ ...LAYOUT_MODES.map((mode): ShortcutRow => {
276
+ const spec = layoutSpec(mode);
277
+ return {
278
+ group: "View",
279
+ combo: spec.key,
280
+ does: spec.does,
281
+ probe: probe(spec.code.slice("Digit".length), { shift: true, code: spec.code }),
282
+ action: "stageLayout",
283
+ menuId: spec.menuId,
284
+ };
285
+ }),
286
+ // The two view-mode rows stay written out: Video's key and Escape's are not a
287
+ // pair (`VIEW_MODE_SPECS` says so), and the Escape row carries a context bit
288
+ // no table can hold. What they no longer own is the key or the word.
289
+ {
290
+ group: "View",
291
+ combo: VIEW_MODE_SPECS.video.key,
292
+ does: `${VIEW_MODE_SPECS.video.label}: the finished picture, and the only view that plays`,
293
+ probe: probe("$", { shift: true, code: VIEW_MODE_SPECS.video.code }),
294
+ action: "viewMode",
295
+ menuId: "view.video",
296
+ },
297
+ {
298
+ group: "View",
299
+ // The spec's `key` is the GLYPH the manual prints ("Esc"); the event this
300
+ // sends is the browser's "Escape". Two spellings of one key, which is why
301
+ // the probe is not built from the combo the way the layout rows' are.
302
+ combo: VIEW_MODE_SPECS.edit.key,
303
+ does: `Leave ${VIEW_MODE_SPECS.video.label}, and put the editor back`,
304
+ probe: probe("Escape"),
305
+ video: true,
306
+ action: "viewMode",
307
+ },
308
+ {
309
+ group: "View",
310
+ combo: "[",
311
+ does: "Hide / show the left panel",
312
+ probe: probe("["),
313
+ action: "togglePanel",
314
+ menuId: "view.panel.rail",
315
+ },
316
+ {
317
+ group: "View",
318
+ combo: "]",
319
+ does: "Hide / show the right panel",
320
+ probe: probe("]"),
321
+ action: "togglePanel",
322
+ menuId: "view.panel.inspector",
323
+ },
324
+ {
325
+ group: "View",
326
+ combo: ".",
327
+ does: "Hide every panel",
328
+ probe: probe("."),
329
+ action: "togglePanel",
330
+ menuId: "view.panel.all",
331
+ },
332
+ {
333
+ group: "View",
334
+ combo: "⌥M",
335
+ does: "Sound mode on / off: the timeline's tracks and mixer",
336
+ probe: probe("m", { alt: true }),
337
+ action: "soundMode",
338
+ menuId: "time.mode.sound",
339
+ },
340
+ {
341
+ group: "View",
342
+ combo: "\\",
343
+ does: "Fit the timeline",
344
+ probe: probe("\\"),
345
+ action: "fitTimeline",
346
+ menuId: "time.fit",
347
+ },
348
+ {
349
+ group: "View",
350
+ combo: "⌘+",
351
+ does: "Zoom in",
352
+ probe: probe("=", { ctrl: true }),
353
+ action: "zoom",
354
+ menuId: "view.zoomIn",
355
+ },
356
+ {
357
+ group: "View",
358
+ combo: "⌘−",
359
+ does: "Zoom out",
360
+ probe: probe("-", { ctrl: true }),
361
+ action: "zoom",
362
+ menuId: "view.zoomOut",
363
+ },
364
+ {
365
+ group: "View",
366
+ combo: "⌘0",
367
+ does: "Zoom to 100%",
368
+ probe: probe("0", { ctrl: true }),
369
+ action: "zoomReset",
370
+ menuId: "view.zoomReset",
371
+ },
372
+ {
373
+ group: "View",
374
+ combo: "Home",
375
+ does: "Fit the active pane",
376
+ probe: probe("Home"),
377
+ action: "fit",
378
+ menuId: "view.fit",
379
+ },
380
+ {
381
+ group: "View",
382
+ combo: "⇧C",
383
+ does: "Fit the active pane (Blender's frame all)",
384
+ probe: probe("c", { shift: true }),
385
+ action: "fit",
386
+ },
387
+ {
388
+ group: "Edit",
389
+ combo: "C",
390
+ does: "Comment on the picture at the playhead",
391
+ probe: probe("c"),
392
+ action: "storyboardComment",
393
+ },
394
+ {
395
+ group: "View",
396
+ combo: "Z",
397
+ does: `Cycle working-pane shading (${SHADING_MODES.map((m) => shadingLabel(m).toLowerCase()).join(" / ")})`,
398
+ probe: probe("z"),
399
+ action: "shadingCycle",
400
+ },
401
+ {
402
+ group: "View",
403
+ combo: "⇧Z",
404
+ does: `${shadingLabel("wireframe")} on / off`,
405
+ probe: probe("z", { shift: true }),
406
+ action: "wireframeToggle",
407
+ },
408
+ {
409
+ group: "View",
410
+ combo: "⌥Z",
411
+ does: "Grid floor on / off (working pane)",
412
+ probe: probe("z", { alt: true }),
413
+ action: "gridToggle",
414
+ menuId: "view.grid",
415
+ },
416
+ {
417
+ group: "View",
418
+ combo: "⇧R",
419
+ does: "Rulers on / off (camera pane)",
420
+ probe: probe("r", { shift: true }),
421
+ action: "rulersToggle",
422
+ menuId: "view.rulers",
423
+ },
424
+
425
+ {
426
+ group: "3D view",
427
+ combo: "G",
428
+ does: "Grab the selection: move it with the mouse, click to place",
429
+ probe: probe("g"),
430
+ workingPane: true,
431
+ hasSelection: true,
432
+ action: "modalStart",
433
+ },
434
+ {
435
+ group: "3D view",
436
+ combo: "R",
437
+ // A Shot's 3D view only: inside a definition the one pane is the frame,
438
+ // where R arms Rect (the keymap's `framePane` bit). A code component's
439
+ // preview is no frame, so R keeps its rotate there.
440
+ does: "Rotate the selection (roll). Inside a Studio-made component, R arms Rect",
441
+ probe: probe("r"),
442
+ workingPane: true,
443
+ hasSelection: true,
444
+ action: "modalStart",
445
+ },
446
+ {
447
+ group: "3D view",
448
+ combo: "S",
449
+ does: "Scale the selection",
450
+ probe: probe("s"),
451
+ workingPane: true,
452
+ hasSelection: true,
453
+ action: "modalStart",
454
+ },
455
+ {
456
+ group: "3D view",
457
+ combo: "G / R / S",
458
+ does: "Nothing selected: move / roll / zoom the camera",
459
+ probe: probe("g"),
460
+ workingPane: true,
461
+ action: "modalStart",
462
+ },
463
+ {
464
+ group: "3D view",
465
+ combo: "X / Y / Z",
466
+ does: "Constrain the transform to an axis (again clears; G+Z = depth)",
467
+ probe: probe("x"),
468
+ transformModal: true,
469
+ action: "modalAxis",
470
+ },
471
+ {
472
+ group: "3D view",
473
+ combo: "⇧X / ⇧Y / ⇧Z",
474
+ does: "Constrain a move to the PLANE normal to that axis (⇧X moves in YZ)",
475
+ probe: probe("x", { shift: true }),
476
+ transformModal: true,
477
+ action: "modalAxis",
478
+ },
479
+ {
480
+ group: "3D view",
481
+ combo: "0-9",
482
+ does: "Type an exact value: G X 200 Enter",
483
+ probe: probe("2"),
484
+ transformModal: true,
485
+ action: "modalNumber",
486
+ },
487
+ {
488
+ group: "3D view",
489
+ combo: "Backspace",
490
+ does: "Erase a typed digit (empty gives the mouse back)",
491
+ probe: probe("Backspace"),
492
+ transformModal: true,
493
+ action: "modalNumberBack",
494
+ },
495
+ // `hasSelection` only because the row PROBES with R over a live modal, and
496
+ // the manual's example is a layer transform; the camera rolls under R as
497
+ // well (`framingDeltaFor`, and the gizmo's own arc).
498
+ {
499
+ group: "3D view",
500
+ combo: "G / R / S",
501
+ does: "Switch transform without leaving the gesture",
502
+ probe: probe("r"),
503
+ transformModal: true,
504
+ hasSelection: true,
505
+ action: "modalStart",
506
+ },
507
+ {
508
+ group: "3D view",
509
+ combo: "Enter",
510
+ does: "Confirm the transform (or click)",
511
+ probe: probe("Enter"),
512
+ transformModal: true,
513
+ action: "modalConfirm",
514
+ },
515
+ {
516
+ group: "3D view",
517
+ combo: "Esc",
518
+ does: "Cancel the transform (or right-click)",
519
+ probe: probe("Escape"),
520
+ transformModal: true,
521
+ action: "modalCancel",
522
+ },
523
+ {
524
+ group: "3D view",
525
+ combo: "Num1 / 3 / 7",
526
+ does: "Front / right / top view (⌘ = the opposite)",
527
+ probe: probe("1", { code: "Numpad1" }),
528
+ action: "workingViewPreset",
529
+ },
530
+ {
531
+ group: "3D view",
532
+ combo: "Num0",
533
+ // The verb table's own sentence, for the reason the layout rows take
534
+ // theirs from `layouts.ts`: the bar, the menu and this manual must not
535
+ // spell one verb three ways again.
536
+ does: cameraVerb("pilot").sentence,
537
+ probe: probe("0", { code: "Numpad0" }),
538
+ action: "pilotToggle",
539
+ menuId: cameraVerb("pilot").menuId,
540
+ },
541
+ {
542
+ group: "3D view",
543
+ combo: "Esc",
544
+ does: "Stop piloting the camera",
545
+ probe: probe("Escape"),
546
+ context: { pilot: true },
547
+ action: "pilotEject",
548
+ },
549
+ {
550
+ group: "3D view",
551
+ combo: "Num.",
552
+ does: "Frame the selection",
553
+ probe: probe(".", { code: "NumpadDecimal" }),
554
+ action: "fitSelection",
555
+ menuId: "view.fitSelection",
556
+ },
557
+ {
558
+ group: "3D view",
559
+ combo: "Num2 / 4 / 6 / 8",
560
+ does: "Orbit 15°; Num9 looks from the opposite side",
561
+ probe: probe("4", { code: "Numpad4" }),
562
+ action: "orbitStep",
563
+ },
564
+ {
565
+ group: "3D view",
566
+ combo: "Num5",
567
+ does: "Orthographic / perspective",
568
+ probe: probe("5", { code: "Numpad5" }),
569
+ action: "toggleWorkingOrtho",
570
+ menuId: "view.ortho",
571
+ },
572
+ // NO NUMPAD, NO PROBLEM. The numpad lane keys on `event.code`, so a laptop
573
+ // without one had no keyboard path at all to the three views a 3D pane is
574
+ // most often asked for. Blender maps the bare top-row digits; here those are
575
+ // kept free, so the alt lane takes them.
576
+ {
577
+ group: "3D view",
578
+ combo: "⌥1 / ⌥3 / ⌥7",
579
+ does: "Front / right / top view without a numpad (add ⇧ for the opposite side)",
580
+ probe: probe("1", { alt: true }),
581
+ action: "workingViewPreset",
582
+ },
583
+ {
584
+ group: "3D view",
585
+ combo: "⌥5",
586
+ does: "Orthographic / perspective without a numpad",
587
+ probe: probe("5", { alt: true }),
588
+ action: "toggleWorkingOrtho",
589
+ },
590
+ {
591
+ group: "3D view",
592
+ combo: "Num/",
593
+ does: "Go inside the selection, and back out",
594
+ probe: probe("/", { code: "NumpadDivide" }),
595
+ action: "toggleIsolation",
596
+ },
597
+ {
598
+ group: "3D view",
599
+ combo: "Num+ / Num−",
600
+ does: "Zoom the active pane",
601
+ probe: probe("+", { code: "NumpadAdd" }),
602
+ action: "zoom",
603
+ },
604
+
605
+ {
606
+ group: "Edit",
607
+ combo: "Arrows",
608
+ does: "Nudge selection 1 px (⇧ = 10)",
609
+ probe: probe("ArrowRight"),
610
+ hasSelection: true,
611
+ action: "nudge",
612
+ },
613
+ {
614
+ group: "Edit",
615
+ combo: "← →",
616
+ does: "Move the selected keyframes 1 frame (⇧ = 10)",
617
+ probe: probe("ArrowRight"),
618
+ context: { hasKeyframeSelection: true },
619
+ action: "nudgeKeys",
620
+ },
621
+ {
622
+ group: "Edit",
623
+ combo: "⌘Z",
624
+ does: "Undo",
625
+ probe: probe("z", { ctrl: true }),
626
+ action: "undo",
627
+ menuId: "edit.undo",
628
+ },
629
+ {
630
+ group: "Edit",
631
+ combo: "⌘⇧Z",
632
+ does: "Redo",
633
+ probe: probe("z", { ctrl: true, shift: true }),
634
+ action: "redo",
635
+ menuId: "edit.redo",
636
+ },
637
+ // The OTHER redo, which every Windows editor binds and this one has always
638
+ // answered. It had no row, so the deleted docs table was the only place it
639
+ // was written down. No `menuId`: one menu row cannot print two accelerators,
640
+ // and ⌘⇧Z is the one it shows.
641
+ {
642
+ group: "Edit",
643
+ combo: "⌘Y",
644
+ does: "Redo (the other one)",
645
+ probe: probe("y", { ctrl: true }),
646
+ action: "redo",
647
+ },
648
+ {
649
+ group: "Edit",
650
+ combo: "⌘C",
651
+ does: "Copy the selected layers",
652
+ probe: probe("c", { ctrl: true }),
653
+ hasSelection: true,
654
+ action: "copyLayers",
655
+ menuId: "edit.copy",
656
+ },
657
+ {
658
+ group: "Edit",
659
+ combo: "⌘X",
660
+ does: "Cut the selected layers",
661
+ probe: probe("x", { ctrl: true }),
662
+ hasSelection: true,
663
+ action: "cutLayers",
664
+ menuId: "edit.cut",
665
+ },
666
+ {
667
+ group: "Edit",
668
+ combo: "⌘V",
669
+ does: "Paste the copied layers into their Shot",
670
+ probe: probe("v", { ctrl: true }),
671
+ context: { hasLayerClip: true },
672
+ action: "pasteLayers",
673
+ menuId: "edit.paste",
674
+ },
675
+ {
676
+ group: "Edit",
677
+ combo: "⌘C",
678
+ does: "Copy the selected keyframes",
679
+ probe: probe("c", { ctrl: true }),
680
+ context: { hasKeyframeSelection: true },
681
+ action: "copyKeyframes",
682
+ },
683
+ {
684
+ group: "Edit",
685
+ combo: "⌘V",
686
+ does: "Paste the copied keyframes at the playhead",
687
+ probe: probe("v", { ctrl: true }),
688
+ context: { hasKeyframeClip: true },
689
+ action: "pasteKeyframes",
690
+ },
691
+ {
692
+ group: "Edit",
693
+ combo: "⌘⌥C",
694
+ does: "Copy the selected layer's animation",
695
+ probe: probe("c", { ctrl: true, alt: true }),
696
+ // A Mac's Option composes the letter, so its probe carries the composed
697
+ // key and the `code` the keymap reads instead (`chordChar`).
698
+ mac: { combo: "⌥⌘C", probe: probe("ç", { meta: true, alt: true, code: "KeyC" }) },
699
+ // ⌥⌘C is also Chrome's View > Developer > Inspect Elements on a Mac. The
700
+ // page still gets the keydown first: Chromium's
701
+ // `BrowserCommandController::IsReservedCommandOrKey` reserves only the
702
+ // tab, window and quit commands (not `IDC_DEV_TOOLS_INSPECT`), so a
703
+ // claimed chord is `preventDefault`ed and Inspect never opens. With no
704
+ // layer selected the keymap leaves it to Chrome. Checked against the
705
+ // Chromium source (2026-10-12), not yet on Mac hardware: confirm there.
706
+ hasSelection: true,
707
+ action: "copyAnimation",
708
+ menuId: "edit.copyAnimation",
709
+ },
710
+ {
711
+ group: "Edit",
712
+ combo: "⌘⌥V",
713
+ does: "Paste the copied animation onto the selected layers, at the playhead",
714
+ probe: probe("v", { ctrl: true, alt: true }),
715
+ mac: { combo: "⌥⌘V", probe: probe("√", { meta: true, alt: true, code: "KeyV" }) },
716
+ hasSelection: true,
717
+ context: { hasAnimationClip: true },
718
+ action: "pasteAnimation",
719
+ menuId: "edit.pasteAnimation.playhead",
720
+ },
721
+ {
722
+ group: "Edit",
723
+ combo: "⌘⇧⌥C",
724
+ does: "Copy the selected layer's effects",
725
+ probe: probe("c", { ctrl: true, alt: true, shift: true }),
726
+ mac: { combo: "⇧⌥⌘C", probe: probe("Ç", { meta: true, alt: true, shift: true, code: "KeyC" }) },
727
+ hasSelection: true,
728
+ action: "copyEffects",
729
+ menuId: "edit.copyEffects",
730
+ },
731
+ {
732
+ group: "Edit",
733
+ combo: "⌘⇧⌥V",
734
+ does: "Paste the copied effects onto the selected layers",
735
+ probe: probe("v", { ctrl: true, alt: true, shift: true }),
736
+ mac: { combo: "⇧⌥⌘V", probe: probe("◊", { meta: true, alt: true, shift: true, code: "KeyV" }) },
737
+ hasSelection: true,
738
+ context: { hasEffectsClip: true },
739
+ action: "pasteEffects",
740
+ menuId: "edit.pasteEffects",
741
+ },
742
+ {
743
+ group: "Edit",
744
+ combo: "⌘S",
745
+ does: "Save a named version",
746
+ probe: probe("s", { ctrl: true }),
747
+ action: "save",
748
+ menuId: "file.save",
749
+ },
750
+ {
751
+ group: "Edit",
752
+ combo: "⌘D",
753
+ does: "Duplicate",
754
+ probe: probe("d", { ctrl: true }),
755
+ action: "duplicate",
756
+ menuId: "edit.duplicate",
757
+ },
758
+ {
759
+ group: "Edit",
760
+ combo: "⌘A",
761
+ does: "Select all, in the scope you are in",
762
+ probe: probe("a", { ctrl: true }),
763
+ action: "selectAll",
764
+ menuId: "edit.selectAll",
765
+ },
766
+ // Blender's bare A, the same verb through a second key. Split off ⌘A's row so
767
+ // that row's combo is one accelerator the menu can print; this one carries no
768
+ // id, because a row may name only one key.
769
+ {
770
+ group: "Edit",
771
+ combo: "A",
772
+ does: "Select all (Blender's key)",
773
+ probe: probe("a"),
774
+ action: "selectAll",
775
+ },
776
+ {
777
+ group: "Edit",
778
+ combo: "⌘⇧A",
779
+ does: "Deselect all",
780
+ probe: probe("a", { ctrl: true, shift: true }),
781
+ action: "deselect",
782
+ menuId: "edit.deselect",
783
+ },
784
+ {
785
+ group: "Edit",
786
+ combo: "B",
787
+ does: "Box select with the next drag",
788
+ probe: probe("b"),
789
+ action: "boxSelect",
790
+ },
791
+ {
792
+ group: "Edit",
793
+ combo: "⇧D",
794
+ does: "Duplicate",
795
+ probe: probe("d", { shift: true }),
796
+ action: "duplicate",
797
+ },
798
+ {
799
+ group: "Edit",
800
+ combo: "Esc",
801
+ does: "Deselect",
802
+ probe: probe("Escape"),
803
+ action: "deselect",
804
+ },
805
+ {
806
+ group: "Edit",
807
+ combo: "Del",
808
+ does: "Delete selection",
809
+ probe: probe("Delete"),
810
+ action: "deleteSelection",
811
+ menuId: "edit.delete",
812
+ },
813
+ {
814
+ group: "Edit",
815
+ combo: "⇧Backspace",
816
+ does: "Ripple delete: remove the layers and close the gap",
817
+ probe: probe("Backspace", { shift: true }),
818
+ action: "rippleDelete",
819
+ },
820
+ {
821
+ group: "Edit",
822
+ combo: "⌘G",
823
+ does: "Group",
824
+ probe: probe("g", { ctrl: true }),
825
+ action: "group",
826
+ menuId: "edit.group",
827
+ },
828
+ {
829
+ group: "Edit",
830
+ combo: "⌘⇧G",
831
+ does: "Ungroup",
832
+ probe: probe("g", { ctrl: true, shift: true }),
833
+ action: "ungroup",
834
+ menuId: "edit.ungroup",
835
+ },
836
+ // ── ARRANGE, Figma's bracket chords ─────────────────────────────────────
837
+ //
838
+ // Each layer among its own siblings, which is paint order (the layers panel
839
+ // lists them front first). The probes carry `code`, because the keymap keys
840
+ // on the physical key: Shift turns `]` into `}` and Option into a quote. The
841
+ // front and back rows split by platform (`mac`): ⌘⇧] and ⌘⇧[ are the Mac's
842
+ // tab switch and are never claimed.
843
+ {
844
+ group: "Edit",
845
+ combo: "⌘]",
846
+ does: "Bring forward, past one sibling",
847
+ probe: probe("]", { ctrl: true, code: "BracketRight" }),
848
+ action: "arrange",
849
+ menuId: "layout.arrange.forward",
850
+ },
851
+ {
852
+ group: "Edit",
853
+ combo: "⌘[",
854
+ does: "Send backward, past one sibling",
855
+ probe: probe("[", { ctrl: true, code: "BracketLeft" }),
856
+ action: "arrange",
857
+ menuId: "layout.arrange.backward",
858
+ },
859
+ {
860
+ group: "Edit",
861
+ combo: "⌘⇧]",
862
+ does: "Bring to front, among its siblings",
863
+ probe: probe("}", { ctrl: true, shift: true, code: "BracketRight" }),
864
+ mac: { combo: "⌥⌘]", probe: probe("‘", { meta: true, alt: true, code: "BracketRight" }) },
865
+ action: "arrange",
866
+ menuId: "layout.arrange.front",
867
+ },
868
+ {
869
+ group: "Edit",
870
+ combo: "⌘⇧[",
871
+ does: "Send to back, among its siblings",
872
+ probe: probe("{", { ctrl: true, shift: true, code: "BracketLeft" }),
873
+ mac: { combo: "⌥⌘[", probe: probe("“", { meta: true, alt: true, code: "BracketLeft" }) },
874
+ action: "arrange",
875
+ menuId: "layout.arrange.back",
876
+ },
877
+ {
878
+ group: "Edit",
879
+ combo: "⌥L",
880
+ does: "Lock / unlock",
881
+ probe: probe("l", { alt: true }),
882
+ action: "toggleLock",
883
+ menuId: "layer.lock",
884
+ },
885
+ {
886
+ group: "Edit",
887
+ combo: "H",
888
+ does: "Hide the selection",
889
+ probe: probe("h"),
890
+ action: "setHidden",
891
+ menuId: "layer.hide",
892
+ },
893
+ {
894
+ group: "Edit",
895
+ combo: "⌥⇧H",
896
+ does: "Hide everything else",
897
+ probe: probe("Ó", { alt: true, shift: true, code: "KeyH" }),
898
+ action: "setHidden",
899
+ menuId: "layer.hideOthers",
900
+ },
901
+ {
902
+ group: "Edit",
903
+ combo: "⌘⇧H",
904
+ does: "Show everything again",
905
+ probe: probe("h", { ctrl: true, shift: true }),
906
+ action: "setHidden",
907
+ menuId: "layer.reveal",
908
+ },
909
+ // ── ALIGN, ⌥ + A D W S H V ──────────────────────────────────────────────
910
+ //
911
+ // The words come from `ALIGN_LABELS`, the table the align grid and the
912
+ // Arrange menu already read. Each probe carries `code`: a Mac's Option
913
+ // composes the letter.
914
+ ...(
915
+ [
916
+ ["left", "A"],
917
+ ["right", "D"],
918
+ ["top", "W"],
919
+ ["bottom", "S"],
920
+ ["hcenter", "H"],
921
+ ["vcenter", "V"],
922
+ ] as const
923
+ ).map(
924
+ ([mode, letter]): ShortcutRow => ({
925
+ group: "Edit",
926
+ combo: `⌥${letter}`,
927
+ does: ALIGN_LABELS[mode],
928
+ probe: probe(letter.toLowerCase(), { alt: true, code: `Key${letter}` }),
929
+ action: "align",
930
+ menuId: `layout.align.${mode}`,
931
+ }),
932
+ ),
933
+ // ── OPACITY ON THE BARE DIGITS ──────────────────────────────────────────
934
+ //
935
+ // One row for the tenths and one for 0, the Help sheet's two lines. Neither
936
+ // has a menu id: a row may name only one key.
937
+ {
938
+ group: "Edit",
939
+ combo: "1 to 9",
940
+ does: "Opacity 10% to 90%",
941
+ probe: probe("5", { code: "Digit5" }),
942
+ hasSelection: true,
943
+ action: "opacity",
944
+ },
945
+ {
946
+ group: "Edit",
947
+ combo: "0",
948
+ does: "Opacity 100%",
949
+ probe: probe("0", { code: "Digit0" }),
950
+ hasSelection: true,
951
+ action: "opacity",
952
+ },
953
+ {
954
+ group: "Edit",
955
+ combo: "Q",
956
+ does: "Trim the start to the playhead",
957
+ probe: probe("q"),
958
+ hasSelection: true,
959
+ action: "trimToPlayhead",
960
+ menuId: "layer.trim.in",
961
+ },
962
+ {
963
+ group: "Edit",
964
+ combo: "W",
965
+ does: "Trim the end to the playhead (keeps that frame)",
966
+ probe: probe("w"),
967
+ hasSelection: true,
968
+ action: "trimToPlayhead",
969
+ menuId: "layer.trim.out",
970
+ },
971
+ {
972
+ group: "Edit",
973
+ combo: "⇧H",
974
+ does: "Flip horizontal",
975
+ probe: probe("H", { shift: true, code: "KeyH" }),
976
+ hasSelection: true,
977
+ action: "flip",
978
+ menuId: "layer.flip.x",
979
+ },
980
+ {
981
+ group: "Edit",
982
+ combo: "⇧V",
983
+ does: "Flip vertical",
984
+ probe: probe("V", { shift: true, code: "KeyV" }),
985
+ hasSelection: true,
986
+ action: "flip",
987
+ menuId: "layer.flip.y",
988
+ },
989
+ // SPLIT INTO TWO from one "⌥G / ⌥R" line, for the layout digits' reason: the
990
+ // Layer ▸ Clear rows name one channel each.
991
+ {
992
+ group: "Edit",
993
+ combo: "⌥G",
994
+ does: "Clear the position",
995
+ probe: probe("g", { alt: true }),
996
+ action: "clearTransform",
997
+ menuId: "layer.clear.position",
998
+ },
999
+ {
1000
+ group: "Edit",
1001
+ combo: "⌥R",
1002
+ does: "Clear the rotation",
1003
+ probe: probe("r", { alt: true }),
1004
+ action: "clearTransform",
1005
+ menuId: "layer.clear.rotation",
1006
+ },
1007
+ // The third channel. It was live in `keymap.ts` with no row here, so the only
1008
+ // place it was written down was the hand-kept table in docs/shot.md, and
1009
+ // deleting that table left a working key documented nowhere. No `menuId`:
1010
+ // the Layer ▸ Clear submenu carries position and rotation only.
1011
+ {
1012
+ group: "Edit",
1013
+ combo: "⌥⇧S",
1014
+ does: "Clear the size",
1015
+ probe: probe("S", { alt: true, shift: true, code: "KeyS" }),
1016
+ action: "clearTransform",
1017
+ },
1018
+ {
1019
+ group: "Edit",
1020
+ combo: "Enter",
1021
+ does: "Edit inside the selection",
1022
+ probe: probe("Enter"),
1023
+ hasSelection: true,
1024
+ action: "enterIsolation",
1025
+ menuId: "layer.isolate",
1026
+ },
1027
+ {
1028
+ group: "Edit",
1029
+ combo: "Esc",
1030
+ does: "Come back out one level",
1031
+ probe: probe("Escape"),
1032
+ isolation: true,
1033
+ action: "exitIsolation",
1034
+ menuId: "layer.isolate.exit",
1035
+ },
1036
+ // The rest of Escape's ladder, one level per press with nothing selected.
1037
+ {
1038
+ group: "Edit",
1039
+ combo: "Esc",
1040
+ does: "Back to the Shot you opened the component from",
1041
+ probe: probe("Escape"),
1042
+ context: { editingDef: true, componentsOrigin: true },
1043
+ action: "backToShot",
1044
+ },
1045
+ {
1046
+ group: "Edit",
1047
+ combo: "Esc",
1048
+ does: "Leave the component",
1049
+ probe: probe("Escape"),
1050
+ context: { editingDef: true },
1051
+ action: "closeComponentContext",
1052
+ },
1053
+ {
1054
+ group: "Edit",
1055
+ combo: "Esc",
1056
+ does: "Leave the code component",
1057
+ probe: probe("Escape"),
1058
+ context: { codeComponent: true },
1059
+ action: "closeCodeComponent",
1060
+ },
1061
+ {
1062
+ group: "Edit",
1063
+ combo: "Esc",
1064
+ does: "Leave Components",
1065
+ probe: probe("Escape"),
1066
+ context: { componentsOpen: true },
1067
+ action: "leaveComponents",
1068
+ },
1069
+ {
1070
+ group: "Edit",
1071
+ combo: "Esc",
1072
+ does: "Leave the Shot, back to all Shots",
1073
+ probe: probe("Escape"),
1074
+ context: { enteredScene: true },
1075
+ action: "leaveScene",
1076
+ },
1077
+ {
1078
+ group: "Edit",
1079
+ combo: "Enter",
1080
+ does: "Finish the path you are drawing",
1081
+ probe: probe("Enter"),
1082
+ penActive: true,
1083
+ action: "penCommit",
1084
+ },
1085
+ {
1086
+ group: "Edit",
1087
+ combo: "Esc",
1088
+ does: "Throw away the path you are drawing",
1089
+ probe: probe("Escape"),
1090
+ penActive: true,
1091
+ action: "penAbandon",
1092
+ },
1093
+
1094
+ {
1095
+ group: "Animate & camera",
1096
+ combo: "⇧A",
1097
+ does: "Animate: edits write keyframes at the playhead",
1098
+ probe: probe("a", { shift: true }),
1099
+ action: "animateToggle",
1100
+ menuId: "tool.animate",
1101
+ },
1102
+ {
1103
+ group: "Animate & camera",
1104
+ combo: "M",
1105
+ does: "Drop a marker at the playhead",
1106
+ probe: probe("m"),
1107
+ action: "addMarker",
1108
+ menuId: "time.marker.add",
1109
+ },
1110
+ {
1111
+ group: "Animate & camera",
1112
+ combo: "⌥C",
1113
+ does: cameraVerb("toView").sentence,
1114
+ probe: probe("c", { alt: true }),
1115
+ action: "alignCameraToView",
1116
+ menuId: cameraVerb("toView").menuId,
1117
+ },
1118
+ {
1119
+ group: "Animate & camera",
1120
+ combo: "⌘B",
1121
+ does: "Split the layers under the playhead",
1122
+ probe: probe("b", { ctrl: true }),
1123
+ action: "splitAtPlayhead",
1124
+ menuId: "edit.split",
1125
+ },
1126
+ {
1127
+ group: "Animate & camera",
1128
+ combo: "⌘⇧B",
1129
+ does: "Split the shot at the playhead",
1130
+ probe: probe("b", { ctrl: true, shift: true }),
1131
+ action: "splitSceneAtPlayhead",
1132
+ },
1133
+ {
1134
+ group: "Animate & camera",
1135
+ combo: "⌘E",
1136
+ does: "Export video",
1137
+ probe: probe("e", { ctrl: true }),
1138
+ action: "openExport",
1139
+ menuId: "file.export",
1140
+ },
1141
+ // In "View" would be truer, and it is here because the manual's four groups
1142
+ // are about the DOCUMENT and this one is about the chrome. It is last in the
1143
+ // table so it reads as the outlier it is.
1144
+ {
1145
+ group: "View",
1146
+ combo: "⌘K",
1147
+ does: "Open the menu, ready to search",
1148
+ probe: probe("k", { ctrl: true }),
1149
+ action: "appMenu",
1150
+ menuId: "app.menu",
1151
+ },
1152
+ {
1153
+ group: "View",
1154
+ combo: "⌘,",
1155
+ does: "Open Preferences",
1156
+ probe: probe(",", { ctrl: true }),
1157
+ action: "openPrefs",
1158
+ menuId: "prefs.open",
1159
+ },
1160
+ // The key the left column's search field shows as its hint, so the hint and
1161
+ // the manual are one claim with one test behind it.
1162
+ {
1163
+ group: "View",
1164
+ combo: "/",
1165
+ does: "Search the left column",
1166
+ probe: probe("/", { code: "Slash" }),
1167
+ action: "focusSearch",
1168
+ },
1169
+ ];
1170
+
1171
+ /**
1172
+ * THE MATERIAL GRAPH'S BINDINGS, in a table of their own.
1173
+ *
1174
+ * ── WHY THEY ARE NOT ROWS OF `SHORTCUTS` ────────────────────────────────
1175
+ *
1176
+ * Every row of that table is walked through `resolveKey` with one fixed
1177
+ * context, and these twelve answer their action ONLY with `materialGraph:
1178
+ * true` — outside the canvas ⌘D duplicates a layer, Delete deletes the mesh
1179
+ * and `F` arms a tool. Folded into the main table, each row would need the
1180
+ * context bit set and the manual would print a key beside a sentence that is
1181
+ * true in one place and false everywhere else.
1182
+ *
1183
+ * So: a second table, the same `ShortcutRow` shape, the same test treatment
1184
+ * (see test/materialKeymap.test.ts), and `shortcutFor` indexes both — which is
1185
+ * what lets the node right-click menu print an accelerator without typing one.
1186
+ */
1187
+ /** The board's own lane, walked by test/storyboardKeymap.test.ts the way the
1188
+ * material rows are: every one means something else outside the board. The
1189
+ * two `storyboardCards` rows say they need a card selected. */
1190
+ export const STORYBOARD_SHORTCUTS: readonly ShortcutRow[] = [
1191
+ {
1192
+ group: "Storyboard",
1193
+ combo: "Del",
1194
+ does: "Delete the selected cards",
1195
+ probe: probe("Delete"),
1196
+ storyboard: true,
1197
+ action: "storyboardDelete",
1198
+ },
1199
+ {
1200
+ group: "Storyboard",
1201
+ combo: "C",
1202
+ does: "Comment on the selected card",
1203
+ probe: probe("c"),
1204
+ storyboard: true,
1205
+ action: "storyboardComment",
1206
+ },
1207
+ {
1208
+ group: "Storyboard",
1209
+ combo: "N",
1210
+ does: "Next open comment",
1211
+ probe: probe("n"),
1212
+ storyboard: true,
1213
+ action: "storyboardNextThread",
1214
+ },
1215
+ {
1216
+ group: "Storyboard",
1217
+ combo: "⇧N",
1218
+ does: "Previous open comment",
1219
+ probe: probe("n", { shift: true }),
1220
+ storyboard: true,
1221
+ action: "storyboardPrevThread",
1222
+ },
1223
+ {
1224
+ group: "Storyboard",
1225
+ combo: "⌘A",
1226
+ does: "Select every card",
1227
+ probe: probe("a", { ctrl: true }),
1228
+ storyboard: true,
1229
+ action: "storyboardSelectAll",
1230
+ },
1231
+ {
1232
+ group: "Storyboard",
1233
+ combo: "⌘D",
1234
+ does: "Duplicate the selected cards",
1235
+ probe: probe("d", { ctrl: true }),
1236
+ storyboard: true,
1237
+ action: "storyboardDuplicate",
1238
+ },
1239
+ {
1240
+ group: "Storyboard",
1241
+ combo: "F2",
1242
+ does: "Rename the selected planned shot",
1243
+ probe: probe("F2"),
1244
+ storyboard: true,
1245
+ action: "storyboardRename",
1246
+ },
1247
+ // ── THE THREE CLIPBOARD ROWS ─────────────────────────────────────────────
1248
+ //
1249
+ // Documented here so the combos appear in the shortcut sheet, and resolving
1250
+ // to an arm that returns FALSE so behaviour is byte-identical to leaving
1251
+ // them unbound. The
1252
+ // work is done by native document handlers in StoryboardView.tsx, because binding
1253
+ // these for real would `preventDefault` the keydown, cancel the browser's
1254
+ // own paste event, and silently take away pasting an image FILE onto the
1255
+ // board. See `storyboardClipboard` in keymap.ts.
1256
+ {
1257
+ group: "Storyboard",
1258
+ combo: "⌘C",
1259
+ does: "Copy the selection",
1260
+ probe: probe("c", { ctrl: true }),
1261
+ storyboard: true,
1262
+ action: "storyboardClipboard",
1263
+ },
1264
+ {
1265
+ group: "Storyboard",
1266
+ combo: "⌘X",
1267
+ does: "Cut the selection",
1268
+ probe: probe("x", { ctrl: true }),
1269
+ storyboard: true,
1270
+ action: "storyboardClipboard",
1271
+ },
1272
+ {
1273
+ group: "Storyboard",
1274
+ combo: "⌘V",
1275
+ does: "Paste, including an image or video from the system clipboard",
1276
+ probe: probe("v", { ctrl: true }),
1277
+ storyboard: true,
1278
+ action: "storyboardClipboard",
1279
+ },
1280
+ {
1281
+ group: "Storyboard",
1282
+ combo: "Esc",
1283
+ does: "Clear the board selection, then the layer selection",
1284
+ probe: probe("Escape"),
1285
+ storyboard: true,
1286
+ action: "storyboardEscape",
1287
+ },
1288
+ {
1289
+ group: "Storyboard",
1290
+ combo: "⇧S",
1291
+ does: "Close the Storyboard",
1292
+ probe: probe("s", { shift: true }),
1293
+ storyboard: true,
1294
+ action: "storyboardToggle",
1295
+ },
1296
+ // ── A SHOT BOARD'S SKETCH KEYS ───────────────────────────────────────────
1297
+ //
1298
+ // Answered by `resolveShotBoardKey` on the shot board's own element
1299
+ // (ShotBoard.tsx), so each row says `shotBoard`. Bare C arms the Component
1300
+ // tool there; on the whole board it still comments.
1301
+ sketchRow("Arrows", "Move the selected sketch items 1px", probe("ArrowRight"), "sketchMove"),
1302
+ sketchRow("⇧ Arrows", "Move the selected sketch items 10px", probe("ArrowRight", { shift: true }), "sketchMove"),
1303
+ sketchRow("[", "Send the selected sketch item back one step", probe("["), "sketchRestack"),
1304
+ sketchRow("]", "Bring the selected sketch item forward one step", probe("]"), "sketchRestack"),
1305
+ sketchRow("Enter", "Edit the selected text on a sketch", probe("Enter"), "sketchEdit"),
1306
+ sketchRow("V", "Sketch: Select", probe("v"), "sketchTool"),
1307
+ sketchRow("T", "Sketch: Text", probe("t"), "sketchTool"),
1308
+ sketchRow("R", "Sketch: Box", probe("r"), "sketchTool"),
1309
+ sketchRow("A", "Sketch: Arrow", probe("a"), "sketchTool"),
1310
+ sketchRow("I", "Sketch: add an image", probe("i"), "sketchTool"),
1311
+ sketchRow("C", "Sketch: add a component", probe("c"), "sketchTool"),
1312
+ ];
1313
+
1314
+ /** One shot board row. */
1315
+ function sketchRow(combo: string, does: string, at: KeyEventLike, action: SketchKeyAction["kind"]): ShortcutRow {
1316
+ return { group: "Storyboard", combo, does, probe: at, storyboard: true, shotBoard: true, action };
1317
+ }
1318
+
1319
+ export const MATERIAL_SHORTCUTS: readonly ShortcutRow[] = [
1320
+ {
1321
+ group: "Material nodes",
1322
+ combo: "Tab",
1323
+ does: "Open the node palette",
1324
+ probe: probe("Tab"),
1325
+ materialGraph: true,
1326
+ action: "graphPalette",
1327
+ menuId: "graph.add",
1328
+ },
1329
+ {
1330
+ group: "Material nodes",
1331
+ combo: "⇧A",
1332
+ does: "Open the node palette (Blender's Add)",
1333
+ probe: probe("a", { shift: true }),
1334
+ materialGraph: true,
1335
+ action: "graphPalette",
1336
+ },
1337
+ {
1338
+ group: "Material nodes",
1339
+ combo: "⌘D",
1340
+ does: "Duplicate the selected nodes",
1341
+ probe: probe("d", { ctrl: true }),
1342
+ materialGraph: true,
1343
+ action: "graphDuplicate",
1344
+ menuId: "graph.duplicate",
1345
+ },
1346
+ {
1347
+ group: "Material nodes",
1348
+ combo: "⇧D",
1349
+ does: "Duplicate the selected nodes",
1350
+ probe: probe("d", { shift: true }),
1351
+ materialGraph: true,
1352
+ action: "graphDuplicate",
1353
+ },
1354
+ {
1355
+ group: "Material nodes",
1356
+ combo: "⌘C",
1357
+ does: "Copy the selected nodes",
1358
+ probe: probe("c", { ctrl: true }),
1359
+ materialGraph: true,
1360
+ action: "graphCopy",
1361
+ menuId: "graph.copy",
1362
+ },
1363
+ {
1364
+ group: "Material nodes",
1365
+ combo: "⌘X",
1366
+ does: "Cut the selected nodes",
1367
+ probe: probe("x", { ctrl: true }),
1368
+ materialGraph: true,
1369
+ action: "graphCut",
1370
+ menuId: "graph.cut",
1371
+ },
1372
+ {
1373
+ group: "Material nodes",
1374
+ combo: "⌘V",
1375
+ does: "Paste nodes",
1376
+ probe: probe("v", { ctrl: true }),
1377
+ materialGraph: true,
1378
+ action: "graphPaste",
1379
+ menuId: "graph.paste",
1380
+ },
1381
+ {
1382
+ group: "Material nodes",
1383
+ combo: "⌘A",
1384
+ does: "Select every node in this scope",
1385
+ probe: probe("a", { ctrl: true }),
1386
+ materialGraph: true,
1387
+ action: "graphSelectAll",
1388
+ },
1389
+ {
1390
+ group: "Material nodes",
1391
+ combo: "⌘G",
1392
+ does: "Box the selected nodes into a group",
1393
+ probe: probe("g", { ctrl: true }),
1394
+ materialGraph: true,
1395
+ action: "graphGroup",
1396
+ menuId: "graph.group",
1397
+ },
1398
+ {
1399
+ group: "Material nodes",
1400
+ combo: "⌘⇧G",
1401
+ does: "Take the group box away",
1402
+ probe: probe("g", { ctrl: true, shift: true }),
1403
+ materialGraph: true,
1404
+ action: "graphUngroup",
1405
+ menuId: "graph.ungroup",
1406
+ },
1407
+ {
1408
+ group: "Material nodes",
1409
+ combo: "Enter",
1410
+ does: "Open the selected subgraph",
1411
+ probe: probe("Enter"),
1412
+ materialGraph: true,
1413
+ action: "graphEnter",
1414
+ menuId: "graph.enter",
1415
+ },
1416
+ {
1417
+ group: "Material nodes",
1418
+ combo: "P",
1419
+ does: "Show / hide the preview on the selected cards",
1420
+ probe: probe("p"),
1421
+ materialGraph: true,
1422
+ action: "graphPreviewToggle",
1423
+ menuId: "graph.preview",
1424
+ },
1425
+ {
1426
+ group: "Material nodes",
1427
+ combo: "Home",
1428
+ does: "Fit the whole graph",
1429
+ probe: probe("Home"),
1430
+ materialGraph: true,
1431
+ action: "graphFrame",
1432
+ },
1433
+ {
1434
+ group: "Material nodes",
1435
+ combo: "F",
1436
+ does: "Fit the whole graph",
1437
+ probe: probe("f"),
1438
+ materialGraph: true,
1439
+ action: "graphFrame",
1440
+ },
1441
+ {
1442
+ group: "Material nodes",
1443
+ combo: "T",
1444
+ does: "Tidy the graph, or just the selection",
1445
+ probe: probe("t"),
1446
+ materialGraph: true,
1447
+ action: "graphTidy",
1448
+ menuId: "graph.tidy",
1449
+ },
1450
+ {
1451
+ group: "Material nodes",
1452
+ combo: "Arrows",
1453
+ does: "Nudge the selected nodes 8 px (⇧ = 80)",
1454
+ probe: probe("ArrowRight"),
1455
+ materialGraph: true,
1456
+ action: "graphNudge",
1457
+ },
1458
+ {
1459
+ group: "Material nodes",
1460
+ combo: "Del",
1461
+ does: "Delete the selected nodes and wires",
1462
+ probe: probe("Delete"),
1463
+ materialGraph: true,
1464
+ action: "graphDelete",
1465
+ menuId: "graph.delete",
1466
+ },
1467
+ {
1468
+ group: "Material nodes",
1469
+ combo: "Esc",
1470
+ does: "Close the palette, then the selection, then one subgraph level, then the editor",
1471
+ probe: probe("Escape"),
1472
+ materialGraph: true,
1473
+ action: "graphEscape",
1474
+ },
1475
+ ];
1476
+
1477
+ /** By menu id, over BOTH tables: a node menu row prints its accelerator the
1478
+ * same way a layer menu row does. Built once — both tables are constants.
1479
+ * The storyboard rows name no menu row, so they are not indexed. */
1480
+ const BY_MENU_ID: ReadonlyMap<string, ShortcutRow> = new Map(
1481
+ [...SHORTCUTS, ...MATERIAL_SHORTCUTS].flatMap((row) =>
1482
+ row.menuId === undefined ? [] : [[row.menuId, row] as const],
1483
+ ),
1484
+ );
1485
+
1486
+ /**
1487
+ * Is this a Mac? Read ONCE — the platform does not change mid-session, and a
1488
+ * per-render probe would put a `navigator` touch inside every menu build.
1489
+ *
1490
+ * `navigator.platform` is deprecated but it is the only synchronous answer that
1491
+ * works in a plain iframe, and a wrong guess here costs a glyph rather than a
1492
+ * behaviour. Anything that is not a Mac gets the Windows/Linux spelling, which
1493
+ * is the right default for the one that is not a Mac.
1494
+ */
1495
+ const IS_MAC =
1496
+ typeof navigator !== "undefined" &&
1497
+ /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? "");
1498
+
1499
+ /**
1500
+ * A combo in the spelling THIS machine's keyboard uses.
1501
+ *
1502
+ * THE PLATFORM BRANCH THE OLD COMMENT PROMISED. It used to say there was none,
1503
+ * deliberately, and that the whole chrome should take it at once or not at all —
1504
+ * because a menu printing `Ctrl+Z` beside a manual printing ⌘Z is a second
1505
+ * vocabulary for one key rather than a fix. That was right, and this is the
1506
+ * "at once": both readers, the menus through `shortcutFor` and the manual
1507
+ * through `HelpSection`, come through here, and the manual's footnote about ⌘
1508
+ * meaning Ctrl is gone because the table now says Ctrl itself.
1509
+ *
1510
+ * The glyphs stay the authored form in `SHORTCUTS`, so there is still exactly
1511
+ * one place a combo is written down. This is a rendering step, not a fork.
1512
+ *
1513
+ * A glyph NOT followed by a key is prose (`"⌘ and a click"`), so it becomes the
1514
+ * bare word rather than a dangling `Ctrl+`.
1515
+ */
1516
+ export function formatCombo(combo: string, mac: boolean = IS_MAC): string {
1517
+ if (mac) return combo;
1518
+ return combo
1519
+ .replace(/⌘(?=\S)/g, "Ctrl+")
1520
+ .replace(/⌘/g, "Ctrl")
1521
+ .replace(/⇧(?=\S)/g, "Shift+")
1522
+ .replace(/⇧/g, "Shift")
1523
+ .replace(/⌥(?=\S)/g, "Alt+")
1524
+ .replace(/⌥/g, "Alt");
1525
+ }
1526
+
1527
+ /** The key that arms a tool, in this machine's spelling ("R", "⇧L" on a Mac,
1528
+ * "Shift+L" elsewhere), or null for a tool with no binding. */
1529
+ export function toolKey(t: ShotTool, mac: boolean = IS_MAC): string | null {
1530
+ const spec = specOf(t);
1531
+ return spec.key === null ? null : formatCombo(`${spec.shift === true ? "⇧" : ""}${spec.key.toUpperCase()}`, mac);
1532
+ }
1533
+
1534
+ /**
1535
+ * A row's chord in this machine's spelling: its own `mac` chord on a Mac when
1536
+ * it has one (the keys differ there, not only the glyphs), `combo` otherwise.
1537
+ * Every reader goes through here, so the menus and the manual agree.
1538
+ */
1539
+ export function comboOf(row: ShortcutRow, mac: boolean = IS_MAC): string {
1540
+ return mac && row.mac !== undefined ? row.mac.combo : formatCombo(row.combo, mac);
1541
+ }
1542
+
1543
+ /** The combo string for a menu row, in this machine's spelling. */
1544
+ export function shortcutFor(menuId: string): string | undefined {
1545
+ const row = BY_MENU_ID.get(menuId);
1546
+ return row === undefined ? undefined : comboOf(row);
1547
+ }
1548
+
1549
+ /**
1550
+ * The combo for an ACTION, in this machine's spelling, for a refusal that has
1551
+ * to name a key.
1552
+ *
1553
+ * `shortcutFor` cannot answer it: a binding only has a menu id when a menu row
1554
+ * prints it, and the one caller here is a sentence rather than a row. Typing
1555
+ * the glyphs into that sentence instead would be a second place a combo is
1556
+ * written down, which this file's header exists to prevent, and a sentence is
1557
+ * the surface least likely to be found when the key moves.
1558
+ *
1559
+ * The FIRST row claiming the action, which is the only row for every action a
1560
+ * refusal names today. A second row on one action would be an alias, and an
1561
+ * alias's first spelling is the one to teach.
1562
+ */
1563
+ export function comboForAction(action: KeyAction["kind"]): string | undefined {
1564
+ const row = SHORTCUTS.find((r) => r.action === action);
1565
+ return row === undefined ? undefined : comboOf(row);
1566
+ }
1567
+
1568
+ /**
1569
+ * A `title=` suffix: " (Ctrl+Z)", or "" for a row with no binding.
1570
+ *
1571
+ * Here rather than in each toolbar, because every header control was writing
1572
+ * the same three lines and several had skipped them entirely in favour of a
1573
+ * literal "⌘Z" — which is the wrong spelling on every Windows machine the
1574
+ * editor runs on. An UNBOUND row returning "" is the point: the timeline
1575
+ * toggle has no key, and the caller should not have to know that.
1576
+ */
1577
+ export function shortcutHint(menuId: string): string {
1578
+ const combo = shortcutFor(menuId);
1579
+ return combo === undefined ? "" : ` (${combo})`;
1580
+ }
1581
+
1582
+ /**
1583
+ * The exact event a menu row's key produces, for the handful of rows whose verb
1584
+ * lives INSIDE `handleKey` and nowhere else.
1585
+ *
1586
+ * ── WHY A MENU ROW WOULD EVER SYNTHESISE A KEY ──────────────────────────
1587
+ *
1588
+ * `state/menu.ts`'s rule is that every entry calls an existing store verb, so a
1589
+ * menu can never be a second implementation of an edit. Six actions have no
1590
+ * verb to call: the scoped hide triple (⌥⇧H / ⌘⇧H reach for `relativesOf` and the
1591
+ * isolation scope), the two clear-transform channels, and the zoom reset. Each
1592
+ * is a thirty-line arm of `handleKey`'s switch, and copying one into a menu
1593
+ * builder is exactly the second implementation the rule exists to prevent.
1594
+ *
1595
+ * `store.handleKey(probe)` is not a second binding site — it is a call INTO the
1596
+ * one that exists, with the event that key produces. The arm derives its own
1597
+ * context from state, so the menu row refuses for the same reasons and with the
1598
+ * same sentence the key does. `resolveKey` reads `editing` off `event.target`,
1599
+ * which a probe does not carry, so `isEditable(null)` is false and the call can
1600
+ * never be swallowed by the field the author is typing in.
1601
+ *
1602
+ * Prefer a verb wherever one exists. This is for the rows where none does.
1603
+ */
1604
+ export function probeFor(menuId: string): KeyEventLike | undefined {
1605
+ return BY_MENU_ID.get(menuId)?.probe;
1606
+ }