@wildo-ai/saas-website 1.1.5 → 1.1.6

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 (182) hide show
  1. package/dist/esm/astro/blog-post-bridge.d.ts +9 -0
  2. package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -1
  3. package/dist/esm/astro/blog-post-bridge.js +10 -5
  4. package/dist/esm/astro/blog-post-bridge.js.map +1 -1
  5. package/dist/esm/astro/bridge-runtime.d.ts +25 -6
  6. package/dist/esm/astro/bridge-runtime.d.ts.map +1 -1
  7. package/dist/esm/astro/bridge-runtime.js +10 -6
  8. package/dist/esm/astro/bridge-runtime.js.map +1 -1
  9. package/dist/esm/astro/label-pack-loader.d.ts +4 -4
  10. package/dist/esm/astro/label-pack-loader.d.ts.map +1 -1
  11. package/dist/esm/astro/label-pack-loader.js +9 -4
  12. package/dist/esm/astro/label-pack-loader.js.map +1 -1
  13. package/dist/esm/astro/robots-renderer.d.ts +9 -8
  14. package/dist/esm/astro/robots-renderer.d.ts.map +1 -1
  15. package/dist/esm/astro/robots-renderer.js +15 -15
  16. package/dist/esm/astro/robots-renderer.js.map +1 -1
  17. package/dist/esm/astro/website-page-head.renderer.d.ts +1 -1
  18. package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -1
  19. package/dist/esm/astro/website-page-head.renderer.js +2 -1
  20. package/dist/esm/astro/website-page-head.renderer.js.map +1 -1
  21. package/dist/esm/astro/website-site-context.d.ts +5 -2
  22. package/dist/esm/astro/website-site-context.d.ts.map +1 -1
  23. package/dist/esm/astro/website-site-context.js.map +1 -1
  24. package/dist/esm/companion-exports.d.ts +8 -15
  25. package/dist/esm/companion-exports.d.ts.map +1 -1
  26. package/dist/esm/companion-exports.js +8 -15
  27. package/dist/esm/companion-exports.js.map +1 -1
  28. package/dist/esm/config/define-website-config.d.ts +3 -2
  29. package/dist/esm/config/define-website-config.d.ts.map +1 -1
  30. package/dist/esm/config/define-website-config.js +10 -10
  31. package/dist/esm/config/define-website-config.js.map +1 -1
  32. package/dist/esm/config/load-website-config.d.ts +6 -0
  33. package/dist/esm/config/load-website-config.d.ts.map +1 -1
  34. package/dist/esm/config/load-website-config.js +18 -9
  35. package/dist/esm/config/load-website-config.js.map +1 -1
  36. package/dist/esm/config/load-website-pricing-catalog.d.ts +13 -0
  37. package/dist/esm/config/load-website-pricing-catalog.d.ts.map +1 -0
  38. package/dist/esm/config/load-website-pricing-catalog.js +34 -0
  39. package/dist/esm/config/load-website-pricing-catalog.js.map +1 -0
  40. package/dist/esm/config/load-website-site-context.d.ts +53 -0
  41. package/dist/esm/config/load-website-site-context.d.ts.map +1 -0
  42. package/dist/esm/config/load-website-site-context.js +98 -0
  43. package/dist/esm/config/load-website-site-context.js.map +1 -0
  44. package/dist/esm/config-loader.d.ts +2 -0
  45. package/dist/esm/config-loader.d.ts.map +1 -1
  46. package/dist/esm/config-loader.js +2 -0
  47. package/dist/esm/config-loader.js.map +1 -1
  48. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +26 -6
  49. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -1
  50. package/dist/esm/core/anonymous-session/InboundContactForm.js +51 -33
  51. package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -1
  52. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +11 -0
  53. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -1
  54. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js +15 -4
  55. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -1
  56. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts +14 -2
  57. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -1
  58. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js +34 -4
  59. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -1
  60. package/dist/esm/core/anonymous-session/website-submission-challenge.d.ts +36 -0
  61. package/dist/esm/core/anonymous-session/website-submission-challenge.d.ts.map +1 -0
  62. package/dist/esm/core/anonymous-session/website-submission-challenge.js +69 -0
  63. package/dist/esm/core/anonymous-session/website-submission-challenge.js.map +1 -0
  64. package/dist/esm/core/consent/WebsiteConsentBanner.d.ts.map +1 -1
  65. package/dist/esm/core/consent/WebsiteConsentBanner.js +11 -10
  66. package/dist/esm/core/consent/WebsiteConsentBanner.js.map +1 -1
  67. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +25 -5
  68. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -1
  69. package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -1
  70. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +18 -0
  71. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -1
  72. package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -2
  73. package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -1
  74. package/dist/esm/core/external-providers/WebsiteProviderComponent.d.ts +57 -0
  75. package/dist/esm/core/external-providers/WebsiteProviderComponent.d.ts.map +1 -0
  76. package/dist/esm/core/external-providers/WebsiteProviderComponent.js +56 -0
  77. package/dist/esm/core/external-providers/WebsiteProviderComponent.js.map +1 -0
  78. package/dist/esm/core/external-providers/provider-component-registry.website.d.ts +63 -0
  79. package/dist/esm/core/external-providers/provider-component-registry.website.d.ts.map +1 -0
  80. package/dist/esm/core/external-providers/provider-component-registry.website.js +68 -0
  81. package/dist/esm/core/external-providers/provider-component-registry.website.js.map +1 -0
  82. package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts +36 -0
  83. package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts.map +1 -0
  84. package/dist/esm/core/external-providers/useWebsiteProviderComponent.js +59 -0
  85. package/dist/esm/core/external-providers/useWebsiteProviderComponent.js.map +1 -0
  86. package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts +15 -5
  87. package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts.map +1 -1
  88. package/dist/esm/core/external-providers/useWebsiteProviderScripts.js +39 -24
  89. package/dist/esm/core/external-providers/useWebsiteProviderScripts.js.map +1 -1
  90. package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts +11 -0
  91. package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts.map +1 -0
  92. package/dist/esm/core/external-providers/website-provider-sdk-activations.js +14 -0
  93. package/dist/esm/core/external-providers/website-provider-sdk-activations.js.map +1 -0
  94. package/dist/esm/core/factories/define-website-section.d.ts +3 -3
  95. package/dist/esm/core/factories/define-website-section.js +3 -3
  96. package/dist/esm/core/factories/define-website-section.js.map +1 -1
  97. package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -1
  98. package/dist/esm/core/layouts/WebsitePageLayout.js +10 -7
  99. package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -1
  100. package/dist/esm/index.d.ts +30 -65
  101. package/dist/esm/index.d.ts.map +1 -1
  102. package/dist/esm/index.js +30 -65
  103. package/dist/esm/index.js.map +1 -1
  104. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts +35 -6
  105. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -1
  106. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js +39 -5
  107. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -1
  108. package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.d.ts +32 -0
  109. package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.d.ts.map +1 -0
  110. package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js +36 -0
  111. package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js.map +1 -0
  112. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +6 -5
  113. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -1
  114. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js +8 -7
  115. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -1
  116. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +14 -7
  117. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -1
  118. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +14 -7
  119. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -1
  120. package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.d.ts +57 -0
  121. package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.d.ts.map +1 -0
  122. package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js +76 -0
  123. package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js.map +1 -0
  124. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +23 -0
  125. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -1
  126. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js +20 -2
  127. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -1
  128. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts +7 -5
  129. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -1
  130. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -1
  131. package/dist/tsconfig.build.tsbuildinfo +1 -1
  132. package/package.json +5 -5
  133. package/src/__tests__/bundle-isolation.test.ts +19 -22
  134. package/src/astro/__tests__/blog-post-bridge.test.tsx +18 -2
  135. package/src/astro/__tests__/bridge-runtime.test.tsx +21 -3
  136. package/src/astro/__tests__/label-pack-loader.test.ts +7 -4
  137. package/src/astro/blog-post-bridge.tsx +19 -5
  138. package/src/astro/bridge-runtime.tsx +35 -11
  139. package/src/astro/label-pack-loader.ts +8 -4
  140. package/src/astro/robots-renderer.ts +15 -15
  141. package/src/astro/website-page-head.renderer.ts +2 -2
  142. package/src/astro/website-site-context.ts +5 -2
  143. package/src/companion-exports.ts +8 -15
  144. package/src/config/__tests__/website-build-origins.test.ts +52 -0
  145. package/src/config/define-website-config.ts +10 -10
  146. package/src/config/load-website-config.ts +25 -14
  147. package/src/config/load-website-pricing-catalog.ts +52 -0
  148. package/src/config/load-website-site-context.ts +145 -0
  149. package/src/config-loader.ts +2 -0
  150. package/src/core/__tests__/contexts.test.tsx +20 -1
  151. package/src/core/__tests__/website-consent.test.tsx +2 -1
  152. package/src/core/anonymous-session/InboundContactForm.tsx +100 -45
  153. package/src/core/anonymous-session/__tests__/InboundContactForm.labels.test.tsx +108 -0
  154. package/src/core/anonymous-session/__tests__/InboundContactForm.submission-challenge.test.tsx +139 -0
  155. package/src/core/anonymous-session/inbound-contact-form.schema.ts +16 -4
  156. package/src/core/anonymous-session/website-anonymous-session-client.ts +39 -4
  157. package/src/core/anonymous-session/website-submission-challenge.ts +99 -0
  158. package/src/core/consent/WebsiteConsentBanner.tsx +11 -10
  159. package/src/core/contexts/WebsiteRuntimeContext.tsx +25 -5
  160. package/src/core/contexts/useWebsiteRuntime.ts +33 -2
  161. package/src/core/external-providers/WebsiteProviderComponent.tsx +153 -0
  162. package/src/core/external-providers/__tests__/frontend-provider-registry.website.test.ts +4 -0
  163. package/src/core/external-providers/__tests__/website-provider-component.test.tsx +238 -0
  164. package/src/core/external-providers/__tests__/website-provider-sdks.test.tsx +72 -0
  165. package/src/core/external-providers/provider-component-registry.website.ts +115 -0
  166. package/src/core/external-providers/useWebsiteProviderComponent.ts +76 -0
  167. package/src/core/external-providers/useWebsiteProviderScripts.ts +56 -37
  168. package/src/core/external-providers/website-provider-sdk-activations.ts +23 -0
  169. package/src/core/factories/define-website-section.ts +3 -3
  170. package/src/core/layouts/WebsitePageLayout.tsx +11 -6
  171. package/src/index.ts +31 -65
  172. package/src/schemas/__tests__/design-token-css-property.test.ts +72 -0
  173. package/src/schemas/__tests__/pricing-offer-structured-data.test.ts +122 -0
  174. package/src/schemas/__tests__/structured-data-reconciliation.test.ts +35 -0
  175. package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +45 -5
  176. package/src/schemas/label-keys/__tests__/scaffolded-consent-label-pack.parity.test.ts +12 -0
  177. package/src/schemas/label-keys/website-provider-component-label-keys.schemas.ts +40 -0
  178. package/src/schemas/manifests/website-root-config.shared.schemas.ts +10 -9
  179. package/src/schemas/sections/website-section-definition.shared.schemas.ts +14 -7
  180. package/src/schemas/structured-data/pricing-offer-structured-data.shared.ts +144 -0
  181. package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +46 -1
  182. package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +7 -5
