@hyperframes/studio 0.8.34 → 0.8.36
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-D4aSRr_M.js → hyperframes-player-BIpJmC69.js} +1 -1
- package/dist/assets/index-BmPeyP0k.js +441 -0
- package/dist/assets/{index-aNyQBoJv.js → index-Df3VgdgL.js} +1 -1
- package/dist/assets/{index-hch4ktcC.js → index-z2_QKypm.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/index.js +76 -209
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +0 -7
- package/src/components/StudioRightPanel.tsx +0 -7
- package/src/components/StudioRightPanel.types.ts +0 -2
- package/src/components/editor/SnapToolbar.test.tsx +0 -2
- package/src/components/editor/manualEdits.test.ts +3 -3
- package/src/components/editor/manualEdits.ts +1 -1
- package/src/components/editor/manualEditsParsing.ts +12 -21
- package/src/components/panels/VariablesOtherCompositions.tsx +1 -4
- package/src/components/panels/VariablesPanel.tsx +1 -5
- package/src/components/studioColorGradingScope.ts +0 -4
- package/src/hooks/elementLifecycleOpsTestUtils.ts +0 -1
- package/src/hooks/externalFileChangeOwnership.test.ts +40 -1
- package/src/hooks/gsapScriptCommitTypes.ts +0 -1
- package/src/hooks/sdkSelfWriteRegistry.ts +1 -1
- package/src/hooks/timelineAudioGroupCreate.ts +0 -7
- package/src/hooks/timelineAudioGroupVolume.test.ts +0 -1
- package/src/hooks/timelineAudioGroupVolume.ts +0 -6
- package/src/hooks/timelineEditingHelpers.test.ts +0 -3
- package/src/hooks/timelineEditingHelpers.ts +0 -10
- package/src/hooks/timelineElementFxAttribute.ts +0 -6
- package/src/hooks/timelineTrackVisibility.test.ts +0 -17
- package/src/hooks/timelineTrackVisibility.ts +0 -15
- package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +0 -1
- package/src/hooks/useAppHotkeys.textEditing.test.tsx +0 -2
- package/src/hooks/useAppHotkeys.ts +1 -7
- package/src/hooks/useAudioGroupCarveAssignment.test.tsx +0 -1
- package/src/hooks/useClipboard.ts +1 -11
- package/src/hooks/useDomEditCommits.test.tsx +0 -2
- package/src/hooks/useDomEditCommits.ts +2 -21
- package/src/hooks/useDomEditSession.test.tsx +0 -1
- package/src/hooks/useDomEditSession.ts +0 -7
- package/src/hooks/useEditorSave.test.tsx +0 -1
- package/src/hooks/useEditorSave.ts +2 -6
- package/src/hooks/useElementLifecycleOps.ts +0 -3
- package/src/hooks/useExternalFileChangeCoordinator.test.tsx +54 -0
- package/src/hooks/useExternalFileChangeCoordinator.ts +38 -22
- package/src/hooks/useFileManager.projectOwnership.test.tsx +0 -1
- package/src/hooks/useFileManager.ts +0 -3
- package/src/hooks/useGroupCommits.ts +0 -2
- package/src/hooks/useGsapScriptCommits.test.tsx +0 -2
- package/src/hooks/useGsapScriptCommits.ts +2 -5
- package/src/hooks/usePreviewInteraction.test.ts +14 -5
- package/src/hooks/usePreviewInteraction.ts +3 -3
- package/src/hooks/useProjectCompositionVariables.ts +3 -6
- package/src/hooks/useRazorSplit.history.test.tsx +0 -2
- package/src/hooks/useRazorSplit.test.ts +0 -2
- package/src/hooks/useRazorSplit.test.tsx +0 -1
- package/src/hooks/useRazorSplit.ts +0 -6
- package/src/hooks/useSdkSession.ts +3 -11
- package/src/hooks/useSlideshowPersist.ts +1 -5
- package/src/hooks/useTimelineAssetDropOps.ts +0 -6
- package/src/hooks/useTimelineDeleteOps.ts +0 -4
- package/src/hooks/useTimelineEditing.test.tsx +0 -3
- package/src/hooks/useTimelineEditing.ts +0 -16
- package/src/hooks/useTimelineEditingTypes.ts +0 -1
- package/src/hooks/useTimelineGroupEditing.ts +0 -6
- package/src/hooks/useVariablesPersist.ts +0 -3
- package/src/player/components/Player.tsx +18 -5
- package/src/player/components/playerModuleRetry.test.ts +41 -0
- package/src/player/lib/playbackTypes.ts +7 -0
- package/src/player/lib/timelineIframeHelpers.test.ts +30 -0
- package/src/player/lib/timelineIframeHelpers.ts +16 -0
- package/src/utils/sdkCutover.familyGate.test.ts +0 -1
- package/src/utils/sdkCutover.gate.test.ts +0 -1
- package/src/utils/sdkCutover.test.ts +0 -27
- package/src/utils/sdkEditTransaction.ts +0 -4
- package/src/utils/setSlideshowManifest.test.ts +0 -1
- package/src/utils/studioFileHistory.ts +0 -1
- package/src/webmcp/types.ts +5 -1
- package/src/webmcp/useStudioAgentTools.polyfill.test.tsx +219 -0
- package/src/webmcp/useStudioAgentTools.test.tsx +81 -67
- package/src/webmcp/useStudioAgentTools.ts +49 -28
- package/src/webmcp/webmcpTestUtils.ts +56 -0
- package/dist/assets/index-CkxPQxLJ.js +0 -441
- package/src/hooks/useSdkSession.test.ts +0 -32
|
@@ -42,7 +42,6 @@ interface SetElementAttributeInput {
|
|
|
42
42
|
previewIframe: HTMLIFrameElement | null;
|
|
43
43
|
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
44
44
|
recordEdit: Parameters<typeof persistElementAttribute>[0]["recordEdit"];
|
|
45
|
-
domEditSaveTimestampRef: MutableRef<number>;
|
|
46
45
|
pendingTimelineEditPathRef: MutableRef<Set<string>>;
|
|
47
46
|
}
|
|
48
47
|
|
|
@@ -56,7 +55,6 @@ async function setElementAttribute({
|
|
|
56
55
|
previewIframe,
|
|
57
56
|
writeProjectFile,
|
|
58
57
|
recordEdit,
|
|
59
|
-
domEditSaveTimestampRef,
|
|
60
58
|
pendingTimelineEditPathRef,
|
|
61
59
|
}: SetElementAttributeInput): Promise<string[]> {
|
|
62
60
|
const targetPath = element.sourceFile || activeCompPath || "index.html";
|
|
@@ -72,7 +70,6 @@ async function setElementAttribute({
|
|
|
72
70
|
label,
|
|
73
71
|
writeProjectFile,
|
|
74
72
|
recordEdit,
|
|
75
|
-
domEditSaveTimestampRef,
|
|
76
73
|
pendingTimelineEditPathRef,
|
|
77
74
|
patchLive: (v) => patchLiveElementAttribute(previewIframe, element, attr, v, activeCompPath),
|
|
78
75
|
});
|
|
@@ -84,7 +81,6 @@ export function useSetElementAttribute({
|
|
|
84
81
|
showToast,
|
|
85
82
|
writeProjectFile,
|
|
86
83
|
recordEdit,
|
|
87
|
-
domEditSaveTimestampRef,
|
|
88
84
|
previewIframeRef,
|
|
89
85
|
pendingTimelineEditPathRef,
|
|
90
86
|
isRecordingRef,
|
|
@@ -122,7 +118,6 @@ export function useSetElementAttribute({
|
|
|
122
118
|
previewIframe: previewIframeRef.current,
|
|
123
119
|
writeProjectFile,
|
|
124
120
|
recordEdit,
|
|
125
|
-
domEditSaveTimestampRef,
|
|
126
121
|
pendingTimelineEditPathRef,
|
|
127
122
|
});
|
|
128
123
|
} catch (error) {
|
|
@@ -136,7 +131,6 @@ export function useSetElementAttribute({
|
|
|
136
131
|
previewIframeRef,
|
|
137
132
|
writeProjectFile,
|
|
138
133
|
recordEdit,
|
|
139
|
-
domEditSaveTimestampRef,
|
|
140
134
|
pendingTimelineEditPathRef,
|
|
141
135
|
isRecordingRef,
|
|
142
136
|
showToast,
|
|
@@ -65,7 +65,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
65
65
|
|
|
66
66
|
const writes = new Map<string, string>();
|
|
67
67
|
const recordEdit = vi.fn();
|
|
68
|
-
const timestampRef = { current: 0 };
|
|
69
68
|
const pendingRef = { current: new Set<string>() };
|
|
70
69
|
|
|
71
70
|
await toggleTimelineTrackHidden({
|
|
@@ -83,7 +82,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
83
82
|
writes.set(path, content);
|
|
84
83
|
},
|
|
85
84
|
recordEdit,
|
|
86
|
-
domEditSaveTimestampRef: timestampRef,
|
|
87
85
|
pendingTimelineEditPathRef: pendingRef,
|
|
88
86
|
});
|
|
89
87
|
|
|
@@ -96,7 +94,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
96
94
|
expect(writes.get("index.html")).toContain('id="skip" data-start="0" data-duration="2"');
|
|
97
95
|
expect(writes.get("scene.html")).toContain('data-hidden=""');
|
|
98
96
|
expect(pendingRef.current).toEqual(new Set(["index.html", "scene.html"]));
|
|
99
|
-
expect(timestampRef.current).toBeGreaterThan(0);
|
|
100
97
|
expect(recordEdit).toHaveBeenCalledTimes(1);
|
|
101
98
|
// Display row, not the raw key: track 0 is the first row, so it reads "1",
|
|
102
99
|
// the same number the track header announces for that row.
|
|
@@ -133,7 +130,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
133
130
|
writes.set(path, content);
|
|
134
131
|
},
|
|
135
132
|
recordEdit: vi.fn(),
|
|
136
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
137
133
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
138
134
|
});
|
|
139
135
|
|
|
@@ -164,7 +160,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
164
160
|
previewIframe: null,
|
|
165
161
|
writeProjectFile: async () => {},
|
|
166
162
|
recordEdit,
|
|
167
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
168
163
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
169
164
|
});
|
|
170
165
|
|
|
@@ -191,7 +186,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
191
186
|
previewIframe: null,
|
|
192
187
|
writeProjectFile: async () => {},
|
|
193
188
|
recordEdit,
|
|
194
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
195
189
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
196
190
|
});
|
|
197
191
|
|
|
@@ -215,7 +209,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
215
209
|
previewIframe: null,
|
|
216
210
|
writeProjectFile: async () => {},
|
|
217
211
|
recordEdit,
|
|
218
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
219
212
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
220
213
|
});
|
|
221
214
|
|
|
@@ -246,7 +239,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
246
239
|
previewIframe: null,
|
|
247
240
|
writeProjectFile: async () => {},
|
|
248
241
|
recordEdit,
|
|
249
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
250
242
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
251
243
|
});
|
|
252
244
|
|
|
@@ -272,7 +264,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
272
264
|
previewIframe: null,
|
|
273
265
|
writeProjectFile: async () => {},
|
|
274
266
|
recordEdit,
|
|
275
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
276
267
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
277
268
|
});
|
|
278
269
|
|
|
@@ -303,7 +294,6 @@ describe("toggleTimelineTrackHidden", () => {
|
|
|
303
294
|
previewIframe: null,
|
|
304
295
|
writeProjectFile: async () => {},
|
|
305
296
|
recordEdit,
|
|
306
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
307
297
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
308
298
|
});
|
|
309
299
|
|
|
@@ -359,7 +349,6 @@ describe("toggleTimelineElementHidden", () => {
|
|
|
359
349
|
writes.set(path, content);
|
|
360
350
|
},
|
|
361
351
|
recordEdit,
|
|
362
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
363
352
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
364
353
|
});
|
|
365
354
|
|
|
@@ -417,7 +406,6 @@ describe("toggleTimelineElementHidden", () => {
|
|
|
417
406
|
writes.push({ path, content });
|
|
418
407
|
},
|
|
419
408
|
recordEdit,
|
|
420
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
421
409
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
422
410
|
});
|
|
423
411
|
|
|
@@ -484,7 +472,6 @@ describe("createAudioGroupAndAssignMembers", () => {
|
|
|
484
472
|
writes.set(path, content);
|
|
485
473
|
},
|
|
486
474
|
recordEdit,
|
|
487
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
488
475
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
489
476
|
});
|
|
490
477
|
|
|
@@ -557,7 +544,6 @@ describe("createAudioGroupAndAssignMembers", () => {
|
|
|
557
544
|
writes.set(path, content);
|
|
558
545
|
},
|
|
559
546
|
recordEdit: vi.fn(),
|
|
560
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
561
547
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
562
548
|
});
|
|
563
549
|
|
|
@@ -592,7 +578,6 @@ describe("createAudioGroupAndAssignMembers", () => {
|
|
|
592
578
|
previewIframe: null,
|
|
593
579
|
writeProjectFile: async () => {},
|
|
594
580
|
recordEdit,
|
|
595
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
596
581
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
597
582
|
}),
|
|
598
583
|
).rejects.toThrow("a group needs at least two");
|
|
@@ -612,7 +597,6 @@ describe("createAudioGroupAndAssignMembers", () => {
|
|
|
612
597
|
previewIframe: null,
|
|
613
598
|
writeProjectFile: async () => {},
|
|
614
599
|
recordEdit: vi.fn(),
|
|
615
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
616
600
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
617
601
|
}),
|
|
618
602
|
).rejects.toThrow("Invalid audio group id");
|
|
@@ -641,7 +625,6 @@ describe("createAudioGroupAndAssignMembers", () => {
|
|
|
641
625
|
previewIframe: iframe,
|
|
642
626
|
writeProjectFile: async () => {},
|
|
643
627
|
recordEdit: vi.fn(),
|
|
644
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
645
628
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
646
629
|
}),
|
|
647
630
|
).rejects.toThrow();
|
|
@@ -39,7 +39,6 @@ interface ToggleTimelineTrackHiddenInput {
|
|
|
39
39
|
previewIframe: HTMLIFrameElement | null;
|
|
40
40
|
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
41
41
|
recordEdit: (input: RecordEditInput) => Promise<void>;
|
|
42
|
-
domEditSaveTimestampRef: MutableRef<number>;
|
|
43
42
|
pendingTimelineEditPathRef: MutableRef<Set<string>>;
|
|
44
43
|
}
|
|
45
44
|
|
|
@@ -57,7 +56,6 @@ interface SetElementsHiddenInput {
|
|
|
57
56
|
previewIframe: HTMLIFrameElement | null;
|
|
58
57
|
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
59
58
|
recordEdit: (input: RecordEditInput) => Promise<void>;
|
|
60
|
-
domEditSaveTimestampRef: MutableRef<number>;
|
|
61
59
|
pendingTimelineEditPathRef: MutableRef<Set<string>>;
|
|
62
60
|
}
|
|
63
61
|
|
|
@@ -140,7 +138,6 @@ async function setElementsHidden({
|
|
|
140
138
|
previewIframe,
|
|
141
139
|
writeProjectFile,
|
|
142
140
|
recordEdit,
|
|
143
|
-
domEditSaveTimestampRef,
|
|
144
141
|
pendingTimelineEditPathRef,
|
|
145
142
|
}: SetElementsHiddenInput): Promise<string[]> {
|
|
146
143
|
if (elements.length === 0) return [];
|
|
@@ -176,7 +173,6 @@ async function setElementsHidden({
|
|
|
176
173
|
pendingTimelineEditPathRef.current.add(targetPath);
|
|
177
174
|
}
|
|
178
175
|
|
|
179
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
180
176
|
const changedPaths = await saveProjectFilesWithHistory({
|
|
181
177
|
projectId,
|
|
182
178
|
label,
|
|
@@ -190,7 +186,6 @@ async function setElementsHidden({
|
|
|
190
186
|
writeFile: writeProjectFile,
|
|
191
187
|
recordEdit,
|
|
192
188
|
});
|
|
193
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
194
189
|
for (const element of elements) {
|
|
195
190
|
usePlayerStore.getState().updateElement(element.key ?? element.id, { hidden });
|
|
196
191
|
}
|
|
@@ -215,7 +210,6 @@ export async function toggleTimelineTrackHidden({
|
|
|
215
210
|
previewIframe,
|
|
216
211
|
writeProjectFile,
|
|
217
212
|
recordEdit,
|
|
218
|
-
domEditSaveTimestampRef,
|
|
219
213
|
pendingTimelineEditPathRef,
|
|
220
214
|
}: ToggleTimelineTrackHiddenInput): Promise<string[]> {
|
|
221
215
|
// `track` is the fractional sort key the callback needs; the history entry is
|
|
@@ -245,7 +239,6 @@ export async function toggleTimelineTrackHidden({
|
|
|
245
239
|
previewIframe,
|
|
246
240
|
writeProjectFile,
|
|
247
241
|
recordEdit,
|
|
248
|
-
domEditSaveTimestampRef,
|
|
249
242
|
pendingTimelineEditPathRef,
|
|
250
243
|
});
|
|
251
244
|
}
|
|
@@ -259,7 +252,6 @@ export async function toggleTimelineElementHidden({
|
|
|
259
252
|
previewIframe,
|
|
260
253
|
writeProjectFile,
|
|
261
254
|
recordEdit,
|
|
262
|
-
domEditSaveTimestampRef,
|
|
263
255
|
pendingTimelineEditPathRef,
|
|
264
256
|
}: ToggleTimelineElementHiddenInput): Promise<string[]> {
|
|
265
257
|
const keys = new Set(typeof elementKey === "string" ? [elementKey] : elementKey);
|
|
@@ -280,7 +272,6 @@ export async function toggleTimelineElementHidden({
|
|
|
280
272
|
previewIframe,
|
|
281
273
|
writeProjectFile,
|
|
282
274
|
recordEdit,
|
|
283
|
-
domEditSaveTimestampRef,
|
|
284
275
|
pendingTimelineEditPathRef,
|
|
285
276
|
});
|
|
286
277
|
}
|
|
@@ -291,7 +282,6 @@ export function useTimelineTrackVisibilityEditing({
|
|
|
291
282
|
showToast,
|
|
292
283
|
writeProjectFile,
|
|
293
284
|
recordEdit,
|
|
294
|
-
domEditSaveTimestampRef,
|
|
295
285
|
previewIframeRef,
|
|
296
286
|
pendingTimelineEditPathRef,
|
|
297
287
|
isRecordingRef,
|
|
@@ -325,7 +315,6 @@ export function useTimelineTrackVisibilityEditing({
|
|
|
325
315
|
previewIframe: previewIframeRef.current,
|
|
326
316
|
writeProjectFile,
|
|
327
317
|
recordEdit,
|
|
328
|
-
domEditSaveTimestampRef,
|
|
329
318
|
pendingTimelineEditPathRef,
|
|
330
319
|
});
|
|
331
320
|
forceReloadSdkSession?.();
|
|
@@ -342,7 +331,6 @@ export function useTimelineTrackVisibilityEditing({
|
|
|
342
331
|
previewIframeRef,
|
|
343
332
|
writeProjectFile,
|
|
344
333
|
recordEdit,
|
|
345
|
-
domEditSaveTimestampRef,
|
|
346
334
|
pendingTimelineEditPathRef,
|
|
347
335
|
isRecordingRef,
|
|
348
336
|
showToast,
|
|
@@ -358,7 +346,6 @@ export function useTimelineElementVisibilityEditing({
|
|
|
358
346
|
showToast,
|
|
359
347
|
writeProjectFile,
|
|
360
348
|
recordEdit,
|
|
361
|
-
domEditSaveTimestampRef,
|
|
362
349
|
previewIframeRef,
|
|
363
350
|
pendingTimelineEditPathRef,
|
|
364
351
|
isRecordingRef,
|
|
@@ -394,7 +381,6 @@ export function useTimelineElementVisibilityEditing({
|
|
|
394
381
|
previewIframe: previewIframeRef.current,
|
|
395
382
|
writeProjectFile,
|
|
396
383
|
recordEdit,
|
|
397
|
-
domEditSaveTimestampRef,
|
|
398
384
|
pendingTimelineEditPathRef,
|
|
399
385
|
});
|
|
400
386
|
forceReloadSdkSession?.();
|
|
@@ -411,7 +397,6 @@ export function useTimelineElementVisibilityEditing({
|
|
|
411
397
|
previewIframeRef,
|
|
412
398
|
writeProjectFile,
|
|
413
399
|
recordEdit,
|
|
414
|
-
domEditSaveTimestampRef,
|
|
415
400
|
pendingTimelineEditPathRef,
|
|
416
401
|
isRecordingRef,
|
|
417
402
|
showToast,
|
|
@@ -43,7 +43,6 @@ function Harness() {
|
|
|
43
43
|
readOptionalProjectFile: vi.fn(async () => ""),
|
|
44
44
|
readProjectFile: vi.fn(async () => ""),
|
|
45
45
|
writeProjectFile: vi.fn(async () => undefined),
|
|
46
|
-
domEditSaveTimestampRef: useRef(0),
|
|
47
46
|
showToast: vi.fn(),
|
|
48
47
|
syncHistoryPreviewAfterApply: vi.fn(async () => undefined),
|
|
49
48
|
waitForPendingDomEditSaves: vi.fn(async () => undefined),
|
|
@@ -70,7 +70,6 @@ function Harness() {
|
|
|
70
70
|
const [selectionRefreshed, setSelectionRefreshed] = useState(false);
|
|
71
71
|
const selectionRef = useRef<DomEditSelection | null>(parentWithTextChild());
|
|
72
72
|
const clearSelectionRef = useRef<() => void>(() => undefined);
|
|
73
|
-
const saveTimestampRef = useRef(0);
|
|
74
73
|
const leftSidebarRef = useRef<LeftSidebarHandle | null>(null);
|
|
75
74
|
|
|
76
75
|
useAppHotkeys({
|
|
@@ -87,7 +86,6 @@ function Harness() {
|
|
|
87
86
|
readOptionalProjectFile: vi.fn(async () => ""),
|
|
88
87
|
readProjectFile: vi.fn(async () => ""),
|
|
89
88
|
writeProjectFile: vi.fn(async () => undefined),
|
|
90
|
-
domEditSaveTimestampRef: saveTimestampRef,
|
|
91
89
|
showToast: vi.fn(),
|
|
92
90
|
syncHistoryPreviewAfterApply: vi.fn(async () => undefined),
|
|
93
91
|
waitForPendingDomEditSaves: vi.fn(async () => undefined),
|
|
@@ -118,7 +118,6 @@ interface UseAppHotkeysParams {
|
|
|
118
118
|
readOptionalProjectFile: (path: string) => Promise<string>;
|
|
119
119
|
readProjectFile: (path: string) => Promise<string>;
|
|
120
120
|
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
121
|
-
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
122
121
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
123
122
|
syncHistoryPreviewAfterApply: (restore: {
|
|
124
123
|
paths?: string[];
|
|
@@ -377,7 +376,6 @@ export function useAppHotkeys({
|
|
|
377
376
|
readOptionalProjectFile,
|
|
378
377
|
readProjectFile,
|
|
379
378
|
writeProjectFile,
|
|
380
|
-
domEditSaveTimestampRef,
|
|
381
379
|
showToast,
|
|
382
380
|
syncHistoryPreviewAfterApply,
|
|
383
381
|
waitForPendingDomEditSaves,
|
|
@@ -405,10 +403,9 @@ export function useAppHotkeys({
|
|
|
405
403
|
);
|
|
406
404
|
const writeHistoryFile = useCallback(
|
|
407
405
|
async (path: string, content: string): Promise<void> => {
|
|
408
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
409
406
|
await writeProjectFile(path, content);
|
|
410
407
|
},
|
|
411
|
-
[
|
|
408
|
+
[writeProjectFile],
|
|
412
409
|
);
|
|
413
410
|
const serializeHistoryFiles = useCallback(
|
|
414
411
|
<T>(paths: readonly string[], task: () => Promise<T>) =>
|
|
@@ -455,9 +452,6 @@ export function useAppHotkeys({
|
|
|
455
452
|
onAfterUndoRedo?.();
|
|
456
453
|
// If the active composition was among the written files, force-reload
|
|
457
454
|
// the SDK session so its in-memory doc matches the reverted content.
|
|
458
|
-
// writeHistoryFile sets domEditSaveTimestampRef which activates the
|
|
459
|
-
// 2 s suppress window — without this call the file-change event would
|
|
460
|
-
// be swallowed and the SDK session would stay on stale pre-undo content.
|
|
461
455
|
if (activeCompPath && result.paths?.includes(activeCompPath)) {
|
|
462
456
|
forceReloadSdkSession?.();
|
|
463
457
|
}
|
|
@@ -72,7 +72,6 @@ function renderAssign(writeProjectFile: (path: string, content: string) => Promi
|
|
|
72
72
|
showToast,
|
|
73
73
|
writeProjectFile,
|
|
74
74
|
recordEdit: async () => {},
|
|
75
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
76
75
|
pendingTimelineEditPathRef: { current: new Set() },
|
|
77
76
|
previewIframeRef: { current: null },
|
|
78
77
|
});
|
|
@@ -25,7 +25,6 @@ interface UseClipboardOptions {
|
|
|
25
25
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
26
26
|
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
27
27
|
recordEdit: (input: RecordEditInput) => Promise<void>;
|
|
28
|
-
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
29
28
|
reloadPreview: () => void;
|
|
30
29
|
handleTimelineElementDelete: (element: TimelineElement) => Promise<void>;
|
|
31
30
|
handleDomEditElementDelete: (selection: DomEditSelection) => Promise<void>;
|
|
@@ -55,7 +54,6 @@ export function useClipboard({
|
|
|
55
54
|
showToast,
|
|
56
55
|
writeProjectFile,
|
|
57
56
|
recordEdit,
|
|
58
|
-
domEditSaveTimestampRef,
|
|
59
57
|
reloadPreview,
|
|
60
58
|
handleTimelineElementDelete,
|
|
61
59
|
handleDomEditElementDelete,
|
|
@@ -171,7 +169,6 @@ export function useClipboard({
|
|
|
171
169
|
);
|
|
172
170
|
}
|
|
173
171
|
|
|
174
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
175
172
|
await saveProjectFilesWithHistory({
|
|
176
173
|
projectId: pid,
|
|
177
174
|
label: payload.kind === "timeline-clip" ? "Paste clip" : "Paste element",
|
|
@@ -188,14 +185,7 @@ export function useClipboard({
|
|
|
188
185
|
const message = error instanceof Error ? error.message : "Failed to paste";
|
|
189
186
|
showToast(message);
|
|
190
187
|
}
|
|
191
|
-
}, [
|
|
192
|
-
activeCompPath,
|
|
193
|
-
domEditSaveTimestampRef,
|
|
194
|
-
recordEdit,
|
|
195
|
-
reloadPreview,
|
|
196
|
-
showToast,
|
|
197
|
-
writeProjectFile,
|
|
198
|
-
]);
|
|
188
|
+
}, [activeCompPath, recordEdit, reloadPreview, showToast, writeProjectFile]);
|
|
199
189
|
|
|
200
190
|
const handleCut = useCallback(async (): Promise<boolean> => {
|
|
201
191
|
const copied = handleCopy();
|
|
@@ -209,7 +209,6 @@ function renderDomEditCommits(
|
|
|
209
209
|
const recordEdit = vi.fn(async () => {});
|
|
210
210
|
const previewIframeRef: MutableRefObject<HTMLIFrameElement | null> = { current: iframe };
|
|
211
211
|
const projectIdRef: MutableRefObject<string | null> = options.projectIdRef ?? { current: "p1" };
|
|
212
|
-
const domEditSaveTimestampRef: MutableRefObject<number> = { current: 0 };
|
|
213
212
|
const reloadPreview = vi.fn();
|
|
214
213
|
|
|
215
214
|
function Probe() {
|
|
@@ -219,7 +218,6 @@ function renderDomEditCommits(
|
|
|
219
218
|
showToast,
|
|
220
219
|
queueDomEditSave: options.queueDomEditSave ?? (async (save) => save()),
|
|
221
220
|
writeProjectFile: options.writeProjectFile ?? (async () => {}),
|
|
222
|
-
domEditSaveTimestampRef,
|
|
223
221
|
editHistory: { recordEdit },
|
|
224
222
|
fileTree: [],
|
|
225
223
|
importedFontAssetsRef: { current: options.importedFontAssets ?? [] },
|
|
@@ -50,7 +50,6 @@ export interface UseDomEditCommitsParams {
|
|
|
50
50
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
51
51
|
queueDomEditSave: <T>(save: () => Promise<T>) => Promise<T>;
|
|
52
52
|
writeProjectFile: (path: string, content: string, expectedContent?: string) => Promise<void>;
|
|
53
|
-
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
54
53
|
editHistory: { recordEdit: (entry: RecordEditInput) => Promise<void> };
|
|
55
54
|
fileTree: string[];
|
|
56
55
|
importedFontAssetsRef: React.MutableRefObject<ImportedFontAsset[]>;
|
|
@@ -98,7 +97,6 @@ export function useDomEditCommits({
|
|
|
98
97
|
showToast,
|
|
99
98
|
queueDomEditSave,
|
|
100
99
|
writeProjectFile,
|
|
101
|
-
domEditSaveTimestampRef,
|
|
102
100
|
editHistory,
|
|
103
101
|
fileTree,
|
|
104
102
|
importedFontAssetsRef,
|
|
@@ -214,11 +212,6 @@ export function useDomEditCommits({
|
|
|
214
212
|
}
|
|
215
213
|
}
|
|
216
214
|
|
|
217
|
-
// Mark the save timestamp before the file write so the SSE file-change
|
|
218
|
-
// handler suppresses the reload even if the event arrives before the
|
|
219
|
-
// response (the server writes the file and emits SSE during the fetch).
|
|
220
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
221
|
-
|
|
222
215
|
const patchResponse = await fetch(
|
|
223
216
|
buildProjectApiPath(pid, `/file-mutations/patch-element/${encodeURIComponent(targetPath)}`),
|
|
224
217
|
{
|
|
@@ -315,7 +308,6 @@ export function useDomEditCommits({
|
|
|
315
308
|
editHistory,
|
|
316
309
|
writeProjectFile,
|
|
317
310
|
projectIdRef,
|
|
318
|
-
domEditSaveTimestampRef,
|
|
319
311
|
reloadPreview,
|
|
320
312
|
showToast,
|
|
321
313
|
forceReloadSdkSession,
|
|
@@ -359,7 +351,6 @@ export function useDomEditCommits({
|
|
|
359
351
|
);
|
|
360
352
|
}
|
|
361
353
|
|
|
362
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
363
354
|
const atomicResult = await patchElementBatches(pid, batches);
|
|
364
355
|
const allMatched =
|
|
365
356
|
atomicResult.durable && atomicResult.files.every((result) => result.allMatched);
|
|
@@ -385,8 +376,7 @@ export function useDomEditCommits({
|
|
|
385
376
|
// produces a visible blink. Skip the reload when the caller asked for it
|
|
386
377
|
// AND the persist is provably in sync: style-only ops, every target
|
|
387
378
|
// matched. Any unmatched patch means the live DOM now shows state disk
|
|
388
|
-
// doesn't hold — reload so the preview reconverges.
|
|
389
|
-
// reload is independently suppressed by domEditSaveTimestampRef above.)
|
|
379
|
+
// doesn't hold — reload so the preview reconverges.
|
|
390
380
|
const skipSafe =
|
|
391
381
|
options.skipReload === true && batchesAreInlineStyleOnly(batches) && durable;
|
|
392
382
|
if (!durable || (changed && !skipSafe)) reloadPreview();
|
|
@@ -411,15 +401,7 @@ export function useDomEditCommits({
|
|
|
411
401
|
throw error;
|
|
412
402
|
});
|
|
413
403
|
},
|
|
414
|
-
[
|
|
415
|
-
domEditSaveTimestampRef,
|
|
416
|
-
editHistory,
|
|
417
|
-
forceReloadSdkSession,
|
|
418
|
-
projectIdRef,
|
|
419
|
-
queueDomEditSave,
|
|
420
|
-
reloadPreview,
|
|
421
|
-
showToast,
|
|
422
|
-
],
|
|
404
|
+
[editHistory, forceReloadSdkSession, projectIdRef, queueDomEditSave, reloadPreview, showToast],
|
|
423
405
|
);
|
|
424
406
|
|
|
425
407
|
// ── Text & style commits (delegated to useDomEditTextCommits) ──
|
|
@@ -479,7 +461,6 @@ export function useDomEditCommits({
|
|
|
479
461
|
activeCompPath,
|
|
480
462
|
showToast,
|
|
481
463
|
writeProjectFile,
|
|
482
|
-
domEditSaveTimestampRef,
|
|
483
464
|
editHistory,
|
|
484
465
|
projectIdRef,
|
|
485
466
|
reloadPreview,
|
|
@@ -84,7 +84,6 @@ function createSessionParams(
|
|
|
84
84
|
readProjectFile: async () => "",
|
|
85
85
|
writeProjectFile: async () => {},
|
|
86
86
|
updateEditingFileContent: vi.fn(),
|
|
87
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
88
87
|
editHistory: { recordEdit: async () => {} },
|
|
89
88
|
fileTree: [],
|
|
90
89
|
importedFontAssetsRef: { current: [] },
|
|
@@ -47,7 +47,6 @@ export interface UseDomEditSessionParams {
|
|
|
47
47
|
readProjectFile: (path: string) => Promise<string>;
|
|
48
48
|
writeProjectFile: (path: string, content: string, expectedContent?: string) => Promise<void>;
|
|
49
49
|
updateEditingFileContent: (path: string, content: string) => void;
|
|
50
|
-
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
51
50
|
editHistory: { recordEdit: (entry: RecordEditInput) => Promise<void> };
|
|
52
51
|
fileTree: string[];
|
|
53
52
|
importedFontAssetsRef: React.MutableRefObject<ImportedFontAsset[]>;
|
|
@@ -91,7 +90,6 @@ export function useDomEditSession({
|
|
|
91
90
|
readProjectFile,
|
|
92
91
|
writeProjectFile,
|
|
93
92
|
updateEditingFileContent,
|
|
94
|
-
domEditSaveTimestampRef,
|
|
95
93
|
editHistory,
|
|
96
94
|
fileTree,
|
|
97
95
|
importedFontAssetsRef,
|
|
@@ -211,7 +209,6 @@ export function useDomEditSession({
|
|
|
211
209
|
activeCompPath,
|
|
212
210
|
previewIframeRef,
|
|
213
211
|
editHistory,
|
|
214
|
-
domEditSaveTimestampRef,
|
|
215
212
|
reloadPreview,
|
|
216
213
|
onCacheInvalidate: bumpGsapCache,
|
|
217
214
|
onFileContentChanged: updateEditingFileContent,
|
|
@@ -249,7 +246,6 @@ export function useDomEditSession({
|
|
|
249
246
|
showToast,
|
|
250
247
|
queueDomEditSave,
|
|
251
248
|
writeProjectFile,
|
|
252
|
-
domEditSaveTimestampRef,
|
|
253
249
|
editHistory,
|
|
254
250
|
fileTree,
|
|
255
251
|
importedFontAssetsRef,
|
|
@@ -283,7 +279,6 @@ export function useDomEditSession({
|
|
|
283
279
|
editHistory,
|
|
284
280
|
writeProjectFile,
|
|
285
281
|
reloadPreview,
|
|
286
|
-
domEditSaveTimestampRef,
|
|
287
282
|
compositionPath: activeCompPath,
|
|
288
283
|
readProjectFile,
|
|
289
284
|
publishSession: publishSdkSession,
|
|
@@ -298,7 +293,6 @@ export function useDomEditSession({
|
|
|
298
293
|
editHistory,
|
|
299
294
|
writeProjectFile,
|
|
300
295
|
reloadPreview,
|
|
301
|
-
domEditSaveTimestampRef,
|
|
302
296
|
compositionPath: activeCompPath,
|
|
303
297
|
readProjectFile,
|
|
304
298
|
publishSession: publishSdkSession,
|
|
@@ -327,7 +321,6 @@ export function useDomEditSession({
|
|
|
327
321
|
activeCompPath,
|
|
328
322
|
showToast,
|
|
329
323
|
writeProjectFile,
|
|
330
|
-
domEditSaveTimestampRef,
|
|
331
324
|
editHistory,
|
|
332
325
|
projectIdRef,
|
|
333
326
|
reloadPreview,
|
|
@@ -27,7 +27,6 @@ async function mountEditorSave(writeProjectFile: WriteProjectFile) {
|
|
|
27
27
|
readProjectFile: vi.fn(async () => "before"),
|
|
28
28
|
writeProjectFile,
|
|
29
29
|
recordEdit: vi.fn(async () => undefined),
|
|
30
|
-
domEditSaveTimestampRef: { current: 0 },
|
|
31
30
|
setRefreshKey: vi.fn(),
|
|
32
31
|
showToast,
|
|
33
32
|
});
|
|
@@ -23,7 +23,6 @@ interface UseEditorSaveOptions {
|
|
|
23
23
|
readProjectFile: (path: string) => Promise<string>;
|
|
24
24
|
writeProjectFile: (path: string, content: string, expectedContent?: string) => Promise<void>;
|
|
25
25
|
recordEdit: (input: RecordEditInput) => Promise<void>;
|
|
26
|
-
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
27
26
|
setRefreshKey: React.Dispatch<React.SetStateAction<number>>;
|
|
28
27
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
29
28
|
}
|
|
@@ -53,7 +52,6 @@ export function useEditorSave({
|
|
|
53
52
|
readProjectFile,
|
|
54
53
|
writeProjectFile,
|
|
55
54
|
recordEdit,
|
|
56
|
-
domEditSaveTimestampRef,
|
|
57
55
|
setRefreshKey,
|
|
58
56
|
showToast,
|
|
59
57
|
}: UseEditorSaveOptions): EditorSaveHandle {
|
|
@@ -156,11 +154,10 @@ export function useEditorSave({
|
|
|
156
154
|
if (saveRafRef.current != null) cancelAnimationFrame(saveRafRef.current);
|
|
157
155
|
saveRafRef.current = requestAnimationFrame(() => {
|
|
158
156
|
saveRafRef.current = null;
|
|
159
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
160
157
|
void persistCandidate(candidate);
|
|
161
158
|
});
|
|
162
159
|
},
|
|
163
|
-
[
|
|
160
|
+
[editingPathRef, projectIdRef, persistCandidate],
|
|
164
161
|
);
|
|
165
162
|
|
|
166
163
|
const flushPendingSave = useCallback(async (): Promise<EditorSaveDrainResult> => {
|
|
@@ -173,11 +170,10 @@ export function useEditorSave({
|
|
|
173
170
|
return inFlightRef.current;
|
|
174
171
|
}
|
|
175
172
|
if (candidate) {
|
|
176
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
177
173
|
return persistCandidate(candidate);
|
|
178
174
|
}
|
|
179
175
|
return (await inFlightRef.current) ?? { status: "clean" };
|
|
180
|
-
}, [
|
|
176
|
+
}, [persistCandidate]);
|
|
181
177
|
|
|
182
178
|
const discardPendingSave = useCallback(() => {
|
|
183
179
|
if (saveRafRef.current != null) cancelAnimationFrame(saveRafRef.current);
|
|
@@ -75,7 +75,6 @@ export function useElementLifecycleOps({
|
|
|
75
75
|
activeCompPath,
|
|
76
76
|
showToast,
|
|
77
77
|
writeProjectFile,
|
|
78
|
-
domEditSaveTimestampRef,
|
|
79
78
|
editHistory,
|
|
80
79
|
projectIdRef,
|
|
81
80
|
reloadPreview,
|
|
@@ -147,7 +146,6 @@ export function useElementLifecycleOps({
|
|
|
147
146
|
}
|
|
148
147
|
}
|
|
149
148
|
|
|
150
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
151
149
|
// One request for the whole selection. Removing members one at a time
|
|
152
150
|
// cost a round trip and a rewrite of the file EACH, and a canvas
|
|
153
151
|
// selection runs to hundreds of members — the file ended up correct, but
|
|
@@ -220,7 +218,6 @@ export function useElementLifecycleOps({
|
|
|
220
218
|
[
|
|
221
219
|
activeCompPath,
|
|
222
220
|
clearDomSelection,
|
|
223
|
-
domEditSaveTimestampRef,
|
|
224
221
|
editHistory.recordEdit,
|
|
225
222
|
onTrySdkDelete,
|
|
226
223
|
onElementDeleted,
|