sitesmith: narrow CANVAS_TYPES to just Container

The canonical Craft.js state from real saves shows that layout shells
(Section, BackgroundSection, HeroSimple, FeaturesGrid, ColumnLayout,
CTASection, FormContainer, Navbar, Footer) all serialize with
isCanvas:false. Only Container instances are canvases. The shells use
internal <Element canvas id="..."> linkedNodes for their drop targets.

Our previous CANVAS_TYPES set claimed all those shells were canvases,
which made Craft.js's toNodeTree walker hit an uncaught Invariant —
the shell asserted "I'm a canvas" but its render ignores data.nodes,
so the walker would chase phantom children.
This commit is contained in:
2026-05-24 16:27:38 -07:00
parent 6428f93cec
commit 849f432330
2 changed files with 13 additions and 16 deletions

View File

@@ -4,16 +4,13 @@ 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 /** Only `Container` instances are "real" canvases in serialized state — they
* config of each component. Leaf components (Heading, TextBlock, ButtonLink, …) * directly render whatever is in node.data.nodes. Layout-shell components
* are NOT canvases and must serialize with isCanvas:false or their rendered * (Section, HeroSimple, FeaturesGrid, ColumnLayout, CTASection, etc) use
* content gets swallowed by an empty drop-target wrapper. */ * Craft.js <Element canvas id="…"> linkedNodes internally; their own
const CANVAS_TYPES = new Set<string>([ * isCanvas must be FALSE or Craft.js's toNodeTree walker trips an Invariant
'Container', 'Section', 'ColumnLayout', 'BackgroundSection', * because the shell claims to be a canvas but its render ignores `nodes`. */
'HeaderZone', 'FooterZone', const CANVAS_TYPES = new Set<string>(['Container']);
'HeroSimple', 'FeaturesGrid', 'CTASection',
'FormContainer', 'Navbar', 'Footer',
]);
interface PageContextValue { interface PageContextValue {
pages: PageData[]; pages: PageData[];

View File

@@ -3,12 +3,12 @@ import type { NodeTree } from '@craftjs/core';
import { usePages } from '../state/PageContext'; import { usePages } from '../state/PageContext';
import { SitesmithResponse, SerializedTreeNode } from '../types/sitesmith'; import { SitesmithResponse, SerializedTreeNode } from '../types/sitesmith';
/** Component types that act as drop targets (isCanvas = true) */ /** Only Container is a "real" Craft.js canvas in serialized state. Layout
const CANVAS_TYPES = new Set([ * shells (Section/HeroSimple/ColumnLayout/etc) use <Element canvas> linkedNodes
'Container', 'Section', 'ColumnLayout', 'BackgroundSection', * internally — their own node must serialize with isCanvas:false or
'HeroSimple', 'FeaturesGrid', 'CTASection', * toNodeTree's walker hits an Invariant because the shell claims to be a
'FormContainer', 'Navbar', 'Footer', * canvas but its render ignores `data.nodes`. */
]); const CANVAS_TYPES = new Set(['Container']);
/** /**
* Flatten a SerializedTreeNode tree into a Craft.js node map ready for * Flatten a SerializedTreeNode tree into a Craft.js node map ready for