chore(release): 版本发布 - publish v1.5.1

This commit is contained in:
ddCat
2026-09-18 15:26:26 +08:00
parent 4eaa28b807
commit 9eb2fcd453
35 changed files with 1127 additions and 304 deletions

View File

@@ -166,15 +166,59 @@ test("50 distinct media completions project once and keep the local viewport", a
expect(get).not.toHaveBeenCalled();
});
test("a missing Agent revision preserves the local draft and prevents automatic overwrite", async () => {
test("a delta gap reconciles against the cloud before declaring a conflict", async () => {
await setup();
await expect(applyAgentCanvasPatches(initial.id, [{ canvasId: initial.id, baseRevision: 2, revision: 3, nodes: [], connections: [] }])).rejects.toThrow("版本不连续");
expect(useCanvasStore.getState().projects[0].revision).toBe(1);
expect(useSyncProgressStore.getState().syncingProjects[initial.id]?.phase).not.toBe("conflict");
expect(await readCanvasSyncDrafts(initial.id)).toHaveLength(0);
await refreshCanvasAfterAgent(initial.id);
expect(useCanvasStore.getState().projects[0]).toMatchObject(remote);
expect(useSyncProgressStore.getState().syncingProjects[initial.id].phase).toBe("done");
});
test("reconciliation after a delta gap still protects genuinely conflicting edits", async () => {
await setup();
useCanvasStore.setState({ projects: [{ ...initial, title: "未保存标题" }] });
await expect(applyAgentCanvasPatches(initial.id, [{ canvasId: initial.id, baseRevision: 2, revision: 3, nodes: [], connections: [] }])).rejects.toThrow("版本不连续");
await expect(refreshCanvasAfterAgent(initial.id)).rejects.toThrow("本地存在未同步编辑");
expect(useSyncProgressStore.getState().syncingProjects[initial.id].phase).toBe("conflict");
expect((await readCanvasSyncDrafts(initial.id))[0].project.revision).toBe(1);
expect((await readCanvasSyncDrafts(initial.id))[0].project.title).toBe("未保存标题");
await expect(saveRemoteUserDataNow(initial.id)).rejects.toThrow("云端画布已有更新");
});
test("an identical cloud snapshot clears a stale conflict without replaying editor nodes", async () => {
await setup(initial);
useSyncProgressStore.getState().setProjectProgress(initial.id, { phase: "conflict", draftCount: 9 });
let deliveries = 0;
unsubscribe = subscribeAgentCanvasRefresh(() => { deliveries++; });
await refreshCanvasAfterAgent(initial.id);
expect(deliveries).toBe(0);
expect(useSyncProgressStore.getState().syncingProjects[initial.id]).toMatchObject({ phase: "done", draftCount: 9 });
await expect(saveRemoteUserDataNow(initial.id)).resolves.toBeUndefined();
});
test("replayed events against an unchanged cloud ancestor preserve pending local edits", async () => {
await setup(initial);
await refreshCanvasAfterAgent(initial.id);
const edited = { ...initial, title: "等待保存的标题" };
useCanvasStore.setState({ projects: [edited] });
await refreshCanvasAfterAgent(initial.id);
expect(useCanvasStore.getState().projects[0]).toBe(edited);
expect(useSyncProgressStore.getState().syncingProjects[initial.id].phase).toBe("pending");
expect(await readCanvasSyncDrafts(initial.id)).toHaveLength(0);
});
test("an unverified dirty cache cannot become a save baseline merely because revisions match", async () => {
await setup(initial);
const edited = { ...initial, title: "离线标题" };
useCanvasStore.setState({ projects: [edited] });
await initializeRemoteUserDataSession("agent-user");
await expect(refreshCanvasAfterAgent(initial.id)).rejects.toThrow("本地存在未同步编辑");
expect(useCanvasStore.getState().projects[0]).toBe(edited);
expect((await readCanvasSyncDrafts(initial.id))[0].project.title).toBe(edited.title);
});
test("Agent updates merge a local title edit and the next save uses the new revision", async () => {
await setup();
useCanvasStore.setState({ projects: [{ ...initial, title: "本地标题" }] });

View File

@@ -4,7 +4,8 @@ import { apiClient } from "../src/services/api/request";
import { canvasContentHash } from "../src/lib/canvas/canvas-content";
import { rebaseCanvasProjects, parseCanvasStorageDocument } from "../src/lib/canvas/canvas-storage-revision";
import { readCanvasSyncDrafts } from "../src/services/canvas-sync-drafts";
import { initializeRemoteUserDataSession, installRemoteUserDataAutoSync, loadCanvasProjectForEditing, resetRemoteUserDataSync, saveRemoteUserDataNow, syncRemoteUserData } from "../src/services/user-data-sync";
import { applyAgentCanvasPatches, refreshCanvasAfterAgent, initializeRemoteUserDataSession, installRemoteUserDataAutoSync, loadCanvasProjectForEditing, resetRemoteUserDataSync, saveRemoteUserDataNow, syncRemoteUserData } from "../src/services/user-data-sync";
import { createAgentCanvasSync } from "../src/services/agent-canvas-sync";
import { flushCanvasStorePersistence, useCanvasStore, type CanvasProject } from "../src/stores/canvas/use-canvas-store";
import { flushAssetStorePersistence, useAssetStore } from "../src/stores/use-asset-store";
import { useSyncProgressStore } from "../src/stores/use-sync-progress-store";
@@ -135,6 +136,42 @@ test("stale viewport, no-op restore and open do not submit old content", async (
expect(requests.filter((request) => request.method === "put")).toHaveLength(0);
});
test("load latest, save and reload stay synced when Agent history replays an unversioned delta", async () => {
remote.set("canvas", { ...canvas(), revision: 9 });
await loadCanvasProjectForEditing("canvas", { latest: true });
useCanvasStore.getState().renameProject("canvas", "已保存的标题");
await saveRemoteUserDataNow("canvas");
expect(remote.get("canvas")!.revision).toBe(10);
for (let reload = 0; reload < 3; reload++) {
resetRemoteUserDataSync();
await useCanvasStore.persist.rehydrate();
await initializeRemoteUserDataSession(scope);
await loadCanvasProjectForEditing("canvas");
let resolve!: () => void;
let reject!: (error: unknown) => void;
const reconciled = new Promise<void>((done, fail) => { resolve = done; reject = fail; });
const sync = createAgentCanvasSync({
canvasId: "canvas", batchMs: 0,
applyPatches: (patches) => applyAgentCanvasPatches("canvas", patches),
refresh: async () => { await refreshCanvasAfterAgent("canvas"); resolve(); },
onError: reject,
});
try {
sync.receive({ eventId: "historical-event", runId: "completed-run", seq: 1, type: "canvas_updated", createdAt: "2026-09-17", payload: {
canvasId: "canvas",
canvasPatch: { canvasId: "canvas", updatedAt: "2026-09-17", nodes: [{ before: null, after: { ...canvas().nodes[0], id: "stale-node" } }], connections: [] },
} });
await reconciled;
expect(useSyncProgressStore.getState().syncingProjects.canvas.phase).toBe("done");
await saveRemoteUserDataNow("canvas");
expect(useCanvasStore.getState().openProject("canvas")).toMatchObject({ revision: 10, title: "已保存的标题", nodes: canvas().nodes });
expect(await readCanvasSyncDrafts("canvas")).toHaveLength(0);
} finally { sync.dispose(); }
}
expect(requests.filter((request) => request.method === "put")).toHaveLength(1);
});
test("a conflict preserves drafts, stops retries and does not block another canvas", async () => {
remote.set("canvas", { ...addNode(canvas(), "remote-video"), revision: 2 });
useCanvasStore.getState().updateProject("canvas", { nodes: addNode(canvas(), "local-video").nodes });