diff --git a/skills/ve-html-template-generator/README.md b/skills/ve-html-template-generator/README.md new file mode 100644 index 000000000..fa823a185 --- /dev/null +++ b/skills/ve-html-template-generator/README.md @@ -0,0 +1,192 @@ +# VE HTML Template Generator Skill + +Quick guide for using `ve-html-template-generator`. + +## What to provide in the prompt + +Minimum required inputs: + +- `client`: client slug (example: `yeti`) +- `html`: local HTML path (preferred) or URL + +Minimal prompt: + +```text +Use $ve-html-template-generator. +client: yeti +html: /Users/you/Downloads/yeti-location-source.html +``` + +## Optional inputs + +- `customRoot`: defaults to `packages/visual-editor/src/components/custom/` +- `starterRoot`: defaults to `starter/src/templates/` +- `scope`: what to include/exclude from source page +- `notes`: constraints for slot structure, component behavior, etc. + +Example with optional inputs: + +```text +Use $ve-html-template-generator. +client: acme +html: /tmp/acme-homepage.html +scope: Exclude legal policy bands and cookie modal markup. +notes: +- Split every heading/body/cta into focused nested slots. +- Lock generated slot surfaces for editing-only use (`allow={[]}` on all section and nested layout slot renders). +- Keep hours entity-defaulted. +- If source has multiple hours bands (for example store + drive-thru), generate separate entity-bound hours slots for each band. +- Do not use any sibling client templates under `starter/src/templates/*` or sibling custom client folders under `packages/visual-editor/src/components/custom/*` as reference; derive structure from source HTML + shared VE components only. +- Nearby Stores/Locations should follow shared NearbyLocations-style dynamic behavior, not hardcoded static store links. +- Build client-local slots/atoms by adapting the closest shared generic baseline first, then layer client-specific changes. +- For FAQ/Q&A bands, adapt shared FAQ behavior patterns first (question/answer modeling + interaction), then restyle to match source. +- For footer social links, baseline against shared `FooterSocialLinksSlot` behavior (platform-specific fields + URL validation + icon rendering), not text-link lists. +- Decompose location summary content into nested focused slots (back-link, heading/meta, status, CTA), not one omnibus summary slot. +- For user-visible copy props, use `translatableString` (or entity-backed string fields) so embedded fields work; reserve plain `text` fields for URLs/class names/ids. +- Ignore likely hidden utility labels from HTML in nav/footer (for example `Top Links`, `Actions`, `Menu`) unless there is source evidence they are visibly rendered. +- Any CTA-like label must have an actionable link/href (or CTA entity). Never render CTA labels as plain non-interactive text. +- Compose complex layouts grid-first using shared Core Information/slot baselines, then wrap into client-bespoke section/slot names. +- Use shared map/image patterns (no map iframes, no backgroundImageUrl text fields). +- Keep functionality first: preserve entity wiring (especially hours) and real map behavior before visual refinements. +- Match header/footer chrome to the source site (dark or light) and preserve readable contrast. +- Include source-theme header/footer background options (for example source brand blue), not only white/neutral choices. +- Use full-bleed hero/promo shells only when source sections are edge-to-edge (`px-0 md:px-0` + `max-w-none`). +- Render rich text slots via `resolveComponentData` output (React element or string), not html-only parsing. +- If source has media + list sections (for example amenities with a lead image), keep a dedicated media slot plus list/content slots. +- When merging similar sections, keep optional slots (like CTA slots) and add top-level show/hide toggles instead of removing those slots. +- For sections with multiple slots, expose top-level show/hide toggles for most slots (`slot count - 1` coverage target). +- When store-info includes hours/location/map/parking, preserve stacked order (`Hours -> Location -> Map -> Parking`) unless source evidence clearly differs. +``` + +## What the skill generates + +Under `packages/visual-editor/src/components/custom//`: + +- `atoms/*.tsx` +- `components/*Section.tsx` +- `components/*Slot.tsx` +- `index.ts` +- `ve.ts` + +Under `packages/visual-editor/src/components/categories/`: + +- `SectionsCategory.tsx` +- `SlotsCategory.tsx` + +Shared package registration updates: + +- `packages/visual-editor/src/components/configs/mainConfig.tsx` (visible `Sections` + hidden `Slots` category wiring) +- `packages/visual-editor/src/components/categories/index.ts` (category exports) +- `packages/visual-editor/src/components/index.ts` (generated client exports) + +Under `starter/src/templates//`: + +- `-config.tsx` +- `-template.tsx` + +Starter is a consumer-only wrapper. It should not be the source of truth for generated sections/slots/atoms. + +## Validate output + +Run validator: + +```bash +python3 skills/ve-html-template-generator/scripts/validate_client_template.py --client-path starter/src/templates/ +``` + +Optional screenshot smoke test scaffold: + +```bash +python3 skills/ve-html-template-generator/scripts/scaffold_client_template_smoke_test.py \ + --client-slug \ + --template-path starter/src/templates//-template.tsx \ + --config-path starter/src/templates//-config.tsx +``` + +Run generated smoke test: + +```bash +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.smoke.test.tsx +``` + +On first run for a newly generated test, seed screenshot baselines: + +```bash +pnpm -C packages/visual-editor exec vitest run --update \ + src/components/generated/Template.smoke.test.tsx +``` + +Smoke screenshots should capture full page height so lower-page sections (for example footer/legal) are validated. + +Default source-vs-generated section parity scaffold (captures source HTML and generated section screenshots): + +```bash +python3 skills/ve-html-template-generator/scripts/scaffold_client_template_section_parity_test.py \ + --client-slug \ + --html-path /path/to/source.html \ + --config-path starter/src/templates//-config.tsx \ + --overwrite +``` + +Run section parity test: + +```bash +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.section-parity.test.tsx +``` + +For first-time baseline capture of a new generated parity test, run once with `--update`. + +This section parity run is now the default post-generation step for the skill. +After the initial parity run, apply one focused correction pass immediately, then rerun smoke + section parity. +After those reruns, run final build checks before considering the task done: + +```bash +pnpm -C packages/visual-editor build +pnpm -C starter build +``` + +Default behavior for this skill is now: `generate -> parity -> correction pass -> rerun -> build gate`. + +Optional section parity controls: + +```bash +CLIENT_TEMPLATE_SECTION_PARITY_LIMIT=10 \ +CLIENT_TEMPLATE_SECTION_PARITY_ALL_VIEWPORTS=1 \ +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.section-parity.test.tsx +``` + +Optional full-page parity scaffold: + +```bash +python3 skills/ve-html-template-generator/scripts/scaffold_client_template_visual_parity_test.py \ + --client-slug \ + --html-path /path/to/source.html \ + --config-path starter/src/templates//-config.tsx \ + --overwrite +``` + +Run parity test: + +```bash +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.visual-parity.test.tsx +``` + +For first-time baseline capture of a new generated parity test, run once with `--update`. + +Screenshot parity should stay advisory-first: use it to find layout/media/styling gaps, but keep shared baseline functionality and slot wiring intact. +By default, parity should drive one focused correction pass, not open-ended restyling loops. + +## From-scratch verification + +Use this before evaluating a new generation: + +1. Clear or avoid old saved layout data for the target entity (`document.__.layout`). +2. Open editor and drag the new client sections from left nav. +3. Verify header/footer nested slots are clickable/editable (brand/nav/utility, signup/links/legal). +4. Verify default values are visible in the props panel before edits. + +Old saved layouts from earlier generations can hide or distort current slot behavior. diff --git a/skills/ve-html-template-generator/SKILL.md b/skills/ve-html-template-generator/SKILL.md new file mode 100644 index 000000000..469b01db7 --- /dev/null +++ b/skills/ve-html-template-generator/SKILL.md @@ -0,0 +1,420 @@ +--- +name: ve-html-template-generator +description: Generate isolated client-specific Yext Visual Editor templates from existing website HTML by decomposing a page into bespoke atoms, slot components, and section components, then producing package-scoped custom component files under packages/visual-editor/src/components/custom/client-slug plus client template/config files under starter/src/templates/client-slug. Use when asked to create or update a client template from downloaded HTML, view-source markup, or exported page snapshots (Step 1 of the client-template epic). +--- + +# VE HTML Template Generator + +## Enforce Client Isolation + +Treat every client template as fully isolated from shared/generic VE components. + +Required isolation rules: + +- Generate client-specific atoms, sections, slot components, and category/config files. +- Generate new client-specific header and footer sections even when shared versions appear identical. +- Do not import or register shared categories/components/atoms in client generated files. +- Do not read, copy, or adapt any sibling client templates under `starter/src/templates/*` or sibling custom client folders under `packages/visual-editor/src/components/custom/*` (except the target client being generated/updated). +- Never use another client template as a starting scaffold. Baselines come from shared/generic VE components in `packages/visual-editor/src/components/...` plus the target source HTML only. +- Keep custom atoms/slots/sections under `packages/visual-editor/src/components/custom//...`. +- Keep starter files minimal: only `starter/src/templates//-config.tsx` and `starter/src/templates//-template.tsx`. + +## Default Assumptions + +Assume these defaults unless the user explicitly overrides them: + +- Generate client-only atoms, sections, categories, config, and template files. +- Recreate header/footer as client-owned sections. +- Use slot-first section architecture (`sections compose slot children`). +- Default hours content to entity mode (`field: "hours"`, not hard-coded weekly constants). +- Default non-hours content to constant mode unless user explicitly requests KG-first defaults. +- Do not expose atoms in the left nav; expose sections only. +- Create exactly one visible client-specific section category in the left nav. +- Create one hidden client-specific slot category in config. +- Use custom implementation root `packages/visual-editor/src/components/custom/`. +- Do not modify shared/generic VE section/slot implementations, but do update shared package registries for the new client category wiring. +- Do not add or depend on `registerMainConfigExtension`/runtime mainConfig mutation from `starter`. +- Run section-level screenshot parity by default after initial generation (report-first, no default diff gate). +- Prioritize functionality/usability over visual similarity (entity wiring, slot editability, map/hours behavior) when tradeoffs are required. + +## Minimal Prompt Contract + +Treat these as the only required user inputs: + +- `client`: client slug (example: `yeti`) +- `html`: local HTML file path or URL + +If the user provides only `client` and `html`, proceed with all defaults in this skill and do not ask for additional setup questions. + +Example minimal invocation: + +```text +Use $ve-html-template-generator. +client: yeti +html: /path/to/yeti-homepage.html +``` + +## Collect Inputs + +Gather these inputs before coding: + +- Client slug (example: `yeti`) +- Source HTML file path (preferred) or page URL +- Custom implementation root (`packages/visual-editor/src/components/custom/`) unless overridden +- Starter wrapper root (`starter/src/templates/`) unless overridden +- Scope boundaries (for example: "exclude only legal/policy fragments") + +If only a URL is provided, fetch once and save a local snapshot: + +```bash +curl -L "" -o /tmp/-page.html +``` + +## Build Section Plan + +Create a first-pass section breakdown from the HTML: + +```bash +python3 scripts/extract_page_sections.py \ + --input /tmp/-page.html \ + --output /tmp/-sections.json +``` + +Review the JSON and adjust obvious bad splits before generating components. + +Favor one horizontal band per component. Keep sections small and cohesive. +Derive the section list from source evidence, not prior client templates. + +Before coding, produce a quick source-evidence map: + +- planned section -> source selector/snippet (heading/copy/image/link evidence) +- include only sections that have source evidence (or explicit user request) +- infer page archetype from HTML (`location-detail`, `marketing`, etc.) and only include archetype-appropriate sections (do not auto-insert FAQ/store-info bands unless source supports them) + +Before coding sections, run normalization guidance from +`references/section-normalization.md` to: + +- split over-aggregated slots into focused concerns +- merge structurally duplicate sections into one reusable section with toggles/presets + +## Generate Template Files + +Create exactly this shape for each client: + +- `packages/visual-editor/src/components/custom//atoms/*.tsx` +- `packages/visual-editor/src/components/custom//components/Section.tsx` +- `packages/visual-editor/src/components/custom//components/Slot.tsx` +- `packages/visual-editor/src/components/custom//index.ts` +- `packages/visual-editor/src/components/custom//ve.ts` +- `packages/visual-editor/src/components/categories/SectionsCategory.tsx` +- `packages/visual-editor/src/components/categories/SlotsCategory.tsx` +- `starter/src/templates//-config.tsx` +- `starter/src/templates//-template.tsx` + +Follow `references/template-contract.md` for naming, registration, and assembly. + +## Generate Section Components + +Follow `references/ve-field-patterns.md` for all prop and field conventions. +Follow `references/slot-paradigm.md` for section and slot composition. +Follow `references/section-normalization.md` for slot decomposition and duplicate-section merge decisions. +Follow `references/layout-containment.md` for section shell and slot containment rules. +Follow `references/map-image-parity.md` for map/image parity with existing shared VE components. +Follow `references/baseline-reuse-map.md` to select shared baselines for client-local atoms/slots/sections. + +When generating inside this repository, mirror these implementation patterns: + +- `packages/visual-editor/src/components/pageSections/VideoSection.tsx` (slot-based section shell) +- `packages/visual-editor/src/components/pageSections/CoreInfoSection.tsx` (multi-slot section and default slot props) +- `packages/visual-editor/src/components/pageSections/AboutSection/AboutSection.tsx` (multi-column slot layout and slot rendering behavior) +- `packages/visual-editor/src/components/contentBlocks/HoursTable.tsx` (hours entity field defaults) + +Required conventions: + +- Define section props with `styles` and hidden `slots`. +- Define slot fields with `{ type: "slot" }` and `visible: false`. +- Render content through slot children (`Slot allow={[]} />`) instead of hard-coded inline structures. +- Render slot children with `style={{ height: "auto" }}` unless there is a specific, justified alternative. +- For slot components that compose child slots (for example header/footer layout slots), apply the same lock-down rule on every nested slot render: `allow={[]}` plus `style={{ height: "auto" }}`. +- Generated slots are for structured prop editing, not free-form nesting; do not leave any generated slot render permissive. +- Keep slots concern-specific; avoid combined slot names such as `HoursLocationSlot`. +- Decompose textual concerns aggressively: heading/subheading/body/legal text/each CTA should be separate slots by default. +- Decompose CTA concerns aggressively: each CTA (primary vs secondary) should be its own slot rather than embedded in text slots. +- For sections with 2+ slots, expose top-level `show...` controls for most slots (target coverage: slot count minus one, or higher). +- For user-visible non-entity copy inputs, use `translatableString` fields (embedded-field capable), not plain `text`/`textarea` inputs. +- Keep plain `text` inputs for infrastructure-only values (for example URLs, class names, ids, API keys, numeric query params), not rendered copy. +- Header/footer must be nested slot trees, not terminal omnibus slots: + - section shell slot(s) -> layout slot -> focused child slots (logo, nav links, utility links, CTA groups, legal groups) + - avoid one-slot header/footer implementations that inline all text/links/buttons +- When source HTML includes likely hidden utility labels in nav/footer (for example `Top Links`, `Actions`, `Menu` headings), do not surface those as visible heading copy unless source evidence shows they are actually visible. +- Header/footer layout slots must be directly editable: + - every layout slot `slots.` key must have populated `defaultProps.slots.` entries + - do not leave any child slot empty when section defaults are instantiated + - do not rely on runtime layout normalization/migrations to make header/footer slots editable +- If a section needs grouping, use a layout slot that composes child slots instead of one slot with many unrelated text/CTA fields. +- Ensure every slot `type` referenced in section `defaultProps.slots` is registered in client slot category components and included in `-config.tsx` components. +- Do not use placeholder slot defaults (`props: {}`); populate full slot prop objects in section `defaultProps.slots`. +- For array fields in slot components, always provide `defaultItemProps` so newly added items are well-formed. +- Use `YextField(..., { type: "entityField", filter: ... })` for editable content props inside slot components. +- Use `entityField` only for supported/intentional types (for example `type.string`, `type.rich_text_v2`, `type.image`, `type.phone`, `type.hours`). +- Avoid `entityField` for `type.url`; use explicit URL fields (`text` or `translatableString`) unless a dedicated supported pattern is required. +- Represent content props as `YextEntityField` (`field`, `constantValue`, `constantValueEnabled`). +- For any `YextEntityField` default, use localized constant objects (for example `{ en: "Heading", hasLocalizedValue: "true" }`) instead of plain string constants so right-panel defaults hydrate correctly. +- For any `YextEntityField` default, use localized constant objects that include `hasLocalizedValue: "true"` at the top level. +- Apply localized constant object rules consistently in `defaultProps`, array `defaultItemProps`, and any section-specific slot-prop overrides created via spread. +- For location-stream templates, default location-specific content (name/address/phone/hours) to entity mode or derive from entity fields; avoid hardcoded city/address/phone constants with empty field bindings. +- For location-stream templates, avoid hardcoded map/directions URLs in defaults; derive from address data at render-time or expose entity-backed fields. +- Map implementations should mirror shared VE behavior: + - prefer `MapboxStaticMapComponent` with coordinate entity field defaults (`field: "yextDisplayCoordinate"`) + - or use `getDirections`-derived links when embed providers are unavailable + - render an actual map surface (static map image or shared map component), not a decorative placeholder panel + - avoid `iframe` map embeds with hardcoded provider URLs + - avoid `mapImage` fallback fields in map slots unless explicitly requested by the user + - do not seed map slots with hero/promo marketing images as map defaults +- Background images should mirror shared hero/promo behavior: + - use `data.backgroundImage` as `YextEntityField<...>` with `filter.types: ["type.image"]` + - resolve at render-time via shared image resolution patterns (`resolveYextEntityField` + `getImageUrl`) + - avoid `backgroundImageUrl` text fields in `styles` or `data` +- Image slots should mirror shared `ImageWrapper` behavior: + - `data.image` entity field using `type.image` + - style controls for width/aspect ratio/constrain behavior + - render-safe empty state when image data is missing +- Resolve content through `resolveComponentData(..., locale, streamDocument)`. +- Use `EntityField` wrappers around rendered values that map to KG/static content. +- Include practical style props for text size, color, alignment, and spacing/background. +- Provide render-safe `defaultProps` that approximate source content. +- For hours-related slot components, default to entity hours (`field: "hours"`, `constantValueEnabled: false`). +- When source has multiple distinct hours bands (for example store hours + drive-thru hours), generate separate hours slots/components for each band (for example `StoreHoursSlot`, `DriveThruHoursSlot`), and keep each band entity-bound. +- Use defensive rendering in list/slot components (guard missing item structures instead of directly dereferencing nested `.field` paths). +- Merge duplicate sections with the same slot/style structure into a single reusable section and expose behavior differences as toggles (for example `showCTA`). +- When merging duplicate sections, keep the superset of optional slots (especially CTA/disclaimer/body variants) and control each optional slot with top-level show/hide toggles instead of removing slots. +- Any CTA-like label must map to an actionable CTA control (CTA/link/button with href/link action). Never render CTA labels as plain non-interactive text. +- Use baseline-first generation: copy/adapt proven shared section shell patterns into client-local files before inventing new section shells. +- Use baseline-first slot/atom generation too: + - choose the closest shared slot/atom behavior by concern + - copy/adapt into client-local files + - only invent new slot/atom APIs when source fidelity requires it +- Prefer grid-first composition when source layout is complex: assemble with shared grid/core-info atoms/slots first, then wrap that composition into client-bespoke named sections with focused slot labels. +- For FAQ/Q&A bands, baseline against shared FAQ implementations first (`FAQsSection` + FAQ card patterns), then adapt styling/layout to match source visuals. +- Nearby stores/location clusters should follow shared NearbyLocations behavior (dynamic lookup from document coordinate/radius/limit) rather than static hardcoded link lists. +- Footer social link groups should follow shared `FooterSocialLinksSlot` behavior (platform-specific fields + URL validation + icon rendering), not generic text-link lists. +- Do not keep location summary or hero summary content in one omnibus slot; use nested layout slots with focused child slots (back-link, heading/address/meta, status, CTA group). +- In multi-column sections, apply containment-safe wrappers (`overflow-hidden` at section content level, `min-w-0` on columns, no absolute positioning for core content blocks). +- If a section exposes `styles.textColor`, avoid hardcoded slot text color classes (for example `text-white`) that neutralize that control; use inherited color or slot-level style props. +- For header/footer slots, avoid hardcoded white/light text and border utility classes (`text-white`, `text-white/..`, `text-neutral-100`, `border-white/...`) that can conflict with section backgrounds; use inherited color or explicit style props. +- Header/footer section defaults should match source-site chrome (dark or light) and preserve readable contrast in the default state. +- Header/footer background controls should include source-theme options (for example source brand blue for Yeti-like dark chrome), not only neutral/white options. +- When source has stacked store-info content (hours/location/map/parking), preserve source order in section structure. Default to `Hours -> Location -> Map -> Parking` when all four are present. +- Hero/promo bands should use full-bleed shell wrappers (`className="px-0 md:px-0 py-0 md:py-0"` + `contentClassName="max-w-none"`) only when the source band is edge-to-edge. +- Hero/promo media slots should not apply rounded corner classes unless the source design explicitly uses rounded media. +- Hero/promo image elements should render as block-level (`className` includes `block h-full w-full object-cover`) to avoid baseline whitespace gaps. +- For rich text slot rendering, prefer `resolveComponentData(...)` render output (`ReactElement | string`) over manual `html` extraction only; this prevents hidden copy when rich text value shapes vary (html/json/localized object forms). +- For source bands that combine media + list content (for example amenities/gallery + feature list), include an explicit media slot alongside list/content slots; do not collapse to list-only when source contains meaningful imagery. + +## Generate Client Template Root + +In `-template.tsx`: + +- Import client section components from `@yext/visual-editor` (package exports), not from local starter `components/`. +- Use full `@yext/pages` template structure as the client entrypoint. +- Compose page output in source order. +- In default layout content entries, set section `props` to section `defaultProps` (or a deliberate deep override of `defaultProps`) instead of `props: {}`. +- Pass stream document metadata into Puck render (`metadata={{ streamDocument: document }}`) for field resolution consistency. +- Keep the implementation client-scoped. Do not modify generic/shared templates unless requested. + +In `-config.tsx`: + +- Register only client-owned section and slot category components from `@yext/visual-editor` (no shared page-section categories). +- Define one visible client-specific sidebar category for generated sections in the left nav. +- Define one hidden client-specific category for slot components. +- Ensure the visible category contains sections only (no atoms, no slot components). +- Ensure every slot component used by section defaults is present in the slot category component map and therefore available in registry lookups during drag/drop. +- Include slot component registrations in `config.components` (for example: spread `SlotsCategoryComponents`). + +Follow `references/client-config-category-pattern.md` for a concrete config/category shape. + +If `starter/src/ve.config.tsx` exists, integrate generated client sections into starter editor nav: + +- Keep starter as a consumer of package config. Do not make `mainConfig` depend on starter imports. +- Prefer `...mainConfig.components`/`...mainConfig.categories` for client section visibility after package registration. +- Add `-location` mapped to `Config` in `componentRegistry`. +- Only add explicit client section/slot maps to starter `devConfig` if package `mainConfig` has not been updated yet. +- Extend starter `DevProps` only when explicit starter-local client maps are added. + +For package-default availability (non-starter), update shared package config too: + +- Add a package-native `SectionsCategory.tsx` file under `packages/visual-editor/src/components/categories`. +- Add a package-native `SlotsCategory.tsx` file under `packages/visual-editor/src/components/categories`. +- Register both `...CategoryComponents` maps in `packages/visual-editor/src/components/configs/mainConfig.tsx`. +- Add a visible `Sections` category and hidden `Slots` category in `mainConfig.categories`. +- Keep `directoryConfig` and `locatorConfig` unchanged unless explicitly requested. + +## Validate + +Run repository validation commands and fix errors before finishing. +Use `references/quality-checklist.md` as a mandatory gate before finalizing. +Use `references/test-and-screenshot-workflow.md` as a required post-generation step for every new generation unless the user explicitly opts out. +Treat screenshot parity as an advisory signal for spacing/hierarchy/media gaps; do not regress shared baseline functionality (slots, field wiring, editability, data behavior) to chase pixel-perfect matches. +Treat section parity pairing as heuristic by source order and generated section order; use diff output to guide review, not as absolute truth. +Apply at most one parity-driven correction pass by default, focused on high-impact issues (missing media/maps, incorrect hierarchy/layout, severe contrast/readability, major spacing breaks). +For major structure/layout mismatches (slot order, missing key bands, wrong visual hierarchy), increase parity weighting enough to correct those in the single default correction pass. +Do not make parity-driven refactors that reduce slot decomposition, field consistency, or editor stability. +Always run final build checks after the correction-pass reruns, and keep fixing until build commands pass. + +Run skill validation: + +```bash +python3 scripts/validate_client_template.py --client-path starter/src/templates/ +``` + +Confirm: + +- Directory and file names match the required contract exactly. +- No shared/generic VE sections/components/atoms are imported into client config. +- Sections are slot-based and slot fields are hidden. +- Slot types referenced by sections are registered in client slot category and config component registry. +- Section default slot props are populated (no empty `{}` slot prop placeholders). +- Slot array fields define `defaultItemProps`. +- Unsupported `entityField` filters like `type.url` are not used. +- `YextEntityField` defaults do not use plain-string `constantValue`; they use localized objects with `hasLocalizedValue: "true"`. +- `YextEntityField` and `YextEntityField` defaults include localized objects with `hasLocalizedValue: "true"` when locale keys are used. +- Sections avoid composite slot concerns (for example `HoursLocationSlot`). +- All slot render calls include `allow={[]}` and `style={{ height: "auto" }}` by default in both section files and nested slot/layout files. +- Non-list slot components avoid over-aggregated text/CTA fields. +- Sections with 2+ slots expose top-level show/hide controls for most slots (target coverage: slot count minus one). +- Multi-slot section shells include containment cues (`overflow-hidden`, column wrappers with `min-w-0`) to prevent visual spillover. +- Location-stream templates avoid hardcoded location constants and map/directions URLs in defaults. +- Map blocks follow shared VE map patterns (no hardcoded iframe map embeds). +- Map slots do not use fallback `mapImage` fields/defaults unless explicitly requested. +- Map defaults do not use hero/promo marketing image URLs. +- Background image and image slot data follow shared VE image entity patterns (no plain URL text controls for core image sources). +- Nearby-locations behavior is not hardcoded to a static link list; it follows shared NearbyLocations-style dynamic data lookup. +- FAQ/Q&A sections preserve shared FAQ behavior patterns (question/answer data shape + interaction model) while matching source styling. +- Slot/atom implementations are adapted from the closest shared baseline unless source constraints require a deliberate divergence. +- Header/footer implementations are nested slot trees with focused child slots. +- Header/footer slot styles maintain text/background contrast without relying on hardcoded white/light utility classes. +- Header/footer defaults match source chrome style (dark/light) while preserving readable contrast. +- Hero/promo sections only use full-bleed wrappers when source parity requires edge-to-edge media. +- No sibling client template paths/symbols are referenced from generated files. +- No sibling custom client paths/symbols are referenced from `packages/visual-editor/src/components/custom/*`. +- Validator does not flag suspicious structural similarity with older sibling client templates. +- Store-info sections that include `HoursSlot`, `LocationInfoSlot`, `MapSlot`, and `ParkingSlot` preserve source-first order (`Hours -> Location -> Map -> Parking`) unless source evidence clearly differs. +- Rich text slot copy renders when edited/defaulted (no html-only parsing assumptions). +- Merged promo-like sections preserve optional CTA slots and expose top-level CTA show/hide toggles. +- Section `textColor` style controls are not undermined by hardcoded slot text color classes. +- No structurally duplicate sections exist when a toggle/variant-based merge is feasible. +- Hours defaults use entity binding, not static weekly strings. +- Multi-band hours sections (for example store + drive-thru) are represented as multiple entity-bound hours slots, not one slot with a secondary heading and no second hours data source. +- Default layout section entries do not use empty `props: {}`. +- Starter editor left nav includes the generated client section category when starter config exists. +- Starter config consumes package `mainConfig` for client sections/slots after package registration (no starter-owned mainConfig mutation). +- Fresh drag/drop from left nav works for header/footer sections and their nested child slots (brand/nav/utility, signup/links/legal) without blank/non-clickable slot regions. +- Footer social areas render platform-aware social link icons/links (not a plain social text list). +- User-facing copy fields are translatable/embedded-field capable (`translatableString` or entity-backed string fields), not plain text inputs. +- Header/nav utility groups do not include noisy hidden headings (for example `Top Links` / `Actions`) unless source shows those labels on-screen. +- CTA labels are always tied to actionable href/link controls (no plain-text CTA labels). +- No Storm-side integration logic is introduced. +- The generated page structure preserves source hierarchy (hero -> body bands -> CTA/footer). + +## From-Scratch Test Protocol + +Before calling generation "good", validate in a clean run: + +1. Remove/ignore old generated client layout data for the target entity (or use a new entity with no saved `__layout`). +2. Start editor, drag fresh generated sections from left nav. +3. Confirm nested header/footer slots are selectable and editable in canvas and right props panel. +4. Confirm defaults are visible in props panel before any edits. +5. Run validator script and address all failures. + +## Required Screenshot Parity Step + +After initial generation, always scaffold and run section parity: + +```bash +python3 scripts/scaffold_client_template_section_parity_test.py \ + --client-slug \ + --html-path /path/to/source.html \ + --config-path starter/src/templates//-config.tsx \ + --overwrite +``` + +```bash +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.section-parity.test.tsx +``` + +After this first parity run, perform one focused correction pass in the same turn by default: + +- fix the highest-impact issues surfaced by parity + smoke (missing/incorrect bands, major spacing/layout drift, contrast/accessibility failures) +- rerun smoke and section parity +- run final build checks: + - `pnpm -C packages/visual-editor build` + - `pnpm -C starter build` +- report what changed and what still differs + +Do not stop at parity report output unless the user explicitly asks to skip correction. +Do not stop at rerun screenshots either if build checks fail; fix build errors before finishing. + +For a brand-new generated parity test, run once with `--update` to seed screenshot baselines. + +Use parity output to apply one focused correction pass. If browser tests cannot run in the current environment, report that parity execution was skipped and why. + +Optional section parity controls: + +```bash +CLIENT_TEMPLATE_SECTION_PARITY_LIMIT=10 \ +CLIENT_TEMPLATE_SECTION_PARITY_ALL_VIEWPORTS=1 \ +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.section-parity.test.tsx +``` + +Optional section parity gate (keep disabled by default; enable only when needed): + +```bash +CLIENT_TEMPLATE_SECTION_PARITY_MAX_DIFF= \ +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.section-parity.test.tsx +``` + +## Optional Additional QA + +When rapid QA is requested, scaffold a smoke screenshot test: + +```bash +python3 scripts/scaffold_client_template_smoke_test.py \ + --client-slug \ + --template-path starter/src/templates//-template.tsx \ + --config-path starter/src/templates//-config.tsx +``` + +Then run the generated test with the repository Vitest browser workflow to produce screenshot artifacts. +For a brand-new generated smoke test, run once with `--update` to seed screenshot baselines. + +Smoke screenshots should capture full rendered page height (not just viewport fold) so footer/late-page regressions are visible. + +Optional full-page source-vs-generated parity scaffold: + +```bash +python3 scripts/scaffold_client_template_visual_parity_test.py \ + --client-slug \ + --html-path /path/to/source.html \ + --config-path starter/src/templates//-config.tsx \ + --overwrite +``` + +Run parity test: + +```bash +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.visual-parity.test.tsx +``` + +For a brand-new generated visual parity test, run once with `--update` to seed screenshot baselines. + +Optional parity gate (fail if diff is above threshold): + +```bash +CLIENT_TEMPLATE_PARITY_MAX_DIFF= \ +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.visual-parity.test.tsx +``` diff --git a/skills/ve-html-template-generator/agents/openai.yaml b/skills/ve-html-template-generator/agents/openai.yaml new file mode 100644 index 000000000..2df0746ef --- /dev/null +++ b/skills/ve-html-template-generator/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "VE HTML Template Generator" + short_description: "Generate slot-based client templates" + default_prompt: "Use $ve-html-template-generator with `client: ` and `html: ` to generate isolated, slot-based client component files under `packages/visual-editor/src/components/custom/` plus starter wrapper config/template files under `starter/src/templates/`, then run section screenshot parity as the default post-generation QA step." diff --git a/skills/ve-html-template-generator/references/baseline-reuse-map.md b/skills/ve-html-template-generator/references/baseline-reuse-map.md new file mode 100644 index 000000000..f3e0d2f53 --- /dev/null +++ b/skills/ve-html-template-generator/references/baseline-reuse-map.md @@ -0,0 +1,59 @@ +# Baseline Reuse Map + +Use this map before writing client-local atoms/slots/sections. Pick the closest shared baseline, then copy/adapt it into `packages/visual-editor/src/components/custom//...`. + +## Why + +- Client output should stay isolated from shared runtime registrations. +- But implementation quality should still match proven shared patterns. +- "New client component" should usually mean "client-local copy of a proven baseline plus client-specific changes", not an entirely novel shell. + +## Baseline Selection + +- Nearby locations bands: + - Section baseline: `packages/visual-editor/src/components/pageSections/NearbyLocations/NearbyLocations.tsx` + - Slot baseline: `packages/visual-editor/src/components/pageSections/NearbyLocations/NearbyLocationsCardsWrapper.tsx` + - Expect dynamic radius/limit + coordinate-driven lookup, not hardcoded nearby-store links. +- FAQ / Q&A bands: + - Section baseline: `packages/visual-editor/src/components/pageSections/FAQsSection/FAQsSection.tsx` + - Card baseline: `packages/visual-editor/src/components/pageSections/FAQsSection/FAQCard.tsx` + - Reuse shared FAQ data/functionality patterns first (question/answer rich text behavior, card/list structure, interaction model), then style to source visuals. +- Hours blocks: + - Baseline: `packages/visual-editor/src/components/contentBlocks/HoursTable.tsx` + - Expect `field: "hours"` entity defaults. + - For multiple hours bands (store/drive-thru/lobby), create separate entity-bound hours slots per band. +- Maps/directions: + - Baseline families: map patterns already used in shared VE (`MapboxStaticMapComponent`, `getDirections`). + - Avoid static iframe embeds and hardcoded one-off map URLs. +- Media/image slots: + - Baseline: shared image resolution + wrapper patterns (`resolveYextEntityField`, `getImageUrl`, `ImageWrapper`-style behavior). +- Multi-slot info bands: + - Baselines: `CoreInfoSection`, `AboutSection`, `VideoSection`. + - For complex/irregular bands, start with shared grid/core-info composition patterns, then wrap into client-bespoke section names and focused slots. +- CTA/link slots: + - Baseline: shared CTA slot ergonomics (show/hide toggles at section level for optional actions). + - Baseline implementation: `packages/visual-editor/src/components/contentBlocks/CtaWrapper.tsx` + - Every CTA label should map to actionable link/href/cta data; do not render CTA labels as plain text spans. +- Footer social links: + - Baseline: `packages/visual-editor/src/components/footer/FooterSocialLinksSlot.tsx` + - Expect platform-specific social fields, URL validation, and icon-based rendering (not plain text social link lists). + +## Slot/Atom Reuse Rules + +- Prefer adapting existing shared slot APIs first; only diverge when source fidelity requires it. +- Keep slot composition nested and concern-focused. +- For optional content in merged sections, keep the slot and gate with show/hide toggles. +- Keep generated slots non-nestable (`allow={[]}`) because they are editor surfaces, not arbitrary content containers. +- For FAQ/Q&A sections, prioritize shared FAQ interaction and data-shape behavior over bespoke one-off card models. +- Suppress likely hidden utility nav headings (for example `Top Links`, `Actions`, `Menu`) unless source evidence confirms they are visibly rendered. + +## Nearby Stores Rule + +When source includes a "Nearby Stores/Locations" area on a location template: + +- Do not seed static links as the primary behavior. +- Model after shared NearbyLocations behavior: + - radius + limit controls + - coordinate/document-driven lookup + - dynamic card/list rendering +- If source has a short static fallback list, keep it optional and clearly secondary to dynamic lookup. diff --git a/skills/ve-html-template-generator/references/client-config-category-pattern.md b/skills/ve-html-template-generator/references/client-config-category-pattern.md new file mode 100644 index 000000000..11ce6a400 --- /dev/null +++ b/skills/ve-html-template-generator/references/client-config-category-pattern.md @@ -0,0 +1,115 @@ +# Client Config and Category Pattern + +Use this pattern to keep client templates isolated, slot-based, and visible in both package `mainConfig` and starter `/edit`. + +## Goal + +- Keep custom section/slot/atom implementation in package custom paths. +- Expose client sections in one visible left-nav category. +- Register client slot components in one hidden category. +- Keep starter as a consumer of package config, not the source of truth. +- Ensure every `type: ""` referenced in section defaults is present in slot category component maps. + +## Package Category Files + +Add: + +- `packages/visual-editor/src/components/categories/SectionsCategory.tsx` +- `packages/visual-editor/src/components/categories/SlotsCategory.tsx` + +Pattern: + +```tsx +import { + YetiHeaderSection, + YetiHeaderSectionProps, +} from "../custom/yeti/components/YetiHeaderSection.tsx"; + +export interface YetiSectionsCategoryProps { + YetiHeaderSection: YetiHeaderSectionProps; +} + +export const YetiSectionsCategoryComponents = { + YetiHeaderSection, +}; + +export const YetiSectionsCategory = Object.keys( + YetiSectionsCategoryComponents, +) as (keyof YetiSectionsCategoryProps)[]; +``` + +Apply the same shape for slots category files. + +Slot registration rule: + +- If a section default uses `type: "YetiHoursSlot"`, then `YetiHoursSlot` must be included in `YetiSlotsCategoryComponents`. + +## Starter Client Config Example + +`starter/src/templates//-config.tsx` should import category/component maps from `@yext/visual-editor`: + +```tsx +import { Config, DropZone } from "@puckeditor/core"; +import { + YetiSectionsCategory, + YetiSectionsCategoryComponents, + YetiSectionsCategoryProps, + YetiSlotsCategory, + YetiSlotsCategoryComponents, + YetiSlotsCategoryProps, +} from "@yext/visual-editor"; + +export interface YetiTemplateProps + extends YetiSectionsCategoryProps, + YetiSlotsCategoryProps {} + +export const yetiConfig: Config = { + components: { + ...YetiSectionsCategoryComponents, + ...YetiSlotsCategoryComponents, + }, + categories: { + yetiSections: { + title: "Yeti Sections", + components: YetiSectionsCategory, + }, + yetiSlots: { + components: YetiSlotsCategory, + visible: false, + }, + }, + root: { + render: () => ( + + ), + }, +}; +``` + +## Package MainConfig Integration + +To make generated sections available in default `main` template flow: + +1. Export new category files from `packages/visual-editor/src/components/categories/index.ts`. +2. Register `SectionsCategoryComponents` and `SlotsCategoryComponents` in `packages/visual-editor/src/components/configs/mainConfig.tsx`. +3. Add one visible `Sections` and one hidden `Slots` category in `mainConfig.categories`. +4. Keep `directoryConfig` and `locatorConfig` unchanged unless explicitly requested. + +## Starter Editor Integration + +When `starter/src/ve.config.tsx` exists: + +1. Keep `devConfig` based on `...mainConfig.components` and `...mainConfig.categories`. +2. Add `-location` -> `Config` in `componentRegistry`. +3. Avoid starter-local ownership of client categories/components when package `mainConfig` already includes them. + +Do not add slot categories to visible starter nav categories. + +## Notes + +- Keep category IDs deterministic (`Sections`, `Slots`). +- Keep section category titles human-readable (`Yeti Sections`). +- Include header and footer sections in the visible section category. diff --git a/skills/ve-html-template-generator/references/layout-containment.md b/skills/ve-html-template-generator/references/layout-containment.md new file mode 100644 index 000000000..106414b9e --- /dev/null +++ b/skills/ve-html-template-generator/references/layout-containment.md @@ -0,0 +1,69 @@ +# Layout Containment + +Use these rules to prevent slot content from spilling into neighboring sections in editor or live rendering. + +## Shell Rules + +- Keep section content in normal document flow; avoid absolute/fixed positioning for core content. +- Add `overflow-hidden` to section content wrappers when multiple slots are stacked or arranged in columns. +- In grid/flex column layouts, add `min-w-0` to each column wrapper. +- Prefer intrinsic height (`h-auto`/content-driven) over fixed heights for text-heavy columns. + +## Slot Render Rules + +- Render slot children with `style={{ height: "auto" }}` by default. +- Keep `allow={[]}` unless the section intentionally supports free slot replacement. +- Use explicit wrappers per slot row/column to preserve boundaries. + +## Two-Column Details Pattern + +```tsx + +
+
+ + + +
+
+ +
+
+
+``` + +## Pre-Ship Checks + +- Resize content-heavy slots (long address, long parking copy, many hour rows) and verify no overlap with next section. +- Drag/reorder section in editor and confirm drop-zone overlays stay inside section bounds. + +## Full-Bleed Media Pattern + +For hero/promo sections that should match full-width source media: + +- Use section shell with zero horizontal padding and unconstrained content width. +- Ensure breakpoint padding is also removed (`md:px-0`) so desktop does not retain side gaps. +- Keep overlay copy padded internally while media itself spans edge-to-edge. +- Avoid rounded-corner wrappers on full-bleed media slots unless source design is rounded. + +Example pattern: + +```tsx + +
+ +
+
+ +
+
+
+
+``` diff --git a/skills/ve-html-template-generator/references/map-image-parity.md b/skills/ve-html-template-generator/references/map-image-parity.md new file mode 100644 index 000000000..63e192980 --- /dev/null +++ b/skills/ve-html-template-generator/references/map-image-parity.md @@ -0,0 +1,52 @@ +# Map and Image Parity + +Use this reference to keep generated client templates consistent with existing shared VE component behavior. + +## Map Parity Baseline + +Prefer these shared patterns: + +- `packages/visual-editor/src/components/pageSections/StaticMapSection.tsx` +- `packages/visual-editor/src/components/contentBlocks/MapboxStaticMap.tsx` +- `packages/visual-editor/src/components/contentBlocks/Address.tsx` (`getDirections` usage) + +Required map rules: + +- For location templates, prefer `MapboxStaticMapComponent` with coordinate entity defaults: + - `coordinate.field: "yextDisplayCoordinate"` +- If a static map embed is not available, use `getDirections(...)` links derived from address/entity data. +- Render a real map surface (shared map component/static map image) for map bands; do not use faux coordinate placeholder cards. +- Do not hardcode map provider URLs in defaults. +- Do not use raw `iframe` map embeds for core map blocks. +- Do not model map preview as a generic `mapImage` content field by default. +- Do not seed map defaults with hero/promo marketing imagery. + +## Image Parity Baseline + +Prefer these shared patterns: + +- `packages/visual-editor/src/components/pageSections/HeroSection.tsx` +- `packages/visual-editor/src/components/pageSections/PromoSection/PromoSection.tsx` +- `packages/visual-editor/src/components/contentBlocks/image/Image.tsx` (`ImageWrapper`) +- `packages/visual-editor/src/components/pageSections/heroVariants/SpotlightHero.tsx` + +Required image rules: + +- Background images should be modeled as `data.backgroundImage` entity fields: + - `type: "entityField"` + - `filter.types: ["type.image"]` +- Resolve background images at render-time via shared image resolver patterns: + - `resolveYextEntityField(...)` + - `getImageUrl(...)` +- Do not use plain URL text controls for core background image sources: + - avoid `backgroundImageUrl` fields in `styles`/`data` +- Image slots should follow `ImageWrapper`-style data shapes: + - `data.image` is an image entity field + - width/aspect-ratio style controls + - safe empty-state behavior if image data is missing + +## Default Data Guidance + +- For image defaults, include URL + dimensions so previews render immediately. +- Keep image defaults entity-compatible (constant by default, entity toggle available). +- For location templates, avoid store-specific hardcoded map URLs in defaults. diff --git a/skills/ve-html-template-generator/references/quality-checklist.md b/skills/ve-html-template-generator/references/quality-checklist.md new file mode 100644 index 000000000..6e4fb272f --- /dev/null +++ b/skills/ve-html-template-generator/references/quality-checklist.md @@ -0,0 +1,106 @@ +# Quality Checklist + +Run this checklist before finishing generation. + +## Architecture + +- Every section component has `slots` in props and fields. +- Slot fields are hidden (`visible: false`). +- Section render output composes slot children (``). +- Nested slot/layout components that render child slots also use ``. +- Slot default items use client-owned slot component types. +- Every slot type referenced by section defaults is registered in client slot category components. +- Section slot defaults are populated with real props (no `props: {}` placeholders). +- Slots represent focused concerns, not combined concerns (for example avoid `HoursLocationSlot`). +- Hours/location/parking concerns are represented by separate slots when present. +- Sections with 2+ slots expose top-level show/hide controls for most slots (target coverage: slot count minus one). +- Multi-band hours content (store/drive-thru/etc.) is represented as separate entity-bound hours slots. +- Text concerns are decomposed into focused slots (heading/body/disclaimer instead of one omnibus text slot). +- CTA concerns are decomposed into dedicated slots (primary/secondary CTA slots when both are present). +- Header/footer structures use nested slots (layout slot + focused child slots), not one terminal omnibus slot. +- Header/footer layout slots have fully populated child slot defaults (no empty child slot arrays). +- Footer social links use dedicated social-links slot behavior (platform-aware link fields + icon rendering), not generic text-link list slots. +- Header/footer section defaults match source chrome style (dark or light) while preserving readable contrast. +- Header/footer background style controls include source-theme options (for example brand-blue/dark chrome choices when source uses dark header/footer). +- Client-local sections/slots/atoms are adapted from the closest shared baseline unless a source-driven divergence is required. + +## Data Defaults + +- Most editable text/image content defaults to constant mode. +- Hours defaults to mapped entity mode (`field: "hours"` and `constantValueEnabled: false`). +- No hard-coded weekly hours rows unless explicitly requested by user. +- User-facing copy inputs use embedded-field-capable controls (`translatableString` or entity-backed string fields), not plain `text` inputs. +- Array fields include `defaultItemProps` so adding rows in editor cannot create malformed items. +- Avoid `entityField` filters with `type.url`; use text/translatable URL fields instead. +- `YextEntityField` defaults use localized objects (`{ en: "...", hasLocalizedValue: "true" }`), not plain string constants. +- `YextEntityField` defaults include localized objects with `hasLocalizedValue: "true"` when locale keys are used. +- Location-stream templates avoid hardcoded location constants (city/address/phone) with empty `field` bindings. +- Location-stream templates avoid hardcoded map/directions URLs tied to one address. +- Map implementations follow shared VE patterns (`MapboxStaticMapComponent` or `getDirections`), not hardcoded iframe map embeds. +- Map sections render real map surfaces (shared map component/static map image), not decorative placeholder cards. +- Map slots do not default to generic `mapImage` content fields unless explicitly requested. +- Map defaults are not reused from hero/promo image assets. +- Nearby stores/locations blocks are dynamic (shared NearbyLocations-style lookup), not static hardcoded store-link lists. +- FAQ/Q&A sections preserve shared FAQ behavior patterns (question/answer modeling + interaction behavior), with client/source-specific styling layered on top. +- Background images are entity-driven (`type.image`) instead of plain URL text fields like `backgroundImageUrl`. +- Image slots use shared-style image data patterns (`data.image` entity field + width/aspect controls + empty-state-safe behavior). + +## Editor Nav and Config + +- Client sections are visible in one client section category. +- Client slot components are in one hidden client slot category. +- Client slot components are included in `config.components` registration (for example via `SlotsCategoryComponents` spread). +- Atoms are not nav-selectable. +- Starter config is updated so client sections show in starter `/edit` left nav when applicable. +- Starter remains a consumer of package `mainConfig` (`...mainConfig.components`, `...mainConfig.categories`) instead of owning generated client section/slot maps. +- Starter `componentRegistry` includes `-location` mapped to `Config`. +- Starter `DevProps` is only extended with client section/slot props when explicit starter-local client component maps are intentionally added. +- Package `mainConfig` registers client-specific `SectionsCategoryComponents` and `SlotsCategoryComponents`. +- Package `mainConfig.categories` includes visible `Sections` + hidden `Slots` categories. +- Selecting a slot-backed section shows populated slot props in the right-hand props panel. +- All slot render calls include `allow={[]}` and `style={{ height: "auto" }}` in both sections and nested slot/layout components. +- Freshly dragged header/footer sections are editable in-canvas (no blank/non-clickable nested slot regions). + +## Runtime and UX + +- Use render-safe defaults so sections display immediately. +- Prefer theme-backed controls for color selectors over raw text color fields. +- Add `liveVisibility` toggles for section-level show/hide behavior. +- Use section-level error boundaries/wrappers where appropriate. +- Ensure `` receives `metadata={{ streamDocument: document }}` in client template entrypoints. +- Render code defensively handles partially defined array rows (no unsafe nested `.field` reads). +- Similar sections that differ only by copy/CTA visibility are merged into one component with toggles. +- Merged sections preserve optional slots from all source variants (for example CTA slots) and expose explicit show/hide toggles for those slots. +- CTA labels are always actionable (paired with href/link/CTA entity data), not rendered as plain text. +- Multi-slot section shells include containment cues (`overflow-hidden`, column wrappers with `min-w-0`). +- Store-info sections that include hours/location/map/parking preserve source-first slot order (default `Hours -> Location -> Map -> Parking`). +- Core section/slot wrappers avoid absolute/fixed positioning for main content. +- Section-level `textColor` controls remain effective (slot content does not force hardcoded conflicting text color classes). +- Header/footer editability is preserved through nested child slots for logo/nav/CTA/legal concerns. +- Header/footer text and border styles maintain contrast and do not rely on fixed white/light utility classes. +- Header/nav utility defaults avoid noisy hidden headings (for example `Top Links`, `Actions`, `Menu`) unless source explicitly shows them. +- Hero/promo sections that are media-led use full-bleed section shell wrappers (`px-0 md:px-0 py-0 md:py-0` + `max-w-none`) when source parity requires edge-to-edge imagery. +- Hero/promo media slots avoid rounded corners by default unless source design explicitly calls for rounded media. +- Hero/promo image elements render as block-level (`block h-full w-full object-cover`) to avoid baseline whitespace seams. +- Rich text slot rendering uses `resolveComponentData` render output (React element or string) rather than html-only extraction, so copy remains visible across value shapes. +- Source bands that pair media + list/content keep a dedicated media slot instead of dropping imagery during decomposition. +- Generated files do not reference sibling client template paths/symbols under `starter/src/templates/` or `packages/visual-editor/src/components/custom/`. + +## Final Validation + +- Run `python3 scripts/validate_client_template.py --client-path starter/src/templates/`. +- Type check passes for generated client template files. +- Sections render in expected order in default layout. +- Default layout section entries use populated props (default props or explicit overrides), not `props: {}` placeholders. +- Client category appears in left nav in starter editor. +- Validate with a clean/new layout state (no stale `document.__.layout` from old generations). +- Hours slot resolves live entity hours without manual constant content. +- Hours blocks keep explicit entity-backed wiring (`field: "hours"`) and expose entity/constant state correctly in the editor. +- Summary/hero info areas avoid monolithic slot props and are decomposed into nested focused child slots. +- Validator does not report suspicious structural similarity to older sibling client templates. +- Section parity test is scaffolded and run for the generation (or explicitly reported as skipped with reason if browser screenshot tests are unavailable). +- Parity-driven updates are limited to high-impact visual corrections and do not degrade slot architecture or field/editability behavior. +- For major hierarchy/order mismatches, parity should drive one corrective pass (source order/stacking/theme contrast) before finalizing. +- Optional smoke screenshot tests are scaffolded when requested and run (or explicitly reported as not run if environment lacks browser test support). +- After correction-pass reruns, run `pnpm -C packages/visual-editor build` and ensure it exits successfully. +- After correction-pass reruns, run `pnpm -C starter build` and ensure it exits successfully. diff --git a/skills/ve-html-template-generator/references/section-normalization.md b/skills/ve-html-template-generator/references/section-normalization.md new file mode 100644 index 000000000..026d78ff6 --- /dev/null +++ b/skills/ve-html-template-generator/references/section-normalization.md @@ -0,0 +1,131 @@ +# Section Normalization + +Use this pass before writing final section files. + +## Goal + +- Split over-aggregated slots into focused slot components. +- Merge structurally duplicate sections into one reusable section. + +## Slot Decomposition Rules + +Each slot should map to one clear concern. Avoid omnibus slots. + +Good: + +- `HoursSlot` +- `LocationInfoSlot` +- `ParkingSlot` + +Bad: + +- `HoursLocationSlot` +- `InfoAndHoursSlot` + +When a section contains hours, location info, and parking, default to three slots: + +- `HoursSlot` +- `LocationInfoSlot` +- `ParkingSlot` + +If source contains multiple hours bands (for example store hours and drive-thru hours), do not collapse them into one hours slot. +Generate one dedicated entity-bound hours slot per band. + +Text and CTA decomposition defaults: + +- `HeadingSlot` for primary heading text. +- `BodySlot` for descriptive copy. +- `PrimaryCtaSlot` and `SecondaryCtaSlot` for distinct CTAs. +- `DisclaimerSlot` for legal/supporting copy. +- Use embedded-field-capable copy inputs (`translatableString` or entity-backed string fields) for user-visible non-entity text. +- Every CTA label slot/field should pair with actionable href/link/CTA data; do not keep CTA labels as plain text-only output. + +For non-list slots, treat these as over-aggregated and split them: + +- 3+ translatable text entity fields in one slot +- Any CTA label/link fields combined with multiple unrelated text fields + +If visual grouping is needed, create a layout slot that composes focused child slots. + +Nested-slot bias: + +- Prefer nested layout slots over packing multiple text/CTA fields into one terminal slot. +- Keep parent section as a shell, with layout slots composing focused content slots. +- Keep nested slot renders locked for editing-only usage (`allow={[]}` + `style={{ height: "auto" }}`). + +Header/footer decomposition: + +- Treat header/footer as high-priority slotification targets. +- Split monolithic header/footer slots into layout + focused child slots. +- Do not leave header/footer copy/links/buttons in one terminal slot component. +- Do not model footer social as a generic text-link list slot. Use a dedicated social-links slot pattern with platform-aware link fields and icon rendering. +- Suppress noisy hidden utility heading labels in nav/footer defaults (`Top Links`, `Actions`, `Menu`) unless source evidence confirms visible rendering. + +Location summary decomposition: + +- Treat hero/location summary content as a layout slot composed of focused child slots. +- Split into child concerns such as: + - back link + - location heading/address/meta + - status row + - CTA row +- Avoid monolithic summary slots with many unrelated text/link fields. + +## Duplicate Section Merge Rules + +If two sections share the same layout shell, slot schema, and style controls, merge them into one section component. + +Use configurable toggles/variants instead of duplicate files. + +When merging, preserve the superset of optional content slots from all variants (do not drop "sometimes present" slots). + +Example: + +- If one variant has CTA and another does not, keep CTA slot(s) in merged component. +- Add explicit top-level show/hide toggles for those optional slots (`showPrimaryCta`, `showSecondaryCta`, etc.). +- Default toggles per layout instance to match source parity (disabled where not used). + +Example merge: + +- Replace `CustomizeSection` + `ReserveSection` with `PromoBannerSection` +- Add style/data toggles: + - `showCTA: boolean` + - `overlayClass` + - `backgroundImageUrl` +- Keep alternate defaults as preset prop objects. + +## Decision Heuristic + +Treat sections as duplicates when all are true: + +- Same slot keys +- Same style field keys +- Same dominant layout pattern (same wrapper structure) + +When duplicates are detected, generate one component plus multiple default preset objects. + +## Baseline-First Rules + +Use existing shared VE sections as implementation baselines before inventing novel structures. +Do not use sibling client templates as baselines. + +Preferred baseline order: + +1. `CoreInfoSection` for multi-slot information grids +2. `AboutSection` for mixed column content and sidebar patterns +3. `VideoSection` for simple heading+content shells + +For complex bespoke pages, assemble with shared grid/core-info atom patterns first, then wrap that composition in client-bespoke section names and focused slot labels. + +Then adapt/copy those patterns into client-local files (do not import shared sections directly). + +For location pages with nearby stores/locations content, baseline against NearbyLocations patterns rather than creating static nearby-link lists. + +## Containment Rules + +Before finalizing, verify section shell containment: + +- Multi-slot section container uses `overflow-hidden`. +- Grid/flex column wrappers include `min-w-0`. +- Slot calls use `style={{ height: "auto" }}` unless intentionally overridden. +- Core content in section/slot wrappers avoids absolute/fixed positioning. diff --git a/skills/ve-html-template-generator/references/slot-paradigm.md b/skills/ve-html-template-generator/references/slot-paradigm.md new file mode 100644 index 000000000..9b277c374 --- /dev/null +++ b/skills/ve-html-template-generator/references/slot-paradigm.md @@ -0,0 +1,218 @@ +# Slot Paradigm + +Use this for all generated client sections. + +## Section Shape + +Client sections should be structural wrappers that render slot content. + +```tsx +import { ComponentConfig, Fields, PuckComponent, Slot } from "@puckeditor/core"; +import { YextField } from "@yext/visual-editor"; + +type ClientHeroSectionProps = { + styles: { + backgroundColor?: string; + }; + slots: { + HeadingSlot: Slot; + BodySlot: Slot; + PrimaryCTASlot: Slot; + }; + liveVisibility: boolean; +}; + +const fields: Fields = { + styles: YextField("Styles", { + type: "object", + objectFields: { + backgroundColor: YextField("Background Color", { + type: "select", + options: "BACKGROUND_COLOR", + }), + }, + }), + slots: { + type: "object", + objectFields: { + HeadingSlot: { type: "slot" }, + BodySlot: { type: "slot" }, + PrimaryCTASlot: { type: "slot" }, + }, + visible: false, + }, + liveVisibility: YextField("Visible on Live Page", { + type: "radio", + options: [ + { label: "Show", value: true }, + { label: "Hide", value: false }, + ], + }), +}; + +const HeroView: PuckComponent = ({ slots }) => { + return ( +
+ + + +
+ ); +}; +``` + +## Default Slot Props + +Each section must define `defaultProps.slots` with client-owned slot types. + +```tsx +defaultProps: { + slots: { + HeadingSlot: [{ type: "YetiHeadingSlot", props: { ... } }], + BodySlot: [{ type: "YetiBodySlot", props: { ... } }], + PrimaryCTASlot: [{ type: "YetiCTASlot", props: { ... } }], + }, +} +``` + +Never use placeholder slot defaults like `props: {}`. + +## Slot Drop Lock + +Generated slots are for editing pre-scaffolded fields/props, not for arbitrary nesting. + +- Always render generated slot calls with `allow={[]}`. +- Apply this to section-level slot calls and nested layout-slot calls. +- Keep `style={{ height: "auto" }}` on those slot calls unless intentionally overridden. + +Prefer exporting `defaultProps` from each slot component and reusing those objects in section slot defaults. + +## Top-Level Slot Visibility Controls + +For sections with 2+ slots, expose top-level `show...` toggles in `styles` for most slots. + +- Target coverage: `slot count - 1` or higher. +- Keep always-on slots only when they are structurally required. +- Wire each toggle directly in section render logic, not only inside nested child slots. + +Example: + +```tsx +styles: { + showHeading: boolean; + showBody: boolean; + showPrimaryCta: boolean; +} +``` + +Example: + +```tsx +import { defaultYetiFaqListSlotProps } from "./YetiFaqListSlot"; + +slots: { + FaqListSlot: [{ type: "YetiFaqListSlot", props: defaultYetiFaqListSlotProps }], +} +``` + +## Hours Slot Rule + +When a section includes hours, route through a client slot component and default to entity hours: + +```tsx +HoursSlot: [ + { + type: "YetiHoursSlot", + props: { + data: { + hours: { + field: "hours", + constantValue: {}, + constantValueEnabled: false, + }, + }, + }, + }, +]; +``` + +Avoid hard-coding weekly hours strings in section defaults. + +If source has multiple hours bands, generate multiple hours slots (for example `StoreHoursSlot`, `DriveThruHoursSlot`) and keep each one entity-bound. + +## Decomposition Heuristic + +Prefer multiple focused slots over one combined slot. + +When a section visually contains distinct concerns (hours table, location details, parking notes), represent each concern as its own slot rather than a merged slot. + +Apply this rule to copy/CTA decomposition as well: + +- Heading text should be its own slot. +- Body/description text should be its own slot. +- Each CTA should be its own slot (primary CTA, secondary CTA). +- Legal/disclaimer text should be its own slot. +- For user-visible non-entity copy fields, use embedded-field-capable inputs (`translatableString`) rather than plain `text` inputs. +- CTA labels must map to actionable href/link/CTA data; do not render CTA labels as plain text spans. + +Avoid non-list slots with many text fields plus CTA fields. If grouping is needed, use a layout slot that renders child slots. + +Favor nested slot composition when in doubt: + +- Section shell slot -> layout slot -> focused text/CTA slots +- Avoid terminal slots that try to own all copy and actions for an entire band + +Header/footer specific rule: + +- Header/footer should always be multi-layer slot compositions, not single terminal content slots. +- Use focused child slots for: + - logo/brand + - navigation groups + - CTA groups + - utility/legal groups +- Keep header/footer layout orchestration in layout slots and keep content concerns in child slots. +- Ensure layout slots have populated default child slots so they are editable immediately after drag/drop. +- Avoid introducing layout-slot shapes that require runtime backfill/normalization to become editable. +- Footer social link concerns should use dedicated social slot patterns (platform-aware fields + icon rendering), not generic text-link list slots. +- Avoid noisy nav utility heading defaults (`Top Links`, `Actions`, `Menu`) unless source evidence confirms they are visibly rendered. + +Location summary specific rule: + +- Avoid one monolithic summary slot with many unrelated fields. +- Use nested summary layout slots with focused child slots (back-link, heading/meta, status, CTA). + +Recommended pattern: + +```tsx +slots: { + HoursSlot: Slot; + LocationInfoSlot: Slot; + MapSlot: Slot; + ParkingSlot: Slot; +} +``` + +When all four concerns are present, preserve source-first stacked order by default: + +`Hours -> Location -> Map -> Parking` + +## Containment Pattern for Multi-Column Sections + +Use containment-safe wrappers to avoid slot overflow into adjacent sections: + +```tsx +
+
+
+ + + +
+
+ +
+
+
+``` + +For core content, avoid absolute/fixed positioning in slot wrappers unless strictly decorative and fully bounded by a relative container. diff --git a/skills/ve-html-template-generator/references/template-contract.md b/skills/ve-html-template-generator/references/template-contract.md new file mode 100644 index 000000000..8b5f2cc2c --- /dev/null +++ b/skills/ve-html-template-generator/references/template-contract.md @@ -0,0 +1,168 @@ +# Template Contract + +Use this contract for every generated client template. + +## Required Directory Layout + +```text +packages/visual-editor/src/components/custom// + atoms/ + .tsx + ... + components/ + Section.tsx + Slot.tsx + ... + index.ts + ve.ts + +packages/visual-editor/src/components/categories/ + SectionsCategory.tsx + SlotsCategory.tsx + +starter/src/templates// + -config.tsx + -template.tsx +``` + +## Naming Rules + +- Use lowercase client slug for generated folder and starter template filenames. +- Use `PascalCase` for section/slot component filenames and exports. +- Suffix generated section components with `Section`. +- Suffix slot components with `Slot`. +- Keep component names stable after first generation; update implementation instead of renaming unless required. +- Prefix client-branded header/footer names with client context (`YetiHeaderSection`, `YetiFooterSection`). + +## Isolation Rules + +- Do not import shared/generic VE atoms, sections, categories, or config maps into client generated section/slot/atom implementations. +- Do not import, reference, or adapt sibling client templates under `starter/src/templates/`. +- Do not import, reference, or adapt sibling custom clients under `packages/visual-editor/src/components/custom/`. +- Always generate new client-specific header/footer sections. +- Always generate client-specific atoms even when equivalent shared atoms exist. +- Keep generated section/slot/atom implementation in `packages/visual-editor/src/components/custom/`. +- Use shared generic components only as behavior baselines to copy/adapt into client-local files. + +## Template Responsibilities + +In `starter/src/templates//-template.tsx`: + +- Use a full `@yext/pages` template entrypoint structure. +- Import client section components from `@yext/visual-editor`. +- Import client config from `./-config`. +- Preserve source page order when composing the initial layout and defaults. +- In default layout `content`, use section `defaultProps` (or explicit deep overrides) instead of `props: {}` placeholders. +- Export the client template entrypoint used by the site template system. + +In `starter/src/templates//-config.tsx`: + +- Register client-owned section and slot category component maps imported from `@yext/visual-editor`. +- Define one visible client-specific category for sections in the left nav. +- Define one hidden client-specific category for slot components. +- Expose only section components in the visible category. +- Do not register atoms as nav-selectable components. +- Register all slot component types referenced by section `defaultProps.slots`. + +In `packages/visual-editor/src/components/categories/SectionsCategory.tsx` and `SlotsCategory.tsx`: + +- Import section/slot components from `../generated//components/...`. +- Export props interfaces and `...CategoryComponents` maps. +- Export category arrays from `Object.keys(...CategoryComponents)`. + +In `packages/visual-editor/src/components/configs/mainConfig.tsx`: + +- Register `SectionsCategoryComponents` and `SlotsCategoryComponents` in `components`. +- Add one visible `Sections` category and one hidden `Slots` category in `mainConfig.categories`. +- Leave `directoryConfig` and `locatorConfig` unchanged unless explicitly requested. + +When `starter/src/ve.config.tsx` is present: + +- Keep starter as a consumer of package `mainConfig` (`...mainConfig.components`, `...mainConfig.categories`). +- Add `-location` -> `Config` to `componentRegistry`. +- Avoid starter-local mutation of `mainConfig` or starter-sourced category ownership. + +## Section Component Responsibilities + +In each `packages/visual-editor/src/components/custom//components/Section.tsx`: + +- Define explicit props with `data`, `styles`, and `slots`. +- Drive structure/content composition through slots, not inline hard-coded markup. +- Use intuitive, concern-specific slots (for example `HoursSlot`, `LocationInfoSlot`, `ParkingSlot`). +- When source has multiple hours bands, represent each band with its own entity-bound hours slot. +- Decompose text and CTA concerns into dedicated slots by default (for example `HeadingSlot`, `BodySlot`, `PrimaryCtaSlot`). +- Use embedded-field-capable controls for user-visible copy (`translatableString` or entity-backed string fields), not plain `text` inputs. +- Keep sections focused on one horizontal band from the source page. +- Keep defaults close to source copy and imagery for fast first review. +- Avoid introducing cross-client abstractions in first-pass output. +- Nearby stores/location sections should follow shared NearbyLocations-style dynamic behavior, not static hardcoded store-link lists. +- FAQ/Q&A sections should follow shared FAQ data/interaction patterns first, then adapt visuals. +- When source includes media + list compositions (for example amenities image + amenities list), preserve a dedicated media slot in the section shell. +- When combining two similar sections, preserve the superset of optional slots (for example CTA slots) and gate optional ones with top-level show/hide style toggles. +- Use containment-safe section shells (`overflow-hidden`, `min-w-0` column wrappers, slot render calls with `style={{ height: "auto" }}`). +- Header/footer sections must use nested slot composition (layout slot + focused child slots), not single omnibus slots. +- Header/footer layout slots must include populated default child-slot entries so nested slots are immediately editable after drag/drop. +- Header/footer slot content should not depend on fixed white/light text classes for readability; preserve contrast across background options. +- Header/footer section defaults should match source chrome style (dark or light) while preserving readable contrast. +- Avoid noisy utility heading defaults in header/nav groups (for example `Top Links`, `Actions`, `Menu`) unless source evidence shows those headings are visibly rendered. +- Footer social links should follow the shared social-links baseline (platform-specific fields, URL validation, icon rendering), not plain text social link lists. +- Location summary-style content should use nested layout + focused child slots rather than one large summary slot. +- Hero/promo sections should use full-bleed shell wrappers (`px-0 md:px-0` outer padding, `max-w-none` content wrapper) when source parity expects media to reach section edges. + +## Slot Component Responsibilities + +In each `packages/visual-editor/src/components/custom//components/Slot.tsx`: + +- Implement client-owned content blocks used by section slots. +- Keep slot component APIs narrow and reusable across client sections. +- Treat generated slots as non-nestable editing surfaces: any `` render inside slot/layout components must use `allow={[]}` and `style={{ height: "auto" }}`. +- For hours slots, default to mapped entity hours (`field: "hours"`) instead of hard-coded constant hours. +- Export slot components through client slot category maps so Puck can resolve them during drag/drop. +- For array controls, provide `defaultItemProps` to prevent malformed new rows. +- Do not rely on `type.url` entity selectors for editable URL values. +- Map slots should follow shared VE map patterns (`MapboxStaticMapComponent` or `getDirections`), not hardcoded iframe URLs. +- Image/background slots should follow shared VE image entity patterns (`type.image` selectors and render-time image resolution). +- Media-led hero/promo slots should avoid rounded-corner wrappers unless source design explicitly requires rounded media. +- Rich text slots should render `resolveComponentData` output directly (React element or string) instead of assuming `value.html` is always present. +- CTA labels should always pair with actionable href/link/CTA data and render as CTA controls, not plain text spans. + +## Atom Responsibilities + +In each `packages/visual-editor/src/components/custom//atoms/.tsx`: + +- Keep atom API minimal and predictable. +- Include only behavior needed by client-owned sections. +- Avoid importing shared atoms from outside the client generated folder. +- Do not depend on runtime layout-shape normalization to make generated slots editable; generate correct slot defaults directly. + +## Minimum Completion Checklist + +- Every major visual band has exactly one section component. +- Header and footer are client-specific sections. +- Atoms used by sections are client-specific atoms. +- Sections use hidden slot fields and render through slot children. +- All slot render calls are locked (`allow={[]}`) in both sections and nested slot/layout components. +- Section slot defaults are fully populated (no `props: {}` placeholders). +- Header/footer default section backgrounds/styles match source chrome style. +- Starter client config/template import client category components/sections from `@yext/visual-editor`. +- Package `mainConfig` and categories include the new client section/slot category wiring. +- No sibling client symbols/paths are referenced in generated code. +- The client config includes visible `clientSections` and hidden `clientSlots` categories. +- Slot components with array fields include `defaultItemProps`. +- `YextEntityField` defaults use localized constant objects (`{ en, hasLocalizedValue: "true" }`) so right-panel defaults are visible on first load. +- `YextEntityField` defaults include `hasLocalizedValue: "true"` when using locale-key constants. +- User-facing copy fields are embedded-field capable (`translatableString` or entity-backed string fields), not plain text controls. +- Hours/location/parking are not collapsed into a single omnibus slot. +- Similar shell sections are merged into one configurable section where feasible. +- Location-stream templates avoid hardcoded city/address/phone/map defaults tied to a single location. +- Map blocks avoid hardcoded iframe provider embeds and rely on shared map patterns. +- Map slots do not default to generic `mapImage` fields populated with marketing imagery. +- Background images and image slots are entity-driven (`type.image`) rather than plain URL text fields. +- Section-level text color controls are not neutralized by hardcoded slot text color classes. +- Header/nav defaults do not include hidden utility heading noise (`Top Links`, `Actions`, `Menu`) unless source visibly renders those headings. +- CTA labels are actionable (link/href/cta), not plain text. +- Hero/promo media reaches section edges for full-width source bands (no accidental inset wrappers or rounded-card framing), including desktop breakpoints. +- The template root imports and uses the client config. +- The template default layout does not use empty section `props: {}` placeholders. +- The generated files compile with no type errors. +- The output is easy to iterate component-by-component in follow-up prompts. diff --git a/skills/ve-html-template-generator/references/test-and-screenshot-workflow.md b/skills/ve-html-template-generator/references/test-and-screenshot-workflow.md new file mode 100644 index 000000000..53f6e1e6a --- /dev/null +++ b/skills/ve-html-template-generator/references/test-and-screenshot-workflow.md @@ -0,0 +1,173 @@ +# Test and Screenshot Workflow + +Use this on every generation as the default post-generation parity workflow. + +## Goal + +- Create a smoke test that renders the generated client template/config. +- Capture screenshots for quick visual review. +- Reuse the existing Vitest browser + screenshot matcher workflow. +- Prefer section-level source-vs-generated comparison before whole-page comparison. +- Always capture section-level source-vs-generated parity in one pass (report-first defaults). + +## Scaffold Test + +Run: + +```bash +python3 scripts/scaffold_client_template_smoke_test.py \ + --client-slug \ + --template-path starter/src/templates//-template.tsx \ + --config-path starter/src/templates//-config.tsx +``` + +This creates: + +- `packages/visual-editor/src/components/generated/Template.smoke.test.tsx` + +## Required Section Parity (Default) + +Run: + +```bash +python3 scripts/scaffold_client_template_section_parity_test.py \ + --client-slug \ + --html-path /path/to/source.html \ + --config-path starter/src/templates//-config.tsx \ + --overwrite +``` + +This creates: + +- `packages/visual-editor/src/components/generated/Template.section-parity.test.tsx` +- `packages/visual-editor/src/components/generated/Template.section-parity.source.html` + +Run section parity test: + +```bash +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.section-parity.test.tsx +``` + +For a brand-new generated parity test, run once with `--update` to seed screenshot baselines: + +```bash +pnpm -C packages/visual-editor exec vitest run --update \ + src/components/generated/Template.section-parity.test.tsx +``` + +After the initial parity run, apply one focused correction pass immediately, then rerun smoke + section parity. +After those reruns, run final build checks: + +```bash +pnpm -C packages/visual-editor build +pnpm -C starter build +``` + +Default workflow is now: `generate -> parity -> correction pass -> rerun -> build gate`. + +Optional section parity controls: + +```bash +CLIENT_TEMPLATE_SECTION_PARITY_LIMIT=10 \ +CLIENT_TEMPLATE_SECTION_PARITY_ALL_VIEWPORTS=1 \ +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.section-parity.test.tsx +``` + +Optional section parity gate: + +```bash +CLIENT_TEMPLATE_SECTION_PARITY_MAX_DIFF= \ +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.section-parity.test.tsx +``` + +## Optional Full-Page Parity + +Optional full-page parity scaffold: + +```bash +python3 scripts/scaffold_client_template_visual_parity_test.py \ + --client-slug \ + --html-path /path/to/source.html \ + --config-path starter/src/templates//-config.tsx \ + --overwrite +``` + +This creates: + +- `packages/visual-editor/src/components/generated/Template.visual-parity.test.tsx` +- `packages/visual-editor/src/components/generated/Template.source.html` + +## Run Test + +Run only the generated test: + +```bash +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.smoke.test.tsx +``` + +For a brand-new generated smoke test, run once with `--update` to seed screenshot baselines: + +```bash +pnpm -C packages/visual-editor exec vitest run --update \ + src/components/generated/Template.smoke.test.tsx +``` + +Smoke screenshots should capture full page height so lower-page sections (for example footer/legal) are included in review. + +Run visual parity test: + +```bash +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.visual-parity.test.tsx +``` + +For a brand-new generated visual parity test, run once with `--update` to seed screenshot baselines: + +```bash +pnpm -C packages/visual-editor exec vitest run --update \ + src/components/generated/Template.visual-parity.test.tsx +``` + +Optional parity gate: + +```bash +CLIENT_TEMPLATE_PARITY_MAX_DIFF= \ +pnpm -C packages/visual-editor exec vitest run \ + src/components/generated/Template.visual-parity.test.tsx +``` + +## Test Expectations + +Generated smoke tests should: + +- render a minimal layout using the generated client config +- verify no fatal render errors +- run across desktop/tablet/mobile viewports +- capture full rendered page height screenshots for baseline visual review + +Generated visual parity tests should: + +- capture section-level source screenshot baselines and generated section screenshots +- log diff pixel counts per section so large regressions are easy to triage +- default to report-only behavior unless explicit parity thresholds are provided +- treat section pairing as a heuristic by source order and generated section registration order (manual review is still required) +- capture source HTML screenshot baselines per viewport +- capture generated template screenshots per viewport +- log source-vs-generated diff pixel counts +- optionally fail when diff exceeds `CLIENT_TEMPLATE_PARITY_MAX_DIFF` + +## Notes + +- Keep smoke tests lightweight; they are intended for rapid iteration feedback. +- Keep screenshot parity advisory-first: fix missing media/structure/wiring issues first, then tune styling. +- Do not break baseline slot behavior or field editability for screenshot parity wins. +- Apply a single parity-driven correction pass by default; only run extra passes for severe mismatches or explicit user request. +- Prioritize high-impact parity fixes: missing imagery/maps, wrong section hierarchy, readability/contrast failures, and major spacing/layout breaks. +- Treat hierarchy/order mismatches as mandatory correction targets in the default pass (for example stacked store info ordering, header/footer theme contrast). +- For map sections that require API keys, prefer deterministic fallbacks and modest screenshot thresholds. +- If the local environment cannot run browser screenshot tests, still generate the test file and report that execution was skipped. +- Use parity screenshots as an iterative loop: fix largest visual gaps first (missing media, spacing, hierarchy, contrast), rerun parity test, repeat. diff --git a/skills/ve-html-template-generator/references/ve-field-patterns.md b/skills/ve-html-template-generator/references/ve-field-patterns.md new file mode 100644 index 000000000..4887a72d1 --- /dev/null +++ b/skills/ve-html-template-generator/references/ve-field-patterns.md @@ -0,0 +1,329 @@ +# VE Field Patterns + +Use these patterns for generated components so they work with Visual Editor controls. + +## Slot-First Rule + +Section components should be layout shells that render slot children. + +Required shape in section props: + +```ts +slots: { + : Slot; + ... +} +``` + +Required shape in section fields: + +```ts +slots: { + type: "object", + objectFields: { + : { type: "slot" }, + }, + visible: false, +} +``` + +Use slot defaults in `defaultProps.slots` with client-owned slot component types. + +## Local Atom Rule + +When a section needs primitives (heading, body text, image wrapper, button), implement/import them from `packages/visual-editor/src/components/custom//atoms/*`. + +Do not import shared atoms from generic VE directories. + +## Core Shape: Entity vs Constant + +Use this data shape for content-bearing props: + +```ts +type YextEntityField = { + field: string; + constantValue: T; + constantValueEnabled?: boolean; +}; +``` + +Initialize most fields with meaningful constants and `constantValueEnabled: true`. + +Exception: + +- Hours fields should default to mapped entity values (`field: "hours"`) and entity mode. + +Supported entity selector filters for this skill: + +- `type.string` +- `type.rich_text_v2` +- `type.image` +- `type.phone` +- `type.hours` + +Avoid `entityField` with `type.url` in generated client templates. + +## Text Field Pattern + +Use an entity field selector filtered to string values: + +```ts +text: YextField("Text", { + type: "entityField", + filter: { types: ["type.string"] }, +}); +``` + +Default value pattern: + +```ts +text: { + field: "", + constantValue: { en: "Section heading", hasLocalizedValue: "true" }, + constantValueEnabled: true, +} +``` + +Do not use a plain string for `constantValue` on `YextEntityField` defaults. + +Bad: + +```ts +constantValue: "Section heading"; +``` + +This can render in preview, but the right-side prop editor often initializes as empty because it reads localized values from `constantValue[locale]`. + +## Rich Text Pattern + +Use rich text selector for marketing copy blocks: + +```ts +description: YextField("Description", { + type: "entityField", + filter: { types: ["type.rich_text_v2"] }, +}); +``` + +Default constant value should be locale-aware and render-safe. + +For `YextEntityField` defaults, include `hasLocalizedValue: "true"` when using locale keys. + +Good: + +```ts +description: { + field: "", + constantValue: { + en: { html: "

