buildNodeTree now delegates its structural walk (ColumnLayout linkedNodes,
SHELL_INNER wrapping, style:[]->{} normalization) to flattenTreeForCraft,
materializing each flat node into a real Craft.js Node via
query.parseFreshNode — except the synthetic SHELL_INNER wrapper, which is
still hand-built (parseFreshNode would merge in Container's default
craft.props and change its look). sanitizeAiTree now lives in craft-tree.ts
and is re-exported here for existing callers.
Delete the dead serializeTreeForCraft (only its own tests called it) and
its now-orphaned tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an AI-boundary validation/sanitization pass in apply-ai-response.ts so
malformed or malicious Sitesmith output can never crash addNodeTree or
corrupt live craft state:
- resolvedName allowlist: any node whose type.resolvedName is not a
registered component in componentResolver is dropped (with its subtree)
and a console.warn is logged; buildNodeTree throws before touching
Craft.js if the tree ROOT itself is invalid, which existing call sites
already catch and warn on (fail soft, never throws into addNodeTree).
- update_props protected-key list (node_id) — an AI-supplied node_id can no
longer overwrite the target node's real id; style is merged shallowly
instead of replaced wholesale so unrelated existing style keys survive.
- id policy: regenerate, never skip. Any AI-supplied node id that is
'ROOT', empty/non-string, or collides with an existing/already-used id is
replaced with a fresh ai-auto-N id; the node itself is kept.
Extends apply-ai-response.test.ts with coverage for all of the above plus
regression tests proving fully-valid AI responses still apply exactly as
before.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>