@wildo-ai/saas-website 1.1.1

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 (383) hide show
  1. package/LICENSE +34 -0
  2. package/dist/esm/.builder.pid +9 -0
  3. package/dist/esm/astro/blog-post-bridge.d.ts +324 -0
  4. package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -0
  5. package/dist/esm/astro/blog-post-bridge.js +57 -0
  6. package/dist/esm/astro/blog-post-bridge.js.map +1 -0
  7. package/dist/esm/astro/blog-post-head.renderer.d.ts +129 -0
  8. package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -0
  9. package/dist/esm/astro/blog-post-head.renderer.js +139 -0
  10. package/dist/esm/astro/blog-post-head.renderer.js.map +1 -0
  11. package/dist/esm/astro/bridge-runtime.d.ts +224 -0
  12. package/dist/esm/astro/bridge-runtime.d.ts.map +1 -0
  13. package/dist/esm/astro/bridge-runtime.js +30 -0
  14. package/dist/esm/astro/bridge-runtime.js.map +1 -0
  15. package/dist/esm/astro/collect-expected-label-keys.d.ts +65 -0
  16. package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -0
  17. package/dist/esm/astro/collect-expected-label-keys.js +101 -0
  18. package/dist/esm/astro/collect-expected-label-keys.js.map +1 -0
  19. package/dist/esm/astro/headers-renderer.d.ts +32 -0
  20. package/dist/esm/astro/headers-renderer.d.ts.map +1 -0
  21. package/dist/esm/astro/headers-renderer.js +64 -0
  22. package/dist/esm/astro/headers-renderer.js.map +1 -0
  23. package/dist/esm/astro/i18n-routing.helper.d.ts +91 -0
  24. package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -0
  25. package/dist/esm/astro/i18n-routing.helper.js +23 -0
  26. package/dist/esm/astro/i18n-routing.helper.js.map +1 -0
  27. package/dist/esm/astro/internal/html-escape.d.ts +31 -0
  28. package/dist/esm/astro/internal/html-escape.d.ts.map +1 -0
  29. package/dist/esm/astro/internal/html-escape.js +40 -0
  30. package/dist/esm/astro/internal/html-escape.js.map +1 -0
  31. package/dist/esm/astro/label-pack-loader.d.ts +134 -0
  32. package/dist/esm/astro/label-pack-loader.d.ts.map +1 -0
  33. package/dist/esm/astro/label-pack-loader.js +298 -0
  34. package/dist/esm/astro/label-pack-loader.js.map +1 -0
  35. package/dist/esm/astro/llms-txt-server.d.ts +28 -0
  36. package/dist/esm/astro/llms-txt-server.d.ts.map +1 -0
  37. package/dist/esm/astro/llms-txt-server.js +33 -0
  38. package/dist/esm/astro/llms-txt-server.js.map +1 -0
  39. package/dist/esm/astro/pick-labels-for-page.d.ts +63 -0
  40. package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -0
  41. package/dist/esm/astro/pick-labels-for-page.js +73 -0
  42. package/dist/esm/astro/pick-labels-for-page.js.map +1 -0
  43. package/dist/esm/astro/robots-renderer.d.ts +85 -0
  44. package/dist/esm/astro/robots-renderer.d.ts.map +1 -0
  45. package/dist/esm/astro/robots-renderer.js +157 -0
  46. package/dist/esm/astro/robots-renderer.js.map +1 -0
  47. package/dist/esm/astro/sitemap-coverage.d.ts +101 -0
  48. package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -0
  49. package/dist/esm/astro/sitemap-coverage.js +67 -0
  50. package/dist/esm/astro/sitemap-coverage.js.map +1 -0
  51. package/dist/esm/astro/structured-data.renderer.d.ts +21 -0
  52. package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -0
  53. package/dist/esm/astro/structured-data.renderer.js +142 -0
  54. package/dist/esm/astro/structured-data.renderer.js.map +1 -0
  55. package/dist/esm/astro/website-page-head.renderer.d.ts +93 -0
  56. package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -0
  57. package/dist/esm/astro/website-page-head.renderer.js +184 -0
  58. package/dist/esm/astro/website-page-head.renderer.js.map +1 -0
  59. package/dist/esm/astro/website-page-runtime.helper.d.ts +112 -0
  60. package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -0
  61. package/dist/esm/astro/website-page-runtime.helper.js +60 -0
  62. package/dist/esm/astro/website-page-runtime.helper.js.map +1 -0
  63. package/dist/esm/astro/website-site-context.d.ts +126 -0
  64. package/dist/esm/astro/website-site-context.d.ts.map +1 -0
  65. package/dist/esm/astro/website-site-context.js +37 -0
  66. package/dist/esm/astro/website-site-context.js.map +1 -0
  67. package/dist/esm/astro-island.d.ts +56 -0
  68. package/dist/esm/astro-island.d.ts.map +1 -0
  69. package/dist/esm/astro-island.js +61 -0
  70. package/dist/esm/astro-island.js.map +1 -0
  71. package/dist/esm/astro.d.ts +151 -0
  72. package/dist/esm/astro.d.ts.map +1 -0
  73. package/dist/esm/astro.js +151 -0
  74. package/dist/esm/astro.js.map +1 -0
  75. package/dist/esm/companion-exports.d.ts +38 -0
  76. package/dist/esm/companion-exports.d.ts.map +1 -0
  77. package/dist/esm/companion-exports.js +38 -0
  78. package/dist/esm/companion-exports.js.map +1 -0
  79. package/dist/esm/components/low-level/WebsiteButton.d.ts +73 -0
  80. package/dist/esm/components/low-level/WebsiteButton.d.ts.map +1 -0
  81. package/dist/esm/components/low-level/WebsiteButton.js +68 -0
  82. package/dist/esm/components/low-level/WebsiteButton.js.map +1 -0
  83. package/dist/esm/components/low-level/WebsiteHeading.d.ts +78 -0
  84. package/dist/esm/components/low-level/WebsiteHeading.d.ts.map +1 -0
  85. package/dist/esm/components/low-level/WebsiteHeading.js +41 -0
  86. package/dist/esm/components/low-level/WebsiteHeading.js.map +1 -0
  87. package/dist/esm/components/low-level/WebsiteImage.d.ts +73 -0
  88. package/dist/esm/components/low-level/WebsiteImage.d.ts.map +1 -0
  89. package/dist/esm/components/low-level/WebsiteImage.js +21 -0
  90. package/dist/esm/components/low-level/WebsiteImage.js.map +1 -0
  91. package/dist/esm/components/low-level/WebsiteInternalButton.d.ts +90 -0
  92. package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -0
  93. package/dist/esm/components/low-level/WebsiteInternalButton.js +47 -0
  94. package/dist/esm/components/low-level/WebsiteInternalButton.js.map +1 -0
  95. package/dist/esm/components/low-level/WebsiteInternalLink.d.ts +99 -0
  96. package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -0
  97. package/dist/esm/components/low-level/WebsiteInternalLink.js +68 -0
  98. package/dist/esm/components/low-level/WebsiteInternalLink.js.map +1 -0
  99. package/dist/esm/components/low-level/WebsiteLink.d.ts +50 -0
  100. package/dist/esm/components/low-level/WebsiteLink.d.ts.map +1 -0
  101. package/dist/esm/components/low-level/WebsiteLink.js +22 -0
  102. package/dist/esm/components/low-level/WebsiteLink.js.map +1 -0
  103. package/dist/esm/components/low-level/WebsiteText.d.ts +57 -0
  104. package/dist/esm/components/low-level/WebsiteText.d.ts.map +1 -0
  105. package/dist/esm/components/low-level/WebsiteText.js +57 -0
  106. package/dist/esm/components/low-level/WebsiteText.js.map +1 -0
  107. package/dist/esm/config/define-website-config.d.ts +83 -0
  108. package/dist/esm/config/define-website-config.d.ts.map +1 -0
  109. package/dist/esm/config/define-website-config.js +126 -0
  110. package/dist/esm/config/define-website-config.js.map +1 -0
  111. package/dist/esm/config/index.d.ts +38 -0
  112. package/dist/esm/config/index.d.ts.map +1 -0
  113. package/dist/esm/config/index.js +38 -0
  114. package/dist/esm/config/index.js.map +1 -0
  115. package/dist/esm/config/load-website-config.d.ts +136 -0
  116. package/dist/esm/config/load-website-config.d.ts.map +1 -0
  117. package/dist/esm/config/load-website-config.js +177 -0
  118. package/dist/esm/config/load-website-config.js.map +1 -0
  119. package/dist/esm/config/wildo-website-config.schemas.d.ts +251 -0
  120. package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -0
  121. package/dist/esm/config/wildo-website-config.schemas.js +302 -0
  122. package/dist/esm/config/wildo-website-config.schemas.js.map +1 -0
  123. package/dist/esm/config-loader.d.ts +35 -0
  124. package/dist/esm/config-loader.d.ts.map +1 -0
  125. package/dist/esm/config-loader.js +35 -0
  126. package/dist/esm/config-loader.js.map +1 -0
  127. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +38 -0
  128. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -0
  129. package/dist/esm/core/anonymous-session/InboundContactForm.js +77 -0
  130. package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -0
  131. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +41 -0
  132. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -0
  133. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js +58 -0
  134. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -0
  135. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts +81 -0
  136. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -0
  137. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js +177 -0
  138. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -0
  139. package/dist/esm/core/contexts/WebsitePageContext.d.ts +40 -0
  140. package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -0
  141. package/dist/esm/core/contexts/WebsitePageContext.js +8 -0
  142. package/dist/esm/core/contexts/WebsitePageContext.js.map +1 -0
  143. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +162 -0
  144. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -0
  145. package/dist/esm/core/contexts/WebsiteRuntimeContext.js +22 -0
  146. package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -0
  147. package/dist/esm/core/contexts/WebsiteSectionContext.d.ts +47 -0
  148. package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -0
  149. package/dist/esm/core/contexts/WebsiteSectionContext.js +8 -0
  150. package/dist/esm/core/contexts/WebsiteSectionContext.js.map +1 -0
  151. package/dist/esm/core/contexts/useWebsitePage.d.ts +13 -0
  152. package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -0
  153. package/dist/esm/core/contexts/useWebsitePage.js +22 -0
  154. package/dist/esm/core/contexts/useWebsitePage.js.map +1 -0
  155. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +19 -0
  156. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -0
  157. package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -0
  158. package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -0
  159. package/dist/esm/core/contexts/useWebsiteSection.d.ts +12 -0
  160. package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -0
  161. package/dist/esm/core/contexts/useWebsiteSection.js +22 -0
  162. package/dist/esm/core/contexts/useWebsiteSection.js.map +1 -0
  163. package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts +17 -0
  164. package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -0
  165. package/dist/esm/core/external-providers/frontend-provider-registry.website.js +23 -0
  166. package/dist/esm/core/external-providers/frontend-provider-registry.website.js.map +1 -0
  167. package/dist/esm/core/factories/define-website-page-manifest.d.ts +70 -0
  168. package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -0
  169. package/dist/esm/core/factories/define-website-page-manifest.js +51 -0
  170. package/dist/esm/core/factories/define-website-page-manifest.js.map +1 -0
  171. package/dist/esm/core/factories/define-website-section.d.ts +101 -0
  172. package/dist/esm/core/factories/define-website-section.d.ts.map +1 -0
  173. package/dist/esm/core/factories/define-website-section.js +71 -0
  174. package/dist/esm/core/factories/define-website-section.js.map +1 -0
  175. package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts +23 -0
  176. package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -0
  177. package/dist/esm/core/hooks/useWebsiteDesignTokens.js +25 -0
  178. package/dist/esm/core/hooks/useWebsiteDesignTokens.js.map +1 -0
  179. package/dist/esm/core/hooks/useWebsiteLabel.d.ts +68 -0
  180. package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -0
  181. package/dist/esm/core/hooks/useWebsiteLabel.js +103 -0
  182. package/dist/esm/core/hooks/useWebsiteLabel.js.map +1 -0
  183. package/dist/esm/core/layouts/WebsitePageLayout.d.ts +87 -0
  184. package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -0
  185. package/dist/esm/core/layouts/WebsitePageLayout.js +170 -0
  186. package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -0
  187. package/dist/esm/core/layouts/WebsiteSection.d.ts +91 -0
  188. package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -0
  189. package/dist/esm/core/layouts/WebsiteSection.js +26 -0
  190. package/dist/esm/core/layouts/WebsiteSection.js.map +1 -0
  191. package/dist/esm/core/routing/internal-routing.utils.d.ts +65 -0
  192. package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -0
  193. package/dist/esm/core/routing/internal-routing.utils.js +14 -0
  194. package/dist/esm/core/routing/internal-routing.utils.js.map +1 -0
  195. package/dist/esm/index.d.ts +129 -0
  196. package/dist/esm/index.d.ts.map +1 -0
  197. package/dist/esm/index.js +129 -0
  198. package/dist/esm/index.js.map +1 -0
  199. package/dist/esm/mdx/BlogPost.d.ts +193 -0
  200. package/dist/esm/mdx/BlogPost.d.ts.map +1 -0
  201. package/dist/esm/mdx/BlogPost.js +161 -0
  202. package/dist/esm/mdx/BlogPost.js.map +1 -0
  203. package/dist/esm/mdx/blog-post-collection.config.d.ts +139 -0
  204. package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -0
  205. package/dist/esm/mdx/blog-post-collection.config.js +27 -0
  206. package/dist/esm/mdx/blog-post-collection.config.js.map +1 -0
  207. package/dist/esm/mdx/blog-post-frontmatter-source.d.ts +12 -0
  208. package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -0
  209. package/dist/esm/mdx/blog-post-frontmatter-source.js +13 -0
  210. package/dist/esm/mdx/blog-post-frontmatter-source.js.map +1 -0
  211. package/dist/esm/mdx/index.d.ts +22 -0
  212. package/dist/esm/mdx/index.d.ts.map +1 -0
  213. package/dist/esm/mdx/index.js +22 -0
  214. package/dist/esm/mdx/index.js.map +1 -0
  215. package/dist/esm/mdx/website-mdx-provider.d.ts +90 -0
  216. package/dist/esm/mdx/website-mdx-provider.d.ts.map +1 -0
  217. package/dist/esm/mdx/website-mdx-provider.js +8 -0
  218. package/dist/esm/mdx/website-mdx-provider.js.map +1 -0
  219. package/dist/esm/mdx.d.ts +38 -0
  220. package/dist/esm/mdx.d.ts.map +1 -0
  221. package/dist/esm/mdx.js +38 -0
  222. package/dist/esm/mdx.js.map +1 -0
  223. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts +75 -0
  224. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -0
  225. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js +85 -0
  226. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js.map +1 -0
  227. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts +120 -0
  228. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -0
  229. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js +152 -0
  230. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -0
  231. package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts +73 -0
  232. package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -0
  233. package/dist/esm/schemas/label-keys/website-label-key.schemas.js +76 -0
  234. package/dist/esm/schemas/label-keys/website-label-key.schemas.js.map +1 -0
  235. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts +209 -0
  236. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -0
  237. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js +218 -0
  238. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js.map +1 -0
  239. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +431 -0
  240. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -0
  241. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js +438 -0
  242. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -0
  243. package/dist/esm/schemas/refs/page-ref.schemas.d.ts +40 -0
  244. package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -0
  245. package/dist/esm/schemas/refs/page-ref.schemas.js +43 -0
  246. package/dist/esm/schemas/refs/page-ref.schemas.js.map +1 -0
  247. package/dist/esm/schemas/refs/section-ref.schemas.d.ts +41 -0
  248. package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -0
  249. package/dist/esm/schemas/refs/section-ref.schemas.js +44 -0
  250. package/dist/esm/schemas/refs/section-ref.schemas.js.map +1 -0
  251. package/dist/esm/schemas/sections/website-section-category.shared.d.ts +63 -0
  252. package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -0
  253. package/dist/esm/schemas/sections/website-section-category.shared.js +64 -0
  254. package/dist/esm/schemas/sections/website-section-category.shared.js.map +1 -0
  255. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +114 -0
  256. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -0
  257. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +69 -0
  258. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -0
  259. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +100 -0
  260. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -0
  261. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js +355 -0
  262. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -0
  263. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts +58 -0
  264. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -0
  265. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js +2 -0
  266. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -0
  267. package/dist/esm/schemas/validators/label-pack.validator.d.ts +144 -0
  268. package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -0
  269. package/dist/esm/schemas/validators/label-pack.validator.js +100 -0
  270. package/dist/esm/schemas/validators/label-pack.validator.js.map +1 -0
  271. package/dist/tsconfig.build.tsbuildinfo +1 -0
  272. package/package.json +143 -0
  273. package/src/__tests__/bundle-isolation.test.ts +607 -0
  274. package/src/astro/__tests__/blog-post-bridge.test.tsx +288 -0
  275. package/src/astro/__tests__/blog-post-head.renderer.test.ts +249 -0
  276. package/src/astro/__tests__/bridge-runtime.test.tsx +198 -0
  277. package/src/astro/__tests__/collect-expected-label-keys.test.ts +69 -0
  278. package/src/astro/__tests__/headers-renderer.test.ts +143 -0
  279. package/src/astro/__tests__/i18n-routing.helper.test.ts +116 -0
  280. package/src/astro/__tests__/label-pack-loader.test.ts +305 -0
  281. package/src/astro/__tests__/llms-txt-server.test.ts +127 -0
  282. package/src/astro/__tests__/pick-labels-for-page.test.ts +60 -0
  283. package/src/astro/__tests__/robots-renderer.test.ts +228 -0
  284. package/src/astro/__tests__/sitemap-coverage.test.ts +230 -0
  285. package/src/astro/__tests__/structured-data.renderer.test.ts +172 -0
  286. package/src/astro/__tests__/website-page-head.renderer.test.ts +424 -0
  287. package/src/astro/__tests__/website-page-runtime.helper.test.ts +157 -0
  288. package/src/astro/__tests__/website-site-context.test.ts +121 -0
  289. package/src/astro/blog-post-bridge.tsx +404 -0
  290. package/src/astro/blog-post-head.renderer.ts +293 -0
  291. package/src/astro/bridge-runtime.tsx +270 -0
  292. package/src/astro/collect-expected-label-keys.ts +113 -0
  293. package/src/astro/headers-renderer.ts +82 -0
  294. package/src/astro/i18n-routing.helper.ts +116 -0
  295. package/src/astro/internal/html-escape.ts +41 -0
  296. package/src/astro/label-pack-loader.ts +385 -0
  297. package/src/astro/llms-txt-server.ts +56 -0
  298. package/src/astro/pick-labels-for-page.ts +85 -0
  299. package/src/astro/robots-renderer.ts +171 -0
  300. package/src/astro/sitemap-coverage.ts +186 -0
  301. package/src/astro/structured-data.renderer.ts +171 -0
  302. package/src/astro/website-page-head.renderer.ts +279 -0
  303. package/src/astro/website-page-runtime.helper.ts +188 -0
  304. package/src/astro/website-site-context.ts +136 -0
  305. package/src/astro-island.ts +61 -0
  306. package/src/astro.ts +151 -0
  307. package/src/companion-exports.ts +38 -0
  308. package/src/components/low-level/WebsiteButton.tsx +118 -0
  309. package/src/components/low-level/WebsiteHeading.tsx +96 -0
  310. package/src/components/low-level/WebsiteImage.tsx +96 -0
  311. package/src/components/low-level/WebsiteInternalButton.tsx +172 -0
  312. package/src/components/low-level/WebsiteInternalLink.tsx +193 -0
  313. package/src/components/low-level/WebsiteLink.tsx +71 -0
  314. package/src/components/low-level/WebsiteText.tsx +75 -0
  315. package/src/components/low-level/__tests__/WebsiteInternalButton.test.tsx +169 -0
  316. package/src/components/low-level/__tests__/WebsiteInternalLink.test.tsx +202 -0
  317. package/src/components/low-level/__tests__/primitives.test.tsx +141 -0
  318. package/src/config/__tests__/define-website-config.test.ts +231 -0
  319. package/src/config/__tests__/load-website-config.test.ts +215 -0
  320. package/src/config/define-website-config.ts +134 -0
  321. package/src/config/index.ts +38 -0
  322. package/src/config/load-website-config.ts +229 -0
  323. package/src/config/wildo-website-config.schemas.ts +322 -0
  324. package/src/config-loader.ts +35 -0
  325. package/src/core/__tests__/WebsitePageLayout.test.tsx +184 -0
  326. package/src/core/__tests__/WebsiteSection.test.tsx +85 -0
  327. package/src/core/__tests__/contexts.test.tsx +133 -0
  328. package/src/core/__tests__/core-boundary.test.ts +85 -0
  329. package/src/core/__tests__/define-website-page-manifest.test.tsx +111 -0
  330. package/src/core/__tests__/define-website-section.test.tsx +89 -0
  331. package/src/core/__tests__/useWebsiteDesignTokens.test.tsx +41 -0
  332. package/src/core/__tests__/useWebsiteLabel.test.tsx +117 -0
  333. package/src/core/anonymous-session/InboundContactForm.tsx +157 -0
  334. package/src/core/anonymous-session/__tests__/inbound-contact-form.schema.test.ts +44 -0
  335. package/src/core/anonymous-session/inbound-contact-form.schema.ts +62 -0
  336. package/src/core/anonymous-session/website-anonymous-session-client.ts +194 -0
  337. package/src/core/contexts/WebsitePageContext.tsx +52 -0
  338. package/src/core/contexts/WebsiteRuntimeContext.tsx +175 -0
  339. package/src/core/contexts/WebsiteSectionContext.tsx +59 -0
  340. package/src/core/contexts/useWebsitePage.ts +28 -0
  341. package/src/core/contexts/useWebsiteRuntime.ts +34 -0
  342. package/src/core/contexts/useWebsiteSection.ts +28 -0
  343. package/src/core/external-providers/frontend-provider-registry.website.ts +62 -0
  344. package/src/core/factories/define-website-page-manifest.ts +102 -0
  345. package/src/core/factories/define-website-section.ts +128 -0
  346. package/src/core/hooks/useWebsiteDesignTokens.ts +26 -0
  347. package/src/core/hooks/useWebsiteLabel.ts +121 -0
  348. package/src/core/layouts/WebsitePageLayout.tsx +311 -0
  349. package/src/core/layouts/WebsiteSection.tsx +139 -0
  350. package/src/core/routing/__tests__/internal-routing.utils.test.ts +134 -0
  351. package/src/core/routing/internal-routing.utils.ts +77 -0
  352. package/src/index.ts +132 -0
  353. package/src/mdx/BlogPost.tsx +296 -0
  354. package/src/mdx/__tests__/BlogPost.test.tsx +218 -0
  355. package/src/mdx/__tests__/blog-post-collection.config.test.ts +127 -0
  356. package/src/mdx/__tests__/website-mdx-provider.test.tsx +82 -0
  357. package/src/mdx/blog-post-collection.config.ts +177 -0
  358. package/src/mdx/blog-post-frontmatter-source.ts +31 -0
  359. package/src/mdx/index.ts +39 -0
  360. package/src/mdx/website-mdx-provider.tsx +95 -0
  361. package/src/mdx.ts +38 -0
  362. package/src/schemas/__tests__/blog-frontmatter.test.ts +50 -0
  363. package/src/schemas/__tests__/companion-exports-react-free.test.ts +51 -0
  364. package/src/schemas/__tests__/design-tokens.test.ts +163 -0
  365. package/src/schemas/__tests__/label-key.test.ts +46 -0
  366. package/src/schemas/__tests__/label-pack-validator.test.ts +146 -0
  367. package/src/schemas/__tests__/page-manifest.test.ts +66 -0
  368. package/src/schemas/__tests__/refs.test.ts +37 -0
  369. package/src/schemas/__tests__/root-config.test.ts +292 -0
  370. package/src/schemas/__tests__/section-definition.test.ts +47 -0
  371. package/src/schemas/__tests__/structured-data-reconciliation.test.ts +543 -0
  372. package/src/schemas/blog/blog-post-frontmatter.shared.schemas.ts +111 -0
  373. package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +166 -0
  374. package/src/schemas/label-keys/website-label-key.schemas.ts +88 -0
  375. package/src/schemas/manifests/website-page-manifest.shared.schemas.ts +266 -0
  376. package/src/schemas/manifests/website-root-config.shared.schemas.ts +504 -0
  377. package/src/schemas/refs/page-ref.schemas.ts +49 -0
  378. package/src/schemas/refs/section-ref.schemas.ts +50 -0
  379. package/src/schemas/sections/website-section-category.shared.ts +65 -0
  380. package/src/schemas/sections/website-section-definition.shared.schemas.ts +120 -0
  381. package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +579 -0
  382. package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +62 -0
  383. package/src/schemas/validators/label-pack.validator.ts +201 -0
