@hyperframes/studio 0.8.49 → 0.8.51

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 (281) hide show
  1. package/dist/assets/{hyperframes-player-CqON2Rzo.js → hyperframes-player-Cn_PpHqP.js} +3 -3
  2. package/dist/assets/index-C05SNrvY.js +360 -0
  3. package/dist/assets/{index-mIgSsKqs.js → index-CPS3Cvj8.js} +1 -1
  4. package/dist/assets/{index-BGovDksE.js → index-Clrb11lj.js} +1 -1
  5. package/dist/assets/index-DUR9qzQM.css +1 -0
  6. package/dist/index.d.ts +29 -16
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +7283 -8606
  9. package/dist/index.js.map +1 -1
  10. package/dist/styles/tailwind-preset.js.map +1 -1
  11. package/package.json +11 -12
  12. package/src/App.tsx +132 -143
  13. package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
  14. package/src/captions/components/CaptionOverlayUtils.ts +2 -3
  15. package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
  16. package/src/captions/components/CaptionTimeline.tsx +1 -1
  17. package/src/captions/components/shared.tsx +2 -2
  18. package/src/components/AskAgentModal.tsx +5 -5
  19. package/src/components/EditorShell.selectionSync.test.tsx +4 -0
  20. package/src/components/EditorShell.tsx +28 -7
  21. package/src/components/ExternalFileConflictBanner.tsx +16 -16
  22. package/src/components/LintModal.tsx +6 -16
  23. package/src/components/MediaPreview.tsx +2 -2
  24. package/src/components/SaveQueuePausedBanner.tsx +2 -2
  25. package/src/components/StudioHeader.tsx +2 -62
  26. package/src/components/StudioLeftSidebar.tsx +7 -4
  27. package/src/components/StudioOverlays.tsx +2 -6
  28. package/src/components/StudioRightPanel.tsx +4 -4
  29. package/src/components/StudioToast.tsx +2 -2
  30. package/src/components/TimelineToolbar.tsx +7 -7
  31. package/src/components/dock/Dock.test.tsx +192 -0
  32. package/src/components/dock/Dock.tsx +264 -0
  33. package/src/components/dock/dock.css +23 -0
  34. package/src/components/dock/dockLayout.ts +58 -0
  35. package/src/components/dock/dockLayoutSchema.ts +66 -0
  36. package/src/components/dock/dockLayoutStore.ts +91 -0
  37. package/src/components/dock/panelRegistry.ts +67 -0
  38. package/src/components/editor/AnimationCard.tsx +4 -4
  39. package/src/components/editor/AnimationCardParts.tsx +6 -6
  40. package/src/components/editor/BlockParamsPanel.tsx +4 -4
  41. package/src/components/editor/BorderRadiusEditor.tsx +1 -1
  42. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  43. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  44. package/src/components/editor/DomEditOverlay.tsx +1 -1
  45. package/src/components/editor/EaseCurveSection.tsx +1 -1
  46. package/src/components/editor/EaseParamFields.tsx +5 -5
  47. package/src/components/editor/FileTree.tsx +4 -4
  48. package/src/components/editor/FileTreeIcons.tsx +1 -1
  49. package/src/components/editor/FileTreeNodes.tsx +13 -13
  50. package/src/components/editor/InlineTextToolbar.tsx +1 -1
  51. package/src/components/editor/InspectorHeaderActions.tsx +3 -3
  52. package/src/components/editor/KeyframeDiamond.tsx +1 -1
  53. package/src/components/editor/KeyframeEaseList.tsx +1 -1
  54. package/src/components/editor/LayersPanel.tsx +3 -3
  55. package/src/components/editor/MarqueeOverlay.tsx +1 -1
  56. package/src/components/editor/OffCanvasIndicators.tsx +1 -1
  57. package/src/components/editor/PreviewGuides.test.tsx +88 -0
  58. package/src/components/editor/PreviewGuides.tsx +125 -0
  59. package/src/components/editor/PromotableControl.tsx +2 -2
  60. package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
  61. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  62. package/src/components/editor/SnapToolbar.test.tsx +21 -0
  63. package/src/components/editor/SnapToolbar.tsx +27 -3
  64. package/src/components/editor/TimelineFxPopover.tsx +1 -1
  65. package/src/components/editor/TopologyLens.tsx +4 -4
  66. package/src/components/editor/Transform3DCube.tsx +2 -2
  67. package/src/components/editor/audioFxRevealTarget.ts +1 -0
  68. package/src/components/editor/domEditing.test.ts +1 -1
  69. package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
  70. package/src/components/editor/domEditingAgentPrompt.ts +9 -3
  71. package/src/components/editor/previewGuidesStore.ts +26 -0
  72. package/src/components/editor/propertyPanelColor.tsx +11 -11
  73. package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
  74. package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
  75. package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
  76. package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
  77. package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
  78. package/src/components/editor/propertyPanelCommitField.tsx +1 -1
  79. package/src/components/editor/propertyPanelFill.tsx +4 -4
  80. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  81. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
  82. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  83. package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
  84. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
  85. package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
  86. package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
  87. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
  88. package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
  89. package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
  90. package/src/components/editor/propertyPanelFont.tsx +5 -5
  91. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  92. package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
  93. package/src/components/editor/propertyPanelFxControls.tsx +6 -6
  94. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  95. package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
  96. package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
  97. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  98. package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
  99. package/src/components/editor/propertyPanelSections.tsx +8 -8
  100. package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
  101. package/src/components/feedback/feedbackTrigger.ts +0 -1
  102. package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
  103. package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
  104. package/src/components/nle/NLEPreview.test.ts +13 -0
  105. package/src/components/nle/NLEPreview.tsx +12 -7
  106. package/src/components/nle/PreviewOverlays.tsx +6 -6
  107. package/src/components/nle/PreviewPane.test.tsx +52 -0
  108. package/src/components/nle/PreviewPane.tsx +4 -2
  109. package/src/components/nle/TimelinePane.tsx +17 -3
  110. package/src/components/nle/TimelineResizeDivider.tsx +2 -2
  111. package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +1 -1
  112. package/src/components/nle/useCanvasZOrderTimelineMirror.ts +3 -3
  113. package/src/components/panels/SlideshowPanel.tsx +2 -2
  114. package/src/components/panels/SlideshowSubPanels.tsx +20 -17
  115. package/src/components/panels/VariablesBindElement.tsx +2 -2
  116. package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
  117. package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
  118. package/src/components/panels/VariablesPanel.tsx +3 -3
  119. package/src/components/panels/VariablesValueControls.tsx +2 -2
  120. package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
  121. package/src/components/renders/RenderQueue.test.tsx +7 -0
  122. package/src/components/renders/RenderQueue.tsx +6 -6
  123. package/src/components/renders/RenderQueueItem.tsx +6 -6
  124. package/src/components/sidebar/AssetCard.tsx +8 -8
  125. package/src/components/sidebar/AssetContextMenu.tsx +8 -8
  126. package/src/components/sidebar/AssetsTab.tsx +2 -2
  127. package/src/components/sidebar/AudioRow.tsx +6 -6
  128. package/src/components/sidebar/BlocksTab.tsx +6 -6
  129. package/src/components/sidebar/CompositionsTab.tsx +5 -5
  130. package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
  131. package/src/components/sidebar/LeftSidebar.lintBadge.test.tsx +58 -0
  132. package/src/components/sidebar/LeftSidebar.tsx +19 -5
  133. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  134. package/src/components/ui/Button.test.tsx +60 -0
  135. package/src/components/ui/Button.tsx +4 -4
  136. package/src/components/ui/SearchInput.tsx +2 -2
  137. package/src/components/ui/Tooltip.tsx +1 -1
  138. package/src/hooks/appHotkeysDispatch.ts +11 -0
  139. package/src/hooks/timelineAudioGroupCreate.ts +2 -2
  140. package/src/hooks/timelineTrackVisibility.ts +8 -11
  141. package/src/hooks/useAppHotkeys.textEditing.test.tsx +1 -0
  142. package/src/hooks/useAppHotkeys.ts +4 -1
  143. package/src/hooks/useAskAgentModal.ts +2 -0
  144. package/src/hooks/useClipboard.test.ts +234 -0
  145. package/src/hooks/useClipboard.ts +264 -70
  146. package/src/hooks/useGroupCommits.test.ts +0 -1
  147. package/src/hooks/useLintModal.test.tsx +65 -0
  148. package/src/hooks/useLintModal.ts +3 -0
  149. package/src/hooks/useRazorSplit.history.test.tsx +0 -1
  150. package/src/hooks/useRazorSplit.test.ts +0 -2
  151. package/src/hooks/useRazorSplit.test.tsx +0 -1
  152. package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
  153. package/src/hooks/useSdkSession.ts +43 -10
  154. package/src/hooks/useStudioContextValue.ts +5 -19
  155. package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
  156. package/src/hooks/useTimelineAssetDropOps.ts +52 -11
  157. package/src/hooks/useTimelineEditing.test.tsx +0 -1
  158. package/src/main.tsx +9 -3
  159. package/src/player/components/AudioWaveform.tsx +1 -1
  160. package/src/player/components/AutomationSelectionMenu.tsx +2 -2
  161. package/src/player/components/ClipContextMenu.tsx +90 -10
  162. package/src/player/components/CompositionThumbnail.test.ts +0 -3
  163. package/src/player/components/CompositionThumbnail.tsx +3 -4
  164. package/src/player/components/EditModal.tsx +1 -1
  165. package/src/player/components/ImageThumbnail.tsx +1 -1
  166. package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
  167. package/src/player/components/LayerDisclosureRow.tsx +2 -2
  168. package/src/player/components/PlayerControls.tsx +1 -1
  169. package/src/player/components/ShortcutsPanel.tsx +9 -9
  170. package/src/player/components/SpeedMenu.tsx +3 -5
  171. package/src/player/components/Timeline.tsx +16 -15
  172. package/src/player/components/TimelineCanvas.tsx +45 -8
  173. package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
  174. package/src/player/components/TimelineEmptyState.tsx +5 -5
  175. package/src/player/components/TimelineFxButton.tsx +6 -6
  176. package/src/player/components/TimelineGestureOverlay.test.tsx +35 -1
  177. package/src/player/components/TimelineGestureOverlay.tsx +1 -1
  178. package/src/player/components/TimelineGroupHeader.tsx +5 -5
  179. package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
  180. package/src/player/components/TimelineGroupRow.tsx +1 -1
  181. package/src/player/components/TimelineLanes.test.tsx +54 -1
  182. package/src/player/components/TimelineLanes.tsx +2 -5
  183. package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
  184. package/src/player/components/TimelineOverlays.tsx +12 -0
  185. package/src/player/components/TimelineRuler.tsx +1 -1
  186. package/src/player/components/TimelineShortcutHint.tsx +1 -1
  187. package/src/player/components/TimelineTrackHeader.tsx +1 -1
  188. package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
  189. package/src/player/components/TimelineTypes.ts +6 -1
  190. package/src/player/components/TrackGapContextMenu.tsx +1 -1
  191. package/src/player/components/VideoThumbnail.tsx +4 -2
  192. package/src/player/components/VolumeControl.tsx +2 -2
  193. package/src/player/components/timelineAuthoredMoveTarget.ts +1 -2
  194. package/src/player/components/timelineCallbacks.ts +8 -8
  195. package/src/player/components/timelineClipChildren.test.ts +2 -2
  196. package/src/player/components/timelineClipChildren.tsx +2 -1
  197. package/src/player/components/timelineClipDragCommit.ts +2 -2
  198. package/src/player/components/timelineClipDragPreview.ts +51 -27
  199. package/src/player/components/timelineClipDragTypes.ts +3 -0
  200. package/src/player/components/timelineDragDrop.test.tsx +92 -1
  201. package/src/player/components/timelineDragDrop.ts +82 -33
  202. package/src/player/components/timelineEditCapabilities.ts +1 -2
  203. package/src/player/components/timelineEditing.test.ts +0 -16
  204. package/src/player/components/timelineGroupEditing.test.ts +1 -3
  205. package/src/player/components/timelineGroupEditing.ts +0 -1
  206. package/src/player/components/timelineLaneProps.ts +3 -0
  207. package/src/player/components/timelineSnapping.test.ts +27 -0
  208. package/src/player/components/timelineSnapping.ts +14 -1
  209. package/src/player/components/timelineTrackDisplay.ts +1 -1
  210. package/src/player/components/timelineTrackPersistPipeline.test.ts +0 -53
  211. package/src/player/components/trackHeaderLabelRows.tsx +4 -4
  212. package/src/player/components/useTimelineClipContextMenu.ts +32 -0
  213. package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
  214. package/src/player/components/useTimelineClipDrag.ts +30 -3
  215. package/src/player/components/useTimelineTrackDerivations.ts +1 -1
  216. package/src/player/hooks/timelineDomBinding.test.ts +0 -3
  217. package/src/player/hooks/timelineSyncHydration.test.ts +18 -0
  218. package/src/player/hooks/timelineSyncHydration.ts +37 -18
  219. package/src/player/hooks/useTimelinePlayer.test.ts +3 -20
  220. package/src/player/hooks/useTimelineRowElements.test.ts +28 -0
  221. package/src/player/hooks/useTimelineRowElements.ts +17 -0
  222. package/src/player/hooks/useTimelineSyncCallbacks.emptyManifest.test.tsx +41 -0
  223. package/src/player/hooks/useTimelineSyncCallbacks.ts +9 -10
  224. package/src/player/lib/describeClips.test.ts +95 -0
  225. package/src/player/lib/describeClips.ts +52 -0
  226. package/src/player/lib/timelineDOM.test.ts +0 -80
  227. package/src/player/lib/timelineDOM.ts +1 -61
  228. package/src/player/lib/timelineElementHelpers.ts +2 -53
  229. package/src/player/store/playerStore.ts +5 -0
  230. package/src/player/store/timelineElement.ts +1 -3
  231. package/src/shims/canvasBrowserStub.js +50 -0
  232. package/src/styles/iconTokens.ts +20 -0
  233. package/src/styles/palette-reset.css +6 -0
  234. package/src/styles/studio.css +24 -21
  235. package/src/styles/tailwind-preset.shared.js +3 -0
  236. package/src/styles/theme.css +213 -0
  237. package/src/styles/theme.test.ts +196 -0
  238. package/src/utils/clipboardPayload.test.ts +42 -2
  239. package/src/utils/clipboardPayload.ts +57 -32
  240. package/src/utils/previewSafeMargins.test.ts +34 -0
  241. package/src/utils/previewSafeMargins.ts +10 -0
  242. package/src/utils/razorSplitTransaction.test.ts +0 -1
  243. package/src/utils/sdkCanvasComposition.test.ts +77 -0
  244. package/src/utils/studioUiPreferences.test.ts +43 -0
  245. package/src/utils/studioUiPreferences.ts +34 -6
  246. package/src/utils/timelineDropTrackInsert.test.ts +134 -0
  247. package/src/utils/timelineDropTrackInsert.ts +83 -0
  248. package/src/utils/timelineElementSplit.test.ts +1 -2
  249. package/src/utils/timelineElementSplit.ts +0 -1
  250. package/src/webmcp/tools/lookTools.test.ts +40 -0
  251. package/src/webmcp/tools/lookTools.ts +32 -1
  252. package/dist/assets/index-B9LfWNyM.css +0 -1
  253. package/dist/assets/index-Bgmx2h7W.js +0 -441
  254. package/src/components/StudioGlobalDragOverlay.tsx +0 -24
  255. package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
  256. package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
  257. package/src/components/storyboard/FramePoster.test.tsx +0 -61
  258. package/src/components/storyboard/FramePoster.tsx +0 -86
  259. package/src/components/storyboard/StoryboardDirection.tsx +0 -45
  260. package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
  261. package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
  262. package/src/components/storyboard/StoryboardGrid.tsx +0 -55
  263. package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
  264. package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
  265. package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
  266. package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
  267. package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
  268. package/src/components/storyboard/StoryboardView.tsx +0 -169
  269. package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
  270. package/src/components/storyboard/frameComments.test.ts +0 -95
  271. package/src/components/storyboard/frameComments.ts +0 -115
  272. package/src/components/storyboard/frameStatus.ts +0 -36
  273. package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
  274. package/src/components/storyboard/storyboardReviewStage.ts +0 -45
  275. package/src/components/storyboard/useFrameComments.ts +0 -98
  276. package/src/contexts/ViewModeContext.tsx +0 -142
  277. package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
  278. package/src/hooks/useProjectSignaturePoll.ts +0 -68
  279. package/src/hooks/useStoryboard.ts +0 -99
  280. package/src/player/hooks/useExpandedTimelineElements.test.ts +0 -774
  281. package/src/player/hooks/useExpandedTimelineElements.ts +0 -460
