@wildo-ai/saas-website 1.1.8 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/esm/astro/assert-website-pages-have-sections.d.ts +28 -0
  2. package/dist/esm/astro/assert-website-pages-have-sections.d.ts.map +1 -0
  3. package/dist/esm/astro/assert-website-pages-have-sections.js +38 -0
  4. package/dist/esm/astro/assert-website-pages-have-sections.js.map +1 -0
  5. package/dist/esm/astro/label-pack-loader.d.ts +6 -0
  6. package/dist/esm/astro/label-pack-loader.d.ts.map +1 -1
  7. package/dist/esm/astro/label-pack-loader.js +10 -0
  8. package/dist/esm/astro/label-pack-loader.js.map +1 -1
  9. package/dist/esm/astro.d.ts +1 -0
  10. package/dist/esm/astro.d.ts.map +1 -1
  11. package/dist/esm/astro.js +1 -0
  12. package/dist/esm/astro.js.map +1 -1
  13. package/dist/esm/core/factories/define-website-section.d.ts +2 -0
  14. package/dist/esm/core/factories/define-website-section.d.ts.map +1 -1
  15. package/dist/esm/core/factories/define-website-section.js +2 -0
  16. package/dist/esm/core/factories/define-website-section.js.map +1 -1
  17. package/dist/esm/core/layouts/WebsitePageSections.d.ts +43 -0
  18. package/dist/esm/core/layouts/WebsitePageSections.d.ts.map +1 -0
  19. package/dist/esm/core/layouts/WebsitePageSections.js +13 -0
  20. package/dist/esm/core/layouts/WebsitePageSections.js.map +1 -0
  21. package/dist/esm/index.d.ts +1 -0
  22. package/dist/esm/index.d.ts.map +1 -1
  23. package/dist/esm/index.js +1 -0
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts +2 -0
  26. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -1
  27. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js +12 -3
  28. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js.map +1 -1
  29. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +9 -0
  30. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -1
  31. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +11 -0
  32. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -1
  33. package/dist/tsconfig.build.tsbuildinfo +1 -1
  34. package/package.json +5 -5
  35. package/src/astro/__tests__/assert-website-pages-have-sections.test.ts +75 -0
  36. package/src/astro/assert-website-pages-have-sections.ts +41 -0
  37. package/src/astro/label-pack-loader.ts +10 -0
  38. package/src/astro.ts +1 -0
  39. package/src/core/__tests__/WebsitePageSections.test.tsx +57 -0
  40. package/src/core/factories/define-website-section.ts +4 -0
  41. package/src/core/layouts/WebsitePageSections.tsx +68 -0
  42. package/src/index.ts +1 -0
  43. package/src/schemas/__tests__/page-manifest.test.ts +4 -7
  44. package/src/schemas/manifests/website-page-manifest.shared.schemas.ts +12 -3
  45. package/src/schemas/sections/website-section-definition.shared.schemas.ts +11 -0
