sitesmith: fix blank canvas on Replace site
treeToState() was setting isCanvas:true on every node, including leaf components (Heading, TextBlock, ButtonLink, Spacer, ImageBlock). Craft.js then renders those as empty drop-canvas wrappers instead of their actual content, so the canvas appears blank after applying an AI-generated 'replace' response. Now uses a CANVAS_TYPES set matching the apply-ai-response utility: only the layout wrappers (Container, Section, ColumnLayout, Hero/Features/ CTA sections, FormContainer, Navbar, Footer, etc.) are canvases. ROOT is forced to be a canvas regardless of source type so children render. Also defensively normalizes props.style: AI sometimes emits an empty array instead of an object, which can confuse downstream consumers.
This commit is contained in:
@@ -4,6 +4,17 @@ import { PageData } from '../types';
|
|||||||
import { SerializedTreeNode } from '../types/sitesmith';
|
import { SerializedTreeNode } from '../types/sitesmith';
|
||||||
import { useSiteDesign, SiteDesign } from './SiteDesignContext';
|
import { useSiteDesign, SiteDesign } from './SiteDesignContext';
|
||||||
|
|
||||||
|
/** Layout components that accept children. Must match the .craft.rules.canMoveIn
|
||||||
|
* config of each component. Leaf components (Heading, TextBlock, ButtonLink, …)
|
||||||
|
* are NOT canvases and must serialize with isCanvas:false or their rendered
|
||||||
|
* content gets swallowed by an empty drop-target wrapper. */
|
||||||
|
const CANVAS_TYPES = new Set<string>([
|
||||||
|
'Container', 'Section', 'ColumnLayout', 'BackgroundSection',
|
||||||
|
'HeaderZone', 'FooterZone',
|
||||||
|
'HeroSimple', 'FeaturesGrid', 'CTASection',
|
||||||
|
'FormContainer', 'Navbar', 'Footer',
|
||||||
|
]);
|
||||||
|
|
||||||
interface PageContextValue {
|
interface PageContextValue {
|
||||||
pages: PageData[];
|
pages: PageData[];
|
||||||
headerPage: PageData;
|
headerPage: PageData;
|
||||||
@@ -290,11 +301,21 @@ export const PageProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
const walk = (node: SerializedTreeNode, parent: string | null): string => {
|
const walk = (node: SerializedTreeNode, parent: string | null): string => {
|
||||||
const id = (node.props.node_id as string | undefined) || `ai-auto-${counter++}`;
|
const id = (node.props.node_id as string | undefined) || `ai-auto-${counter++}`;
|
||||||
const childIds: string[] = [];
|
const childIds: string[] = [];
|
||||||
|
const typeName = node.type?.resolvedName;
|
||||||
|
// Normalize props: the AI sometimes emits `style: []` instead of `{}`.
|
||||||
|
// React/Craft.js choke when a CSSProperties slot is an array — normalize it.
|
||||||
|
const rawProps = node.props ?? {};
|
||||||
|
const props: Record<string, unknown> = { ...rawProps };
|
||||||
|
if (Array.isArray(props.style)) props.style = {};
|
||||||
nodes[id] = {
|
nodes[id] = {
|
||||||
type: node.type,
|
type: node.type,
|
||||||
isCanvas: true,
|
// isCanvas must match the component's craft.rules — only layout
|
||||||
props: node.props,
|
// wrappers accept children. Setting it true on leaf components
|
||||||
displayName: node.type.resolvedName,
|
// (Heading, TextBlock, ButtonLink, etc) makes Craft.js render them
|
||||||
|
// as empty drop-canvas wrappers and the actual content disappears.
|
||||||
|
isCanvas: typeName ? CANVAS_TYPES.has(typeName) : false,
|
||||||
|
props,
|
||||||
|
displayName: typeName,
|
||||||
custom: {},
|
custom: {},
|
||||||
hidden: false,
|
hidden: false,
|
||||||
parent,
|
parent,
|
||||||
@@ -311,6 +332,8 @@ export const PageProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
if (rootId !== 'ROOT') {
|
if (rootId !== 'ROOT') {
|
||||||
nodes['ROOT'] = nodes[rootId];
|
nodes['ROOT'] = nodes[rootId];
|
||||||
(nodes['ROOT'] as any).parent = null;
|
(nodes['ROOT'] as any).parent = null;
|
||||||
|
// ROOT must be a canvas regardless of component type so children render.
|
||||||
|
(nodes['ROOT'] as any).isCanvas = true;
|
||||||
delete nodes[rootId];
|
delete nodes[rootId];
|
||||||
// Fix up parent references from ROOT's children
|
// Fix up parent references from ROOT's children
|
||||||
for (const childId of (nodes['ROOT'] as any).nodes) {
|
for (const childId of (nodes['ROOT'] as any).nodes) {
|
||||||
|
|||||||
Reference in New Issue
Block a user