@hyperframes/studio 0.8.50 → 0.8.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-I5YRzB02.js} +3 -3
  2. package/dist/assets/{index-Dt3qBI-9.js → index-BTAfgesM.js} +1 -1
  3. package/dist/assets/index-CxAu_Okg.css +1 -0
  4. package/dist/assets/index-DocJlW2v.js +376 -0
  5. package/dist/assets/{index-DUAIWxkI.js → index-Du5x7KUR.js} +1 -1
  6. package/dist/index.d.ts +419 -20
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +7496 -8288
  9. package/dist/index.js.map +1 -1
  10. package/dist/styles/tailwind-preset.js.map +1 -1
  11. package/package.json +13 -12
  12. package/src/App.tsx +131 -143
  13. package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
  14. package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
  15. package/src/captions/components/CaptionOverlay.tsx +8 -4
  16. package/src/captions/components/CaptionOverlayUtils.ts +2 -3
  17. package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
  18. package/src/captions/components/CaptionTimeline.tsx +1 -1
  19. package/src/captions/components/shared.tsx +2 -2
  20. package/src/components/AskAgentModal.tsx +5 -5
  21. package/src/components/EditorShell.tsx +8 -7
  22. package/src/components/ExternalFileConflictBanner.tsx +16 -16
  23. package/src/components/LintModal.tsx +6 -16
  24. package/src/components/MediaPreview.tsx +2 -2
  25. package/src/components/RightPanelTabs.test.tsx +151 -0
  26. package/src/components/RightPanelTabs.tsx +59 -0
  27. package/src/components/SaveQueuePausedBanner.tsx +2 -2
  28. package/src/components/StudioHeader.dom.test.tsx +181 -0
  29. package/src/components/StudioHeader.tsx +110 -210
  30. package/src/components/StudioLeftSidebar.tsx +7 -4
  31. package/src/components/StudioOverlays.tsx +1 -1
  32. package/src/components/StudioRightPanel.tsx +51 -39
  33. package/src/components/StudioToast.tsx +2 -2
  34. package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
  35. package/src/components/TimelineHistoryButtons.tsx +54 -0
  36. package/src/components/TimelineToolbar.test.tsx +8 -0
  37. package/src/components/TimelineToolbar.tsx +8 -12
  38. package/src/components/dock/Dock.test.tsx +260 -0
  39. package/src/components/dock/Dock.tsx +281 -0
  40. package/src/components/dock/dock.css +28 -0
  41. package/src/components/dock/dockAccessibility.test.ts +166 -0
  42. package/src/components/dock/dockAccessibility.ts +98 -0
  43. package/src/components/dock/dockLayout.test.ts +134 -0
  44. package/src/components/dock/dockLayout.ts +105 -0
  45. package/src/components/dock/dockLayoutSchema.ts +66 -0
  46. package/src/components/dock/dockLayoutStore.ts +91 -0
  47. package/src/components/dock/dockTestHarness.ts +11 -0
  48. package/src/components/dock/panelRegistry.ts +67 -0
  49. package/src/components/editor/AnimationCard.tsx +4 -4
  50. package/src/components/editor/AnimationCardParts.tsx +6 -6
  51. package/src/components/editor/BlockParamsPanel.tsx +4 -4
  52. package/src/components/editor/BorderRadiusEditor.tsx +1 -1
  53. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  54. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  55. package/src/components/editor/DomEditOverlay.tsx +1 -1
  56. package/src/components/editor/EaseCurveSection.tsx +1 -1
  57. package/src/components/editor/EaseParamFields.tsx +5 -5
  58. package/src/components/editor/FileTree.tsx +4 -4
  59. package/src/components/editor/FileTreeIcons.tsx +1 -1
  60. package/src/components/editor/FileTreeNodes.tsx +13 -13
  61. package/src/components/editor/InlineTextToolbar.tsx +1 -1
  62. package/src/components/editor/InspectorHeaderActions.tsx +3 -3
  63. package/src/components/editor/KeyframeDiamond.tsx +1 -1
  64. package/src/components/editor/KeyframeEaseList.tsx +1 -1
  65. package/src/components/editor/LayersPanel.test.ts +97 -1
  66. package/src/components/editor/LayersPanel.tsx +7 -5
  67. package/src/components/editor/MarqueeOverlay.tsx +1 -1
  68. package/src/components/editor/OffCanvasIndicators.tsx +1 -1
  69. package/src/components/editor/PreviewGuides.tsx +1 -1
  70. package/src/components/editor/PromotableControl.tsx +2 -2
  71. package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
  72. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  73. package/src/components/editor/SnapToolbar.tsx +2 -2
  74. package/src/components/editor/TimelineFxPopover.tsx +1 -1
  75. package/src/components/editor/TopologyLens.test.tsx +28 -0
  76. package/src/components/editor/TopologyLens.tsx +7 -5
  77. package/src/components/editor/Transform3DCube.tsx +2 -2
  78. package/src/components/editor/audioFxRevealTarget.ts +1 -0
  79. package/src/components/editor/domEditing.test.ts +1 -1
  80. package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
  81. package/src/components/editor/domEditingAgentPrompt.ts +4 -3
  82. package/src/components/editor/propertyPanelColor.tsx +11 -11
  83. package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
  84. package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
  85. package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
  86. package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
  87. package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
  88. package/src/components/editor/propertyPanelCommitField.tsx +1 -1
  89. package/src/components/editor/propertyPanelFill.tsx +4 -4
  90. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  91. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
  92. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  93. package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
  94. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
  95. package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
  96. package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
  97. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
  98. package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
  99. package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
  100. package/src/components/editor/propertyPanelFont.tsx +5 -5
  101. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  102. package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
  103. package/src/components/editor/propertyPanelFxControls.tsx +6 -6
  104. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  105. package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
  106. package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
  107. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  108. package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
  109. package/src/components/editor/propertyPanelSections.tsx +8 -8
  110. package/src/components/editor/useColorGradingController.test.ts +38 -0
  111. package/src/components/editor/useColorGradingController.ts +9 -1
  112. package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
  113. package/src/components/editor/useColorGradingPreviews.ts +11 -1
  114. package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
  115. package/src/components/editor/usePreviewCompositionRect.ts +21 -0
  116. package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
  117. package/src/components/feedback/feedbackTrigger.ts +0 -1
  118. package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
  119. package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
  120. package/src/components/nle/NLEPreview.tsx +3 -3
  121. package/src/components/nle/PreviewOverlays.tsx +6 -6
  122. package/src/components/nle/PreviewPane.tsx +3 -3
  123. package/src/components/nle/TimelinePane.tsx +3 -3
  124. package/src/components/nle/TimelineResizeDivider.tsx +2 -2
  125. package/src/components/panels/SlideshowPanel.tsx +2 -2
  126. package/src/components/panels/SlideshowSubPanels.tsx +20 -17
  127. package/src/components/panels/VariablesBindElement.tsx +2 -2
  128. package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
  129. package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
  130. package/src/components/panels/VariablesPanel.tsx +3 -3
  131. package/src/components/panels/VariablesValueControls.tsx +2 -2
  132. package/src/components/pointerTargetSize.test.tsx +8 -0
  133. package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
  134. package/src/components/renders/RenderQueue.test.tsx +7 -0
  135. package/src/components/renders/RenderQueue.tsx +7 -7
  136. package/src/components/renders/RenderQueueItem.tsx +6 -6
  137. package/src/components/sidebar/AssetCard.tsx +8 -8
  138. package/src/components/sidebar/AssetContextMenu.tsx +8 -8
  139. package/src/components/sidebar/AssetsTab.tsx +2 -2
  140. package/src/components/sidebar/AudioRow.tsx +6 -6
  141. package/src/components/sidebar/BlocksTab.tsx +6 -6
  142. package/src/components/sidebar/CompositionsTab.tsx +5 -5
  143. package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
  144. package/src/components/sidebar/LeftSidebar.lintBadge.test.tsx +58 -0
  145. package/src/components/sidebar/LeftSidebar.tsx +19 -5
  146. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  147. package/src/components/timelineToolbarStyles.ts +5 -0
  148. package/src/components/ui/Button.test.tsx +252 -0
  149. package/src/components/ui/Button.tsx +89 -126
  150. package/src/components/ui/IconButton.tsx +38 -0
  151. package/src/components/ui/Input.tsx +115 -0
  152. package/src/components/ui/Menu.test.tsx +374 -0
  153. package/src/components/ui/Menu.tsx +213 -0
  154. package/src/components/ui/NumberField.tsx +122 -0
  155. package/src/components/ui/Popover.tsx +74 -0
  156. package/src/components/ui/SearchInput.tsx +2 -2
  157. package/src/components/ui/Select.tsx +100 -0
  158. package/src/components/ui/Slider.tsx +153 -0
  159. package/src/components/ui/Tabs.test.tsx +98 -0
  160. package/src/components/ui/Tabs.tsx +71 -0
  161. package/src/components/ui/Toggle.tsx +63 -0
  162. package/src/components/ui/Tooltip.test.tsx +110 -0
  163. package/src/components/ui/Tooltip.tsx +38 -112
  164. package/src/components/ui/cn.test.ts +30 -0
  165. package/src/components/ui/cn.ts +25 -0
  166. package/src/components/ui/index.ts +29 -2
  167. package/src/components/ui/mountHost.testHelpers.ts +24 -0
  168. package/src/components/ui/valueControls.test.tsx +524 -0
  169. package/src/hooks/useAppHotkeys.ts +21 -80
  170. package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
  171. package/src/hooks/useCaptionDetection.ts +3 -1
  172. package/src/hooks/useEditHistoryActions.test.tsx +90 -0
  173. package/src/hooks/useEditHistoryActions.ts +106 -0
  174. package/src/hooks/useLintModal.test.tsx +65 -0
  175. package/src/hooks/useLintModal.ts +3 -0
  176. package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
  177. package/src/hooks/useSdkSession.ts +43 -10
  178. package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
  179. package/src/hooks/useServerConnection.ts +43 -10
  180. package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
  181. package/src/hooks/useTimelineAssetDropOps.ts +52 -11
  182. package/src/index.ts +51 -0
  183. package/src/main.tsx +9 -3
  184. package/src/player/components/AudioWaveform.tsx +1 -1
  185. package/src/player/components/AutomationSelectionMenu.tsx +3 -3
  186. package/src/player/components/ClipContextMenu.tsx +3 -3
  187. package/src/player/components/CompositionThumbnail.test.ts +0 -3
  188. package/src/player/components/CompositionThumbnail.tsx +3 -4
  189. package/src/player/components/EditModal.tsx +1 -1
  190. package/src/player/components/ImageThumbnail.tsx +1 -1
  191. package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
  192. package/src/player/components/LayerDisclosureRow.tsx +2 -2
  193. package/src/player/components/PlayerControls.tsx +1 -1
  194. package/src/player/components/ShortcutsPanel.tsx +9 -9
  195. package/src/player/components/SpeedMenu.tsx +3 -5
  196. package/src/player/components/Timeline.tsx +2 -1
  197. package/src/player/components/TimelineCanvas.tsx +21 -14
  198. package/src/player/components/TimelineClip.tsx +0 -1
  199. package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
  200. package/src/player/components/TimelineEmptyState.tsx +5 -5
  201. package/src/player/components/TimelineFxButton.tsx +6 -6
  202. package/src/player/components/TimelineGroupHeader.tsx +5 -5
  203. package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
  204. package/src/player/components/TimelineGroupRow.tsx +1 -1
  205. package/src/player/components/TimelineLanes.test.tsx +32 -0
  206. package/src/player/components/TimelineLanes.tsx +2 -5
  207. package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
  208. package/src/player/components/TimelineRuler.tsx +1 -1
  209. package/src/player/components/TimelineShortcutHint.tsx +1 -1
  210. package/src/player/components/TimelineTrackHeader.tsx +1 -1
  211. package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
  212. package/src/player/components/TimelineTypes.ts +2 -1
  213. package/src/player/components/TrackGapContextMenu.tsx +1 -1
  214. package/src/player/components/VideoThumbnail.tsx +4 -2
  215. package/src/player/components/VolumeControl.tsx +2 -2
  216. package/src/player/components/timelineCallbacks.ts +8 -8
  217. package/src/player/components/timelineClipDragPreview.ts +51 -27
  218. package/src/player/components/timelineClipDragTypes.ts +3 -0
  219. package/src/player/components/timelineDragDrop.test.tsx +61 -1
  220. package/src/player/components/timelineDragDrop.ts +57 -15
  221. package/src/player/components/timelineLaneProps.ts +3 -0
  222. package/src/player/components/timelineSnapping.test.ts +27 -0
  223. package/src/player/components/timelineSnapping.ts +14 -1
  224. package/src/player/components/trackHeaderLabelRows.tsx +4 -4
  225. package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
  226. package/src/player/components/useTimelineClipDrag.ts +30 -3
  227. package/src/player/lib/describeClips.ts +3 -54
  228. package/src/player/lib/playbackShortcuts.ts +2 -0
  229. package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
  230. package/src/player/store/previewIframeStore.test.tsx +29 -0
  231. package/src/player/store/previewIframeStore.ts +25 -0
  232. package/src/renderQueueExports.test.tsx +58 -0
  233. package/src/styles/classCandidates.test.ts +105 -0
  234. package/src/styles/classCandidates.ts +281 -0
  235. package/src/styles/hex-baseline.json +84 -0
  236. package/src/styles/hexRatchet.test.ts +201 -0
  237. package/src/styles/iconTokens.ts +20 -0
  238. package/src/styles/palette-reset.css +6 -0
  239. package/src/styles/studio.css +24 -21
  240. package/src/styles/styleSources.test.ts +20 -0
  241. package/src/styles/styleSources.ts +47 -0
  242. package/src/styles/tailwind-preset.shared.js +3 -0
  243. package/src/styles/theme.css +243 -0
  244. package/src/styles/theme.test.ts +183 -0
  245. package/src/styles/tokenGate.test.ts +109 -0
  246. package/src/utils/sourcePatcher.ts +1 -1
  247. package/src/utils/studioHelpers.test.ts +18 -0
  248. package/src/utils/studioHelpers.ts +11 -1
  249. package/src/utils/studioUiPreferences.ts +7 -0
  250. package/src/utils/timelineDropTrackInsert.test.ts +134 -0
  251. package/src/utils/timelineDropTrackInsert.ts +83 -0
  252. package/src/utils/typingTarget.ts +4 -0
  253. package/dist/assets/index-n2ml4tUn.js +0 -442
  254. package/dist/assets/index-pO6p0H27.css +0 -1
  255. package/src/components/PanelTabButton.tsx +0 -31
  256. package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
  257. package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
  258. package/src/components/storyboard/FramePoster.test.tsx +0 -61
  259. package/src/components/storyboard/FramePoster.tsx +0 -86
  260. package/src/components/storyboard/StoryboardDirection.tsx +0 -45
  261. package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
  262. package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
  263. package/src/components/storyboard/StoryboardGrid.tsx +0 -55
  264. package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
  265. package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
  266. package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
  267. package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
  268. package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
  269. package/src/components/storyboard/StoryboardView.tsx +0 -169
  270. package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
  271. package/src/components/storyboard/frameComments.test.ts +0 -95
  272. package/src/components/storyboard/frameComments.ts +0 -115
  273. package/src/components/storyboard/frameStatus.ts +0 -36
  274. package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
  275. package/src/components/storyboard/storyboardReviewStage.ts +0 -45
  276. package/src/components/storyboard/useFrameComments.ts +0 -98
  277. package/src/contexts/ViewModeContext.tsx +0 -142
  278. package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
  279. package/src/hooks/useProjectSignaturePoll.ts +0 -68
  280. package/src/hooks/useStoryboard.ts +0 -99