@@ -1 +1 @@
1
- {"version":3,"file":"website-page-manifest.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/schemas/manifests/website-page-manifest.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType } from 'react';\nimport { z } from 'zod';\n\nimport { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';\nimport { WebsitePageRefSchema } from '../refs/page-ref.schemas';\nimport { WebsiteSectionRefSchema } from '../refs/section-ref.schemas';\n\n/**\n * @wildo_source:part:start saas.website.page-manifest.meta-label-keys facet:layer:shared facet:family:website\n *\n * `WebsitePageMetaLabelKeys` — the i18n keys the Astro adapter (Phase 5)\n * resolves at static-build time to populate `<title>` / `<meta>` /\n * Open Graph tags for the page.\n *\n * - `titleKey` — REQUIRED. Renders into `<title>` AND `og:title`. The\n * manifest commits to declaring this key in every locale's label\n * pack; the validator surfaces missing entries at build start.\n * - `descriptionKey` — REQUIRED. Renders into `<meta name=\"description\">`,\n * `og:description`, and `twitter:description`. Same per-locale\n * commitment.\n * - `ogImageUrl` — OPTIONAL. Static URL (typically a per-locale CDN\n * path) for `og:image` / `twitter:image`. Not a label key because\n * marketing teams overwhelmingly ship one localized asset URL per\n * page rather than authoring image URLs in label packs. When\n * per-locale images are needed, the consumer supplies a function\n * in their site-root config that resolves locale → URL (Phase 5).\n *\n * **Why these three only**: the JSON-LD / canonical / `hreflang` /\n * robots tags are NOT label-key-driven — they are derived\n * mechanically from the manifest (`routePath`), the root config\n * (`locales`, `defaultLocale`), and the section-level\n * `getStructuredData()` hooks. Mixing them into the meta-label-keys\n * shape would conflate human-authored copy with structural metadata.\n */\nconst _WebsitePageMetaLabelKeysSchema = z.object({\n titleKey: WebsiteLabelKeySchema,\n descriptionKey: WebsiteLabelKeySchema,\n ogImageUrl: z.url({ error: 'ogImageUrl must be a valid URL' }).optional(),\n});\n\nexport const WebsitePageMetaLabelKeysSchema: typeof _WebsitePageMetaLabelKeysSchema =\n _WebsitePageMetaLabelKeysSchema;\n\nexport type WebsitePageMetaLabelKeys = z.infer<typeof _WebsitePageMetaLabelKeysSchema>;\n/** @wildo_source:part:end saas.website.page-manifest.meta-label-keys */\n\n/**\n * @wildo_source:part:start saas.website.page-manifest.route-path facet:layer:shared facet:family:website\n *\n * `WebsitePageRoutePath` — the URL path the page mounts at (locale\n * prefix stripped).\n *\n * **Format**:\n * - MUST start with `/`\n * - MAY be the root (`'/'`) for the landing page\n * - segments are lowercase kebab-case\n * - parameter segments use Astro's `[param]` convention\n * (e.g. `'/blog/[slug]'`, `'/case-studies/[slug]'`)\n *\n * The Astro adapter (Phase 5) prepends the locale prefix\n * (`/en`, `/fr`, …) when generating routes. The manifest stores the\n * locale-neutral path because the same `pageComponent` renders for\n * every locale.\n *\n * **Brand**: prevents accidental cross-talk with absolute URLs\n * (`https://…`), with locale-prefixed paths (`/en/pricing`), and with\n * arbitrary string props in the manifest factory.\n */\nconst _WebsitePageRoutePathSchema = z\n .string()\n .min(1, { error: 'routePath must not be empty' })\n .max(256, { error: 'routePath must be at most 256 characters' })\n .regex(\n /^\\/(?:[a-z0-9][a-z0-9-]*(?:\\/[a-z0-9-]+|\\/\\[[a-z][a-zA-Z0-9]*\\])*)?\\/?$/,\n {\n error:\n 'routePath must start with `/`, use lowercase kebab-case segments, and may include Astro `[param]` segments (e.g. \"/\", \"/pricing\", \"/blog/[slug]\")',\n },\n )\n .brand<'WebsitePageRoutePath'>();\n\nexport const WebsitePageRoutePathSchema: typeof _WebsitePageRoutePathSchema =\n _WebsitePageRoutePathSchema;\n\nexport type WebsitePageRoutePath = z.infer<typeof _WebsitePageRoutePathSchema>;\n/** @wildo_source:part:end saas.website.page-manifest.route-path */\n\n/**\n * @wildo_source:part:start saas.website.page-manifest.schema facet:layer:shared facet:family:website\n *\n * `WebsitePageManifest` — the registration-time descriptor for one\n * marketing-site page.\n *\n * **Authoring path**: each page-impl module exports both a\n * `<PageName>Page` React component AND a `<PAGE_NAME>_PAGE_MANIFEST`\n * literal. The manifest is collected into `WebsiteRootConfig.pageManifests[]`;\n * the Astro page file (`src/pages/<page>.astro`) imports the manifest\n * and hands it to `<WebsitePageBridge manifest={…}>` (Phase 5).\n *\n * **Field-by-field rationale** (semantic-weight discipline):\n *\n * - `ref` — the page's structural identity. Used for label-key\n * derivation (`website.<ref>.<sectionRef>.<slot>`), for\n * cross-page navigation lookup, and as the manifest registry key.\n * - `routePath` — the locale-neutral URL path. Decoupled from `ref`\n * because the same page (e.g. `ref: 'pricing'`) may mount at a\n * localized path (`/tarifs` in French) — the locale-aware path\n * resolution is a Phase 5 concern; this field is the canonical\n * English-language path.\n * - `pageComponent` — the React component the bridge renders inside\n * `<WebsitePageContext value={{ pageRef: ref }}>`. Type relaxed to\n * `unknown` at the schema layer for the same reasons as\n * `WebsiteSectionDefinition.Component`; the manifest factory\n * (`defineWebsitePageManifest`, Phase 2) preserves precise typing\n * at the consumer call site.\n * - `sectionRefs` — the contractual list of section refs the page\n * declares. Used by the label-pack validator to cross-reference\n * `expectedLabelKeys` collected from each section against the\n * page's commitment. **Order matters** for the validator's\n * diagnostic output (sections are reported in declaration order)\n * but does NOT control render order — render order is whatever the\n * `pageComponent` JSX lays out.\n * - `metaLabelKeys` — see `WebsitePageMetaLabelKeysSchema`.\n * - `hydration` — opt-in flag. `'lazy'` (default) corresponds to\n * `client:idle` in the Astro adapter (deferred hydration); `'eager'`\n * corresponds to `client:load` (immediate hydration). The page\n * author flips this to `'eager'` only when the page hosts\n * above-the-fold interactive sections (e.g. a contact form).\n *\n * **Important — informational, NOT auto-wired**: Astro's\n * `client:*` directives MUST resolve to a literal token at compile\n * time (the Astro compiler reads the directive from the JSX source\n * to decide which hydration helper to bundle); a runtime-computed\n * value will not work. The framework therefore CANNOT translate\n * this field into the directive on the consumer's behalf — the\n * manifest field is the single source of truth a consumer's\n * per-page `.astro` template should USE to pick the matching\n * `client:idle` / `client:load` directive by hand. Keeping the two\n * in lock-step is a discipline obligation; the audit CLI (Phase 7)\n * will surface a drift warning when a page's hydration field does\n * not match the directive in its `.astro` file.\n *\n * **Refinements**:\n * - `sectionRefs` MUST contain unique values. Duplicates indicate\n * either a copy-paste bug (two sections pointing at the same\n * label-key root) or an intent to share a label-key root that\n * the framework rejects to keep the per-page validation\n * unambiguous (consumers who need a shared label-key root\n * across two distinct sections should pull the shared content\n * to the page level via `WebsiteRootConfig`-level globals,\n * Phase 2.5).\n */\nexport enum WebsitePageHydrationStrategy {\n /** `client:idle` in the Astro adapter (deferred hydration). */\n LAZY = 'lazy',\n /** `client:load` in the Astro adapter (immediate hydration). */\n EAGER = 'eager',\n}\n\nexport const WebsitePageHydrationStrategySchema: z.ZodEnum<typeof WebsitePageHydrationStrategy> =\n z.enum(WebsitePageHydrationStrategy);\n\n/**\n * @wildo_source:part:start saas.website.page-manifest.seo facet:layer:shared facet:family:website\n *\n * `WebsitePageSeoConfig` — per-page SEO/AEO/GEO knobs.\n *\n * The marketing-site default is to be FULLY discoverable by every\n * crawler (search engines AND AI training crawlers). This block holds\n * the per-page opt-out levers that flip a page out of that default.\n *\n * **Field rationale**:\n *\n * - `noaiOptOut?` — when `true`, the page emits\n * `<meta name=\"robots\" content=\"noai, noimageai\">` AND a sibling\n * `_headers` file entry signalling `X-Robots-Tag: noai, noimageai`\n * for crawlers that don't honor in-document meta. ONE knob drives\n * BOTH channels (`headers-renderer.ts` reads the same flag) — the\n * framework owns the dual emission so consumers can't accidentally\n * set one and forget the other.\n *\n * **Why default-IN to AI training (D-AG3)**: the marketing-site\n * default is `false` (consent to AI training). Marketing pages\n * benefit from AI-engine citation; opting out is a per-page\n * editorial decision (typically pricing pages — to avoid stale\n * price training; or legal/internal pages — to avoid free-text\n * reproduction of contractual language). Per-blog-post opt-out\n * would mirror this knob on a sibling blog-post manifest schema\n * if that need materializes.\n */\nconst _WebsitePageSeoConfigSchema = z.object({\n noaiOptOut: z.boolean().optional(),\n});\n\nexport const WebsitePageSeoConfigSchema: typeof _WebsitePageSeoConfigSchema =\n _WebsitePageSeoConfigSchema;\n\nexport type WebsitePageSeoConfig = z.infer<typeof _WebsitePageSeoConfigSchema>;\n/** @wildo_source:part:end saas.website.page-manifest.seo */\n\nconst _WebsitePageManifestSchema = z\n .object({\n ref: WebsitePageRefSchema,\n routePath: _WebsitePageRoutePathSchema,\n pageComponent: z.unknown(),\n sectionRefs: z.array(WebsiteSectionRefSchema).min(1, {\n error: 'A page manifest must declare at least one sectionRef',\n }),\n metaLabelKeys: _WebsitePageMetaLabelKeysSchema,\n hydration: WebsitePageHydrationStrategySchema.optional(),\n seo: _WebsitePageSeoConfigSchema.optional(),\n })\n .superRefine((manifest, ctx) => {\n const seen = new Set<string>();\n for (const [index, sectionRef] of manifest.sectionRefs.entries()) {\n const key = sectionRef as unknown as string;\n if (seen.has(key)) {\n ctx.addIssue({\n code: 'custom',\n path: ['sectionRefs', index],\n message: `Duplicate sectionRef \"${key}\" in page manifest \"${manifest.ref as unknown as string}\"`,\n });\n }\n seen.add(key);\n }\n\n const expectedPagePrefix = `website.${manifest.ref as unknown as string}.`;\n for (const [field, key] of [\n ['titleKey', manifest.metaLabelKeys.titleKey] as const,\n ['descriptionKey', manifest.metaLabelKeys.descriptionKey] as const,\n ]) {\n if (!(key as unknown as string).startsWith(expectedPagePrefix)) {\n ctx.addIssue({\n code: 'custom',\n path: ['metaLabelKeys', field],\n message: `metaLabelKeys.${field} (\"${key as unknown as string}\") must be rooted under the page ref \"${manifest.ref as unknown as string}\" (expected prefix \"${expectedPagePrefix}\")`,\n });\n }\n }\n });\n\nexport const WebsitePageManifestSchema: typeof _WebsitePageManifestSchema =\n _WebsitePageManifestSchema;\n\n/**\n * Runtime shape of `WebsitePageManifest`.\n *\n * **Why `ComponentType<any>` and NOT `ComponentType<unknown>`** —\n * same variance reasoning as `WebsiteSectionDefinition.Component`:\n * `ComponentType<TProps>` is CONTRAVARIANT in `TProps`, so a\n * narrower `TypedWebsitePageManifest<TProps>` (returned by\n * `defineWebsitePageManifest<TProps>`) was not assignable to a\n * `WebsitePageManifest` typed with `ComponentType<unknown>`. That\n * blocked every consumer call-site that passed a typed manifest to\n * helpers / registries / validators expecting the bare type\n * (root config's `pageManifests[]`, the Astro adapter's\n * `loadWebsitePageRuntime`, and the head-renderer). Using `any`\n * matches the React-canonical pattern for heterogeneous component\n * containers and reflects the runtime contract: the framework only\n * RENDERS these components — it never constructs ad-hoc props for\n * them; the consumer's JSX call site supplies the precise props.\n */\nexport interface WebsitePageManifest extends z.infer<typeof _WebsitePageManifestSchema> {\n pageComponent: ComponentType<any>;\n}\n/** @wildo_source:part:end saas.website.page-manifest.schema */\n"]}
1
+ {"version":3,"file":"website-page-manifest.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/schemas/manifests/website-page-manifest.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType } from 'react';\nimport { z } from 'zod';\n\nimport { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';\nimport { WebsitePageRefSchema } from '../refs/page-ref.schemas';\nimport { WebsiteSectionRefSchema } from '../refs/section-ref.schemas';\n\n/**\n * @wildo_source:part:start saas.website.page-manifest.meta-label-keys facet:layer:shared facet:family:website\n *\n * `WebsitePageMetaLabelKeys` — the i18n keys the Astro adapter (Phase 5)\n * resolves at static-build time to populate `<title>` / `<meta>` /\n * Open Graph tags for the page.\n *\n * - `titleKey` — REQUIRED. Renders into `<title>` AND `og:title`. The\n * manifest commits to declaring this key in every locale's label\n * pack; the validator surfaces missing entries at build start.\n * - `descriptionKey` — REQUIRED. Renders into `<meta name=\"description\">`,\n * `og:description`, and `twitter:description`. Same per-locale\n * commitment.\n * - `ogImageUrl` — OPTIONAL. Static URL (typically a per-locale CDN\n * path) for `og:image` / `twitter:image`. Not a label key because\n * marketing teams overwhelmingly ship one localized asset URL per\n * page rather than authoring image URLs in label packs. When\n * per-locale images are needed, the consumer supplies a function\n * in their site-root config that resolves locale → URL (Phase 5).\n *\n * **Why these three only**: the JSON-LD / canonical / `hreflang` /\n * robots tags are NOT label-key-driven — they are derived\n * mechanically from the manifest (`routePath`), the root config\n * (`locales`, `defaultLocale`), and the section-level\n * `getStructuredData()` hooks. Mixing them into the meta-label-keys\n * shape would conflate human-authored copy with structural metadata.\n */\nconst _WebsitePageMetaLabelKeysSchema = z.object({\n titleKey: WebsiteLabelKeySchema,\n descriptionKey: WebsiteLabelKeySchema,\n ogImageUrl: z.url({ error: 'ogImageUrl must be a valid URL' }).optional(),\n});\n\nexport const WebsitePageMetaLabelKeysSchema: typeof _WebsitePageMetaLabelKeysSchema =\n _WebsitePageMetaLabelKeysSchema;\n\nexport type WebsitePageMetaLabelKeys = z.infer<typeof _WebsitePageMetaLabelKeysSchema>;\n/** @wildo_source:part:end saas.website.page-manifest.meta-label-keys */\n\n/**\n * @wildo_source:part:start saas.website.page-manifest.route-path facet:layer:shared facet:family:website\n *\n * `WebsitePageRoutePath` — the URL path the page mounts at (locale\n * prefix stripped).\n *\n * **Format**:\n * - MUST start with `/`\n * - MAY be the root (`'/'`) for the landing page\n * - segments are lowercase kebab-case\n * - parameter segments use Astro's `[param]` convention\n * (e.g. `'/blog/[slug]'`, `'/case-studies/[slug]'`)\n *\n * The Astro adapter (Phase 5) prepends the locale prefix\n * (`/en`, `/fr`, …) when generating routes. The manifest stores the\n * locale-neutral path because the same `pageComponent` renders for\n * every locale.\n *\n * **Brand**: prevents accidental cross-talk with absolute URLs\n * (`https://…`), with locale-prefixed paths (`/en/pricing`), and with\n * arbitrary string props in the manifest factory.\n */\nconst _WebsitePageRoutePathSchema = z\n .string()\n .min(1, { error: 'routePath must not be empty' })\n .max(256, { error: 'routePath must be at most 256 characters' })\n .regex(\n /^\\/(?:[a-z0-9][a-z0-9-]*(?:\\/[a-z0-9-]+|\\/\\[[a-z][a-zA-Z0-9]*\\])*)?\\/?$/,\n {\n error:\n 'routePath must start with `/`, use lowercase kebab-case segments, and may include Astro `[param]` segments (e.g. \"/\", \"/pricing\", \"/blog/[slug]\")',\n },\n )\n .brand<'WebsitePageRoutePath'>();\n\nexport const WebsitePageRoutePathSchema: typeof _WebsitePageRoutePathSchema =\n _WebsitePageRoutePathSchema;\n\nexport type WebsitePageRoutePath = z.infer<typeof _WebsitePageRoutePathSchema>;\n/** @wildo_source:part:end saas.website.page-manifest.route-path */\n\n/**\n * @wildo_source:part:start saas.website.page-manifest.schema facet:layer:shared facet:family:website\n *\n * `WebsitePageManifest` — the registration-time descriptor for one\n * marketing-site page.\n *\n * **Authoring path**: each page-impl module exports both a\n * `<PageName>Page` React component AND a `<PAGE_NAME>_PAGE_MANIFEST`\n * literal. The manifest is collected into `WebsiteRootConfig.pageManifests[]`;\n * the Astro page file (`src/pages/<page>.astro`) imports the manifest\n * and hands it to `<WebsitePageBridge manifest={…}>` (Phase 5).\n *\n * **Field-by-field rationale** (semantic-weight discipline):\n *\n * - `ref` — the page's structural identity. Used for label-key\n * derivation (`website.<ref>.<sectionRef>.<slot>`), for\n * cross-page navigation lookup, and as the manifest registry key.\n * - `routePath` — the locale-neutral URL path. Decoupled from `ref`\n * because the same page (e.g. `ref: 'pricing'`) may mount at a\n * localized path (`/tarifs` in French) — the locale-aware path\n * resolution is a Phase 5 concern; this field is the canonical\n * English-language path.\n * - `pageComponent` — the React component the bridge renders inside\n * `<WebsitePageContext value={{ pageRef: ref }}>`. Type relaxed to\n * `unknown` at the schema layer for the same reasons as\n * `WebsiteSectionDefinition.Component`; the manifest factory\n * (`defineWebsitePageManifest`, Phase 2) preserves precise typing\n * at the consumer call site.\n * - `sectionRefs` — the contractual list of section refs the page\n * declares. Used by the label-pack validator to cross-reference\n * `expectedLabelKeys` collected from each section against the\n * page's commitment. **Order matters** for the validator's\n * diagnostic output (sections are reported in declaration order)\n * but does NOT control render order — render order is whatever the\n * `pageComponent` JSX lays out.\n * - `metaLabelKeys` — see `WebsitePageMetaLabelKeysSchema`.\n * - `hydration` — opt-in flag. `'lazy'` (default) corresponds to\n * `client:idle` in the Astro adapter (deferred hydration); `'eager'`\n * corresponds to `client:load` (immediate hydration). The page\n * author flips this to `'eager'` only when the page hosts\n * above-the-fold interactive sections (e.g. a contact form).\n *\n * **Important — informational, NOT auto-wired**: Astro's\n * `client:*` directives MUST resolve to a literal token at compile\n * time (the Astro compiler reads the directive from the JSX source\n * to decide which hydration helper to bundle); a runtime-computed\n * value will not work. The framework therefore CANNOT translate\n * this field into the directive on the consumer's behalf — the\n * manifest field is the single source of truth a consumer's\n * per-page `.astro` template should USE to pick the matching\n * `client:idle` / `client:load` directive by hand. Keeping the two\n * in lock-step is a discipline obligation; the audit CLI (Phase 7)\n * will surface a drift warning when a page's hydration field does\n * not match the directive in its `.astro` file.\n *\n * **Refinements**:\n * - `sectionRefs` MAY be empty — a page whose sections are still being authored. It may be\n * served by a dev server and may not be built: see `assertWebsitePagesHaveSections`.\n * - `sectionRefs` MUST contain unique values. Duplicates indicate\n * either a copy-paste bug (two sections pointing at the same\n * label-key root) or an intent to share a label-key root that\n * the framework rejects to keep the per-page validation\n * unambiguous (consumers who need a shared label-key root\n * across two distinct sections should pull the shared content\n * to the page level via `WebsiteRootConfig`-level globals,\n * Phase 2.5).\n */\nexport enum WebsitePageHydrationStrategy {\n /** `client:idle` in the Astro adapter (deferred hydration). */\n LAZY = 'lazy',\n /** `client:load` in the Astro adapter (immediate hydration). */\n EAGER = 'eager',\n}\n\nexport const WebsitePageHydrationStrategySchema: z.ZodEnum<typeof WebsitePageHydrationStrategy> =\n z.enum(WebsitePageHydrationStrategy);\n\n/**\n * @wildo_source:part:start saas.website.page-manifest.seo facet:layer:shared facet:family:website\n *\n * `WebsitePageSeoConfig` — per-page SEO/AEO/GEO knobs.\n *\n * The marketing-site default is to be FULLY discoverable by every\n * crawler (search engines AND AI training crawlers). This block holds\n * the per-page opt-out levers that flip a page out of that default.\n *\n * **Field rationale**:\n *\n * - `noaiOptOut?` — when `true`, the page emits\n * `<meta name=\"robots\" content=\"noai, noimageai\">` AND a sibling\n * `_headers` file entry signalling `X-Robots-Tag: noai, noimageai`\n * for crawlers that don't honor in-document meta. ONE knob drives\n * BOTH channels (`headers-renderer.ts` reads the same flag) — the\n * framework owns the dual emission so consumers can't accidentally\n * set one and forget the other.\n *\n * **Why default-IN to AI training (D-AG3)**: the marketing-site\n * default is `false` (consent to AI training). Marketing pages\n * benefit from AI-engine citation; opting out is a per-page\n * editorial decision (typically pricing pages — to avoid stale\n * price training; or legal/internal pages — to avoid free-text\n * reproduction of contractual language). Per-blog-post opt-out\n * would mirror this knob on a sibling blog-post manifest schema\n * if that need materializes.\n */\nconst _WebsitePageSeoConfigSchema = z.object({\n noaiOptOut: z.boolean().optional(),\n});\n\nexport const WebsitePageSeoConfigSchema: typeof _WebsitePageSeoConfigSchema =\n _WebsitePageSeoConfigSchema;\n\nexport type WebsitePageSeoConfig = z.infer<typeof _WebsitePageSeoConfigSchema>;\n/** @wildo_source:part:end saas.website.page-manifest.seo */\n\nconst _WebsitePageManifestSchema = z\n .object({\n ref: WebsitePageRefSchema,\n routePath: _WebsitePageRoutePathSchema,\n pageComponent: z.unknown(),\n /*\n * EMPTY is a valid manifest (#2205): a page whose sections are not authored YET. The build lane\n * creates that state on purpose (the page task writes the empty list, then each section task\n * appends itself), and refusing it here made `astro dev` crash at module load until every page\n * of the site had a section. An empty page is unfinished, not false, so it is representable and\n * a dev server renders it (`WebsitePageSections` shows the pending state). What is refused is\n * BUILDING one: `assertWebsitePagesHaveSections`, run from the build-start hook, because a\n * published page with nothing on it is a statement to a real reader.\n */\n sectionRefs: z.array(WebsiteSectionRefSchema),\n metaLabelKeys: _WebsitePageMetaLabelKeysSchema,\n hydration: WebsitePageHydrationStrategySchema.optional(),\n seo: _WebsitePageSeoConfigSchema.optional(),\n })\n .superRefine((manifest, ctx) => {\n const seen = new Set<string>();\n for (const [index, sectionRef] of manifest.sectionRefs.entries()) {\n const key = sectionRef as unknown as string;\n if (seen.has(key)) {\n ctx.addIssue({\n code: 'custom',\n path: ['sectionRefs', index],\n message: `Duplicate sectionRef \"${key}\" in page manifest \"${manifest.ref as unknown as string}\"`,\n });\n }\n seen.add(key);\n }\n\n const expectedPagePrefix = `website.${manifest.ref as unknown as string}.`;\n for (const [field, key] of [\n ['titleKey', manifest.metaLabelKeys.titleKey] as const,\n ['descriptionKey', manifest.metaLabelKeys.descriptionKey] as const,\n ]) {\n if (!(key as unknown as string).startsWith(expectedPagePrefix)) {\n ctx.addIssue({\n code: 'custom',\n path: ['metaLabelKeys', field],\n message: `metaLabelKeys.${field} (\"${key as unknown as string}\") must be rooted under the page ref \"${manifest.ref as unknown as string}\" (expected prefix \"${expectedPagePrefix}\")`,\n });\n }\n }\n });\n\nexport const WebsitePageManifestSchema: typeof _WebsitePageManifestSchema =\n _WebsitePageManifestSchema;\n\n/**\n * Runtime shape of `WebsitePageManifest`.\n *\n * **Why `ComponentType<any>` and NOT `ComponentType<unknown>`** —\n * same variance reasoning as `WebsiteSectionDefinition.Component`:\n * `ComponentType<TProps>` is CONTRAVARIANT in `TProps`, so a\n * narrower `TypedWebsitePageManifest<TProps>` (returned by\n * `defineWebsitePageManifest<TProps>`) was not assignable to a\n * `WebsitePageManifest` typed with `ComponentType<unknown>`. That\n * blocked every consumer call-site that passed a typed manifest to\n * helpers / registries / validators expecting the bare type\n * (root config's `pageManifests[]`, the Astro adapter's\n * `loadWebsitePageRuntime`, and the head-renderer). Using `any`\n * matches the React-canonical pattern for heterogeneous component\n * containers and reflects the runtime contract: the framework only\n * RENDERS these components — it never constructs ad-hoc props for\n * them; the consumer's JSX call site supplies the precise props.\n */\nexport interface WebsitePageManifest extends z.infer<typeof _WebsitePageManifestSchema> {\n pageComponent: ComponentType<any>;\n}\n/** @wildo_source:part:end saas.website.page-manifest.schema */\n"]}
@@ -41,6 +41,14 @@ import type { WebsiteStructuredDataReturn } from '../structured-data/website-str
41
41
  * and is caught by nothing until a reviewer reads the page. The
