sitesmith: route ColumnLayout children through linkedNodes (Invariant fix)
ColumnLayout's render uses <Element id="col-0" is={Container} canvas>
which expects the columns to live in linkedNodes, not data.nodes. The
AI nests its column containers as direct children, so they'd land in
data.nodes — Craft.js's render ignores them (the layout draws fresh
empty Elements), but the orphaned children remain in state with
parent: <columnlayout-id>. Any subsequent toNodeTree walk then trips
on this inconsistency and the uncaught Invariant kills the editor.
Normalizer added in two places — treeToState (for scope=site/page
replaces) and buildNodeTree (for scope=section inserts and patch ops):
when we see a ColumnLayout with direct children, move them into
linkedNodes keyed col-0/col-1/col-2..., clear data.nodes, set the
column nodes' isCanvas to true (they hold content), and sync the
"columns" prop to the actual count.
This commit is contained in:
@@ -325,6 +325,24 @@ export const PageProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
for (const child of node.nodes ?? []) {
|
for (const child of node.nodes ?? []) {
|
||||||
childIds.push(walk(child, id));
|
childIds.push(walk(child, id));
|
||||||
}
|
}
|
||||||
|
// ColumnLayout uses Craft.js linkedNodes with fixed ids (col-0, col-1, ...).
|
||||||
|
// The AI emits children as direct `nodes`, but ColumnLayout's render ignores
|
||||||
|
// them and creates fresh column Elements — the AI's children become orphans
|
||||||
|
// and any subsequent toNodeTree walk hits an Invariant. Move direct children
|
||||||
|
// into linkedNodes so they render in the columns the user actually sees.
|
||||||
|
if (typeName === 'ColumnLayout' && childIds.length > 0) {
|
||||||
|
const linked: Record<string, string> = {};
|
||||||
|
childIds.forEach((cid, i) => {
|
||||||
|
linked[`col-${i}`] = cid;
|
||||||
|
if (nodes[cid]) (nodes[cid] as any).isCanvas = true; // columns are canvases
|
||||||
|
});
|
||||||
|
(nodes[id] as any).nodes = [];
|
||||||
|
(nodes[id] as any).linkedNodes = linked;
|
||||||
|
// Reflect the actual column count on the component so its render matches.
|
||||||
|
const cur = (nodes[id] as any).props || {};
|
||||||
|
if (!cur.columns || cur.columns !== childIds.length) cur.columns = childIds.length;
|
||||||
|
(nodes[id] as any).props = cur;
|
||||||
|
}
|
||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
const rootId = walk(tree, null);
|
const rootId = walk(tree, null);
|
||||||
|
|||||||
@@ -86,6 +86,20 @@ function buildNodeTree(query: any, tree: SerializedTreeNode): NodeTree {
|
|||||||
const childId = walk(child, id);
|
const childId = walk(child, id);
|
||||||
craftNodes[id].data.nodes.push(childId);
|
craftNodes[id].data.nodes.push(childId);
|
||||||
}
|
}
|
||||||
|
// ColumnLayout uses linkedNodes (col-0, col-1, ...) — not direct children.
|
||||||
|
if (node.type.resolvedName === 'ColumnLayout' && craftNodes[id].data.nodes.length > 0) {
|
||||||
|
const linked: Record<string, string> = {};
|
||||||
|
craftNodes[id].data.nodes.forEach((cid: string, i: number) => {
|
||||||
|
linked[`col-${i}`] = cid;
|
||||||
|
if (craftNodes[cid]) craftNodes[cid].data.isCanvas = true;
|
||||||
|
});
|
||||||
|
craftNodes[id].data.nodes = [];
|
||||||
|
craftNodes[id].data.linkedNodes = linked;
|
||||||
|
const colCount = Object.keys(linked).length;
|
||||||
|
if (!craftNodes[id].data.props.columns || craftNodes[id].data.props.columns !== colCount) {
|
||||||
|
craftNodes[id].data.props.columns = colCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user