@@ -85,9 +85,10 @@ export type WebsiteSitemapConfig = z.infer<typeof _WebsiteSitemapConfigSchema>;
85
85
  * `'noindex'` is the deliberate exception (staging environments,
86
86
  * beta surfaces, sites still under construction). The opt-OUT
87
87
  * shape mirrors `WebsiteSitemapConfig.enabled` for consistency.
88
- * - `'noindex'` emits a `User-agent: *\nDisallow: /` block — the
89
- * standard "tell every well-behaved crawler to stay away"
90
- * primitive.
88
+ * - `'noindex'` is expressed by a `<meta name="robots" content="noindex">`
89
+ * on every page, NOT by `Disallow: /` in `robots.txt` (#494): a crawler
90
+ * barred from fetching a page never reads its noindex, so the URL can
91
+ * still be indexed bare. `robots.txt` stays allow-everything for it.
91
92
  *
92
93
  * **Why `disallow[]` is a path-prefix list (not a regex)**:
93
94
  * - The `robots.txt` spec only supports literal path prefixes
@@ -412,8 +413,8 @@ export declare const WebsiteRootConfigSchema: typeof _WebsiteRootConfigSchema;
412
413
  * and `navigation.footerComponent` as `unknown` (their schema field
413
414
  * type), losing the `ComponentType<unknown>` narrowing carried by the
414
415
  * hand-written `WebsitePageManifest` and `WebsiteNavigationConfig`
415
- * interfaces. Factories such as `defineWebsiteRootConfig` and the
416
- * layout primitives consume `WebsiteRootConfig` and must see those
416
+ * interfaces. The layout primitives and the Astro helpers consume
417
+ * `WebsiteRootConfig` and must see those
417
418
  * fields pre-narrowed; otherwise every consumer would need a local
418
419
  * `as ComponentType` cast at the JSX call site, defeating the type-safe
419
420
  * component contract.
@@ -1 +1 @@
1
- {"version":3,"file":"website-root-config.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/manifests/website-root-config.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,wCAAwC,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,QAAA,MAAM,2BAA2B;;;;;;;;;;;;iBAU/B,CAAC;AAEH,eAAO,MAAM,0BAA0B,EAAE,OAAO,2BACnB,CAAC;AAE9B,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAC/E,6DAA6D;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,QAAA,MAAM,0BAA0B;;;;;;;;iBAsC5B,CAAC;AAEL,eAAO,MAAM,yBAAyB,EAAE,OAAO,0BACnB,CAAC;AAE7B,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC7E,4DAA4D;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,2BAA2B;;iBAE/B,CAAC;AAEH,eAAO,MAAM,0BAA0B,EAAE,OAAO,2BACnB,CAAC;AAE9B,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAC/E,8DAA8D;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,QAAA,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BzB,CAAC;AAEL,eAAO,MAAM,sBAAsB,EAAE,OAAO,uBAAiD,CAAC;AAE9F,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACvE,0DAA0D;AAE1D;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,8BAA8B;;;iBAGlC,CAAC;AAEH,eAAO,MAAM,6BAA6B,EAAE,OAAO,8BACnB,CAAC;AAEjC,MAAM,WAAW,uBACf,SAAQ,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC;IACtD,eAAe,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACxC,eAAe,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;CACzC;AACD,iEAAiE;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,QAAA,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2F1B,CAAC;AAEL,eAAO,MAAM,uBAAuB,EAAE,OAAO,wBAAmD,CAAC;AAEjG;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,EACxC,eAAe,GAAG,YAAY,CAC/B,GAAG;IACF,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,UAAU,EAAE,uBAAuB,CAAC;CACrC,CAAC;;AACF,6DAA6D"}
1
+ {"version":3,"file":"website-root-config.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/manifests/website-root-config.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,wCAAwC,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,QAAA,MAAM,2BAA2B;;;;;;;;;;;;iBAU/B,CAAC;AAEH,eAAO,MAAM,0BAA0B,EAAE,OAAO,2BACnB,CAAC;AAE9B,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAC/E,6DAA6D;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,QAAA,MAAM,0BAA0B;;;;;;;;iBAsC5B,CAAC;AAEL,eAAO,MAAM,yBAAyB,EAAE,OAAO,0BACnB,CAAC;AAE7B,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC7E,4DAA4D;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,2BAA2B;;iBAE/B,CAAC;AAEH,eAAO,MAAM,0BAA0B,EAAE,OAAO,2BACnB,CAAC;AAE9B,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAC/E,8DAA8D;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,QAAA,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BzB,CAAC;AAEL,eAAO,MAAM,sBAAsB,EAAE,OAAO,uBAAiD,CAAC;AAE9F,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACvE,0DAA0D;AAE1D;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,8BAA8B;;;iBAGlC,CAAC;AAEH,eAAO,MAAM,6BAA6B,EAAE,OAAO,8BACnB,CAAC;AAEjC,MAAM,WAAW,uBACf,SAAQ,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC;IACtD,eAAe,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACxC,eAAe,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;CACzC;AACD,iEAAiE;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,QAAA,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2F1B,CAAC;AAEL,eAAO,MAAM,uBAAuB,EAAE,OAAO,wBAAmD,CAAC;AAEjG;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,EACxC,eAAe,GAAG,YAAY,CAC/B,GAAG;IACF,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,UAAU,EAAE,uBAAuB,CAAC;CACrC,CAAC;;AACF,6DAA6D"}
@@ -83,9 +83,10 @@ export const WebsiteSitemapConfigSchema = _WebsiteSitemapConfigSchema;
83
83
  * `'noindex'` is the deliberate exception (staging environments,
84
84
  * beta surfaces, sites still under construction). The opt-OUT
85
85
  * shape mirrors `WebsiteSitemapConfig.enabled` for consistency.
86
- * - `'noindex'` emits a `User-agent: *\nDisallow: /` block — the
87
- * standard "tell every well-behaved crawler to stay away"
88
- * primitive.
86
+ * - `'noindex'` is expressed by a `<meta name="robots" content="noindex">`
87
+ * on every page, NOT by `Disallow: /` in `robots.txt` (#494): a crawler
88
+ * barred from fetching a page never reads its noindex, so the URL can
89
+ * still be indexed bare. `robots.txt` stays allow-everything for it.
89
90
  *
90
91
  * **Why `disallow[]` is a path-prefix list (not a regex)**:
91
92
  * - The `robots.txt` spec only supports literal path prefixes
@@ -383,10 +384,10 @@ const _WebsiteRootConfigSchema = z
383
384
  * the chrome) or above it (creating a structural surface
384
385
  * the framework needs anyway).
385
386
  *
386
- * The key MUST also be present in `chromeLabelKeys` (the
387
- * label-pack validator silences "orphaned" warnings off
388
- * `chromeLabelKeys`) — the framework adds it automatically via
389
- * the `superRefine` pass below so consumers cannot forget.
387
+ * It is validated as a chrome key without being listed in
388
+ * `chromeLabelKeys`: the label-pack loader adds it to the effective
389
+ * chrome key set (`composeEffectiveChromeLabelKeys` in
390
+ * `astro/label-pack-loader.ts`), so consumers cannot forget it.
390
391
  */
391
392
  skipToMainContentLabelKey: WebsiteLabelKeySchema,
392
393
  })
