diff --git a/packages/studio/src/hooks/usePersistentEditHistory.test.ts b/packages/studio/src/hooks/usePersistentEditHistory.test.ts index 06c69b5668c..549418ce4fb 100644 --- a/packages/studio/src/hooks/usePersistentEditHistory.test.ts +++ b/packages/studio/src/hooks/usePersistentEditHistory.test.ts @@ -62,7 +62,7 @@ async function studio({ withHistory = true } = {}) { const file = () => readFileSync(join(dir, "index.html"), "utf8"); const save = (content: string) => writeFileSync(join(dir, "index.html"), content); const readFile = async (path: string) => readFileSync(join(dir, path), "utf8"); - return { dir, hook: () => hook, file, save, readFile }; + return { dir, history, hook: () => hook, file, save, readFile }; } it("an edit Studio saved is undone and redone by the project's history, with the preview's before and after", async () => { @@ -411,6 +411,30 @@ it("a key one page holds does not join another page's edit under the same key", expect(file()).toBe("B"); }); +it("a host's write claimed under claimKey joins Studio's save under the same key: one undo takes back both", async () => { + const { dir, history, hook, file, save, readFile } = await studio(); + const key = "drop:1"; + writeFileSync(join(dir, "clip.mp4"), "media"); + await history.claim({ kind: "person", name: "You" }, "Dropped on timeline", ["clip.mp4"], { + coalesceKey: hook().claimKey(key), + idleMs: Infinity, + }); + save("B"); + await act(() => + hook().recordEdit({ + label: "Dropped on timeline", + coalesceKey: key, + coalesceMs: Infinity, + files: { "index.html": { before: "A", after: "B" } }, + }), + ); + + await act(() => hook().undo({ readFile })); + + expect(file()).toBe("A"); + expect(existsSync(join(dir, "clip.mp4"))).toBe(false); +}); + it("an undo before the history view shows a drag's held claim still waits on the files it wrote", async () => { const { hook, save, readFile } = await studio(); const server = globalThis.fetch; diff --git a/packages/studio/src/hooks/usePersistentEditHistory.ts b/packages/studio/src/hooks/usePersistentEditHistory.ts index 28b6035e86f..6e0e8f18d20 100644 --- a/packages/studio/src/hooks/usePersistentEditHistory.ts +++ b/packages/studio/src/hooks/usePersistentEditHistory.ts @@ -209,6 +209,7 @@ export function usePersistentEditHistory({ projectId }: UsePersistentEditHistory projectIdRef.current = projectId; const [own] = useState(createOwnHistory); const [pageKeyScope] = useState(generateId); + const claimKey = useCallback((key: string) => `${pageKeyScope}:${key}`, [pageKeyScope]); const refresh = useCallback(async () => { if (!projectId) return; @@ -246,7 +247,7 @@ export function usePersistentEditHistory({ projectId }: UsePersistentEditHistory paths, overwrote: await overwroteVersions(files), ...(coalesceKey && { - coalesceKey: `${pageKeyScope}:${coalesceKey}`, + coalesceKey: claimKey(coalesceKey), idleMs: coalesceMs ?? DEFAULT_COALESCE_MS, }), }); @@ -258,7 +259,7 @@ export function usePersistentEditHistory({ projectId }: UsePersistentEditHistory heldClaimRef.current = claimed && coalesceKey ? { paths, at: Date.now() } : null; await refresh(); }, - [projectId, refresh, own, pageKeyScope], + [projectId, refresh, own, claimKey], ); const step = useCallback( @@ -341,6 +342,7 @@ export function usePersistentEditHistory({ projectId }: UsePersistentEditHistory redoPaths: view.forward?.paths ?? [], state, recordEdit, + claimKey, undo, redo, predict: own.predict,