@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 +1 @@
1
- {"version":3,"sources":["../../src/styles/tailwind-preset.shared.js","../../src/styles/tailwind-preset.ts"],"sourcesContent":["const studioPreset = {\n theme: {\n extend: {\n colors: {\n studio: {\n bg: \"#0a0a0a\",\n surface: \"#141414\",\n border: \"#262626\",\n text: \"#e5e5e5\",\n muted: \"#737373\",\n accent: \"#3CE6AC\",\n },\n panel: {\n bg: \"#0C0C0E\",\n // Open inspector-section body — slightly lighter than headers (bg)\n // so the recessed scrollable region reads distinct.\n \"bg-inset\": \"#121214\",\n input: \"#161618\",\n surface: \"#18181B\",\n hover: \"#27272A\",\n border: \"#1E1E1E\",\n \"border-input\": \"#27272A\",\n hairline: \"#1A1A1C\",\n \"text-0\": \"#FAFAFA\",\n \"text-1\": \"#E4E4E7\",\n \"text-2\": \"#A1A1AA\",\n \"text-3\": \"#71717A\",\n \"text-4\": \"#52525B\",\n \"text-5\": \"#3F3F46\",\n accent: \"#3CE6AC\",\n danger: \"#EF4444\",\n media: \"#00E3FF\",\n container: \"#F5A623\",\n },\n },\n },\n },\n plugins: [],\n};\n\nexport default studioPreset;\n","import studioPreset from \"./tailwind-preset.shared.js\";\n\nexport default studioPreset;\n"],"mappings":";;;AAAA,IAAM,eAAe;AAAA,EACnB,OAAO;AAAA,IACL,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,QAAQ;AAAA,UACN,IAAI;AAAA,UACJ,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,UACP,QAAQ;AAAA,QACV;AAAA,QACA,OAAO;AAAA,UACL,IAAI;AAAA;AAAA;AAAA,UAGJ,YAAY;AAAA,UACZ,OAAO;AAAA,UACP,SAAS;AAAA,UACT,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,gBAAgB;AAAA,UAChB,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,OAAO;AAAA,UACP,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,SAAS,CAAC;AACZ;AAEA,IAAO,iCAAQ;;;ACtCf,IAAO,0BAAQ;","names":[]}
1
+ {"version":3,"sources":["../../src/styles/tailwind-preset.shared.js","../../src/styles/tailwind-preset.ts"],"sourcesContent":["/** @deprecated Superseded by `src/styles/theme.css` (published as\n * `@hyperframes/studio/theme.css`); kept one more major for Tailwind v3\n * consumers. Values are byte-identical; theme.test.ts fails if they drift. */\nconst studioPreset = {\n theme: {\n extend: {\n colors: {\n studio: {\n bg: \"#0a0a0a\",\n surface: \"#141414\",\n border: \"#262626\",\n text: \"#e5e5e5\",\n muted: \"#737373\",\n accent: \"#3CE6AC\",\n },\n panel: {\n bg: \"#0C0C0E\",\n // Open inspector-section body — slightly lighter than headers (bg)\n // so the recessed scrollable region reads distinct.\n \"bg-inset\": \"#121214\",\n input: \"#161618\",\n surface: \"#18181B\",\n hover: \"#27272A\",\n border: \"#1E1E1E\",\n \"border-input\": \"#27272A\",\n hairline: \"#1A1A1C\",\n \"text-0\": \"#FAFAFA\",\n \"text-1\": \"#E4E4E7\",\n \"text-2\": \"#A1A1AA\",\n \"text-3\": \"#71717A\",\n \"text-4\": \"#52525B\",\n \"text-5\": \"#3F3F46\",\n accent: \"#3CE6AC\",\n danger: \"#EF4444\",\n media: \"#00E3FF\",\n container: \"#F5A623\",\n },\n },\n },\n },\n plugins: [],\n};\n\nexport default studioPreset;\n","import studioPreset from \"./tailwind-preset.shared.js\";\n\nexport default studioPreset;\n"],"mappings":";;;AAGA,IAAM,eAAe;AAAA,EACnB,OAAO;AAAA,IACL,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,QAAQ;AAAA,UACN,IAAI;AAAA,UACJ,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,UACP,QAAQ;AAAA,QACV;AAAA,QACA,OAAO;AAAA,UACL,IAAI;AAAA;AAAA;AAAA,UAGJ,YAAY;AAAA,UACZ,OAAO;AAAA,UACP,SAAS;AAAA,UACT,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,gBAAgB;AAAA,UAChB,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,OAAO;AAAA,UACP,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,SAAS,CAAC;AACZ;AAEA,IAAO,iCAAQ;;;ACzCf,IAAO,0BAAQ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.8.50",
3
+ "version": "0.8.52",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,12 +23,14 @@
23
23
  "import": "./dist/styles/tailwind-preset.js",
24
24
  "types": "./dist/styles/tailwind-preset.d.ts"
25
25
  },