@@ -1 +1 @@
1
- {"version":3,"file":"website-root-config.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/schemas/manifests/website-root-config.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType } from 'react';\nimport { z } from 'zod';\n\nimport { WebsiteDesignTokensSchema } from '../design-tokens/website-design-tokens.shared.schemas';\nimport { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';\nimport {\n WebsitePageManifestSchema,\n type WebsitePageManifest,\n} from './website-page-manifest.shared.schemas';\n\n/**\n * @wildo_source:part:start saas.website.seo.sitemap-config facet:layer:shared facet:family:website\n *\n * `WebsiteSitemapConfig` — site-wide sitemap policy block.\n *\n * Materialized at build time by `@astrojs/sitemap` (the official\n * Astro plugin) when the consumer wires it into `astro.config.ts`\n * (see `website-seo-aeo-geo` SKILL). The framework owns the\n * **schema** (so config + enforcement live in lock-step with the\n * page registry) but the framework does NOT own the runtime\n * generator — `@astrojs/sitemap` already covers per-page\n * `lastmod` / `changefreq` / `priority` + i18n `xhtml:link\n * hreflang` annotations natively, and re-implementing that surface\n * inside the engine would duplicate well-trodden Astro tooling for\n * zero added value.\n *\n * # Field rationale\n *\n * **Why `enabled` defaults to `true`**:\n * - Marketing sites that want to ship public pages overwhelmingly\n * also want them in `sitemap.xml` (search engines treat its\n * absence as a discoverability signal). The opt-OUT shape avoids\n * a silent regression where a consumer ships pages that crawlers\n * never index because they forgot to flip a flag.\n * - The escape hatch (`enabled: false`) covers staging deployments\n * and the rare per-app decision to suppress sitemap emission\n * entirely (e.g. a private beta surface routed under a\n * `noindex` policy).\n *\n * **Why `changefreq` / `priority` are OPTIONAL** (no defaults):\n * - Both fields are non-binding hints — Google explicitly\n * ignores `priority` and treats `changefreq` as a soft signal.\n * Forcing the consumer to pick a value would either invite\n * superstitious authoring (`priority: 1.0` on every page) or\n * bury the field's actual ergonomic shape (per-page overrides\n * via `WebsitePageManifest.seo` — deferred until a real\n * consumer requests it).\n * - Unset values are simply omitted from the emitted `<url>`\n * entries; `@astrojs/sitemap` handles that automatically.\n *\n * **Validation**:\n * - `priority` is `z.number().min(0).max(1)` — outside the spec\n * range would silently break crawler ingestion.\n * - `changefreq` is the union of the seven values the sitemap\n * spec defines; arbitrary strings would be silently dropped by\n * compliant crawlers.\n */\nconst _WebsiteSitemapConfigSchema = z.object({\n enabled: z.boolean().default(true),\n changefreq: z\n .enum(['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'])\n .optional(),\n priority: z\n .number()\n .min(0, { error: 'WebsiteSitemapConfig.priority must be between 0 and 1' })\n .max(1, { error: 'WebsiteSitemapConfig.priority must be between 0 and 1' })\n .optional(),\n});\n\nexport const WebsiteSitemapConfigSchema: typeof _WebsiteSitemapConfigSchema =\n _WebsiteSitemapConfigSchema;\n\nexport type WebsiteSitemapConfig = z.infer<typeof _WebsiteSitemapConfigSchema>;\n/** @wildo_source:part:end saas.website.seo.sitemap-config */\n\n/**\n * @wildo_source:part:start saas.website.seo.robots-config facet:layer:shared facet:family:website\n *\n * `WebsiteRobotsConfig` — site-wide `robots.txt` policy block.\n *\n * Materialized at request time by `renderRobotsTxt(siteContext)`\n * from `@wildo-ai/saas-website/astro` (consumer wires a thin\n * `src/pages/robots.txt.ts` Astro endpoint that returns the helper's\n * output). Scoped to the SITE-WIDE policy surface — per-page\n * `noindex` is a separate concern that lands on\n * `WebsitePageManifest.seo` once a real consumer requests it (today\n * the site-wide `disallow[]` covers staging-environment exclusion of\n * specific path prefixes, the dominant real-world use case).\n *\n * # Field rationale\n *\n * **Why `policy` defaults to `'index'`**:\n * - The default for a public marketing site is \"let crawlers in\";\n * `'noindex'` is the deliberate exception (staging environments,\n * beta surfaces, sites still under construction). The opt-OUT\n * shape mirrors `WebsiteSitemapConfig.enabled` for consistency.\n * - `'noindex'` emits a `User-agent: *\\nDisallow: /` block — the\n * standard \"tell every well-behaved crawler to stay away\"\n * primitive.\n *\n * **Why `disallow[]` is a path-prefix list (not a regex)**:\n * - The `robots.txt` spec only supports literal path prefixes\n * with `*` / `$` wildcards; Disallow is NOT a regex surface.\n * Typing it as `string[]` (validated to start with `/`) keeps\n * the contract honest and lets consumers paste from their CDN\n * allowlists without translation.\n *\n * **Why `allowedCrawlers` and `disallowedCrawlers` are mutually\n * exclusive**:\n * - The two fields are two halves of the SAME AI-crawler policy\n * decision. `allowedCrawlers` is the deny-by-default shape\n * (only the named bots may crawl); `disallowedCrawlers` is the\n * allow-by-default shape (every bot except the named ones may\n * crawl). Mixing both would emit a contradictory `robots.txt`\n * (a bot in both lists would receive conflicting directives).\n * A `superRefine` enforces the mutual-exclusion invariant at\n * parse time so the build fails fast.\n *\n * **Validation**:\n * - `disallow[]` entries must start with `/` (sitemap path-prefix\n * spec) — anything else would silently match nothing.\n * - `allowedCrawlers` / `disallowedCrawlers` entries must be\n * non-empty strings (a blank User-agent token would emit\n * `User-agent: ` which compliant crawlers ignore — silent\n * misconfig).\n * - `superRefine` rejects the combined-list anti-pattern.\n */\nconst _WebsiteRobotsConfigSchema = z\n .object({\n policy: z.enum(['index', 'noindex']).default('index'),\n disallow: z\n .array(\n z\n .string()\n .min(1)\n .refine((path) => path.startsWith('/'), {\n error: 'WebsiteRobotsConfig.disallow[] entries must start with \"/\"',\n }),\n )\n .optional(),\n /**\n * Deny-by-default AI-crawler allowlist. When set, the emitted\n * `robots.txt` lists each named bot under its own\n * `User-agent: <bot>\\nAllow: /` block, then closes with a\n * catch-all `User-agent: *\\nDisallow: /`. Mutually exclusive\n * with `disallowedCrawlers` (see `superRefine` below).\n */\n allowedCrawlers: z.array(z.string().min(1)).optional(),\n /**\n * Allow-by-default AI-crawler denylist. When set, the emitted\n * `robots.txt` lists each named bot under its own\n * `User-agent: <bot>\\nDisallow: /` block. Mutually exclusive\n * with `allowedCrawlers` (see `superRefine` below).\n */\n disallowedCrawlers: z.array(z.string().min(1)).optional(),\n })\n .superRefine((value, ctx) => {\n if (value.allowedCrawlers !== undefined && value.disallowedCrawlers !== undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['allowedCrawlers'],\n message:\n 'WebsiteRobotsConfig.allowedCrawlers and disallowedCrawlers are mutually exclusive — pick one policy direction (deny-by-default vs allow-by-default).',\n });\n }\n });\n\nexport const WebsiteRobotsConfigSchema: typeof _WebsiteRobotsConfigSchema =\n _WebsiteRobotsConfigSchema;\n\nexport type WebsiteRobotsConfig = z.infer<typeof _WebsiteRobotsConfigSchema>;\n/** @wildo_source:part:end saas.website.seo.robots-config */\n\n/**\n * @wildo_source:part:start saas.website.seo.llms-txt-config facet:layer:shared facet:family:website\n *\n * `WebsiteLlmsTxtConfig` — site-wide `llms.txt` publication policy.\n *\n * The framework owns only the SERVING surface; the CONTENT remains\n * hand-authored in `src/content/llms.txt`. This block therefore needs\n * exactly one knob:\n *\n * - `enabled?: boolean`\n * - `true` → publish when the file exists\n * - `false` → suppress publication even if the file exists\n * - `undefined` → auto-detect (`readLlmsTxtBody` publishes when the\n * file exists, returns `null` when absent)\n *\n * Keeping the \"auto-detect when undefined\" convention in the HELPER\n * (not as a schema default) is deliberate: schema defaults are static,\n * while file presence is runtime/build-time state.\n */\nconst _WebsiteLlmsTxtConfigSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const WebsiteLlmsTxtConfigSchema: typeof _WebsiteLlmsTxtConfigSchema =\n _WebsiteLlmsTxtConfigSchema;\n\nexport type WebsiteLlmsTxtConfig = z.infer<typeof _WebsiteLlmsTxtConfigSchema>;\n/** @wildo_source:part:end saas.website.seo.llms-txt-config */\n\n/**\n * @wildo_source:part:start saas.website.root-config.seo facet:layer:shared facet:family:website\n *\n * `WebsiteSeoConfig` — site-wide SEO/social-graph configuration\n * AUTHORED ON THE ROOT CONFIG.\n *\n * # Scope\n *\n * The public marketing-site origin belongs only in the per-service\n * `wildo.website.config.ts` declaration. Keep it out of this site-level SEO\n * block so the origin and localized routing have one source of truth.\n *\n * What stays on this block: site-level branding + hero-image\n * fallbacks that are co-authored with chrome labels and page\n * manifests respectively, NOT with the URL/i18n routing surface.\n *\n * # Field rationale\n *\n * **Why `siteName` is REQUIRED**:\n * - `og:site_name` is the brand label social platforms render\n * above the page title in shared previews. Without it, every\n * LinkedIn / Facebook / Slack preview shows the bare title with\n * no brand context.\n * - It is also the recommended `application-name` meta tag value\n * and a useful default for JSON-LD `Organization.name` schema\n * when the application ships structured data.\n *\n * **Why `defaultOgImageUrl` is OPTIONAL**:\n * - Each page's `WebsitePageManifest.metaLabelKeys.ogImageUrl`\n * takes precedence; the site-level default is a fallback for\n * pages that did not commit to a hero image.\n * - Marketing surfaces shipping no OG images at all (acceptable\n * for a placeholder build) should not be forced to author one.\n *\n * **Why `sitemap?` and `robots?` are OPTIONAL** (with sensible\n * defaults inside each sub-schema):\n * - The marketing-site default ships a public surface — every\n * page indexable, no AI-crawler restrictions, no per-path\n * disallow. Forcing every consumer to author both blocks just\n * to land in that default state would pollute the call site.\n * - Omitting either block at the call site is equivalent to\n * authoring `{}` — `WebsiteSitemapConfig.enabled` defaults to\n * `true`, `WebsiteRobotsConfig.policy` defaults to `'index'`.\n * The per-block field rationale lives on each sub-schema.\n * - `llmsTxt?` is similarly optional, but with a different default\n * mechanic: omitting the block means \"auto-detect from file\n * presence\" rather than \"use a static schema default\". The helper\n * `readLlmsTxtBody(...)` publishes when `src/content/llms.txt`\n * exists and returns `null` otherwise.\n *\n * **Validation**:\n * - `siteName` is `z.string().min(1)` — empty strings would emit\n * `<meta property=\"og:site_name\" content=\"\" />` which social\n * platforms render as a blank line.\n * - `defaultOgImageUrl` (when present) is `z.url()`.\n * - `sitemap` / `robots` validation lives on the sub-schemas.\n */\nconst _WebsiteSeoConfigSchema = z\n .object({\n siteName: z\n .string()\n .min(1, { error: 'WebsiteSeoConfig.siteName must be a non-empty string' }),\n defaultOgImageUrl: z\n .url({ error: 'WebsiteSeoConfig.defaultOgImageUrl must be a fully-qualified URL' })\n .optional(),\n /**\n * Sitemap policy block. Optional — omitting it is equivalent to\n * `{ enabled: true }` (the marketing-site default). See\n * `WebsiteSitemapConfigSchema` for field-by-field rationale.\n */\n sitemap: _WebsiteSitemapConfigSchema.optional(),\n /**\n * Robots.txt policy block. Optional — omitting it is equivalent\n * to `{ policy: 'index' }` (the marketing-site default). See\n * `WebsiteRobotsConfigSchema` for field-by-field rationale.\n */\n robots: _WebsiteRobotsConfigSchema.optional(),\n /**\n * `llms.txt` publication policy. Optional — omitting it means\n * \"auto-detect from file presence\". See\n * `WebsiteLlmsTxtConfigSchema` for the serving contract.\n */\n llmsTxt: _WebsiteLlmsTxtConfigSchema.optional(),\n });\n\nexport const WebsiteSeoConfigSchema: typeof _WebsiteSeoConfigSchema = _WebsiteSeoConfigSchema;\n\nexport type WebsiteSeoConfig = z.infer<typeof _WebsiteSeoConfigSchema>;\n/** @wildo_source:part:end saas.website.root-config.seo */\n\n/**\n * @wildo_source:part:start saas.website.root-config.navigation facet:layer:shared facet:family:website\n *\n * `WebsiteNavigationConfig` — site-wide header/footer slot bindings.\n *\n * The framework intentionally ships no concrete header or footer. The\n * application authors both as React components, registers them once here, and\n * the page layout renders the site-wide chrome around every page.\n *\n * Both fields are typed as `unknown` at the schema layer with the\n * `WebsiteNavigationConfig` interface narrowing them to\n * `ComponentType<unknown>` — same rationale as\n * `WebsiteSectionDefinition.Component` and `WebsitePageManifest.pageComponent`.\n */\nconst _WebsiteNavigationConfigSchema = z.object({\n headerComponent: z.unknown(),\n footerComponent: z.unknown(),\n});\n\nexport const WebsiteNavigationConfigSchema: typeof _WebsiteNavigationConfigSchema =\n _WebsiteNavigationConfigSchema;\n\nexport interface WebsiteNavigationConfig\n extends z.infer<typeof _WebsiteNavigationConfigSchema> {\n headerComponent: ComponentType<unknown>;\n footerComponent: ComponentType<unknown>;\n}\n/** @wildo_source:part:end saas.website.root-config.navigation */\n\n/**\n * @wildo_source:part:start saas.website.root-config.schema facet:layer:shared facet:family:website\n *\n * `WebsiteRootConfig` — the site-level configuration consumed by:\n * - the Astro adapter to drive i18n routing, page mounting,\n * and design-token CSS emission,\n * - the website audit command to validate label-pack\n * coverage and SEO structured-data conformance,\n * - the runtime `<WebsitePageLayout>` to render navigation\n * and apply design tokens.\n *\n * **Authoring convention**: each marketing-site service exports a single\n * `WEBSITE_ROOT_CONFIG: WebsiteRootConfig` from `src/website-root.config.ts`.\n * The Astro adapter and the audit command\n * both consume this canonical entry point; there is exactly one site\n * config per marketing site (no per-page or per-section overrides at\n * this level — overrides flow through the L0/L1/L1.5 component\n * registration hierarchy described in D6).\n *\n * # Scope\n *\n * This block carries site-level chrome and registry data only. Per-locale\n * routing (`locales` / `defaultLocale`) and the public origin belong in the\n * per-service `wildo.website.config.ts` declaration, avoiding duplicated\n * configuration that could diverge during site generation.\n *\n * **Field-by-field rationale**:\n *\n * - `pageManifests` — the registry of every page on the site. Indexed\n * downstream by `manifest.ref`. Refs MUST be unique (refinement\n * below).\n * - `designTokens` — see `WebsiteDesignTokensSchema`.\n * - `navigation` — see `WebsiteNavigationConfigSchema`.\n * - `chromeLabelKeys` — OPTIONAL. The set of fully-composed label keys\n * the site-wide chrome (header / footer / any global slot the\n * consumer renders outside a section context) reads via\n * `useWebsiteLabelByKey(...)`. Declared here, NOT in section\n * `expectedLabelKeys`, because chrome is rendered outside the\n * page+section context — `useWebsiteLabel(slot)` is structurally\n * unavailable to it. Threading these through the label-pack\n * validator (a) silences the always-on \"orphaned chrome key\" build\n * warnings and (b) lets the build fail fast when a chrome key is\n * missing from a locale pack, the same way section keys do.\n * Defaults to an empty array; sites with no chrome label reads\n * may omit the field entirely.\n * - `seo` — site-level branding + hero-image fallback (see\n * `WebsiteSeoConfigSchema`). The marketing-site origin USED to live\n * here as `seo.siteOrigin`; it now lives only on\n * `WildoMarketingWebsiteConfig.publicOrigin`.\n * - `skipToMainContentLabelKey` — see field JSDoc below.\n *\n * **Refinements**:\n * - `pageManifests[].ref` uniqueness — the registry is keyed by ref;\n * duplicates would silently overwrite each other in the lookup\n * table the bridge uses for cross-page links.\n * - `pageManifests[].routePath` uniqueness — two pages cannot mount\n * at the same path (Astro would error at build time, but we\n * surface it earlier here with a clearer diagnostic).\n * - `chromeLabelKeys` uniqueness — duplicates indicate copy-paste\n * bugs at the consumer's call site; surface them here.\n */\nconst _WebsiteRootConfigSchema = z\n .object({\n pageManifests: z.array(WebsitePageManifestSchema).min(1, {\n error: 'WebsiteRootConfig must declare at least one page manifest',\n }),\n designTokens: WebsiteDesignTokensSchema,\n navigation: _WebsiteNavigationConfigSchema,\n chromeLabelKeys: z.array(WebsiteLabelKeySchema).default([]),\n /**\n * Site-level branding + hero-image fallback used by the Astro\n * head renderer to compose `<meta property=\"og:site_name\">` and\n * the per-page OG image fallback. The marketing-site origin\n * (`siteOrigin`) USED to live here; it was moved to\n * `WildoMarketingWebsiteConfig.publicOrigin` in the seam-collapse\n * (single source of truth for \"what's the origin this site is\n * reachable at\").\n */\n seo: _WebsiteSeoConfigSchema,\n /**\n * Label key the framework reads to render the \"Skip to main\n * content\" link as the first focusable element on every page\n * Required because:\n * - Skip-to-main is a baseline keyboard-accessibility\n * affordance — WCAG 2.1 §2.4.1 \"Bypass Blocks\" (Level A)\n * mandates it. A marketing-site framework that omits it\n * fails AA for the entire surface; making the contract\n * opt-in invites that regression on every new consumer.\n * - The text is announced verbatim by assistive tech, so it\n * MUST be localized — a hardcoded English fallback would\n * speak \"Skip to main content\" to French users. Forcing the\n * key through the label-pack validation surface means the\n * dev server fails fast when the consumer forgets to add\n * the entry to a locale pack.\n * - The framework (NOT the consumer's chrome) owns rendering\n * the skip link because the chrome is itself the obstacle\n * the link bypasses — a chrome-side implementation would\n * either be inside the chrome (impossible to focus before\n * the chrome) or above it (creating a structural surface\n * the framework needs anyway).\n *\n * The key MUST also be present in `chromeLabelKeys` (the\n * label-pack validator silences \"orphaned\" warnings off\n * `chromeLabelKeys`) — the framework adds it automatically via\n * the `superRefine` pass below so consumers cannot forget.\n */\n skipToMainContentLabelKey: WebsiteLabelKeySchema,\n })\n .superRefine((config, ctx) => {\n const seenRefs = new Set<string>();\n const seenRoutePaths = new Set<string>();\n for (const [index, manifest] of config.pageManifests.entries()) {\n const refKey = manifest.ref as unknown as string;\n if (seenRefs.has(refKey)) {\n ctx.addIssue({\n code: 'custom',\n path: ['pageManifests', index, 'ref'],\n message: `Duplicate page ref \"${refKey}\" in WebsiteRootConfig.pageManifests`,\n });\n }\n seenRefs.add(refKey);\n\n const routeKey = manifest.routePath as unknown as string;\n if (seenRoutePaths.has(routeKey)) {\n ctx.addIssue({\n code: 'custom',\n path: ['pageManifests', index, 'routePath'],\n message: `Duplicate routePath \"${routeKey}\" in WebsiteRootConfig.pageManifests`,\n });\n }\n seenRoutePaths.add(routeKey);\n }\n\n /**\n * `chromeLabelKeys` uniqueness — a duplicate is harmless at\n * runtime (the validator de-dupes via a Set) but always indicates\n * a copy-paste bug at the consumer's call site. Surfacing it here\n * keeps the chrome contract tidy and matches the discipline applied\n * to `pageManifests[].ref` and `locales`.\n */\n const seenChromeKeys = new Set<string>();\n for (const [index, key] of config.chromeLabelKeys.entries()) {\n const keyStr = key as unknown as string;\n if (seenChromeKeys.has(keyStr)) {\n ctx.addIssue({\n code: 'custom',\n path: ['chromeLabelKeys', index],\n message: `Duplicate chrome label key \"${keyStr}\" in WebsiteRootConfig.chromeLabelKeys`,\n });\n }\n seenChromeKeys.add(keyStr);\n }\n });\n\nexport const WebsiteRootConfigSchema: typeof _WebsiteRootConfigSchema = _WebsiteRootConfigSchema;\n\n/**\n * Runtime shape of `WebsiteRootConfig`.\n *\n * Hand-written (NOT a bare `z.infer<typeof _WebsiteRootConfigSchema>`)\n * because `z.infer` walks the schema tree and would type\n * `pageManifests[number].pageComponent`, `navigation.headerComponent`\n * and `navigation.footerComponent` as `unknown` (their schema field\n * type), losing the `ComponentType<unknown>` narrowing carried by the\n * hand-written `WebsitePageManifest` and `WebsiteNavigationConfig`\n * interfaces. Factories such as `defineWebsiteRootConfig` and the\n * layout primitives consume `WebsiteRootConfig` and must see those\n * fields pre-narrowed; otherwise every consumer would need a local\n * `as ComponentType` cast at the JSX call site, defeating the type-safe\n * component contract.\n *\n * The `Omit` + intersection pattern keeps the rest of the inferred\n * shape (`designTokens`, `seo`, `chromeLabelKeys`,\n * `skipToMainContentLabelKey`) sourced directly from the schema.\n */\nexport type WebsiteRootConfig = Omit<\n z.infer<typeof _WebsiteRootConfigSchema>,\n 'pageManifests' | 'navigation'\n> & {\n pageManifests: WebsitePageManifest[];\n navigation: WebsiteNavigationConfig;\n};\n/** @wildo_source:part:end saas.website.root-config.schema */\n"]}
1
+ {"version":3,"file":"website-root-config.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/schemas/manifests/website-root-config.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType } from 'react';\nimport { z } from 'zod';\n\nimport { WebsiteDesignTokensSchema } from '../design-tokens/website-design-tokens.shared.schemas';\nimport { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';\nimport {\n WebsitePageManifestSchema,\n type WebsitePageManifest,\n} from './website-page-manifest.shared.schemas';\n\n/**\n * @wildo_source:part:start saas.website.seo.sitemap-config facet:layer:shared facet:family:website\n *\n * `WebsiteSitemapConfig` — site-wide sitemap policy block.\n *\n * Materialized at build time by `@astrojs/sitemap` (the official\n * Astro plugin) when the consumer wires it into `astro.config.ts`\n * (see `website-seo-aeo-geo` SKILL). The framework owns the\n * **schema** (so config + enforcement live in lock-step with the\n * page registry) but the framework does NOT own the runtime\n * generator — `@astrojs/sitemap` already covers per-page\n * `lastmod` / `changefreq` / `priority` + i18n `xhtml:link\n * hreflang` annotations natively, and re-implementing that surface\n * inside the engine would duplicate well-trodden Astro tooling for\n * zero added value.\n *\n * # Field rationale\n *\n * **Why `enabled` defaults to `true`**:\n * - Marketing sites that want to ship public pages overwhelmingly\n * also want them in `sitemap.xml` (search engines treat its\n * absence as a discoverability signal). The opt-OUT shape avoids\n * a silent regression where a consumer ships pages that crawlers\n * never index because they forgot to flip a flag.\n * - The escape hatch (`enabled: false`) covers staging deployments\n * and the rare per-app decision to suppress sitemap emission\n * entirely (e.g. a private beta surface routed under a\n * `noindex` policy).\n *\n * **Why `changefreq` / `priority` are OPTIONAL** (no defaults):\n * - Both fields are non-binding hints — Google explicitly\n * ignores `priority` and treats `changefreq` as a soft signal.\n * Forcing the consumer to pick a value would either invite\n * superstitious authoring (`priority: 1.0` on every page) or\n * bury the field's actual ergonomic shape (per-page overrides\n * via `WebsitePageManifest.seo` — deferred until a real\n * consumer requests it).\n * - Unset values are simply omitted from the emitted `<url>`\n * entries; `@astrojs/sitemap` handles that automatically.\n *\n * **Validation**:\n * - `priority` is `z.number().min(0).max(1)` — outside the spec\n * range would silently break crawler ingestion.\n * - `changefreq` is the union of the seven values the sitemap\n * spec defines; arbitrary strings would be silently dropped by\n * compliant crawlers.\n */\nconst _WebsiteSitemapConfigSchema = z.object({\n enabled: z.boolean().default(true),\n changefreq: z\n .enum(['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'])\n .optional(),\n priority: z\n .number()\n .min(0, { error: 'WebsiteSitemapConfig.priority must be between 0 and 1' })\n .max(1, { error: 'WebsiteSitemapConfig.priority must be between 0 and 1' })\n .optional(),\n});\n\nexport const WebsiteSitemapConfigSchema: typeof _WebsiteSitemapConfigSchema =\n _WebsiteSitemapConfigSchema;\n\nexport type WebsiteSitemapConfig = z.infer<typeof _WebsiteSitemapConfigSchema>;\n/** @wildo_source:part:end saas.website.seo.sitemap-config */\n\n/**\n * @wildo_source:part:start saas.website.seo.robots-config facet:layer:shared facet:family:website\n *\n * `WebsiteRobotsConfig` — site-wide `robots.txt` policy block.\n *\n * Materialized at request time by `renderRobotsTxt(siteContext)`\n * from `@wildo-ai/saas-website/astro` (consumer wires a thin\n * `src/pages/robots.txt.ts` Astro endpoint that returns the helper's\n * output). Scoped to the SITE-WIDE policy surface — per-page\n * `noindex` is a separate concern that lands on\n * `WebsitePageManifest.seo` once a real consumer requests it (today\n * the site-wide `disallow[]` covers staging-environment exclusion of\n * specific path prefixes, the dominant real-world use case).\n *\n * # Field rationale\n *\n * **Why `policy` defaults to `'index'`**:\n * - The default for a public marketing site is \"let crawlers in\";\n * `'noindex'` is the deliberate exception (staging environments,\n * beta surfaces, sites still under construction). The opt-OUT\n * shape mirrors `WebsiteSitemapConfig.enabled` for consistency.\n * - `'noindex'` is expressed by a `<meta name=\"robots\" content=\"noindex\">`\n * on every page, NOT by `Disallow: /` in `robots.txt` (#494): a crawler\n * barred from fetching a page never reads its noindex, so the URL can\n * still be indexed bare. `robots.txt` stays allow-everything for it.\n *\n * **Why `disallow[]` is a path-prefix list (not a regex)**:\n * - The `robots.txt` spec only supports literal path prefixes\n * with `*` / `$` wildcards; Disallow is NOT a regex surface.\n * Typing it as `string[]` (validated to start with `/`) keeps\n * the contract honest and lets consumers paste from their CDN\n * allowlists without translation.\n *\n * **Why `allowedCrawlers` and `disallowedCrawlers` are mutually\n * exclusive**:\n * - The two fields are two halves of the SAME AI-crawler policy\n * decision. `allowedCrawlers` is the deny-by-default shape\n * (only the named bots may crawl); `disallowedCrawlers` is the\n * allow-by-default shape (every bot except the named ones may\n * crawl). Mixing both would emit a contradictory `robots.txt`\n * (a bot in both lists would receive conflicting directives).\n * A `superRefine` enforces the mutual-exclusion invariant at\n * parse time so the build fails fast.\n *\n * **Validation**:\n * - `disallow[]` entries must start with `/` (sitemap path-prefix\n * spec) — anything else would silently match nothing.\n * - `allowedCrawlers` / `disallowedCrawlers` entries must be\n * non-empty strings (a blank User-agent token would emit\n * `User-agent: ` which compliant crawlers ignore — silent\n * misconfig).\n * - `superRefine` rejects the combined-list anti-pattern.\n */\nconst _WebsiteRobotsConfigSchema = z\n .object({\n policy: z.enum(['index', 'noindex']).default('index'),\n disallow: z\n .array(\n z\n .string()\n .min(1)\n .refine((path) => path.startsWith('/'), {\n error: 'WebsiteRobotsConfig.disallow[] entries must start with \"/\"',\n }),\n )\n .optional(),\n /**\n * Deny-by-default AI-crawler allowlist. When set, the emitted\n * `robots.txt` lists each named bot under its own\n * `User-agent: <bot>\\nAllow: /` block, then closes with a\n * catch-all `User-agent: *\\nDisallow: /`. Mutually exclusive\n * with `disallowedCrawlers` (see `superRefine` below).\n */\n allowedCrawlers: z.array(z.string().min(1)).optional(),\n /**\n * Allow-by-default AI-crawler denylist. When set, the emitted\n * `robots.txt` lists each named bot under its own\n * `User-agent: <bot>\\nDisallow: /` block. Mutually exclusive\n * with `allowedCrawlers` (see `superRefine` below).\n */\n disallowedCrawlers: z.array(z.string().min(1)).optional(),\n })\n .superRefine((value, ctx) => {\n if (value.allowedCrawlers !== undefined && value.disallowedCrawlers !== undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['allowedCrawlers'],\n message:\n 'WebsiteRobotsConfig.allowedCrawlers and disallowedCrawlers are mutually exclusive — pick one policy direction (deny-by-default vs allow-by-default).',\n });\n }\n });\n\nexport const WebsiteRobotsConfigSchema: typeof _WebsiteRobotsConfigSchema =\n _WebsiteRobotsConfigSchema;\n\nexport type WebsiteRobotsConfig = z.infer<typeof _WebsiteRobotsConfigSchema>;\n/** @wildo_source:part:end saas.website.seo.robots-config */\n\n/**\n * @wildo_source:part:start saas.website.seo.llms-txt-config facet:layer:shared facet:family:website\n *\n * `WebsiteLlmsTxtConfig` — site-wide `llms.txt` publication policy.\n *\n * The framework owns only the SERVING surface; the CONTENT remains\n * hand-authored in `src/content/llms.txt`. This block therefore needs\n * exactly one knob:\n *\n * - `enabled?: boolean`\n * - `true` → publish when the file exists\n * - `false` → suppress publication even if the file exists\n * - `undefined` → auto-detect (`readLlmsTxtBody` publishes when the\n * file exists, returns `null` when absent)\n *\n * Keeping the \"auto-detect when undefined\" convention in the HELPER\n * (not as a schema default) is deliberate: schema defaults are static,\n * while file presence is runtime/build-time state.\n */\nconst _WebsiteLlmsTxtConfigSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const WebsiteLlmsTxtConfigSchema: typeof _WebsiteLlmsTxtConfigSchema =\n _WebsiteLlmsTxtConfigSchema;\n\nexport type WebsiteLlmsTxtConfig = z.infer<typeof _WebsiteLlmsTxtConfigSchema>;\n/** @wildo_source:part:end saas.website.seo.llms-txt-config */\n\n/**\n * @wildo_source:part:start saas.website.root-config.seo facet:layer:shared facet:family:website\n *\n * `WebsiteSeoConfig` — site-wide SEO/social-graph configuration\n * AUTHORED ON THE ROOT CONFIG.\n *\n * # Scope\n *\n * The public marketing-site origin belongs only in the per-service\n * `wildo.website.config.ts` declaration. Keep it out of this site-level SEO\n * block so the origin and localized routing have one source of truth.\n *\n * What stays on this block: site-level branding + hero-image\n * fallbacks that are co-authored with chrome labels and page\n * manifests respectively, NOT with the URL/i18n routing surface.\n *\n * # Field rationale\n *\n * **Why `siteName` is REQUIRED**:\n * - `og:site_name` is the brand label social platforms render\n * above the page title in shared previews. Without it, every\n * LinkedIn / Facebook / Slack preview shows the bare title with\n * no brand context.\n * - It is also the recommended `application-name` meta tag value\n * and a useful default for JSON-LD `Organization.name` schema\n * when the application ships structured data.\n *\n * **Why `defaultOgImageUrl` is OPTIONAL**:\n * - Each page's `WebsitePageManifest.metaLabelKeys.ogImageUrl`\n * takes precedence; the site-level default is a fallback for\n * pages that did not commit to a hero image.\n * - Marketing surfaces shipping no OG images at all (acceptable\n * for a placeholder build) should not be forced to author one.\n *\n * **Why `sitemap?` and `robots?` are OPTIONAL** (with sensible\n * defaults inside each sub-schema):\n * - The marketing-site default ships a public surface — every\n * page indexable, no AI-crawler restrictions, no per-path\n * disallow. Forcing every consumer to author both blocks just\n * to land in that default state would pollute the call site.\n * - Omitting either block at the call site is equivalent to\n * authoring `{}` — `WebsiteSitemapConfig.enabled` defaults to\n * `true`, `WebsiteRobotsConfig.policy` defaults to `'index'`.\n * The per-block field rationale lives on each sub-schema.\n * - `llmsTxt?` is similarly optional, but with a different default\n * mechanic: omitting the block means \"auto-detect from file\n * presence\" rather than \"use a static schema default\". The helper\n * `readLlmsTxtBody(...)` publishes when `src/content/llms.txt`\n * exists and returns `null` otherwise.\n *\n * **Validation**:\n * - `siteName` is `z.string().min(1)` — empty strings would emit\n * `<meta property=\"og:site_name\" content=\"\" />` which social\n * platforms render as a blank line.\n * - `defaultOgImageUrl` (when present) is `z.url()`.\n * - `sitemap` / `robots` validation lives on the sub-schemas.\n */\nconst _WebsiteSeoConfigSchema = z\n .object({\n siteName: z\n .string()\n .min(1, { error: 'WebsiteSeoConfig.siteName must be a non-empty string' }),\n defaultOgImageUrl: z\n .url({ error: 'WebsiteSeoConfig.defaultOgImageUrl must be a fully-qualified URL' })\n .optional(),\n /**\n * Sitemap policy block. Optional — omitting it is equivalent to\n * `{ enabled: true }` (the marketing-site default). See\n * `WebsiteSitemapConfigSchema` for field-by-field rationale.\n */\n sitemap: _WebsiteSitemapConfigSchema.optional(),\n /**\n * Robots.txt policy block. Optional — omitting it is equivalent\n * to `{ policy: 'index' }` (the marketing-site default). See\n * `WebsiteRobotsConfigSchema` for field-by-field rationale.\n */\n robots: _WebsiteRobotsConfigSchema.optional(),\n /**\n * `llms.txt` publication policy. Optional — omitting it means\n * \"auto-detect from file presence\". See\n * `WebsiteLlmsTxtConfigSchema` for the serving contract.\n */\n llmsTxt: _WebsiteLlmsTxtConfigSchema.optional(),\n });\n\nexport const WebsiteSeoConfigSchema: typeof _WebsiteSeoConfigSchema = _WebsiteSeoConfigSchema;\n\nexport type WebsiteSeoConfig = z.infer<typeof _WebsiteSeoConfigSchema>;\n/** @wildo_source:part:end saas.website.root-config.seo */\n\n/**\n * @wildo_source:part:start saas.website.root-config.navigation facet:layer:shared facet:family:website\n *\n * `WebsiteNavigationConfig` — site-wide header/footer slot bindings.\n *\n * The framework intentionally ships no concrete header or footer. The\n * application authors both as React components, registers them once here, and\n * the page layout renders the site-wide chrome around every page.\n *\n * Both fields are typed as `unknown` at the schema layer with the\n * `WebsiteNavigationConfig` interface narrowing them to\n * `ComponentType<unknown>` — same rationale as\n * `WebsiteSectionDefinition.Component` and `WebsitePageManifest.pageComponent`.\n */\nconst _WebsiteNavigationConfigSchema = z.object({\n headerComponent: z.unknown(),\n footerComponent: z.unknown(),\n});\n\nexport const WebsiteNavigationConfigSchema: typeof _WebsiteNavigationConfigSchema =\n _WebsiteNavigationConfigSchema;\n\nexport interface WebsiteNavigationConfig\n extends z.infer<typeof _WebsiteNavigationConfigSchema> {\n headerComponent: ComponentType<unknown>;\n footerComponent: ComponentType<unknown>;\n}\n/** @wildo_source:part:end saas.website.root-config.navigation */\n\n/**\n * @wildo_source:part:start saas.website.root-config.schema facet:layer:shared facet:family:website\n *\n * `WebsiteRootConfig` — the site-level configuration consumed by:\n * - the Astro adapter to drive i18n routing, page mounting,\n * and design-token CSS emission,\n * - the website audit command to validate label-pack\n * coverage and SEO structured-data conformance,\n * - the runtime `<WebsitePageLayout>` to render navigation\n * and apply design tokens.\n *\n * **Authoring convention**: each marketing-site service exports a single\n * `WEBSITE_ROOT_CONFIG: WebsiteRootConfig` from `src/website-root.config.ts`.\n * The Astro adapter and the audit command\n * both consume this canonical entry point; there is exactly one site\n * config per marketing site (no per-page or per-section overrides at\n * this level — overrides flow through the L0/L1/L1.5 component\n * registration hierarchy described in D6).\n *\n * # Scope\n *\n * This block carries site-level chrome and registry data only. Per-locale\n * routing (`locales` / `defaultLocale`) and the public origin belong in the\n * per-service `wildo.website.config.ts` declaration, avoiding duplicated\n * configuration that could diverge during site generation.\n *\n * **Field-by-field rationale**:\n *\n * - `pageManifests` — the registry of every page on the site. Indexed\n * downstream by `manifest.ref`. Refs MUST be unique (refinement\n * below).\n * - `designTokens` — see `WebsiteDesignTokensSchema`.\n * - `navigation` — see `WebsiteNavigationConfigSchema`.\n * - `chromeLabelKeys` — OPTIONAL. The set of fully-composed label keys\n * the site-wide chrome (header / footer / any global slot the\n * consumer renders outside a section context) reads via\n * `useWebsiteLabelByKey(...)`. Declared here, NOT in section\n * `expectedLabelKeys`, because chrome is rendered outside the\n * page+section context — `useWebsiteLabel(slot)` is structurally\n * unavailable to it. Threading these through the label-pack\n * validator (a) silences the always-on \"orphaned chrome key\" build\n * warnings and (b) lets the build fail fast when a chrome key is\n * missing from a locale pack, the same way section keys do.\n * Defaults to an empty array; sites with no chrome label reads\n * may omit the field entirely.\n * - `seo` — site-level branding + hero-image fallback (see\n * `WebsiteSeoConfigSchema`). The marketing-site origin USED to live\n * here as `seo.siteOrigin`; it now lives only on\n * `WildoMarketingWebsiteConfig.publicOrigin`.\n * - `skipToMainContentLabelKey` — see field JSDoc below.\n *\n * **Refinements**:\n * - `pageManifests[].ref` uniqueness — the registry is keyed by ref;\n * duplicates would silently overwrite each other in the lookup\n * table the bridge uses for cross-page links.\n * - `pageManifests[].routePath` uniqueness — two pages cannot mount\n * at the same path (Astro would error at build time, but we\n * surface it earlier here with a clearer diagnostic).\n * - `chromeLabelKeys` uniqueness — duplicates indicate copy-paste\n * bugs at the consumer's call site; surface them here.\n */\nconst _WebsiteRootConfigSchema = z\n .object({\n pageManifests: z.array(WebsitePageManifestSchema).min(1, {\n error: 'WebsiteRootConfig must declare at least one page manifest',\n }),\n designTokens: WebsiteDesignTokensSchema,\n navigation: _WebsiteNavigationConfigSchema,\n chromeLabelKeys: z.array(WebsiteLabelKeySchema).default([]),\n /**\n * Site-level branding + hero-image fallback used by the Astro\n * head renderer to compose `<meta property=\"og:site_name\">` and\n * the per-page OG image fallback. The marketing-site origin\n * (`siteOrigin`) USED to live here; it was moved to\n * `WildoMarketingWebsiteConfig.publicOrigin` in the seam-collapse\n * (single source of truth for \"what's the origin this site is\n * reachable at\").\n */\n seo: _WebsiteSeoConfigSchema,\n /**\n * Label key the framework reads to render the \"Skip to main\n * content\" link as the first focusable element on every page\n * Required because:\n * - Skip-to-main is a baseline keyboard-accessibility\n * affordance — WCAG 2.1 §2.4.1 \"Bypass Blocks\" (Level A)\n * mandates it. A marketing-site framework that omits it\n * fails AA for the entire surface; making the contract\n * opt-in invites that regression on every new consumer.\n * - The text is announced verbatim by assistive tech, so it\n * MUST be localized — a hardcoded English fallback would\n * speak \"Skip to main content\" to French users. Forcing the\n * key through the label-pack validation surface means the\n * dev server fails fast when the consumer forgets to add\n * the entry to a locale pack.\n * - The framework (NOT the consumer's chrome) owns rendering\n * the skip link because the chrome is itself the obstacle\n * the link bypasses — a chrome-side implementation would\n * either be inside the chrome (impossible to focus before\n * the chrome) or above it (creating a structural surface\n * the framework needs anyway).\n *\n * It is validated as a chrome key without being listed in\n * `chromeLabelKeys`: the label-pack loader adds it to the effective\n * chrome key set (`composeEffectiveChromeLabelKeys` in\n * `astro/label-pack-loader.ts`), so consumers cannot forget it.\n */\n skipToMainContentLabelKey: WebsiteLabelKeySchema,\n })\n .superRefine((config, ctx) => {\n const seenRefs = new Set<string>();\n const seenRoutePaths = new Set<string>();\n for (const [index, manifest] of config.pageManifests.entries()) {\n const refKey = manifest.ref as unknown as string;\n if (seenRefs.has(refKey)) {\n ctx.addIssue({\n code: 'custom',\n path: ['pageManifests', index, 'ref'],\n message: `Duplicate page ref \"${refKey}\" in WebsiteRootConfig.pageManifests`,\n });\n }\n seenRefs.add(refKey);\n\n const routeKey = manifest.routePath as unknown as string;\n if (seenRoutePaths.has(routeKey)) {\n ctx.addIssue({\n code: 'custom',\n path: ['pageManifests', index, 'routePath'],\n message: `Duplicate routePath \"${routeKey}\" in WebsiteRootConfig.pageManifests`,\n });\n }\n seenRoutePaths.add(routeKey);\n }\n\n /**\n * `chromeLabelKeys` uniqueness — a duplicate is harmless at\n * runtime (the validator de-dupes via a Set) but always indicates\n * a copy-paste bug at the consumer's call site. Surfacing it here\n * keeps the chrome contract tidy and matches the discipline applied\n * to `pageManifests[].ref` and `locales`.\n */\n const seenChromeKeys = new Set<string>();\n for (const [index, key] of config.chromeLabelKeys.entries()) {\n const keyStr = key as unknown as string;\n if (seenChromeKeys.has(keyStr)) {\n ctx.addIssue({\n code: 'custom',\n path: ['chromeLabelKeys', index],\n message: `Duplicate chrome label key \"${keyStr}\" in WebsiteRootConfig.chromeLabelKeys`,\n });\n }\n seenChromeKeys.add(keyStr);\n }\n });\n\nexport const WebsiteRootConfigSchema: typeof _WebsiteRootConfigSchema = _WebsiteRootConfigSchema;\n\n/**\n * Runtime shape of `WebsiteRootConfig`.\n *\n * Hand-written (NOT a bare `z.infer<typeof _WebsiteRootConfigSchema>`)\n * because `z.infer` walks the schema tree and would type\n * `pageManifests[number].pageComponent`, `navigation.headerComponent`\n * and `navigation.footerComponent` as `unknown` (their schema field\n * type), losing the `ComponentType<unknown>` narrowing carried by the\n * hand-written `WebsitePageManifest` and `WebsiteNavigationConfig`\n * interfaces. The layout primitives and the Astro helpers consume\n * `WebsiteRootConfig` and must see those\n * fields pre-narrowed; otherwise every consumer would need a local\n * `as ComponentType` cast at the JSX call site, defeating the type-safe\n * component contract.\n *\n * The `Omit` + intersection pattern keeps the rest of the inferred\n * shape (`designTokens`, `seo`, `chromeLabelKeys`,\n * `skipToMainContentLabelKey`) sourced directly from the schema.\n */\nexport type WebsiteRootConfig = Omit<\n z.infer<typeof _WebsiteRootConfigSchema>,\n 'pageManifests' | 'navigation'\n> & {\n pageManifests: WebsitePageManifest[];\n navigation: WebsiteNavigationConfig;\n};\n/** @wildo_source:part:end saas.website.root-config.schema */\n"]}
@@ -9,7 +9,8 @@ import type { WebsiteStructuredDataReturn } from '../structured-data/website-str
9
9
  * `core/define-website-section.tsx` module in Phase 2 because it carries
