@hyperframes/studio 0.8.50 → 0.8.53-desktop.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-PTEnAHhg.js} +3 -3
  2. package/dist/assets/{index-DUAIWxkI.js → index--myXp8ui.js} +1 -1
  3. package/dist/assets/{index-Dt3qBI-9.js → index-BjslOS9g.js} +1 -1
  4. package/dist/assets/index-BnXZuIJD.css +1 -0
  5. package/dist/assets/index-CNyviK_l.js +390 -0
  6. package/dist/index.css +29 -0
  7. package/dist/index.css.map +1 -0
  8. package/dist/index.d.ts +825 -24
  9. package/dist/index.html +2 -2
  10. package/dist/index.js +8911 -9853
  11. package/dist/index.js.map +1 -1
  12. package/dist/styles/tailwind-preset.js.map +1 -1
  13. package/package.json +13 -12
  14. package/src/App.tsx +145 -163
  15. package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
  16. package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
  17. package/src/captions/components/CaptionOverlay.tsx +8 -4
  18. package/src/captions/components/CaptionOverlayUtils.ts +2 -3
  19. package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
  20. package/src/captions/components/CaptionTimeline.tsx +1 -1
  21. package/src/captions/components/shared.tsx +2 -2
  22. package/src/components/AskAgentModal.tsx +5 -5
  23. package/src/components/EditorShell.selectionSync.test.tsx +9 -4
  24. package/src/components/EditorShell.tsx +59 -62
  25. package/src/components/ExternalFileConflictBanner.tsx +16 -16
  26. package/src/components/LintModal.tsx +6 -16
  27. package/src/components/MediaPreview.tsx +2 -2
  28. package/src/components/RightPanelTabs.test.tsx +151 -0
  29. package/src/components/RightPanelTabs.tsx +59 -0
  30. package/src/components/SaveQueuePausedBanner.tsx +2 -2
  31. package/src/components/StudioHeader.dom.test.tsx +181 -0
  32. package/src/components/StudioHeader.test.ts +0 -12
  33. package/src/components/StudioHeader.tsx +118 -221
  34. package/src/components/StudioLeftPanels.tsx +202 -0
  35. package/src/components/StudioOverlays.tsx +1 -1
  36. package/src/components/{StudioRightPanel.tsx → StudioRightPanels.tsx} +57 -194
  37. package/src/components/{StudioRightPanel.types.ts → StudioRightPanels.types.ts} +4 -3
  38. package/src/components/StudioToast.tsx +2 -2
  39. package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
  40. package/src/components/TimelineHistoryButtons.tsx +54 -0
  41. package/src/components/TimelineToolbar.test.tsx +8 -0
  42. package/src/components/TimelineToolbar.tsx +8 -12
  43. package/src/components/dock/Dock.test.tsx +274 -0
  44. package/src/components/dock/Dock.tsx +291 -0
  45. package/src/components/dock/dock.css +28 -0
  46. package/src/components/dock/dockAccessibility.test.ts +166 -0
  47. package/src/components/dock/dockAccessibility.ts +98 -0
  48. package/src/components/dock/dockLayout.test.ts +134 -0
  49. package/src/components/dock/dockLayout.ts +105 -0
  50. package/src/components/dock/dockLayoutSchema.ts +66 -0
  51. package/src/components/dock/dockLayoutStore.test.ts +28 -0
  52. package/src/components/dock/dockLayoutStore.ts +108 -0
  53. package/src/components/dock/dockTestHarness.ts +11 -0
  54. package/src/components/dock/panelRegistry.ts +67 -0
  55. package/src/components/editor/AnimationCard.tsx +4 -4
  56. package/src/components/editor/AnimationCardParts.tsx +6 -6
  57. package/src/components/editor/BlockParamsPanel.tsx +4 -4
  58. package/src/components/editor/BorderRadiusEditor.tsx +1 -1
  59. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  60. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  61. package/src/components/editor/DomEditOverlay.tsx +1 -1
  62. package/src/components/editor/EaseCurveSection.tsx +1 -1
  63. package/src/components/editor/EaseParamFields.tsx +5 -5
  64. package/src/components/editor/FileTree.tsx +4 -4
  65. package/src/components/editor/FileTreeIcons.tsx +1 -1
  66. package/src/components/editor/FileTreeNodes.tsx +13 -13
  67. package/src/components/editor/InlineTextToolbar.tsx +1 -1
  68. package/src/components/editor/InspectorHeaderActions.tsx +3 -3
  69. package/src/components/editor/KeyframeDiamond.tsx +1 -1
  70. package/src/components/editor/KeyframeEaseList.tsx +1 -1
  71. package/src/components/editor/LayersPanel.test.ts +97 -1
  72. package/src/components/editor/LayersPanel.tsx +7 -5
  73. package/src/components/editor/MarqueeOverlay.tsx +1 -1
  74. package/src/components/editor/OffCanvasIndicators.tsx +1 -1
  75. package/src/components/editor/PreviewGuides.tsx +1 -1
  76. package/src/components/editor/PromotableControl.tsx +2 -2
  77. package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
  78. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  79. package/src/components/editor/SnapToolbar.test.tsx +0 -3
  80. package/src/components/editor/SnapToolbar.tsx +2 -2
  81. package/src/components/editor/TimelineFxPopover.tsx +1 -1
  82. package/src/components/editor/TopologyLens.test.tsx +28 -0
  83. package/src/components/editor/TopologyLens.tsx +7 -5
  84. package/src/components/editor/Transform3DCube.tsx +2 -2
  85. package/src/components/editor/audioFxRevealTarget.ts +1 -0
  86. package/src/components/editor/domEditing.test.ts +1 -1
  87. package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
  88. package/src/components/editor/domEditingAgentPrompt.ts +4 -3
  89. package/src/components/editor/propertyPanelColor.tsx +11 -11
  90. package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
  91. package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
  92. package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
  93. package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
  94. package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
  95. package/src/components/editor/propertyPanelCommitField.tsx +1 -1
  96. package/src/components/editor/propertyPanelFill.tsx +4 -4
  97. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  98. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
  99. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  100. package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
  101. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
  102. package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
  103. package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
  104. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
  105. package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
  106. package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
  107. package/src/components/editor/propertyPanelFont.tsx +5 -5
  108. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  109. package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
  110. package/src/components/editor/propertyPanelFxControls.tsx +6 -6
  111. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  112. package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
  113. package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
  114. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  115. package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
  116. package/src/components/editor/propertyPanelSections.tsx +8 -8
  117. package/src/components/editor/useColorGradingController.test.ts +38 -0
  118. package/src/components/editor/useColorGradingController.ts +9 -1
  119. package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
  120. package/src/components/editor/useColorGradingPreviews.ts +11 -1
  121. package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
  122. package/src/components/editor/usePreviewCompositionRect.ts +21 -0
  123. package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
  124. package/src/components/feedback/feedbackTrigger.ts +0 -1
  125. package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
  126. package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
  127. package/src/components/nle/NLEContext.tsx +0 -43
  128. package/src/components/nle/NLEPreview.tsx +3 -3
  129. package/src/components/nle/PreviewOverlays.tsx +6 -6
  130. package/src/components/nle/PreviewPane.tsx +3 -3
  131. package/src/components/nle/TimelinePane.tsx +53 -67
  132. package/src/components/panels/SlideshowPanel.tsx +3 -3
  133. package/src/components/panels/SlideshowSubPanels.tsx +20 -17
  134. package/src/components/panels/VariablesBindElement.tsx +2 -2
  135. package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
  136. package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
  137. package/src/components/panels/VariablesPanel.tsx +4 -4
  138. package/src/components/panels/VariablesValueControls.tsx +2 -2
  139. package/src/components/pointerTargetSize.test.tsx +8 -0
  140. package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
  141. package/src/components/renders/RenderQueue.test.tsx +7 -0
  142. package/src/components/renders/RenderQueue.tsx +7 -7
  143. package/src/components/renders/RenderQueueItem.tsx +6 -6
  144. package/src/components/renders/RenderQueuePanel.tsx +1 -1
  145. package/src/components/sidebar/AssetCard.tsx +8 -8
  146. package/src/components/sidebar/AssetContextMenu.tsx +8 -8
  147. package/src/components/sidebar/AssetsTab.tsx +2 -2
  148. package/src/components/sidebar/AudioRow.tsx +6 -6
  149. package/src/components/sidebar/BlocksTab.tsx +6 -6
  150. package/src/components/sidebar/{LeftSidebar.masterComposition.test.tsx → CompositionsPanel.masterComposition.test.tsx} +6 -9
  151. package/src/components/sidebar/CompositionsPanel.tsx +15 -0
  152. package/src/components/sidebar/CompositionsTab.tsx +5 -5
  153. package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
  154. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  155. package/src/components/sidebar/SidebarLintButton.test.tsx +53 -0
  156. package/src/components/sidebar/SidebarLintButton.tsx +50 -0
  157. package/src/components/timelineToolbarStyles.ts +5 -0
  158. package/src/components/ui/Button.test.tsx +252 -0
  159. package/src/components/ui/Button.tsx +89 -126
  160. package/src/components/ui/IconButton.tsx +38 -0
  161. package/src/components/ui/Input.tsx +115 -0
  162. package/src/components/ui/Menu.test.tsx +374 -0
  163. package/src/components/ui/Menu.tsx +213 -0
  164. package/src/components/ui/NumberField.tsx +122 -0
  165. package/src/components/ui/Popover.tsx +74 -0
  166. package/src/components/ui/SearchInput.tsx +2 -2
  167. package/src/components/ui/Select.tsx +100 -0
  168. package/src/components/ui/Slider.tsx +153 -0
  169. package/src/components/ui/Tabs.test.tsx +98 -0
  170. package/src/components/ui/Tabs.tsx +71 -0
  171. package/src/components/ui/Toggle.tsx +63 -0
  172. package/src/components/ui/Tooltip.test.tsx +110 -0
  173. package/src/components/ui/Tooltip.tsx +38 -112
  174. package/src/components/ui/cn.test.ts +30 -0
  175. package/src/components/ui/cn.ts +25 -0
  176. package/src/components/ui/index.ts +29 -2
  177. package/src/components/ui/mountHost.testHelpers.ts +24 -0
  178. package/src/components/ui/valueControls.test.tsx +524 -0
  179. package/src/contexts/PanelLayoutContext.tsx +3 -57
  180. package/src/hooks/appHotkeysDispatch.ts +3 -4
  181. package/src/hooks/timelineEditPermission.ts +31 -0
  182. package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +0 -2
  183. package/src/hooks/useAppHotkeys.test.ts +0 -1
  184. package/src/hooks/useAppHotkeys.textEditing.test.tsx +0 -3
  185. package/src/hooks/useAppHotkeys.ts +21 -84
  186. package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
  187. package/src/hooks/useCaptionDetection.ts +3 -1
  188. package/src/hooks/useDomEditPreviewSync.ts +4 -6
  189. package/src/hooks/useDomEditSession.ts +0 -7
  190. package/src/hooks/useDomEditWiring.ts +4 -9
  191. package/src/hooks/useEditHistoryActions.test.tsx +90 -0
  192. package/src/hooks/useEditHistoryActions.ts +106 -0
  193. package/src/hooks/useFileManager.ts +21 -123
  194. package/src/hooks/useLintModal.test.tsx +65 -0
  195. package/src/hooks/useLintModal.ts +3 -0
  196. package/src/hooks/usePanelLayout.test.ts +108 -296
  197. package/src/hooks/usePanelLayout.ts +50 -255
  198. package/src/hooks/usePersistentEditHistory.ts +1 -1
  199. package/src/hooks/useProjectFileWriter.ts +150 -0
  200. package/src/hooks/useRightPanelIntents.test.ts +181 -0
  201. package/src/hooks/useRightPanelIntents.ts +63 -0
  202. package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
  203. package/src/hooks/useSdkSession.ts +43 -10
  204. package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
  205. package/src/hooks/useServerConnection.ts +43 -10
  206. package/src/hooks/useSlideshowTabState.test.ts +9 -31
  207. package/src/hooks/useSlideshowTabState.ts +6 -19
  208. package/src/hooks/useStudioContextValue.test.ts +6 -18
  209. package/src/hooks/useStudioContextValue.ts +5 -26
  210. package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
  211. package/src/hooks/useTimelineAssetDropOps.ts +52 -11
  212. package/src/hooks/useTimelineDeleteOps.test.tsx +111 -0
  213. package/src/hooks/useTimelineEditing.test.tsx +322 -3
  214. package/src/hooks/useTimelineEditing.ts +103 -16
  215. package/src/hooks/useTimelineEditingTypes.ts +3 -0
  216. package/src/hooks/useTrackPendingTimelineEdit.test.tsx +62 -0
  217. package/src/hooks/useTrackPendingTimelineEdit.ts +22 -0
  218. package/src/hostMount.test.tsx +222 -0
  219. package/src/index.ts +87 -0
  220. package/src/main.tsx +9 -3
  221. package/src/player/components/AudioWaveform.tsx +1 -1
  222. package/src/player/components/AutomationSelectionMenu.tsx +3 -3
  223. package/src/player/components/ClipContextMenu.tsx +3 -3
  224. package/src/player/components/CompositionThumbnail.test.ts +0 -3
  225. package/src/player/components/CompositionThumbnail.tsx +3 -4
  226. package/src/player/components/EditModal.tsx +1 -1
  227. package/src/player/components/ImageThumbnail.tsx +1 -1
  228. package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
  229. package/src/player/components/LayerDisclosureRow.tsx +2 -2
  230. package/src/player/components/PlayerControls.tsx +1 -1
  231. package/src/player/components/ShortcutsPanel.tsx +9 -9
  232. package/src/player/components/SpeedMenu.tsx +3 -5
  233. package/src/player/components/Timeline.tsx +3 -2
  234. package/src/player/components/TimelineCanvas.tsx +21 -14
  235. package/src/player/components/TimelineClip.tsx +0 -1
  236. package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
  237. package/src/player/components/TimelineEmptyState.tsx +5 -5
  238. package/src/player/components/TimelineFxButton.tsx +6 -6
  239. package/src/player/components/TimelineGroupHeader.tsx +5 -5
  240. package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
  241. package/src/player/components/TimelineGroupRow.tsx +1 -1
  242. package/src/player/components/TimelineLanes.test.tsx +32 -0
  243. package/src/player/components/TimelineLanes.tsx +2 -5
  244. package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
  245. package/src/player/components/TimelineRuler.tsx +1 -1
  246. package/src/player/components/TimelineShortcutHint.tsx +1 -1
  247. package/src/player/components/TimelineTrackHeader.test.tsx +26 -5
  248. package/src/player/components/TimelineTrackHeader.tsx +2 -2
  249. package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
  250. package/src/player/components/TimelineTypes.ts +2 -1
  251. package/src/player/components/TrackGapContextMenu.tsx +1 -1
  252. package/src/player/components/VideoThumbnail.tsx +4 -2
  253. package/src/player/components/VolumeControl.tsx +2 -2
  254. package/src/player/components/timelineCallbacks.ts +8 -8
  255. package/src/player/components/timelineClipDragPreview.ts +51 -27
  256. package/src/player/components/timelineClipDragTypes.ts +3 -0
  257. package/src/player/components/timelineDragDrop.test.tsx +61 -1
  258. package/src/player/components/timelineDragDrop.ts +57 -15
  259. package/src/player/components/timelineLaneProps.ts +3 -0
  260. package/src/player/components/timelineSnapping.test.ts +27 -0
  261. package/src/player/components/timelineSnapping.ts +14 -1
  262. package/src/player/components/timelineTheme.test.ts +4 -4
  263. package/src/player/components/timelineTheme.ts +30 -30
  264. package/src/player/components/trackHeaderLabelRows.tsx +4 -4
  265. package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
  266. package/src/player/components/useTimelineClipDrag.ts +30 -3
  267. package/src/player/lib/describeClips.ts +3 -54
  268. package/src/player/lib/playbackShortcuts.ts +2 -0
  269. package/src/player/lib/timelinePerformanceDiagnostics.test.ts +3 -2
  270. package/src/player/lib/timelinePerformanceDiagnostics.ts +1 -1
  271. package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
  272. package/src/player/store/previewIframeStore.test.tsx +29 -0
  273. package/src/player/store/previewIframeStore.ts +25 -0
  274. package/src/renderQueueExports.test.tsx +58 -0
  275. package/src/styles/classCandidates.test.ts +105 -0
  276. package/src/styles/classCandidates.ts +281 -0
  277. package/src/styles/hex-baseline.json +84 -0
  278. package/src/styles/hexRatchet.test.ts +201 -0
  279. package/src/styles/iconTokens.ts +20 -0
  280. package/src/styles/palette-reset.css +6 -0
  281. package/src/styles/studio.css +24 -21
  282. package/src/styles/styleSources.test.ts +20 -0
  283. package/src/styles/styleSources.ts +47 -0
  284. package/src/styles/tailwind-preset.shared.js +3 -0
  285. package/src/styles/theme.css +270 -0
  286. package/src/styles/theme.test.ts +183 -0
  287. package/src/styles/tokenGate.test.ts +109 -0
  288. package/src/utils/sourcePatcher.ts +1 -1
  289. package/src/utils/studioHelpers.test.ts +18 -0
  290. package/src/utils/studioHelpers.ts +11 -8
  291. package/src/utils/studioUiPreferences.test.ts +10 -16
  292. package/src/utils/studioUiPreferences.ts +7 -16
  293. package/src/utils/timelineDropTrackInsert.test.ts +134 -0
  294. package/src/utils/timelineDropTrackInsert.ts +83 -0
  295. package/src/utils/typingTarget.ts +4 -0
  296. package/dist/assets/index-n2ml4tUn.js +0 -442
  297. package/dist/assets/index-pO6p0H27.css +0 -1
  298. package/src/components/PanelTabButton.tsx +0 -31
  299. package/src/components/StudioLeftSidebar.tsx +0 -204
  300. package/src/components/nle/TimelineResizeDivider.tsx +0 -101
  301. package/src/components/sidebar/LeftSidebar.storage.test.ts +0 -28
  302. package/src/components/sidebar/LeftSidebar.tsx +0 -334
  303. package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
  304. package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
  305. package/src/components/storyboard/FramePoster.test.tsx +0 -61
  306. package/src/components/storyboard/FramePoster.tsx +0 -86
  307. package/src/components/storyboard/StoryboardDirection.tsx +0 -45
  308. package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
  309. package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
  310. package/src/components/storyboard/StoryboardGrid.tsx +0 -55
  311. package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
  312. package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
  313. package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
  314. package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
  315. package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
  316. package/src/components/storyboard/StoryboardView.tsx +0 -169
  317. package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
  318. package/src/components/storyboard/frameComments.test.ts +0 -95
  319. package/src/components/storyboard/frameComments.ts +0 -115
  320. package/src/components/storyboard/frameStatus.ts +0 -36
  321. package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
  322. package/src/components/storyboard/storyboardReviewStage.ts +0 -45
  323. package/src/components/storyboard/useFrameComments.ts +0 -98
  324. package/src/contexts/ViewModeContext.tsx +0 -142
  325. package/src/hooks/useInspectorSplitResize.ts +0 -51
  326. package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
  327. package/src/hooks/useProjectSignaturePoll.ts +0 -68
  328. package/src/hooks/useStoryboard.ts +0 -99
  329. package/src/utils/fitPanels.test.ts +0 -164
  330. package/src/utils/fitPanels.ts +0 -151