42
42
  * build and `wildo website audit` check the pack against the
43
43
  * DECLARED keys, so declare every slot the component reads.
44
+ * - `anchorId?` — the section's DOM id, so a link (`<a href="#tiers">`)
45
+ * can target it. Belongs to the DEFINITION because a page renders its
46
+ * sections list through `WebsitePageSections`, which places every
47
+ * section itself: an anchor a page author would otherwise write on a
48
+ * hand-placed `<WebsiteSection id=…>` has nowhere else to live. A
49
+ * valid HTML id that is also URL-fragment-safe: a letter, then
50
+ * letters, digits, `-` or `_`. Uniqueness on a page is the author's
51
+ * to keep, like the id attribute it becomes.
44
52
  * - `Component` — the React component that renders the section. Typed
45
53
  * as `ComponentType<unknown>` here because schema-layer cannot lock
46
54
  * the component's prop shape (props vary per section). The factory
@@ -67,6 +75,7 @@ declare const _WebsiteSectionDefinitionSchema: z.ZodObject<{
67
75
  sectionRef: z.core.$ZodBranded<z.ZodString, "WebsiteSectionRef", "out">;
68
76
  category: z.ZodEnum<typeof import("./website-section-category.shared").WebsiteSectionCategory>;
69
77
  expectedLabelKeys: z.ZodReadonly<z.ZodArray<z.core.$ZodBranded<z.ZodString, "WebsiteLabelKey", "out">>>;
78
+ anchorId: z.ZodOptional<z.ZodString>;
70
79
  Component: z.ZodUnknown;
71
80
  getStructuredData: z.ZodOptional<z.ZodUnknown>;
72
81
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"website-section-definition.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/sections/website-section-definition.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2DAA2D,CAAC;AAG7G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,QAAA,MAAM,+BAA+B;;;;;;iBAMnC,CAAC;AAEH,eAAO,MAAM,8BAA8B,EAAE,OAAO,+BACnB,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,wBAAyB,SAAQ,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC;IAC/F,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;IAC9B;;;;;;;;;;;;;;;;;OAiBG;IACH,iBAAiB,CAAC,EAAE,MAAM,2BAA2B,CAAC;CACvD;;AACD,qEAAqE"}
1
+ {"version":3,"file":"website-section-definition.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/sections/website-section-definition.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2DAA2D,CAAC;AAG7G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,QAAA,MAAM,+BAA+B;;;;;;;iBASnC,CAAC;AAEH,eAAO,MAAM,8BAA8B,EAAE,OAAO,+BACnB,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,wBAAyB,SAAQ,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC;IAC/F,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;IAC9B;;;;;;;;;;;;;;;;;OAiBG;IACH,iBAAiB,CAAC,EAAE,MAAM,2BAA2B,CAAC;CACvD;;AACD,qEAAqE"}
@@ -42,6 +42,14 @@ import { WebsiteSectionCategorySchema } from './website-section-category.shared.
42
42
  * and is caught by nothing until a reviewer reads the page. The
