@hyperframes/studio 0.8.50 → 0.8.52

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 (280) hide show
  1. package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-I5YRzB02.js} +3 -3
  2. package/dist/assets/{index-Dt3qBI-9.js → index-BTAfgesM.js} +1 -1
  3. package/dist/assets/index-CxAu_Okg.css +1 -0
  4. package/dist/assets/index-DocJlW2v.js +376 -0
  5. package/dist/assets/{index-DUAIWxkI.js → index-Du5x7KUR.js} +1 -1
  6. package/dist/index.d.ts +419 -20
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +7496 -8288
  9. package/dist/index.js.map +1 -1
  10. package/dist/styles/tailwind-preset.js.map +1 -1
  11. package/package.json +13 -12
  12. package/src/App.tsx +131 -143
  13. package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
  14. package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
  15. package/src/captions/components/CaptionOverlay.tsx +8 -4
  16. package/src/captions/components/CaptionOverlayUtils.ts +2 -3
  17. package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
  18. package/src/captions/components/CaptionTimeline.tsx +1 -1
  19. package/src/captions/components/shared.tsx +2 -2
  20. package/src/components/AskAgentModal.tsx +5 -5
  21. package/src/components/EditorShell.tsx +8 -7
  22. package/src/components/ExternalFileConflictBanner.tsx +16 -16
  23. package/src/components/LintModal.tsx +6 -16
  24. package/src/components/MediaPreview.tsx +2 -2
  25. package/src/components/RightPanelTabs.test.tsx +151 -0
  26. package/src/components/RightPanelTabs.tsx +59 -0
  27. package/src/components/SaveQueuePausedBanner.tsx +2 -2
  28. package/src/components/StudioHeader.dom.test.tsx +181 -0
  29. package/src/components/StudioHeader.tsx +110 -210
  30. package/src/components/StudioLeftSidebar.tsx +7 -4
  31. package/src/components/StudioOverlays.tsx +1 -1
  32. package/src/components/StudioRightPanel.tsx +51 -39
  33. package/src/components/StudioToast.tsx +2 -2
  34. package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
  35. package/src/components/TimelineHistoryButtons.tsx +54 -0
  36. package/src/components/TimelineToolbar.test.tsx +8 -0
  37. package/src/components/TimelineToolbar.tsx +8 -12
  38. package/src/components/dock/Dock.test.tsx +260 -0
  39. package/src/components/dock/Dock.tsx +281 -0
  40. package/src/components/dock/dock.css +28 -0
  41. package/src/components/dock/dockAccessibility.test.ts +166 -0
  42. package/src/components/dock/dockAccessibility.ts +98 -0
  43. package/src/components/dock/dockLayout.test.ts +134 -0
  44. package/src/components/dock/dockLayout.ts +105 -0
  45. package/src/components/dock/dockLayoutSchema.ts +66 -0
  46. package/src/components/dock/dockLayoutStore.ts +91 -0
  47. package/src/components/dock/dockTestHarness.ts +11 -0
  48. package/src/components/dock/panelRegistry.ts +67 -0
  49. package/src/components/editor/AnimationCard.tsx +4 -4
  50. package/src/components/editor/AnimationCardParts.tsx +6 -6
  51. package/src/components/editor/BlockParamsPanel.tsx +4 -4
  52. package/src/components/editor/BorderRadiusEditor.tsx +1 -1
  53. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  54. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  55. package/src/components/editor/DomEditOverlay.tsx +1 -1
  56. package/src/components/editor/EaseCurveSection.tsx +1 -1
  57. package/src/components/editor/EaseParamFields.tsx +5 -5
  58. package/src/components/editor/FileTree.tsx +4 -4
  59. package/src/components/editor/FileTreeIcons.tsx +1 -1
  60. package/src/components/editor/FileTreeNodes.tsx +13 -13
  61. package/src/components/editor/InlineTextToolbar.tsx +1 -1
  62. package/src/components/editor/InspectorHeaderActions.tsx +3 -3
  63. package/src/components/editor/KeyframeDiamond.tsx +1 -1
  64. package/src/components/editor/KeyframeEaseList.tsx +1 -1
  65. package/src/components/editor/LayersPanel.test.ts +97 -1
  66. package/src/components/editor/LayersPanel.tsx +7 -5
  67. package/src/components/editor/MarqueeOverlay.tsx +1 -1
  68. package/src/components/editor/OffCanvasIndicators.tsx +1 -1
  69. package/src/components/editor/PreviewGuides.tsx +1 -1
  70. package/src/components/editor/PromotableControl.tsx +2 -2
  71. package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
  72. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  73. package/src/components/editor/SnapToolbar.tsx +2 -2
  74. package/src/components/editor/TimelineFxPopover.tsx +1 -1
  75. package/src/components/editor/TopologyLens.test.tsx +28 -0
  76. package/src/components/editor/TopologyLens.tsx +7 -5
  77. package/src/components/editor/Transform3DCube.tsx +2 -2
  78. package/src/components/editor/audioFxRevealTarget.ts +1 -0
  79. package/src/components/editor/domEditing.test.ts +1 -1
  80. package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
  81. package/src/components/editor/domEditingAgentPrompt.ts +4 -3
  82. package/src/components/editor/propertyPanelColor.tsx +11 -11
  83. package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
  84. package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
  85. package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
  86. package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
  87. package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
  88. package/src/components/editor/propertyPanelCommitField.tsx +1 -1
  89. package/src/components/editor/propertyPanelFill.tsx +4 -4
  90. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  91. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
  92. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  93. package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
  94. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
  95. package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
  96. package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
  97. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
  98. package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
  99. package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
  100. package/src/components/editor/propertyPanelFont.tsx +5 -5
  101. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  102. package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
  103. package/src/components/editor/propertyPanelFxControls.tsx +6 -6
  104. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  105. package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
  106. package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
  107. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  108. package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
  109. package/src/components/editor/propertyPanelSections.tsx +8 -8
  110. package/src/components/editor/useColorGradingController.test.ts +38 -0
  111. package/src/components/editor/useColorGradingController.ts +9 -1
  112. package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
  113. package/src/components/editor/useColorGradingPreviews.ts +11 -1
  114. package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
  115. package/src/components/editor/usePreviewCompositionRect.ts +21 -0
  116. package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
  117. package/src/components/feedback/feedbackTrigger.ts +0 -1
  118. package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
  119. package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
  120. package/src/components/nle/NLEPreview.tsx +3 -3
  121. package/src/components/nle/PreviewOverlays.tsx +6 -6
  122. package/src/components/nle/PreviewPane.tsx +3 -3
  123. package/src/components/nle/TimelinePane.tsx +3 -3
  124. package/src/components/nle/TimelineResizeDivider.tsx +2 -2
  125. package/src/components/panels/SlideshowPanel.tsx +2 -2
  126. package/src/components/panels/SlideshowSubPanels.tsx +20 -17
  127. package/src/components/panels/VariablesBindElement.tsx +2 -2
  128. package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
  129. package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
  130. package/src/components/panels/VariablesPanel.tsx +3 -3
  131. package/src/components/panels/VariablesValueControls.tsx +2 -2
  132. package/src/components/pointerTargetSize.test.tsx +8 -0
  133. package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
  134. package/src/components/renders/RenderQueue.test.tsx +7 -0
  135. package/src/components/renders/RenderQueue.tsx +7 -7
  136. package/src/components/renders/RenderQueueItem.tsx +6 -6
  137. package/src/components/sidebar/AssetCard.tsx +8 -8
  138. package/src/components/sidebar/AssetContextMenu.tsx +8 -8
  139. package/src/components/sidebar/AssetsTab.tsx +2 -2
  140. package/src/components/sidebar/AudioRow.tsx +6 -6
  141. package/src/components/sidebar/BlocksTab.tsx +6 -6
  142. package/src/components/sidebar/CompositionsTab.tsx +5 -5
  143. package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
  144. package/src/components/sidebar/LeftSidebar.lintBadge.test.tsx +58 -0
  145. package/src/components/sidebar/LeftSidebar.tsx +19 -5
  146. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  147. package/src/components/timelineToolbarStyles.ts +5 -0
  148. package/src/components/ui/Button.test.tsx +252 -0
  149. package/src/components/ui/Button.tsx +89 -126
  150. package/src/components/ui/IconButton.tsx +38 -0
  151. package/src/components/ui/Input.tsx +115 -0
  152. package/src/components/ui/Menu.test.tsx +374 -0
  153. package/src/components/ui/Menu.tsx +213 -0
  154. package/src/components/ui/NumberField.tsx +122 -0
  155. package/src/components/ui/Popover.tsx +74 -0
  156. package/src/components/ui/SearchInput.tsx +2 -2
  157. package/src/components/ui/Select.tsx +100 -0
  158. package/src/components/ui/Slider.tsx +153 -0
  159. package/src/components/ui/Tabs.test.tsx +98 -0
  160. package/src/components/ui/Tabs.tsx +71 -0
  161. package/src/components/ui/Toggle.tsx +63 -0
  162. package/src/components/ui/Tooltip.test.tsx +110 -0
  163. package/src/components/ui/Tooltip.tsx +38 -112
  164. package/src/components/ui/cn.test.ts +30 -0
  165. package/src/components/ui/cn.ts +25 -0
  166. package/src/components/ui/index.ts +29 -2
  167. package/src/components/ui/mountHost.testHelpers.ts +24 -0
  168. package/src/components/ui/valueControls.test.tsx +524 -0
  169. package/src/hooks/useAppHotkeys.ts +21 -80
  170. package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
  171. package/src/hooks/useCaptionDetection.ts +3 -1
  172. package/src/hooks/useEditHistoryActions.test.tsx +90 -0
  173. package/src/hooks/useEditHistoryActions.ts +106 -0
  174. package/src/hooks/useLintModal.test.tsx +65 -0
  175. package/src/hooks/useLintModal.ts +3 -0
  176. package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
  177. package/src/hooks/useSdkSession.ts +43 -10
  178. package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
  179. package/src/hooks/useServerConnection.ts +43 -10
  180. package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
  181. package/src/hooks/useTimelineAssetDropOps.ts +52 -11
  182. package/src/index.ts +51 -0
  183. package/src/main.tsx +9 -3
  184. package/src/player/components/AudioWaveform.tsx +1 -1
  185. package/src/player/components/AutomationSelectionMenu.tsx +3 -3
  186. package/src/player/components/ClipContextMenu.tsx +3 -3
  187. package/src/player/components/CompositionThumbnail.test.ts +0 -3
  188. package/src/player/components/CompositionThumbnail.tsx +3 -4
  189. package/src/player/components/EditModal.tsx +1 -1
  190. package/src/player/components/ImageThumbnail.tsx +1 -1
  191. package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
  192. package/src/player/components/LayerDisclosureRow.tsx +2 -2
  193. package/src/player/components/PlayerControls.tsx +1 -1
  194. package/src/player/components/ShortcutsPanel.tsx +9 -9
  195. package/src/player/components/SpeedMenu.tsx +3 -5
  196. package/src/player/components/Timeline.tsx +2 -1
  197. package/src/player/components/TimelineCanvas.tsx +21 -14
  198. package/src/player/components/TimelineClip.tsx +0 -1
  199. package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
  200. package/src/player/components/TimelineEmptyState.tsx +5 -5
  201. package/src/player/components/TimelineFxButton.tsx +6 -6
  202. package/src/player/components/TimelineGroupHeader.tsx +5 -5
  203. package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
  204. package/src/player/components/TimelineGroupRow.tsx +1 -1
  205. package/src/player/components/TimelineLanes.test.tsx +32 -0
  206. package/src/player/components/TimelineLanes.tsx +2 -5
  207. package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
  208. package/src/player/components/TimelineRuler.tsx +1 -1
  209. package/src/player/components/TimelineShortcutHint.tsx +1 -1
  210. package/src/player/components/TimelineTrackHeader.tsx +1 -1
  211. package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
  212. package/src/player/components/TimelineTypes.ts +2 -1
  213. package/src/player/components/TrackGapContextMenu.tsx +1 -1
  214. package/src/player/components/VideoThumbnail.tsx +4 -2
  215. package/src/player/components/VolumeControl.tsx +2 -2
  216. package/src/player/components/timelineCallbacks.ts +8 -8
  217. package/src/player/components/timelineClipDragPreview.ts +51 -27
  218. package/src/player/components/timelineClipDragTypes.ts +3 -0
  219. package/src/player/components/timelineDragDrop.test.tsx +61 -1
  220. package/src/player/components/timelineDragDrop.ts +57 -15
  221. package/src/player/components/timelineLaneProps.ts +3 -0
  222. package/src/player/components/timelineSnapping.test.ts +27 -0
  223. package/src/player/components/timelineSnapping.ts +14 -1
  224. package/src/player/components/trackHeaderLabelRows.tsx +4 -4
  225. package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
  226. package/src/player/components/useTimelineClipDrag.ts +30 -3
  227. package/src/player/lib/describeClips.ts +3 -54
  228. package/src/player/lib/playbackShortcuts.ts +2 -0
  229. package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
  230. package/src/player/store/previewIframeStore.test.tsx +29 -0
  231. package/src/player/store/previewIframeStore.ts +25 -0
  232. package/src/renderQueueExports.test.tsx +58 -0
  233. package/src/styles/classCandidates.test.ts +105 -0
  234. package/src/styles/classCandidates.ts +281 -0
  235. package/src/styles/hex-baseline.json +84 -0
  236. package/src/styles/hexRatchet.test.ts +201 -0
  237. package/src/styles/iconTokens.ts +20 -0
  238. package/src/styles/palette-reset.css +6 -0
  239. package/src/styles/studio.css +24 -21
  240. package/src/styles/styleSources.test.ts +20 -0
  241. package/src/styles/styleSources.ts +47 -0
  242. package/src/styles/tailwind-preset.shared.js +3 -0
  243. package/src/styles/theme.css +243 -0
  244. package/src/styles/theme.test.ts +183 -0
  245. package/src/styles/tokenGate.test.ts +109 -0
  246. package/src/utils/sourcePatcher.ts +1 -1
  247. package/src/utils/studioHelpers.test.ts +18 -0
  248. package/src/utils/studioHelpers.ts +11 -1
  249. package/src/utils/studioUiPreferences.ts +7 -0
  250. package/src/utils/timelineDropTrackInsert.test.ts +134 -0
  251. package/src/utils/timelineDropTrackInsert.ts +83 -0
  252. package/src/utils/typingTarget.ts +4 -0
  253. package/dist/assets/index-n2ml4tUn.js +0 -442
  254. package/dist/assets/index-pO6p0H27.css +0 -1
  255. package/src/components/PanelTabButton.tsx +0 -31
  256. package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
  257. package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
  258. package/src/components/storyboard/FramePoster.test.tsx +0 -61
  259. package/src/components/storyboard/FramePoster.tsx +0 -86
  260. package/src/components/storyboard/StoryboardDirection.tsx +0 -45
  261. package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
  262. package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
  263. package/src/components/storyboard/StoryboardGrid.tsx +0 -55
  264. package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
  265. package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
  266. package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
  267. package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
  268. package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
  269. package/src/components/storyboard/StoryboardView.tsx +0 -169
  270. package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
  271. package/src/components/storyboard/frameComments.test.ts +0 -95
  272. package/src/components/storyboard/frameComments.ts +0 -115
  273. package/src/components/storyboard/frameStatus.ts +0 -36
  274. package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
  275. package/src/components/storyboard/storyboardReviewStage.ts +0 -45
  276. package/src/components/storyboard/useFrameComments.ts +0 -98
  277. package/src/contexts/ViewModeContext.tsx +0 -142
  278. package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
  279. package/src/hooks/useProjectSignaturePoll.ts +0 -68
  280. package/src/hooks/useStoryboard.ts +0 -99