@@ -1,135 +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, beforeEach, describe, expect, it, vi } from "vitest";
6
- import { useProjectSignaturePoll } from "./useProjectSignaturePoll";
7
-
8
- Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true);
9
-
10
- function HookHost({
11
- signature,
12
- onChange,
13
- }: {
14
- signature: string | undefined;
15
- onChange: () => void;
16
- }) {
17
- useProjectSignaturePoll("demo", signature, onChange);
18
- return null;
19
- }
20
-
21
- let root: Root | null = null;
22
- let container: HTMLElement | null = null;
23
-
24
- function mount(signature: string | undefined, onChange: () => void): void {
25
- act(() => {
26
- if (!root) {
27
- container = document.createElement("div");
28
- document.body.appendChild(container);
29
- root = createRoot(container);
30
- }
31
- root.render(<HookHost signature={signature} onChange={onChange} />);
32
- });
33
- }
34
-
35
- function unmount(): void {
36
- if (root) {
37
- act(() => root?.unmount());
38
- root = null;
39
- }
40
- container?.remove();
41
- container = null;
42
- }
43
-
44
- function mockSignatureResponse(signature: string): ReturnType<typeof vi.fn> {
45
- const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ signature }) }));
46
- vi.stubGlobal("fetch", fetchMock);
47
- return fetchMock;
48
- }
49
-
50
- describe("useProjectSignaturePoll", () => {
51
- beforeEach(() => {
52
- vi.useFakeTimers();
53
- });
54
-
55
- afterEach(() => {
56
- unmount();
57
- vi.useRealTimers();
58
- vi.unstubAllGlobals();
59
- vi.restoreAllMocks();
60
- });
61
-
62
- it("fires onChange when the polled signature moves past the loaded one", async () => {
63
- const fetchMock = mockSignatureResponse("sig-b");
64
- const onChange = vi.fn();
65
- mount("sig-a", onChange);
66
-
67
- await vi.advanceTimersByTimeAsync(2000);
68
- expect(fetchMock).toHaveBeenCalledWith("/api/projects/demo/signature");
69
- expect(onChange).toHaveBeenCalledTimes(1);
70
- });
71
-
72
- it("stays quiet while the polled signature matches", async () => {
73
- mockSignatureResponse("sig-a");
74
- const onChange = vi.fn();
75
- mount("sig-a", onChange);
76
-
77
- await vi.advanceTimersByTimeAsync(6000);
78
- expect(onChange).not.toHaveBeenCalled();
79
- });
80
-
81
- it("does not poll before a baseline signature exists", async () => {
82
- const fetchMock = mockSignatureResponse("sig-a");
83
- const onChange = vi.fn();
84
- mount(undefined, onChange);
85
-
86
- await vi.advanceTimersByTimeAsync(6000);
87
- expect(fetchMock).not.toHaveBeenCalled();
88
- expect(onChange).not.toHaveBeenCalled();
89
- });
90
-
91
- it("re-arms against the caller's refreshed signature without re-mounting", async () => {
92
- mockSignatureResponse("sig-b");
93
- const onChange = vi.fn();
94
- mount("sig-a", onChange);
95
-
96
- await vi.advanceTimersByTimeAsync(2000);
97
- expect(onChange).toHaveBeenCalledTimes(1);
98
-
99
- // The caller refetched and now holds the polled signature — no more firing.
100
- mount("sig-b", onChange);
101
- await vi.advanceTimersByTimeAsync(6000);
102
- expect(onChange).toHaveBeenCalledTimes(1);
103
- });
104
-
105
- it("skips ticks while the tab is hidden and re-checks on visibility", async () => {
106
- const fetchMock = mockSignatureResponse("sig-b");
107
- const onChange = vi.fn();
108
- let hidden = true;
109
- vi.spyOn(document, "hidden", "get").mockImplementation(() => hidden);
110
- mount("sig-a", onChange);
111
-
112
- await vi.advanceTimersByTimeAsync(6000);
113
- expect(fetchMock).not.toHaveBeenCalled();
114
-
115
- hidden = false;
116
- await act(async () => {
117
- document.dispatchEvent(new Event("visibilitychange"));
118
- await vi.advanceTimersByTimeAsync(0);
119
- });
120
- expect(onChange).toHaveBeenCalledTimes(1);
121
- });
122
-
123
- it("stops polling after unmount", async () => {
124
- mockSignatureResponse("sig-b");
125
- const onChange = vi.fn();
126
- mount("sig-a", onChange);
127
-
128
- await vi.advanceTimersByTimeAsync(2000);
129
- expect(onChange).toHaveBeenCalledTimes(1);
130
-
131
- unmount();
132
- await vi.advanceTimersByTimeAsync(10_000);
133
- expect(onChange).toHaveBeenCalledTimes(1);
134
- });
135
- });
@@ -1,68 +0,0 @@
1
- import { useEffect, useRef } from "react";
2
- import { buildProjectApiPath } from "../utils/projectRouting";
3
-
4
- const POLL_INTERVAL_MS = 2000;
5
-
6
- /** One poll: the current signature, or null on any failure (skip this tick). */
7
- async function fetchProjectSignature(projectId: string): Promise<string | null> {
8
- try {
9
- const res = await fetch(buildProjectApiPath(projectId, "/signature"));
10
- if (!res.ok) return null;
11
- const body = (await res.json()) as { signature?: string };
12
- return typeof body.signature === "string" ? body.signature : null;
13
- } catch {
14
- return null;
15
- }
16
- }
17
-
18
- /**
19
- * Poll the project's content signature and fire `onChange` when it no longer
20
- * matches `currentSignature` — the storyboard board uses this to refresh itself
21
- * while an agent writes sketch frames to disk.
22
- *
23
- * The comparison baseline is the signature the caller's data was loaded with,
24
- * so a refetch triggered by `onChange` naturally re-arms the poll with the new
25
- * value. Ticks are skipped while the tab is hidden (a visibility flip re-checks
26
- * immediately) and while a previous request is still in flight; request
27
- * failures are ignored — polling degrades to today's manual-reload behavior.
28
- */
29
- export function useProjectSignaturePoll(
30
- projectId: string | null,
31
- currentSignature: string | undefined,
32
- onChange: () => void,
33
- ): void {
34
- const signatureRef = useRef(currentSignature);
35
- const onChangeRef = useRef(onChange);
36
- signatureRef.current = currentSignature;
37
- onChangeRef.current = onChange;
38
-
39
- useEffect(() => {
40
- if (!projectId) return;
41
- let disposed = false;
42
- let inFlight = false;
43
-
44
- const tick = async () => {
45
- if (disposed || inFlight || document.hidden) return;
46
- // No baseline yet (initial storyboard fetch still loading, or an older
47
- // server without the signature field) — nothing to compare against.
48
- if (signatureRef.current === undefined) return;
49
- inFlight = true;
50
- const latest = await fetchProjectSignature(projectId);
51
- inFlight = false;
52
- if (disposed || latest === null) return;
53
- if (latest !== signatureRef.current) onChangeRef.current();
54
- };
55
-
56
- const interval = window.setInterval(() => void tick(), POLL_INTERVAL_MS);
57
- const onVisibilityChange = () => {
58
- if (!document.hidden) void tick();
59
- };
60
- document.addEventListener("visibilitychange", onVisibilityChange);
61
-
62
- return () => {
63
- disposed = true;
64
- window.clearInterval(interval);
65
- document.removeEventListener("visibilitychange", onVisibilityChange);
66
- };
67
- }, [projectId]);
68
- }
@@ -1,99 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import type {
3
- StoryboardFrame,
4
- StoryboardGlobals,
5
- StoryboardWarning,
6
- } from "@hyperframes/core/storyboard";
7
- import { buildProjectApiPath } from "../utils/projectRouting";
8
-
9
- /** A frame as returned by the API: parsed frame + disk-resolution info. */
10
- export interface StoryboardFrameView extends StoryboardFrame {
11
- /** Whether `src` resolves to an existing file inside the project. */
12
- srcExists: boolean;
13
- }
14
-
15
- /** The companion narration script (SCRIPT.md), when present alongside the storyboard. */
16
- export interface StoryboardScript {
17
- exists: boolean;
18
- path: string;
19
- content: string;
20
- }
21
-
22
- /** Shape of `GET /api/projects/:id/storyboard`. */
23
- export interface StoryboardResponse {
24
- exists: boolean;
25
- path: string;
26
- globals: StoryboardGlobals;
27
- frames: StoryboardFrameView[];
28
- warnings: StoryboardWarning[];
29
- script?: StoryboardScript;
30
- /** Project content signature this payload was derived from (poster cache-bust + change poll). */
31
- signature?: string;
32
- }
33
-
34
- export interface UseStoryboardResult {
35
- data: StoryboardResponse | null;
36
- loading: boolean;
37
- error: string | null;
38
- reload: () => void;
39
- }
40
-
41
- /**
42
- * Load the parsed storyboard manifest for a project. Markdown stays canonical on
43
- * disk; this fetches the server-derived JSON the storyboard view renders.
44
- *
45
- * `reload()` refetches in place: the blocking spinner only shows on first load
46
- * and project switches, so background refreshes (file-change polling, post-save
47
- * reloads) swap data without flashing the board.
48
- */
49
- export function useStoryboard(projectId: string | null): UseStoryboardResult {
50
- const [data, setData] = useState<StoryboardResponse | null>(null);
51
- const [loading, setLoading] = useState(true);
52
- const [error, setError] = useState<string | null>(null);
53
- const [reloadKey, setReloadKey] = useState(0);
54
- const hasDataRef = useRef(false);
55
- const lastProjectRef = useRef<string | null>(null);
56
-
57
- const reload = useCallback(() => setReloadKey((k) => k + 1), []);
58
-
59
- useEffect(() => {
60
- if (!projectId) return;
61
- let cancelled = false;
62
- if (lastProjectRef.current !== projectId) {
63
- lastProjectRef.current = projectId;
64
- hasDataRef.current = false;
65
- setData(null);
66
- }
67
- if (!hasDataRef.current) setLoading(true);
68
- setError(null);
69
-
70
- // Route through buildProjectApiPath so the (URL-derived) projectId is encoded
71
- // into the path rather than interpolated raw (CodeQL js/client-side-request-forgery).
72
- fetch(buildProjectApiPath(projectId, "/storyboard"))
73
- .then((res) => {
74
- if (!res.ok) throw new Error(`storyboard request failed: ${res.status}`);
75
- return res.json() as Promise<StoryboardResponse>;
76
- })
77
- .then((json) => {
78
- if (cancelled) return;
79
- hasDataRef.current = true;
80
- setData(json);
81
- })
82
- .catch((err: unknown) => {
83
- if (cancelled) return;
84
- // A failed background refresh keeps the last good board on screen;
85
- // the blocking error state is only for loads with nothing to show.
86
- if (hasDataRef.current) return;
87
- setError(err instanceof Error ? err.message : "failed to load storyboard");
88
- })
89
- .finally(() => {
90
- if (!cancelled) setLoading(false);
91
- });
92
-
93
- return () => {
94
- cancelled = true;
95
- };
96
- }, [projectId, reloadKey]);
97
-
98
- return { data, loading, error, reload };
99
- }