@@ -16,12 +16,12 @@ import type { BlockPreviewInfo } from "./sidebar/BlocksTab";
16
16
  import type { GestureRecordingState } from "./editor/GestureRecordControl";
17
17
  import { useTimelineSelectionPreviewSync } from "../hooks/useTimelineSelectionPreviewSync";
18
18
  import { StudioAgentTools } from "../webmcp/StudioAgentTools";
19
+ import type { TimelineDropPlacement } from "../player/components/timelineCallbacks";
19
20
 
20
21
  type RenderClipContent = (
21
22
  element: TimelineElement,
22
23
  style: { clip: string; label: string },
23
24
  ) => ReactNode;
24
- type TimelineDropPlacement = Pick<TimelineElement, "start" | "track">;
25
25
 
26
26
  // The seven move/resize/split/razor handlers come from TimelineEditCallbackDeps
27
27
  // (shared with useTimelineEditCallbacks); the rest are drop + wiring props.
@@ -30,8 +30,6 @@ export interface EditorShellProps extends TimelineEditCallbackDeps {
30
30
  left: ReactNode;
31
31
  /** Right panel (inspector/design) or null when collapsed, in the top row. */
32
32
  right: ReactNode;
33
- /** Hide the whole shell (e.g. while the storyboard view is active). */
34
- hidden?: boolean;
35
33
  timelineToolbar: ReactNode;
36
34
  renderClipContent: RenderClipContent;
37
35
  handleTimelineElementDelete: (element: TimelineElement) => Promise<void> | void;
@@ -55,6 +53,10 @@ export interface EditorShellProps extends TimelineEditCallbackDeps {
55
53
  files: File[],
56
54
  placement?: TimelineDropPlacement,
57
55
  ) => Promise<void> | void;
56
+ onCopyClip: () => boolean;
57
+ onPasteClip: () => Promise<void>;
58
+ onDuplicateClip: () => Promise<boolean>;
59
+ canPasteClip: () => boolean;
58
60
  setCompIdToSrc: (map: Map<string, string>) => void;
59
61
  setCompositionLoading: (loading: boolean) => void;
60
62
  shouldShowMotionPath: boolean;
@@ -72,7 +74,6 @@ export interface EditorShellProps extends TimelineEditCallbackDeps {
72
74
  export function EditorShell({
73
75
  left,
74
76
  right,
75
- hidden,
76
77
  timelineToolbar,
77
78
  renderClipContent,
78
79
  handleTimelineElementDelete,
@@ -93,6 +94,10 @@ export function EditorShell({
93
94
  handleTimelineElementSplit,
94
95
  handleRazorSplit,
95
96
  handleRazorSplitAll,
97
+ onCopyClip,
98
+ onPasteClip,
99
+ onDuplicateClip,
100
+ canPasteClip,
96
101
  setCompIdToSrc,
97
102
  setCompositionLoading,
98
103
  shouldShowMotionPath,
@@ -149,7 +154,7 @@ export function EditorShell({
149
154
  });
150
155
 
151
156
  return (
152
- <div className={`flex flex-col flex-1 min-h-0${hidden ? " hidden" : ""}`}>
157
+ <div className="flex flex-col flex-1 min-h-0">
153
158
  <TimelineEditProvider value={timelineEditCallbacks}>
154
159
  <NLEProvider
155
160
  projectId={projectId}
@@ -181,6 +186,10 @@ export function EditorShell({
181
186
  onBlockDrop={handleTimelineBlockDrop}
182
187
  onCompositionDrop={handleTimelineCompositionDrop}
183
188
  onDeleteElement={handleTimelineElementDelete}
189
+ onCopyClip={onCopyClip}
190
+ onPasteClip={onPasteClip}
191
+ onDuplicateClip={onDuplicateClip}
192
+ canPasteClip={canPasteClip}
184
193
  previewOverlay={
185
194
  <PreviewOverlays
186
195
  shouldShowMotionPath={shouldShowMotionPath}
@@ -219,6 +228,10 @@ interface EditorShellBodyProps {
219
228
  placement: TimelineDropPlacement,
220
229
  ) => Promise<void> | void;
221
230
  onDeleteElement: (element: TimelineElement) => Promise<void> | void;
231
+ onCopyClip: () => boolean;
232
+ onPasteClip: () => Promise<void>;
233
+ onDuplicateClip: () => Promise<boolean>;
234
+ canPasteClip: () => boolean;
222
235
  }
223
236
 
224
237
  function EditorShellBody({
@@ -235,6 +248,10 @@ function EditorShellBody({
235
248
  onBlockDrop,
236
249
  onCompositionDrop,
237
250
  onDeleteElement,
251
+ onCopyClip,
252
+ onPasteClip,
253
+ onDuplicateClip,
254
+ canPasteClip,
238
255
  }: EditorShellBodyProps) {
239
256
  const { compositionStack, updateCompositionStack, containerRef } = useNLEContext();
240
257
 
@@ -259,7 +276,7 @@ function EditorShellBody({
259
276
  ref={containerRef}
260
277
  // Shell canvas is a step LIGHTER than the near-black panel cards so the
261
278
  // gaps between panels read as visible seams (CapCut-style).
262
- className="flex flex-col flex-1 min-h-0 bg-[#18181B]"
279
+ className="flex flex-col flex-1 min-h-0 bg-panel-surface"
263
280
  onKeyDown={handleKeyDown}
264
281
  tabIndex={-1}
265
282
  >
@@ -289,10 +306,14 @@ function EditorShellBody({
289
306
  onBlockDrop={onBlockDrop}
290
307
  onCompositionDrop={onCompositionDrop}
291
308
  onDeleteElement={onDeleteElement}
309
+ onCopyClip={onCopyClip}
310
+ onPasteClip={onPasteClip}
311
+ onDuplicateClip={onDuplicateClip}
312
+ canPasteClip={canPasteClip}
292
313
  onSelectTimelineElement={onSelectTimelineElement}
293
314
  timelineFooter={
294
315
  captionEditMode ? (
295
- <div className="border-t border-neutral-800/30 flex-shrink-0" style={{ height: 60 }}>
316
+ <div className="border-t border-neutral-800/30 shrink-0" style={{ height: 60 }}>
296
317
  <div className="flex items-center gap-1.5 px-2 py-0.5">
297
318
  <span className="text-[9px] font-medium text-neutral-500 uppercase tracking-wider">
298
319
  Captions
@@ -32,7 +32,7 @@ function ConflictReview({
32
32
 
33
33
  return (
34
34
  <div
35
- className="fixed inset-0 z-[110] flex items-center justify-center bg-black/70 px-5 backdrop-blur-sm"
35
+ className="fixed inset-0 z-110 flex items-center justify-center bg-black/70 px-5 backdrop-blur-xs"
36
36
  onClick={onClose}
37
37
  >
38
38
  <div
@@ -41,7 +41,7 @@ function ConflictReview({
41
41
  aria-modal="true"
42
42
  aria-labelledby="external-conflict-title"
43
43
  tabIndex={-1}
44
- className="max-h-[88vh] w-full max-w-5xl overflow-auto rounded-xl border border-amber-400/30 bg-neutral-950 p-5 text-neutral-100 shadow-2xl outline-none"
44
+ className="max-h-[88vh] w-full max-w-5xl overflow-auto rounded-xl border border-amber-400/30 bg-neutral-950 p-5 text-neutral-100 shadow-2xl outline-hidden"
45
45
  onClick={(event) => event.stopPropagation()}
46
46
  >
47
47
  <div className="flex items-start justify-between gap-4">
@@ -53,7 +53,7 @@ function ConflictReview({
53
53
  Reviewing or exporting does not change either version.
54
54
  </p>
55
55
  </div>
56
- <button type="button" onClick={onClose} className="rounded px-2 py-1 text-neutral-400">
56
+ <button type="button" onClick={onClose} className="rounded-sm px-2 py-1 text-neutral-400">
57
57
  Close
58
58
  </button>
59
59
  </div>
@@ -68,14 +68,14 @@ function ConflictReview({
68
68
  <div className="flex gap-2">
69
69
  <button
70
70
  type="button"
71
- className="rounded border border-neutral-700 px-2 py-1 text-[11px]"
71
+ className="rounded-sm border border-neutral-700 px-2 py-1 text-[11px]"
72
72
  onClick={() => void navigator.clipboard.writeText(side.content)}
73
73
  >
74
74
  Copy
75
75
  </button>
76
76
  <button
77
77
  type="button"
78
- className="rounded border border-neutral-700 px-2 py-1 text-[11px]"
78
+ className="rounded-sm border border-neutral-700 px-2 py-1 text-[11px]"
79
79
  onClick={() =>
80
80
  downloadText(`${conflict.error.filePath}.${side.suffix}.html`, side.content)
81
81
  }
@@ -87,7 +87,7 @@ function ConflictReview({
87
87
  <textarea
88
88
  readOnly
89
89
  value={side.content}
90
- className="h-80 w-full resize-y rounded border border-neutral-800 bg-neutral-900 p-3 font-mono text-[11px] leading-relaxed text-neutral-300"
90
+ className="h-80 w-full resize-y rounded-sm border border-neutral-800 bg-neutral-900 p-3 font-mono text-[11px] leading-relaxed text-neutral-300"
91
91
  />
92
92
  </section>
93
93
  ))}
@@ -110,7 +110,7 @@ function FailedDraftReview({
110
110
  useDialogBehavior({ open: true, onClose, containerRef });
111
111
  return (
112
112
  <div
113
- className="fixed inset-0 z-[110] flex items-center justify-center bg-black/70 px-5 backdrop-blur-sm"
113
+ className="fixed inset-0 z-110 flex items-center justify-center bg-black/70 px-5 backdrop-blur-xs"
114
114
  onClick={onClose}
115
115
  >
116
116
  <div
@@ -119,7 +119,7 @@ function FailedDraftReview({
119
119
  aria-modal="true"
120
120
  aria-labelledby="failed-draft-title"
121
121
  tabIndex={-1}
122
- className="max-h-[88vh] w-full max-w-3xl overflow-auto rounded-xl border border-amber-400/30 bg-neutral-950 p-5 text-neutral-100 shadow-2xl outline-none"
122
+ className="max-h-[88vh] w-full max-w-3xl overflow-auto rounded-xl border border-amber-400/30 bg-neutral-950 p-5 text-neutral-100 shadow-2xl outline-hidden"
123
123
  onClick={(event) => event.stopPropagation()}
124
124
  >
125
125
  <div className="flex items-start justify-between gap-4">
@@ -131,21 +131,21 @@ function FailedDraftReview({
131
131
  Copy or download this draft before choosing to discard it.
132
132
  </p>
133
133
  </div>
134
- <button type="button" onClick={onClose} className="rounded px-2 py-1 text-neutral-400">
134
+ <button type="button" onClick={onClose} className="rounded-sm px-2 py-1 text-neutral-400">
135
135
  Close
136
136
  </button>
137
137
  </div>
138
138
  <div className="mt-4 flex justify-end gap-2">
139
139
  <button
140
140
  type="button"
141
- className="rounded border border-neutral-700 px-2 py-1 text-[11px]"
141
+ className="rounded-sm border border-neutral-700 px-2 py-1 text-[11px]"
142
142
  onClick={() => void navigator.clipboard.writeText(content)}
143
143
  >
144
144
  Copy
145
145
  </button>
146
146
  <button
147
147
  type="button"
148
- className="rounded border border-neutral-700 px-2 py-1 text-[11px]"
148
+ className="rounded-sm border border-neutral-700 px-2 py-1 text-[11px]"
149
149
  onClick={() => downloadText(`${path}.studio.html`, content)}
150
150
  >
151
151
  Download
@@ -154,7 +154,7 @@ function FailedDraftReview({
154
154
  <textarea
155
155
  readOnly
156
156
  value={content}
157
- className="mt-2 h-80 w-full resize-y rounded border border-neutral-800 bg-neutral-900 p-3 font-mono text-[11px] leading-relaxed text-neutral-300"
157
+ className="mt-2 h-80 w-full resize-y rounded-sm border border-neutral-800 bg-neutral-900 p-3 font-mono text-[11px] leading-relaxed text-neutral-300"
158
158
  />
159
159
  </div>
160
160
  </div>
@@ -176,7 +176,7 @@ export function ExternalFileConflictBanner({
176
176
  <>
177
177
  <div
178
178
  role="alert"
179
- className="absolute left-1/2 top-14 z-[94] flex max-w-[calc(100vw-32px)] -translate-x-1/2 flex-wrap items-center gap-3 rounded-md border border-amber-400/30 bg-amber-950/95 px-4 py-2 text-[12px] font-medium text-amber-50 shadow-lg"
179
+ className="absolute left-1/2 top-14 z-94 flex max-w-[calc(100vw-32px)] -translate-x-1/2 flex-wrap items-center gap-3 rounded-md border border-amber-400/30 bg-amber-950/95 px-4 py-2 text-[12px] font-medium text-amber-50 shadow-lg"
180
180
  >
181
181
  <span>
182
182
  {conflict
@@ -201,7 +201,7 @@ export function ExternalFileConflictBanner({
201
201
  <button
202
202
  type="button"
203
203
  onClick={() => void coordinator.useExternalFile()}
204
- className="rounded border border-amber-200/30 px-2 py-1"
204
+ className="rounded-sm border border-amber-200/30 px-2 py-1"
205
205
  >
206
206
  Discard Studio edits and reload file
207
207
  </button>
@@ -217,7 +217,7 @@ export function ExternalFileConflictBanner({
217
217
  void coordinator.keepStudioFile();
218
218
  }
219
219
  }}
220
- className="rounded border border-red-300/40 px-2 py-1 text-red-100"
220
+ className="rounded-sm border border-red-300/40 px-2 py-1 text-red-100"
221
221
  >
222
222
  Overwrite file with Studio version
223
223
  </button>
@@ -234,7 +234,7 @@ export function ExternalFileConflictBanner({
234
234
  void coordinator.keepStudioFile();
235
235
  }
236
236
  }}
237
- className="rounded border border-red-300/40 px-2 py-1 text-red-100"
237
+ className="rounded-sm border border-red-300/40 px-2 py-1 text-red-100"
238
238
  >
239
239
  Overwrite file with recovered Studio draft
240
240
  </button>
@@ -58,7 +58,7 @@ export function LintModal({
58
58
 
59
59
  return (
60
60
  <div
61
- className="hf-backdrop-in fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm"
61
+ className="hf-backdrop-in fixed inset-0 z-100 flex items-center justify-center bg-black/60 backdrop-blur-xs"
62
62
  onClick={requestClose}
63
63
  >
64
64
  <div
@@ -67,7 +67,7 @@ export function LintModal({
67
67
  aria-modal="true"
68
68
  aria-label={title}
69
69
  tabIndex={-1}
70
- className="bg-neutral-950 border border-neutral-800 rounded-xl shadow-2xl w-full max-w-xl max-h-[80vh] flex flex-col overflow-hidden outline-none"
70
+ className="bg-neutral-950 border border-neutral-800 rounded-xl shadow-2xl w-full max-w-xl max-h-[80vh] flex flex-col overflow-hidden outline-hidden"
71
71
  onClick={(e) => e.stopPropagation()}
72
72
  >
73
73
  {/* Header */}
@@ -130,20 +130,13 @@ export function LintModal({
130
130
  {errors.map((f, i) => (
131
131
  <div key={`e-${i}`} className="py-3 border-b border-neutral-800/50 last:border-0">
132
132
  <div className="flex items-start gap-2">
133
- <WarningIcon
134
- size={14}
135
- className="text-red-400 flex-shrink-0 mt-0.5"
136
- weight="fill"
137
- />
133
+ <WarningIcon size={14} className="text-red-400 shrink-0 mt-0.5" weight="fill" />
138
134
  <div className="min-w-0">
139
135
  <p className="text-sm text-neutral-200">{f.message}</p>
140
136
  {f.file && <p className="text-xs text-neutral-600 font-mono mt-0.5">{f.file}</p>}
141
137
  {f.fixHint && (
142
138
  <div className="flex items-start gap-1 mt-1.5">
143
- <CaretRightIcon
144
- size={10}
145
- className="text-studio-accent flex-shrink-0 mt-0.5"
146
- />
139
+ <CaretRightIcon size={10} className="text-studio-accent shrink-0 mt-0.5" />
147
140
  <p className="text-xs text-studio-accent">{f.fixHint}</p>
148
141
  </div>
149
142
  )}
@@ -154,16 +147,13 @@ export function LintModal({
154
147
  {warnings.map((f, i) => (
155
148
  <div key={`w-${i}`} className="py-3 border-b border-neutral-800/50 last:border-0">
156
149
  <div className="flex items-start gap-2">
157
- <WarningIcon size={14} className="text-amber-400 flex-shrink-0 mt-0.5" />
150
+ <WarningIcon size={14} className="text-amber-400 shrink-0 mt-0.5" />
158
151
  <div className="min-w-0">
159
152
  <p className="text-sm text-neutral-300">{f.message}</p>
160
153
  {f.file && <p className="text-xs text-neutral-600 font-mono mt-0.5">{f.file}</p>}
161
154
  {f.fixHint && (
162
155
  <div className="flex items-start gap-1 mt-1.5">
163
- <CaretRightIcon
164
- size={10}
165
- className="text-studio-accent flex-shrink-0 mt-0.5"
166
- />
156
+ <CaretRightIcon size={10} className="text-studio-accent shrink-0 mt-0.5" />
167
157
  <p className="text-xs text-studio-accent">{f.fixHint}</p>
168
158
  </div>
169
159
  )}
@@ -45,7 +45,7 @@ export function MediaPreview({ projectId, filePath }: { projectId: string; fileP
45
45
  src={serveUrl}
46
46
  alt={name}
47
47
  onError={setFailed}
48
- className="max-w-full max-h-[70%] object-contain rounded border border-neutral-800"
48
+ className="max-w-full max-h-[70%] object-contain rounded-sm border border-neutral-800"
49
49
  />
50
50
  <span className="mt-3 text-[11px] text-neutral-500 font-mono">{filePath}</span>
51
51
  </div>
@@ -59,7 +59,7 @@ export function MediaPreview({ projectId, filePath }: { projectId: string; fileP
59
59
  src={serveUrl}
60
60
  controls
61
61
  onError={setFailed}
62
- className="max-w-full max-h-[70%] rounded border border-neutral-800"
62
+ className="max-w-full max-h-[70%] rounded-sm border border-neutral-800"
63
63
  />
64
64
  <span className="mt-3 text-[11px] text-neutral-500 font-mono">{filePath}</span>
65
65
  </div>
@@ -8,14 +8,14 @@ interface SaveQueuePausedBannerProps {
8
8
  export function SaveQueuePausedBanner({ message, onRetry }: SaveQueuePausedBannerProps) {
9
9
  return (
10
10
  <div
11
- className="hf-backdrop-in absolute left-1/2 top-14 z-[92] flex max-w-[calc(100vw-32px)] -translate-x-1/2 items-center gap-3 rounded-md border border-red-500/30 bg-red-950/85 px-4 py-2 text-[12px] font-medium text-red-100 shadow-lg shadow-black/30"
11
+ className="hf-backdrop-in absolute left-1/2 top-14 z-92 flex max-w-[calc(100vw-32px)] -translate-x-1/2 items-center gap-3 rounded-md border border-red-500/30 bg-red-950/85 px-4 py-2 text-[12px] font-medium text-red-100 shadow-lg shadow-black/30"
12
12
  role="alert"
13
13
  >
14
14
  <span>{message}</span>
15
15
  <button
16
16
  type="button"
17
17
  onClick={onRetry}
18
- className="rounded border border-red-300/20 px-2 py-1 text-[11px] text-red-100 transition-colors hover:bg-red-400/10 active:scale-[0.98]"
18
+ className="rounded-sm border border-red-300/20 px-2 py-1 text-[11px] text-red-100 transition-colors hover:bg-red-400/10 active:scale-[0.98]"
19
19
  >
20
20
  Retry saving
21
21
  </button>
@@ -1,9 +1,8 @@
1
- import { useRef, type MouseEvent } from "react";
1
+ import type { MouseEvent } from "react";
2
2
  import { RotateCcw, RotateCw, Camera } from "../icons/SystemIcons";
3
3
  import { getHistoryShortcutLabel } from "../utils/studioHelpers";
4
4
  import { useStudioShellContext } from "../contexts/StudioContext";
5
5
  import { usePanelLayoutContext } from "../contexts/PanelLayoutContext";
6
- import { useViewMode, type StudioViewMode } from "../contexts/ViewModeContext";
7
6
  import { trackStudioEvent } from "../utils/studioTelemetry";
8
7
  import { Tooltip } from "./ui";
9
8
 
@@ -139,63 +138,6 @@ function HyperframesLogo() {
139
138
  );
140
139
  }
141
140
 
142
- const VIEW_MODE_OPTIONS: Array<{ mode: StudioViewMode; label: string }> = [
143
- { mode: "storyboard", label: "Storyboard" },
144
- { mode: "timeline", label: "Preview" },
145
- ];
146
-
147
- /** Segmented control switching the main stage between storyboard and preview. */
148
- export function ViewModeToggle() {
149
- const { viewMode, setViewMode } = useViewMode();
150
- const tabRefs = useRef<Array<HTMLButtonElement | null>>([]);
151
-
152
- const selectMode = (mode: StudioViewMode) => {
153
- if (mode === viewMode) return;
154
- if (setViewMode(mode)) trackStudioEvent("view_mode_toggle", { mode });
155
- };
156
-
157
- // Complete APG tabs pattern: roving tabIndex + arrow-key navigation.
158
- const handleKeyDown = (e: React.KeyboardEvent, index: number) => {
159
- if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
160
- e.preventDefault();
161
- const dir = e.key === "ArrowLeft" ? -1 : 1;
162
- const next = (index + dir + VIEW_MODE_OPTIONS.length) % VIEW_MODE_OPTIONS.length;
163
- tabRefs.current[next]?.focus();
164
- selectMode(VIEW_MODE_OPTIONS[next].mode);
165
- };
166
-
167
- return (
168
- <div
169
- className="flex items-center gap-0.5 rounded-md bg-neutral-800 p-0.5"
170
- role="tablist"
171
- aria-label="Studio view"
172
- >
173
- {VIEW_MODE_OPTIONS.map(({ mode, label }, index) => {
174
- const active = viewMode === mode;
175
- return (
176
- <button
177
- key={mode}
178
- ref={(el) => {
179
- tabRefs.current[index] = el;
180
- }}
181
- type="button"
182
- role="tab"
183
- aria-selected={active}
184
- tabIndex={active ? 0 : -1}
185
- onClick={() => selectMode(mode)}
186
- onKeyDown={(e) => handleKeyDown(e, index)}
187
- className={`rounded px-3 py-1 text-[11px] font-medium transition-colors active:scale-[0.98] outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-studio-accent ${
188
- active ? "bg-neutral-200 text-neutral-900" : "text-neutral-400 hover:text-neutral-200"
189
- }`}
190
- >
191
- {label}
192
- </button>
193
- );
194
- })}
195
- </div>
196
- );
197
- }
198
-
199
141
  /**
200
142
  * Does the header's Inspector button open the panel, or close it?
201
143
  *
@@ -232,7 +174,7 @@ export function StudioHeader({
232
174
  const ffmpegMissing = renderQueue.ffmpegMissing;
233
175
 
234
176
  return (
235
- <div className="flex items-center justify-between h-10 px-3 bg-neutral-900 border-b border-neutral-800 flex-shrink-0">
177
+ <div className="flex items-center justify-between h-10 px-3 bg-neutral-900 border-b border-neutral-800 shrink-0">
236
178
  {/* Left: logo + project name */}
237
179
  <div className="flex items-center gap-3">
238
180
  <HyperframesLogo />
@@ -241,8 +183,6 @@ export function StudioHeader({
241
183
  </span>
242
184
  <span className="text-[11px] font-medium text-neutral-300">{projectId}</span>
243
185
  </div>
244
- {/* Center: storyboard / preview toggle */}
245
- <ViewModeToggle />
246
186
  {/* Right: toolbar buttons */}
247
187
  <div className="flex items-center gap-1.5">
248
188
  <Tooltip
@@ -17,6 +17,7 @@ export interface StudioLeftSidebarProps {
17
17
  onLint: () => void;
18
18
  linting: boolean;
19
19
  lintFindingCount?: number;
20
+ lintHasError?: boolean;
20
21
  lintFindingsByFile?: Map<string, { count: number; messages: string[] }>;
21
22
  onAddAssetToTimeline?: (path: string) => void;
22
23
  onAddCompositionToTimeline?: (path: string) => void;
@@ -31,6 +32,7 @@ export function StudioLeftSidebar({
31
32
  onLint,
32
33
  linting,
33
34
  lintFindingCount,
35
+ lintHasError,
34
36
  lintFindingsByFile,
35
37
  onAddAssetToTimeline,
36
38
  onAddCompositionToTimeline,
@@ -86,7 +88,7 @@ export function StudioLeftSidebar({
86
88
 
87
89
  if (effectiveLeftCollapsed) {
88
90
  return (
89
- <div className="mr-0.5 flex w-10 flex-shrink-0 flex-col items-center rounded-lg border border-neutral-800/50 bg-neutral-950 pt-1">
91
+ <div className="mr-0.5 flex w-10 shrink-0 flex-col items-center rounded-lg border border-neutral-800/50 bg-neutral-950 pt-1">
90
92
  <button
91
93
  type="button"
92
94
  onClick={toggleLeftSidebar}
@@ -160,6 +162,7 @@ export function StudioLeftSidebar({
160
162
  onLint={onLint}
161
163
  linting={linting}
162
164
  lintFindingCount={lintFindingCount}
165
+ lintHasError={lintHasError}
163
166
  lintFindingsByFile={lintFindingsByFile}
164
167
  onToggleCollapse={toggleLeftSidebar}
165
168
  onAddBlock={onAddBlock}
@@ -169,14 +172,14 @@ export function StudioLeftSidebar({
169
172
  />
170
173
  {/* Vertical resize divider: 3px visible seam, 13px pointer-capture zone via
171
174
  the absolutely-positioned inner hit area. The outer element is w-[3px] so
172
- it contributes only 3px of gap in the flex row; the inner -left-[2px]
175
+ it contributes only 3px of gap in the flex row; the inner left-[-2px]
173
176
  element widens the hit area without affecting layout. */}
174
177
  <div
175
178
  role="separator"
176
179
  aria-label="Resize sidebar"
177
180
  aria-orientation="vertical"
178
181
  tabIndex={0}
179
- className="group relative w-[3px] flex-shrink-0 cursor-col-resize outline-none focus-visible:bg-studio-accent/20"
182
+ className="group relative w-[3px] shrink-0 cursor-col-resize outline-hidden focus-visible:bg-studio-accent/20"
180
183
  style={{ touchAction: "none" }}
181
184
  onPointerDown={(e) => handlePanelResizeStart("left", e)}
182
185
  onPointerMove={handlePanelResizeMove}
@@ -195,7 +198,7 @@ export function StudioLeftSidebar({
195
198
  24px WCAG 2.2 (2.5.8) target because the next pixel on either side is
196
199
  live: the sidebar's scrolling tab content on the left, the preview
197
200
  stage on the right. Silently stealing their clicks is the worse bug. */}
198
- <div className="absolute inset-y-0 -left-[2px] w-[13px]" />
201
+ <div className="absolute inset-y-0 left-[-2px] w-[13px]" />
199
202
  {/* Visible hairline */}
200
203
  <div className="absolute top-1/2 left-0 h-[52px] w-[3px] -translate-y-1/2 bg-white/12 transition-colors group-hover:bg-white/18 group-active:bg-white/24" />
201
204
  </div>
@@ -1,7 +1,6 @@
1
1
  import type { ComponentProps } from "react";
2
2
  import { LintModal } from "./LintModal";
3
3
  import { AskAgentModal } from "./AskAgentModal";
4
- import { StudioGlobalDragOverlay } from "./StudioGlobalDragOverlay";
5
4
  import { StudioToast } from "./StudioToast";
6
5
  import { StudioFeedbackCard } from "./feedback/StudioFeedbackCard";
7
6
  import { buildAgentContextPreview } from "./editor/domEditingAgentPrompt";
@@ -19,14 +18,13 @@ export interface StudioOverlaysProps {
19
18
  clearConsoleErrors: () => void;
20
19
  domEditSession: ReturnType<typeof useDomEditSession>;
21
20
  activeCompPath: string | null;
22
- dragOverlayActive: boolean;
23
21
  toasts: ReturnType<typeof useToast>["toasts"];
24
22
  dismissToast: (id: number) => void;
25
23
  }
26
24
 
27
25
  /**
28
26
  * Floating overlays for the studio shell: lint / console-error modals, the
29
- * ask-agent modal, the global drag overlay, and the toast. Extracted from
27
+ * ask-agent modal, and the toast. Extracted from
30
28
  * `App.tsx` to keep the shell within the studio's 600-line decomposition budget.
31
29
  */
32
30
  // fallow-ignore-next-line complexity
@@ -39,7 +37,6 @@ export function StudioOverlays({
39
37
  clearConsoleErrors,
40
38
  domEditSession,
41
39
  activeCompPath,
42
- dragOverlayActive,
43
40
  toasts,
44
41
  dismissToast,
45
42
  }: StudioOverlaysProps) {
@@ -78,10 +75,9 @@ export function StudioOverlays({
78
75
  }}
79
76
  />
80
77
  )}
81
- {dragOverlayActive && <StudioGlobalDragOverlay />}
82
78
  {/* One bottom-right stack so the feedback card and toasts queue instead
83
79
  of covering each other. Empty when nothing is showing. */}
84
- <div className="absolute bottom-6 right-6 z-[91] flex flex-col items-end gap-2">
80
+ <div className="absolute bottom-6 right-6 z-91 flex flex-col items-end gap-2">
85
81
  {toasts.map((toast) => (
86
82
  <StudioToast
87
83
  key={toast.id}
@@ -347,7 +347,7 @@ export function StudioRightPanel({
347
347
  aria-label="Resize inspector panel"
348
348
  aria-orientation="vertical"
349
349
  tabIndex={0}
350
- className="group relative w-[3px] flex-shrink-0 cursor-col-resize outline-none focus-visible:bg-studio-accent/20"
350
+ className="group relative w-[3px] shrink-0 cursor-col-resize outline-hidden focus-visible:bg-studio-accent/20"
351
351
  style={{ touchAction: "none" }}
352
352
  onPointerDown={(e) => handlePanelResizeStart("right", e)}
353
353
  onPointerMove={handlePanelResizeMove}
@@ -364,12 +364,12 @@ export function StudioRightPanel({
364
364
  {/* Asymmetric hit zone: 8px into the preview's p-2 gutter (the only dead
365
365
  space), the 3px seam, 2px into the card. Stops short of the 24px WCAG
366
366
  2.5.8 target because the next pixel each way is live. */}
367
- <div className="absolute inset-y-0 -left-[8px] w-[13px]" />
367
+ <div className="absolute inset-y-0 left-[-8px] w-[13px]" />
368
368
  {/* Visible hairline */}
369
369
  <div className="absolute top-1/2 left-0 h-[52px] w-[3px] -translate-y-1/2 bg-white/12 transition-colors group-hover:bg-white/18 group-active:bg-white/24" />
370
370
  </div>
371
371
  <div
372
- className="flex min-w-0 flex-shrink-0 flex-col overflow-hidden rounded-lg border border-neutral-800 bg-neutral-950"
372
+ className="flex min-w-0 shrink-0 flex-col overflow-hidden rounded-lg border border-neutral-800 bg-neutral-950"
373
373
  style={{ width: rightWidth }}
374
374
  >
375
375
  {captionEditMode ? (
@@ -444,7 +444,7 @@ export function StudioRightPanel({
444
444
  role="separator"
445
445
  aria-label="Resize Layers and Design panes"
446
446
  aria-orientation="horizontal"
447
- className="group flex h-2 flex-shrink-0 cursor-row-resize items-center justify-center border-y border-neutral-800 bg-neutral-900"
447
+ className="group flex h-2 shrink-0 cursor-row-resize items-center justify-center border-y border-neutral-800 bg-neutral-900"
448
448
  style={{ touchAction: "none" }}
449
449
  onPointerDown={handleInspectorSplitResizeStart}
450
450
  onPointerMove={handleInspectorSplitResizeMove}
@@ -30,7 +30,7 @@ export function StudioToast({ message, tone, leaving, onDismiss }: StudioToastPr
30
30
  }}
31
31
  >
32
32
  <span
33
- className={`min-w-0 break-words leading-5 ${isError ? "text-red-200" : "text-neutral-200"}`}
33
+ className={`min-w-0 wrap-break-word leading-5 ${isError ? "text-red-200" : "text-neutral-200"}`}
34
34
  >
35
35
  {message}
36
36
  </span>
@@ -38,7 +38,7 @@ export function StudioToast({ message, tone, leaving, onDismiss }: StudioToastPr
38
38
  <button
39
39
  type="button"
40
40
  onClick={onDismiss}
41
- className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-white/10 hover:text-neutral-300"
41
+ className="flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-white/10 hover:text-neutral-300"
42
42
  aria-label="Dismiss"
43
43
  >
44
44
  <svg
@@ -189,8 +189,8 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
189
189
  // CapCut-flat icon buttons: no per-button border/box chrome — a transparent
190
190
  // 28px hit area with a subtle rounded hover wash, consistent 16px glyphs.
191
191
  const flatBtn = "flex h-7 w-7 items-center justify-center rounded-md transition-colors";
192
- const flatIdle = `${flatBtn} text-neutral-400 hover:bg-white/[0.06] hover:text-neutral-200 active:scale-[0.98]`;
193
- const flatActive = `${flatBtn} bg-white/[0.08] text-neutral-100 active:scale-[0.98]`;
192
+ const flatIdle = `${flatBtn} text-neutral-400 hover:bg-white/6 hover:text-neutral-200 active:scale-[0.98]`;
193
+ const flatActive = `${flatBtn} bg-white/8 text-neutral-100 active:scale-[0.98]`;
194
194
  const flatDisabled = `${flatBtn} text-neutral-700 cursor-not-allowed`;
195
195
 
196
196
  return (
@@ -297,7 +297,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
297
297
  className={
298
298
  !onToggleKeyframe
299
299
  ? flatDisabled
300
- : `${flatBtn} active:scale-[0.98] hover:bg-white/[0.06] ${
300
+ : `${flatBtn} active:scale-[0.98] hover:bg-white/6 ${
301
301
  keyframeState === "active"
302
302
  ? "text-studio-accent"
303
303
  : keyframeState === "inactive"
@@ -332,7 +332,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
332
332
  onClick={() => setAutoKeyframeEnabled(!autoKeyframeEnabled)}
333
333
  aria-label="Auto-record manual edits as keyframes"
334
334
  aria-pressed={autoKeyframeEnabled}
335
- className={`${flatBtn} active:scale-[0.98] hover:bg-white/[0.06] ${
335
+ className={`${flatBtn} active:scale-[0.98] hover:bg-white/6 ${
336
336
  autoKeyframeEnabled
337
337
  ? "text-red-400 hover:text-red-300"
338
338
  : "text-neutral-600 hover:text-neutral-400"
@@ -429,7 +429,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
429
429
  }}
430
430
  className={
431
431
  canAdd
432
- ? `${flatBtn} text-neutral-400 hover:bg-white/[0.06] hover:text-[#22c55e] active:scale-[0.98]`
432
+ ? `${flatBtn} text-neutral-400 hover:bg-white/6 hover:text-[#22c55e] active:scale-[0.98]`
433
433
  : flatDisabled
434
434
  }
435
435
  >
@@ -467,7 +467,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
467
467
  className={`h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${
468
468
  thumbnailsVisible
469
469
  ? "bg-studio-accent/10 text-studio-accent"
470
- : "text-neutral-400 hover:bg-white/[0.06] hover:text-neutral-200"
470
+ : "text-neutral-400 hover:bg-white/6 hover:text-neutral-200"
471
471
  }`}
472
472
  >
473
473
  <Image size={16} aria-hidden="true" />
@@ -480,7 +480,7 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool
480
480
  className={`h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${
481
481
  zoomMode === "fit"
482
482
  ? "bg-studio-accent/10 text-studio-accent"
483
- : "text-neutral-400 hover:bg-white/[0.06] hover:text-neutral-200"
483
+ : "text-neutral-400 hover:bg-white/6 hover:text-neutral-200"
484
484
  }`}
485
485
  >
486
486
  Fit