From f30fc6efec7f54e034715b43b605447fafd6f41e Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Sun, 9 Aug 2026 07:04:54 -0700 Subject: [PATCH] fix(site-builder): add Hero/CTA components to Layers virtual-rows registry A grep across every component under src/components/ (not just the 12 files the original registry hardcoded) turns up three more leaf components with the identical array-prop-as-content pattern: Hero (HeroSimple.tsx), Call to Action (CallToAction.tsx) and CTA Section (CTASection.tsx) all render a shared `ctas?: CtaButton[]` prop whose items are `{ text, href, variant?, target? }`. Without this, a Hero's CTA buttons still wouldn't appear in the Layers tree. Verified displayName, prop name and label field against each component's craft.props defaults and the shared CtaButton type in sections/_cta-helpers.tsx -- all three matched exactly, no corrections needed. A follow-up sweep for any further array-prop leaf components found none: the only other array fields in the tree are nested one level inside already-covered items (ContactFormField.options, PricingPlan.features), not top-level component props. Co-Authored-By: Claude Opus 5 (1M context) --- .../panels/left/layers-virtual-rows.test.ts | 29 +++++++++++++++++++ craft/src/panels/left/layers-virtual-rows.ts | 16 +++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/craft/src/panels/left/layers-virtual-rows.test.ts b/craft/src/panels/left/layers-virtual-rows.test.ts index 9c334e6..4223b12 100644 --- a/craft/src/panels/left/layers-virtual-rows.test.ts +++ b/craft/src/panels/left/layers-virtual-rows.test.ts @@ -45,6 +45,35 @@ describe('deriveVirtualRows', () => { ]); }); + test('Hero, Call to Action and CTA Section derive rows from their shared ctas prop', () => { + const ctas = [ + { text: 'Get Started', href: '#', variant: 'primary' }, + { text: 'Learn More', href: '#learn', variant: 'outline' }, + ]; + expect(deriveVirtualRows('Hero', { ctas })).toEqual([ + { index: 0, label: 'Get Started' }, + { index: 1, label: 'Learn More' }, + ]); + expect(deriveVirtualRows('Call to Action', { ctas })).toEqual([ + { index: 0, label: 'Get Started' }, + { index: 1, label: 'Learn More' }, + ]); + expect(deriveVirtualRows('CTA Section', { ctas })).toEqual([ + { index: 0, label: 'Get Started' }, + { index: 1, label: 'Learn More' }, + ]); + }); + + test('a CTA with no text falls back to "Button N"', () => { + const rows = deriveVirtualRows('Hero', { + ctas: [{ href: '#' }, { text: '', href: '#empty' }], + }); + expect(rows).toEqual([ + { index: 0, label: 'Button 1' }, + { index: 1, label: 'Button 2' }, + ]); + }); + test('every registry entry has a non-empty prop, label and fallback', () => { for (const [name, spec] of Object.entries(VIRTUAL_CHILD_PROPS)) { expect(spec.prop, `${name}.prop`).toBeTruthy(); diff --git a/craft/src/panels/left/layers-virtual-rows.ts b/craft/src/panels/left/layers-virtual-rows.ts index c8402a5..3211624 100644 --- a/craft/src/panels/left/layers-virtual-rows.ts +++ b/craft/src/panels/left/layers-virtual-rows.ts @@ -38,7 +38,18 @@ export interface VirtualChildSpec { * - ContentSlider items are `{ heading, text, ... }`, not `{ title }` -- * registry corrected from `title` to `heading`. * Every other entry (prop name and label field) matched the component as - * originally drafted. */ + * originally drafted. + * + * Hero, Call to Action and CTA Section were added after a follow-up sweep + * of every component under src/components/ for top-level array props (not + * just the 12 files initially listed) turned up three more leaf components + * with the identical pattern: all three render a `ctas?: CtaButton[]` prop + * (see sections/_cta-helpers.tsx) whose items are `{ text, href, variant?, + * target? }`, so `text` is the label field. Verified against each file's + * `craft.displayName` and `craft.props.ctas` defaults. That sweep found no + * further candidates -- the only other array fields in the tree are nested + * one level down inside already-covered items (ContactFormField.options, + * PricingPlan.features), not top-level component props. */ export const VIRTUAL_CHILD_PROPS: Record = { 'Features Grid': { prop: 'features', label: 'title', fallback: 'Feature' }, Tabs: { prop: 'tabs', label: 'label', fallback: 'Tab' }, @@ -52,6 +63,9 @@ export const VIRTUAL_CHILD_PROPS: Record = { 'Social Links': { prop: 'links', label: 'platform', fallback: 'Link' }, Navbar: { prop: 'links', label: 'text', fallback: 'Link' }, 'Contact Form': { prop: 'fields', label: 'label', fallback: 'Field' }, + Hero: { prop: 'ctas', label: 'text', fallback: 'Button' }, + 'Call to Action': { prop: 'ctas', label: 'text', fallback: 'Button' }, + 'CTA Section': { prop: 'ctas', label: 'text', fallback: 'Button' }, }; const MAX_LABEL = 40;