treeToCraftState's ROOT re-key branch reparented nodes['ROOT'].nodes
children to 'ROOT' but not nodes['ROOT'].linkedNodes children. For a
ColumnLayout- or Section/BackgroundSection/FormContainer-rooted AI
`replace`, flattenTreeForCraft puts content in linkedNodes (col-N /
section-inner etc.) whose parent was left pointing at the OLD root id
-- which is then deleted, leaving a dangling parent reference that
breaks select/move/delete of those nodes in the Craft.js editor.
Now the same loop that reparents nodes[] children also reparents
linkedNodes children.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
treeToState had no resolvedName allowlist, so an AI `replace` response
(scope site/page/header/footer, reaches this function via
actions.deserialize with no further validation downstream) containing an
unknown component could produce a state that throws at deserialize/render.
Extract the core transform into standalone treeToCraftState (directly
unit-testable without mounting a Craft.js Editor), route it through
sanitizeAiTree for the same resolvedName allowlist + id/ROOT repair
buildNodeTree already had, and fall back to the empty canvas if the tree's
own root is invalid. Delegate the structural walk to the shared
flattenTreeForCraft extracted in the previous commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>