@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
@@ -1,307 +0,0 @@
1
- import { useEffect, useMemo, useState } from "react";
2
- import type { StoryboardResponse } from "../../hooks/useStoryboard";
3
- import { Button } from "../ui/Button";
4
- import { StoryboardDirection } from "./StoryboardDirection";
5
- import { StoryboardGrid } from "./StoryboardGrid";
6
- import { StoryboardScriptPanel } from "./StoryboardScriptPanel";
7
- import { StoryboardSourceEditor, type SourceFile } from "./StoryboardSourceEditor";
8
- import { StoryboardFrameFocus } from "./StoryboardFrameFocus";
9
- import { StoryboardReviewGuide } from "./StoryboardReviewGuide";
10
- import {
11
- AgentChatMessageButton,
12
- APPLY_STORYBOARD_FEEDBACK_MESSAGE,
13
- } from "./AgentChatMessageButton";
14
- import { useFrameComments, type CommentsSubmitState } from "./useFrameComments";
15
-
16
- type SubView = "board" | "source";
17
-
18
- export interface StoryboardLoadedProps {
19
- projectId: string;
20
- data: StoryboardResponse;
21
- /** Re-fetch the manifest after a source edit is saved. */
22
- reload: () => void;
23
- /** Select a composition in the timeline (used by "Open in Preview"). */
24
- onSelectComposition: (path: string) => void;
25
- }
26
-
27
- function clampIndex(index: number, count: number): number {
28
- return Math.max(1, Math.min(count, index));
29
- }
30
-
31
- /** A storyboard that exists on disk: Board (contact sheet) ↔ Source ↔ frame focus. */
32
- // fallow-ignore-next-line complexity
33
- export function StoryboardLoaded({
34
- projectId,
35
- data,
36
- reload,
37
- onSelectComposition,
38
- }: StoryboardLoadedProps) {
39
- const [subView, setSubView] = useState<SubView>("board");
40
- const [sourceDirty, setSourceDirty] = useState(false);
41
- const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
42
- const [feedbackMessageCopied, setFeedbackMessageCopied] = useState(false);
43
- const comments = useFrameComments(data.frames);
44
- // When the board refreshes off a project change (agent revised frames), the
45
- // agent has likely consumed the comments file too — re-check so the pending
46
- // banner clears the moment revisions land, not on the next window focus.
47
- const { refreshPending } = comments;
48
- useEffect(() => {
49
- void refreshPending();
50
- }, [data.signature, refreshPending]);
51
- useEffect(() => {
52
- if (comments.draftCount > 0) setFeedbackMessageCopied(false);
53
- }, [comments.draftCount]);
54
-
55
- const saveFeedbackAndCopyMessage = async () => {
56
- const saved = await comments.submit();
57
- if (!saved) return;
58
- try {
59
- await navigator.clipboard.writeText(APPLY_STORYBOARD_FEEDBACK_MESSAGE);
60
- setFeedbackMessageCopied(true);
61
- } catch {
62
- setFeedbackMessageCopied(false);
63
- }
64
- };
65
- const sourceFiles = useMemo<SourceFile[]>(() => {
66
- const files: SourceFile[] = [{ path: data.path, label: data.path }];
67
- if (data.script?.exists) files.push({ path: data.script.path, label: data.script.path });
68
- return files;
69
- // Depend on the stable fields, not the `data.script` object — every reload()
70
- // produces a fresh object and would needlessly re-create this array.
71
- }, [data.path, data.script?.path, data.script?.exists]);
72
-
73
- // Leaving the source editor drops its in-memory buffer; confirm when it's dirty.
74
- // fallow-ignore-next-line complexity
75
- const changeSubView = (next: SubView) => {
76
- if (next === subView) return;
77
- if (
78
- subView === "source" &&
79
- sourceDirty &&
80
- !window.confirm("Discard unsaved markdown changes?")
81
- ) {
82
- return;
83
- }
84
- setSubView(next);
85
- };
86
-
87
- const focusedFrame =
88
- focusedIndex != null ? (data.frames.find((f) => f.index === focusedIndex) ?? null) : null;
89
-
90
- if (focusedFrame) {
91
- return (
92
- <StoryboardFrameFocus
93
- key={focusedFrame.index}
94
- projectId={projectId}
95
- storyboardPath={data.path}
96
- frame={focusedFrame}
97
- frameCount={data.frames.length}
98
- onBack={() => setFocusedIndex(null)}
99
- onNavigate={(delta) =>
100
- setFocusedIndex(clampIndex(focusedFrame.index + delta, data.frames.length))
101
- }
102
- onSaved={reload}
103
- onSelectComposition={onSelectComposition}
104
- scriptExists={Boolean(data.script?.exists)}
105
- commentDraft={comments.drafts[focusedFrame.index] ?? ""}
106
- onCommentDraftChange={(text) => comments.setDraft(focusedFrame.index, text)}
107
- pendingComment={
108
- comments.pending?.find((entry) => entry.frame === focusedFrame.index)?.text ?? null
109
- }
110
- pendingCommentCount={comments.pending?.length ?? 0}
111
- commentDraftCount={comments.draftCount}
112
- commentsSubmitState={comments.submitState}
113
- commentsSubmitError={comments.submitError}
114
- feedbackMessageCopied={feedbackMessageCopied}
115
- onFeedbackMessageCopied={() => setFeedbackMessageCopied(true)}
116
- onSaveFeedback={() => void saveFeedbackAndCopyMessage()}
117
- posterVersion={data.signature}
118
- />
119
- );
120
- }
121
-
122
- return (
123
- <div className="flex w-full max-w-[100vw] flex-1 min-h-0 min-w-0 flex-col overflow-hidden bg-neutral-950 text-neutral-200">
124
- <div className="flex flex-wrap items-center gap-3 border-b border-neutral-800 px-4 py-2">
125
- <SubViewToggle value={subView} onChange={changeSubView} />
126
- {subView === "board" && (
127
- <CommentsSubmitBar
128
- draftCount={comments.draftCount}
129
- pendingCount={comments.pending?.length ?? 0}
130
- submitState={comments.submitState}
131
- submitError={comments.submitError}
132
- messageCopied={feedbackMessageCopied}
133
- onSave={() => void saveFeedbackAndCopyMessage()}
134
- onMessageCopied={() => setFeedbackMessageCopied(true)}
135
- />
136
- )}
137
- </div>
138
- {subView === "board" ? (
139
- <div className="flex-1 min-h-0 overflow-auto">
140
- <div className="mx-auto max-w-[1400px] px-4 py-5 sm:px-8 sm:py-8">
141
- <StoryboardDirection globals={data.globals} frameCount={data.frames.length} />
142
- <StoryboardReviewGuide
143
- frames={data.frames}
144
- draftCount={comments.draftCount}
145
- pendingCount={comments.pending?.length ?? 0}
146
- onFeedbackMessageCopied={() => setFeedbackMessageCopied(true)}
147
- />
148
- <StoryboardWarnings
149
- warnings={data.warnings}
150
- onOpenSource={() => changeSubView("source")}
151
- />
152
- <StoryboardGrid
153
- projectId={projectId}
154
- frames={data.frames}
155
- onOpenFrame={setFocusedIndex}
156
- commentDrafts={comments.drafts}
157
- onCommentDraftChange={comments.setDraft}
158
- pendingComments={comments.pending}
159
- posterVersion={data.signature}
160
- />
161
- {data.script && <StoryboardScriptPanel script={data.script} />}
162
- </div>
163
- </div>
164
- ) : (
165
- <StoryboardSourceEditor
166
- files={sourceFiles}
167
- onSaved={reload}
168
- onDirtyChange={setSourceDirty}
169
- />
170
- )}
171
- </div>
172
- );
173
- }
174
-
175
- /** Batch-submit the per-frame comment drafts to `.hyperframes/frame-comments.json`. */
176
- function CommentsSubmitBar({
177
- draftCount,
178
- pendingCount,
179
- submitState,
180
- submitError,
181
- messageCopied,
182
- onSave,
183
- onMessageCopied,
184
- }: {
185
- draftCount: number;
186
- pendingCount: number;
187
- submitState: CommentsSubmitState;
188
- submitError: string | null;
189
- messageCopied: boolean;
190
- onSave: () => void;
191
- onMessageCopied: () => void;
192
- }) {
193
- return (
194
- <div className="ml-auto flex min-w-0 flex-1 flex-wrap items-center justify-end gap-2 sm:flex-none">
195
- {pendingCount > 0 && (
196
- <>
197
- <span className="text-xs text-sky-300">
198
- {messageCopied
199
- ? "Feedback saved · Message copied — paste it in your terminal or IDE agent chat."
200
- : "Feedback saved · Agent not notified."}
201
- </span>
202
- <AgentChatMessageButton
203
- message={APPLY_STORYBOARD_FEEDBACK_MESSAGE}
204
- label={messageCopied ? "Copy again" : "Copy prompt for agent"}
205
- onCopied={onMessageCopied}
206
- />
207
- </>
208
- )}
209
- {pendingCount === 0 && draftCount === 0 && (
210
- <span className="text-xs text-neutral-500">Add frame comments to request changes.</span>
211
- )}
212
- {submitError && (
213
- <span className="max-w-64 truncate text-xs text-red-400" title={submitError}>
214
- Couldn’t submit: {submitError}
215
- </span>
216
- )}
217
- {draftCount > 0 && (
218
- <Button
219
- variant="primary"
220
- size="sm"
221
- loading={submitState === "saving"}
222
- disabled={submitState === "saving"}
223
- onClick={onSave}
224
- >
225
- Save &amp; copy message ({draftCount})
226
- </Button>
227
- )}
228
- </div>
229
- );
230
- }
231
-
232
- function StoryboardWarnings({
233
- warnings,
234
- onOpenSource,
235
- }: {
236
- warnings: StoryboardResponse["warnings"];
237
- onOpenSource: () => void;
238
- }) {
239
- if (warnings.length === 0) return null;
240
- return (
241
- <details className="mt-3 rounded-lg border border-amber-900/60 bg-amber-950/20 px-4 py-2 text-xs text-amber-200">
242
- <summary className="cursor-pointer font-medium">
243
- {warnings.length} storyboard warning{warnings.length === 1 ? "" : "s"}
244
- </summary>
245
- <ul className="mt-2 space-y-1 text-amber-200/80">
246
- {warnings.map((warning, index) => (
247
- <li key={`${warning.line ?? "unknown"}-${index}`}>
248
- {warning.line ? `Line ${warning.line}: ` : ""}
249
- {warning.message}
250
- </li>
251
- ))}
252
- </ul>
253
- <button
254
- type="button"
255
- onClick={onOpenSource}
256
- className="mt-2 rounded text-amber-100 underline underline-offset-2 hover:text-white"
257
- >
258
- Open source to fix
259
- </button>
260
- </details>
261
- );
262
- }
263
-
264
- const SUB_VIEWS: Array<{ value: SubView; label: string }> = [
265
- { value: "board", label: "Board" },
266
- { value: "source", label: "Source" },
267
- ];
268
-
269
- function SubViewToggle({ value, onChange }: { value: SubView; onChange: (next: SubView) => void }) {
270
- // Complete tabs contract: roving tabIndex + arrow-key navigation (the roles
271
- // alone promised keyboard behavior the buttons didn't have).
272
- const handleKeyDown = (e: React.KeyboardEvent) => {
273
- if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
274
- e.preventDefault();
275
- const currentIndex = SUB_VIEWS.findIndex((v) => v.value === value);
276
- const delta = e.key === "ArrowRight" ? 1 : -1;
277
- const next = SUB_VIEWS[(currentIndex + delta + SUB_VIEWS.length) % SUB_VIEWS.length];
278
- if (next) onChange(next.value);
279
- };
280
-
281
- return (
282
- <div
283
- className="flex items-center gap-0.5 rounded-md bg-neutral-900 p-0.5"
284
- role="tablist"
285
- aria-label="Storyboard view"
286
- onKeyDown={handleKeyDown}
287
- >
288
- {SUB_VIEWS.map((option) => (
289
- <button
290
- key={option.value}
291
- type="button"
292
- role="tab"
293
- aria-selected={value === option.value}
294
- tabIndex={value === option.value ? 0 : -1}
295
- onClick={() => onChange(option.value)}
296
- className={`rounded px-3 py-1 text-xs font-medium transition-colors active:scale-[0.98] outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-studio-accent ${
297
- value === option.value
298
- ? "bg-neutral-700 text-neutral-100"
299
- : "text-neutral-400 hover:text-neutral-200"
300
- }`}
301
- >
302
- {option.label}
303
- </button>
304
- ))}
305
- </div>
306
- );
307
- }
@@ -1,300 +0,0 @@
1
- import type { StoryboardFrameView } from "../../hooks/useStoryboard";
2
- import {
3
- AgentChatMessageButton,
4
- APPLY_STORYBOARD_FEEDBACK_MESSAGE,
5
- } from "./AgentChatMessageButton";
6
- import { FRAME_STATUS_META, FRAME_STATUS_ORDER } from "./frameStatus";
7
- import {
8
- deriveStoryboardHandoffStep,
9
- deriveStoryboardReviewStage,
10
- type StoryboardHandoffStep,
11
- type StoryboardReviewStage,
12
- } from "./storyboardReviewStage";
13
-
14
- const GUIDE_COPY: Record<StoryboardReviewStage, { eyebrow: string; title: string; body: string }> =
15
- {
16
- empty: {
17
- eyebrow: "Waiting for a plan",
18
- title: "The storyboard has no frames yet",
19
- body: "Ask your agent to draft the story plan. Frames will appear here automatically.",
20
- },
21
- "plan-review": {
22
- eyebrow: "Ready for review",
23
- title: "Review the story plan",
24
- body: "Check the sequence, scene direction, and voiceover before visual work begins. Leave frame comments, save them, then reply in your terminal or IDE agent chat.",
25
- },
26
- "sketch-in-progress": {
27
- eyebrow: "Build in progress",
28
- title: "Visual sketches are in progress",
29
- body: "New posters appear automatically as your agent builds them. You can comment now; wait until no frames remain in Outline before approving the layouts.",
30
- },
31
- "sketch-review": {
32
- eyebrow: "Ready for review",
33
- title: "Review the visual direction",
34
- body: "Check composition, hierarchy, and copy. Save frame comments, then reply in your terminal or IDE agent chat.",
35
- },
36
- "animation-in-progress": {
37
- eyebrow: "Build in progress",
38
- title: "Animation is in progress",
39
- body: "The board refreshes as frames advance. Review completed frames now; the final review is ready when every frame is Animated.",
40
- },
41
- "final-review": {
42
- eyebrow: "Ready for review",
43
- title: "Review motion and timing",
44
- body: "Every frame is animated. Open a frame in Preview to review it in the timeline, or leave frame comments for another revision.",
45
- },
46
- };
47
-
48
- const REVIEW_ACTION_COPY: Record<
49
- StoryboardReviewStage,
50
- { body: string; approvalMessage?: string }
51
- > = {
52
- empty: { body: "Add comments as previews arrive." },
53
- "plan-review": {
54
- body: "Add comments where you want changes. If everything looks right, approve this pass in agent chat.",
55
- approvalMessage: "Approve this storyboard plan and continue to visual sketches.",
56
- },
57
- "sketch-in-progress": {
58
- body: "Add comments as previews arrive. You’ll be prompted to approve when this pass is ready.",
59
- },
60
- "sketch-review": {
61
- body: "Add comments where you want changes. If everything looks right, approve this pass in agent chat.",
62
- approvalMessage: "Approve these storyboard sketches and continue to animation.",
63
- },
64
- "animation-in-progress": {
65
- body: "Add comments as previews arrive. You’ll be prompted to approve when this pass is ready.",
66
- },
67
- "final-review": {
68
- body: "Add comments where you want changes. If everything looks right, approve this pass in agent chat.",
69
- approvalMessage: "Approve this final storyboard review and continue to rendering.",
70
- },
71
- };
72
-
73
- type ReviewStepOffset = -1 | 0 | 1;
74
- const REVIEW_STEP_STATES: Record<
75
- ReviewStepOffset,
76
- {
77
- textClass: string;
78
- numberClass: string;
79
- ariaCurrent: "step" | undefined;
80
- marker: (number: StoryboardHandoffStep) => StoryboardHandoffStep | string;
81
- }
82
- > = {
83
- [-1]: {
84
- textClass: "text-emerald-400",
85
- numberClass: "border-emerald-700 bg-emerald-500/10",
86
- ariaCurrent: undefined,
87
- marker: () => "✓",
88
- },
89
- [0]: {
90
- textClass: "text-sky-300",
91
- numberClass: "border-sky-500 bg-sky-500/15",
92
- ariaCurrent: "step",
93
- marker: (number) => number,
94
- },
95
- [1]: {
96
- textClass: "text-neutral-600",
97
- numberClass: "border-neutral-700",
98
- ariaCurrent: undefined,
99
- marker: (number) => number,
100
- },
101
- };
102
-
103
- const REVIEW_STEP_SEPARATOR_CLASS: Record<StoryboardHandoffStep, string> = {
104
- 1: "invisible",
105
- 2: "",
106
- 3: "",
107
- };
108
-
109
- export interface StoryboardReviewGuideProps {
110
- frames: StoryboardFrameView[];
111
- draftCount: number;
112
- pendingCount: number;
113
- onFeedbackMessageCopied: () => void;
114
- }
115
-
116
- /** Stage-aware instructions plus the explicit Studio → agent handoff. */
117
- export function StoryboardReviewGuide({
118
- frames,
119
- draftCount,
120
- pendingCount,
121
- onFeedbackMessageCopied,
122
- }: StoryboardReviewGuideProps) {
123
- const summary = deriveStoryboardReviewStage(frames);
124
- const copy = GUIDE_COPY[summary.stage];
125
- const handoffStep = deriveStoryboardHandoffStep(draftCount, pendingCount);
126
- const progress = progressLabel(summary);
127
-
128
- return (
129
- <section className="mt-5 rounded-lg border border-neutral-800 bg-neutral-900/60 px-4 py-3">
130
- <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
131
- <div className="max-w-3xl">
132
- <div className="text-[10px] font-semibold uppercase tracking-wider text-sky-400">
133
- {copy.eyebrow}
134
- </div>
135
- <h2 className="mt-0.5 text-sm font-semibold text-neutral-100">{copy.title}</h2>
136
- <p className="mt-1 text-xs leading-relaxed text-neutral-400">{copy.body}</p>
137
- </div>
138
- {summary.frameCount > 0 && (
139
- <div className="shrink-0">
140
- <div className="mb-1.5 text-right text-[11px] font-medium text-neutral-300">
141
- {progress}
142
- </div>
143
- <div className="flex flex-wrap justify-end gap-1.5" aria-label="Frame status summary">
144
- {FRAME_STATUS_ORDER.map((status) => (
145
- <span
146
- key={status}
147
- className={`rounded px-2 py-1 text-[10px] font-medium ${FRAME_STATUS_META[status].chipClass}`}
148
- >
149
- {summary.counts[status]} {FRAME_STATUS_META[status].label}
150
- </span>
151
- ))}
152
- </div>
153
- </div>
154
- )}
155
- </div>
156
-
157
- {summary.frameCount > 0 && (
158
- <div className="mt-3 border-t border-neutral-800 pt-3">
159
- <ReviewSteps current={handoffStep} />
160
- <NextAction
161
- stage={summary.stage}
162
- step={handoffStep}
163
- draftCount={draftCount}
164
- onFeedbackMessageCopied={onFeedbackMessageCopied}
165
- />
166
- </div>
167
- )}
168
- </section>
169
- );
170
- }
171
-
172
- function ReviewSteps({ current }: { current: StoryboardHandoffStep }) {
173
- const steps = ["Review frames", "Save feedback", "Reply in agent chat"];
174
- return (
175
- <ol className="hidden items-center gap-2 sm:flex" aria-label="Storyboard review workflow">
176
- {steps.map((label, index) => (
177
- <ReviewStep
178
- key={label}
179
- label={label}
180
- number={(index + 1) as StoryboardHandoffStep}
181
- current={current}
182
- />
183
- ))}
184
- </ol>
185
- );
186
- }
187
-
188
- function ReviewStep({
189
- label,
190
- number,
191
- current,
192
- }: {
193
- label: string;
194
- number: StoryboardHandoffStep;
195
- current: StoryboardHandoffStep;
196
- }) {
197
- const offset = Math.sign(number - current) as ReviewStepOffset;
198
- const state = REVIEW_STEP_STATES[offset];
199
-
200
- return (
201
- <li className="flex items-center gap-2">
202
- <span
203
- className={`text-neutral-700 ${REVIEW_STEP_SEPARATOR_CLASS[number]}`}
204
- aria-hidden="true"
205
- >
206
- →
207
- </span>
208
- <span
209
- aria-current={state.ariaCurrent}
210
- className={`flex items-center gap-1.5 text-[11px] font-medium ${state.textClass}`}
211
- >
212
- <span
213
- className={`flex h-5 w-5 items-center justify-center rounded-full border text-[10px] ${state.numberClass}`}
214
- >
215
- {state.marker(number)}
216
- </span>
217
- {label}
218
- </span>
219
- </li>
220
- );
221
- }
222
-
223
- function NextAction({
224
- stage,
225
- step,
226
- draftCount,
227
- onFeedbackMessageCopied,
228
- }: {
229
- stage: StoryboardReviewStage;
230
- step: StoryboardHandoffStep;
231
- draftCount: number;
232
- onFeedbackMessageCopied: () => void;
233
- }) {
234
- if (step === 3) {
235
- return <AgentHandoffAction onFeedbackMessageCopied={onFeedbackMessageCopied} />;
236
- }
237
- if (step === 2) return <SaveFeedbackAction draftCount={draftCount} />;
238
- return <ReviewFramesAction stage={stage} />;
239
- }
240
-
241
- function AgentHandoffAction({ onFeedbackMessageCopied }: { onFeedbackMessageCopied: () => void }) {
242
- return (
243
- <div className="mt-3 flex flex-col gap-3 rounded-md border border-sky-900/70 bg-sky-950/20 px-3 py-2.5 sm:flex-row sm:items-center sm:justify-between">
244
- <div className="max-w-3xl">
245
- <div className="text-xs font-semibold text-sky-200">Next: return to your agent chat</div>
246
- <p className="mt-0.5 text-[11px] text-neutral-400">
247
- Feedback is saved, but the agent has not been notified. Paste this prompt in your terminal
248
- or IDE agent chat.
249
- </p>
250
- </div>
251
- <AgentChatMessageButton
252
- message={APPLY_STORYBOARD_FEEDBACK_MESSAGE}
253
- onCopied={onFeedbackMessageCopied}
254
- />
255
- </div>
256
- );
257
- }
258
-
259
- function SaveFeedbackAction({ draftCount }: { draftCount: number }) {
260
- return (
261
- <div className="mt-3 rounded-md border border-neutral-800 bg-neutral-950/40 px-3 py-2.5">
262
- <div>
263
- <div className="text-xs font-semibold text-neutral-200">Next: save your feedback</div>
264
- <p className="mt-0.5 text-[11px] text-neutral-500">
265
- {draftCount} frame{draftCount === 1 ? " has" : "s have"} feedback ready. Use Save &amp;
266
- copy message above to prepare this batch for your agent.
267
- </p>
268
- </div>
269
- </div>
270
- );
271
- }
272
-
273
- function ReviewFramesAction({ stage }: { stage: StoryboardReviewStage }) {
274
- const copy = REVIEW_ACTION_COPY[stage];
275
-
276
- return (
277
- <div className="mt-3 flex flex-col gap-3 rounded-md border border-neutral-800 bg-neutral-950/40 px-3 py-2.5 sm:flex-row sm:items-center sm:justify-between">
278
- <div>
279
- <div className="text-xs font-semibold text-neutral-200">Next: review the frames</div>
280
- <p className="mt-0.5 text-[11px] text-neutral-500">{copy.body}</p>
281
- </div>
282
- {copy.approvalMessage && (
283
- <AgentChatMessageButton message={copy.approvalMessage} label="Copy approval message" />
284
- )}
285
- </div>
286
- );
287
- }
288
-
289
- // The label intentionally folds six workflow states into three user-facing progress formats.
290
- // fallow-ignore-next-line complexity
291
- function progressLabel(summary: ReturnType<typeof deriveStoryboardReviewStage>): string {
292
- if (summary.stage === "sketch-in-progress" || summary.stage === "sketch-review") {
293
- const ready = summary.frameCount - summary.counts.outline;
294
- return `${ready} of ${summary.frameCount} visual sketches ready`;
295
- }
296
- if (summary.stage === "animation-in-progress" || summary.stage === "final-review") {
297
- return `${summary.counts.animated} of ${summary.frameCount} animations ready`;
298
- }
299
- return `${summary.frameCount} plan frame${summary.frameCount === 1 ? "" : "s"} ready`;
300
- }
@@ -1,26 +0,0 @@
1
- import type { StoryboardScript } from "../../hooks/useStoryboard";
2
-
3
- export interface StoryboardScriptPanelProps {
4
- script: StoryboardScript;
5
- }
6
-
7
- /**
8
- * Collapsible view of the companion narration script (SCRIPT.md). The mature
9
- * pipeline keeps the full voiceover script — voice settings, per-line delivery
10
- * and timing — in this file; here it's surfaced read-only alongside the frames.
11
- * (Per-frame VO iteration will live in the frame focus view.)
12
- */
13
- export function StoryboardScriptPanel({ script }: StoryboardScriptPanelProps) {
14
- if (!script.exists) return null;
15
- return (
16
- <details className="mt-10 rounded-lg border border-neutral-800 bg-neutral-900/50">
17
- <summary className="cursor-pointer select-none px-4 py-3 text-sm font-medium text-neutral-300">
18
- Narration script
19
- <span className="ml-2 font-normal text-neutral-500">{script.path}</span>
20
- </summary>
21
- <pre className="max-h-[420px] overflow-auto whitespace-pre-wrap border-t border-neutral-800 px-4 py-3 text-xs leading-relaxed text-neutral-400">
22
- {script.content}
23
- </pre>
24
- </details>
25
- );
26
- }