@hyperframes/studio 0.8.50 → 0.8.53-desktop.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 (330) hide show
  1. package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-PTEnAHhg.js} +3 -3
  2. package/dist/assets/{index-DUAIWxkI.js → index--myXp8ui.js} +1 -1
  3. package/dist/assets/{index-Dt3qBI-9.js → index-BjslOS9g.js} +1 -1
  4. package/dist/assets/index-BnXZuIJD.css +1 -0
  5. package/dist/assets/index-CNyviK_l.js +390 -0
  6. package/dist/index.css +29 -0
  7. package/dist/index.css.map +1 -0
  8. package/dist/index.d.ts +825 -24
  9. package/dist/index.html +2 -2
  10. package/dist/index.js +8911 -9853
  11. package/dist/index.js.map +1 -1
  12. package/dist/styles/tailwind-preset.js.map +1 -1
  13. package/package.json +13 -12
  14. package/src/App.tsx +145 -163
  15. package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
  16. package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
  17. package/src/captions/components/CaptionOverlay.tsx +8 -4
  18. package/src/captions/components/CaptionOverlayUtils.ts +2 -3
  19. package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
  20. package/src/captions/components/CaptionTimeline.tsx +1 -1
  21. package/src/captions/components/shared.tsx +2 -2
  22. package/src/components/AskAgentModal.tsx +5 -5
  23. package/src/components/EditorShell.selectionSync.test.tsx +9 -4
  24. package/src/components/EditorShell.tsx +59 -62
  25. package/src/components/ExternalFileConflictBanner.tsx +16 -16
  26. package/src/components/LintModal.tsx +6 -16
  27. package/src/components/MediaPreview.tsx +2 -2
  28. package/src/components/RightPanelTabs.test.tsx +151 -0
  29. package/src/components/RightPanelTabs.tsx +59 -0
  30. package/src/components/SaveQueuePausedBanner.tsx +2 -2
  31. package/src/components/StudioHeader.dom.test.tsx +181 -0
  32. package/src/components/StudioHeader.test.ts +0 -12
  33. package/src/components/StudioHeader.tsx +118 -221
  34. package/src/components/StudioLeftPanels.tsx +202 -0
  35. package/src/components/StudioOverlays.tsx +1 -1
  36. package/src/components/{StudioRightPanel.tsx → StudioRightPanels.tsx} +57 -194
  37. package/src/components/{StudioRightPanel.types.ts → StudioRightPanels.types.ts} +4 -3
  38. package/src/components/StudioToast.tsx +2 -2
  39. package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
  40. package/src/components/TimelineHistoryButtons.tsx +54 -0
  41. package/src/components/TimelineToolbar.test.tsx +8 -0
  42. package/src/components/TimelineToolbar.tsx +8 -12
  43. package/src/components/dock/Dock.test.tsx +274 -0
  44. package/src/components/dock/Dock.tsx +291 -0
  45. package/src/components/dock/dock.css +28 -0
  46. package/src/components/dock/dockAccessibility.test.ts +166 -0
  47. package/src/components/dock/dockAccessibility.ts +98 -0
  48. package/src/components/dock/dockLayout.test.ts +134 -0
  49. package/src/components/dock/dockLayout.ts +105 -0
  50. package/src/components/dock/dockLayoutSchema.ts +66 -0
  51. package/src/components/dock/dockLayoutStore.test.ts +28 -0
  52. package/src/components/dock/dockLayoutStore.ts +108 -0
  53. package/src/components/dock/dockTestHarness.ts +11 -0
  54. package/src/components/dock/panelRegistry.ts +67 -0
  55. package/src/components/editor/AnimationCard.tsx +4 -4
  56. package/src/components/editor/AnimationCardParts.tsx +6 -6
  57. package/src/components/editor/BlockParamsPanel.tsx +4 -4
  58. package/src/components/editor/BorderRadiusEditor.tsx +1 -1
  59. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  60. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  61. package/src/components/editor/DomEditOverlay.tsx +1 -1
  62. package/src/components/editor/EaseCurveSection.tsx +1 -1
  63. package/src/components/editor/EaseParamFields.tsx +5 -5
  64. package/src/components/editor/FileTree.tsx +4 -4
  65. package/src/components/editor/FileTreeIcons.tsx +1 -1
  66. package/src/components/editor/FileTreeNodes.tsx +13 -13
  67. package/src/components/editor/InlineTextToolbar.tsx +1 -1
  68. package/src/components/editor/InspectorHeaderActions.tsx +3 -3
  69. package/src/components/editor/KeyframeDiamond.tsx +1 -1
  70. package/src/components/editor/KeyframeEaseList.tsx +1 -1
  71. package/src/components/editor/LayersPanel.test.ts +97 -1
  72. package/src/components/editor/LayersPanel.tsx +7 -5
  73. package/src/components/editor/MarqueeOverlay.tsx +1 -1
  74. package/src/components/editor/OffCanvasIndicators.tsx +1 -1
  75. package/src/components/editor/PreviewGuides.tsx +1 -1
  76. package/src/components/editor/PromotableControl.tsx +2 -2
  77. package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
  78. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  79. package/src/components/editor/SnapToolbar.test.tsx +0 -3
  80. package/src/components/editor/SnapToolbar.tsx +2 -2
  81. package/src/components/editor/TimelineFxPopover.tsx +1 -1
  82. package/src/components/editor/TopologyLens.test.tsx +28 -0
  83. package/src/components/editor/TopologyLens.tsx +7 -5
  84. package/src/components/editor/Transform3DCube.tsx +2 -2
  85. package/src/components/editor/audioFxRevealTarget.ts +1 -0
  86. package/src/components/editor/domEditing.test.ts +1 -1
  87. package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
  88. package/src/components/editor/domEditingAgentPrompt.ts +4 -3
  89. package/src/components/editor/propertyPanelColor.tsx +11 -11
  90. package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
  91. package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
  92. package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
  93. package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
  94. package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
  95. package/src/components/editor/propertyPanelCommitField.tsx +1 -1
  96. package/src/components/editor/propertyPanelFill.tsx +4 -4
  97. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  98. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
  99. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  100. package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
  101. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
  102. package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
  103. package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
  104. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
  105. package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
  106. package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
  107. package/src/components/editor/propertyPanelFont.tsx +5 -5
  108. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  109. package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
  110. package/src/components/editor/propertyPanelFxControls.tsx +6 -6
  111. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  112. package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
  113. package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
  114. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  115. package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
  116. package/src/components/editor/propertyPanelSections.tsx +8 -8
  117. package/src/components/editor/useColorGradingController.test.ts +38 -0
  118. package/src/components/editor/useColorGradingController.ts +9 -1
  119. package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
  120. package/src/components/editor/useColorGradingPreviews.ts +11 -1
  121. package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
  122. package/src/components/editor/usePreviewCompositionRect.ts +21 -0
  123. package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
  124. package/src/components/feedback/feedbackTrigger.ts +0 -1
  125. package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
  126. package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
  127. package/src/components/nle/NLEContext.tsx +0 -43
  128. package/src/components/nle/NLEPreview.tsx +3 -3
  129. package/src/components/nle/PreviewOverlays.tsx +6 -6
  130. package/src/components/nle/PreviewPane.tsx +3 -3
  131. package/src/components/nle/TimelinePane.tsx +53 -67
  132. package/src/components/panels/SlideshowPanel.tsx +3 -3
  133. package/src/components/panels/SlideshowSubPanels.tsx +20 -17
  134. package/src/components/panels/VariablesBindElement.tsx +2 -2
  135. package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
  136. package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
  137. package/src/components/panels/VariablesPanel.tsx +4 -4
  138. package/src/components/panels/VariablesValueControls.tsx +2 -2
  139. package/src/components/pointerTargetSize.test.tsx +8 -0
  140. package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
  141. package/src/components/renders/RenderQueue.test.tsx +7 -0
  142. package/src/components/renders/RenderQueue.tsx +7 -7
  143. package/src/components/renders/RenderQueueItem.tsx +6 -6
  144. package/src/components/renders/RenderQueuePanel.tsx +1 -1
  145. package/src/components/sidebar/AssetCard.tsx +8 -8
  146. package/src/components/sidebar/AssetContextMenu.tsx +8 -8
  147. package/src/components/sidebar/AssetsTab.tsx +2 -2
  148. package/src/components/sidebar/AudioRow.tsx +6 -6
  149. package/src/components/sidebar/BlocksTab.tsx +6 -6
  150. package/src/components/sidebar/{LeftSidebar.masterComposition.test.tsx → CompositionsPanel.masterComposition.test.tsx} +6 -9
  151. package/src/components/sidebar/CompositionsPanel.tsx +15 -0
  152. package/src/components/sidebar/CompositionsTab.tsx +5 -5
  153. package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
  154. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  155. package/src/components/sidebar/SidebarLintButton.test.tsx +53 -0
  156. package/src/components/sidebar/SidebarLintButton.tsx +50 -0
  157. package/src/components/timelineToolbarStyles.ts +5 -0
  158. package/src/components/ui/Button.test.tsx +252 -0
  159. package/src/components/ui/Button.tsx +89 -126
  160. package/src/components/ui/IconButton.tsx +38 -0
  161. package/src/components/ui/Input.tsx +115 -0
  162. package/src/components/ui/Menu.test.tsx +374 -0
  163. package/src/components/ui/Menu.tsx +213 -0
  164. package/src/components/ui/NumberField.tsx +122 -0
  165. package/src/components/ui/Popover.tsx +74 -0
  166. package/src/components/ui/SearchInput.tsx +2 -2
  167. package/src/components/ui/Select.tsx +100 -0
  168. package/src/components/ui/Slider.tsx +153 -0
  169. package/src/components/ui/Tabs.test.tsx +98 -0
  170. package/src/components/ui/Tabs.tsx +71 -0
  171. package/src/components/ui/Toggle.tsx +63 -0
  172. package/src/components/ui/Tooltip.test.tsx +110 -0
  173. package/src/components/ui/Tooltip.tsx +38 -112
  174. package/src/components/ui/cn.test.ts +30 -0
  175. package/src/components/ui/cn.ts +25 -0
  176. package/src/components/ui/index.ts +29 -2
  177. package/src/components/ui/mountHost.testHelpers.ts +24 -0
  178. package/src/components/ui/valueControls.test.tsx +524 -0
  179. package/src/contexts/PanelLayoutContext.tsx +3 -57
  180. package/src/hooks/appHotkeysDispatch.ts +3 -4
  181. package/src/hooks/timelineEditPermission.ts +31 -0
  182. package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +0 -2
  183. package/src/hooks/useAppHotkeys.test.ts +0 -1
  184. package/src/hooks/useAppHotkeys.textEditing.test.tsx +0 -3
  185. package/src/hooks/useAppHotkeys.ts +21 -84
  186. package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
  187. package/src/hooks/useCaptionDetection.ts +3 -1
  188. package/src/hooks/useDomEditPreviewSync.ts +4 -6
  189. package/src/hooks/useDomEditSession.ts +0 -7
  190. package/src/hooks/useDomEditWiring.ts +4 -9
  191. package/src/hooks/useEditHistoryActions.test.tsx +90 -0
  192. package/src/hooks/useEditHistoryActions.ts +106 -0
  193. package/src/hooks/useFileManager.ts +21 -123
  194. package/src/hooks/useLintModal.test.tsx +65 -0
  195. package/src/hooks/useLintModal.ts +3 -0
  196. package/src/hooks/usePanelLayout.test.ts +108 -296
  197. package/src/hooks/usePanelLayout.ts +50 -255
  198. package/src/hooks/usePersistentEditHistory.ts +1 -1
  199. package/src/hooks/useProjectFileWriter.ts +150 -0
  200. package/src/hooks/useRightPanelIntents.test.ts +181 -0
  201. package/src/hooks/useRightPanelIntents.ts +63 -0
  202. package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
  203. package/src/hooks/useSdkSession.ts +43 -10
  204. package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
  205. package/src/hooks/useServerConnection.ts +43 -10
  206. package/src/hooks/useSlideshowTabState.test.ts +9 -31
  207. package/src/hooks/useSlideshowTabState.ts +6 -19
  208. package/src/hooks/useStudioContextValue.test.ts +6 -18
  209. package/src/hooks/useStudioContextValue.ts +5 -26
  210. package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
  211. package/src/hooks/useTimelineAssetDropOps.ts +52 -11
  212. package/src/hooks/useTimelineDeleteOps.test.tsx +111 -0
  213. package/src/hooks/useTimelineEditing.test.tsx +322 -3
  214. package/src/hooks/useTimelineEditing.ts +103 -16
  215. package/src/hooks/useTimelineEditingTypes.ts +3 -0
  216. package/src/hooks/useTrackPendingTimelineEdit.test.tsx +62 -0
  217. package/src/hooks/useTrackPendingTimelineEdit.ts +22 -0
  218. package/src/hostMount.test.tsx +222 -0
  219. package/src/index.ts +87 -0
  220. package/src/main.tsx +9 -3
  221. package/src/player/components/AudioWaveform.tsx +1 -1
  222. package/src/player/components/AutomationSelectionMenu.tsx +3 -3
  223. package/src/player/components/ClipContextMenu.tsx +3 -3
  224. package/src/player/components/CompositionThumbnail.test.ts +0 -3
  225. package/src/player/components/CompositionThumbnail.tsx +3 -4
  226. package/src/player/components/EditModal.tsx +1 -1
  227. package/src/player/components/ImageThumbnail.tsx +1 -1
  228. package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
  229. package/src/player/components/LayerDisclosureRow.tsx +2 -2
  230. package/src/player/components/PlayerControls.tsx +1 -1
  231. package/src/player/components/ShortcutsPanel.tsx +9 -9
  232. package/src/player/components/SpeedMenu.tsx +3 -5
  233. package/src/player/components/Timeline.tsx +3 -2
  234. package/src/player/components/TimelineCanvas.tsx +21 -14
  235. package/src/player/components/TimelineClip.tsx +0 -1
  236. package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
  237. package/src/player/components/TimelineEmptyState.tsx +5 -5
  238. package/src/player/components/TimelineFxButton.tsx +6 -6
  239. package/src/player/components/TimelineGroupHeader.tsx +5 -5
  240. package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
  241. package/src/player/components/TimelineGroupRow.tsx +1 -1
  242. package/src/player/components/TimelineLanes.test.tsx +32 -0
  243. package/src/player/components/TimelineLanes.tsx +2 -5
  244. package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
  245. package/src/player/components/TimelineRuler.tsx +1 -1
  246. package/src/player/components/TimelineShortcutHint.tsx +1 -1
  247. package/src/player/components/TimelineTrackHeader.test.tsx +26 -5
  248. package/src/player/components/TimelineTrackHeader.tsx +2 -2
  249. package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
  250. package/src/player/components/TimelineTypes.ts +2 -1
  251. package/src/player/components/TrackGapContextMenu.tsx +1 -1
  252. package/src/player/components/VideoThumbnail.tsx +4 -2
  253. package/src/player/components/VolumeControl.tsx +2 -2
  254. package/src/player/components/timelineCallbacks.ts +8 -8
  255. package/src/player/components/timelineClipDragPreview.ts +51 -27
  256. package/src/player/components/timelineClipDragTypes.ts +3 -0
  257. package/src/player/components/timelineDragDrop.test.tsx +61 -1
  258. package/src/player/components/timelineDragDrop.ts +57 -15
  259. package/src/player/components/timelineLaneProps.ts +3 -0
  260. package/src/player/components/timelineSnapping.test.ts +27 -0
  261. package/src/player/components/timelineSnapping.ts +14 -1
  262. package/src/player/components/timelineTheme.test.ts +4 -4
  263. package/src/player/components/timelineTheme.ts +30 -30
  264. package/src/player/components/trackHeaderLabelRows.tsx +4 -4
  265. package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
  266. package/src/player/components/useTimelineClipDrag.ts +30 -3
  267. package/src/player/lib/describeClips.ts +3 -54
  268. package/src/player/lib/playbackShortcuts.ts +2 -0
  269. package/src/player/lib/timelinePerformanceDiagnostics.test.ts +3 -2
  270. package/src/player/lib/timelinePerformanceDiagnostics.ts +1 -1
  271. package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
  272. package/src/player/store/previewIframeStore.test.tsx +29 -0
  273. package/src/player/store/previewIframeStore.ts +25 -0
  274. package/src/renderQueueExports.test.tsx +58 -0
  275. package/src/styles/classCandidates.test.ts +105 -0
  276. package/src/styles/classCandidates.ts +281 -0
  277. package/src/styles/hex-baseline.json +84 -0
  278. package/src/styles/hexRatchet.test.ts +201 -0
  279. package/src/styles/iconTokens.ts +20 -0
  280. package/src/styles/palette-reset.css +6 -0
  281. package/src/styles/studio.css +24 -21
  282. package/src/styles/styleSources.test.ts +20 -0
  283. package/src/styles/styleSources.ts +47 -0
  284. package/src/styles/tailwind-preset.shared.js +3 -0
  285. package/src/styles/theme.css +270 -0
  286. package/src/styles/theme.test.ts +183 -0
  287. package/src/styles/tokenGate.test.ts +109 -0
  288. package/src/utils/sourcePatcher.ts +1 -1
  289. package/src/utils/studioHelpers.test.ts +18 -0
  290. package/src/utils/studioHelpers.ts +11 -8
  291. package/src/utils/studioUiPreferences.test.ts +10 -16
  292. package/src/utils/studioUiPreferences.ts +7 -16
  293. package/src/utils/timelineDropTrackInsert.test.ts +134 -0
  294. package/src/utils/timelineDropTrackInsert.ts +83 -0
  295. package/src/utils/typingTarget.ts +4 -0
  296. package/dist/assets/index-n2ml4tUn.js +0 -442
  297. package/dist/assets/index-pO6p0H27.css +0 -1
  298. package/src/components/PanelTabButton.tsx +0 -31
  299. package/src/components/StudioLeftSidebar.tsx +0 -204
  300. package/src/components/nle/TimelineResizeDivider.tsx +0 -101
  301. package/src/components/sidebar/LeftSidebar.storage.test.ts +0 -28
  302. package/src/components/sidebar/LeftSidebar.tsx +0 -334
  303. package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
  304. package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
  305. package/src/components/storyboard/FramePoster.test.tsx +0 -61
  306. package/src/components/storyboard/FramePoster.tsx +0 -86
  307. package/src/components/storyboard/StoryboardDirection.tsx +0 -45
  308. package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
  309. package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
  310. package/src/components/storyboard/StoryboardGrid.tsx +0 -55
  311. package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
  312. package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
  313. package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
  314. package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
  315. package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
  316. package/src/components/storyboard/StoryboardView.tsx +0 -169
  317. package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
  318. package/src/components/storyboard/frameComments.test.ts +0 -95
  319. package/src/components/storyboard/frameComments.ts +0 -115
  320. package/src/components/storyboard/frameStatus.ts +0 -36
  321. package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
  322. package/src/components/storyboard/storyboardReviewStage.ts +0 -45
  323. package/src/components/storyboard/useFrameComments.ts +0 -98
  324. package/src/contexts/ViewModeContext.tsx +0 -142
  325. package/src/hooks/useInspectorSplitResize.ts +0 -51
  326. package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
  327. package/src/hooks/useProjectSignaturePoll.ts +0 -68
  328. package/src/hooks/useStoryboard.ts +0 -99
  329. package/src/utils/fitPanels.test.ts +0 -164
  330. package/src/utils/fitPanels.ts +0 -151
