diff --git a/app/api/canvas/[id]/route.ts b/app/api/canvas/[id]/route.ts index 88af0bd0d..5b8f76aa8 100644 --- a/app/api/canvas/[id]/route.ts +++ b/app/api/canvas/[id]/route.ts @@ -4,6 +4,10 @@ import { db } from '@/lib/db'; import { canvasProject } from '@/lib/db/schema'; import { syncCanvasNodesForProject } from '@/lib/db/queries'; import { eq, and } from 'drizzle-orm'; +import { + buildStaleCanvasSaveConflictPayload, + isStaleCanvasSaveBaseRevision, +} from '@/lib/canvas/canvas-save-conflict'; function resolveWorkspaceType(input: unknown) { return input === "agent" || input === "video" ? input : "canvas"; @@ -13,19 +17,6 @@ function getDefaultWorkspaceTitle(workspaceType: ReturnType { + test("rejects stale autosave bases without blocking fresh or missing revisions", () => { + const currentUpdatedAt = new Date("2026-06-22T00:00:05.500Z"); + + expect(parseCanvasRevisionDate("not-a-date")).toBeNull(); + expect( + isStaleCanvasSaveBaseRevision({ + baseCanvasRevision: "2026-06-22T00:00:00.000Z", + currentUpdatedAt, + }) + ).toBe(true); + expect( + isStaleCanvasSaveBaseRevision({ + baseCanvasUpdatedAt: "2026-06-22T00:00:05.000Z", + currentUpdatedAt, + }) + ).toBe(false); + expect( + isStaleCanvasSaveBaseRevision({ + currentUpdatedAt, + }) + ).toBe(false); + expect(buildStaleCanvasSaveConflictPayload(currentUpdatedAt)).toMatchObject({ + success: false, + code: "STALE_CANVAS_REVISION", + canvasUpdatedAt: "2026-06-22T00:00:05.500Z", + }); + }); + const plannerContext: CanvasAgentContext = { version: 1, source: "canvas",