Commit Graph

5 Commits

Author SHA1 Message Date
d0925d9e2d site-builder: dynamic CTAs, section anchors, edit-with-Sitesmith
Three related features:

1. Dynamic CTA buttons on HeroSimple, CTASection, CallToAction.
   New shared ctas[] array (text + href + variant + target) replaces the
   primary/secondary pair. Settings panel gets add/remove/reorder controls.
   Legacy fields stay readable for backwards compat — first user edit
   migrates the section onto the new array.

2. Anchor IDs on all layout/section components (Container, Section,
   BackgroundSection, ColumnLayout, plus 6 section blocks done by parallel
   subagent, plus Hero/CTA/CallToAction). Anchor input lives in the
   settings panel with an "auto from heading" button that walks the
   subtree for the first Heading.text. Renders as id="..." on the
   outermost element so #anchor URLs resolve.

3. Edit-with-Sitesmith targeted invocation. Right-click → "Ask Sitesmith"
   and a button at the top of the right-side settings panel both open the
   modal pre-targeted at the selected node. The node's serialized subtree
   is sent to the server; system prompt is augmented to require a patch
   with replace_node. Editor lifts modal state into a new SitesmithContext.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:43:28 -07:00
87dd4340f7 sitesmith: pre-create section-inner/bg-section-inner/form-inner linkedNodes
The Invariant 'component type (undefined) does not exist in the resolver'
was Craft.js's toNodeTree choking on the linkedNode that <Element id="X">
auto-creates at render time inside Section / BackgroundSection /
FormContainer. The auto-created node stores its type as the Container
React component class itself, not as {resolvedName:'Container'}, so the
later type.resolvedName lookup returns undefined.

For each shell, treeToState (and apply-ai-response's buildNodeTree) now
synthesizes the linkedNode container up-front with a proper serialized
type, moves the AI's direct children into it, and reparents them. This
matches the canonical shape Craft.js writes when the user manually builds
a site, so Craft.js never has to materialize the linkedNode itself.
2026-05-24 17:22:40 -07:00
849f432330 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.
2026-05-24 16:27:38 -07:00
6428f93cec 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.
2026-05-24 16:17:25 -07:00
cf3457aa15 sitesmith: apply-ai-response utility (replace + patch + ask) + PageContext helpers
Add apply-ai-response.ts with serializeTreeForCraft, buildNodeTree, findNodeIdByAiNodeId,
and useApplyAiResponse hook covering replace (site/page/section), patch (5 ops), and ask.
Extend PageContext with replaceAllPages, replaceCurrentPage, setHeader, setFooter helpers
that mirror the existing actions.deserialize/loadState pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 14:20:51 -07:00