@hyperframes/studio 0.8.75 → 0.8.76

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 (107) hide show
  1. package/dist/assets/hyperframes-player-BkDxgSKG.js +464 -0
  2. package/dist/assets/{index-BiFKcNjs.js → index-COX0scwJ.js} +1 -1
  3. package/dist/assets/{index-BlUYw1MB.js → index-DeHir1up.js} +156 -156
  4. package/dist/assets/{index-HjexPsny.js → index-FmKUmYhC.js} +1 -1
  5. package/dist/{chunk-7BP32G62.js → chunk-IYDE66BY.js} +6 -1
  6. package/dist/chunk-IYDE66BY.js.map +1 -0
  7. package/dist/{domEditingLayers-UKMVYXT6.js → domEditingLayers-2XBFXXSW.js} +4 -2
  8. package/dist/index.d.ts +34 -69
  9. package/dist/index.html +1 -1
  10. package/dist/index.js +4147 -4253
  11. package/dist/index.js.map +1 -1
  12. package/package.json +7 -7
  13. package/src/components/StudioRightPanels.types.ts +0 -2
  14. package/src/components/editor/DomEditOverlay.test.ts +5 -0
  15. package/src/components/editor/LayersPanel.tsx +3 -14
  16. package/src/components/editor/PreviewOverlayProvider.test.tsx +6 -1
  17. package/src/components/editor/domEditing.ts +1 -0
  18. package/src/components/editor/domEditingLayers.test.ts +27 -0
  19. package/src/components/editor/domEditingLayers.ts +9 -0
  20. package/src/components/editor/offCanvasIndicatorRefresh.test.tsx +57 -35
  21. package/src/components/editor/overlayFrameLoop.test.ts +16 -0
  22. package/src/components/editor/overlayFrameLoop.ts +11 -0
  23. package/src/components/editor/usePreviewCompositionRect.test.tsx +6 -1
  24. package/src/components/nle/NLEPreview.test.ts +94 -17
  25. package/src/components/nle/NLEPreview.tsx +14 -0
  26. package/src/components/nle/PreviewPoster.tsx +99 -0
  27. package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +30 -49
  28. package/src/components/panels/VariablesPanel.tsx +0 -2
  29. package/src/components/sidebar/CompositionsTab.drag.test.tsx +5 -1
  30. package/src/components/sidebar/CompositionsTab.rootBadge.test.tsx +14 -0
  31. package/src/components/sidebar/CompositionsTab.test.ts +4 -6
  32. package/src/components/sidebar/CompositionsTab.tsx +28 -21
  33. package/src/components/studioColorGradingScope.ts +0 -3
  34. package/src/hooks/domEditDeleteMembers.ts +0 -2
  35. package/src/hooks/gsapScriptCommitTypes.ts +0 -2
  36. package/src/hooks/keyframeCacheAstLoad.test.ts +31 -1
  37. package/src/hooks/keyframeCacheAstLoad.ts +2 -1
  38. package/src/hooks/timelineAudioGroupCreate.ts +0 -1
  39. package/src/hooks/timelineEditingHelpers.ts +0 -3
  40. package/src/hooks/timelineTimingSync.ts +0 -1
  41. package/src/hooks/timelineTrackVisibility.ts +0 -1
  42. package/src/hooks/useBlockHandlers.ts +0 -2
  43. package/src/hooks/useClipboard.test.ts +69 -10
  44. package/src/hooks/useClipboard.ts +28 -19
  45. package/src/hooks/useDomEditCommits.test.tsx +0 -1
  46. package/src/hooks/useDomEditCommits.ts +0 -4
  47. package/src/hooks/useEditHistoryActions.test.tsx +31 -14
  48. package/src/hooks/useEditHistoryActions.ts +12 -7
  49. package/src/hooks/useEditorSave.ts +0 -3
  50. package/src/hooks/useElementLifecycleOps.ts +0 -1
  51. package/src/hooks/useEnableKeyframes.test.ts +9 -1
  52. package/src/hooks/useFileManager.ts +0 -2
  53. package/src/hooks/useGroupCommits.ts +0 -1
  54. package/src/hooks/useGsapScriptCommits.ts +0 -1
  55. package/src/hooks/useLintModal.test.tsx +34 -1
  56. package/src/hooks/useLintModal.ts +32 -30
  57. package/src/hooks/usePersistentEditHistory.test.ts +221 -315
  58. package/src/hooks/usePersistentEditHistory.ts +155 -342
  59. package/src/hooks/usePopulateKeyframeCacheForFile.test.tsx +2 -0
  60. package/src/hooks/usePreviewPersistence.ts +0 -2
  61. package/src/hooks/useProjectCompositionVariables.ts +1 -3
  62. package/src/hooks/useRazorSplit.history.test.tsx +46 -67
  63. package/src/hooks/useRazorSplit.test.ts +17 -47
  64. package/src/hooks/useRenderClipContent.test.ts +29 -0
  65. package/src/hooks/useRenderClipContent.ts +6 -3
  66. package/src/hooks/useSdkSession.lifecycle.test.tsx +34 -4
  67. package/src/hooks/useSdkSession.ts +8 -2
  68. package/src/hooks/useSlideshowPersist.ts +0 -2
  69. package/src/hooks/useStudioContextValue.ts +6 -1
  70. package/src/hooks/useTimelineAssetDropOps.ts +0 -1
  71. package/src/hooks/useTimelineDeleteOps.ts +0 -1
  72. package/src/hooks/useTimelineEditing.test.tsx +0 -1
  73. package/src/hooks/useTimelineEditingTypes.ts +0 -2
  74. package/src/hostMount.test.tsx +53 -22
  75. package/src/main.tsx +3 -0
  76. package/src/player/components/CompositionThumbnail.tsx +14 -0
  77. package/src/player/components/Player.test.ts +49 -0
  78. package/src/player/components/Player.tsx +21 -7
  79. package/src/player/components/timelineClipDragCommit.test.ts +7 -39
  80. package/src/player/hooks/timelineDomBinding.test.ts +50 -0
  81. package/src/player/hooks/timelineSyncHydration.test.ts +25 -1
  82. package/src/player/hooks/timelineSyncHydration.ts +3 -3
  83. package/src/player/hooks/useShadowPreviewReload.ts +1 -0
  84. package/src/player/hooks/useTimelinePlayer.play.test.ts +120 -0
  85. package/src/player/hooks/useTimelinePlayer.ts +1 -0
  86. package/src/player/hooks/useTimelineSyncCallbacks.ts +2 -0
  87. package/src/player/lib/timelineElementHelpers.ts +45 -9
  88. package/src/player/store/playerStore.test.ts +35 -1
  89. package/src/player/store/playerStore.ts +29 -0
  90. package/src/player/store/readinessSlice.ts +34 -6
  91. package/src/utils/blockInstaller.ts +0 -3
  92. package/src/utils/previewPrefetch.test.ts +22 -0
  93. package/src/utils/previewPrefetch.ts +14 -0
  94. package/src/utils/razorSplitTransaction.test.ts +0 -1
  95. package/src/utils/razorSplitTransaction.ts +1 -1
  96. package/src/utils/sdkEditTransaction.ts +0 -3
  97. package/src/utils/studioFileHistory.test.ts +0 -9
  98. package/src/utils/studioFileHistory.ts +1 -5
  99. package/src/utils/timelineCompositionInsert.ts +0 -1
  100. package/dist/assets/hyperframes-player-WbBVbWuO.js +0 -464
  101. package/dist/chunk-7BP32G62.js.map +0 -1
  102. package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +0 -60
  103. package/src/utils/editHistory.test.ts +0 -534
  104. package/src/utils/editHistory.ts +0 -236
  105. package/src/utils/editHistoryStorage.test.ts +0 -37
  106. package/src/utils/editHistoryStorage.ts +0 -99
  107. /package/dist/{domEditingLayers-UKMVYXT6.js.map → domEditingLayers-2XBFXXSW.js.map} +0 -0
