import { test, expect } from "bun:test"; import { CORE_DESIGN_SYSTEMS, validateDesignSystem } from "../../design-system/src/index.ts"; import { generateDesigns, DEFAULT_DESIGN_BRIEF, type GenerationRequest, type GenerationProvider, } from "../src/generation.ts"; import { designRevision, checkDecisionLocks, preserveDecisionLocks } from "../src/decisions.ts"; import { combineDesignProposals } from "../src/guidance.ts"; import { StudioSession } from "../src/session.ts"; const source = () => structuredClone(CORE_DESIGN_SYSTEMS.square); async function request( kind: "directions" | "edit" = "directions", locks: string[] = [], system = source(), ): Promise { return { system, locks, brief: { ...DEFAULT_DESIGN_BRIEF, product: "Booking site", audience: "Mobile customers" }, baseRevision: await designRevision(system, locks), kind, instruction: kind === "edit" ? "make buttons rounded and use spacious spacing" : undefined, }; } const patched = (changes: unknown[]) => JSON.stringify({ alternatives: [{ name: "Edited", rationale: "Requested change", patch: changes }], }); const set = (path: string, value: unknown) => ({ op: "set", path, value }); test("local guided generation produces three distinct validated directions without changing source", async () => { const input = await request(); const original = JSON.stringify(input.system); const result = await generateDesigns(input); expect(result.diagnostics).toEqual([]); expect(result.alternatives).toHaveLength(3); expect(new Set(result.alternatives.map((p) => JSON.stringify(p.system))).size).toBe(3); for (const proposal of result.alternatives) { expect(validateDesignSystem(proposal.system)).toEqual([]); expect(Object.keys(proposal.system.recipes!)).toHaveLength(104); expect(proposal.system.id).toBe(input.system.id); } expect(JSON.stringify(input.system)).toBe(original); expect(result.provenance.provider).toBe("local-rules"); expect(result.provenance.maxOutputTokens).toBe(0); }); test("guided geometry, density, surface and brand choices affect the brief direction", async () => { const input = await request(); input.brief = { ...input.brief, geometry: "rounded", density: "spacious", surface: "soft", brandColor: "#112233", }; const result = await generateDesigns(input); expect(result.diagnostics).toEqual([]); const system = result.alternatives[0]!.system; expect(system.tokens["shape.control"]!.value).toBe("16px"); expect(system.tokens["space.control"]!.value).toBe("1.25rem"); expect(system.tokens["elevation.panel"]!.value).toContain("24px"); expect(system.modes!.light!["color.primary"]!.value).toBe("#112233"); expect(result.brief).toEqual(input.brief); }); test("direct locks, mode overrides and alias changes cannot alter a locked token", () => { const base = source(); base.tokens["shape.alias"] = { type: "dimension", value: { ref: "shape.control" } }; let next = structuredClone(base); next.tokens["shape.control"]!.value = "8px"; expect(checkDecisionLocks(base, next, ["/tokens/shape.alias"])).not.toEqual([]); next = structuredClone(base); next.modes!.light!["shape.control"] = { type: "dimension", value: "8px" }; expect(checkDecisionLocks(base, next, ["/tokens/shape.alias"])).not.toEqual([]); next = structuredClone(base); next.tokens["space.page"]!.value = "5rem"; expect(checkDecisionLocks(base, next, ["/tokens/shape.alias"])).toEqual([]); }); test("component recipe locks protect referenced token values and absent decisions", () => { const base = source(), next = structuredClone(base); next.tokens["shape.control"]!.value = "8px"; expect(checkDecisionLocks(base, next, ["/recipes/Button"])).not.toEqual([]); const added = structuredClone(base); added.recipes!.Button!.base!.control!.rotate = "2deg"; expect(checkDecisionLocks(base, added, ["/recipes/Button/base/control/rotate"])).not.toEqual([]); }); test("directions preserve locks, including recipe token dependencies", async () => { const input = await request("directions", ["/recipes/Button"]); const result = await generateDesigns(input); expect(result.diagnostics).toEqual([]); for (const p of result.alternatives) expect(checkDecisionLocks(input.system, p.system, input.locks)).toEqual([]); const next = preserveDecisionLocks( input.system, structuredClone(CORE_DESIGN_SYSTEMS.soft), input.locks, ); expect(checkDecisionLocks(input.system, next, input.locks)).toEqual([]); }); test("manual edits, undo, reload and unlock keep lock-violating drafts recoverable", () => { const memory = new Map(); const storage = { getItem: (k: string) => memory.get(k) ?? null, setItem: (k: string, v: string) => { memory.set(k, v); }, }; const session = new StudioSession(undefined, storage); session.setLocks(["/tokens/shape.control"]); session.mutate((s) => { s.tokens["shape.control"]!.value = "9px"; }, "Locked edit"); expect(session.valid.tokens["shape.control"]!.value).toBe("0"); expect(session.diagnostics[0]!.code).toBe("WRN-STUDIO-LOCK"); expect(session.source).toContain("9px"); const restored = new StudioSession(undefined, storage); expect(restored.restore()).toBe(true); expect(restored.locks).toEqual(session.locks); expect(restored.diagnostics[0]!.code).toBe("WRN-STUDIO-LOCK"); restored.setLocks([]); expect(restored.valid.tokens["shape.control"]!.value).toBe("9px"); restored.undo(); expect(restored.valid.tokens["shape.control"]!.value).toBe("0"); }); test("word-based edits validate whole requests and reject unknown or locked changes", async () => { const result = await generateDesigns(await request("edit")); expect(result.diagnostics).toEqual([]); expect(result.alternatives[0]!.system.tokens["shape.control"]!.value).toBe("16px"); const locked = await generateDesigns(await request("edit", ["/tokens/shape.control"])); expect(locked.alternatives).toEqual([]); expect(locked.diagnostics[0]!.message).toContain("LOCK"); const input = await request("edit"); input.instruction = "make buttons rounded and invent a new calendar"; const failed = await generateDesigns(input); expect(failed.alternatives).toEqual([]); expect(input.system.tokens["shape.control"]!.value).toBe("0"); input.instruction = "set primary color to #aabbcc and remove shadows and use serif typography"; expect((await generateDesigns(input)).diagnostics).toEqual([]); }); test("stale generation base and stale combinations fail without applying", async () => { const input = await request(); input.baseRevision = "a".repeat(64); expect(generateDesigns(input)).rejects.toThrow("WRN-STUDIO-CONFLICT"); const good = await request(); const batch = await generateDesigns(good); const next = structuredClone(good.system); next.name = "Changed"; await expect(combineDesignProposals(next, batch, { colors: "direction-2" }, [])).rejects.toThrow( "CONFLICT", ); }); test("foundation decisions combine and revalidate before entering a draft", async () => { const input = await request(); const batch = await generateDesigns(input); const next = await combineDesignProposals( input.system, batch, { geometry: "direction-2", typography: "direction-3" }, [], ); expect(validateDesignSystem(next)).toEqual([]); expect(next.tokens["shape.control"]).toEqual( batch.alternatives[1]!.system.tokens["shape.control"], ); expect(next.tokens["type.body"]).toEqual(batch.alternatives[2]!.system.tokens["type.body"]); }); test("provider adapter uses bounded patches, compact capabilities and one repair", async () => { let calls = 0; let context = ""; const provider: GenerationProvider = { name: "fixture", async generate(prompt, options) { calls++; context = prompt; expect(options!.maxTokens).toBe(512); expect(options!.signal).toBeDefined(); return { provider: "fixture", model: "test-model", value: calls === 1 ? "not json" : patched([set("/tokens/shape.control/value", "11px")]), }; }, }; const result = await generateDesigns(await request("edit"), { provider, maxOutputTokens: 512 }); expect(calls).toBe(2); expect(result.diagnostics).toEqual([]); expect(result.provenance.model).toBe("test-model"); expect(result.provenance.attempts).toBe(2); expect(context).toContain("capabilities"); expect(context).toContain("Repair these diagnostics"); }); test("provider invalid parts and lock violations cannot survive bounded repair", async () => { let calls = 0; const provider: GenerationProvider = { name: "fixture", async generate() { calls++; return { provider: "fixture", value: patched([set("/tokens/shape.control/value", "12px")]) }; }, }; const result = await generateDesigns(await request("edit", ["/tokens/shape.control"]), { provider, }); expect(calls).toBe(2); expect(result.alternatives).toEqual([]); expect(result.diagnostics[0]!.code).toBe("WRN-STUDIO-PROPOSAL"); const invalid: GenerationProvider = { name: "fixture", async generate() { return { provider: "fixture", value: patched([set("/recipes/Button/base/control/position", "fixed")]), }; }, }; expect( (await generateDesigns(await request("edit"), { provider: invalid })).alternatives, ).toEqual([]); }); test("prototype paths, excessive operations and execution strings are rejected", async () => { for (const patch of [ [set("/tokens/__proto__/value", "x")], Array.from({ length: 49 }, () => set("/name", "x")), [set("/tokens/shape.control/value", "0;}body{display:none")], ]) { const provider: GenerationProvider = { name: "fixture", async generate() { return { provider: "fixture", value: patched(patch) }; }, }; expect((await generateDesigns(await request("edit"), { provider })).alternatives).toEqual([]); } }); test("cancellation and deadline stop even a provider that ignores its signal", async () => { const provider: GenerationProvider = { name: "hung", generate: () => new Promise(() => {}) }; const controller = new AbortController(); const input = await request("edit"); const pending = generateDesigns(input, { provider, signal: controller.signal }); controller.abort(); await expect(pending).rejects.toThrow(); await expect(generateDesigns(input, { provider, timeoutMs: 15 })).rejects.toThrow("cancelled"); expect(input.system.tokens["shape.control"]!.value).toBe("0"); }); test("transport failure is not retried or returned with secret-bearing messages", async () => { let count = 0; const provider: GenerationProvider = { name: "fixture", async generate() { count++; throw Error("secret-key-in-provider-error"); }, }; const result = await generateDesigns(await request("edit"), { provider }); expect(count).toBe(1); expect(JSON.stringify(result)).not.toContain("secret-key-in-provider-error"); expect(result.diagnostics[0]!.code).toBe("WRN-STUDIO-PROVIDER"); }); test("untrusted brief fields and generation budgets are bounded", async () => { const input = await request(); input.brief.product = "x".repeat(2001); await expect(generateDesigns(input)).rejects.toThrow("2000"); await expect(generateDesigns(await request(), { maxOutputTokens: 9000 })).rejects.toThrow( "BUDGET", ); const altered = await request(); altered.brief.brandColor = "red;}body{}"; await expect(generateDesigns(altered)).rejects.toThrow("BRIEF"); });