@hyperframes/studio 0.7.58 → 0.7.60
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.
- package/dist/assets/{hyperframes-player-CtTDO63S.js → hyperframes-player-3XTTaVNf.js} +1 -1
- package/dist/assets/{index-C47jAC3Q.js → index-D6etaey-.js} +1 -1
- package/dist/assets/index-DXbu6IPT.css +1 -0
- package/dist/assets/{index-DeQPzqwH.js → index-Dh_WhagG.js} +1 -1
- package/dist/assets/index-cH6NfVV_.js +426 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.html +2 -2
- package/dist/index.js +13755 -8627
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.d.ts +5 -0
- package/dist/styles/tailwind-preset.js +8 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +4 -4
- package/src/components/DesignPanelPromoteProvider.tsx +15 -2
- package/src/components/StudioRightPanel.tsx +42 -53
- package/src/components/editor/AnimationCard.test.tsx +134 -0
- package/src/components/editor/AnimationCard.tsx +19 -4
- package/src/components/editor/ArcPathControls.tsx +1 -0
- package/src/components/editor/GestureRecordControl.tsx +7 -1
- package/src/components/editor/GsapAnimationSection.tsx +109 -14
- package/src/components/editor/InspectorHeaderActions.tsx +42 -4
- package/src/components/editor/PropertyPanel.test.tsx +948 -0
- package/src/components/editor/PropertyPanel.tsx +144 -148
- package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
- package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
- package/src/components/editor/PropertyPanelFlat.tsx +580 -0
- package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
- package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
- package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
- package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
- package/src/components/editor/gsapLivePreview.ts +34 -0
- package/src/components/editor/manualEditingAvailability.test.ts +10 -0
- package/src/components/editor/manualEditingAvailability.ts +9 -0
- package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
- package/src/components/editor/propertyPanelColor.test.tsx +28 -0
- package/src/components/editor/propertyPanelColor.tsx +31 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
- package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
- package/src/components/editor/propertyPanelFill.tsx +22 -5
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
- package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
- package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +107 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
- package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
- package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
- package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
- package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
- package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
- package/src/components/editor/propertyPanelFont.test.tsx +30 -0
- package/src/components/editor/propertyPanelFont.tsx +136 -95
- package/src/components/editor/propertyPanelHelpers.ts +73 -0
- package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
- package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
- package/src/components/editor/propertyPanelSections.test.tsx +161 -0
- package/src/components/editor/propertyPanelSections.tsx +154 -90
- package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
- package/src/components/editor/propertyPanelTypes.ts +14 -1
- package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
- package/src/components/editor/propertyPanelValueTier.ts +28 -0
- package/src/components/editor/useColorGradingController.test.ts +418 -0
- package/src/components/editor/useColorGradingController.ts +596 -0
- package/src/components/panels/VariablesPanel.tsx +4 -0
- package/src/components/sidebar/CompositionsTab.test.ts +28 -2
- package/src/components/sidebar/CompositionsTab.tsx +3 -1
- package/src/components/storyboard/FramePoster.tsx +24 -4
- package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
- package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
- package/src/components/storyboard/StoryboardGrid.tsx +23 -1
- package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
- package/src/components/storyboard/StoryboardView.tsx +9 -2
- package/src/components/storyboard/frameComments.test.ts +95 -0
- package/src/components/storyboard/frameComments.ts +115 -0
- package/src/components/storyboard/useFrameComments.ts +82 -0
- package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
- package/src/contexts/DesignPanelInputContext.tsx +48 -0
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/contexts/FileManagerContext.tsx +3 -0
- package/src/contexts/PanelLayoutContext.tsx +3 -0
- package/src/contexts/VariablePromoteContext.test.tsx +67 -0
- package/src/contexts/VariablePromoteContext.tsx +10 -5
- package/src/hooks/domEditCommitRunner.ts +11 -0
- package/src/hooks/gsapScriptCommitTypes.ts +6 -4
- package/src/hooks/timelineEditingHelpers.ts +2 -2
- package/src/hooks/timelineTrackVisibility.test.ts +52 -0
- package/src/hooks/timelineTrackVisibility.ts +27 -10
- package/src/hooks/useAppHotkeys.ts +9 -0
- package/src/hooks/useDomEditAttributeCommits.ts +123 -1
- package/src/hooks/useDomEditCommits.test.tsx +41 -1
- package/src/hooks/useDomEditCommits.ts +31 -177
- package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
- package/src/hooks/useDomEditSession.ts +18 -6
- package/src/hooks/useDomEditTextCommits.ts +14 -9
- package/src/hooks/useDomEditWiring.ts +10 -9
- package/src/hooks/useEditorSave.ts +1 -1
- package/src/hooks/useElementLifecycleOps.ts +8 -3
- package/src/hooks/useFileManager.projectOwnership.test.tsx +109 -0
- package/src/hooks/useFileManager.ts +151 -65
- package/src/hooks/useGsapAnimationOps.ts +7 -6
- package/src/hooks/useGsapKeyframeOps.ts +6 -5
- package/src/hooks/useGsapPropertyDebounce.ts +43 -28
- package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
- package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
- package/src/hooks/useGsapScriptCommits.ts +150 -50
- package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
- package/src/hooks/useGsapSelectionHandlers.ts +94 -31
- package/src/hooks/useInspectorSplitResize.ts +51 -0
- package/src/hooks/usePanelLayout.test.ts +118 -0
- package/src/hooks/usePanelLayout.ts +24 -1
- package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
- package/src/hooks/usePersistentEditHistory.test.ts +49 -1
- package/src/hooks/usePersistentEditHistory.ts +71 -31
- package/src/hooks/usePreviewPersistence.ts +4 -1
- package/src/hooks/useProjectCompositionVariables.ts +10 -10
- package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
- package/src/hooks/useProjectSignaturePoll.ts +68 -0
- package/src/hooks/useRazorSplit.history.test.tsx +12 -4
- package/src/hooks/useRazorSplit.test.tsx +88 -0
- package/src/hooks/useRazorSplit.testHelpers.ts +3 -2
- package/src/hooks/useRazorSplit.ts +50 -16
- package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
- package/src/hooks/useSdkSession.ts +155 -14
- package/src/hooks/useSlideshowPersist.ts +7 -1
- package/src/hooks/useStoryboard.ts +23 -4
- package/src/hooks/useTimelineEditing.test.tsx +4 -1
- package/src/hooks/useTimelineEditing.ts +18 -10
- package/src/hooks/useTimelineEditingTypes.ts +5 -1
- package/src/hooks/useTimelineGroupEditing.ts +17 -3
- package/src/hooks/useVariablesPersist.ts +9 -7
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/styles/studio.css +22 -0
- package/src/styles/tailwind-preset.shared.js +7 -0
- package/src/utils/designInputTracking.test.ts +97 -0
- package/src/utils/designInputTracking.ts +80 -0
- package/src/utils/domEditSaveQueue.test.ts +19 -0
- package/src/utils/domEditSaveQueue.ts +2 -1
- package/src/utils/sdkCutover.gate.test.ts +7 -7
- package/src/utils/sdkCutover.test.ts +511 -62
- package/src/utils/sdkCutover.ts +181 -219
- package/src/utils/sdkEditTransaction.ts +293 -0
- package/src/utils/sdkResolverAttempts.ts +94 -0
- package/src/utils/sdkResolverShadow.test.ts +148 -0
- package/src/utils/sdkResolverShadow.ts +139 -106
- package/src/utils/setSlideshowManifest.test.ts +0 -9
- package/src/utils/setSlideshowManifest.ts +20 -27
- package/src/utils/studioFileHistory.test.ts +41 -0
- package/src/utils/studioFileHistory.ts +34 -29
- package/src/utils/studioFileMutationCoordinator.ts +45 -0
- package/src/utils/studioFileVersion.test.ts +36 -0
- package/src/utils/studioFileVersion.ts +23 -0
- package/src/utils/studioHelpers.test.ts +45 -0
- package/src/utils/studioHelpers.ts +45 -14
- package/src/utils/studioSaveDiagnostics.test.ts +18 -0
- package/src/utils/studioSaveDiagnostics.ts +38 -0
- package/dist/assets/index-B_UvTX3E.js +0 -423
- package/dist/assets/index-uahwWkgw.css +0 -1
|
@@ -7,7 +7,7 @@ import type { RightPanelTab } from "../utils/studioHelpers";
|
|
|
7
7
|
import type { PatchTarget } from "../utils/sourcePatcher";
|
|
8
8
|
import type { SidebarTab } from "../components/sidebar/LeftSidebar";
|
|
9
9
|
import type { Composition } from "@hyperframes/sdk";
|
|
10
|
-
import { sdkCutoverPersist, sdkDeletePersist } from "../utils/sdkCutover";
|
|
10
|
+
import { sdkCutoverPersist, sdkDeletePersist, type PublishSdkSession } from "../utils/sdkCutover";
|
|
11
11
|
import { runResolverShadow, recordResolverParity } from "../utils/sdkResolverShadow";
|
|
12
12
|
import { useAskAgentModal } from "./useAskAgentModal";
|
|
13
13
|
import { useDomSelection } from "./useDomSelection";
|
|
@@ -45,7 +45,7 @@ export interface UseDomEditSessionParams {
|
|
|
45
45
|
refreshPreviewDocumentVersion: () => void;
|
|
46
46
|
queueDomEditSave: <T>(save: () => Promise<T>) => Promise<T>;
|
|
47
47
|
readProjectFile: (path: string) => Promise<string>;
|
|
48
|
-
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
48
|
+
writeProjectFile: (path: string, content: string, expectedContent?: string) => Promise<void>;
|
|
49
49
|
updateEditingFileContent: (path: string, content: string) => void;
|
|
50
50
|
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
51
51
|
editHistory: { recordEdit: (entry: RecordEditInput) => Promise<void> };
|
|
@@ -67,6 +67,7 @@ export interface UseDomEditSessionParams {
|
|
|
67
67
|
selectSidebarTab?: (tab: SidebarTab) => void;
|
|
68
68
|
getSidebarTab?: () => SidebarTab;
|
|
69
69
|
sdkSession?: Composition | null;
|
|
70
|
+
publishSdkSession?: PublishSdkSession;
|
|
70
71
|
forceReloadSdkSession?: () => void;
|
|
71
72
|
}
|
|
72
73
|
|
|
@@ -108,10 +109,10 @@ export function useDomEditSession({
|
|
|
108
109
|
selectSidebarTab,
|
|
109
110
|
getSidebarTab,
|
|
110
111
|
sdkSession,
|
|
112
|
+
publishSdkSession,
|
|
111
113
|
forceReloadSdkSession,
|
|
112
114
|
}: UseDomEditSessionParams) {
|
|
113
115
|
void _setRefreshKey;
|
|
114
|
-
|
|
115
116
|
// ── Selection ──
|
|
116
117
|
|
|
117
118
|
const {
|
|
@@ -178,7 +179,6 @@ export function useDomEditSession({
|
|
|
178
179
|
previewDocumentVersion,
|
|
179
180
|
refreshDomEditSelectionFromPreview,
|
|
180
181
|
});
|
|
181
|
-
|
|
182
182
|
// ── GSAP cache (hoisted so both useGsapScriptCommits and useDomEditWiring share the same instance) ──
|
|
183
183
|
|
|
184
184
|
const { version: gsapCacheVersion, bump: bumpGsapCache } = useGsapCacheVersion();
|
|
@@ -217,6 +217,7 @@ export function useDomEditSession({
|
|
|
217
217
|
onFileContentChanged: updateEditingFileContent,
|
|
218
218
|
showToast,
|
|
219
219
|
sdkSession,
|
|
220
|
+
publishSdkSession,
|
|
220
221
|
writeProjectFile,
|
|
221
222
|
forceReloadSdkSession,
|
|
222
223
|
});
|
|
@@ -229,6 +230,7 @@ export function useDomEditSession({
|
|
|
229
230
|
handleDomAttributeCommit,
|
|
230
231
|
handleDomAttributeLiveCommit,
|
|
231
232
|
handleDomHtmlAttributeCommit,
|
|
233
|
+
handleDomAttributesCommit,
|
|
232
234
|
handleDomTextCommit,
|
|
233
235
|
handleDomTextFieldStyleCommit,
|
|
234
236
|
handleDomAddTextField,
|
|
@@ -260,8 +262,13 @@ export function useDomEditSession({
|
|
|
260
262
|
? (selection, operations, originalContent, targetPath, options) => {
|
|
261
263
|
// Resolver shadow runs regardless of the cutover flag — decoupled tripwire.
|
|
262
264
|
// Pass originalContent so the runtime-node filter can suppress hf-ids
|
|
263
|
-
// absent from source (script-created nodes the SDK can't model)
|
|
264
|
-
|
|
265
|
+
// absent from source (script-created nodes the SDK can't model), and
|
|
266
|
+
// the paths so cross-file edits (session models only the active comp)
|
|
267
|
+
// skip instead of emitting structural element_not_found noise.
|
|
268
|
+
runResolverShadow(sdkSession, selection.hfId, operations, originalContent, {
|
|
269
|
+
targetPath,
|
|
270
|
+
compositionPath: activeCompPath,
|
|
271
|
+
});
|
|
265
272
|
return sdkCutoverPersist(
|
|
266
273
|
selection,
|
|
267
274
|
operations,
|
|
@@ -274,6 +281,8 @@ export function useDomEditSession({
|
|
|
274
281
|
reloadPreview,
|
|
275
282
|
domEditSaveTimestampRef,
|
|
276
283
|
compositionPath: activeCompPath,
|
|
284
|
+
readProjectFile,
|
|
285
|
+
publishSession: publishSdkSession,
|
|
277
286
|
},
|
|
278
287
|
options,
|
|
279
288
|
);
|
|
@@ -287,6 +296,8 @@ export function useDomEditSession({
|
|
|
287
296
|
reloadPreview,
|
|
288
297
|
domEditSaveTimestampRef,
|
|
289
298
|
compositionPath: activeCompPath,
|
|
299
|
+
readProjectFile,
|
|
300
|
+
publishSession: publishSdkSession,
|
|
290
301
|
})
|
|
291
302
|
: undefined,
|
|
292
303
|
// Resolver shadow for the z-index reorder edit: it takes the server path (no
|
|
@@ -524,6 +535,7 @@ export function useDomEditSession({
|
|
|
524
535
|
handleDomAttributeCommit,
|
|
525
536
|
handleDomAttributeLiveCommit,
|
|
526
537
|
handleDomHtmlAttributeCommit,
|
|
538
|
+
handleDomAttributesCommit,
|
|
527
539
|
handleDomPathOffsetCommit: handleGsapAwarePathOffsetCommit,
|
|
528
540
|
handleDomGroupPathOffsetCommit: handleGsapAwareGroupPathOffsetCommit,
|
|
529
541
|
handleDomZIndexReorderCommit,
|
|
@@ -145,15 +145,19 @@ export function useDomEditTextCommits({
|
|
|
145
145
|
const domTextCommitVersionRef = useRef(0);
|
|
146
146
|
const domStyleCommitVersionRef = useRef(new Map<string, number>());
|
|
147
147
|
|
|
148
|
-
const {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
148
|
+
const {
|
|
149
|
+
handleDomAttributeCommit,
|
|
150
|
+
handleDomAttributeLiveCommit,
|
|
151
|
+
handleDomHtmlAttributeCommit,
|
|
152
|
+
handleDomAttributesCommit,
|
|
153
|
+
} = useDomEditAttributeCommits({
|
|
154
|
+
activeCompPath,
|
|
155
|
+
previewIframeRef,
|
|
156
|
+
showToast,
|
|
157
|
+
domEditSelection,
|
|
158
|
+
refreshDomEditSelectionFromPreview,
|
|
159
|
+
persistDomEditOperations,
|
|
160
|
+
});
|
|
157
161
|
|
|
158
162
|
const handleDomStyleCommit = useCallback(
|
|
159
163
|
async (property: string, value: string) => {
|
|
@@ -471,6 +475,7 @@ export function useDomEditTextCommits({
|
|
|
471
475
|
handleDomAttributeCommit,
|
|
472
476
|
handleDomAttributeLiveCommit,
|
|
473
477
|
handleDomHtmlAttributeCommit,
|
|
478
|
+
handleDomAttributesCommit,
|
|
474
479
|
handleDomTextCommit,
|
|
475
480
|
commitDomTextFields,
|
|
476
481
|
handleDomTextFieldStyleCommit,
|
|
@@ -55,24 +55,24 @@ export interface UseDomEditWiringParams {
|
|
|
55
55
|
sel: DomEditSelection,
|
|
56
56
|
animId: string,
|
|
57
57
|
updates: { duration?: number; ease?: string; position?: number },
|
|
58
|
-
) => void
|
|
59
|
-
deleteGsapAnimation: (sel: DomEditSelection, animId: string) => void
|
|
60
|
-
deleteAllForSelector: (sel: DomEditSelection, targetSelector: string) => void
|
|
58
|
+
) => Promise<void>;
|
|
59
|
+
deleteGsapAnimation: (sel: DomEditSelection, animId: string) => Promise<void>;
|
|
60
|
+
deleteAllForSelector: (sel: DomEditSelection, targetSelector: string) => Promise<void>;
|
|
61
61
|
addGsapAnimation: (
|
|
62
62
|
sel: DomEditSelection,
|
|
63
63
|
method: "to" | "from" | "set" | "fromTo",
|
|
64
64
|
time: number,
|
|
65
65
|
) => Promise<void>;
|
|
66
|
-
addGsapProperty: (sel: DomEditSelection, animId: string, prop: string) => void
|
|
67
|
-
removeGsapProperty: (sel: DomEditSelection, animId: string, prop: string) => void
|
|
66
|
+
addGsapProperty: (sel: DomEditSelection, animId: string, prop: string) => Promise<void>;
|
|
67
|
+
removeGsapProperty: (sel: DomEditSelection, animId: string, prop: string) => Promise<void>;
|
|
68
68
|
updateGsapFromProperty: (
|
|
69
69
|
sel: DomEditSelection,
|
|
70
70
|
animId: string,
|
|
71
71
|
prop: string,
|
|
72
72
|
value: number | string,
|
|
73
|
-
) => void
|
|
74
|
-
addGsapFromProperty: (sel: DomEditSelection, animId: string, prop: string) => void
|
|
75
|
-
removeGsapFromProperty: (sel: DomEditSelection, animId: string, prop: string) => void
|
|
73
|
+
) => Promise<void>;
|
|
74
|
+
addGsapFromProperty: (sel: DomEditSelection, animId: string, prop: string) => Promise<void>;
|
|
75
|
+
removeGsapFromProperty: (sel: DomEditSelection, animId: string, prop: string) => Promise<void>;
|
|
76
76
|
addKeyframe: (
|
|
77
77
|
sel: DomEditSelection,
|
|
78
78
|
animId: string,
|
|
@@ -105,7 +105,7 @@ export interface UseDomEditWiringParams {
|
|
|
105
105
|
animId: string,
|
|
106
106
|
resolvedFromValues?: Record<string, number | string>,
|
|
107
107
|
) => Promise<void>;
|
|
108
|
-
removeAllKeyframes: (sel: DomEditSelection, animId: string) => void
|
|
108
|
+
removeAllKeyframes: (sel: DomEditSelection, animId: string) => Promise<void>;
|
|
109
109
|
handleDomManualEditsReset: (sel: DomEditSelection) => void;
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -245,6 +245,7 @@ export function useDomEditWiring({
|
|
|
245
245
|
removeAllKeyframes,
|
|
246
246
|
handleDomManualEditsReset,
|
|
247
247
|
selectedGsapAnimations,
|
|
248
|
+
showToast,
|
|
248
249
|
});
|
|
249
250
|
|
|
250
251
|
// ── Preview sync side-effects ──
|
|
@@ -14,7 +14,7 @@ interface UseEditorSaveOptions {
|
|
|
14
14
|
editingPathRef: React.RefObject<string | undefined>;
|
|
15
15
|
projectIdRef: React.RefObject<string | null>;
|
|
16
16
|
readProjectFile: (path: string) => Promise<string>;
|
|
17
|
-
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
17
|
+
writeProjectFile: (path: string, content: string, expectedContent?: string) => Promise<void>;
|
|
18
18
|
recordEdit: (input: RecordEditInput) => Promise<void>;
|
|
19
19
|
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
20
20
|
setRefreshKey: React.Dispatch<React.SetStateAction<number>>;
|
|
@@ -20,10 +20,15 @@ import {
|
|
|
20
20
|
type LayerRevealCommitOwnership,
|
|
21
21
|
} from "../components/editor/useLayerRevealOverride";
|
|
22
22
|
import type { CommitDomEditPatchBatches, DomEditPatchBatch } from "./domEditCommitTypes";
|
|
23
|
+
import { cutoverCommittedOrThrow, type CutoverResult } from "../utils/sdkCutover";
|
|
23
24
|
|
|
24
25
|
interface UseElementLifecycleOpsParams extends DomEditCommitBaseParams {
|
|
25
|
-
/** Route delete through SDK when session resolves the hf-id
|
|
26
|
-
onTrySdkDelete?: (
|
|
26
|
+
/** Route delete through SDK when session resolves the hf-id. */
|
|
27
|
+
onTrySdkDelete?: (
|
|
28
|
+
hfId: string,
|
|
29
|
+
originalContent: string,
|
|
30
|
+
targetPath: string,
|
|
31
|
+
) => Promise<CutoverResult>;
|
|
27
32
|
/** Resolver-shadow tripwire for the reordered targets (telemetry-only, decoupled from cutover). */
|
|
28
33
|
onReorderShadow?: (targets: string[]) => void;
|
|
29
34
|
/** Resync the SDK session after a server-fallback delete. */
|
|
@@ -97,7 +102,7 @@ export function useElementLifecycleOps({
|
|
|
97
102
|
|
|
98
103
|
if (onTrySdkDelete && selection.hfId) {
|
|
99
104
|
const handled = await onTrySdkDelete(selection.hfId, originalContent, targetPath);
|
|
100
|
-
if (handled) {
|
|
105
|
+
if (cutoverCommittedOrThrow(handled)) {
|
|
101
106
|
clearDomSelection();
|
|
102
107
|
usePlayerStore.getState().setSelectedElementId(null);
|
|
103
108
|
showToast(`Deleted ${label}. Use Undo to restore it.`, "info");
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { act } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
vi.mock("./useFileTree", () => ({
|
|
7
|
+
useFileTree: () => ({
|
|
8
|
+
projectDir: "",
|
|
9
|
+
fileTree: [],
|
|
10
|
+
setFileTree: vi.fn(),
|
|
11
|
+
fileTreeLoaded: true,
|
|
12
|
+
refreshFileTree: vi.fn(async () => {}),
|
|
13
|
+
compositions: [],
|
|
14
|
+
assets: [],
|
|
15
|
+
fontAssets: [],
|
|
16
|
+
}),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock("./useEditorSave", () => ({
|
|
20
|
+
useEditorSave: () => ({
|
|
21
|
+
saveRafRef: { current: null },
|
|
22
|
+
handleContentChange: vi.fn(),
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
import { useFileManager } from "./useFileManager";
|
|
27
|
+
|
|
28
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
29
|
+
|
|
30
|
+
describe("useFileManager project ownership", () => {
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
vi.unstubAllGlobals();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("keeps delayed callbacks bound to the project that created them", async () => {
|
|
36
|
+
let resolveProjectARead: ((value: Response) => void) | undefined;
|
|
37
|
+
const projectARead = new Promise<Response>((resolve) => {
|
|
38
|
+
resolveProjectARead = resolve;
|
|
39
|
+
});
|
|
40
|
+
const fetchMock = vi.fn((url: string, init?: RequestInit) => {
|
|
41
|
+
if (url.endsWith("/files/missing.html") && !init?.method) {
|
|
42
|
+
return Promise.resolve({ ok: false, status: 404 } as Response);
|
|
43
|
+
}
|
|
44
|
+
if (url.includes("project-a") && !init?.method) return projectARead;
|
|
45
|
+
if (!init?.method) {
|
|
46
|
+
return Promise.resolve({
|
|
47
|
+
ok: true,
|
|
48
|
+
json: async () => ({ content: "PROJECT_B", version: "b-v1" }),
|
|
49
|
+
} as Response);
|
|
50
|
+
}
|
|
51
|
+
return Promise.resolve({ ok: true, json: async () => ({ version: "a-v2" }) } as Response);
|
|
52
|
+
});
|
|
53
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
54
|
+
|
|
55
|
+
const captured: { manager: ReturnType<typeof useFileManager> | null } = { manager: null };
|
|
56
|
+
function Probe({ projectId }: { projectId: string }) {
|
|
57
|
+
captured.manager = useFileManager({
|
|
58
|
+
projectId,
|
|
59
|
+
showToast: vi.fn(),
|
|
60
|
+
recordEdit: vi.fn(async () => {}),
|
|
61
|
+
domEditSaveTimestampRef: { current: 0 },
|
|
62
|
+
setRefreshKey: vi.fn(),
|
|
63
|
+
});
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const root = createRoot(document.createElement("div"));
|
|
68
|
+
await act(async () => root.render(<Probe projectId="project-a/../other?x=1" />));
|
|
69
|
+
const managerA = captured.manager;
|
|
70
|
+
if (!managerA) throw new Error("project A manager did not render");
|
|
71
|
+
const delayedRead = managerA.readProjectFile("index.html");
|
|
72
|
+
|
|
73
|
+
await act(async () => root.render(<Probe projectId="project-b#fragment" />));
|
|
74
|
+
const managerB = captured.manager;
|
|
75
|
+
if (!managerB) throw new Error("project B manager did not render");
|
|
76
|
+
expect(managerB.writeProjectFile).not.toBe(managerA.writeProjectFile);
|
|
77
|
+
|
|
78
|
+
resolveProjectARead?.({
|
|
79
|
+
ok: true,
|
|
80
|
+
json: async () => ({ content: "PROJECT_A", version: "a-v1" }),
|
|
81
|
+
} as Response);
|
|
82
|
+
await expect(delayedRead).resolves.toBe("PROJECT_A");
|
|
83
|
+
await managerA.writeProjectFile("index.html", "A_AFTER");
|
|
84
|
+
await managerA.writeProjectFile("missing.html", "A_NEW");
|
|
85
|
+
await expect(managerB.readProjectFile("index.html")).resolves.toBe("PROJECT_B");
|
|
86
|
+
await expect(managerB.readOptionalProjectFile("index.html")).resolves.toBe("PROJECT_B");
|
|
87
|
+
|
|
88
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
89
|
+
"/api/projects/project-a%2F..%2Fother%3Fx%3D1/files/index.html",
|
|
90
|
+
);
|
|
91
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
92
|
+
"/api/projects/project-a%2F..%2Fother%3Fx%3D1/files/index.html",
|
|
93
|
+
expect.objectContaining({ method: "PUT", body: "A_AFTER" }),
|
|
94
|
+
);
|
|
95
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
96
|
+
"/api/projects/project-a%2F..%2Fother%3Fx%3D1/files/missing.html",
|
|
97
|
+
);
|
|
98
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
99
|
+
"/api/projects/project-a%2F..%2Fother%3Fx%3D1/files/missing.html",
|
|
100
|
+
expect.objectContaining({ method: "PUT", body: "A_NEW" }),
|
|
101
|
+
);
|
|
102
|
+
expect(fetchMock).toHaveBeenCalledWith("/api/projects/project-b%23fragment/files/index.html");
|
|
103
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
104
|
+
"/api/projects/project-b%23fragment/files/index.html?optional=1",
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
await act(async () => root.unmount());
|
|
108
|
+
});
|
|
109
|
+
});
|