10
10
  * React types in its inputs). The shape is declared in `schemas/`
11
11
  * because it is the **observable contract** the page-manifest
12
- * registration, label-pack validator, and SEO dispatcher depend on.
12
+ * registration, the label-pack validator, `renderPageStructuredData` and the
13
+ * structured-data audit depend on.
13
14
  *
14
15
  * **Shape rationale** (each field carries semantic weight — see
15
16
  * `design-philosophy.mdc`):
@@ -19,7 +20,8 @@ import type { WebsiteStructuredDataReturn } from '../structured-data/website-str
19
20
  * (a section can be reused with the same `sectionRef` across pages
20
21
  * when its label-key root is intentionally shared).
21
22
  * - `category` — see `WebsiteSectionCategory` JSDoc; tags the section
22
- * for SEO-dispatch, skill-mapping, and analytics labelling.
23
+ * for skill-mapping and analytics labelling. It does NOT select the
24
+ * section's structured data.
23
25
  * - `expectedLabelKeys` — the **fully-composed** label keys the section
24
26
  * commits to consuming. The plan originally proposed leaf-relative
25
27
  * keys (`'headline'`) resolved at runtime against the
@@ -32,9 +34,13 @@ import type { WebsiteStructuredDataReturn } from '../structured-data/website-str
32
34
  * graph — `wildo website audit` can list a page's required keys