@@ -66,8 +66,8 @@ export function AskAgentModal({
66
66
  <div
67
67
  className={
68
68
  anchorPoint
69
- ? "hf-backdrop-in fixed inset-0 z-[100] bg-black/60 backdrop-blur-sm"
70
- : "hf-backdrop-in fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm"
69
+ ? "hf-backdrop-in fixed inset-0 z-100 bg-black/60 backdrop-blur-xs"
70
+ : "hf-backdrop-in fixed inset-0 z-100 flex items-center justify-center bg-black/60 backdrop-blur-xs"
71
71
  }
72
72
  onClick={requestClose}
73
73
  >
@@ -77,7 +77,7 @@ export function AskAgentModal({
77
77
  aria-modal="true"
78
78
  aria-label="Copy prompt to AI agent"
79
79
  tabIndex={-1}
80
- className={`w-[480px] rounded-2xl border border-neutral-800 bg-neutral-950 shadow-2xl outline-none ${
80
+ className={`w-[480px] rounded-2xl border border-neutral-800 bg-neutral-950 shadow-2xl outline-hidden ${
81
81
  anchorPoint ? "fixed" : ""
82
82
  }`}
83
83
  style={modalPositionStyle}
@@ -112,7 +112,7 @@ export function AskAgentModal({
112
112
  <div className="px-5 py-4 space-y-3">
113
113
  <textarea
114
114
  ref={inputRef}
115
- className="w-full h-24 px-3 py-2 rounded-lg border border-neutral-800 bg-neutral-900/60 text-sm text-neutral-200 placeholder-neutral-600 resize-none focus:outline-none focus:border-studio-accent/60 focus:ring-1 focus:ring-studio-accent/30"
115
+ className="w-full h-24 px-3 py-2 rounded-lg border border-neutral-800 bg-neutral-900/60 text-sm text-neutral-200 placeholder-neutral-600 resize-none focus:outline-hidden focus:border-studio-accent/60 focus:ring-1 focus:ring-studio-accent/30"
116
116
  placeholder="Describe what you want to change…"
117
117
  value={value}
118
118
  onChange={(e) => setValue(e.target.value)}
@@ -127,7 +127,7 @@ export function AskAgentModal({
127
127
  <summary className="text-[11px] text-neutral-500 cursor-pointer select-none hover:text-neutral-400">
128
128
  Context included in prompt
129
129
  </summary>
130
- <pre className="mt-2 max-h-40 overflow-auto rounded-lg bg-neutral-900/80 px-3 py-2 text-[11px] leading-relaxed text-neutral-500 whitespace-pre-wrap break-words border border-neutral-800/50">
130
+ <pre className="mt-2 max-h-40 overflow-auto rounded-lg bg-neutral-900/80 px-3 py-2 text-[11px] leading-relaxed text-neutral-500 whitespace-pre-wrap wrap-break-word border border-neutral-800/50">
131
131
  {contextPreview}
132
132
  </pre>
133
133
  </details>
@@ -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;
@@ -67,6 +65,10 @@ export interface EditorShellProps extends TimelineEditCallbackDeps {
67
65
  isGestureRecording?: boolean;
68
66
  recordingState?: GestureRecordingState;
69
67
  onToggleRecording?: () => void;
68
+ /**
69
+ * Host layer over the preview, positioned with `usePreviewCompositionRect`. Hidden in
70
+ * fullscreen and during a block preview; below the selection overlay past z-index 10.
71
+ */
70
72
  gestureOverlay?: ReactNode;
71
73
  }
72
74
 
@@ -76,7 +78,6 @@ export interface EditorShellProps extends TimelineEditCallbackDeps {
76
78
  export function EditorShell({
77
79
  left,
78
80
  right,
79
- hidden,
80
81
  timelineToolbar,
81
82
  renderClipContent,
82
83
  handleTimelineElementDelete,
@@ -157,7 +158,7 @@ export function EditorShell({
157
158
  });
158
159
 
159
160
  return (
160
- <div className={`flex flex-col flex-1 min-h-0${hidden ? " hidden" : ""}`}>
161
+ <div className="flex flex-col flex-1 min-h-0">
161
162
  <TimelineEditProvider value={timelineEditCallbacks}>
162
163
  <NLEProvider
163
164
  projectId={projectId}
@@ -279,7 +280,7 @@ function EditorShellBody({
279
280
  ref={containerRef}
280
281
  // Shell canvas is a step LIGHTER than the near-black panel cards so the
281
282
  // gaps between panels read as visible seams (CapCut-style).
282
- className="flex flex-col flex-1 min-h-0 bg-[#18181B]"
283
+ className="flex flex-col flex-1 min-h-0 bg-panel-surface"
283
284
  onKeyDown={handleKeyDown}
284
285
  tabIndex={-1}
285
286
  >
@@ -316,7 +317,7 @@ function EditorShellBody({
316
317
  onSelectTimelineElement={onSelectTimelineElement}
317
318
  timelineFooter={
318
319
  captionEditMode ? (
319
- <div className="border-t border-neutral-800/30 flex-shrink-0" style={{ height: 60 }}>
320
+ <div className="border-t border-neutral-800/30 shrink-0" style={{ height: 60 }}>
320
321
  <div className="flex items-center gap-1.5 px-2 py-0.5">
321
322
  <span className="text-[9px] font-medium text-neutral-500 uppercase tracking-wider">
322
323
  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>
@@ -0,0 +1,151 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ /**
4
+ * The strip's keyboard behaviour, which tab reads as selected, and how the hotkey
5
+ * filters classify the elements (KTD13). The arrow-key tests are new behaviour, not a port.
6
+ */
7
+ import React, { act } from "react";
8
+ import { createRoot, type Root } from "react-dom/client";
9
+ import { afterEach, expect, it, vi } from "vitest";
10
+ import { RightPanelTabs, type RightPanelTabDescriptor } from "./RightPanelTabs";
11
+ import { isTypingTarget } from "../utils/typingTarget";
12
+ import { shouldIgnorePlaybackShortcutTarget } from "../player/lib/playbackShortcuts";
13
+
14
+ (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
15
+
16
+ const IDS = ["design", "layers", "renders", "variables"];
17
+
18
+ let mounted: { root: Root; host: HTMLElement } | null = null;
19
+
20
+ afterEach(() => {
21
+ if (!mounted) return;
22
+ const { root, host } = mounted;
23
+ mounted = null;
24
+ act(() => root.unmount());
25
+ host.remove();
26
+ });
27
+
28
+ /** The harness owns the active state too, so a selection moves as it does in the real panel. */
29
+ function mount(
30
+ initialActive: string[],
31
+ activateOnFocus = true,
32
+ ): {
33
+ host: HTMLElement;
34
+ selections: ReturnType<typeof vi.fn>;
35
+ } {
36
+ const selections = vi.fn();
37
+
38
+ function Harness() {
39
+ const [active, setActive] = React.useState(initialActive);
40
+ const tabs: RightPanelTabDescriptor[] = IDS.map((id) => ({
41
+ id,
42
+ label: id,
43
+ tooltip: `${id} tooltip`,
44
+ active: active.includes(id),
45
+ onSelect: () => {
46
+ selections(id);
47
+ setActive([id]);
48
+ },
49
+ }));
50
+ return <RightPanelTabs tabs={tabs} activateOnFocus={activateOnFocus} />;
51
+ }
52
+
53
+ const host = document.createElement("div");
54
+ document.body.append(host);
55
+ const root = createRoot(host);
56
+ mounted = { root, host };
57
+ act(() => root.render(<Harness />));
58
+ return { host, selections };
59
+ }
60
+
61
+ function tab(host: HTMLElement, id: string): HTMLElement {
62
+ const el = host.querySelector<HTMLElement>(`[data-tab-id="${id}"]`);
63
+ if (!el) throw new Error(`no tab for ${id}`);
64
+ return el;
65
+ }
66
+
67
+ function selected(host: HTMLElement): string | null {
68
+ return (
69
+ host.querySelector('[role="tab"][aria-selected="true"]')?.getAttribute("data-tab-id") ?? null
70
+ );
71
+ }
72
+
73
+ /** Base UI moves the roving tabindex synchronously and the focus one task later. */
74
+ async function arrow(key: string): Promise<void> {
75
+ const target = document.activeElement ?? document.body;
76
+ act(() => {
77
+ target.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, composed: true }));
78
+ });
79
+ await act(async () => {
80
+ await new Promise((resolve) => setTimeout(resolve, 0));
81
+ });
82
+ }
83
+
84
+ it("moves from Design to Layers with ArrowRight and asks the panel to switch", async () => {
85
+ const { host, selections } = mount(["design"]);
86
+ act(() => tab(host, "design").focus());
87
+
88
+ await arrow("ArrowRight");
89
+
90
+ expect(document.activeElement).toBe(tab(host, "layers"));
91
+ expect(selections).toHaveBeenCalledWith("layers");
92
+ });
93
+
94
+ it("only moves focus on an arrow key when selection would toggle a pane", async () => {
95
+ const { host, selections } = mount(["design"], false);
96
+ act(() => tab(host, "design").focus());
97
+
98
+ await arrow("ArrowRight");
99
+
100
+ expect(document.activeElement).toBe(tab(host, "layers"));
101
+ expect(selections).not.toHaveBeenCalled();
102
+ });
103
+
104
+ it("jumps to the first and last tab with Home and End", async () => {
105
+ const { host, selections } = mount(["design"]);
106
+ act(() => tab(host, "design").focus());
107
+
108
+ await arrow("End");
109
+ expect(document.activeElement).toBe(tab(host, "variables"));
110
+ expect(selections).toHaveBeenLastCalledWith("variables");
111
+
112
+ await arrow("Home");
113
+ expect(document.activeElement).toBe(tab(host, "design"));
114
+ expect(selections).toHaveBeenLastCalledWith("design");
115
+ });
116
+
117
+ it("selects the tab whose content is on screen", () => {
118
+ const { host } = mount(["renders"]);
119
+
120
+ expect(selected(host)).toBe("renders");
121
+ });
122
+
123
+ it("leaves every tab unselected when the panel shows something the strip has no tab for", () => {
124
+ // Block params take over the panel body without a tab of their own. The old
125
+ // buttons all read unpressed in that state; nothing should read selected now.
126
+ const { host } = mount([]);
127
+
128
+ expect(selected(host)).toBe(null);
129
+ });
130
+
131
+ it("keeps a second open pane looking open in the legacy split inspector", () => {
132
+ // Design and Layers render together there. Only one tab can carry
133
+ // aria-selected, so the other has to keep the selected look or the strip
134
+ // would claim a pane is closed while it is on screen.
135
+ const { host } = mount(["design", "layers"]);
136
+
137
+ expect(selected(host)).toBe("design");
138
+ expect(tab(host, "layers").className).toContain("bg-hover");
139
+ });
140
+
141
+ it("classifies its tabs for the hotkey filters exactly as the old buttons did (KTD13)", () => {
142
+ // The old strip rendered plain <button>s: not a typing target, and claimed by
143
+ // the playback filter through its `button` selector. Base UI's tab is a
144
+ // <button> too, so both verdicts have to be unchanged.
145
+ const { host } = mount(["design"]);
146
+ const el = tab(host, "design");
147
+
148
+ expect(el.tagName).toBe("BUTTON");
149
+ expect(isTypingTarget(el)).toBe(false);
150
+ expect(shouldIgnorePlaybackShortcutTarget(el)).toBe(true);
151
+ });
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The inspector tab strip on the shared Tabs primitive. The selected tab is derived from the
3
+ * panel's state; in the legacy split inspector two can be open, only one holds `aria-selected`.
4
+ */
5
+
6
+ import { Tab, Tabs, TabsList, Tooltip, cn } from "./ui";
7
+
8
+ export interface RightPanelTabDescriptor {
9
+ /** Stable id, also the `data-tab-id` attribute the strip is queried by. */
10
+ id: string;
11
+ label: string;
12
+ tooltip: string;
13
+ /** Whether this tab's content is on screen right now. */
14
+ active: boolean;
15
+ /** Run when the strip moves to this tab, by click or by arrow key. */
16
+ onSelect: () => void;
17
+ }
18
+
19
+ /** The look `aria-selected` gives the selected tab, for a second open pane. */
20
+ const SELECTED_LOOK = "bg-hover text-text-0";
21
+
22
+ export function RightPanelTabs({
23
+ tabs,
24
+ activateOnFocus = true,
25
+ }: {
26
+ tabs: readonly RightPanelTabDescriptor[];
27
+ /** False when `onSelect` toggles a pane, so an arrow key only moves focus. */
28
+ activateOnFocus?: boolean;
29
+ }) {
30
+ // `null` when the layout holds a tab this strip does not show (block params),
31
+ // which leaves every tab unselected, exactly as the old buttons did.
32
+ const value = tabs.find((tab) => tab.active)?.id ?? null;
33
+
34
+ return (
35
+ <Tabs
36
+ value={value}
37
+ onValueChange={(next) => {
38
+ tabs.find((tab) => tab.id === next)?.onSelect();
39
+ }}
40
+ >
41
+ <TabsList
42
+ aria-label="Inspector panels"
43
+ activateOnFocus={activateOnFocus}
44
+ className="flex min-w-0 items-center gap-1 overflow-hidden rounded-none border-b border-border-strong bg-transparent px-3 py-2"
45
+ >
46
+ {tabs.map((tab) => (
47
+ <Tooltip key={tab.id} label={tab.tooltip} side="bottom">
48
+ <Tab
49
+ value={tab.id}
50
+ className={cn("h-ctl-lg rounded-lg px-3 font-medium", tab.active && SELECTED_LOOK)}
51
+ >
52
+ {tab.label}
53
+ </Tab>
54
+ </Tooltip>
55
+ ))}
56
+ </TabsList>
57
+ </Tabs>
58
+ );
59
+ }
@@ -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>