26
+ "./theme.css": "./src/styles/theme.css",
26
27
  "./package.json": "./package.json"
27
28
  },
28
29
  "publishConfig": {
29
30
  "access": "public"
30
31
  },
31
32
  "dependencies": {
33
+ "@base-ui/react": "1.7.0",
32
34
  "@codemirror/autocomplete": "^6.20.1",
33
35
  "@codemirror/commands": "^6.10.3",
34
36
  "@codemirror/lang-css": "^6.3.1",
@@ -44,32 +46,31 @@
44
46
  "@phosphor-icons/react": "^2.1.10",
45
47
  "@tanstack/react-virtual": "^3.14.6",
46
48
  "bpm-detective": "^2.0.5",
47
- "dompurify": "^3.2.4",
49
+ "cn": "0.2.4",
50
+ "dockview-react": "8.3.1",
48
51
  "gsap": "^3.13.0",
49
- "marked": "^14.1.4",
50
52
  "mediabunny": "^1.45.3",
51
- "@hyperframes/player": "0.8.50",
52
- "@hyperframes/core": "0.8.50",
53
- "@hyperframes/studio-server": "0.8.50",
54
- "@hyperframes/sdk": "0.8.50",
55
- "@hyperframes/parsers": "0.8.50"
53
+ "@hyperframes/core": "0.8.52",
54
+ "@hyperframes/player": "0.8.52",
55
+ "@hyperframes/sdk": "0.8.52",
56
+ "@hyperframes/parsers": "0.8.52",
57
+ "@hyperframes/studio-server": "0.8.52"
56
58
  },
57
59
  "devDependencies": {
60
+ "@tailwindcss/vite": "^4.3.3",
58
61
  "@types/react": "19",
59
62
  "@types/react-dom": "19",
60
63
  "@vitejs/plugin-react": "^4.0.0",
61
- "autoprefixer": "^10.4.0",
62
64
  "chokidar": "^4.0.3",
63
65
  "fake-indexeddb": "^6.2.5",
64
- "postcss": "^8.4.0",
65
66
  "puppeteer-core": "^25.2.1",
66
- "tailwindcss": "^3.4.0",
67
+ "tailwindcss": "^4.3.3",
67
68
  "tsup": "^8.0.0",
68
69
  "typescript": "^5.0.0",
69
70
  "vite": "^6.4.2",
70
71
  "vitest": "^4.1.11",
71
72
  "zustand": "^5.0.0",
72
- "@hyperframes/producer": "0.8.50"
73
+ "@hyperframes/producer": "0.8.52"
73
74
  },