33
35
  * without booting React.
34
36
  * 3. The leaf-relative ergonomics (`useWebsiteLabel('headline')`)
35
- * survive in Phase 2 — the hook composes the leaf with the
36
- * page+section context and validates the result against
37
- * `expectedLabelKeys`.
37
+ * survive — the hook composes the leaf with the page+section
38
+ * context and looks the result up in the active pack. It does
39
+ * NOT check the key against `expectedLabelKeys`: a leaf the
40
+ * section never declared renders its key (with a console warning)
41
+ * and is caught by nothing until a reviewer reads the page. The
42
+ * build and `wildo website audit` check the pack against the
43
+ * DECLARED keys, so declare every slot the component reads.
38
44
  * - `Component` — the React component that renders the section. Typed
39
45
  * as `ComponentType<unknown>` here because schema-layer cannot lock
40
46
  * the component's prop shape (props vary per section). The factory
@@ -44,8 +50,9 @@ import type { WebsiteStructuredDataReturn } from '../structured-data/website-str
44
50
  * - `getStructuredData?` — optional pure function the SEO/AEO/GEO
45
51
  * builder library invokes at static-build time to materialize JSON-LD
46
52
  * for the section. The return type is intentionally `unknown` here;
47
- * the SEO library narrows by `category` (e.g. `PRICING` →
48
- * `Product | AggregateOffer`).
53
+ * nothing narrows it by `category` — the section returns whatever
54
+ * vocabulary it emits, and `wildo website audit` reconciles that
55
+ * against the application's Schema.org catalog.
49
56
  *
