diff --git a/craft/src/utils/apply-ai-response.test.ts b/craft/src/utils/apply-ai-response.test.ts index 26a361d..b06e84a 100644 --- a/craft/src/utils/apply-ai-response.test.ts +++ b/craft/src/utils/apply-ai-response.test.ts @@ -1,5 +1,6 @@ import { describe, test, expect, vi } from 'vitest'; import { sanitizeAiTree, __test } from './apply-ai-response'; +import type { SitesmithPatchOp } from '../types/sitesmith'; describe('findNodeIdByAiNodeId', () => { const query = { @@ -173,7 +174,7 @@ describe('applyPatch', () => { text: 'Old', style: { color: 'red', fontSize: '16px' }, }); - const ops = [ + const ops: SitesmithPatchOp[] = [ { op: 'update_props', node_id: 'ai-node-1', @@ -194,7 +195,7 @@ describe('applyPatch', () => { text: 'Old', style: { color: 'red', fontSize: '16px' }, }); - const ops = [ + const ops: SitesmithPatchOp[] = [ { op: 'update_props', node_id: 'ai-node-1', @@ -211,7 +212,7 @@ describe('applyPatch', () => { const { query, actions, propsById } = makeSingleNodeHarness('craft-1', 'ai-node-1', { style: { color: 'red', fontSize: '16px' }, }); - const ops = [ + const ops: SitesmithPatchOp[] = [ { op: 'update_props', node_id: 'ai-node-1', props: { style: { color: 'blue' } } }, ]; __test.applyPatch(actions, query, ops); @@ -221,7 +222,7 @@ describe('applyPatch', () => { test('an op with an unknown resolvedName tree is skipped without throwing; other ops in the batch still apply', () => { const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const { query, actions, propsById } = makeSingleNodeHarness('craft-1', 'ai-node-1', {}); - const ops = [ + const ops: SitesmithPatchOp[] = [ { op: 'insert_after', node_id: 'ai-node-1', diff --git a/craft/src/utils/apply-ai-response.ts b/craft/src/utils/apply-ai-response.ts index 814208b..0891372 100644 --- a/craft/src/utils/apply-ai-response.ts +++ b/craft/src/utils/apply-ai-response.ts @@ -1,7 +1,7 @@ import { useEditor } from '@craftjs/core'; import type { NodeTree } from '@craftjs/core'; import { usePages } from '../state/PageContext'; -import { SitesmithResponse, SerializedTreeNode } from '../types/sitesmith'; +import { SitesmithResponse, SerializedTreeNode, SitesmithPatchOp } from '../types/sitesmith'; import { sanitizeAiTree, flattenTreeForCraft } from './craft-tree'; // Re-exported for existing callers/tests that import sanitizeAiTree from @@ -155,7 +155,7 @@ export function useApplyAiResponse() { function applyPatch( actions: any, query: any, - ops: any[], + ops: SitesmithPatchOp[], ): { ok: boolean; message?: string } { for (const op of ops) { const id = findNodeIdByAiNodeId(query, op.node_id); @@ -237,7 +237,12 @@ function applyPatch( break; default: - console.warn('sitesmith patch: unknown op', (op as any).op); + // Every SitesmithPatchOp variant is handled above, so `op` is typed + // `never` here at compile time — but the AI response is untrusted + // JSON (see useSitesmith.ts's `j: SendResult = await r.json()`), so + // a malformed/unrecognized `op` field can still reach this branch at + // runtime. Log the raw value rather than assuming a shape. + console.warn('sitesmith patch: unknown op', op); } } return { ok: true };