@@ -428,14 +428,50 @@ function nodeMatchesManifestClip(node: Element, clip: ClipManifestClip): boolean
428
428
  });
429
429
  }
430
430
 
431
- function findTimelineDomNode(doc: Document, id: string): Element | null {
432
- return (
433
- doc.getElementById(id) ??
434
- doc.querySelector(`[data-hf-id="${CSS.escape(id)}"]`) ??
435
- doc.querySelector(`[data-composition-id="${CSS.escape(id)}"]`) ??
436
- doc.querySelector(`.${CSS.escape(id)}`) ??
437
- null
438
- );
431
+ /** Whether `node` sits in the composition the clip was read from, the chain the runtime records outermost first
432
+ * (`resolveNearestCompositionContext` in core's runtime/timeline.ts). A clip without that scope accepts any node. */
433
+ function nodeInClipScope(node: Element, clip: ClipManifestClip): boolean {
434
+ const scope = clip.compositionAncestors;
435
+ if (!scope) return true;
436
+ const ids: string[] = [];
437
+ for (let cursor = node.parentElement; cursor; cursor = cursor.parentElement) {
438
+ const id = cursor.getAttribute("data-composition-id");
439
+ if (id) ids.unshift(id);
440
+ }
441
+ return ids.length === scope.length && ids.every((id, index) => id === scope[index]);
442
+ }
443
+
444
+ /** The first match in the clip's composition across `selectors`; a lone match stands only when none is in scope, as a
445
+ * healed host can stale the clip's chain for a pass. An id can repeat in a sub-composition earlier in the document. */
446
+ function findInClipScope(
447
+ doc: Document,
448
+ clip: ClipManifestClip,
449
+ selectors: string[],
450
+ ): Element | null {
451
+ let lone: Element | null = null;
452
+ for (const selector of selectors) {
453
+ const nodes = Array.from(doc.querySelectorAll(selector));
454
+ const scoped = nodes.find((node) => nodeInClipScope(node, clip));
455
+ if (scoped) return scoped;
456
+ if (nodes.length === 1) lone ??= nodes[0];
457
+ }
458
+ return lone;
459
+ }
460
+
461
+ export function findClipElementById(doc: Document, clip: ClipManifestClip): Element | null {
462
+ if (!clip.id) return null;
463
+ const first = doc.getElementById(clip.id);
464
+ if (!first || nodeInClipScope(first, clip)) return first;
465
+ return findInClipScope(doc, clip, [`[id="${CSS.escape(clip.id)}"]`]);
466
+ }
467
+
468
+ function findTimelineDomNode(doc: Document, clip: ClipManifestClip): Element | null {
469
+ if (!clip.id) return null;
470
+ const first = doc.getElementById(clip.id);
471
+ if (first && nodeInClipScope(first, clip)) return first;
472
+ const id = CSS.escape(clip.id);
473
+ const byOtherKeys = [`[data-hf-id="${id}"]`, `[data-composition-id="${id}"]`, `.${id}`];
474
+ return findInClipScope(doc, clip, first ? [`[id="${id}"]`, ...byOtherKeys] : byOtherKeys);
439
475
  }
440
476
 
441
477
  export function findTimelineDomNodeForClip(
@@ -445,7 +481,7 @@ export function findTimelineDomNodeForClip(
445
481
  usedNodes = new Set<Element>(),
446
482
  getCandidates = () => getTimelineDomNodes(doc),
447
483
  ): Element | null {
448
- const byIdentity = clip.id ? findTimelineDomNode(doc, clip.id) : null;
484
+ const byIdentity = findTimelineDomNode(doc, clip);
449
485
  if (byIdentity && !usedNodes.has(byIdentity) && nodeMatchesClipTag(byIdentity, clip))
450
486
  return byIdentity;
451
487
 
@@ -1,6 +1,6 @@
1
1
  // @vitest-environment happy-dom
2
2
  import { describe, it, expect, beforeEach, vi } from "vitest";
3
- import { usePlayerStore, liveTime, type TimelineElement } from "./playerStore";
3
+ import { usePlayerStore, liveTime, whenPreviewBooted, type TimelineElement } from "./playerStore";
4
4
  import { thumbnailRevisionOf } from "./thumbnailSlice";
5
5
 
6
6
  /** The playback/selection state `reset()` restores (persistent prefs asserted separately). */
@@ -343,9 +343,20 @@ describe("usePlayerStore", () => {
343
343
  const video = doc.createElement("video");
344
344
  Object.defineProperty(video, "readyState", { value: 0, configurable: true });
345
345
  doc.body.appendChild(video);
346
+ usePlayerStore.getState().markPreviewLoadStep(doc);
346
347
  return { doc, video };
347
348
  }
348
349
 
350
+ it("waits for the document's load step before marking it ready", async () => {
351
+ const doc = document.implementation.createHTMLDocument("composition");
352
+ usePlayerStore.getState().requestTimelineReady(doc);
353
+ await new Promise((resolve) => setTimeout(resolve, 50));
354
+ expect(usePlayerStore.getState().timelineReady).toBe(false);
355
+
356
+ usePlayerStore.getState().markPreviewLoadStep(doc);
357
+ await vi.waitFor(() => expect(usePlayerStore.getState().timelineReady).toBe(true));
358
+ });
359
+
349
360
  it("resolves timelineReady once the doc's media settles", async () => {
350
361
  const { doc, video } = stalledVideoDoc();
351
362
  usePlayerStore.getState().requestTimelineReady(doc);
@@ -748,3 +759,26 @@ describe("liveTime", () => {
748
759
  expect(result).toBe(false);
749
760
  });
750
761
  });
762
+
763
+ describe("whenPreviewBooted", () => {
764
+ it("resolves false for a project the user left before its preview booted", async () => {
765
+ usePlayerStore.setState({ timelineProjectId: "project-a", previewBooted: false });
766
+ const waitA = whenPreviewBooted("project-a");
767
+
768
+ usePlayerStore.getState().beginTimelineSession("project-b");
769
+ usePlayerStore.getState().markPreviewBooted();
770
+
771
+ await expect(waitA).resolves.toBe(false);
772
+ await expect(whenPreviewBooted("project-b")).resolves.toBe(true);
773
+ });
774
+
775
+ it("waits through the open of its own project, then resolves on that project's boot", async () => {
776
+ usePlayerStore.setState({ timelineProjectId: "project-a", previewBooted: true });
777
+ const waitB = whenPreviewBooted("project-b");
778
+
779
+ usePlayerStore.getState().beginTimelineSession("project-b");
780
+ usePlayerStore.getState().markPreviewBooted();
781
+
782
+ await expect(waitB).resolves.toBe(true);
783
+ });
784
+ });
@@ -551,3 +551,32 @@ export const usePlayerStore = create<PlayerState>((set, get) => ({
551
551
  }));
552
552
 
553
553
  attachPlayerStoreDevHandle(usePlayerStore);
554
+
555
+ export function isPreviewBooted(projectId: string): boolean {
556
+ const { previewBooted, timelineProjectId } = usePlayerStore.getState();
557
+ return previewBooted && timelineProjectId === projectId;
558
+ }
559
+
560
+ /** True once projectId's live preview has booted, false once another project replaces it.
561
+ * Open-time work the first frame does not need (server parses, lint) waits on it. */
562
+ export function whenPreviewBooted(projectId: string): Promise<boolean> {
563
+ const openedFrom = usePlayerStore.getState().timelineProjectId;
564
+ let seen = false;
565
+ const settle = (state: PlayerState): boolean | null => {
566
+ if (state.timelineProjectId === projectId) {
567
+ seen = true;
568
+ return state.previewBooted ? true : null;
569
+ }
570
+ return seen || state.timelineProjectId !== openedFrom ? false : null;
571
+ };
572
+ return new Promise((resolve) => {
573
+ const now = settle(usePlayerStore.getState());
574
+ if (now !== null) return resolve(now);
575
+ const stop = usePlayerStore.subscribe((state) => {
576
+ const result = settle(state);
577
+ if (result === null) return;
578
+ stop();
579
+ resolve(result);
580
+ });
581
+ });
582
+ }
@@ -8,26 +8,45 @@ import { settleCompositionReadiness } from "@hyperframes/core/composition-readin
8
8
 
9
9
  export interface PlaybackReadinessSlice {
10
10
  timelineReady: boolean;
11
+ /** Latched when the project's first preview shows its first frame (or fails), kept through edit
12
+ * reloads, so work that must not compete with the boot waits for it once. */
13
+ previewBooted: boolean;
11
14
  setTimelineReady: (ready: boolean) => void;
15
+ markPreviewBooted: () => void;
12
16
  /** Sets timelineReady once doc's readiness inputs settle, or immediately
13
- * if doc is null. A wait a later call supersedes never wins the race. */
17
+ * if doc is null. A wait a later call supersedes never wins the race.
18
+ * Waits for doc's load step first: it pauses and rewinds the preview, so a
19
+ * Play enabled before it would be undone. */
14
20
  requestTimelineReady: (doc: Document | null) => void;
21
+ /** Called by the preview's load step for the document it ran on. */
22
+ markPreviewLoadStep: (doc: Document) => void;
15
23
  }
16
24
 
17
25
  let timelineReadyGeneration = 0;
26
+ const loadStepDocs = new WeakSet<Document>();
27
+ let awaitingLoadStep: { doc: Document; generation: number } | null = null;
18
28
 
19
29
  /** For a full timeline reset: bumps the generation so any requestTimelineReady
20
30
  * wait in flight can never resolve into what replaced it. */
21
- export function resetPlaybackReadinessState(): Pick<PlaybackReadinessSlice, "timelineReady"> {
31
+ export function resetPlaybackReadinessState(): Pick<
32
+ PlaybackReadinessSlice,
33
+ "timelineReady" | "previewBooted"
34
+ > {
22
35
  timelineReadyGeneration++;
23
- return { timelineReady: false };
36
+ return { timelineReady: false, previewBooted: false };
24
37
  }
25
38
 
26
39
  export function createPlaybackReadinessSlice(
27
40
  set: StoreApi<PlaybackReadinessSlice>["setState"],
28
41
  ): PlaybackReadinessSlice {
42
+ const settle = (doc: Document, generation: number) =>
43
+ settleCompositionReadiness(doc, () => {
44
+ if (generation === timelineReadyGeneration) set({ timelineReady: true });
45
+ });
29
46
  return {
30
47
  timelineReady: false,
48
+ previewBooted: false,
49
+ markPreviewBooted: () => set({ previewBooted: true }),
31
50
  setTimelineReady: (ready) => {
32
51
  timelineReadyGeneration++;
33
52
  set({ timelineReady: ready });
@@ -35,9 +54,18 @@ export function createPlaybackReadinessSlice(
35
54
  requestTimelineReady: (doc) => {
36
55
  const generation = ++timelineReadyGeneration;
37
56
  if (!doc) return set({ timelineReady: true });
38
- settleCompositionReadiness(doc, () => {
39
- if (generation === timelineReadyGeneration) set({ timelineReady: true });
40
- });
57
+ if (!loadStepDocs.has(doc)) {
58
+ awaitingLoadStep = { doc, generation };
59
+ return;
60
+ }
61
+ settle(doc, generation);
62
+ },
63
+ markPreviewLoadStep: (doc) => {
64
+ loadStepDocs.add(doc);
65
+ if (awaitingLoadStep?.doc !== doc) return;
66
+ const { generation } = awaitingLoadStep;
67
+ awaitingLoadStep = null;
68
+ settle(doc, generation);
41
69
  },
42
70
  };
43
71
  }
@@ -9,7 +9,6 @@ import { collectHtmlIds } from "./studioHelpers";
9
9
  import { generateId } from "./generateId";
10
10
  import { formatTimelineAttributeNumber } from "../player/components/timelineEditing";
11
11
  import { saveProjectFilesWithHistory } from "./studioFileHistory";
12
- import type { EditHistoryKind } from "./editHistory";
13
12
  import { extendRootDurationInSource } from "./rootDuration";
14
13
  import { deriveTimelineStoreKeyForDomId } from "../player/lib/timelineElementHelpers";
15
14
 
@@ -41,7 +40,6 @@ interface AddBlockOptions {
41
40
  writeProjectFile: (path: string, content: string) => Promise<void>;
42
41
  recordEdit: (entry: {
43
42
  label: string;
44
- kind: EditHistoryKind;
45
43
  coalesceKey?: string;
46
44
  files: Record<string, { before: string; after: string }>;
47
45
  }) => Promise<void>;
@@ -234,7 +232,6 @@ export async function addBlockToProject(
234
232
  await saveProjectFilesWithHistory({
235
233
  projectId,
236
234
  label: `Add ${isBlock ? "block" : "component"}: ${block.title}`,
237
- kind: "timeline",
238
235
  files: { [targetPath]: patchedContent },
239
236
  readFile: async () => originalContent,
240
237
  writeFile: writeProjectFile,
@@ -0,0 +1,22 @@
1
+ // @vitest-environment happy-dom
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { prefetchPreviewForHash } from "./previewPrefetch";
4
+
5
+ describe("prefetchPreviewForHash", () => {
6
+ afterEach(() => vi.unstubAllGlobals());
7
+
8
+ it("requests the opening project's preview once, not on every hash update", () => {
9
+ const fetchStub = vi.fn(() => Promise.resolve(new Response("")));
10
+ vi.stubGlobal("fetch", fetchStub);
11
+
12
+ prefetchPreviewForHash("#project/demo");
13
+ prefetchPreviewForHash("#project/demo?t=2&tab=design");
14
+ prefetchPreviewForHash("#project/other");
15
+ prefetchPreviewForHash("#settings");
16
+
17
+ expect(fetchStub.mock.calls.map(([url]) => new URL(String(url)).pathname)).toEqual([
18
+ "/api/projects/demo/preview",
19
+ "/api/projects/other/preview",
20
+ ]);
21
+ });
22
+ });
@@ -0,0 +1,14 @@
1
+ import { applyPreviewVariablesToUrl } from "../hooks/previewVariablesStore";
2
+ import { buildProjectApiPath, parseProjectIdFromHash } from "./projectRouting";
3
+
4
+ let prefetched: string | null = null;
5
+
6
+ /** Requests the URL's project preview while Studio boots, so the server builds it in parallel. */
7
+ export function prefetchPreviewForHash(hash: string): void {
8
+ const projectId = parseProjectIdFromHash(hash);
9
+ if (!projectId || projectId === prefetched) return;
10
+ prefetched = projectId;
11
+ const url = new URL(buildProjectApiPath(projectId, "/preview"), window.location.origin);
12
+ applyPreviewVariablesToUrl(url);
13
+ fetch(url).catch(() => undefined);
14
+ }
@@ -133,7 +133,6 @@ describe("runAtomicCutTransaction", () => {
133
133
  expect(writeProjectFile).not.toHaveBeenCalled();
134
134
  expect(recordEdit).toHaveBeenCalledWith({
135
135
  label: "Split timeline clip",
136
- kind: "timeline",
137
136
  files: { "index.html": { before: "before", after: "after" } },
138
137
  });
139
138
  expect(observe).toHaveBeenCalledWith("index.html", '"v1"');
@@ -181,7 +181,7 @@ export function runAtomicCutTransaction(input: RunAtomicCutInput): Promise<Atomi
181
181
  result.files.map((file) => [file.path, { before: file.before, after: file.after }]),
182
182
  );
183
183
  try {
184
- await input.recordEdit({ label: input.label, kind: "timeline", files: snapshots });
184
+ await input.recordEdit({ label: input.label, files: snapshots });
185
185
  } catch (error) {
186
186
  try {
187
187
  await rollbackUnrecordedCut(result.files, input.writeProjectFile);
@@ -1,5 +1,4 @@
1
1
  import { openComposition, type Composition } from "@hyperframes/sdk";
2
- import type { EditHistoryKind } from "./editHistory";
3
2
  import { hashContent, markSelfWrite } from "../hooks/sdkSelfWriteRegistry";
4
3
  import { trackStudioEvent } from "./studioTelemetry";
5
4
  import { serializeStudioFileMutation } from "./studioFileMutationCoordinator";
@@ -32,7 +31,6 @@ export interface CutoverDeps {
32
31
  editHistory: {
33
32
  recordEdit: (entry: {
34
33
  label: string;
35
- kind: EditHistoryKind;
36
34
  coalesceKey?: string;
37
35
  coalesceMs?: number;
38
36
  files: Record<string, { before: string; after: string }>;
@@ -248,7 +246,6 @@ async function writeAndRecord(
248
246
  try {
249
247
  await deps.editHistory.recordEdit({
250
248
  label: options?.label ?? "Edit layer",
251
- kind: "manual",
252
249
  ...(options?.coalesceKey ? { coalesceKey: options.coalesceKey } : {}),
253
250
  ...(options?.coalesceMs != null ? { coalesceMs: options.coalesceMs } : {}),
254
251
  files: { [targetPath]: { before: originalContent, after } },
@@ -11,7 +11,6 @@ describe("saveProjectFilesWithHistory", () => {
11
11
  await saveProjectFilesWithHistory({
12
12
  projectId: "project-1",
13
13
  label: "Move layer",
14
- kind: "manual",
15
14
  files: { "index.html": "after" },
16
15
  readFile: async (path) => reads[path],
17
16
  writeFile: async (path, content) => {
@@ -23,7 +22,6 @@ describe("saveProjectFilesWithHistory", () => {
23
22
  expect(writes).toEqual({ "index.html": "after" });
24
23
  expect(recordEdit).toHaveBeenCalledWith({
25
24
  label: "Move layer",
26
- kind: "manual",
27
25
  coalesceKey: undefined,
28
26
  files: { "index.html": { before: "before", after: "after" } },
29
27
  });
@@ -42,7 +40,6 @@ describe("saveProjectFilesWithHistory", () => {
42
40
  await saveProjectFilesWithHistory({
43
41
  projectId: "project-1",
44
42
  label: "Delete timeline clip",
45
- kind: "timeline",
46
43
  files: { "index.html": "removed+shrunk" },
47
44
  readFile: async () => "original",
48
45
  diskContent: { "index.html": "removed" },
@@ -67,7 +64,6 @@ describe("saveProjectFilesWithHistory", () => {
67
64
  await saveProjectFilesWithHistory({
68
65
  projectId: "project-1",
69
66
  label: "Move layer",
70
- kind: "manual",
71
67
  files: { "index.html": "after" },
72
68
  readFile: async () => "before",
73
69
  writeFile: async (path, _content, expectedContent) => {
@@ -85,7 +81,6 @@ describe("saveProjectFilesWithHistory", () => {
85
81
  const changedPaths = await saveProjectFilesWithHistory({
86
82
  projectId: "project-1",
87
83
  label: "Edit layer",
88
- kind: "manual",
89
84
  files: { "index.html": "same" },
90
85
  readFile: async () => "same",
91
86
  writeFile,
@@ -109,7 +104,6 @@ describe("saveProjectFilesWithHistory", () => {
109
104
  saveProjectFilesWithHistory({
110
105
  projectId: "project-1",
111
106
  label: "Move layer",
112
- kind: "manual",
113
107
  files: {
114
108
  "index.html": "index-after",
115
109
  "scene.html": "scene-after",
@@ -144,7 +138,6 @@ describe("saveProjectFilesWithHistory", () => {
144
138
  saveProjectFilesWithHistory({
145
139
  projectId: "project-1",
146
140
  label: "Move layer",
147
- kind: "manual",
148
141
  files: {
149
142
  "index.html": "index-after",
150
143
  "scene.html": "scene-after",
@@ -178,7 +171,6 @@ describe("saveProjectFilesWithHistory", () => {
178
171
  saveProjectFilesWithHistory({
179
172
  projectId: "project-1",
180
173
  label: "Move layer",
181
- kind: "manual",
182
174
  files: {
183
175
  "index.html": "index-after",
184
176
  "scene.html": "scene-after",
@@ -223,7 +215,6 @@ describe("saveProjectFilesWithHistory", () => {
223
215
  const save = saveProjectFilesWithHistory({
224
216
  projectId: "project-1",
225
217
  label: "Edit source",
226
- kind: "source",
227
218
  files: { "index.html": "editor-after" },
228
219
  readFile,
229
220
  writeFile,
@@ -1,12 +1,10 @@
1
1
  import { buildProjectApiPath } from "./projectRouting";
2
2
  import type { MutableRefObject } from "react";
3
- import type { EditHistoryKind } from "./editHistory";
4
3
  import { serializeStudioFileMutations } from "./studioFileMutationCoordinator";
5
4
  import { createStudioSaveHttpError } from "./studioSaveDiagnostics";
6
5
 
7
6
  export interface RecordEditInput {
8
7
  label: string;
9
- kind: EditHistoryKind;
10
8
  coalesceKey?: string;
11
9
  coalesceMs?: number;
12
10
  files: Record<string, { before: string; after: string }>;
@@ -27,7 +25,6 @@ type ProjectFileWriter = (path: string, content: string, expectedContent?: strin
27
25
  interface SaveProjectFilesWithHistoryInput {
28
26
  projectId: string;
29
27
  label: string;
30
- kind: EditHistoryKind;
31
28
  coalesceKey?: string;
32
29
  coalesceMs?: number;
33
30
  files: Record<string, string>;
@@ -65,7 +62,6 @@ export async function readProjectFileContent(pid: string, path: string): Promise
65
62
 
66
63
  export async function saveProjectFilesWithHistory({
67
64
  label,
68
- kind,
69
65
  coalesceKey,
70
66
  coalesceMs,
71
67
  files,
@@ -93,7 +89,7 @@ export async function saveProjectFilesWithHistory({
93
89
  writtenPaths.push(path);
94
90
  }
95
91
 
96
- await recordEdit({ label, kind, coalesceKey, coalesceMs, files: snapshots });
92
+ await recordEdit({ label, coalesceKey, coalesceMs, files: snapshots });
97
93
  } catch (error) {
98
94
  try {
99
95
  for (const path of writtenPaths.reverse()) {
@@ -70,7 +70,6 @@ export async function commitTimelineCompositionInsertion(input: {
70
70
  try {
71
71
  await input.recordEdit({
72
72
  label: "Add composition to timeline",
73
- kind: "timeline",
74
73
  files: { [input.targetPath]: { before: result.before, after: result.after } },
75
74
  });
76
75
  } catch (error) {