43
43
  * build and `wildo website audit` check the pack against the
44
44
  * DECLARED keys, so declare every slot the component reads.
45
+ * - `anchorId?` — the section's DOM id, so a link (`<a href="#tiers">`)
46
+ * can target it. Belongs to the DEFINITION because a page renders its
47
+ * sections list through `WebsitePageSections`, which places every
48
+ * section itself: an anchor a page author would otherwise write on a
49
+ * hand-placed `<WebsiteSection id=…>` has nowhere else to live. A
50
+ * valid HTML id that is also URL-fragment-safe: a letter, then
51
+ * letters, digits, `-` or `_`. Uniqueness on a page is the author's
52
+ * to keep, like the id attribute it becomes.
45
53
  * - `Component` — the React component that renders the section. Typed
46
54
  * as `ComponentType<unknown>` here because schema-layer cannot lock
47
55
  * the component's prop shape (props vary per section). The factory
@@ -68,6 +76,9 @@ const _WebsiteSectionDefinitionSchema = z.object({
68
76
  sectionRef: WebsiteSectionRefSchema,
69
77
  category: WebsiteSectionCategorySchema,
70
78
  expectedLabelKeys: z.array(WebsiteLabelKeySchema).readonly(),
79
+ anchorId: z.string().regex(/^[A-Za-z][A-Za-z0-9_-]*$/, {
80
+ error: 'A section anchorId must start with a letter and contain only letters, digits, "-" or "_"',
81
+ }).optional(),
71
82
  Component: z.unknown(),
72
83
  getStructuredData: z.unknown().optional(),
73
84
  });
