fix(studio): make sdk cutover transactional (#2155)

This commit is contained in:
James Russo
2026-07-15 10:29:22 -04:00
committed by GitHub
parent 7d21cc9b8a
commit 42055296ee
45 changed files with 2481 additions and 654 deletions
@@ -1,7 +1,11 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { createEmptyEditHistory } from "../utils/editHistory";
import type { EditHistoryStorageAdapter } from "../utils/editHistoryStorage";
import { createMemoryEditHistoryStorage } from "../utils/editHistoryStorage";
import {
serializeStudioFileMutation,
serializeStudioFileMutations,
} from "../utils/studioFileMutationCoordinator";
import {
createPersistentEditHistoryController,
createPersistentEditHistoryStore,
@@ -213,6 +217,50 @@ describe("createPersistentEditHistoryController", () => {
expect(store.snapshot().canRedo).toBe(true);
});
it("waits for same-file mutations before checking an undo hash", async () => {
const storage = createMemoryEditHistoryStorage();
const store = createPersistentEditHistoryStore({
projectId: "project-1",
storage,
initialState: createEmptyEditHistory(),
now: () => 100,
onChange: () => {},
});
await store.recordEdit({
label: "Edit source",
kind: "source",
files: { "index.html": { before: "before", after: "after" } },
});
let disk = "after";
let release!: () => void;
const blocked = new Promise<void>((resolve) => {
release = resolve;
});
const writeFile = vi.fn(async (_path: string, content: string) => {
disk = content;
});
const priorMutation = serializeStudioFileMutation(writeFile, "index.html", async () => {
await blocked;
disk = "newer-edit";
});
const readFile = vi.fn(async () => disk);
const undo = store.undo({
readFile,
writeFile,
serialize: (paths, task) => serializeStudioFileMutations(writeFile, paths, task),
});
await Promise.resolve();
expect(readFile).not.toHaveBeenCalled();
release();
await priorMutation;
await expect(undo).resolves.toMatchObject({ ok: false, reason: "content-mismatch" });
expect(disk).toBe("newer-edit");
expect(writeFile).not.toHaveBeenCalled();
expect(store.snapshot().canUndo).toBe(true);
});
it("returns per-file restored/previous content so the preview can soft-apply", async () => {
const storage = createMemoryEditHistoryStorage();
const store = createPersistentEditHistoryStore({