74
75
  "peerDependencies": {
75
76
  "react": "19",
package/src/App.tsx CHANGED
@@ -1,3 +1,4 @@
1
+ import { useOwnPreviewIframe, usePreviewIframeStore } from "./player/store/previewIframeStore";
1
2
  import { buildProjectApiPath } from "./utils/projectRouting";
2
3
  import { useState, useCallback, useRef, useMemo, useLayoutEffect } from "react";
3
4
  import type { LeftSidebarHandle, SidebarTab } from "./components/sidebar/LeftSidebar";
@@ -53,8 +54,6 @@ import { StudioRightPanel } from "./components/StudioRightPanel";
53
54
  import { TimelineToolbar } from "./components/TimelineToolbar";
54
55
  import { StudioPlaybackProvider, StudioShellProvider } from "./contexts/StudioContext";
55
56
  import { PanelLayoutProvider } from "./contexts/PanelLayoutContext";
56
- import { ViewModeProvider, useViewModeState } from "./contexts/ViewModeContext";
57
- import { StoryboardView } from "./components/storyboard/StoryboardView";
58
57
  import { FileManagerProvider } from "./contexts/FileManagerContext";
59
58
  import { DomEditProvider } from "./contexts/DomEditContext";
60
59
  import { StudioSplash } from "./components/StudioSplash";
@@ -68,10 +67,9 @@ const getTimelineSelectionSet = () => usePlayerStore.getState().selectedElementI
68
67
  export function StudioApp() {
69
68
  const { projectId, resolving, waitingForServer } = useServerConnection();
70
69
  const initialUrlStateRef = useRef(readStudioUrlStateFromWindow());
71
- const viewModeValue = useViewModeState();
72
70
  useStudioSessionStart(projectId, resolving, waitingForServer);
73
71
  const [compIdToSrc, setCompIdToSrc] = useState<Map<string, string>>(new Map());
74
- const [previewIframe, setPreviewIframe] = useState<HTMLIFrameElement | null>(null);
72
+ const previewIframe = useOwnPreviewIframe();
75
73
  const [compositionLoading, setCompositionLoading] = useState(true);
76
74
  const [refreshKey, setRefreshKey] = useState(0);
77
75
  const [previewDocumentVersion, refreshPreviewDocumentVersion] = usePreviewDocumentVersion();
@@ -327,10 +325,8 @@ export function StudioApp() {
327
325
  effectiveTimelineDuration,
328
326
  });
329
327
  const compositionDimensions = useCompositionDimensions(previewIframeRef);
330
- const { lintModal, linting, handleLint, closeLintModal, findingsByFile } = useLintModal(
331
- projectId,
332
- refreshKey,
333
- );
328
+ const { lintModal, linting, handleLint, closeLintModal, findingsByFile, hasLintError } =
329
+ useLintModal(projectId, refreshKey);
334
330
  const frameCapture = useFrameCapture({
335
331
  projectId,
336
332
  activeCompPath,
@@ -364,7 +360,7 @@ export function StudioApp() {
364
360
  const handlePreviewIframeRef = useCallback(
365
361
  (iframe: HTMLIFrameElement | null) => {
366
362
  previewIframeRef.current = iframe;
367
- setPreviewIframe(iframe);
363
+ usePreviewIframeStore.getState().setIframe(iframe);
368
364
  appHotkeys.syncPreviewHotkeys(iframe);
369
365
  resetConsoleErrors();
370
366
  refreshPreviewDocumentVersion();
@@ -441,144 +437,136 @@ export function StudioApp() {
441
437
  return (
442
438
  <StudioShellProvider value={studioCtxValue}>
443
439
  <StudioPlaybackProvider value={studioCtxValue}>
444
- <ViewModeProvider value={viewModeValue}>
445
- <PanelLayoutProvider value={panelLayout}>
446
- <FileManagerProvider value={fileManager}>
447
- <DomEditProvider value={domEditSession}>
448
- <div
449
- className="flex flex-col h-full w-full bg-neutral-950 relative"
450
- onDragOver={fileDrop.onDragOver}
451
- onDrop={fileDrop.onDrop}
452
- >
453
- <StudioHeader
454
- captureFrameHref={frameCapture.captureFrameHref}
455
- captureFrameFilename={frameCapture.captureFrameFilename}
456
- handleCaptureFrameClick={frameCapture.handleCaptureFrameClick}
457
- refreshCaptureFrameTime={frameCapture.refreshCaptureFrameTime}
458
- capturing={frameCapture.capturing}
459
- inspectorButtonActive={inspectorButtonActive}
460
- inspectorPanelActive={inspectorPanelActive}
461
- onExport={() => {
462
- void (async () => {
463
- await previewPersistence.waitForPendingDomEditSaves();
464
- await renderQueue.startRender(undefined);
465
- })();
466
- }}
440
+ <PanelLayoutProvider value={panelLayout}>
441
+ <FileManagerProvider value={fileManager}>
442
+ <DomEditProvider value={domEditSession}>
443
+ <div
444
+ className="flex flex-col h-full w-full bg-neutral-950 relative"
445
+ onDragOver={fileDrop.onDragOver}
446
+ onDrop={fileDrop.onDrop}
447
+ >
448
+ <StudioHeader
449
+ captureFrameHref={frameCapture.captureFrameHref}
450
+ captureFrameFilename={frameCapture.captureFrameFilename}
451
+ handleCaptureFrameClick={frameCapture.handleCaptureFrameClick}
452
+ refreshCaptureFrameTime={frameCapture.refreshCaptureFrameTime}
453
+ capturing={frameCapture.capturing}
454
+ inspectorButtonActive={inspectorButtonActive}
455
+ inspectorPanelActive={inspectorPanelActive}
456
+ onExport={() => {
457
+ void (async () => {
458
+ await previewPersistence.waitForPendingDomEditSaves();
459
+ await renderQueue.startRender(undefined);
460
+ })();
461
+ }}
462
+ />
463
+ {previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && (
464
+ <SaveQueuePausedBanner
465
+ message={previewPersistence.domEditSaveQueuePaused}
466
+ onRetry={previewPersistence.resetDomEditSaveQueueBreaker}
467
467
  />
468
- {previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && (
469
- <SaveQueuePausedBanner
470
- message={previewPersistence.domEditSaveQueuePaused}
471
- onRetry={previewPersistence.resetDomEditSaveQueueBreaker}
472
- />
473
- )}
474
- <ExternalFileConflictBanner coordinator={externalFileChanges} />
475
- {viewModeValue.viewMode === "storyboard" && (
476
- <StoryboardView
477
- projectId={projectId}
468
+ )}
469
+ <ExternalFileConflictBanner coordinator={externalFileChanges} />
470
+ <EditorShell
471
+ left={
472
+ <StudioLeftSidebar
473
+ leftSidebarRef={leftSidebarRef}
478
474
  onSelectComposition={handleSelectComposition}
475
+ onAddBlock={handleAddBlock}
476
+ onPreviewBlock={setBlockPreview}
477
+ onLint={handleLint}
478
+ linting={linting}
479
+ lintFindingCount={lintModal?.length ?? findingsByFile.size}
480
+ lintFindingsByFile={findingsByFile}
481
+ lintHasError={hasLintError}
482
+ onAddAssetToTimeline={handleAddAssetAtPlayhead}
483
+ onAddCompositionToTimeline={handleAddCompositionAtPlayhead}
479
484
  />
480
- )}
481
- <EditorShell
482
- hidden={viewModeValue.viewMode === "storyboard"}
483
- left={
484
- <StudioLeftSidebar
485
- leftSidebarRef={leftSidebarRef}
486
- onSelectComposition={handleSelectComposition}
487
- onAddBlock={handleAddBlock}
488
- onPreviewBlock={setBlockPreview}
489
- onLint={handleLint}
490
- linting={linting}
491
- lintFindingCount={lintModal?.length ?? findingsByFile.size}
492
- lintFindingsByFile={findingsByFile}
493
- onAddAssetToTimeline={handleAddAssetAtPlayhead}
494
- onAddCompositionToTimeline={handleAddCompositionAtPlayhead}
485
+ }
486
+ right={
487
+ panelLayout.effectiveRightCollapsed ? null : (
488
+ <StudioRightPanel
489
+ designPanelActive={designPanelActive}
490
+ activeBlockParams={activeBlockParams}
491
+ onCloseBlockParams={() => {
492
+ setActiveBlockParams(null);
493
+ panelLayout.setRightPanelTab("design");
494
+ }}
495
+ recordingState={gestureState}
496
+ recordingDuration={gestureRecording.recordingDuration}
497
+ onToggleRecording={handleToggleRecording}
498
+ sdkSession={sdkHandle.session}
499
+ publishSdkSession={sdkHandle.publish}
500
+ forceReloadSdkSession={sdkHandle.forceReload}
501
+ reloadPreview={reloadPreview}
502
+ recordEdit={editHistory.recordEdit}
503
+ onToggleElementHidden={timelineEditing.handleToggleElementHidden}
504
+ onAutoGroupCarveSources={timelineEditing.handleAutoGroupCarveSources}
505
+ onAddMediaOverlay={handleAddMediaOverlay}
495
506
  />
496
- }
497
- right={
498
- panelLayout.effectiveRightCollapsed ? null : (
499
- <StudioRightPanel
500
- designPanelActive={designPanelActive}
501
- activeBlockParams={activeBlockParams}
502
- onCloseBlockParams={() => {
503
- setActiveBlockParams(null);
504
- panelLayout.setRightPanelTab("design");
505
- }}
506
- recordingState={gestureState}
507
- recordingDuration={gestureRecording.recordingDuration}
508
- onToggleRecording={handleToggleRecording}
509
- sdkSession={sdkHandle.session}
510
- publishSdkSession={sdkHandle.publish}
511
- forceReloadSdkSession={sdkHandle.forceReload}
512
- reloadPreview={reloadPreview}
513
- recordEdit={editHistory.recordEdit}
514
- onToggleElementHidden={timelineEditing.handleToggleElementHidden}
515
- onAutoGroupCarveSources={timelineEditing.handleAutoGroupCarveSources}
516
- onAddMediaOverlay={handleAddMediaOverlay}
517
- />
518
- )
519
- }
520
- timelineToolbar={timelineToolbar}
521
- renderClipContent={renderClipContent}
522
- handleTimelineElementDelete={timelineEditing.handleTimelineElementDelete}
523
- handleTimelineAssetDrop={timelineEditing.handleTimelineAssetDrop}
524
- handleTimelineBlockDrop={handleTimelineBlockDrop}
525
- handleTimelineCompositionDrop={timelineEditing.handleTimelineCompositionDrop}
526
- handlePreviewBlockDrop={handlePreviewBlockDrop}
527
- handleTimelineFileDrop={timelineEditing.handleTimelineFileDrop}
528
- handleTimelineElementMove={timelineEditing.handleTimelineElementMove}
529
- handleTimelineElementsMove={handleTimelineElementsMove}
530
- handleTimelineElementResize={timelineEditing.handleTimelineElementResize}
531
- handleTimelineGroupResize={timelineEditing.handleTimelineGroupResize}
532
- handleToggleTrackHidden={timelineEditing.handleToggleTrackHidden}
533
- setAudioGroupAttribute={timelineEditing.setAudioGroupAttribute}
534
- handleGroupClips={timelineEditing.handleAutoGroupCarveSources}
535
- setElementFxAttribute={timelineEditing.setElementFxAttribute}
536
- handleBlockedTimelineEdit={timelineEditing.handleBlockedTimelineEdit}
537
- handleTimelineElementSplit={timelineEditing.handleTimelineElementSplit}
538
- handleRazorSplit={timelineEditing.handleRazorSplit}
539
- handleRazorSplitAll={timelineEditing.handleRazorSplitAll}
540
- onCopyClip={handleCopy}
541
- onPasteClip={handlePaste}
542
- onDuplicateClip={handleDuplicate}
543
- canPasteClip={canPaste}
544
- setCompIdToSrc={setCompIdToSrc}
545
- setCompositionLoading={setCompositionLoading}
546
- shouldShowMotionPath={shouldShowMotionPath}
547
- shouldShowSelectedDomBounds={shouldShowSelectedDomBounds}
548
- isGestureRecording={gestureState === "recording"}
549
- recordingState={gestureState}
550
- onToggleRecording={handleToggleRecording}
551
- blockPreview={blockPreview}
552
- gestureOverlay={
553
- gestureState === "recording" && previewIframe ? (
554
- <GestureTrailOverlay
555
- samples={gestureRecording.samplesRef.current}
556
- sampleCount={gestureRecording.samplesRef.current.length}
557
- trail={gestureRecording.trailRef.current}
558
- canvasRect={canvasRectRef.current!}
559
- compositionSize={compositionDimensions ?? undefined}
560
- mode="recording"
561
- />
562
- ) : undefined
563
- }
564
- />
565
- <StudioOverlays
566
- projectId={projectId}
567
- projectDir={fileManager.projectDir}
568
- lintModal={lintModal}
569
- closeLintModal={closeLintModal}
570
- consoleErrors={consoleErrors}
571
- clearConsoleErrors={() => setConsoleErrors(null)}
572
- domEditSession={domEditSession}
573
- activeCompPath={activeCompPath}
574
- toasts={toasts}
575
- dismissToast={dismissToast}
576
- />
577
- </div>
578
- </DomEditProvider>
579
- </FileManagerProvider>
580
- </PanelLayoutProvider>
581
- </ViewModeProvider>
507
+ )
508
+ }
509
+ timelineToolbar={timelineToolbar}
510
+ renderClipContent={renderClipContent}
511
+ handleTimelineElementDelete={timelineEditing.handleTimelineElementDelete}
512
+ handleTimelineAssetDrop={timelineEditing.handleTimelineAssetDrop}
513
+ handleTimelineBlockDrop={handleTimelineBlockDrop}
514
+ handleTimelineCompositionDrop={timelineEditing.handleTimelineCompositionDrop}
515
+ handlePreviewBlockDrop={handlePreviewBlockDrop}
516
+ handleTimelineFileDrop={timelineEditing.handleTimelineFileDrop}
517
+ handleTimelineElementMove={timelineEditing.handleTimelineElementMove}
518
+ handleTimelineElementsMove={handleTimelineElementsMove}
519
+ handleTimelineElementResize={timelineEditing.handleTimelineElementResize}
520
+ handleTimelineGroupResize={timelineEditing.handleTimelineGroupResize}
521
+ handleToggleTrackHidden={timelineEditing.handleToggleTrackHidden}
522
+ setAudioGroupAttribute={timelineEditing.setAudioGroupAttribute}
523
+ handleGroupClips={timelineEditing.handleAutoGroupCarveSources}
524
+ setElementFxAttribute={timelineEditing.setElementFxAttribute}
525
+ handleBlockedTimelineEdit={timelineEditing.handleBlockedTimelineEdit}
526
+ handleTimelineElementSplit={timelineEditing.handleTimelineElementSplit}
527
+ handleRazorSplit={timelineEditing.handleRazorSplit}
528
+ handleRazorSplitAll={timelineEditing.handleRazorSplitAll}
529
+ onCopyClip={handleCopy}
530
+ onPasteClip={handlePaste}
531
+ onDuplicateClip={handleDuplicate}
532
+ canPasteClip={canPaste}
533
+ setCompIdToSrc={setCompIdToSrc}
534
+ setCompositionLoading={setCompositionLoading}
535
+ shouldShowMotionPath={shouldShowMotionPath}
536
+ shouldShowSelectedDomBounds={shouldShowSelectedDomBounds}
537
+ isGestureRecording={gestureState === "recording"}
538
+ recordingState={gestureState}
539
+ onToggleRecording={handleToggleRecording}
540
+ blockPreview={blockPreview}
541
+ gestureOverlay={
542
+ gestureState === "recording" && previewIframe ? (
543
+ <GestureTrailOverlay
544
+ samples={gestureRecording.samplesRef.current}
545
+ sampleCount={gestureRecording.samplesRef.current.length}
546
+ trail={gestureRecording.trailRef.current}
547
+ canvasRect={canvasRectRef.current!}
548
+ compositionSize={compositionDimensions ?? undefined}
549
+ mode="recording"
550
+ />
551
+ ) : undefined
552
+ }
553
+ />
554
+ <StudioOverlays
555
+ projectId={projectId}
556
+ projectDir={fileManager.projectDir}
557
+ lintModal={lintModal}
558
+ closeLintModal={closeLintModal}
559
+ consoleErrors={consoleErrors}
560
+ clearConsoleErrors={() => setConsoleErrors(null)}
561
+ domEditSession={domEditSession}
562
+ activeCompPath={activeCompPath}
563
+ toasts={toasts}
564
+ dismissToast={dismissToast}
565
+ />
566
+ </div>
567
+ </DomEditProvider>
568
+ </FileManagerProvider>
569
+ </PanelLayoutProvider>
582
570
  </StudioPlaybackProvider>
583
571
  </StudioShellProvider>
584
572
  );
@@ -171,7 +171,7 @@ function AnimationPhase({
171
171
  }}
172
172
  className="flex-1 accent-studio-accent disabled:opacity-40"
173
173
  />
174
- <span className="text-2xs text-neutral-400 font-mono w-8 text-right flex-shrink-0">
174
+ <span className="text-2xs text-neutral-400 font-mono w-8 text-right shrink-0">
175
175
  {intensity.toFixed(2)}
176
176
  </span>
177
177
  </div>
@@ -249,7 +249,7 @@ export const CaptionAnimationPanel = memo(function CaptionAnimationPanel() {
249
249
  return (
250
250
  <div className="flex flex-col h-full min-h-0">
251
251
  {gated && (
252
- <div className="flex-shrink-0 mx-3 mt-2 px-2 py-1.5 rounded border border-amber-500/30 bg-amber-500/10">
252
+ <div className="shrink-0 mx-3 mt-2 px-2 py-1.5 rounded-sm border border-amber-500/30 bg-amber-500/10">
253
253
  <p className="text-2xs text-amber-300/90 leading-snug">
254
254
  Animation editing isn&apos;t applied to playback or saved yet, so these controls are
255
255
  disabled.
@@ -286,13 +286,13 @@ export const CaptionAnimationPanel = memo(function CaptionAnimationPanel() {
286
286
  </div>
287
287
 
288
288
  {/* Footer */}
289
- <div className="flex-shrink-0 px-3 py-2 border-t border-neutral-800">
289
+ <div className="shrink-0 px-3 py-2 border-t border-neutral-800">
290
290
  <button
291
291
  type="button"
292
292
  onClick={handleApplyToAll}
293
293
  disabled={gated}
294
294
  title={gated ? "Disabled until animation editing is applied to playback" : undefined}
295
- className="w-full py-1.5 rounded border border-neutral-700 text-2xs text-neutral-300 hover:border-studio-accent/50 hover:text-studio-accent transition-colors disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:border-neutral-700 disabled:hover:text-neutral-300"
295
+ className="w-full py-1.5 rounded-sm border border-neutral-700 text-2xs text-neutral-300 hover:border-studio-accent/50 hover:text-studio-accent transition-colors disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:border-neutral-700 disabled:hover:text-neutral-300"
296
296
  >
297
297
  Apply to all groups
298
298
  </button>
@@ -4,6 +4,7 @@ import { createRoot } from "react-dom/client";
4
4
  import { expect, it, vi } from "vitest";
5
5
  import { CaptionOverlay } from "./CaptionOverlay";
6
6
  import { useCaptionStore } from "../store";
7
+ import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
7
8
 
8
9
  Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true);
9
10
 
@@ -45,3 +46,54 @@ it("only schedules caption layout for messages from its preview", () => {
45
46
  vi.unstubAllGlobals();
46
47
  }
47
48
  });
49
+
50
+ it("observes and accepts messages from the promoted iframe, not the old one", () => {
51
+ const host = document.createElement("div");
52
+ const a = document.createElement("iframe");
53
+ const b = document.createElement("iframe");
54
+ document.body.append(host, a, b);
55
+ const ref = { current: a };
56
+ const root = createRoot(host);
57
+ const schedule = vi.fn(() => 1);
58
+ const observed: Element[] = [];
59
+ vi.stubGlobal("requestAnimationFrame", schedule);
60
+ vi.stubGlobal("cancelAnimationFrame", vi.fn());
61
+ vi.stubGlobal(
62
+ "ResizeObserver",
63
+ class {
64
+ observe(el: Element) {
65
+ observed.push(el);
66
+ }
67
+ disconnect() {}
68
+ },
69
+ );
70
+ useCaptionStore.getState().setEditMode(true);
71
+ const send = (source: MessageEventSource | null) =>
72
+ act(() => {
73
+ window.dispatchEvent(new MessageEvent("message", { source, data: { source: "hf-preview" } }));
74
+ });
75
+ try {
76
+ act(() => usePreviewIframeStore.getState().setIframe(a));
77
+ act(() => root.render(<CaptionOverlay iframeRef={ref} />));
78
+ expect(observed).toContain(a);
79
+ expect(observed).not.toContain(b);
80
+ act(() => {
81
+ ref.current = b;
82
+ usePreviewIframeStore.getState().setIframe(b);
83
+ });
84
+ expect(observed).toContain(b);
85
+ schedule.mockClear();
86
+ send(a.contentWindow);
87
+ expect(schedule).not.toHaveBeenCalled();
88
+ send(b.contentWindow);
89
+ expect(schedule).toHaveBeenCalledOnce();
90
+ } finally {
91
+ act(() => root.unmount());
92
+ usePreviewIframeStore.setState({ iframe: null });
93
+ useCaptionStore.getState().reset();
94
+ host.remove();
95
+ a.remove();
96
+ b.remove();
97
+ vi.unstubAllGlobals();
98
+ }
99
+ });
@@ -1,7 +1,7 @@
1
- import { memo, useState, useCallback, useRef } from "react";
1
+ import { memo, useEffect, useState, useCallback, useRef } from "react";
2
+ import { useLivePreviewIframe } from "../../player/store/previewIframeStore";
2
3
  import { useCaptionStore } from "../store";
3
4
  import { usePlayerStore } from "../../player";
4
- import { useMountEffect } from "../../hooks/useMountEffect";
5
5
  import { shouldHandleCaptionNudgeKey, isEditableEventTarget } from "../keyboard";
6
6
  import {
7
7
  readWordBoxes,
@@ -32,6 +32,7 @@ function syncToStore(segmentId: string, el: HTMLElement, iframeWin: Window) {
32
32
 
33
33
  export const CaptionOverlay = memo(function CaptionOverlay({ iframeRef }: CaptionOverlayProps) {
34
34
  const isEditMode = useCaptionStore((s) => s.isEditMode);
35
+ const livePreviewIframe = useLivePreviewIframe();
35
36
  const model = useCaptionStore((s) => s.model);
36
37
  const selectedSegmentIds = useCaptionStore((s) => s.selectedSegmentIds);
37
38
  const selectSegment = useCaptionStore((s) => s.selectSegment);
@@ -81,7 +82,9 @@ export const CaptionOverlay = memo(function CaptionOverlay({ iframeRef }: Captio
81
82
  | null
82
83
  >(null);
83
84
 
84
- useMountEffect(() => {
85
+ // The observer and message filter bind to the live iframe element, which a promoted reload replaces.
86
+ // eslint-disable-next-line no-restricted-syntax
87
+ useEffect(() => {
85
88
  if (!isEditMode) return;
86
89
 
87
90
  // Let undo/redo (useAppHotkeys) reapply restored models to this iframe.
@@ -240,7 +243,8 @@ export const CaptionOverlay = memo(function CaptionOverlay({ iframeRef }: Captio
240
243
  window.removeEventListener("keydown", handleKeyDown);
241
244
  cancelDragRef.current = null;
242
245
  };
243
- });
246
+ // eslint-disable-next-line react-hooks/exhaustive-deps
247
+ }, [livePreviewIframe]);
244
248
 
245
249
  const getCssScale = useCallback(() => {
246
250
  const iframe = iframeRef.current;
@@ -221,9 +221,8 @@ export function registerCaptionIframe(ref: React.RefObject<HTMLIFrameElement | n
221
221
 
222
222
  /**
223
223
  * True when the caption preview iframe is mounted AND visible. The caption
224
- * store's isEditMode stays true while the preview is merely hidden (e.g.
225
- * storyboard view), so hotkeys must not route ⌘Z to the caption stack unless
226
- * the user can actually see the captions the undo would change.
224
+ * store's isEditMode stays true while the preview is merely hidden, so hotkeys must not
225
+ * route ⌘Z to the caption stack unless the user can see the captions the undo would change.
227
226
  */
228
227
  export function isCaptionPreviewVisible(): boolean {
229
228
  const iframe = registeredIframe?.current;
@@ -213,7 +213,7 @@ export const CaptionPropertyPanel = memo(function CaptionPropertyPanel({
213
213
  return (
214
214
  <div className="flex flex-col h-full min-h-0">
215
215
  {/* Header */}
216
- <div className="px-3 py-2 border-b border-neutral-800 flex-shrink-0">
216
+ <div className="px-3 py-2 border-b border-neutral-800 shrink-0">
217
217
  <div className="flex items-center justify-between mb-1.5">
218
218
  <span className="text-2xs text-neutral-500">{countLabel}</span>
219
219
  </div>
@@ -225,7 +225,7 @@ export const CaptionPropertyPanel = memo(function CaptionPropertyPanel({
225
225
  aria-selected={activeTab === "style"}
226
226
  onClick={() => setActiveTab("style")}
227
227
  className={[
228
- "flex-1 py-0.5 rounded text-2xs font-medium transition-colors",
228
+ "flex-1 py-0.5 rounded-sm text-2xs font-medium transition-colors",
229
229
  activeTab === "style"
230
230
  ? "bg-studio-accent/20 text-studio-accent border border-studio-accent/50"
231
231
  : "text-neutral-500 border border-neutral-800 hover:text-neutral-300 hover:border-neutral-600",
@@ -239,7 +239,7 @@ export const CaptionPropertyPanel = memo(function CaptionPropertyPanel({
239
239
  aria-selected={activeTab === "animation"}
240
240
  onClick={() => setActiveTab("animation")}
241
241
  className={[
242
- "flex-1 py-0.5 rounded text-2xs font-medium transition-colors",
242
+ "flex-1 py-0.5 rounded-sm text-2xs font-medium transition-colors",
243
243
  activeTab === "animation"
244
244
  ? "bg-studio-accent/20 text-studio-accent border border-studio-accent/50"
245
245
  : "text-neutral-500 border border-neutral-800 hover:text-neutral-300 hover:border-neutral-600",
@@ -109,7 +109,7 @@ export const CaptionTimeline = memo(function CaptionTimeline({
109
109
  tabIndex={0}
110
110
  aria-label={`Caption word "${seg.text}"`}
111
111
  aria-pressed={isSelected}
112
- className={`absolute top-1 bottom-1 rounded flex items-center overflow-hidden cursor-pointer focus-visible:ring-1 focus-visible:ring-white outline-none${
112
+ className={`absolute top-1 bottom-1 rounded flex items-center overflow-hidden cursor-pointer focus-visible:ring-1 focus-visible:ring-white outline-hidden${
113
113
  isSelected ? " ring-1 ring-white/50 z-10" : ""
114
114
  }`}
115
115
  style={{
@@ -2,7 +2,7 @@ import type React from "react";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
 
4
4
  export const inputCls =
5
- "w-full bg-neutral-900 border border-neutral-800 rounded px-1.5 py-0.5 text-2xs text-neutral-200 font-mono outline-none focus:border-studio-accent disabled:opacity-40 disabled:cursor-not-allowed";
5
+ "w-full bg-neutral-900 border border-neutral-800 rounded-sm px-1.5 py-0.5 text-2xs text-neutral-200 font-mono outline-hidden focus:border-studio-accent disabled:opacity-40 disabled:cursor-not-allowed";
6
6
 
7
7
  export function Section({ label, children }: { label: string; children: React.ReactNode }) {
8
8
  return (
@@ -20,7 +20,7 @@ export function Section({ label, children }: { label: string; children: React.Re
20
20
  export function Row({ label, children }: { label: string; children: React.ReactNode }) {
21
21
  return (
22
22
  <div className="flex items-center gap-2">
23
- <span className="text-2xs text-neutral-600 w-14 text-right flex-shrink-0">{label}</span>
23
+ <span className="text-2xs text-neutral-600 w-14 text-right shrink-0">{label}</span>
24
24
  <div className="flex-1 min-w-0">{children}</div>
25
25
  </div>
26
26
  );