@@ -1 +1 @@
1
- {"version":3,"file":"website-section-definition.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/schemas/sections/website-section-definition.shared.schemas.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,MAAM,+BAA+B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,UAAU,EAAE,uBAAuB;IACnC,QAAQ,EAAE,4BAA4B;IACtC,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;IAC5D,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;IACtB,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,8BAA8B,GACzC,+BAA+B,CAAC;AA+ClC,qEAAqE","sourcesContent":["import type { ComponentType } from 'react';\nimport { z } from 'zod';\n\nimport { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';\nimport { WebsiteSectionRefSchema } from '../refs/section-ref.schemas';\nimport type { WebsiteStructuredDataReturn } from '../structured-data/website-structured-data.shared.schemas';\nimport { WebsiteSectionCategorySchema } from './website-section-category.shared';\n\n/**\n * @wildo_source:part:start saas.website.sections.definition-schema facet:layer:shared facet:family:website\n *\n * `WebsiteSectionDefinition` — the runtime contract returned by the\n * `defineWebsiteSection({ … })` factory (authored as a separate\n * `core/define-website-section.tsx` module in Phase 2 because it carries\n * React types in its inputs). The shape is declared in `schemas/`\n * because it is the **observable contract** the page-manifest\n * registration, the label-pack validator, `renderPageStructuredData` and the\n * structured-data audit depend on.\n *\n * **Shape rationale** (each field carries semantic weight — see\n * `design-philosophy.mdc`):\n *\n * - `sectionRef` — the section's identity inside its hosting page.\n * Uniqueness is enforced **per page** at manifest registration time\n * (a section can be reused with the same `sectionRef` across pages\n * when its label-key root is intentionally shared).\n * - `category` — see `WebsiteSectionCategory` JSDoc; tags the section\n * for skill-mapping and analytics labelling. It does NOT select the\n * section's structured data.\n * - `expectedLabelKeys` — the **fully-composed** label keys the section\n * commits to consuming. The plan originally proposed leaf-relative\n * keys (`'headline'`) resolved at runtime against the\n * page+section context, but storing the fully-composed key on the\n * definition has three concrete benefits:\n * 1. The label-pack validator (`validateLabelPackAgainstManifest`)\n * runs as a pure function with no need to walk the JSX tree at\n * build time to discover the leaf-to-key resolution.\n * 2. The contract is auditable from outside the React render\n * graph — `wildo website audit` can list a page's required keys\n * without booting React.\n * 3. The leaf-relative ergonomics (`useWebsiteLabel('headline')`)\n * survive — the hook composes the leaf with the page+section\n * context and looks the result up in the active pack. It does\n * NOT check the key against `expectedLabelKeys`: a leaf the\n * section never declared renders its key (with a console warning)\n * and is caught by nothing until a reviewer reads the page. The\n * build and `wildo website audit` check the pack against the\n * DECLARED keys, so declare every slot the component reads.\n * - `Component` — the React component that renders the section. Typed\n * as `ComponentType<unknown>` here because schema-layer cannot lock\n * the component's prop shape (props vary per section). The factory\n * function (`defineWebsiteSection<TProps>(…)`) preserves the precise\n * prop type at the consumer call site via generics; this schema only\n * guards the runtime shape of \"is callable as a React component\".\n * - `getStructuredData?` — optional pure function the SEO/AEO/GEO\n * builder library invokes at static-build time to materialize JSON-LD\n * for the section. The return type is intentionally `unknown` here;\n * nothing narrows it by `category` — the section returns whatever\n * vocabulary it emits, and `wildo website audit` reconciles that\n * against the application's Schema.org catalog.\n *\n * **Why not `z.custom<ComponentType>(…)`**: a `z.custom` predicate\n * cannot meaningfully validate \"is a React component\" beyond\n * `typeof === 'function'`, and the false-positive cost (any function\n * passes) is too high for the value (none — the consumer either\n * imports a real component or fails at JSX render time with a clearer\n * error). Using `z.unknown()` + a TS-side type cast keeps the runtime\n * cost zero and the type-safety contract entirely on the TS side\n * (where it actually has teeth via the factory's generic).\n */\nconst _WebsiteSectionDefinitionSchema = z.object({\n sectionRef: WebsiteSectionRefSchema,\n category: WebsiteSectionCategorySchema,\n expectedLabelKeys: z.array(WebsiteLabelKeySchema).readonly(),\n Component: z.unknown(),\n getStructuredData: z.unknown().optional(),\n});\n\nexport const WebsiteSectionDefinitionSchema: typeof _WebsiteSectionDefinitionSchema =\n _WebsiteSectionDefinitionSchema;\n\n/**\n * Runtime shape of `WebsiteSectionDefinition`. Note: `Component` and\n * `getStructuredData` are typed precisely on the factory return type\n * (`defineWebsiteSection<TProps>` in Phase 2); the schema-inferred\n * type is the lowest common denominator used at the persistence /\n * validation boundary only.\n *\n * **Why `ComponentType<any>` and NOT `ComponentType<unknown>`**:\n * React component props are CONTRAVARIANT in `ComponentType<T>` —\n * a narrower `ComponentType<{ foo: string }>` is NOT assignable to\n * `ComponentType<unknown>` (because a caller could pass an `unknown`\n * the narrower component cannot accept). This made every consumer\n * who built sections via `defineWebsiteSection<TProps>(…)` unable\n * to feed the resulting `TypedWebsiteSectionDefinition<TProps>` to\n * helpers typed against the bare `WebsiteSectionDefinition` —\n * collectors, validators, and registries. Using `any` here is the\n * exact escape hatch the React typings themselves use for\n * heterogeneous component containers (`React.forwardRef`,\n * `React.lazy`'s internal component slot, etc.) and matches the\n * runtime contract: the framework NEVER instantiates these\n * components directly with arbitrary props — the consumer's JSX\n * supplies the right props at the call site.\n */\nexport interface WebsiteSectionDefinition extends z.infer<typeof _WebsiteSectionDefinitionSchema> {\n Component: ComponentType<any>;\n /**\n * Optional pure function the SEO/AEO/GEO renderer invokes at\n * static-build time to materialize JSON-LD for the section.\n *\n * **Why typed as `WebsiteStructuredDataReturn` (not `unknown`)** —\n * a typed return shape gives section authors autocomplete on\n * `@type` / `@id` / `@context` (the framework-owned fields) while\n * keeping all other fields free-form (Schema.org has hundreds of\n * optional fields per entity; locking them in TS would be both\n * impractical and brittle as Schema.org evolves). The companion\n * catalog reconciliation surface catches missing-required and\n * deprecated-field-used at the SEMANTIC layer (warning-only per\n * D-AG4), so the runtime type stays loose.\n *\n * Returns `null`, `undefined`, or `[]` when the section has no\n * structured-data contribution for the current state — the\n * renderer short-circuits all three.\n */\n getStructuredData?: () => WebsiteStructuredDataReturn;\n}\n/** @wildo_source:part:end saas.website.sections.definition-schema */\n"]}
1
+ {"version":3,"file":"website-section-definition.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/schemas/sections/website-section-definition.shared.schemas.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,MAAM,+BAA+B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,UAAU,EAAE,uBAAuB;IACnC,QAAQ,EAAE,4BAA4B;IACtC,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;IAC5D,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,0BAA0B,EAAE;QACrD,KAAK,EAAE,0FAA0F;KAClG,CAAC,CAAC,QAAQ,EAAE;IACb,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;IACtB,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,8BAA8B,GACzC,+BAA+B,CAAC;AA+ClC,qEAAqE","sourcesContent":["import type { ComponentType } from 'react';\nimport { z } from 'zod';\n\nimport { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';\nimport { WebsiteSectionRefSchema } from '../refs/section-ref.schemas';\nimport type { WebsiteStructuredDataReturn } from '../structured-data/website-structured-data.shared.schemas';\nimport { WebsiteSectionCategorySchema } from './website-section-category.shared';\n\n/**\n * @wildo_source:part:start saas.website.sections.definition-schema facet:layer:shared facet:family:website\n *\n * `WebsiteSectionDefinition` — the runtime contract returned by the\n * `defineWebsiteSection({ … })` factory (authored as a separate\n * `core/define-website-section.tsx` module in Phase 2 because it carries\n * React types in its inputs). The shape is declared in `schemas/`\n * because it is the **observable contract** the page-manifest\n * registration, the label-pack validator, `renderPageStructuredData` and the\n * structured-data audit depend on.\n *\n * **Shape rationale** (each field carries semantic weight — see\n * `design-philosophy.mdc`):\n *\n * - `sectionRef` — the section's identity inside its hosting page.\n * Uniqueness is enforced **per page** at manifest registration time\n * (a section can be reused with the same `sectionRef` across pages\n * when its label-key root is intentionally shared).\n * - `category` — see `WebsiteSectionCategory` JSDoc; tags the section\n * for skill-mapping and analytics labelling. It does NOT select the\n * section's structured data.\n * - `expectedLabelKeys` — the **fully-composed** label keys the section\n * commits to consuming. The plan originally proposed leaf-relative\n * keys (`'headline'`) resolved at runtime against the\n * page+section context, but storing the fully-composed key on the\n * definition has three concrete benefits:\n * 1. The label-pack validator (`validateLabelPackAgainstManifest`)\n * runs as a pure function with no need to walk the JSX tree at\n * build time to discover the leaf-to-key resolution.\n * 2. The contract is auditable from outside the React render\n * graph — `wildo website audit` can list a page's required keys\n * without booting React.\n * 3. The leaf-relative ergonomics (`useWebsiteLabel('headline')`)\n * survive — the hook composes the leaf with the page+section\n * context and looks the result up in the active pack. It does\n * NOT check the key against `expectedLabelKeys`: a leaf the\n * section never declared renders its key (with a console warning)\n * and is caught by nothing until a reviewer reads the page. The\n * build and `wildo website audit` check the pack against the\n * DECLARED keys, so declare every slot the component reads.\n * - `anchorId?` — the section's DOM id, so a link (`<a href=\"#tiers\">`)\n * can target it. Belongs to the DEFINITION because a page renders its\n * sections list through `WebsitePageSections`, which places every\n * section itself: an anchor a page author would otherwise write on a\n * hand-placed `<WebsiteSection id=…>` has nowhere else to live. A\n * valid HTML id that is also URL-fragment-safe: a letter, then\n * letters, digits, `-` or `_`. Uniqueness on a page is the author's\n * to keep, like the id attribute it becomes.\n * - `Component` — the React component that renders the section. Typed\n * as `ComponentType<unknown>` here because schema-layer cannot lock\n * the component's prop shape (props vary per section). The factory\n * function (`defineWebsiteSection<TProps>(…)`) preserves the precise\n * prop type at the consumer call site via generics; this schema only\n * guards the runtime shape of \"is callable as a React component\".\n * - `getStructuredData?` — optional pure function the SEO/AEO/GEO\n * builder library invokes at static-build time to materialize JSON-LD\n * for the section. The return type is intentionally `unknown` here;\n * nothing narrows it by `category` — the section returns whatever\n * vocabulary it emits, and `wildo website audit` reconciles that\n * against the application's Schema.org catalog.\n *\n * **Why not `z.custom<ComponentType>(…)`**: a `z.custom` predicate\n * cannot meaningfully validate \"is a React component\" beyond\n * `typeof === 'function'`, and the false-positive cost (any function\n * passes) is too high for the value (none — the consumer either\n * imports a real component or fails at JSX render time with a clearer\n * error). Using `z.unknown()` + a TS-side type cast keeps the runtime\n * cost zero and the type-safety contract entirely on the TS side\n * (where it actually has teeth via the factory's generic).\n */\nconst _WebsiteSectionDefinitionSchema = z.object({\n sectionRef: WebsiteSectionRefSchema,\n category: WebsiteSectionCategorySchema,\n expectedLabelKeys: z.array(WebsiteLabelKeySchema).readonly(),\n anchorId: z.string().regex(/^[A-Za-z][A-Za-z0-9_-]*$/, {\n error: 'A section anchorId must start with a letter and contain only letters, digits, \"-\" or \"_\"',\n }).optional(),\n Component: z.unknown(),\n getStructuredData: z.unknown().optional(),\n});\n\nexport const WebsiteSectionDefinitionSchema: typeof _WebsiteSectionDefinitionSchema =\n _WebsiteSectionDefinitionSchema;\n\n/**\n * Runtime shape of `WebsiteSectionDefinition`. Note: `Component` and\n * `getStructuredData` are typed precisely on the factory return type\n * (`defineWebsiteSection<TProps>` in Phase 2); the schema-inferred\n * type is the lowest common denominator used at the persistence /\n * validation boundary only.\n *\n * **Why `ComponentType<any>` and NOT `ComponentType<unknown>`**:\n * React component props are CONTRAVARIANT in `ComponentType<T>` —\n * a narrower `ComponentType<{ foo: string }>` is NOT assignable to\n * `ComponentType<unknown>` (because a caller could pass an `unknown`\n * the narrower component cannot accept). This made every consumer\n * who built sections via `defineWebsiteSection<TProps>(…)` unable\n * to feed the resulting `TypedWebsiteSectionDefinition<TProps>` to\n * helpers typed against the bare `WebsiteSectionDefinition` —\n * collectors, validators, and registries. Using `any` here is the\n * exact escape hatch the React typings themselves use for\n * heterogeneous component containers (`React.forwardRef`,\n * `React.lazy`'s internal component slot, etc.) and matches the\n * runtime contract: the framework NEVER instantiates these\n * components directly with arbitrary props — the consumer's JSX\n * supplies the right props at the call site.\n */\nexport interface WebsiteSectionDefinition extends z.infer<typeof _WebsiteSectionDefinitionSchema> {\n Component: ComponentType<any>;\n /**\n * Optional pure function the SEO/AEO/GEO renderer invokes at\n * static-build time to materialize JSON-LD for the section.\n *\n * **Why typed as `WebsiteStructuredDataReturn` (not `unknown`)** —\n * a typed return shape gives section authors autocomplete on\n * `@type` / `@id` / `@context` (the framework-owned fields) while\n * keeping all other fields free-form (Schema.org has hundreds of\n * optional fields per entity; locking them in TS would be both\n * impractical and brittle as Schema.org evolves). The companion\n * catalog reconciliation surface catches missing-required and\n * deprecated-field-used at the SEMANTIC layer (warning-only per\n * D-AG4), so the runtime type stays loose.\n *\n * Returns `null`, `undefined`, or `[]` when the section has no\n * structured-data contribution for the current state — the\n * renderer short-circuits all three.\n */\n getStructuredData?: () => WebsiteStructuredDataReturn;\n}\n/** @wildo_source:part:end saas.website.sections.definition-schema */\n"]}