50
57
  * **Why not `z.custom<ComponentType>(…)`**: a `z.custom` predicate
51
58
  * cannot meaningfully validate "is a React component" beyond
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
@@ -10,7 +10,8 @@ import { WebsiteSectionCategorySchema } from './website-section-category.shared.
10
10
  * `core/define-website-section.tsx` module in Phase 2 because it carries
11
11
  * React types in its inputs). The shape is declared in `schemas/`
12
12
  * because it is the **observable contract** the page-manifest
13
- * registration, label-pack validator, and SEO dispatcher depend on.
13
+ * registration, the label-pack validator, `renderPageStructuredData` and the
14
+ * structured-data audit depend on.
14
15
  *
15
16
  * **Shape rationale** (each field carries semantic weight — see
16
17
  * `design-philosophy.mdc`):
@@ -20,7 +21,8 @@ import { WebsiteSectionCategorySchema } from './website-section-category.shared.
20
21
  * (a section can be reused with the same `sectionRef` across pages
21
22
  * when its label-key root is intentionally shared).
22
23
  * - `category` — see `WebsiteSectionCategory` JSDoc; tags the section
23
- * for SEO-dispatch, skill-mapping, and analytics labelling.
24
+ * for skill-mapping and analytics labelling. It does NOT select the
25
+ * section's structured data.
24
26
  * - `expectedLabelKeys` — the **fully-composed** label keys the section