Example

", json: "{...}" }, + hasLocalizedValue: "true", + }, + constantValueEnabled: true, +} +``` + +Avoid locale-key objects without `hasLocalizedValue`, which can break prop-panel hydration and runtime rendering. + +Render pattern for rich text slots: + +- Resolve with `resolveComponentData(...)` and render the result directly. +- Handle both `ReactElement` and `string` outputs. +- Do not rely exclusively on manual `value.html` parsing, because editor/KG data may arrive as different rich-text object shapes. + +## Hours Pattern + +Use hours selectors for hours-based slot components: + +```ts +hours: YextField("Hours", { + type: "entityField", + filter: { types: ["type.hours"] }, +}); +``` + +Default hours binding pattern: + +```ts +hours: { + field: "hours", + constantValue: {}, + constantValueEnabled: false, +} +``` + +## URL Field Pattern + +Use URL fields directly for links/background URLs: + +```ts +ctaUrl: YextField("CTA URL", { type: "text" }); +``` + +If localization is required, use a translatable-string field strategy rather than `entityField` with `type.url`. + +Do not use URL text fields for core map/image sources (background images, map embeds, directions source URLs) when entity-backed/shared patterns are available. + +## Location Stream Dynamic Data Pattern + +For templates with `entityTypes: ["location"]`, avoid hardcoded location defaults that lock output to one site/store. + +Preferred defaults: + +- Store name/hero title: bind to `field: "name"` when the source represents location identity. +- Address text: bind to address fields (`address.line1`, derived city/state/postal, or `address` object patterns). +- Phone text: bind to `mainPhone` (or equivalent phone entity field) instead of raw text fields. +- Hours: bind to `hours` with `constantValueEnabled: false`. + +Avoid in location-stream defaults: + +- hardcoded city/address strings with empty `field` +- hardcoded map/directions URLs tied to one address +- plain `text` field controls for location primitives (`mapUrl`, `directionsUrl`, `phoneNumber`) unless the user explicitly requests static-only behavior + +## Image Asset Pattern + +Use image selector for visual media blocks: + +```ts +image: YextField( + "Image", + { + type: "entityField", + filter: { types: ["type.image"] }, + }, +); +``` + +Default value should include URL and dimensions to avoid empty render states. + +For background imagery, prefer shared hero/promo shape: + +```ts +backgroundImage: YextField< + any, + ImageType | AssetImageType | TranslatableAssetImage +>("Image", { + type: "entityField", + filter: { types: ["type.image"] }, +}); +``` + +Avoid `backgroundImageUrl` text fields for primary background image control. + +Use shared render-time resolution patterns (`resolveYextEntityField`, `getImageUrl`) for localized image values. + +For image slots, mirror `ImageWrapper` conventions: + +- `data.image` entity field with `type.image` +- style controls like width/aspect ratio +- empty-state-safe render behavior for missing image data + +## Map Pattern + +For location templates, align map behavior with shared VE map patterns: + +- Preferred: `MapboxStaticMapComponent` + coordinate entity field (`field: "yextDisplayCoordinate"`). +- Acceptable fallback: `getDirections(...)` links derived from address/entity data. + +Avoid: + +- hardcoded map provider URLs in defaults +- raw `iframe` embeds as the primary map behavior + +## Resolve Pattern in Render + +Resolve field data through locale + stream document: + +```ts +const streamDocument = useDocument(); +const { i18n } = useTranslation(); + +const headline = resolveComponentData( + data.headline, + i18n.language, + streamDocument, +); +``` + +Wrap resolved field output in `EntityField` where applicable. + +## Section Runtime Pattern + +Use `VisibilityWrapper` and `ComponentErrorBoundary` for top-level section renders when applicable. + +Prefer theme-backed selectors over raw text controls for color choices. + +## Array Robustness Pattern + +For every array field, include `defaultItemProps`: + +```ts +items: YextField("Items", { + type: "array", + arrayFields: { + question: YextField("Question", { + type: "entityField", + filter: { types: ["type.string"] }, + }), + }, + defaultItemProps: { + question: { + field: "", + constantValue: { en: "Question", hasLocalizedValue: "true" }, + constantValueEnabled: true, + }, + }, +}); +``` + +Keep the same localized-object shape when overriding translatable fields through spread defaults: + +```ts +const override = { + ...base, + data: { + ...base.data, + heading: { + field: "", + constantValue: { en: "Reserve Now", hasLocalizedValue: "true" }, + constantValueEnabled: true, + }, + }, +}; +``` + +In render code, guard array entries before reading nested values to avoid crashes from malformed editor rows. + +Safe field-id pattern for list rows: + +```tsx +const questionField = item?.question; + + {resolveComponentData( + questionField ?? { + field: "", + constantValue: { en: "Question", hasLocalizedValue: "true" }, + constantValueEnabled: true, + }, + locale, + streamDocument, + )} +; +``` + +## Common Style Controls + +Include simple, reusable style controls by default: + +- Text alignment (`left`, `center`, `right`) +- Color token selector (`SITE_COLOR` or `BACKGROUND_COLOR`) +- Heading level (if section has heading) +- Section spacing/padding controls + +Keep style props shallow and avoid nested style systems for first-pass generated sections. + +If a section exposes `styles.textColor`, slot components rendered inside it should not hardcode conflicting color classes (for example `text-white`). Let color inherit or use slot style props wired to editor fields. diff --git a/skills/ve-html-template-generator/scripts/extract_page_sections.py b/skills/ve-html-template-generator/scripts/extract_page_sections.py new file mode 100755 index 000000000..b957dda60 --- /dev/null +++ b/skills/ve-html-template-generator/scripts/extract_page_sections.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +"""Extract candidate page sections from an HTML file. + +This is a best-effort helper for planning generated template components. +It favors semantic tags (
,
,
,
,