@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,504 @@
1
+ import type { ComponentType } from 'react';
2
+ import { z } from 'zod';
3
+
4
+ import { WebsiteDesignTokensSchema } from '../design-tokens/website-design-tokens.shared.schemas';
5
+ import { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';
6
+ import {
7
+ WebsitePageManifestSchema,
8
+ type WebsitePageManifest,
9
+ } from './website-page-manifest.shared.schemas';
10
+
11
+ /**
12
+ * @wildo_source:part:start saas.website.seo.sitemap-config facet:layer:shared facet:family:website
13
+ *
14
+ * `WebsiteSitemapConfig` — site-wide sitemap policy block.
15
+ *
16
+ * Materialized at build time by `@astrojs/sitemap` (the official
17
+ * Astro plugin) when the consumer wires it into `astro.config.ts`
18
+ * (see `website-seo-aeo-geo` SKILL). The framework owns the
19
+ * **schema** (so config + enforcement live in lock-step with the
20
+ * page registry) but the framework does NOT own the runtime
21
+ * generator — `@astrojs/sitemap` already covers per-page
22
+ * `lastmod` / `changefreq` / `priority` + i18n `xhtml:link
23
+ * hreflang` annotations natively, and re-implementing that surface
24
+ * inside the engine would duplicate well-trodden Astro tooling for
25
+ * zero added value.
26
+ *
27
+ * # Field rationale
28
+ *
29
+ * **Why `enabled` defaults to `true`**:
30
+ * - Marketing sites that want to ship public pages overwhelmingly
31
+ * also want them in `sitemap.xml` (search engines treat its
32
+ * absence as a discoverability signal). The opt-OUT shape avoids
33
+ * a silent regression where a consumer ships pages that crawlers
34
+ * never index because they forgot to flip a flag.
35
+ * - The escape hatch (`enabled: false`) covers staging deployments
36
+ * and the rare per-app decision to suppress sitemap emission
37
+ * entirely (e.g. a private beta surface routed under a
38
+ * `noindex` policy).
39
+ *
40
+ * **Why `changefreq` / `priority` are OPTIONAL** (no defaults):
41
+ * - Both fields are non-binding hints — Google explicitly
42
+ * ignores `priority` and treats `changefreq` as a soft signal.
43
+ * Forcing the consumer to pick a value would either invite
44
+ * superstitious authoring (`priority: 1.0` on every page) or
45
+ * bury the field's actual ergonomic shape (per-page overrides
46
+ * via `WebsitePageManifest.seo` — deferred until a real
47
+ * consumer requests it).
48
+ * - Unset values are simply omitted from the emitted `<url>`
49
+ * entries; `@astrojs/sitemap` handles that automatically.
50
+ *
51
+ * **Validation**:
52
+ * - `priority` is `z.number().min(0).max(1)` — outside the spec
53
+ * range would silently break crawler ingestion.
54
+ * - `changefreq` is the union of the seven values the sitemap
55
+ * spec defines; arbitrary strings would be silently dropped by
56
+ * compliant crawlers.
57
+ */
58
+ const _WebsiteSitemapConfigSchema = z.object({
59
+ enabled: z.boolean().default(true),
60
+ changefreq: z
61
+ .enum(['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'])
62
+ .optional(),
63
+ priority: z
64
+ .number()
65
+ .min(0, { error: 'WebsiteSitemapConfig.priority must be between 0 and 1' })
66
+ .max(1, { error: 'WebsiteSitemapConfig.priority must be between 0 and 1' })
67
+ .optional(),
68
+ });
69
+
70
+ export const WebsiteSitemapConfigSchema: typeof _WebsiteSitemapConfigSchema =
71
+ _WebsiteSitemapConfigSchema;
72
+
73
+ export type WebsiteSitemapConfig = z.infer<typeof _WebsiteSitemapConfigSchema>;
74
+ /** @wildo_source:part:end saas.website.seo.sitemap-config */
75
+
76
+ /**
77
+ * @wildo_source:part:start saas.website.seo.robots-config facet:layer:shared facet:family:website
78
+ *
79
+ * `WebsiteRobotsConfig` — site-wide `robots.txt` policy block.
80
+ *
81
+ * Materialized at request time by `renderRobotsTxt(siteContext)`
82
+ * from `@wildo-ai/saas-website/astro` (consumer wires a thin
83
+ * `src/pages/robots.txt.ts` Astro endpoint that returns the helper's
84
+ * output). Scoped to the SITE-WIDE policy surface — per-page
85
+ * `noindex` is a separate concern that lands on
86
+ * `WebsitePageManifest.seo` once a real consumer requests it (today
87
+ * the site-wide `disallow[]` covers staging-environment exclusion of
88
+ * specific path prefixes, the dominant real-world use case).
89
+ *
90
+ * # Field rationale
91
+ *
92
+ * **Why `policy` defaults to `'index'`**:
93
+ * - The default for a public marketing site is "let crawlers in";
94
+ * `'noindex'` is the deliberate exception (staging environments,
95
+ * beta surfaces, sites still under construction). The opt-OUT
96
+ * shape mirrors `WebsiteSitemapConfig.enabled` for consistency.
97
+ * - `'noindex'` emits a `User-agent: *\nDisallow: /` block — the
98
+ * standard "tell every well-behaved crawler to stay away"
99
+ * primitive.
100
+ *
101
+ * **Why `disallow[]` is a path-prefix list (not a regex)**:
102
+ * - The `robots.txt` spec only supports literal path prefixes
103
+ * with `*` / `$` wildcards; Disallow is NOT a regex surface.
104
+ * Typing it as `string[]` (validated to start with `/`) keeps
105
+ * the contract honest and lets consumers paste from their CDN
106
+ * allowlists without translation.
107
+ *
108
+ * **Why `allowedCrawlers` and `disallowedCrawlers` are mutually
109
+ * exclusive**:
110
+ * - The two fields are two halves of the SAME AI-crawler policy
111
+ * decision. `allowedCrawlers` is the deny-by-default shape
112
+ * (only the named bots may crawl); `disallowedCrawlers` is the
113
+ * allow-by-default shape (every bot except the named ones may
114
+ * crawl). Mixing both would emit a contradictory `robots.txt`
115
+ * (a bot in both lists would receive conflicting directives).
116
+ * A `superRefine` enforces the mutual-exclusion invariant at
117
+ * parse time so the build fails fast.
118
+ *
119
+ * **Validation**:
120
+ * - `disallow[]` entries must start with `/` (sitemap path-prefix
121
+ * spec) — anything else would silently match nothing.
122
+ * - `allowedCrawlers` / `disallowedCrawlers` entries must be
123
+ * non-empty strings (a blank User-agent token would emit
124
+ * `User-agent: ` which compliant crawlers ignore — silent
125
+ * misconfig).
126
+ * - `superRefine` rejects the combined-list anti-pattern.
127
+ */
128
+ const _WebsiteRobotsConfigSchema = z
129
+ .object({
130
+ policy: z.enum(['index', 'noindex']).default('index'),
131
+ disallow: z
132
+ .array(
133
+ z
134
+ .string()
135
+ .min(1)
136
+ .refine((path) => path.startsWith('/'), {
137
+ error: 'WebsiteRobotsConfig.disallow[] entries must start with "/"',
138
+ }),
139
+ )
140
+ .optional(),
141
+ /**
142
+ * Deny-by-default AI-crawler allowlist. When set, the emitted
143
+ * `robots.txt` lists each named bot under its own
144
+ * `User-agent: <bot>\nAllow: /` block, then closes with a
145
+ * catch-all `User-agent: *\nDisallow: /`. Mutually exclusive
146
+ * with `disallowedCrawlers` (see `superRefine` below).
147
+ */
148
+ allowedCrawlers: z.array(z.string().min(1)).optional(),
149
+ /**
150
+ * Allow-by-default AI-crawler denylist. When set, the emitted
151
+ * `robots.txt` lists each named bot under its own
152
+ * `User-agent: <bot>\nDisallow: /` block. Mutually exclusive
153
+ * with `allowedCrawlers` (see `superRefine` below).
154
+ */
155
+ disallowedCrawlers: z.array(z.string().min(1)).optional(),
156
+ })
157
+ .superRefine((value, ctx) => {
158
+ if (value.allowedCrawlers !== undefined && value.disallowedCrawlers !== undefined) {
159
+ ctx.addIssue({
160
+ code: 'custom',
161
+ path: ['allowedCrawlers'],
162
+ message:
163
+ 'WebsiteRobotsConfig.allowedCrawlers and disallowedCrawlers are mutually exclusive — pick one policy direction (deny-by-default vs allow-by-default).',
164
+ });
165
+ }
166
+ });
167
+
168
+ export const WebsiteRobotsConfigSchema: typeof _WebsiteRobotsConfigSchema =
169
+ _WebsiteRobotsConfigSchema;
170
+
171
+ export type WebsiteRobotsConfig = z.infer<typeof _WebsiteRobotsConfigSchema>;
172
+ /** @wildo_source:part:end saas.website.seo.robots-config */
173
+
174
+ /**
175
+ * @wildo_source:part:start saas.website.seo.llms-txt-config facet:layer:shared facet:family:website
176
+ *
177
+ * `WebsiteLlmsTxtConfig` — site-wide `llms.txt` publication policy.
178
+ *
179
+ * The framework owns only the SERVING surface; the CONTENT remains
180
+ * hand-authored in `src/content/llms.txt`. This block therefore needs
181
+ * exactly one knob:
182
+ *
183
+ * - `enabled?: boolean`
184
+ * - `true` → publish when the file exists
185
+ * - `false` → suppress publication even if the file exists
186
+ * - `undefined` → auto-detect (`readLlmsTxtBody` publishes when the
187
+ * file exists, returns `null` when absent)
188
+ *
189
+ * Keeping the "auto-detect when undefined" convention in the HELPER
190
+ * (not as a schema default) is deliberate: schema defaults are static,
191
+ * while file presence is runtime/build-time state.
192
+ */
193
+ const _WebsiteLlmsTxtConfigSchema = z.object({
194
+ enabled: z.boolean().optional(),
195
+ });
196
+
197
+ export const WebsiteLlmsTxtConfigSchema: typeof _WebsiteLlmsTxtConfigSchema =
198
+ _WebsiteLlmsTxtConfigSchema;
199
+
200
+ export type WebsiteLlmsTxtConfig = z.infer<typeof _WebsiteLlmsTxtConfigSchema>;
201
+ /** @wildo_source:part:end saas.website.seo.llms-txt-config */
202
+
203
+ /**
204
+ * @wildo_source:part:start saas.website.root-config.seo facet:layer:shared facet:family:website
205
+ *
206
+ * `WebsiteSeoConfig` — site-wide SEO/social-graph configuration
207
+ * AUTHORED ON THE ROOT CONFIG.
208
+ *
209
+ * # Scope
210
+ *
211
+ * The public marketing-site origin belongs only in the per-service
212
+ * `wildo.website.config.ts` declaration. Keep it out of this site-level SEO
213
+ * block so the origin and localized routing have one source of truth.
214
+ *
215
+ * What stays on this block: site-level branding + hero-image
216
+ * fallbacks that are co-authored with chrome labels and page
217
+ * manifests respectively, NOT with the URL/i18n routing surface.
218
+ *
219
+ * # Field rationale
220
+ *
221
+ * **Why `siteName` is REQUIRED**:
222
+ * - `og:site_name` is the brand label social platforms render
223
+ * above the page title in shared previews. Without it, every
224
+ * LinkedIn / Facebook / Slack preview shows the bare title with
225
+ * no brand context.
226
+ * - It is also the recommended `application-name` meta tag value
227
+ * and a useful default for JSON-LD `Organization.name` schema
228
+ * when the application ships structured data.
229
+ *
230
+ * **Why `defaultOgImageUrl` is OPTIONAL**:
231
+ * - Each page's `WebsitePageManifest.metaLabelKeys.ogImageUrl`
232
+ * takes precedence; the site-level default is a fallback for
233
+ * pages that did not commit to a hero image.
234
+ * - Marketing surfaces shipping no OG images at all (acceptable
235
+ * for a placeholder build) should not be forced to author one.
236
+ *
237
+ * **Why `sitemap?` and `robots?` are OPTIONAL** (with sensible
238
+ * defaults inside each sub-schema):
239
+ * - The marketing-site default ships a public surface — every
240
+ * page indexable, no AI-crawler restrictions, no per-path
241
+ * disallow. Forcing every consumer to author both blocks just
242
+ * to land in that default state would pollute the call site.
243
+ * - Omitting either block at the call site is equivalent to
244
+ * authoring `{}` — `WebsiteSitemapConfig.enabled` defaults to
245
+ * `true`, `WebsiteRobotsConfig.policy` defaults to `'index'`.
246
+ * The per-block field rationale lives on each sub-schema.
247
+ * - `llmsTxt?` is similarly optional, but with a different default
248
+ * mechanic: omitting the block means "auto-detect from file
249
+ * presence" rather than "use a static schema default". The helper
250
+ * `readLlmsTxtBody(...)` publishes when `src/content/llms.txt`
251
+ * exists and returns `null` otherwise.
252
+ *
253
+ * **Validation**:
254
+ * - `siteName` is `z.string().min(1)` — empty strings would emit
255
+ * `<meta property="og:site_name" content="" />` which social
256
+ * platforms render as a blank line.
257
+ * - `defaultOgImageUrl` (when present) is `z.url()`.
258
+ * - `sitemap` / `robots` validation lives on the sub-schemas.
259
+ */
260
+ const _WebsiteSeoConfigSchema = z
261
+ .object({
262
+ siteName: z
263
+ .string()
264
+ .min(1, { error: 'WebsiteSeoConfig.siteName must be a non-empty string' }),
265
+ defaultOgImageUrl: z
266
+ .url({ error: 'WebsiteSeoConfig.defaultOgImageUrl must be a fully-qualified URL' })
267
+ .optional(),
268
+ /**
269
+ * Sitemap policy block. Optional — omitting it is equivalent to
270
+ * `{ enabled: true }` (the marketing-site default). See
271
+ * `WebsiteSitemapConfigSchema` for field-by-field rationale.
272
+ */
273
+ sitemap: _WebsiteSitemapConfigSchema.optional(),
274
+ /**
275
+ * Robots.txt policy block. Optional — omitting it is equivalent
276
+ * to `{ policy: 'index' }` (the marketing-site default). See
277
+ * `WebsiteRobotsConfigSchema` for field-by-field rationale.
278
+ */
279
+ robots: _WebsiteRobotsConfigSchema.optional(),
280
+ /**
281
+ * `llms.txt` publication policy. Optional — omitting it means
282
+ * "auto-detect from file presence". See
283
+ * `WebsiteLlmsTxtConfigSchema` for the serving contract.
284
+ */
285
+ llmsTxt: _WebsiteLlmsTxtConfigSchema.optional(),
286
+ });
287
+
288
+ export const WebsiteSeoConfigSchema: typeof _WebsiteSeoConfigSchema = _WebsiteSeoConfigSchema;
289
+
290
+ export type WebsiteSeoConfig = z.infer<typeof _WebsiteSeoConfigSchema>;
291
+ /** @wildo_source:part:end saas.website.root-config.seo */
292
+
293
+ /**
294
+ * @wildo_source:part:start saas.website.root-config.navigation facet:layer:shared facet:family:website
295
+ *
296
+ * `WebsiteNavigationConfig` — site-wide header/footer slot bindings.
297
+ *
298
+ * The framework intentionally ships no concrete header or footer. The
299
+ * application authors both as React components, registers them once here, and
300
+ * the page layout renders the site-wide chrome around every page.
301
+ *
302
+ * Both fields are typed as `unknown` at the schema layer with the
303
+ * `WebsiteNavigationConfig` interface narrowing them to
304
+ * `ComponentType<unknown>` — same rationale as
305
+ * `WebsiteSectionDefinition.Component` and `WebsitePageManifest.pageComponent`.
306
+ */
307
+ const _WebsiteNavigationConfigSchema = z.object({
308
+ headerComponent: z.unknown(),
309
+ footerComponent: z.unknown(),
310
+ });
311
+
312
+ export const WebsiteNavigationConfigSchema: typeof _WebsiteNavigationConfigSchema =
313
+ _WebsiteNavigationConfigSchema;
314
+
315
+ export interface WebsiteNavigationConfig
316
+ extends z.infer<typeof _WebsiteNavigationConfigSchema> {
317
+ headerComponent: ComponentType<unknown>;
318
+ footerComponent: ComponentType<unknown>;
319
+ }
320
+ /** @wildo_source:part:end saas.website.root-config.navigation */
321
+
322
+ /**
323
+ * @wildo_source:part:start saas.website.root-config.schema facet:layer:shared facet:family:website
324
+ *
325
+ * `WebsiteRootConfig` — the site-level configuration consumed by:
326
+ * - the Astro adapter to drive i18n routing, page mounting,
327
+ * and design-token CSS emission,
328
+ * - the website audit command to validate label-pack
329
+ * coverage and SEO structured-data conformance,
330
+ * - the runtime `<WebsitePageLayout>` to render navigation
331
+ * and apply design tokens.
332
+ *
333
+ * **Authoring convention**: each marketing-site service exports a single
334
+ * `WEBSITE_ROOT_CONFIG: WebsiteRootConfig` from `src/website-root.config.ts`.
335
+ * The Astro adapter and the audit command
336
+ * both consume this canonical entry point; there is exactly one site
337
+ * config per marketing site (no per-page or per-section overrides at
338
+ * this level — overrides flow through the L0/L1/L1.5 component
339
+ * registration hierarchy described in D6).
340
+ *
341
+ * # Scope
342
+ *
343
+ * This block carries site-level chrome and registry data only. Per-locale
344
+ * routing (`locales` / `defaultLocale`) and the public origin belong in the
345
+ * per-service `wildo.website.config.ts` declaration, avoiding duplicated
346
+ * configuration that could diverge during site generation.
347
+ *
348
+ * **Field-by-field rationale**:
349
+ *
350
+ * - `pageManifests` — the registry of every page on the site. Indexed
351
+ * downstream by `manifest.ref`. Refs MUST be unique (refinement
352
+ * below).
353
+ * - `designTokens` — see `WebsiteDesignTokensSchema`.
354
+ * - `navigation` — see `WebsiteNavigationConfigSchema`.
355
+ * - `chromeLabelKeys` — OPTIONAL. The set of fully-composed label keys
356
+ * the site-wide chrome (header / footer / any global slot the
357
+ * consumer renders outside a section context) reads via
358
+ * `useWebsiteLabelByKey(...)`. Declared here, NOT in section
359
+ * `expectedLabelKeys`, because chrome is rendered outside the
360
+ * page+section context — `useWebsiteLabel(slot)` is structurally
361
+ * unavailable to it. Threading these through the label-pack
362
+ * validator (a) silences the always-on "orphaned chrome key" build
363
+ * warnings and (b) lets the build fail fast when a chrome key is
364
+ * missing from a locale pack, the same way section keys do.
365
+ * Defaults to an empty array; sites with no chrome label reads
366
+ * may omit the field entirely.
367
+ * - `seo` — site-level branding + hero-image fallback (see
368
+ * `WebsiteSeoConfigSchema`). The marketing-site origin USED to live
369
+ * here as `seo.siteOrigin`; it now lives only on
370
+ * `WildoMarketingWebsiteConfig.publicOrigin`.
371
+ * - `skipToMainContentLabelKey` — see field JSDoc below.
372
+ *
373
+ * **Refinements**:
374
+ * - `pageManifests[].ref` uniqueness — the registry is keyed by ref;
375
+ * duplicates would silently overwrite each other in the lookup
376
+ * table the bridge uses for cross-page links.
377
+ * - `pageManifests[].routePath` uniqueness — two pages cannot mount
378
+ * at the same path (Astro would error at build time, but we
379
+ * surface it earlier here with a clearer diagnostic).
380
+ * - `chromeLabelKeys` uniqueness — duplicates indicate copy-paste
381
+ * bugs at the consumer's call site; surface them here.
382
+ */
383
+ const _WebsiteRootConfigSchema = z
384
+ .object({
385
+ pageManifests: z.array(WebsitePageManifestSchema).min(1, {
386
+ error: 'WebsiteRootConfig must declare at least one page manifest',
387
+ }),
388
+ designTokens: WebsiteDesignTokensSchema,
389
+ navigation: _WebsiteNavigationConfigSchema,
390
+ chromeLabelKeys: z.array(WebsiteLabelKeySchema).default([]),
391
+ /**
392
+ * Site-level branding + hero-image fallback used by the Astro
393
+ * head renderer to compose `<meta property="og:site_name">` and
394
+ * the per-page OG image fallback. The marketing-site origin
395
+ * (`siteOrigin`) USED to live here; it was moved to
396
+ * `WildoMarketingWebsiteConfig.publicOrigin` in the seam-collapse
397
+ * (single source of truth for "what's the origin this site is
398
+ * reachable at").
399
+ */
400
+ seo: _WebsiteSeoConfigSchema,
401
+ /**
402
+ * Label key the framework reads to render the "Skip to main
403
+ * content" link as the first focusable element on every page
404
+ * Required because:
405
+ * - Skip-to-main is a baseline keyboard-accessibility
406
+ * affordance — WCAG 2.1 §2.4.1 "Bypass Blocks" (Level A)
407
+ * mandates it. A marketing-site framework that omits it
408
+ * fails AA for the entire surface; making the contract
409
+ * opt-in invites that regression on every new consumer.
410
+ * - The text is announced verbatim by assistive tech, so it
411
+ * MUST be localized — a hardcoded English fallback would
412
+ * speak "Skip to main content" to French users. Forcing the
413
+ * key through the label-pack validation surface means the
414
+ * dev server fails fast when the consumer forgets to add
415
+ * the entry to a locale pack.
416
+ * - The framework (NOT the consumer's chrome) owns rendering
417
+ * the skip link because the chrome is itself the obstacle
418
+ * the link bypasses — a chrome-side implementation would
419
+ * either be inside the chrome (impossible to focus before
420
+ * the chrome) or above it (creating a structural surface
421
+ * the framework needs anyway).
422
+ *
423
+ * The key MUST also be present in `chromeLabelKeys` (the
424
+ * label-pack validator silences "orphaned" warnings off
425
+ * `chromeLabelKeys`) — the framework adds it automatically via
426
+ * the `superRefine` pass below so consumers cannot forget.
427
+ */
428
+ skipToMainContentLabelKey: WebsiteLabelKeySchema,
429
+ })
430
+ .superRefine((config, ctx) => {
431
+ const seenRefs = new Set<string>();
432
+ const seenRoutePaths = new Set<string>();
433
+ for (const [index, manifest] of config.pageManifests.entries()) {
434
+ const refKey = manifest.ref as unknown as string;
435
+ if (seenRefs.has(refKey)) {
436
+ ctx.addIssue({
437
+ code: 'custom',
438
+ path: ['pageManifests', index, 'ref'],
439
+ message: `Duplicate page ref "${refKey}" in WebsiteRootConfig.pageManifests`,
440
+ });
441
+ }
442
+ seenRefs.add(refKey);
443
+
444
+ const routeKey = manifest.routePath as unknown as string;
445
+ if (seenRoutePaths.has(routeKey)) {
446
+ ctx.addIssue({
447
+ code: 'custom',
448
+ path: ['pageManifests', index, 'routePath'],
449
+ message: `Duplicate routePath "${routeKey}" in WebsiteRootConfig.pageManifests`,
450
+ });
451
+ }
452
+ seenRoutePaths.add(routeKey);
453
+ }
454
+
455
+ /**
456
+ * `chromeLabelKeys` uniqueness — a duplicate is harmless at
457
+ * runtime (the validator de-dupes via a Set) but always indicates
458
+ * a copy-paste bug at the consumer's call site. Surfacing it here
459
+ * keeps the chrome contract tidy and matches the discipline applied
460
+ * to `pageManifests[].ref` and `locales`.
461
+ */
462
+ const seenChromeKeys = new Set<string>();
463
+ for (const [index, key] of config.chromeLabelKeys.entries()) {
464
+ const keyStr = key as unknown as string;
465
+ if (seenChromeKeys.has(keyStr)) {
466
+ ctx.addIssue({
467
+ code: 'custom',
468
+ path: ['chromeLabelKeys', index],
469
+ message: `Duplicate chrome label key "${keyStr}" in WebsiteRootConfig.chromeLabelKeys`,
470
+ });
471
+ }
472
+ seenChromeKeys.add(keyStr);
473
+ }
474
+ });
475
+
476
+ export const WebsiteRootConfigSchema: typeof _WebsiteRootConfigSchema = _WebsiteRootConfigSchema;
477
+
478
+ /**
479
+ * Runtime shape of `WebsiteRootConfig`.
480
+ *
481
+ * Hand-written (NOT a bare `z.infer<typeof _WebsiteRootConfigSchema>`)
482
+ * because `z.infer` walks the schema tree and would type
483
+ * `pageManifests[number].pageComponent`, `navigation.headerComponent`
484
+ * and `navigation.footerComponent` as `unknown` (their schema field
485
+ * type), losing the `ComponentType<unknown>` narrowing carried by the
486
+ * hand-written `WebsitePageManifest` and `WebsiteNavigationConfig`
487
+ * interfaces. Factories such as `defineWebsiteRootConfig` and the
488
+ * layout primitives consume `WebsiteRootConfig` and must see those
489
+ * fields pre-narrowed; otherwise every consumer would need a local
490
+ * `as ComponentType` cast at the JSX call site, defeating the type-safe
491
+ * component contract.
492
+ *
493
+ * The `Omit` + intersection pattern keeps the rest of the inferred
494
+ * shape (`designTokens`, `seo`, `chromeLabelKeys`,
495
+ * `skipToMainContentLabelKey`) sourced directly from the schema.
496
+ */
497
+ export type WebsiteRootConfig = Omit<
498
+ z.infer<typeof _WebsiteRootConfigSchema>,
499
+ 'pageManifests' | 'navigation'
500
+ > & {
501
+ pageManifests: WebsitePageManifest[];
502
+ navigation: WebsiteNavigationConfig;
503
+ };
504
+ /** @wildo_source:part:end saas.website.root-config.schema */
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * @wildo_source:part:start saas.website.refs.page facet:layer:shared facet:family:website
5
+ *
6
+ * `WebsitePageRef` — branded kebab-case identifier for a single marketing-site
7
+ * page (e.g. `'landing'`, `'pricing'`, `'contact-us'`).
8
+ *
9
+ * The ref is the **structural identity** of a page across three independent
10
+ * surfaces:
11
+ *
12
+ * 1. **Label-key root**: every label key the page consumes is derived as
13
+ * `website.<pageRef>.<sectionRef>.<slot>` (see `WebsiteLabelKeySchema`).
14
+ * Renaming a `WebsitePageRef` therefore renames every label key the
15
+ * page commits to — handle as a labelled rename, never a silent edit.
16
+ * 2. **Manifest registry key**: `WebsiteRootConfig.pageManifests[]` is keyed
17
+ * by `ref`; the Astro adapter looks pages up by ref when resolving
18
+ * cross-page links (`hreflang`, locale switcher, primary nav).
19
+ * 3. **Skill mapping**: skills like `website-page-authoring` reference the
20
+ * ref vocabulary documented here, NOT individual page files.
21
+ *
22
+ * **Format**: lowercase ASCII letters, digits and `-`. Must start with a
23
+ * letter (no leading digit / hyphen). 1–64 characters. The format is the
24
+ * smallest set that survives URL paths, file names, JSON keys and i18n keys
25
+ * without escaping or case-folding.
26
+ *
27
+ * **Why a brand**: prevents accidental cross-talk with `WebsiteSectionRef`
28
+ * (also kebab-case) and with arbitrary user-supplied strings. Consumers must
29
+ * go through `WebsitePageRefSchema.parse(...)` (or the manifest factory)
30
+ * before a value flows into label-key resolution or routing.
31
+ *
32
+ * @example
33
+ * const ref = WebsitePageRefSchema.parse('landing');
34
+ * // ref: WebsitePageRef ("landing")
35
+ */
36
+ const _WebsitePageRefSchema = z
37
+ .string()
38
+ .min(1, { error: 'WebsitePageRef must be at least 1 character' })
39
+ .max(64, { error: 'WebsitePageRef must be at most 64 characters' })
40
+ .regex(
41
+ /^[a-z][a-z0-9-]*$/,
42
+ { error: 'WebsitePageRef must be lowercase kebab-case starting with a letter (e.g. "landing", "pricing-tiers")' },
43
+ )
44
+ .brand<'WebsitePageRef'>();
45
+
46
+ export const WebsitePageRefSchema: typeof _WebsitePageRefSchema = _WebsitePageRefSchema;
47
+
48
+ export type WebsitePageRef = z.infer<typeof _WebsitePageRefSchema>;
49
+ /** @wildo_source:part:end saas.website.refs.page */
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * @wildo_source:part:start saas.website.refs.section facet:layer:shared facet:family:website
5
+ *
6
+ * `WebsiteSectionRef` — branded kebab-case identifier for one section on a
7
+ * page (e.g. `'hero'`, `'social-proof'`, `'pricing-teaser'`, `'final-cta'`).
8
+ *
9
+ * The section ref is the **section-level structural identity**:
10
+ *
11
+ * 1. **Label-key root**: every label leaf consumed inside a section is
12
+ * rooted at `website.<pageRef>.<sectionRef>.<slot>`. The hook
13
+ * `useWebsiteLabel('headline')` (Phase 2) reads `WebsiteSectionContext`
14
+ * to derive the full key; the section author never types the page or
15
+ * section ref into a label call.
16
+ * 2. **Page manifest contract**: `WebsitePageManifest.sectionRefs[]`
17
+ * declares the refs the page commits to. The label-pack validator
18
+ * cross-references the manifest's declared refs against the actual
19
+ * sections rendered, surfacing mismatches at build start.
20
+ * 3. **DOM landmark**: `<WebsiteSection ref="…">` (Phase 2) emits
21
+ * `<section data-section-ref="…">` so analytics, scroll-tracking, and
22
+ * automated audits can identify sections without parsing class names.
23
+ *
24
+ * **Format**: lowercase ASCII letters, digits and `-`. Must start with a
25
+ * letter. 1–64 characters. Same shape as `WebsitePageRef` for consistency
26
+ * across both ref scopes.
27
+ *
28
+ * **Why a brand**: same reasoning as `WebsitePageRef`. The two refs are
29
+ * structurally identical strings but semantically distinct — branding
30
+ * prevents accidental swaps between page-level and section-level APIs at
31
+ * compile time.
32
+ *
33
+ * @example
34
+ * const heroRef = WebsiteSectionRefSchema.parse('hero');
35
+ * const ctaRef = WebsiteSectionRefSchema.parse('final-cta');
36
+ */
37
+ const _WebsiteSectionRefSchema = z
38
+ .string()
39
+ .min(1, { error: 'WebsiteSectionRef must be at least 1 character' })
40
+ .max(64, { error: 'WebsiteSectionRef must be at most 64 characters' })
41
+ .regex(
42
+ /^[a-z][a-z0-9-]*$/,
43
+ { error: 'WebsiteSectionRef must be lowercase kebab-case starting with a letter (e.g. "hero", "social-proof")' },
44
+ )
45
+ .brand<'WebsiteSectionRef'>();
46
+
47
+ export const WebsiteSectionRefSchema: typeof _WebsiteSectionRefSchema = _WebsiteSectionRefSchema;
48
+
49
+ export type WebsiteSectionRef = z.infer<typeof _WebsiteSectionRefSchema>;
50
+ /** @wildo_source:part:end saas.website.refs.section */
@@ -0,0 +1,65 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * @wildo_source:part:start saas.website.sections.category-enum facet:layer:shared facet:family:website
5
+ *
6
+ * `WebsiteSectionCategory` — closed vocabulary tagging the **structural
7
+ * intent** of a section.
8
+ *
9
+ * The category is NOT a layout-shape constraint and NOT a CSS class; the
10
+ * framework ships zero concrete sections and never inspects
11
+ * the category at render time. The category exists for three orthogonal
12
+ * non-render concerns:
13
+ *
14
+ * 1. **Structured-data dispatch**: the
15
+ * SEO library reads `WebsiteSectionDefinition.category` to decide
16
+ * which JSON-LD vocabulary the section's `getStructuredData()` hook
17
+ * is allowed to emit. e.g. `PRICING` may emit `Product` /
18
+ * `AggregateOffer`; `FAQ` may emit `FAQPage`; `HERO` may emit
19
+ * `Organization` / `WebSite`. Mismatches are surfaced as build-time
20
+ * warnings.
21
+ * 2. **Authoring guidance**: each value has matching application-builder
22
+ * guidance. When introducing a new category, provide its public authoring
23
+ * method before asking an application to use it.
24
+ * 3. **Analytics labelling**: the runtime emits
25
+ * `data-section-category="…"` on the `<WebsiteSection>` wrapper so
26
+ * analytics tooling can group section impressions
27
+ * without parsing className conventions.
28
+ *
29
+ * **Closed vocabulary on purpose**: a free-string `category` would
30
+ * defeat the skill-mapping and SEO-dispatch contracts. Consumers who
31
+ * need a wholly-novel section shape use the `RAW` advanced path
32
+ * (`website-factory-advanced` skill) which bypasses the category
33
+ * vocabulary entirely.
34
+ *
35
+ * **Adding a value**: requires (a) a new entry here, (b) a matching
36
+ * `website_section_<value>` skill stub, (c) (optionally) a
37
+ * structured-data dispatch entry in the SEO library. Removing a value
38
+ * is a labelled rename — every consumer's
39
+ * `defineWebsiteSection({ category: … })` declaration breaks at compile
40
+ * time, which is the desired loud failure.
41
+ */
42
+ export enum WebsiteSectionCategory {
43
+ HERO = 'hero',
44
+ CTA = 'cta',
45
+ FEATURE_BENEFIT_SUMMARY = 'feature-benefit-summary',
46
+ FEATURE_DETAIL = 'feature-detail',
47
+ PRICING = 'pricing',
48
+ CONTACT = 'contact',
49
+ SOCIAL_PROOF = 'social-proof',
50
+ FAQ = 'faq',
51
+ FOOTER_CTA = 'footer-cta',
52
+ /**
53
+ * Escape hatch for sections that don't fit any standard category
54
+ * (third-party widgets, custom one-off marketing pieces). Skips the
55
+ * SEO-dispatch contract — the section's `getStructuredData()` may
56
+ * return any vocabulary, no validation. Authors should prefer a
57
+ * concrete category whenever possible; using `RAW` opts the section
58
+ * out of the framework's structured-data safety net.
59
+ */
60
+ RAW = 'raw',
61
+ }
62
+
63
+ export const WebsiteSectionCategorySchema: z.ZodEnum<typeof WebsiteSectionCategory> =
64
+ z.enum(WebsiteSectionCategory);
65
+ /** @wildo_source:part:end saas.website.sections.category-enum */