@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,24 +0,0 @@
1
- export function StudioGlobalDragOverlay() {
2
- return (
3
- <div className="hf-backdrop-in absolute inset-0 z-[90] flex items-center justify-center bg-black/50 backdrop-blur-sm pointer-events-none">
4
- <div className="flex flex-col items-center gap-3 px-8 py-6 rounded-xl border-2 border-dashed border-studio-accent/60 bg-studio-accent/[0.06]">
5
- <svg
6
- width="32"
7
- height="32"
8
- viewBox="0 0 24 24"
9
- fill="none"
10
- stroke="currentColor"
11
- strokeWidth="1.5"
12
- strokeLinecap="round"
13
- strokeLinejoin="round"
14
- className="text-studio-accent"
15
- >
16
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
17
- <polyline points="7 10 12 15 17 10" />
18
- <line x1="12" y1="15" x2="12" y2="3" />
19
- </svg>
20
- <span className="text-sm font-medium text-studio-accent">Drop to add at the playhead</span>
21
- </div>
22
- </div>
23
- );
24
- }
@@ -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
- }