25
27
  * commits to consuming. The plan originally proposed leaf-relative
26
28
  * keys (`'headline'`) resolved at runtime against the
@@ -33,9 +35,13 @@ import { WebsiteSectionCategorySchema } from './website-section-category.shared.
33
35
  * graph — `wildo website audit` can list a page's required keys
34
36
  * without booting React.
35
37
  * 3. The leaf-relative ergonomics (`useWebsiteLabel('headline')`)
36
- * survive in Phase 2 — the hook composes the leaf with the
37
- * page+section context and validates the result against
38
- * `expectedLabelKeys`.
38
+ * survive — the hook composes the leaf with the page+section
39
+ * context and looks the result up in the active pack. It does
40
+ * NOT check the key against `expectedLabelKeys`: a leaf the
41
+ * section never declared renders its key (with a console warning)
42
+ * and is caught by nothing until a reviewer reads the page. The
43
+ * build and `wildo website audit` check the pack against the
44
+ * DECLARED keys, so declare every slot the component reads.
39
45
  * - `Component` — the React component that renders the section. Typed
40
46
  * as `ComponentType<unknown>` here because schema-layer cannot lock
41
47
  * the component's prop shape (props vary per section). The factory
@@ -45,8 +51,9 @@ import { WebsiteSectionCategorySchema } from './website-section-category.shared.
45
51
  * - `getStructuredData?` — optional pure function the SEO/AEO/GEO
46
52
  * builder library invokes at static-build time to materialize JSON-LD
47
53
  * for the section. The return type is intentionally `unknown` here;
48
- * the SEO library narrows by `category` (e.g. `PRICING` →
49
- * `Product | AggregateOffer`).
54
+ * nothing narrows it by `category` — the section returns whatever
55
+ * vocabulary it emits, and `wildo website audit` reconciles that
56
+ * against the application's Schema.org catalog.
50
57
  *
51
58
  * **Why not `z.custom<ComponentType>(…)`**: a `z.custom` predicate