@@ -0,0 +1,109 @@
1
+ // The token gate (R11, AE1). Tailwind silently drops a class it cannot compile, so `rounded-button`
2
+ // renders as no radius and nothing goes red. This compiles Studio's real entry stylesheet with every
3
+ // class the source claims and reports any candidate producing no selector as `file: class`.
4
+ // Tailwind is the only judge, so there is no allowlist; a failing class is fixed, never excused.
5
+
6
+ import { readFileSync } from "node:fs";
7
+ import path from "node:path";
8
+ import { compile } from "tailwindcss";
9
+ import { describe, expect, it } from "vitest";
10
+ import { extractClassCandidates } from "./classCandidates";
11
+ import { listSourceFiles, loadStylesheet, STYLES_DIR } from "./styleSources";
12
+
13
+ // A class selector in the emitted CSS, escapes removed: `.hover\:bg-surface\/50` reads back as its candidate.
14
+ const CLASS_SELECTOR = /\.((?:\\.|[^\s.,{}()>+~:[\]#'"*/\\])+)/g;
15
+
16
+ // Tailwind's `group`/`peer` variant markers (`group/card`) produce no rule of their own, so no
17
+ // compiled selector can vouch for them. Nothing else is exempt.
18
+ const MARKERS = new Set(["group", "peer"]);
19
+
20
+ // `hf-` is HyperFrames' reserved prefix for semantic hooks, not utilities, so Tailwind is the
21
+ // wrong judge of them.
22
+ const HOOK_PREFIX = /^hf-/;
23
+
24
+ /** Compile Studio's stylesheet and return every class it can produce. */
25
+ async function resolvable(candidates: string[]): Promise<Set<string>> {
26
+ const compiled = await compile(readFileSync(path.join(STYLES_DIR, "studio.css"), "utf8"), {
27
+ base: STYLES_DIR,
28
+ loadStylesheet,
29
+ });
30
+ const css = compiled.build(candidates);
31
+ const selectors = new Set<string>();
32
+ for (const [, selector] of css.matchAll(CLASS_SELECTOR)) {
33
+ selectors.add(selector.replace(/\\(.)/g, "$1"));
34
+ }
35
+ return selectors;
36
+ }
37
+
38
+ // `file: class` for every class the sources claim that Tailwind cannot make. Sources are passed
39
+ // in so the fixtures exercise the same code path as the tree.
40
+ async function unresolved(sources: ReadonlyMap<string, string>): Promise<string[]> {
41
+ const claims = new Map<string, string[]>();
42
+ for (const [file, source] of sources) {
43
+ for (const candidate of extractClassCandidates(source)) {
44
+ const seen = claims.get(candidate.base);
45
+ if (seen) seen.push(file);
46
+ else claims.set(candidate.base, [file]);
47
+ }
48
+ }
49
+ const produced = await resolvable([...claims.keys()]);
50
+ const failures: string[] = [];
51
+ for (const [candidate, files] of claims) {
52
+ if (produced.has(candidate) || MARKERS.has(candidate.split("/")[0])) continue;
53
+ if (HOOK_PREFIX.test(candidate)) continue;
54
+ for (const file of files) failures.push(`${file}: ${candidate}`);
55
+ }
56
+ return failures.sort();
57
+ }
58
+
59
+ // Every file the gate reads, mirroring the `@source` globs in `studio.css`. Tests are excluded:
60
+ // a fixture in a test is not markup.
61
+ function studioSources(): Map<string, string> {
62
+ return listSourceFiles((file) => /\.tsx?$/.test(file) && !/\.test\.tsx?$/.test(file));
63
+ }
64
+
65
+ describe("token gate", () => {
66
+ it("names the file and the class when a class resolves to nothing", async () => {
67
+ // AE1: `rounded-hologram` looks like a token name but is defined nowhere. The map is named
68
+ // `buttonSizes`, not `sizeStyles`: the `cn()` that consumes it puts it in front of the gate.
69
+ const failures = await unresolved(
70
+ new Map([
71
+ [
72
+ "ui/Button.tsx",
73
+ `const buttonSizes = { md: "h-8 rounded-hologram text-mega" };
74
+ const Button = ({ size }) => <button className={cn(buttonSizes[size])} />;`,
75
+ ],
76
+ ]),
77
+ );
78
+
79
+ expect(failures).toEqual(["ui/Button.tsx: rounded-hologram", "ui/Button.tsx: text-mega"]);
80
+ });
81
+
82
+ it("accepts an arbitrary value and reports it as one", async () => {
83
+ const source = `<div className="text-[11px]" />`;
84
+
85
+ expect(await unresolved(new Map([["a.tsx", source]]))).toEqual([]);
86
+ expect(extractClassCandidates(source).filter((c) => c.arbitrary)).toHaveLength(1);
87
+ });
88
+
89
+ it("accepts variants, static utilities and Studio's own CSS classes", async () => {
90
+ // `timeline-clip` is a plain rule in `studio.css`, not a utility.
91
+ const source = `<div className="hover:bg-surface/50 text-center border-dashed timeline-clip" />`;
92
+
93
+ expect(await unresolved(new Map([["a.tsx", source]]))).toEqual([]);
94
+ });
95
+
96
+ it("leaves Studio's own hook prefix and Tailwind's variant markers alone", async () => {
97
+ const source = `<div className="group group/card peer hf-fx-node" />`;
98
+
99
+ expect(await unresolved(new Map([["a.tsx", source]]))).toEqual([]);
100
+ });
101
+
102
+ it("claims nothing for a template chunk that touches an interpolation", async () => {
103
+ expect(await unresolved(new Map([["a.tsx", "<div className={`w-${i}`} />"]]))).toEqual([]);
104
+ });
105
+
106
+ it("resolves every class Studio's own source claims", async () => {
107
+ expect(await unresolved(studioSources())).toEqual([]);
108
+ });
109
+ });
@@ -3,7 +3,7 @@
3
3
  * Handles inline style updates, attribute changes, and text content.
4
4
  */
5
5
 
6
- function escapeRegex(s: string): string {
6
+ export function escapeRegex(s: string): string {
7
7
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
8
  }
9
9
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { afterEach, describe, expect, it, vi } from "vitest";
4
4
  import {
5
+ historyTooltipLabel,
5
6
  findMatchingTimelineElementId,
6
7
  findTimelineIdByAncestor,
7
8
  resolveDroppedAssetDimensions,
@@ -217,3 +218,20 @@ describe("resolveElementTrack", () => {
217
218
  expect(resolveElementTrack({ authoredTrack: undefined, track: 3.6 })).toBe(4);
218
219
  });
219
220
  });
221
+
222
+ describe("historyTooltipLabel", () => {
223
+ // A disabled control gets no pointer events, so its tooltip cannot be opened
224
+ // to read. The wording is checked here instead of through the DOM.
225
+ it("names the action that would be undone", () => {
226
+ expect(historyTooltipLabel("undo", "Move layer")).toMatch(/^Undo Move layer \(.+\)$/);
227
+ });
228
+
229
+ it("names the action that would be redone", () => {
230
+ expect(historyTooltipLabel("redo", "Move layer")).toMatch(/^Redo Move layer \(.+\)$/);
231
+ });
232
+
233
+ it("keeps the shortcut when the history is empty", () => {
234
+ expect(historyTooltipLabel("undo", undefined)).toMatch(/^Undo \(.+\)$/);
235
+ expect(historyTooltipLabel("redo", null)).toMatch(/^Redo \(.+\)$/);
236
+ });
237
+ });
@@ -22,13 +22,6 @@ export type RightPanelTab =
22
22
  | "block-params"
23
23
  | "slideshow"
24
24
  | "variables";
25
- export type RightInspectorPane = "layers" | "design";
26
-
27
- export interface RightInspectorPanes {
28
- layers: boolean;
29
- design: boolean;
30
- }
31
-
32
25
  export interface AgentModalAnchorPoint {
33
26
  x: number;
34
27
  y: number;
@@ -119,13 +112,23 @@ export function shouldIgnoreHistoryShortcut(target: EventTarget | null): boolean
119
112
  return isTypingTarget(target);
120
113
  }
121
114
 
122
- export function getHistoryShortcutLabel(action: "undo" | "redo"): string {
115
+ function getHistoryShortcutLabel(action: "undo" | "redo"): string {
123
116
  const isMac =
124
117
  typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/i.test(navigator.platform);
125
118
  const modifier = isMac ? "Cmd" : "Ctrl";
126
119
  return action === "undo" ? `${modifier}+Z` : `${modifier}+Shift+Z`;
127
120
  }
128
121
 
122
+ /** The Undo / Redo tooltip: the shortcut always, the last action's name when there is one. */
123
+ export function historyTooltipLabel(
124
+ action: "undo" | "redo",
125
+ lastAction: string | null | undefined,
126
+ ): string {
127
+ const shortcut = getHistoryShortcutLabel(action);
128
+ const verb = action === "undo" ? "Undo" : "Redo";
129
+ return lastAction ? `${verb} ${lastAction} (${shortcut})` : `${verb} (${shortcut})`;
130
+ }
131
+
129
132
  export type ElementMatchSelection = Pick<
130
133
  DomEditSelection,
131
134
  | "id"
@@ -20,7 +20,6 @@ describe("studio UI preferences", () => {
20
20
  const storage = createStorage();
21
21
 
22
22
  writeStudioUiPreferences({ timelineVisible: false }, storage);
23
- writeStudioUiPreferences({ leftWidth: 384, rightWidth: 424 }, storage);
24
23
  writeStudioUiPreferences({ playbackRate: 1.5 }, storage);
25
24
  writeStudioUiPreferences({ audioMuted: true }, storage);
26
25
  writeStudioUiPreferences({ audioVolume: 0.4 }, storage);
@@ -28,8 +27,6 @@ describe("studio UI preferences", () => {
28
27
 
29
28
  expect(readStudioUiPreferences(storage)).toEqual({
30
29
  timelineVisible: false,
31
- leftWidth: 384,
32
- rightWidth: 424,
33
30
  playbackRate: 1.5,
34
31
  audioMuted: true,
35
32
  audioVolume: 0.4,
@@ -57,9 +54,6 @@ describe("studio UI preferences", () => {
57
54
  storage.setItem(
58
55
  "hf-studio-ui-preferences",
59
56
  JSON.stringify({
60
- leftCollapsed: "yes",
61
- leftWidth: "wide",
62
- rightWidth: Number.NaN,
63
57
  timelineVisible: true,
64
58
  playbackRate: Number.NaN,
65
59
  audioMuted: "false",
@@ -119,28 +113,28 @@ describe("thumbnailMode preference", () => {
119
113
  describe("per-project scoping", () => {
120
114
  it("keeps two projects' preferences independent", () => {
121
115
  const storage = createStorage();
122
- writeStudioUiPreferences({ leftWidth: 300 }, storage, "alpha");
123
- writeStudioUiPreferences({ leftWidth: 500 }, storage, "beta");
116
+ writeStudioUiPreferences({ playbackRate: 1.25 }, storage, "alpha");
117
+ writeStudioUiPreferences({ playbackRate: 2 }, storage, "beta");
124
118
 
125
- expect(readStudioUiPreferences(storage, "alpha").leftWidth).toBe(300);
126
- expect(readStudioUiPreferences(storage, "beta").leftWidth).toBe(500);
119
+ expect(readStudioUiPreferences(storage, "alpha").playbackRate).toBe(1.25);
120
+ expect(readStudioUiPreferences(storage, "beta").playbackRate).toBe(2);
127
121
  });
128
122
 
129
123
  it("a project with nothing saved yet inherits the pre-scoping global entry once", () => {
130
124
  const storage = createStorage();
131
- storage.setItem("hf-studio-ui-preferences", JSON.stringify({ leftWidth: 600 }));
125
+ storage.setItem("hf-studio-ui-preferences", JSON.stringify({ playbackRate: 0.5 }));
132
126
 
133
- expect(readStudioUiPreferences(storage, "gamma").leftWidth).toBe(600);
127
+ expect(readStudioUiPreferences(storage, "gamma").playbackRate).toBe(0.5);
134
128
  });
135
129
 
136
130
  it("stops inheriting the global entry once the project has its own write", () => {
137
131
  const storage = createStorage();
138
- storage.setItem("hf-studio-ui-preferences", JSON.stringify({ leftWidth: 600 }));
132
+ storage.setItem("hf-studio-ui-preferences", JSON.stringify({ playbackRate: 0.5 }));
139
133
 
140
- writeStudioUiPreferences({ leftWidth: 250 }, storage, "gamma");
134
+ writeStudioUiPreferences({ playbackRate: 1.75 }, storage, "gamma");
141
135
 
142
- expect(readStudioUiPreferences(storage, "gamma").leftWidth).toBe(250);
143
- expect(readStudioUiPreferences(storage, null).leftWidth).toBe(600);
136
+ expect(readStudioUiPreferences(storage, "gamma").playbackRate).toBe(1.75);
137
+ expect(readStudioUiPreferences(storage, null).playbackRate).toBe(0.5);
144
138
  });
145
139
  });
146
140
 
@@ -1,3 +1,6 @@
1
+ import type { SerializedDockview } from "dockview-react";
2
+ import { parseDockLayout } from "../components/dock/dockLayoutSchema";
3
+
1
4
  export interface StoredPreviewZoomState {
2
5
  zoomPercent: number;
3
6
  panX: number;
@@ -7,11 +10,7 @@ export interface StoredPreviewZoomState {
7
10
  export type TimelineTimeDisplayMode = "time" | "frame";
8
11
 
9
12
  export interface StudioUiPreferences {
10
- leftCollapsed?: boolean;
11
- leftWidth?: number;
12
- rightWidth?: number;
13
13
  timelineVisible?: boolean;
14
- timelineHeight?: number;
15
14
  playbackRate?: number;
16
15
  audioMuted?: boolean;
17
16
  audioVolume?: number;
@@ -47,6 +46,8 @@ export interface StudioUiPreferences {
47
46
  * intentionally scoped to one mount.
48
47
  */
49
48
  agentToolsEnabled?: boolean;
49
+ /** The dock's serialized panel tree; parsed by `parseDockLayout` on read. */
50
+ dockLayout?: SerializedDockview;
50
51
  }
51
52
 
52
53
  const STUDIO_UI_PREFERENCES_KEY = "hf-studio-ui-preferences";
@@ -78,21 +79,9 @@ function readStorage(storage: Storage | null, key: string): StudioUiPreferences
78
79
  if (!isRecord(parsed)) return {};
79
80
 
80
81
  const preferences: StudioUiPreferences = {};
81
- if (typeof parsed.leftCollapsed === "boolean") {
82
- preferences.leftCollapsed = parsed.leftCollapsed;
83
- }
84
- if (typeof parsed.leftWidth === "number" && Number.isFinite(parsed.leftWidth)) {
85
- preferences.leftWidth = parsed.leftWidth;
86
- }
87
- if (typeof parsed.rightWidth === "number" && Number.isFinite(parsed.rightWidth)) {
88
- preferences.rightWidth = parsed.rightWidth;
89
- }
90
82
  if (typeof parsed.timelineVisible === "boolean") {
91
83
  preferences.timelineVisible = parsed.timelineVisible;
92
84
  }
93
- if (typeof parsed.timelineHeight === "number" && Number.isFinite(parsed.timelineHeight)) {
94
- preferences.timelineHeight = parsed.timelineHeight;
95
- }
96
85
  if (typeof parsed.playbackRate === "number" && Number.isFinite(parsed.playbackRate)) {
97
86
  preferences.playbackRate = parsed.playbackRate;
98
87
  }
@@ -169,6 +158,8 @@ function readStorage(storage: Storage | null, key: string): StudioUiPreferences
169
158
  if (typeof parsed.agentToolsEnabled === "boolean") {
170
159
  preferences.agentToolsEnabled = parsed.agentToolsEnabled;
171
160
  }
161
+ const dockLayout = parseDockLayout(parsed.dockLayout);
162
+ if (dockLayout) preferences.dockLayout = dockLayout;
172
163
  return preferences;
173
164
  } catch {
174
165
  return {};
@@ -0,0 +1,134 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { TimelineElement } from "../player";
3
+ import {
4
+ applyTrackRenumbers,
5
+ planDropTrackInsert,
6
+ resolveDropTrack,
7
+ } from "./timelineDropTrackInsert";
8
+
9
+ function clip(id: string, track: number, start: number): TimelineElement {
10
+ return {
11
+ id,
12
+ key: id,
13
+ tag: "div",
14
+ start,
15
+ duration: 2,
16
+ track,
17
+ authoredTrack: track,
18
+ hfId: `hf-${id}`,
19
+ domId: id,
20
+ // Main-document elements carry no sourceFile, as in production.
21
+ };
22
+ }
23
+
24
+ const trackOrder = [0, 1, 2];
25
+ const dropped = { id: "new", tag: "img", start: 5, duration: 3 };
26
+ const elements = [clip("a", 0, 0), clip("b", 1, 0), clip("c", 2, 0)];
27
+ const source = elements
28
+ .map(
29
+ (e) =>
30
+ `<div data-hf-id="${e.hfId}" id="${e.id}" data-start="0" data-track-index="${e.track}"></div>`,
31
+ )
32
+ .join("\n");
33
+
34
+ describe("planDropTrackInsert", () => {
35
+ it("opens a lane between two rows and pushes the lanes below down", () => {
36
+ const plan = planDropTrackInsert({ elements, trackOrder, insertRow: 1, dropped });
37
+ expect(plan?.track).toBe(1);
38
+ expect(plan?.renumbers.map((r) => [r.element.id, r.track])).toEqual([
39
+ ["b", 2],
40
+ ["c", 3],
41
+ ]);
42
+ });
43
+
44
+ it("opens a lane above the first row", () => {
45
+ const plan = planDropTrackInsert({ elements, trackOrder, insertRow: 0, dropped });
46
+ expect(plan?.track).toBe(0);
47
+ expect(plan?.renumbers.map((r) => [r.element.id, r.track])).toEqual([
48
+ ["a", 1],
49
+ ["b", 2],
50
+ ["c", 3],
51
+ ]);
52
+ });
53
+
54
+ it("still renumbers when the peers' own sourceFile is a real path, not undefined", () => {
55
+ const subComp = elements.map((e) => ({ ...e, sourceFile: "sub.html" }));
56
+ const plan = planDropTrackInsert({ elements: subComp, trackOrder, insertRow: 1, dropped });
57
+ expect(plan?.track).toBe(1);
58
+ expect(plan?.renumbers.map((r) => [r.element.id, r.track])).toEqual([
59
+ ["b", 2],
60
+ ["c", 3],
61
+ ]);
62
+ });
63
+ });
64
+
65
+ describe("planDropTrackInsert against the display row order", () => {
66
+ it("counts an audio-group anchor row the way a clip drag does", () => {
67
+ // Rows shown: group anchor (-0.5), track 0, track 1. Row boundary 2 is between 0 and 1.
68
+ const grouped = elements.slice(0, 2).map((e) => (e.id === "a" ? { ...e, audioGroup: "g" } : e));
69
+ const plan = planDropTrackInsert({
70
+ elements: grouped,
71
+ trackOrder: [-0.5, 0, 1],
72
+ insertRow: 2,
73
+ dropped,
74
+ });
75
+ expect(plan?.track).toBe(1);
76
+ expect(plan?.renumbers.map((r) => [r.element.id, r.track])).toEqual([["b", 2]]);
77
+ });
78
+ });
79
+
80
+ describe("applyTrackRenumbers", () => {
81
+ it("rewrites only the renumbered clips' data-track-index", () => {
82
+ const plan = planDropTrackInsert({ elements, trackOrder, insertRow: 1, dropped });
83
+ const out = applyTrackRenumbers(source, plan!);
84
+ const tracks = [...out.matchAll(/id="(\w)"[^>]*data-track-index="(\d+)"/g)].map((m) => [
85
+ m[1],
86
+ +m[2],
87
+ ]);
88
+ expect(tracks).toEqual([
89
+ ["a", 0],
90
+ ["b", 2],
91
+ ["c", 3],
92
+ ]);
93
+ });
94
+
95
+ it("throws when a clip's opening tag is missing", () => {
96
+ const plan = planDropTrackInsert({ elements, trackOrder, insertRow: 1, dropped });
97
+ expect(() => applyTrackRenumbers("<div></div>", plan!)).toThrow(/Cannot renumber/);
98
+ });
99
+ });
100
+
101
+ describe("resolveDropTrack", () => {
102
+ it("keeps the aimed lane and the source when no insert is asked for", () => {
103
+ const out = resolveDropTrack({
104
+ source,
105
+ elements,
106
+ placement: { track: 2 },
107
+ dropped,
108
+ });
109
+ expect(out).toEqual({ source, track: 2 });
110
+ });
111
+
112
+ it("returns the new lane and the source with the lanes below pushed down", () => {
113
+ const out = resolveDropTrack({
114
+ source,
115
+ elements,
116
+ placement: { track: 1, insertRow: 1, trackOrder },
117
+ dropped,
118
+ });
119
+ expect(out.track).toBe(1);
120
+ expect(out.source).toContain('id="b" data-start="0" data-track-index="2"');
121
+ });
122
+
123
+ it("refuses to insert when a locked clip would have to move", () => {
124
+ const locked = elements.map((e) => (e.id === "b" ? { ...e, timelineLocked: true } : e));
125
+ expect(() =>
126
+ resolveDropTrack({
127
+ source,
128
+ elements: locked,
129
+ placement: { track: 1, insertRow: 1, trackOrder },
130
+ dropped,
131
+ }),
132
+ ).toThrow(/locked/);
133
+ });
134
+ });
@@ -0,0 +1,83 @@
1
+ import type { TimelineElement } from "../player";
2
+ import { layoutAfterTrackInsert } from "../player/components/timelineDragLanding";
3
+ import { canMoveTimelineElement } from "../player/components/timelineAuthoredMoveTarget";
4
+ import type { TimelineDropPlacement } from "../player/components/timelineCallbacks";
5
+ import { applyPatchByTarget, readAttributeByTarget } from "./sourcePatcher";
6
+ import { buildPatchTarget } from "../hooks/timelineEditingHelpers";
7
+ import { formatTimelineAttributeNumber } from "../player/components/timelineEditing";
8
+
9
+ export interface DropTrackInsertPlan {
10
+ /** Lane the dropped clip is written on. */
11
+ track: number;
12
+ /** Existing clips whose lane changes to make room. */
13
+ renumbers: Array<{ element: TimelineElement; track: number }>;
14
+ }
15
+
16
+ /** Plan a new lane at boundary `insertRow` with the renumber a clip drag into a gutter uses.
17
+ * Null when a locked clip would have to move. */
18
+ export function planDropTrackInsert(input: {
19
+ elements: TimelineElement[];
20
+ /** The row order the timeline shows (anchor rows included), which `insertRow` indexes. */
21
+ trackOrder: readonly number[];
22
+ insertRow: number;
23
+ dropped: Pick<TimelineElement, "id" | "tag" | "start" | "duration">;
24
+ }): DropTrackInsertPlan | null {
25
+ const { elements, trackOrder, insertRow, dropped } = input;
26
+ const newElement: TimelineElement = {
27
+ ...dropped,
28
+ key: dropped.id,
29
+ // Parked on an existing lane so it adds no lane of its own to the topology.
30
+ track: elements[0]?.track ?? 0,
31
+ // sameSourceFile compares this raw field: borrow the peers' value, not the resolved path.
32
+ sourceFile: elements[0]?.sourceFile,
33
+ };
34
+ const layout = layoutAfterTrackInsert(newElement, dropped.start, insertRow, null, {
35
+ elements: [...elements, newElement],
36
+ trackOrder: [...trackOrder],
37
+ });
38
+ if (!layout) return null;
39
+ const byKey = new Map(elements.map((e) => [e.key ?? e.id, e]));
40
+ const renumbers: DropTrackInsertPlan["renumbers"] = [];
41
+ for (const norm of layout.normalized) {
42
+ const key = norm.key ?? norm.id;
43
+ if (key === dropped.id) continue;
44
+ const src = byKey.get(key);
45
+ if (!src || norm.track === (src.authoredTrack ?? src.track)) continue;
46
+ if (!canMoveTimelineElement(src)) return null;
47
+ renumbers.push({ element: src, track: norm.track });
48
+ }
49
+ const track = layout.normalized.find((n) => (n.key ?? n.id) === dropped.id)?.track;
50
+ return track == null ? null : { track, renumbers };
51
+ }
52
+
53
+ /** Rewrite `data-track-index` on each renumbered clip's opening tag, via the shared source patcher. */
54
+ export function applyTrackRenumbers(source: string, plan: DropTrackInsertPlan): string {
55
+ let out = source;
56
+ for (const { element, track } of plan.renumbers) {
57
+ const target = buildPatchTarget(element);
58
+ if (!target || readAttributeByTarget(out, target, "track-index") === undefined) {
59
+ throw new Error(`Cannot renumber the track of "${element.id}" in the source`);
60
+ }
61
+ out = applyPatchByTarget(out, target, {
62
+ type: "attribute",
63
+ property: "track-index",
64
+ value: formatTimelineAttributeNumber(track),
65
+ });
66
+ }
67
+ return out;
68
+ }
69
+
70
+ /** The lane a dropped clip is written on and the source with any lanes pushed down to make room. */
71
+ export function resolveDropTrack(input: {
72
+ source: string;
73
+ elements: TimelineElement[];
74
+ placement: TimelineDropPlacement;
75
+ dropped: Pick<TimelineElement, "id" | "tag" | "start" | "duration">;
76
+ }): { source: string; track: number } {
77
+ const { source, elements, placement, dropped } = input;
78
+ if (placement.insertRow == null) return { source, track: placement.track };
79
+ const { insertRow, trackOrder } = placement;
80
+ const plan = planDropTrackInsert({ elements, trackOrder, insertRow, dropped });
81
+ if (!plan) throw new Error("Cannot open a new track here: a locked clip would have to move.");
82
+ return { source: applyTrackRenumbers(source, plan), track: plan.track };
83
+ }
@@ -33,6 +33,10 @@ const TYPING_SELECTOR = [
33
33
  "[role='textbox']",
34
34
  "[role='searchbox']",
35
35
  "[role='combobox']",
36
+ // A switch is a button in the DOM, but the native checkbox it replaces was an
37
+ // `input` and matched above. Without this row the shared Toggle would let a
38
+ // global shortcut claim Space instead of flipping the control (KTD13).
39
+ "[role='switch']",
36
40
  ".cm-editor",
37
41
  ].join(",");
38
42