@@ -0,0 +1,166 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * CSS-injection-safe value validator shared by every design-token field.
5
+ *
6
+ * **Threat model** — TWO escape surfaces, BOTH covered:
7
+ *
8
+ * 1. CSS *declaration* break-out (Phase 6 third audit fix H-α):
9
+ * the renderer `renderWebsiteDesignTokensCss` interpolates token
10
+ * values **raw** into a `:root { --website-<group>-<token>: <value>; }`
11
+ * block. A value containing any of `;`, `}`, `{`, `/*`, `*\/`, or a
12
+ * newline can break out of the property declaration and inject
13
+ * arbitrary CSS — at minimum a config typo silently corrupts the
14
+ * page; at worst a malicious or untrusted config author injects a
15
+ * `body { display: none; }` rule that blanks the entire site.
16
+ *
17
+ * 2. HTML *style-element* break-out (Phase 6 fourth audit fix H-Χ):
18
+ * the rendered `:root { ... }` block is itself wrapped in an inline
19
+ * `<style>…</style>` element by the head renderer. Per the HTML spec
20
+ * the `<style>` element is a *raw-text* element — its only
21
+ * terminator is the literal sequence `</style`. A token value
22
+ * containing `<` (or, for symmetry, `>`) can therefore close the
23
+ * `<style>` element early and follow up with `<svg onload=alert(1)>`
24
+ * or any other HTML / JS payload, which the H-α deny-list (which
25
+ * did NOT forbid `<` / `>`) failed to catch. CSS values legitimately
26
+ * need NEITHER `<` NOR `>` (no CSS grammar uses them — no color,
27
+ * length, variable reference, calc expression, or functional
28
+ * notation does), so denying both is conservative and correct.
29
+ *
30
+ * **Why validate at the schema layer (not inside the renderer)**:
31
+ * 1. Fail at config load (build-time / boot-time), not at every
32
+ * `<head>` render — a malformed token surfaces in the schema-parse
33
+ * error message instead of silently emitting broken CSS / HTML.
34
+ * 2. Single source of truth — every field that flows into the
35
+ * `<style>`-wrapped CSS block gets the same guard (impossible to
36
+ * add a future token field that bypasses the renderer's escape
37
+ * pass).
38
+ * 3. The renderer stays a simple interpolator, which makes its own
39
+ * audit surface trivially small.
40
+ *
41
+ * **Why a `.refine` over a strict color-string regex**: colors accept
42
+ * `currentColor`, named colors, `var(--foo)` references, oklch
43
+ * functional notation, etc. — a strict positive grammar is ~80 lines
44
+ * and high-maintenance. A negative deny-list of the eight characters
45
+ * that are LITERALLY the only ones that can break out of either the CSS
46
+ * property declaration OR the wrapping `<style>` element is small,
47
+ * complete, and obviously correct.
48
+ */
49
+ const CSS_INJECTION_FORBIDDEN_PATTERN = /[;{}<>\n\r]|\/\*|\*\//;
50
+ const cssSafeValue = (fieldName: string): z.ZodString =>
51
+ z
52
+ .string()
53
+ .min(1, { error: `${fieldName} must not be empty` })
54
+ .refine((value) => !CSS_INJECTION_FORBIDDEN_PATTERN.test(value), {
55
+ error:
56
+ `${fieldName} must not contain CSS-injection or HTML break-out ` +
57
+ `metacharacters (; { } < > /* */ newline) — token values are ` +
58
+ `interpolated raw into <style>:root { --website-...: <value>; }</style>`,
59
+ }) as unknown as z.ZodString;
60
+
61
+ /**
62
+ * @wildo_source:part:start saas.website.design-tokens.colors facet:layer:shared facet:family:website
63
+ *
64
+ * `WebsiteColorTokens` — minimal palette the marketing-site exposes as
65
+ * CSS custom properties (`--website-color-<token>`) on the
66
+ * `<WebsitePageLayout>` root.
67
+ *
68
+ * **Why a dedicated marketing palette** (vs. reusing
69
+ * `presets-components-models/design-tokens-config`):
70
+ *
71
+ * - The SaaS app's design tokens carry many semantic states (`muted`,
72
+ * `destructive`, `ring`, `sidebar-*`, …) that have no marketing-site
73
+ * analogue. Forcing the website to consume the full palette would
74
+ * leak app-internal semantics into a public surface.
75
+ * - Marketing pages need *display-grade* colors (heavily branded
76
+ * accents, gradient stops, hero-overlay tints) that the SaaS app's
77
+ * functional palette deliberately avoids.
78
+ * - Designers overriding website tokens (Phase 6 dogfood proves this
79
+ * path) should not be able to break the SaaS app by editing one
80
+ * tailwind.config — the two surfaces stay decoupled.
81
+ *
82
+ * **Token format**: every value is a CSS color string. Shape validation
83
+ * is intentionally permissive (accepts hex, rgb/rgba, hsl/hsla, oklch,
84
+ * `currentColor`, named colors, CSS variable references) — a strict
85
+ * color-string regex would reject legitimate values (oklch grammar
86
+ * alone is ~80 lines) and a malformed-but-syntactically-valid color
87
+ * fails loudly at render time.
88
+ *
89
+ * **What IS strictly enforced**: every value passes `cssSafeValue`,
90
+ * which rejects the six characters that can break out of a CSS
91
+ * property declaration (`;`, `{`, `}`, `/*`, `*\/`, newline). See the
92
+ * `cssSafeValue` JSDoc above for the full threat model.
93
+ *
94
+ * **Adding a token**: append the field here using `cssSafeValue(...)`,
95
+ * document its semantic intent in the JSDoc, and update the matching
96
+ * CSS-emit helper in Phase 2 (`emitWebsiteColorTokensCss`).
97
+ */
98
+ const _WebsiteColorTokensSchema = z.object({
99
+ background: cssSafeValue('background color'),
100
+ foreground: cssSafeValue('foreground color'),
101
+ primary: cssSafeValue('primary color'),
102
+ primaryForeground: cssSafeValue('primaryForeground color'),
103
+ accent: cssSafeValue('accent color'),
104
+ accentForeground: cssSafeValue('accentForeground color'),
105
+ surface: cssSafeValue('surface color'),
106
+ surfaceForeground: cssSafeValue('surfaceForeground color'),
107
+ border: cssSafeValue('border color'),
108
+ muted: cssSafeValue('muted color'),
109
+ mutedForeground: cssSafeValue('mutedForeground color'),
110
+ });
111
+
112
+ export const WebsiteColorTokensSchema: typeof _WebsiteColorTokensSchema = _WebsiteColorTokensSchema;
113
+
114
+ export type WebsiteColorTokens = z.infer<typeof _WebsiteColorTokensSchema>;
115
+ /** @wildo_source:part:end saas.website.design-tokens.colors */
116
+
117
+ /**
118
+ * @wildo_source:part:start saas.website.design-tokens.radius facet:layer:shared facet:family:website
119
+ *
120
+ * `WebsiteRadiusTokens` — border-radius scale exposed as
121
+ * `--website-radius-<token>` CSS variables.
122
+ *
123
+ * Marketing radii are intentionally a separate token set from the
124
+ * SaaS app's radii: the website typically wants larger pill / hero
125
+ * radii than the app's compact-UI radii. Keeping them separate lets
126
+ * either surface evolve without coupling.
127
+ *
128
+ * Values are CSS length strings (`'0.25rem'`, `'12px'`,
129
+ * `'9999px'`). Same permissive-shape + strict-CSS-injection
130
+ * rationale as `WebsiteColorTokens` (every value runs through
131
+ * `cssSafeValue`).
132
+ */
133
+ const _WebsiteRadiusTokensSchema = z.object({
134
+ none: cssSafeValue('none radius'),
135
+ sm: cssSafeValue('sm radius'),
136
+ md: cssSafeValue('md radius'),
137
+ lg: cssSafeValue('lg radius'),
138
+ xl: cssSafeValue('xl radius'),
139
+ full: cssSafeValue('full radius'),
140
+ });
141
+
142
+ export const WebsiteRadiusTokensSchema: typeof _WebsiteRadiusTokensSchema = _WebsiteRadiusTokensSchema;
143
+
144
+ export type WebsiteRadiusTokens = z.infer<typeof _WebsiteRadiusTokensSchema>;
145
+ /** @wildo_source:part:end saas.website.design-tokens.radius */
146
+
147
+ /**
148
+ * @wildo_source:part:start saas.website.design-tokens.bundle facet:layer:shared facet:family:website
149
+ *
150
+ * `WebsiteDesignTokens` — the complete design-token bundle attached to
151
+ * `WebsiteRootConfig.designTokens`. Currently colors + radii only
152
+ * (per plan Phase 1 scope: "WebsiteColorTokensSchema + WebsiteRadiusTokensSchema (only)").
153
+ *
154
+ * Future expansions (typography scale, motion tokens, shadow elevation)
155
+ * will land as additional fields here without breaking the
156
+ * `WebsiteRootConfig` shape.
157
+ */
158
+ const _WebsiteDesignTokensSchema = z.object({
159
+ colors: _WebsiteColorTokensSchema,
160
+ radii: _WebsiteRadiusTokensSchema,
161
+ });
162
+
163
+ export const WebsiteDesignTokensSchema: typeof _WebsiteDesignTokensSchema = _WebsiteDesignTokensSchema;
164
+
165
+ export type WebsiteDesignTokens = z.infer<typeof _WebsiteDesignTokensSchema>;
166
+ /** @wildo_source:part:end saas.website.design-tokens.bundle */
@@ -0,0 +1,88 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * @wildo_source:part:start saas.website.label-keys.format facet:layer:shared facet:family:i18n
5
+ *
6
+ * `WebsiteLabelKey` — branded i18n key for marketing-site copy.
7
+ *
8
+ * **Format**: `website.<pageRef>.<sectionRef>.<slotPath>`
9
+ *
10
+ * - `website.` — fixed namespace prefix that disambiguates marketing-site
11
+ * labels from app i18n keys (`labels.`, `errors.`, `notifications.`).
12
+ * Marketing-site label packs ship as a separate file per locale
13
+ * (`website/src/i18n/<locale>.json`) so they never bloat the SaaS app
14
+ * bundle.
15
+ * - `<pageRef>` — kebab-case page ref (validated independently by
16
+ * `WebsitePageRefSchema`).
17
+ * - `<sectionRef>` — kebab-case section ref (validated independently by
18
+ * `WebsiteSectionRefSchema`).
19
+ * - `<slotPath>` — leaf identifier the section consumes via
20
+ * `useWebsiteLabel('<slotPath>')` (Phase 2). Allows dots for nested
21
+ * structures (`'cta.primary'`), brackets for indexed array slots
22
+ * (`'tiers[0].name'`), and hyphens for multi-word leaves.
23
+ *
24
+ * **Examples**:
25
+ * - `website.landing.hero.headline`
26
+ * - `website.landing.hero.cta.primary`
27
+ * - `website.pricing.tiers.tiers[0].name`
28
+ * - `website.contact.form.field.email-placeholder`
29
+ *
30
+ * **Why a brand**: prevents the "label key as bare string" failure mode
31
+ * where a typo in `'webiste.landing.hero.headline'` (note the typo) silently
32
+ * resolves to undefined at runtime. Sections must accept
33
+ * `WebsiteLabelKey`-typed props; consumers go through
34
+ * `WebsiteLabelKeySchema.parse(...)` (or the manifest's `metaLabelKeys`
35
+ * fields, which are typed against this schema) before a string flows into
36
+ * label resolution.
37
+ *
38
+ * **Why a single regex**: the three nested refs (`page`, `section`, `slot`)
39
+ * are validated separately by their own schemas inside the `defineWebsiteSection`
40
+ * factory and `WebsitePageManifestSchema`. This schema's job is to validate
41
+ * a fully-composed key — typically inserted directly into JSX as a string
42
+ * literal — without re-parsing the parts.
43
+ */
44
+ const _WebsiteLabelKeySchema = z
45
+ .string()
46
+ .min(10, { error: 'WebsiteLabelKey is too short (must include website.<page>.<section>.<slot>)' })
47
+ .max(256, { error: 'WebsiteLabelKey must be at most 256 characters' })
48
+ .regex(
49
+ /^website\.[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*\.[a-zA-Z0-9.\[\]_-]+$/,
50
+ {
51
+ error:
52
+ 'WebsiteLabelKey must match `website.<page>.<section>.<slot>` where page/section are kebab-case and slot allows dot/bracket/hyphen/underscore (e.g. "website.landing.hero.headline")',
53
+ },
54
+ )
55
+ .brand<'WebsiteLabelKey'>();
56
+
57
+ export const WebsiteLabelKeySchema: typeof _WebsiteLabelKeySchema = _WebsiteLabelKeySchema;
58
+
59
+ export type WebsiteLabelKey = z.infer<typeof _WebsiteLabelKeySchema>;
60
+ /** @wildo_source:part:end saas.website.label-keys.format */
61
+
62
+ /**
63
+ * @wildo_source:part:start saas.website.label-keys.compose-helper facet:layer:shared facet:family:i18n
64
+ *
65
+ * `composeWebsiteLabelKey` — pure helper that composes a `WebsiteLabelKey`
66
+ * from its three parts. Used by `useWebsiteLabel` (Phase 2) and by the
67
+ * label-pack validator to materialize the keys a section commits to.
68
+ *
69
+ * Returns the key as a parsed `WebsiteLabelKey` (brand applied), throwing
70
+ * if the composed string fails the format check (defensive guard against
71
+ * stale refs / inconsistent slot paths leaking past type-system narrowing).
72
+ *
73
+ * @example
74
+ * composeWebsiteLabelKey({
75
+ * pageRef: WebsitePageRefSchema.parse('landing'),
76
+ * sectionRef: WebsiteSectionRefSchema.parse('hero'),
77
+ * slotPath: 'headline',
78
+ * });
79
+ * // -> WebsiteLabelKey ("website.landing.hero.headline")
80
+ */
81
+ export function composeWebsiteLabelKey(parts: {
82
+ pageRef: string;
83
+ sectionRef: string;
84
+ slotPath: string;
85
+ }): WebsiteLabelKey {
86
+ return WebsiteLabelKeySchema.parse(`website.${parts.pageRef}.${parts.sectionRef}.${parts.slotPath}`);
87
+ }
88
+ /** @wildo_source:part:end saas.website.label-keys.compose-helper */
@@ -0,0 +1,266 @@
1
+ import type { ComponentType } from 'react';
2
+ import { z } from 'zod';
3
+
4
+ import { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';
5
+ import { WebsitePageRefSchema } from '../refs/page-ref.schemas';
6
+ import { WebsiteSectionRefSchema } from '../refs/section-ref.schemas';
7
+
8
+ /**
9
+ * @wildo_source:part:start saas.website.page-manifest.meta-label-keys facet:layer:shared facet:family:website
10
+ *
11
+ * `WebsitePageMetaLabelKeys` — the i18n keys the Astro adapter (Phase 5)
12
+ * resolves at static-build time to populate `<title>` / `<meta>` /
13
+ * Open Graph tags for the page.
14
+ *
15
+ * - `titleKey` — REQUIRED. Renders into `<title>` AND `og:title`. The
16
+ * manifest commits to declaring this key in every locale's label
17
+ * pack; the validator surfaces missing entries at build start.
18
+ * - `descriptionKey` — REQUIRED. Renders into `<meta name="description">`,
19
+ * `og:description`, and `twitter:description`. Same per-locale
20
+ * commitment.
21
+ * - `ogImageUrl` — OPTIONAL. Static URL (typically a per-locale CDN
22
+ * path) for `og:image` / `twitter:image`. Not a label key because
23
+ * marketing teams overwhelmingly ship one localized asset URL per
24
+ * page rather than authoring image URLs in label packs. When
25
+ * per-locale images are needed, the consumer supplies a function
26
+ * in their site-root config that resolves locale → URL (Phase 5).
27
+ *
28
+ * **Why these three only**: the JSON-LD / canonical / `hreflang` /
29
+ * robots tags are NOT label-key-driven — they are derived
30
+ * mechanically from the manifest (`routePath`), the root config
31
+ * (`locales`, `defaultLocale`), and the section-level
32
+ * `getStructuredData()` hooks. Mixing them into the meta-label-keys
33
+ * shape would conflate human-authored copy with structural metadata.
34
+ */
35
+ const _WebsitePageMetaLabelKeysSchema = z.object({
36
+ titleKey: WebsiteLabelKeySchema,
37
+ descriptionKey: WebsiteLabelKeySchema,
38
+ ogImageUrl: z.url({ error: 'ogImageUrl must be a valid URL' }).optional(),
39
+ });
40
+
41
+ export const WebsitePageMetaLabelKeysSchema: typeof _WebsitePageMetaLabelKeysSchema =
42
+ _WebsitePageMetaLabelKeysSchema;
43
+
44
+ export type WebsitePageMetaLabelKeys = z.infer<typeof _WebsitePageMetaLabelKeysSchema>;
45
+ /** @wildo_source:part:end saas.website.page-manifest.meta-label-keys */
46
+
47
+ /**
48
+ * @wildo_source:part:start saas.website.page-manifest.route-path facet:layer:shared facet:family:website
49
+ *
50
+ * `WebsitePageRoutePath` — the URL path the page mounts at (locale
51
+ * prefix stripped).
52
+ *
53
+ * **Format**:
54
+ * - MUST start with `/`
55
+ * - MAY be the root (`'/'`) for the landing page
56
+ * - segments are lowercase kebab-case
57
+ * - parameter segments use Astro's `[param]` convention
58
+ * (e.g. `'/blog/[slug]'`, `'/case-studies/[slug]'`)
59
+ *
60
+ * The Astro adapter (Phase 5) prepends the locale prefix
61
+ * (`/en`, `/fr`, …) when generating routes. The manifest stores the
62
+ * locale-neutral path because the same `pageComponent` renders for
63
+ * every locale.
64
+ *
65
+ * **Brand**: prevents accidental cross-talk with absolute URLs
66
+ * (`https://…`), with locale-prefixed paths (`/en/pricing`), and with
67
+ * arbitrary string props in the manifest factory.
68
+ */
69
+ const _WebsitePageRoutePathSchema = z
70
+ .string()
71
+ .min(1, { error: 'routePath must not be empty' })
72
+ .max(256, { error: 'routePath must be at most 256 characters' })
73
+ .regex(
74
+ /^\/(?:[a-z0-9][a-z0-9-]*(?:\/[a-z0-9-]+|\/\[[a-z][a-zA-Z0-9]*\])*)?\/?$/,
75
+ {
76
+ error:
77
+ 'routePath must start with `/`, use lowercase kebab-case segments, and may include Astro `[param]` segments (e.g. "/", "/pricing", "/blog/[slug]")',
78
+ },
79
+ )
80
+ .brand<'WebsitePageRoutePath'>();
81
+
82
+ export const WebsitePageRoutePathSchema: typeof _WebsitePageRoutePathSchema =
83
+ _WebsitePageRoutePathSchema;
84
+
85
+ export type WebsitePageRoutePath = z.infer<typeof _WebsitePageRoutePathSchema>;
86
+ /** @wildo_source:part:end saas.website.page-manifest.route-path */
87
+
88
+ /**
89
+ * @wildo_source:part:start saas.website.page-manifest.schema facet:layer:shared facet:family:website
90
+ *
91
+ * `WebsitePageManifest` — the registration-time descriptor for one
92
+ * marketing-site page.
93
+ *
94
+ * **Authoring path**: each page-impl module exports both a
95
+ * `<PageName>Page` React component AND a `<PAGE_NAME>_PAGE_MANIFEST`
96
+ * literal. The manifest is collected into `WebsiteRootConfig.pageManifests[]`;
97
+ * the Astro page file (`src/pages/<page>.astro`) imports the manifest
98
+ * and hands it to `<WebsitePageBridge manifest={…}>` (Phase 5).
99
+ *
100
+ * **Field-by-field rationale** (semantic-weight discipline):
101
+ *
102
+ * - `ref` — the page's structural identity. Used for label-key
103
+ * derivation (`website.<ref>.<sectionRef>.<slot>`), for
104
+ * cross-page navigation lookup, and as the manifest registry key.
105
+ * - `routePath` — the locale-neutral URL path. Decoupled from `ref`
106
+ * because the same page (e.g. `ref: 'pricing'`) may mount at a
107
+ * localized path (`/tarifs` in French) — the locale-aware path
108
+ * resolution is a Phase 5 concern; this field is the canonical
109
+ * English-language path.
110
+ * - `pageComponent` — the React component the bridge renders inside
111
+ * `<WebsitePageContext value={{ pageRef: ref }}>`. Type relaxed to
112
+ * `unknown` at the schema layer for the same reasons as
113
+ * `WebsiteSectionDefinition.Component`; the manifest factory
114
+ * (`defineWebsitePageManifest`, Phase 2) preserves precise typing
115
+ * at the consumer call site.
116
+ * - `sectionRefs` — the contractual list of section refs the page
117
+ * declares. Used by the label-pack validator to cross-reference
118
+ * `expectedLabelKeys` collected from each section against the
119
+ * page's commitment. **Order matters** for the validator's
120
+ * diagnostic output (sections are reported in declaration order)
121
+ * but does NOT control render order — render order is whatever the
122
+ * `pageComponent` JSX lays out.
123
+ * - `metaLabelKeys` — see `WebsitePageMetaLabelKeysSchema`.
124
+ * - `hydration` — opt-in flag. `'lazy'` (default) corresponds to
125
+ * `client:idle` in the Astro adapter (deferred hydration); `'eager'`
126
+ * corresponds to `client:load` (immediate hydration). The page
127
+ * author flips this to `'eager'` only when the page hosts
128
+ * above-the-fold interactive sections (e.g. a contact form).
129
+ *
130
+ * **Important — informational, NOT auto-wired**: Astro's
131
+ * `client:*` directives MUST resolve to a literal token at compile
132
+ * time (the Astro compiler reads the directive from the JSX source
133
+ * to decide which hydration helper to bundle); a runtime-computed
134
+ * value will not work. The framework therefore CANNOT translate
135
+ * this field into the directive on the consumer's behalf — the
136
+ * manifest field is the single source of truth a consumer's
137
+ * per-page `.astro` template should USE to pick the matching
138
+ * `client:idle` / `client:load` directive by hand. Keeping the two
139
+ * in lock-step is a discipline obligation; the audit CLI (Phase 7)
140
+ * will surface a drift warning when a page's hydration field does
141
+ * not match the directive in its `.astro` file.
142
+ *
143
+ * **Refinements**:
144
+ * - `sectionRefs` MUST contain unique values. Duplicates indicate
145
+ * either a copy-paste bug (two sections pointing at the same
146
+ * label-key root) or an intent to share a label-key root that
147
+ * the framework rejects to keep the per-page validation
148
+ * unambiguous (consumers who need a shared label-key root
149
+ * across two distinct sections should pull the shared content
150
+ * to the page level via `WebsiteRootConfig`-level globals,
151
+ * Phase 2.5).
152
+ */
153
+ export enum WebsitePageHydrationStrategy {
154
+ /** `client:idle` in the Astro adapter (deferred hydration). */
155
+ LAZY = 'lazy',
156
+ /** `client:load` in the Astro adapter (immediate hydration). */
157
+ EAGER = 'eager',
158
+ }
159
+
160
+ export const WebsitePageHydrationStrategySchema: z.ZodEnum<typeof WebsitePageHydrationStrategy> =
161
+ z.enum(WebsitePageHydrationStrategy);
162
+
163
+ /**
164
+ * @wildo_source:part:start saas.website.page-manifest.seo facet:layer:shared facet:family:website
165
+ *
166
+ * `WebsitePageSeoConfig` — per-page SEO/AEO/GEO knobs.
167
+ *
168
+ * The marketing-site default is to be FULLY discoverable by every
169
+ * crawler (search engines AND AI training crawlers). This block holds
170
+ * the per-page opt-out levers that flip a page out of that default.
171
+ *
172
+ * **Field rationale**:
173
+ *
174
+ * - `noaiOptOut?` — when `true`, the page emits
175
+ * `<meta name="robots" content="noai, noimageai">` AND a sibling
176
+ * `_headers` file entry signalling `X-Robots-Tag: noai, noimageai`
177
+ * for crawlers that don't honor in-document meta. ONE knob drives
178
+ * BOTH channels (`headers-renderer.ts` reads the same flag) — the
179
+ * framework owns the dual emission so consumers can't accidentally
180
+ * set one and forget the other.
181
+ *
182
+ * **Why default-IN to AI training (D-AG3)**: the marketing-site
183
+ * default is `false` (consent to AI training). Marketing pages
184
+ * benefit from AI-engine citation; opting out is a per-page
185
+ * editorial decision (typically pricing pages — to avoid stale
186
+ * price training; or legal/internal pages — to avoid free-text
187
+ * reproduction of contractual language). Per-blog-post opt-out
188
+ * would mirror this knob on a sibling blog-post manifest schema
189
+ * if that need materializes.
190
+ */
191
+ const _WebsitePageSeoConfigSchema = z.object({
192
+ noaiOptOut: z.boolean().optional(),
193
+ });
194
+
195
+ export const WebsitePageSeoConfigSchema: typeof _WebsitePageSeoConfigSchema =
196
+ _WebsitePageSeoConfigSchema;
197
+
198
+ export type WebsitePageSeoConfig = z.infer<typeof _WebsitePageSeoConfigSchema>;
199
+ /** @wildo_source:part:end saas.website.page-manifest.seo */
200
+
201
+ const _WebsitePageManifestSchema = z
202
+ .object({
203
+ ref: WebsitePageRefSchema,
204
+ routePath: _WebsitePageRoutePathSchema,
205
+ pageComponent: z.unknown(),
206
+ sectionRefs: z.array(WebsiteSectionRefSchema).min(1, {
207
+ error: 'A page manifest must declare at least one sectionRef',
208
+ }),
209
+ metaLabelKeys: _WebsitePageMetaLabelKeysSchema,
210
+ hydration: WebsitePageHydrationStrategySchema.optional(),
211
+ seo: _WebsitePageSeoConfigSchema.optional(),
212
+ })
213
+ .superRefine((manifest, ctx) => {
214
+ const seen = new Set<string>();
215
+ for (const [index, sectionRef] of manifest.sectionRefs.entries()) {
216
+ const key = sectionRef as unknown as string;
217
+ if (seen.has(key)) {
218
+ ctx.addIssue({
219
+ code: 'custom',
220
+ path: ['sectionRefs', index],
221
+ message: `Duplicate sectionRef "${key}" in page manifest "${manifest.ref as unknown as string}"`,
222
+ });
223
+ }
224
+ seen.add(key);
225
+ }
226
+
227
+ const expectedPagePrefix = `website.${manifest.ref as unknown as string}.`;
228
+ for (const [field, key] of [
229
+ ['titleKey', manifest.metaLabelKeys.titleKey] as const,
230
+ ['descriptionKey', manifest.metaLabelKeys.descriptionKey] as const,
231
+ ]) {
232
+ if (!(key as unknown as string).startsWith(expectedPagePrefix)) {
233
+ ctx.addIssue({
234
+ code: 'custom',
235
+ path: ['metaLabelKeys', field],
236
+ message: `metaLabelKeys.${field} ("${key as unknown as string}") must be rooted under the page ref "${manifest.ref as unknown as string}" (expected prefix "${expectedPagePrefix}")`,
237
+ });
238
+ }
239
+ }
240
+ });
241
+
242
+ export const WebsitePageManifestSchema: typeof _WebsitePageManifestSchema =
243
+ _WebsitePageManifestSchema;
244
+
245
+ /**
246
+ * Runtime shape of `WebsitePageManifest`.
247
+ *
248
+ * **Why `ComponentType<any>` and NOT `ComponentType<unknown>`** —
249
+ * same variance reasoning as `WebsiteSectionDefinition.Component`:
250
+ * `ComponentType<TProps>` is CONTRAVARIANT in `TProps`, so a
251
+ * narrower `TypedWebsitePageManifest<TProps>` (returned by
252
+ * `defineWebsitePageManifest<TProps>`) was not assignable to a
253
+ * `WebsitePageManifest` typed with `ComponentType<unknown>`. That
254
+ * blocked every consumer call-site that passed a typed manifest to
255
+ * helpers / registries / validators expecting the bare type
256
+ * (root config's `pageManifests[]`, the Astro adapter's
257
+ * `loadWebsitePageRuntime`, and the head-renderer). Using `any`
258
+ * matches the React-canonical pattern for heterogeneous component
259
+ * containers and reflects the runtime contract: the framework only
260
+ * RENDERS these components — it never constructs ad-hoc props for
261
+ * them; the consumer's JSX call site supplies the precise props.
262
+ */
263
+ export interface WebsitePageManifest extends z.infer<typeof _WebsitePageManifestSchema> {
264
+ pageComponent: ComponentType<any>;
265
+ }
266
+ /** @wildo_source:part:end saas.website.page-manifest.schema */