52
59
  * cannot meaningfully validate "is a React component" beyond
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;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, label-pack validator, and SEO dispatcher 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 SEO-dispatch, skill-mapping, and analytics labelling.\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 in Phase 2 — the hook composes the leaf with the\n * page+section context and validates the result against\n * `expectedLabelKeys`.\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 * the SEO library narrows by `category` (e.g. `PRICING` →\n * `Product | AggregateOffer`).\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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"]}
@@ -0,0 +1,57 @@
1
+ import { type PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';
2
+ import type { WebsiteStructuredDataPayload } from './website-structured-data.shared.schemas';
3
+ /**
4
+ * @wildo_source:part:start saas.website.pricing-offer-structured-data facet:layer:core facet:family:website facet:audience:app-developer
5
+ *
6
+ * `buildWebsitePricingOfferStructuredData` — the `AggregateOffer` a pricing page publishes, DERIVED from
7
+ * the application's public pricing catalogue (#1835) instead of typed by hand beside it.
8
+ *
9
+ * ## What it publishes, and what it refuses to
10
+ *
11
+ * A search engine shows an offer's `price` as the price of the offer, so an offer is published only
12
+ * when that is TRUE:
13
+ *
14
+ * - the plan is on sale (`availability: AVAILABLE`) — an upcoming plan cannot be bought;
15
+ * - its price is `LISTED` — a starting price is a floor, and an on-request plan has no price;
16
+ * - its published price states the whole offer for its unit: a flat or per-unit amount, never an
17
+ * indicative amount of a tiered, graduated or package price.
18
+ *
19
+ * Each published offer uses the plan's DEFAULT price (the one the catalogue marks) and carries a
20
+ * `UnitPriceSpecification` saying what the number is FOR: its recurrence as `billingDuration` (ISO 8601
21
+ * `P1M` / `P1Y`) and, for a per-unit price, a `referenceQuantity` of one of the page's unit (`member`)
22
+ * — so "$4 per member per month" is never read as "$4".
23
+ *
24
+ * One `AggregateOffer` per (currency, amount basis, recurrence): a range is only meaningful over prices
25
+ * that measure the same thing, so a flat price and a per-member price, or a monthly and a yearly one, are
26
+ * never folded into one `lowPrice` / `highPrice`. EVERY group's `@id` is `aggregateOfferIdPrefix` suffixed with
27
+ * what distinguishes it (`-per-unit-1-month-usd`), so an aggregate's identity depends only on what it
28
+ * aggregates: adding, removing or reordering a plan in ANOTHER group never moves it. Returns `[]` when
29
+ * nothing may be published — a page with only on-request plans publishes no price at all, which is the
30
+ * true statement.
31
+ */
32
+ export interface BuildWebsitePricingOfferStructuredDataInput {
33
+ readonly catalog: PublicPricingCatalog;
34
+ /** The `@id` of the entity the offers price — typically the page's `SoftwareApplication`. */
35
+ readonly itemOfferedId: string;
36
+ /**
37
+ * The prefix of every `AggregateOffer` `@id` (`https://…/pricing#plans`); each group appends what it
38
+ * aggregates, e.g. `#plans-flat-1-month-usd`.
39
+ */
40
+ readonly aggregateOfferIdPrefix: string;
41
+ /** The URL of the page the offers are made on; each offer's URL is this plus `#plan-<key>`. */
42
+ readonly url: string;
43
+ /**
44
+ * The plan's name in the page's locale. Names are text a person reads, so they come from the page's
45
+ * label pack; a plan the callback does not name is published without one rather than with its key.
46
+ */
47
+ readonly planName?: (planKey: string) => string | undefined;
48
+ /**
49
+ * What ONE unit of a per-unit price is, in the page's locale (`member`, `seat`) — published as the
50
+ * offer's `referenceQuantity.unitText`. Required when any published offer is per-unit: omitting it
51
+ * would state a per-member price as a whole price, so the builder refuses instead.
52
+ */
53
+ readonly perUnitName?: string;
54
+ }
55
+ export declare function buildWebsitePricingOfferStructuredData(input: BuildWebsitePricingOfferStructuredDataInput): readonly WebsiteStructuredDataPayload[];
56
+ /** @wildo_source:part:end saas.website.pricing-offer-structured-data */
57
+ //# sourceMappingURL=pricing-offer-structured-data.shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing-offer-structured-data.shared.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/structured-data/pricing-offer-structured-data.shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,oBAAoB,EAG1B,MAAM,sCAAsC,CAAC;AAE9C,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AAE7F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,2CAA2C;IAC1D,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACvC,6FAA6F;IAC7F,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,+FAA+F;IAC/F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC5D;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,sCAAsC,CAAC,KAAK,EAAE,2CAA2C,GAAG,SAAS,4BAA4B,EAAE,CAmDlJ;AA0BD,wEAAwE"}
@@ -0,0 +1,76 @@
1
+ import { ProductAvailability, ProductPriceDisclosure, PublicPricingAmountBasis, PublicPricingRecurrenceUnit, publicPricingAmountDecimal, } from '@wildo-ai/saas-models/public-runtime';
2
+ export function buildWebsitePricingOfferStructuredData(input) {
3
+ const published = input.catalog.plans.flatMap((plan) => {
4
+ const price = publishablePrice(plan);
5
+ return price === undefined ? [] : [{ plan, price }];
6
+ });
7
+ if (published.some(({ price }) => price.amountBasis === PublicPricingAmountBasis.PER_UNIT) && !input.perUnitName) {
8
+ throw new Error('[buildWebsitePricingOfferStructuredData] a published plan is priced per unit, so the page must name the unit '
9
+ + '(`perUnitName`, e.g. "member"); without it the offer would state a per-unit price as the whole price.');
10
+ }
11
+ const groupKeyOf = (price) => `${price.currency}|${price.amountBasis}|${price.recurrence.unit}|${price.recurrence.count}`;
12
+ const groupKeys = [...new Set(published.map(({ price }) => groupKeyOf(price)))];
13
+ return groupKeys.map((groupKey) => {
14
+ const offers = published.filter(({ price }) => groupKeyOf(price) === groupKey);
15
+ const sample = offers[0].price;
16
+ const amounts = offers.map(({ price }) => publicPricingAmountDecimal(price));
17
+ const numeric = amounts.map(Number);
18
+ return {
19
+ '@type': 'AggregateOffer',
20
+ '@id': `${input.aggregateOfferIdPrefix}-${aggregateSuffix(sample)}`,
21
+ itemOffered: { '@id': input.itemOfferedId },
22
+ priceCurrency: sample.currency,
23
+ lowPrice: amounts[numeric.indexOf(Math.min(...numeric))],
24
+ highPrice: amounts[numeric.indexOf(Math.max(...numeric))],
25
+ offerCount: offers.length,
26
+ url: input.url,
27
+ offers: offers.map(({ plan, price }) => {
28
+ const name = input.planName?.(plan.key);
29
+ const amount = publicPricingAmountDecimal(price);
30
+ return {
31
+ '@type': 'Offer',
32
+ ...(name === undefined ? {} : { name }),
33
+ price: amount,
34
+ priceCurrency: price.currency,
35
+ availability: 'https://schema.org/InStock',
36
+ url: `${input.url}#plan-${plan.key}`,
37
+ priceSpecification: {
38
+ '@type': 'UnitPriceSpecification',
39
+ price: amount,
40
+ priceCurrency: price.currency,
41
+ ...(price.recurrence.unit === PublicPricingRecurrenceUnit.ONE_TIME ? {} : { billingDuration: isoDurationOf(price) }),
42
+ ...(price.amountBasis === PublicPricingAmountBasis.PER_UNIT
43
+ ? { referenceQuantity: { '@type': 'QuantitativeValue', value: 1, unitText: input.perUnitName } }
44
+ : {}),
45
+ },
46
+ };
47
+ }),
48
+ };
49
+ });
50
+ }
51
+ /** What distinguishes an aggregate's `@id`: its basis, its recurrence and its currency — exactly its group key. */
52
+ function aggregateSuffix(price) {
53
+ return `${price.amountBasis}-${price.recurrence.count}-${price.recurrence.unit}-${price.currency}`.toLowerCase().replace(/_/gu, '-');
54
+ }
55
+ /** The plan's default price, when publishing it as the offer's price would be true. */
56
+ function publishablePrice(plan) {
57
+ if (plan.availability !== ProductAvailability.AVAILABLE)
58
+ return undefined;
59
+ if (plan.priceDisclosure !== ProductPriceDisclosure.LISTED)
60
+ return undefined;
61
+ const price = plan.prices.find((candidate) => candidate.isDefault) ?? plan.prices[0];
62
+ if (price === undefined || price.amountBasis === PublicPricingAmountBasis.VARIABLE)
63
+ return undefined;
64
+ return price;
65
+ }
66
+ /** Total over the recurring units: a member added to the vocabulary is a compile error here until it is mapped. */
67
+ const ISO_DURATION_DESIGNATOR = {
68
+ [PublicPricingRecurrenceUnit.MONTH]: 'M',
69
+ [PublicPricingRecurrenceUnit.YEAR]: 'Y',
70
+ };
71
+ function isoDurationOf(price) {
72
+ const unit = price.recurrence.unit;
73
+ return `P${price.recurrence.count}${ISO_DURATION_DESIGNATOR[unit]}`;
74
+ }
75
+ /** @wildo_source:part:end saas.website.pricing-offer-structured-data */
76
+ //# sourceMappingURL=pricing-offer-structured-data.shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing-offer-structured-data.shared.js","sourceRoot":"","sources":["../../../../../src/schemas/structured-data/pricing-offer-structured-data.shared.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ProductAvailability,\n ProductPriceDisclosure,\n PublicPricingAmountBasis,\n PublicPricingRecurrenceUnit,\n publicPricingAmountDecimal,\n type PublicPricingCatalog,\n type PublicPricingPlan,\n type PublicPricingPrice,\n} from '@wildo-ai/saas-models/public-runtime';\n\nimport type { WebsiteStructuredDataPayload } from './website-structured-data.shared.schemas';\n\n/**\n * @wildo_source:part:start saas.website.pricing-offer-structured-data facet:layer:core facet:family:website facet:audience:app-developer\n *\n * `buildWebsitePricingOfferStructuredData` — the `AggregateOffer` a pricing page publishes, DERIVED from\n * the application's public pricing catalogue (#1835) instead of typed by hand beside it.\n *\n * ## What it publishes, and what it refuses to\n *\n * A search engine shows an offer's `price` as the price of the offer, so an offer is published only\n * when that is TRUE:\n *\n * - the plan is on sale (`availability: AVAILABLE`) — an upcoming plan cannot be bought;\n * - its price is `LISTED` — a starting price is a floor, and an on-request plan has no price;\n * - its published price states the whole offer for its unit: a flat or per-unit amount, never an\n * indicative amount of a tiered, graduated or package price.\n *\n * Each published offer uses the plan's DEFAULT price (the one the catalogue marks) and carries a\n * `UnitPriceSpecification` saying what the number is FOR: its recurrence as `billingDuration` (ISO 8601\n * `P1M` / `P1Y`) and, for a per-unit price, a `referenceQuantity` of one of the page's unit (`member`)\n * — so \"$4 per member per month\" is never read as \"$4\".\n *\n * One `AggregateOffer` per (currency, amount basis, recurrence): a range is only meaningful over prices\n * that measure the same thing, so a flat price and a per-member price, or a monthly and a yearly one, are\n * never folded into one `lowPrice` / `highPrice`. EVERY group's `@id` is `aggregateOfferIdPrefix` suffixed with\n * what distinguishes it (`-per-unit-1-month-usd`), so an aggregate's identity depends only on what it\n * aggregates: adding, removing or reordering a plan in ANOTHER group never moves it. Returns `[]` when\n * nothing may be published — a page with only on-request plans publishes no price at all, which is the\n * true statement.\n */\nexport interface BuildWebsitePricingOfferStructuredDataInput {\n readonly catalog: PublicPricingCatalog;\n /** The `@id` of the entity the offers price — typically the page's `SoftwareApplication`. */\n readonly itemOfferedId: string;\n /**\n * The prefix of every `AggregateOffer` `@id` (`https://…/pricing#plans`); each group appends what it\n * aggregates, e.g. `#plans-flat-1-month-usd`.\n */\n readonly aggregateOfferIdPrefix: string;\n /** The URL of the page the offers are made on; each offer's URL is this plus `#plan-<key>`. */\n readonly url: string;\n /**\n * The plan's name in the page's locale. Names are text a person reads, so they come from the page's\n * label pack; a plan the callback does not name is published without one rather than with its key.\n */\n readonly planName?: (planKey: string) => string | undefined;\n /**\n * What ONE unit of a per-unit price is, in the page's locale (`member`, `seat`) — published as the\n * offer's `referenceQuantity.unitText`. Required when any published offer is per-unit: omitting it\n * would state a per-member price as a whole price, so the builder refuses instead.\n */\n readonly perUnitName?: string;\n}\n\nexport function buildWebsitePricingOfferStructuredData(input: BuildWebsitePricingOfferStructuredDataInput): readonly WebsiteStructuredDataPayload[] {\n const published = input.catalog.plans.flatMap((plan) => {\n const price = publishablePrice(plan);\n return price === undefined ? [] : [{ plan, price }];\n });\n if (published.some(({ price }) => price.amountBasis === PublicPricingAmountBasis.PER_UNIT) && !input.perUnitName) {\n throw new Error(\n '[buildWebsitePricingOfferStructuredData] a published plan is priced per unit, so the page must name the unit '\n + '(`perUnitName`, e.g. \"member\"); without it the offer would state a per-unit price as the whole price.',\n );\n }\n const groupKeyOf = (price: PublicPricingPrice): string =>\n `${price.currency}|${price.amountBasis}|${price.recurrence.unit}|${price.recurrence.count}`;\n const groupKeys = [...new Set(published.map(({ price }) => groupKeyOf(price)))];\n return groupKeys.map((groupKey) => {\n const offers = published.filter(({ price }) => groupKeyOf(price) === groupKey);\n const sample = offers[0]!.price;\n const amounts = offers.map(({ price }) => publicPricingAmountDecimal(price));\n const numeric = amounts.map(Number);\n return {\n '@type': 'AggregateOffer',\n '@id': `${input.aggregateOfferIdPrefix}-${aggregateSuffix(sample)}`,\n itemOffered: { '@id': input.itemOfferedId },\n priceCurrency: sample.currency,\n lowPrice: amounts[numeric.indexOf(Math.min(...numeric))],\n highPrice: amounts[numeric.indexOf(Math.max(...numeric))],\n offerCount: offers.length,\n url: input.url,\n offers: offers.map(({ plan, price }) => {\n const name = input.planName?.(plan.key);\n const amount = publicPricingAmountDecimal(price);\n return {\n '@type': 'Offer',\n ...(name === undefined ? {} : { name }),\n price: amount,\n priceCurrency: price.currency,\n availability: 'https://schema.org/InStock',\n url: `${input.url}#plan-${plan.key}`,\n priceSpecification: {\n '@type': 'UnitPriceSpecification',\n price: amount,\n priceCurrency: price.currency,\n ...(price.recurrence.unit === PublicPricingRecurrenceUnit.ONE_TIME ? {} : { billingDuration: isoDurationOf(price) }),\n ...(price.amountBasis === PublicPricingAmountBasis.PER_UNIT\n ? { referenceQuantity: { '@type': 'QuantitativeValue', value: 1, unitText: input.perUnitName } }\n : {}),\n },\n };\n }),\n };\n });\n}\n\n/** What distinguishes an aggregate's `@id`: its basis, its recurrence and its currency — exactly its group key. */\nfunction aggregateSuffix(price: PublicPricingPrice): string {\n return `${price.amountBasis}-${price.recurrence.count}-${price.recurrence.unit}-${price.currency}`.toLowerCase().replace(/_/gu, '-');\n}\n\n/** The plan's default price, when publishing it as the offer's price would be true. */\nfunction publishablePrice(plan: PublicPricingPlan): PublicPricingPrice | undefined {\n if (plan.availability !== ProductAvailability.AVAILABLE) return undefined;\n if (plan.priceDisclosure !== ProductPriceDisclosure.LISTED) return undefined;\n const price = plan.prices.find((candidate) => candidate.isDefault) ?? plan.prices[0];\n if (price === undefined || price.amountBasis === PublicPricingAmountBasis.VARIABLE) return undefined;\n return price;\n}\n\n/** Total over the recurring units: a member added to the vocabulary is a compile error here until it is mapped. */\nconst ISO_DURATION_DESIGNATOR: Record<Exclude<PublicPricingRecurrenceUnit, PublicPricingRecurrenceUnit.ONE_TIME>, string> = {\n [PublicPricingRecurrenceUnit.MONTH]: 'M',\n [PublicPricingRecurrenceUnit.YEAR]: 'Y',\n};\n\nfunction isoDurationOf(price: PublicPricingPrice): string {\n const unit = price.recurrence.unit as Exclude<PublicPricingRecurrenceUnit, PublicPricingRecurrenceUnit.ONE_TIME>;\n return `P${price.recurrence.count}${ISO_DURATION_DESIGNATOR[unit]}`;\n}\n/** @wildo_source:part:end saas.website.pricing-offer-structured-data */\n"]}
@@ -80,6 +80,27 @@ export interface StructuredDataCrossEntityReferenceDriftFinding {
80
80
  message: string;
81
81
  }
82
82
  export type StructuredDataReconciliationFinding = StructuredDataCatalogOverrideFinding | StructuredDataCatalogMissFinding | StructuredDataSpecViolationFinding | StructuredDataDeprecatedFieldFinding | StructuredDataCrossEntityReferenceDriftFinding;
83
+ /**
84
+ * An OPTIONAL field a payload leaves out, reported because its catalog entry opted in with
85
+ * `recommendOptionals: true` — the entry's statement that for this type the optional fields are
86
+ * where the richer search and answer-engine results come from.
87
+ *
88
+ * Deliberately NOT a finding. A finding says the payload is wrong (a missing required field, an
89
+ * unknown type, a deprecated field) and `--strict` escalates every finding to a failure. A payload
90
+ * without its optional fields is valid; reporting it through the same channel would fail strict CI for
91
+ * following the vocabulary. So recommendations travel beside the findings, are shown as information,
92
+ * and never change an audit's outcome.
93
+ */
94
+ export interface StructuredDataOptionalFieldRecommendation {
95
+ pageRef: string;
96
+ routePath: string;
97
+ sectionRef: string;
98
+ entityType: string;
99
+ entityId?: string;
100
+ /** The entry's optional fields this payload does not carry, in catalog order. */
101
+ missingOptionalFields: readonly string[];
102
+ message: string;
103
+ }
83
104
  export interface StructuredDataReconciliationPageResult {
84
105
  pageRef: string;
85
106
  routePath: string;
@@ -88,6 +109,8 @@ export interface StructuredDataReconciliationPageResult {
88
109
  }
89
110
  export interface StructuredDataReconciliationResult {
90
111
  findings: StructuredDataReconciliationFinding[];
112
+ /** Optional-field recommendations from entries that opted in — information, never a finding. */
113
+ recommendations: StructuredDataOptionalFieldRecommendation[];
91
114
  pages: StructuredDataReconciliationPageResult[];
92
115
  }
93
116
  export interface ReconcileStructuredDataOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"structured-data-reconciliation.shared.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/structured-data/structured-data-reconciliation.shared.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"structured-data-reconciliation.shared.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/structured-data/structured-data-reconciliation.shared.ts"],"names":[],"mappings":"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"}