@@ -1,334 +0,0 @@
1
- import {
2
- memo,
3
- useState,
4
- useCallback,
5
- useImperativeHandle,
6
- useMemo,
7
- useRef,
8
- forwardRef,
9
- type ReactNode,
10
- } from "react";
11
- import { CompositionsTab } from "./CompositionsTab";
12
- import { AssetsTab } from "./AssetsTab";
13
- import { trackStudioEvent } from "../../utils/studioTelemetry";
14
- import { safeLocalStorage } from "../../utils/safeStorage";
15
- import { resolveMasterCompositionPath } from "../../utils/studioUrlState";
16
- import { BlocksTab, type BlockPreviewInfo } from "./BlocksTab";
17
- import { FileTree } from "../editor/FileTree";
18
- import { Tooltip } from "../ui";
19
-
20
- export type SidebarTab = "compositions" | "assets" | "code" | "blocks";
21
-
22
- export interface LeftSidebarHandle {
23
- selectTab: (tab: SidebarTab) => void;
24
- getTab: () => SidebarTab;
25
- }
26
-
27
- const STORAGE_KEY = "hf-studio-sidebar-tab";
28
-
29
- const SIDEBAR_TABS: Array<{ id: SidebarTab; label: string; tooltip: string }> = [
30
- { id: "code", label: "Code", tooltip: "Source code editor" },
31
- { id: "compositions", label: "Comps", tooltip: "Compositions and sub-compositions" },
32
- { id: "assets", label: "Assets", tooltip: "Videos, images, audio, fonts" },
33
- { id: "blocks", label: "Catalog", tooltip: "Browse blocks and components" },
34
- ];
35
-
36
- // Both the `localStorage` reference and `getItem` itself can throw when the
37
- // browsing context is partitioned or site data is blocked — the same case
38
- // telemetry/config.ts documents. This runs as a `useState` initializer, so an
39
- // unguarded throw here takes the whole editor to the crash boundary rather
40
- // than losing one remembered tab.
41
- export function getPersistedTab(): SidebarTab {
42
- let stored: string | null = null;
43
- try {
44
- stored = safeLocalStorage()?.getItem(STORAGE_KEY) ?? null;
45
- } catch {
46
- /* storage unavailable — fall back to the default tab */
47
- }
48
- if (stored === "assets") return "assets";
49
- if (stored === "code") return "code";
50
- if (stored === "blocks") return "blocks";
51
- return "compositions";
52
- }
53
-
54
- interface LeftSidebarProps {
55
- width?: number;
56
- projectId: string;
57
- compositions: string[];
58
- assets: string[];
59
- activeComposition: string | null;
60
- onSelectComposition: (comp: string) => void;
61
- onImportFiles?: (files: FileList, dir?: string) => void | Promise<void>;
62
- fileTree?: string[];
63
- editingFile?: { path: string; content: string | null } | null;
64
- onSelectFile?: (path: string) => void;
65
- onCreateFile?: (path: string) => void;
66
- onCreateFolder?: (path: string) => void;
67
- onDeleteFile?: (path: string) => void;
68
- onRenameFile?: (oldPath: string, newPath: string) => void;
69
- onDuplicateFile?: (path: string) => void;
70
- onMoveFile?: (oldPath: string, newPath: string) => void;
71
- codeChildren?: ReactNode;
72
- onRenderComposition?: (comp: string) => void;
73
- isRendering?: boolean;
74
- onLint?: () => void;
75
- linting?: boolean;
76
- lintFindingCount?: number;
77
- lintFindingsByFile?: Map<string, { count: number; messages: string[] }>;
78
- onToggleCollapse?: () => void;
79
- onAddBlock?: (blockName: string) => void | Promise<void>;
80
- onPreviewBlock?: (preview: BlockPreviewInfo | null) => void;
81
- takeoverContent?: ReactNode;
82
- onAddAssetToTimeline?: (path: string) => void;
83
- onAddCompositionToTimeline?: (path: string) => void;
84
- }
85
-
86
- export const LeftSidebar = memo(
87
- forwardRef<LeftSidebarHandle, LeftSidebarProps>(function LeftSidebar(
88
- {
89
- width = 240,
90
- projectId,
91
- compositions,
92
- assets,
93
- activeComposition,
94
- onSelectComposition,
95
- onImportFiles,
96
- fileTree: fileProp,
97
- editingFile,
98
- onSelectFile,
99
- onCreateFile,
100
- onCreateFolder,
101
- onDeleteFile,
102
- onRenameFile,
103
- onDuplicateFile,
104
- onMoveFile,
105
- codeChildren,
106
- onRenderComposition,
107
- isRendering,
108
- onLint,
109
- linting,
110
- lintFindingCount,
111
- lintFindingsByFile,
112
- onToggleCollapse,
113
- onAddBlock,
114
- onPreviewBlock,
115
- takeoverContent,
116
- onAddAssetToTimeline,
117
- onAddCompositionToTimeline,
118
- },
119
- ref,
120
- ) {
121
- const [tab, setTab] = useState<SidebarTab>(getPersistedTab);
122
- const tabRef = useRef(tab);
123
- tabRef.current = tab;
124
- const tablistRef = useRef<HTMLDivElement>(null);
125
- const masterCompositionPath = useMemo(
126
- () => resolveMasterCompositionPath(compositions),
127
- [compositions],
128
- );
129
-
130
- const selectTab = useCallback((t: SidebarTab) => {
131
- setTab(t);
132
- try {
133
- safeLocalStorage()?.setItem(STORAGE_KEY, t);
134
- } catch {
135
- /* storage unavailable — the tab just won't be remembered */
136
- }
137
- trackStudioEvent("tab_switch", { panel: "left_sidebar", tab: t });
138
- }, []);
139
-
140
- const getTab = useCallback(() => tabRef.current, []);
141
-
142
- useImperativeHandle(ref, () => ({ selectTab, getTab }), [selectTab, getTab]);
143
-
144
- // APG tabs pattern: Left/Right move focus AND selection between tabs.
145
- const handleTablistKeyDown = useCallback(
146
- (e: React.KeyboardEvent) => {
147
- if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
148
- e.preventDefault();
149
- const ids = SIDEBAR_TABS.map((t) => t.id);
150
- const idx = ids.indexOf(tabRef.current);
151
- const next = ids[(idx + (e.key === "ArrowRight" ? 1 : -1) + ids.length) % ids.length];
152
- selectTab(next);
153
- tablistRef.current?.querySelector<HTMLButtonElement>(`[data-tab-id="${next}"]`)?.focus();
154
- },
155
- [selectTab],
156
- );
157
-
158
- return (
159
- <div
160
- className="flex flex-col h-full overflow-hidden rounded-lg border border-neutral-800/50 bg-neutral-950"
161
- style={{ width }}
162
- >
163
- {takeoverContent ? (
164
- <div className="flex min-h-0 flex-1">{takeoverContent}</div>
165
- ) : (
166
- <>
167
- {/* Tabs — Code first */}
168
- <div className="border-b border-neutral-800/50 px-3 py-3 flex-shrink-0">
169
- <div className="flex items-center gap-2">
170
- <div
171
- ref={tablistRef}
172
- role="tablist"
173
- aria-label="Sidebar panels"
174
- onKeyDown={handleTablistKeyDown}
175
- className="grid min-w-0 flex-1 gap-0.5 rounded-[18px] bg-neutral-900 p-1 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]"
176
- style={{ gridTemplateColumns: "1fr 1fr 1fr 1fr" }}
177
- >
178
- {SIDEBAR_TABS.map((t) => (
179
- <Tooltip key={t.id} label={t.tooltip} side="bottom">
180
- <button
181
- type="button"
182
- role="tab"
183
- data-tab-id={t.id}
184
- aria-selected={tab === t.id}
185
- aria-controls={`sidebar-panel-${t.id}`}
186
- tabIndex={tab === t.id ? 0 : -1}
187
- onClick={() => selectTab(t.id)}
188
- className={`rounded-[14px] px-1.5 py-2 text-[10px] font-semibold truncate transition-all active:scale-[0.97] ${
189
- tab === t.id
190
- ? "bg-neutral-800 text-white"
191
- : "text-neutral-500 hover:text-neutral-200"
192
- }`}
193
- >
194
- {t.label}
195
- </button>
196
- </Tooltip>
197
- ))}
198
- </div>
199
- {onToggleCollapse && (
200
- <button
201
- type="button"
202
- onClick={onToggleCollapse}
203
- className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-transparent text-neutral-500 transition-colors hover:border-neutral-800 hover:bg-neutral-900 hover:text-neutral-300"
204
- title="Hide sidebar"
205
- aria-label="Hide sidebar"
206
- >
207
- <svg
208
- width="14"
209
- height="14"
210
- viewBox="0 0 24 24"
211
- fill="none"
212
- stroke="currentColor"
213
- strokeWidth="1.5"
214
- strokeLinecap="round"
215
- strokeLinejoin="round"
216
- aria-hidden="true"
217
- >
218
- <path d="m14 7-5 5 5 5" />
219
- <path d="M19 4v16" />
220
- </svg>
221
- </button>
222
- )}
223
- </div>
224
- </div>
225
-
226
- {/* Tab content */}
227
- {tab === "compositions" && (
228
- <div
229
- id="sidebar-panel-compositions"
230
- role="tabpanel"
231
- className="flex flex-col flex-1 min-h-0"
232
- >
233
- <CompositionsTab
234
- projectId={projectId}
235
- compositions={compositions}
236
- activeComposition={activeComposition}
237
- masterCompositionPath={masterCompositionPath}
238
- onSelect={onSelectComposition}
239
- onAddToTimeline={onAddCompositionToTimeline}
240
- onRenderComposition={onRenderComposition}
241
- isRendering={isRendering}
242
- lintFindingsByFile={lintFindingsByFile}
243
- />
244
- </div>
245
- )}
246
- {tab === "assets" && (
247
- <div
248
- id="sidebar-panel-assets"
249
- role="tabpanel"
250
- className="flex flex-col flex-1 min-h-0"
251
- >
252
- <AssetsTab
253
- projectId={projectId}
254
- assets={assets}
255
- onImport={onImportFiles}
256
- onDelete={onDeleteFile}
257
- onRename={onRenameFile}
258
- onAddAssetToTimeline={onAddAssetToTimeline}
259
- />
260
- </div>
261
- )}
262
- {tab === "code" && (
263
- <div id="sidebar-panel-code" role="tabpanel" className="flex flex-1 min-h-0">
264
- {(fileProp?.length ?? 0) > 0 && (
265
- <div className="w-[160px] flex-shrink-0 border-r border-neutral-800 overflow-y-auto">
266
- <FileTree
267
- files={fileProp ?? []}
268
- activeFile={editingFile?.path ?? null}
269
- onSelectFile={onSelectFile ?? (() => {})}
270
- onCreateFile={onCreateFile}
271
- onCreateFolder={onCreateFolder}
272
- onDeleteFile={onDeleteFile}
273
- onRenameFile={onRenameFile}
274
- onDuplicateFile={onDuplicateFile}
275
- onMoveFile={onMoveFile}
276
- onImportFiles={onImportFiles}
277
- lintFindingsByFile={lintFindingsByFile}
278
- />
279
- </div>
280
- )}
281
- <div className="flex-1 overflow-hidden min-w-0">
282
- {codeChildren ?? (
283
- <div className="flex items-center justify-center h-full text-neutral-600 text-sm">
284
- Select a file to edit
285
- </div>
286
- )}
287
- </div>
288
- </div>
289
- )}
290
-
291
- {tab === "blocks" && (
292
- <div
293
- id="sidebar-panel-blocks"
294
- role="tabpanel"
295
- className="flex flex-col flex-1 min-h-0"
296
- >
297
- <BlocksTab onAddBlock={onAddBlock} onPreviewBlock={onPreviewBlock} />
298
- </div>
299
- )}
300
-
301
- {/* Lint button pinned at the bottom */}
302
- {onLint && (
303
- <div className="border-t border-neutral-800 p-2 flex-shrink-0">
304
- <button
305
- onClick={onLint}
306
- disabled={linting}
307
- className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 rounded-md text-[11px] font-medium text-neutral-500 enabled:hover:text-amber-300 enabled:hover:bg-neutral-800 enabled:active:scale-[0.98] transition-colors disabled:opacity-40"
308
- >
309
- <svg
310
- width="12"
311
- height="12"
312
- viewBox="0 0 24 24"
313
- fill="none"
314
- stroke="currentColor"
315
- strokeWidth="2"
316
- >
317
- <path d="M9 11l3 3L22 4" />
318
- <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" />
319
- </svg>
320
- {linting ? "Linting…" : "Lint"}
321
- {!linting && lintFindingCount != null && lintFindingCount > 0 && (
322
- <span className="ml-1 min-w-[16px] rounded-full bg-amber-500/20 px-1 text-[9px] font-bold text-amber-400">
323
- {lintFindingCount}
324
- </span>
325
- )}
326
- </button>
327
- </div>
328
- )}
329
- </>
330
- )}
331
- </div>
332
- );
333
- }),
334
- );
@@ -1,47 +0,0 @@
1
- // @vitest-environment happy-dom
2
-
3
- import React, { act } from "react";
4
- import { createRoot } from "react-dom/client";
5
- import { afterEach, describe, expect, it, vi } from "vitest";
6
- import { AgentChatMessageButton } from "./AgentChatMessageButton";
7
-
8
- (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
9
-
10
- afterEach(() => {
11
- document.body.innerHTML = "";
12
- vi.useRealTimers();
13
- vi.restoreAllMocks();
14
- });
15
-
16
- describe("AgentChatMessageButton", () => {
17
- it("reports a successful copy and resets to an explicit re-copy action", async () => {
18
- vi.useFakeTimers();
19
- const writeText = vi.fn().mockResolvedValue(undefined);
20
- Object.defineProperty(navigator, "clipboard", {
21
- configurable: true,
22
- value: { writeText },
23
- });
24
- const onCopied = vi.fn();
25
- const host = document.createElement("div");
26
- document.body.append(host);
27
- const root = createRoot(host);
28
- act(() => {
29
- root.render(<AgentChatMessageButton message="handoff" onCopied={onCopied} />);
30
- });
31
-
32
- const button = host.querySelector("button");
33
- if (!button) throw new Error("copy button not rendered");
34
- await act(async () => {
35
- button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
36
- await Promise.resolve();
37
- });
38
-
39
- expect(writeText).toHaveBeenCalledWith("handoff");
40
- expect(onCopied).toHaveBeenCalledOnce();
41
- expect(button.textContent).toBe("Copied — paste in your agent chat");
42
-
43
- act(() => vi.advanceTimersByTime(3000));
44
- expect(button.textContent).toBe("Copy again");
45
- act(() => root.unmount());
46
- });
47
- });
@@ -1,45 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { Button } from "../ui/Button";
3
-
4
- export const APPLY_STORYBOARD_FEEDBACK_MESSAGE =
5
- "Read the storyboard feedback I saved in .hyperframes/frame-comments.json and revise the frames.";
6
-
7
- export function AgentChatMessageButton({
8
- message,
9
- label = "Copy prompt for agent",
10
- onCopied,
11
- }: {
12
- message: string;
13
- label?: string;
14
- onCopied?: () => void;
15
- }) {
16
- const [copyState, setCopyState] = useState<"idle" | "copied" | "again" | "failed">("idle");
17
-
18
- useEffect(() => {
19
- if (copyState !== "copied") return;
20
- const timeout = window.setTimeout(() => setCopyState("again"), 3000);
21
- return () => window.clearTimeout(timeout);
22
- }, [copyState]);
23
-
24
- const copyMessage = async () => {
25
- try {
26
- await navigator.clipboard.writeText(message);
27
- setCopyState("copied");
28
- onCopied?.();
29
- } catch {
30
- setCopyState("failed");
31
- }
32
- };
33
-
34
- return (
35
- <Button size="sm" variant="secondary" onClick={() => void copyMessage()}>
36
- {copyState === "copied"
37
- ? "Copied — paste in your agent chat"
38
- : copyState === "again"
39
- ? "Copy again"
40
- : copyState === "failed"
41
- ? "Copy failed"
42
- : label}
43
- </Button>
44
- );
45
- }
@@ -1,61 +0,0 @@
1
- // @vitest-environment happy-dom
2
-
3
- import React, { act } from "react";
4
- import { createRoot, type Root } from "react-dom/client";
5
- import { afterEach, describe, expect, it } from "vitest";
6
- import { FramePoster } from "./FramePoster";
7
-
8
- (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
9
-
10
- const roots: Root[] = [];
11
-
12
- afterEach(() => {
13
- act(() => roots.splice(0).forEach((root) => root.unmount()));
14
- document.body.replaceChildren();
15
- });
16
-
17
- // A fresh host per render: several cases compare two surfaces side by side.
18
- function renderPoster(surface?: "tile" | "hero"): HTMLImageElement {
19
- const host = document.createElement("div");
20
- document.body.appendChild(host);
21
- const root = createRoot(host);
22
- roots.push(root);
23
- act(() => {
24
- root.render(
25
- <FramePoster
26
- projectId="demo"
27
- src="frames/01-hero.html"
28
- seconds={3}
29
- title="hero"
30
- surface={surface}
31
- />,
32
- );
33
- });
34
- const img = host.querySelector("img");
35
- if (!img) throw new Error("poster did not render an <img>");
36
- return img;
37
- }
38
-
39
- describe("FramePoster", () => {
40
- // Regression: the contact sheet stretched a 240x135 capture across a wide,
41
- // high-density card, making body copy, thin lines, and sprite details blurry.
42
- it.each([
43
- [undefined, "storyboard"],
44
- ["tile", "storyboard"],
45
- ["hero", "source"],
46
- ] as const)("captures the %s surface at %s density", (surface, output) => {
47
- const url = new URL(renderPoster(surface).src);
48
-
49
- expect(url.searchParams.get("output")).toBe(output);
50
- expect(url.pathname).toBe("/api/projects/demo/thumbnail/frames/01-hero.html");
51
- });
52
-
53
- it("defaults to the tile surface", () => {
54
- expect(renderPoster().className).toBe(renderPoster("tile").className);
55
- });
56
-
57
- it("letterboxes only the hero, so a tile still fills its cell", () => {
58
- expect(renderPoster("hero").className).toContain("object-contain");
59
- expect(renderPoster("tile").className).toContain("object-cover");
60
- });
61
- });
@@ -1,86 +0,0 @@
1
- import { buildProjectApiPath } from "../../utils/projectRouting";
2
- import { useEffect, useState } from "react";
3
- import { buildCompositionThumbnailUrl } from "../../player/components/CompositionThumbnail";
4
-
5
- export interface FramePosterProps {
6
- projectId: string;
7
- /** Project-relative path to the frame's HTML sub-composition. */
8
- src: string;
9
- /** Time (seconds) to seek to for the poster. */
10
- seconds: number;
11
- title: string;
12
- /**
13
- * Where this poster is rendered. A contact-sheet tile is ~300px wide and there
14
- * are many of them; the focus hero is up to 900px wide and there is exactly
15
- * one. Tiles use a bounded high-density capture; the hero uses source density.
16
- * The surface also decides whether the result fills or letterboxes its cell.
17
- */
18
- surface?: "tile" | "hero";
19
- /**
20
- * Project content signature to key the poster URL on. The thumbnail route
21
- * regenerates when the frame's source changes, but the browser only refetches
22
- * when the URL does — so bake the signature in to pick up fresh posters after
23
- * a background board refresh.
24
- */
25
- posterVersion?: string;
26
- }
27
-
28
- /**
29
- * Server-rendered poster for a frame. The thumbnail route seeks the composition
30
- * by time (at its real fps) and caches the result, so there's no live iframe,
31
- * no postMessage seek, and no client-side fps assumption. Shared by the
32
- * contact-sheet tile and the frame-focus view.
33
- */
34
- export function FramePoster({
35
- projectId,
36
- src,
37
- seconds,
38
- title,
39
- surface = "tile",
40
- posterVersion,
41
- }: FramePosterProps) {
42
- const [failed, setFailed] = useState(false);
43
- // The <img> is reused (no key) when a tile/hero swaps to a different frame, so a
44
- // prior load error would stick. Reset when the poster target changes — including
45
- // a new posterVersion, so a frame that failed mid-write retries once it settles.
46
- useEffect(() => setFailed(false), [src, seconds, posterVersion, surface]);
47
- if (failed) {
48
- return (
49
- <div className="flex h-full w-full items-center justify-center text-[11px] text-neutral-600">
50
- Preview unavailable
51
- </div>
52
- );
53
- }
54
- let url = buildCompositionThumbnailUrl({
55
- previewUrl: buildProjectApiPath(projectId, `/preview/comp/${src}`),
56
- seekTime: seconds,
57
- duration: 0,
58
- origin: window.location.origin,
59
- // The normal 240x135 preview is unreadable in the contact sheet, while source
60
- // density is unbounded across all tiles. Give tiles a capped review density
61
- // and reserve true source output for the single focus hero.
62
- output: surface === "hero" ? "source" : "storyboard",
63
- });
64
- if (posterVersion) {
65
- const withVersion = new URL(url, window.location.origin);
66
- withVersion.searchParams.set("sig", posterVersion);
67
- url = withVersion.toString();
68
- }
69
- return (
70
- <img
71
- src={url}
72
- alt={title}
73
- draggable={false}
74
- loading="lazy"
75
- onError={() => setFailed(true)}
76
- className={`h-full w-full ${surface === "hero" ? "object-contain" : "object-cover"}`}
77
- />
78
- );
79
- }
80
-
81
- /** Time (seconds) to show a frame at — past the intro so the key moment is visible. */
82
- export function posterTime(frame: { poster?: number; durationSeconds?: number }): number {
83
- if (frame.poster != null) return frame.poster;
84
- if (frame.durationSeconds != null) return frame.durationSeconds * 0.66;
85
- return 1.5;
86
- }
@@ -1,45 +0,0 @@
1
- import type { StoryboardGlobals } from "@hyperframes/core/storyboard";
2
-
3
- export interface StoryboardDirectionProps {
4
- globals: StoryboardGlobals;
5
- frameCount: number;
6
- }
7
-
8
- /**
9
- * Global direction header: the message/thesis plus arc, audience, and format.
10
- * This is the storyboard's "north star" pulled from the manifest frontmatter.
11
- */
12
- export function StoryboardDirection({ globals, frameCount }: StoryboardDirectionProps) {
13
- const meta = [
14
- { label: "Arc", value: globals.arc },
15
- { label: "Audience", value: globals.audience },
16
- { label: "Voice", value: globals.extra.voice },
17
- { label: "Format", value: globals.format },
18
- { label: "Frames", value: String(frameCount) },
19
- ].filter((item): item is { label: string; value: string } => Boolean(item.value));
20
-
21
- return (
22
- <header className="border-b border-neutral-800 pb-5">
23
- <div className="text-[11px] font-medium uppercase tracking-wider text-neutral-500">
24
- Storyboard
25
- </div>
26
- {globals.message ? (
27
- <h1 className="mt-1 text-2xl font-semibold leading-tight text-neutral-100">
28
- {globals.message}
29
- </h1>
30
- ) : (
31
- <h1 className="mt-1 text-2xl font-semibold leading-tight text-neutral-400">
32
- Untitled storyboard
33
- </h1>
34
- )}
35
- <dl className="mt-4 flex flex-wrap gap-x-8 gap-y-2">
36
- {meta.map((item) => (
37
- <div key={item.label} className="flex items-baseline gap-2">
38
- <dt className="text-[11px] uppercase tracking-wider text-neutral-500">{item.label}</dt>
39
- <dd className="text-sm text-neutral-300">{item.value}</dd>
40
- </div>
41
- ))}
42
- </dl>
43
- </header>
44
- );
45
- }