@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,95 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+
3
+ import { MDXProvider } from '@mdx-js/react';
4
+
5
+ /**
6
+ * @wildo_source:part:start saas.website.blog.mdx-provider facet:layer:mdx facet:family:website
7
+ *
8
+ * `WebsiteMdxProvider` — thin wrapper around `@mdx-js/react`'s
9
+ * `MDXProvider` that scopes a consumer-supplied `components` map for
10
+ * the entire MDX subtree it wraps.
11
+ *
12
+ * # Why a framework wrapper at all (vs. consumers using `MDXProvider` directly)
13
+ *
14
+ * Three application-facing reasons:
15
+ *
16
+ * 1. **Single API surface for embedded business sections.** The framework
17
+ * owns the provider boundary so consumers
18
+ * never directly import from `@mdx-js/react` — they ship a single
19
+ * `mdxComponents` object literal and the framework picks the
20
+ * provider implementation. If `@mdx-js/react` ever splits its
21
+ * package or changes its provider semantics (it has shipped
22
+ * breaking changes between major versions before), the swap
23
+ * lands in this file with zero consumer churn.
24
+ * 2. **Bundle-isolation discipline.** `@mdx-js/react` is browser-safe
25
+ * but heavy (`mdx/types.js`, `react-context` propagation). Walling
26
+ * it behind a single re-export keeps the import surface auditable
27
+ * through the package's client-bundle boundary: the `mdx` public entry is
28
+ * checked separately and lists `@mdx-js/react` exactly once.
29
+ * 3. **`disableParentContext: false` (default) is the right default.**
30
+ * MDX 3 lets a nested `MDXProvider` inherit components from the
31
+ * parent provider, which is what we want when the per-blog-post
32
+ * provider sits inside a site-wide MDX provider used by landing-page
33
+ * islands.
34
+ * Pinning the option here makes the framework's posture explicit;
35
+ * a future change documents itself as a breaking framework decision
36
+ * rather than a silent `@mdx-js/react` default flip.
37
+ *
38
+ * # The `components` map shape
39
+ *
40
+ * The map is `Record<string, ComponentType<any>>` — every key is the
41
+ * tag name an MDX file uses (`<CTASection ref="post-cta" />` resolves
42
+ * via the `CTASection` key). Values are React components the consumer
43
+ * authored under their own `src/sections/blog.sections.tsx` and
44
+ * wrapped in `<WebsiteSection sectionRef={…} category={…}>` so the
45
+ * label-key context is established before the section's
46
+ * `useWebsiteLabel(slot)` calls fire. The framework does NOT inspect
47
+ * the shape — discipline lives in the consumer's section registry
48
+ * + the label-pack validation boundary (which proves the locale pack covers
49
+ * each section's expected keys).
50
+ *
51
+ * # Why a `ComponentType<any>` value type and not a stricter generic
52
+ *
53
+ * MDX components receive their own props (children, plus whatever the
54
+ * MDX author writes inline as JSX attributes). The type system cannot
55
+ * narrow this through the provider boundary because the MDX compiler
56
+ * does the routing at compile time. `ComponentType<any>` is the
57
+ * convention `@mdx-js/react` itself uses (`MDXComponents` in
58
+ * `mdx/types.js`) — staying compatible with the upstream type avoids
59
+ * a wrap/unwrap dance at every consumer call site.
60
+ *
61
+ * # Bundle boundary
62
+ *
63
+ * Ships into the React-island bundle (consumed transitively from
64
+ * `BlogPost.tsx` which itself sits behind the consumer's per-page
65
+ * bridge file). MUST stay clear of `node:*` and SSR-tier specifiers
66
+ * — the bundle-isolation test walks `mdx.ts` as one of its entries.
67
+ */
68
+ export interface WebsiteMdxComponentsMap {
69
+ /**
70
+ * Tag-name → component map. The MDX compiler resolves each
71
+ * `<TagName />` in a blog post's body against this object.
72
+ */
73
+ readonly [tagName: string]: ComponentType<any>;
74
+ }
75
+
76
+ export interface WebsiteMdxProviderProps {
77
+ /**
78
+ * Consumer-supplied component overrides. Keys are MDX tag names;
79
+ * values are the React components those tags resolve to.
80
+ */
81
+ components: WebsiteMdxComponentsMap;
82
+ /**
83
+ * MDX subtree (typically the compiled `<MdxBody/>` from a content
84
+ * collection entry). May render any number of provider-scoped
85
+ * components.
86
+ */
87
+ children: ReactNode;
88
+ }
89
+
90
+ export function WebsiteMdxProvider(props: WebsiteMdxProviderProps): ReactNode {
91
+ const { components, children } = props;
92
+ return <MDXProvider components={components}>{children}</MDXProvider>;
93
+ }
94
+ WebsiteMdxProvider.displayName = 'WebsiteMdxProvider';
95
+ /** @wildo_source:part:end saas.website.blog.mdx-provider */
package/src/mdx.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Top-level barrel for the marketing-blog MDX adapter (Phase 7).
3
+ *
4
+ * # Why a dedicated subpath instead of folding into the root barrel
5
+ *
6
+ * `@wildo-ai/saas-website/mdx` ships into the React-island bundle
7
+ * (consumed transitively from a consumer's per-post `.astro` page
8
+ * which imports `<BlogPost>` and renders it client-side). The
9
+ * `@mdx-js/react` runtime adds non-trivial weight to the closure;
10
+ * isolating the entry behind a dedicated subpath:
11
+ *
12
+ * - keeps `index.ts` (the Node + React companion-runtime entry) free
13
+ * of MDX runtime dependencies for sites that ship landing-page-only
14
+ * marketing surfaces;
15
+ * - lets the bundle-isolation test walk this entry independently
16
+ * (see `engine/saas-website/src/__tests__/bundle-isolation.test.ts`
17
+ * `ENTRY_FILES` list) so an accidental `node:fs` import inside
18
+ * `BlogPost.tsx` regresses against the React-bundle constraint
19
+ * without polluting the other entries' allow-lists.
20
+ *
21
+ * Pattern mirrors `astro.ts` / `astro-island.ts` / `config-loader.ts`:
22
+ * one purpose, one barrel, one bundle-isolation entry.
23
+ */
24
+
25
+ export * from './mdx/index';
26
+ /**
27
+ * The blog adapter's per-locale bridge factory
28
+ * (`createWebsiteBlogPostBridgeIsland`). Lives in `astro/` beside
29
+ * `bridge-runtime.tsx` (same closure-over-deps pattern) but is exported
30
+ * from THIS entry — not `astro-island.ts` — because it hard-imports
31
+ * `./mdx/BlogPost`, whose `@mdx-js/react` peer is optional. Exporting it
32
+ * from the mandatory per-page bridge entry made the optional peer a
33
+ * de-facto install requirement for every non-blog site (Vite resolves the
34
+ * whole `export *` graph before tree-shaking). Here, only sites that opt
35
+ * into the blog adapter — and therefore install `@astrojs/mdx` +
36
+ * `@mdx-js/react` — ever resolve it.
37
+ */
38
+ export * from './astro/blog-post-bridge';
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
4
+
5
+ import { BlogPostFrontmatterSchema } from '../blog/blog-post-frontmatter.shared.schemas';
6
+
7
+ const VALID_INPUT = {
8
+ title: 'Why context windows matter for agentic UX',
9
+ summary: 'A short, focused summary of the post for OG / meta description.',
10
+ slug: 'why-context-matters',
11
+ locale: AvailableLanguage.EN,
12
+ publishedAt: '2026-04-17T10:30:00.000Z',
13
+ authorRef: 'paul-cayon',
14
+ tags: ['agents', 'ux'],
15
+ };
16
+
17
+ describe('BlogPostFrontmatterSchema', () => {
18
+ it('parses a well-formed frontmatter', () => {
19
+ const parsed = BlogPostFrontmatterSchema.parse(VALID_INPUT);
20
+ expect(parsed.slug).toBe('why-context-matters');
21
+ expect(parsed.tags).toHaveLength(2);
22
+ });
23
+
24
+ it('rejects malformed slug / authorRef / tags', () => {
25
+ expect(() =>
26
+ BlogPostFrontmatterSchema.parse({ ...VALID_INPUT, slug: 'Why-Context-Matters' }),
27
+ ).toThrow();
28
+ expect(() =>
29
+ BlogPostFrontmatterSchema.parse({ ...VALID_INPUT, authorRef: 'Paul Cayon' }),
30
+ ).toThrow();
31
+ expect(() =>
32
+ BlogPostFrontmatterSchema.parse({ ...VALID_INPUT, tags: ['UX'] }),
33
+ ).toThrow();
34
+ });
35
+
36
+ it('rejects non-ISO publishedAt', () => {
37
+ expect(() =>
38
+ BlogPostFrontmatterSchema.parse({ ...VALID_INPUT, publishedAt: '2026-04-17' }),
39
+ ).toThrow();
40
+ });
41
+
42
+ it('accepts optional coverImageUrl', () => {
43
+ expect(() =>
44
+ BlogPostFrontmatterSchema.parse({
45
+ ...VALID_INPUT,
46
+ coverImageUrl: 'https://cdn.example.com/blog/why-context.jpg',
47
+ }),
48
+ ).not.toThrow();
49
+ });
50
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ /**
4
+ * Boundary test: importing the companion entry MUST NOT pull React into
5
+ * the evaluation graph. Implemented via a tracking Proxy installed on
6
+ * `globalThis` BEFORE the import: any access to a `'react'`-shaped
7
+ * module would evaluate React's runtime side-effects (which include
8
+ * registering a `__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_*`
9
+ * symbol on the global). We assert no such symbol exists post-import.
10
+ *
11
+ * Approach is intentionally indirect — we cannot stub `react` at the
12
+ * Vitest bundler level without losing the boundary check we want to
13
+ * make. Checking the global symbol is React's own canary for
14
+ * "React's runtime has been evaluated".
15
+ */
16
+ describe('companion-exports React-free boundary', () => {
17
+ it('does not boot React when imported in a Node-only context', async () => {
18
+ const reactCanaryBefore = Object.getOwnPropertySymbols(globalThis).filter((s) =>
19
+ String(s).includes('react'),
20
+ );
21
+ const reactGlobalsBefore = Object.keys(globalThis).filter((k) =>
22
+ k.includes('__REACT_DEVTOOLS_GLOBAL_HOOK__'),
23
+ );
24
+
25
+ await import('../../companion-exports');
26
+
27
+ const reactCanaryAfter = Object.getOwnPropertySymbols(globalThis).filter((s) =>
28
+ String(s).includes('react'),
29
+ );
30
+ const reactGlobalsAfter = Object.keys(globalThis).filter((k) =>
31
+ k.includes('__REACT_DEVTOOLS_GLOBAL_HOOK__'),
32
+ );
33
+
34
+ expect(reactCanaryAfter).toEqual(reactCanaryBefore);
35
+ expect(reactGlobalsAfter).toEqual(reactGlobalsBefore);
36
+ });
37
+
38
+ it('exposes the expected schema surface', async () => {
39
+ const mod = await import('../../companion-exports');
40
+ expect(mod.WebsitePageRefSchema).toBeDefined();
41
+ expect(mod.WebsiteSectionRefSchema).toBeDefined();
42
+ expect(mod.WebsiteLabelKeySchema).toBeDefined();
43
+ expect(mod.WebsiteSectionCategorySchema).toBeDefined();
44
+ expect(mod.WebsiteSectionDefinitionSchema).toBeDefined();
45
+ expect(mod.WebsiteDesignTokensSchema).toBeDefined();
46
+ expect(mod.WebsitePageManifestSchema).toBeDefined();
47
+ expect(mod.WebsiteRootConfigSchema).toBeDefined();
48
+ expect(mod.BlogPostFrontmatterSchema).toBeDefined();
49
+ expect(mod.validateLabelPackAgainstManifest).toBeDefined();
50
+ });
51
+ });
@@ -0,0 +1,163 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ WebsiteColorTokensSchema,
5
+ WebsiteDesignTokensSchema,
6
+ WebsiteRadiusTokensSchema,
7
+ } from '../design-tokens/website-design-tokens.shared.schemas';
8
+
9
+ describe('WebsiteColorTokensSchema', () => {
10
+ it('parses every required token as a non-empty string', () => {
11
+ const tokens = {
12
+ background: '#fff',
13
+ foreground: '#111',
14
+ primary: 'oklch(0.7 0.1 200)',
15
+ primaryForeground: '#fff',
16
+ accent: 'rgb(255 94 91)',
17
+ accentForeground: '#fff',
18
+ surface: 'var(--brand-surface)',
19
+ surfaceForeground: '#111',
20
+ border: '#e5e5e5',
21
+ muted: '#f5f5f5',
22
+ mutedForeground: 'currentColor',
23
+ };
24
+ expect(() => WebsiteColorTokensSchema.parse(tokens)).not.toThrow();
25
+ });
26
+
27
+ it('rejects empty strings', () => {
28
+ expect(() =>
29
+ WebsiteColorTokensSchema.parse({
30
+ background: '',
31
+ foreground: '#111',
32
+ primary: '#0070f3',
33
+ primaryForeground: '#fff',
34
+ accent: '#ff5e5b',
35
+ accentForeground: '#fff',
36
+ surface: '#fafafa',
37
+ surfaceForeground: '#111',
38
+ border: '#e5e5e5',
39
+ muted: '#f5f5f5',
40
+ mutedForeground: '#737373',
41
+ }),
42
+ ).toThrow();
43
+ });
44
+ });
45
+
46
+ describe('WebsiteRadiusTokensSchema', () => {
47
+ it('parses every required token as a non-empty string', () => {
48
+ expect(() =>
49
+ WebsiteRadiusTokensSchema.parse({
50
+ none: '0',
51
+ sm: '0.125rem',
52
+ md: '0.375rem',
53
+ lg: '0.75rem',
54
+ xl: '1.5rem',
55
+ full: '9999px',
56
+ }),
57
+ ).not.toThrow();
58
+ });
59
+ });
60
+
61
+ describe('CSS-injection guard on token values (Phase 6 fix H-α)', () => {
62
+ const validRadii = {
63
+ none: '0',
64
+ sm: '0.125rem',
65
+ md: '0.375rem',
66
+ lg: '0.75rem',
67
+ xl: '1.5rem',
68
+ full: '9999px',
69
+ };
70
+ const validColors = {
71
+ background: '#fff',
72
+ foreground: '#111',
73
+ primary: '#0070f3',
74
+ primaryForeground: '#fff',
75
+ accent: '#ff5e5b',
76
+ accentForeground: '#fff',
77
+ surface: '#fafafa',
78
+ surfaceForeground: '#111',
79
+ border: '#e5e5e5',
80
+ muted: '#f5f5f5',
81
+ mutedForeground: '#737373',
82
+ };
83
+
84
+ it.each([
85
+ ['semicolon', 'red; color: blue'],
86
+ ['close-brace', 'red } body { display: none'],
87
+ ['open-brace', 'red { hack: 1'],
88
+ ['block-comment-open', 'red /* hack'],
89
+ ['block-comment-close', 'red */ hack'],
90
+ ['newline', 'red\nfake: 1'],
91
+ ['carriage-return', 'red\rfake: 1'],
92
+ ])('rejects color values containing %s', (_name, value) => {
93
+ expect(() =>
94
+ WebsiteColorTokensSchema.parse({ ...validColors, primary: value }),
95
+ ).toThrow();
96
+ });
97
+
98
+ it.each([
99
+ ['style-element terminator', '#fff</style><svg onload=alert(1)>'],
100
+ ['raw open-angle-bracket', '#fff<script'],
101
+ ['raw close-angle-bracket', '#fff>hack'],
102
+ ])(
103
+ 'rejects color values containing HTML break-out metacharacters (%s) — Phase 6 fourth audit fix H-Χ',
104
+ (_name, value) => {
105
+ expect(() =>
106
+ WebsiteColorTokensSchema.parse({ ...validColors, primary: value }),
107
+ ).toThrow();
108
+ },
109
+ );
110
+
111
+ it.each([
112
+ ['semicolon', '0.5rem; color: red'],
113
+ ['close-brace', '0.5rem }'],
114
+ ['block-comment-open', '0.5rem /*'],
115
+ ['style-element terminator (H-Χ)', '0.5rem</style>'],
116
+ ['raw open-angle-bracket (H-Χ)', '0.5rem<'],
117
+ ['raw close-angle-bracket (H-Χ)', '0.5rem>'],
118
+ ])('rejects radius values containing %s', (_name, value) => {
119
+ expect(() => WebsiteRadiusTokensSchema.parse({ ...validRadii, md: value })).toThrow();
120
+ });
121
+
122
+ it('accepts legitimate var() references with parentheses (no metacharacters)', () => {
123
+ expect(() =>
124
+ WebsiteColorTokensSchema.parse({ ...validColors, primary: 'var(--brand-primary)' }),
125
+ ).not.toThrow();
126
+ });
127
+
128
+ it('accepts oklch() functional notation', () => {
129
+ expect(() =>
130
+ WebsiteColorTokensSchema.parse({ ...validColors, primary: 'oklch(0.7 0.1 200 / 0.5)' }),
131
+ ).not.toThrow();
132
+ });
133
+ });
134
+
135
+ describe('WebsiteDesignTokensSchema', () => {
136
+ it('parses the colors+radii bundle', () => {
137
+ const parsed = WebsiteDesignTokensSchema.parse({
138
+ colors: {
139
+ background: '#fff',
140
+ foreground: '#111',
141
+ primary: '#0070f3',
142
+ primaryForeground: '#fff',
143
+ accent: '#ff5e5b',
144
+ accentForeground: '#fff',
145
+ surface: '#fafafa',
146
+ surfaceForeground: '#111',
147
+ border: '#e5e5e5',
148
+ muted: '#f5f5f5',
149
+ mutedForeground: '#737373',
150
+ },
151
+ radii: {
152
+ none: '0',
153
+ sm: '0.125rem',
154
+ md: '0.375rem',
155
+ lg: '0.75rem',
156
+ xl: '1.5rem',
157
+ full: '9999px',
158
+ },
159
+ });
160
+ expect(parsed.colors.primary).toBe('#0070f3');
161
+ expect(parsed.radii.full).toBe('9999px');
162
+ });
163
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ WebsiteLabelKeySchema,
5
+ composeWebsiteLabelKey,
6
+ } from '../label-keys/website-label-key.schemas';
7
+ import { WebsitePageRefSchema } from '../refs/page-ref.schemas';
8
+ import { WebsiteSectionRefSchema } from '../refs/section-ref.schemas';
9
+
10
+ describe('WebsiteLabelKeySchema', () => {
11
+ it('accepts simple page.section.slot keys', () => {
12
+ expect(WebsiteLabelKeySchema.parse('website.landing.hero.headline')).toBe(
13
+ 'website.landing.hero.headline',
14
+ );
15
+ });
16
+
17
+ it('accepts dot-nested and bracket-indexed slots', () => {
18
+ expect(WebsiteLabelKeySchema.parse('website.landing.hero.cta.primary')).toBeDefined();
19
+ expect(WebsiteLabelKeySchema.parse('website.pricing.tiers.tiers[0].name')).toBeDefined();
20
+ expect(
21
+ WebsiteLabelKeySchema.parse('website.contact.form.field.email-placeholder'),
22
+ ).toBeDefined();
23
+ });
24
+
25
+ it('rejects non-website prefixes and missing parts', () => {
26
+ expect(() => WebsiteLabelKeySchema.parse('app.landing.hero.headline')).toThrow();
27
+ expect(() => WebsiteLabelKeySchema.parse('website.landing.hero')).toThrow();
28
+ expect(() => WebsiteLabelKeySchema.parse('website..hero.headline')).toThrow();
29
+ expect(() => WebsiteLabelKeySchema.parse('website.Landing.hero.headline')).toThrow();
30
+ });
31
+ });
32
+
33
+ describe('composeWebsiteLabelKey', () => {
34
+ it('round-trips through the full ref → key pipeline', () => {
35
+ const pageRef = WebsitePageRefSchema.parse('landing');
36
+ const sectionRef = WebsiteSectionRefSchema.parse('hero');
37
+ const key = composeWebsiteLabelKey({ pageRef, sectionRef, slotPath: 'headline' });
38
+ expect(key).toBe('website.landing.hero.headline');
39
+ });
40
+
41
+ it('throws on stale refs that would compose an invalid key', () => {
42
+ expect(() =>
43
+ composeWebsiteLabelKey({ pageRef: 'Landing', sectionRef: 'hero', slotPath: 'headline' }),
44
+ ).toThrow();
45
+ });
46
+ });
@@ -0,0 +1,146 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { WebsiteLabelKeySchema, type WebsiteLabelKey } from '../label-keys/website-label-key.schemas';
4
+ import { WebsitePageManifestSchema, type WebsitePageManifest } from '../manifests/website-page-manifest.shared.schemas';
5
+ import { WebsitePageRefSchema } from '../refs/page-ref.schemas';
6
+ import { WebsiteSectionRefSchema } from '../refs/section-ref.schemas';
7
+ import {
8
+ validateLabelPackAgainstManifest,
9
+ type ExpectedLabelKeysBySectionRef,
10
+ type LabelPack,
11
+ } from '../validators/label-pack.validator';
12
+
13
+ const NoopComponent = () => null;
14
+
15
+ const HERO_REF = WebsiteSectionRefSchema.parse('hero');
16
+ const CTA_REF = WebsiteSectionRefSchema.parse('cta');
17
+
18
+ const HERO_KEYS: readonly WebsiteLabelKey[] = [
19
+ WebsiteLabelKeySchema.parse('website.landing.hero.headline'),
20
+ WebsiteLabelKeySchema.parse('website.landing.hero.subheadline'),
21
+ ];
22
+ const CTA_KEYS: readonly WebsiteLabelKey[] = [
23
+ WebsiteLabelKeySchema.parse('website.landing.cta.headline'),
24
+ WebsiteLabelKeySchema.parse('website.landing.cta.primary-cta'),
25
+ ];
26
+
27
+ // The SCHEMA infers `pageComponent` as `unknown` (it is validated as an opaque value), while the
28
+ // declared `WebsitePageManifest` the validator takes says `ComponentType`. The parse still runs —
29
+ // this fixture is checked against the schema — and the annotation names the one field on which
30
+ // the two disagree.
31
+ const MANIFEST: WebsitePageManifest = WebsitePageManifestSchema.parse({
32
+ ref: WebsitePageRefSchema.parse('landing'),
33
+ routePath: '/',
34
+ pageComponent: NoopComponent,
35
+ sectionRefs: [HERO_REF, CTA_REF],
36
+ metaLabelKeys: {
37
+ titleKey: WebsiteLabelKeySchema.parse('website.landing.meta.title'),
38
+ descriptionKey: WebsiteLabelKeySchema.parse('website.landing.meta.description'),
39
+ },
40
+ }) as WebsitePageManifest;
41
+
42
+ const EXPECTED: ExpectedLabelKeysBySectionRef = new Map([
43
+ [HERO_REF, HERO_KEYS],
44
+ [CTA_REF, CTA_KEYS],
45
+ ]);
46
+
47
+ describe('validateLabelPackAgainstManifest', () => {
48
+ it('returns empty diagnostics for a complete pack', () => {
49
+ const pack: LabelPack = {
50
+ 'website.landing.meta.title': 'Welcome',
51
+ 'website.landing.meta.description': 'A great product',
52
+ 'website.landing.hero.headline': 'Hello',
53
+ 'website.landing.hero.subheadline': 'World',
54
+ 'website.landing.cta.headline': 'Try now',
55
+ 'website.landing.cta.primary-cta': 'Sign up',
56
+ };
57
+ const result = validateLabelPackAgainstManifest([MANIFEST], EXPECTED, pack);
58
+ expect(result.missing).toEqual([]);
59
+ expect(result.orphaned).toEqual([]);
60
+ expect(result.unmappedSectionRefs).toEqual([]);
61
+ });
62
+
63
+ it('reports missing keys (sorted)', () => {
64
+ const pack: LabelPack = {
65
+ 'website.landing.meta.title': 'Welcome',
66
+ 'website.landing.meta.description': 'A great product',
67
+ 'website.landing.hero.headline': 'Hello',
68
+ };
69
+ const result = validateLabelPackAgainstManifest([MANIFEST], EXPECTED, pack);
70
+ expect(result.missing).toEqual([
71
+ 'website.landing.cta.headline',
72
+ 'website.landing.cta.primary-cta',
73
+ 'website.landing.hero.subheadline',
74
+ ]);
75
+ expect(result.orphaned).toEqual([]);
76
+ });
77
+
78
+ it('reports orphaned keys (sorted)', () => {
79
+ const pack: LabelPack = {
80
+ 'website.landing.meta.title': 'Welcome',
81
+ 'website.landing.meta.description': 'A great product',
82
+ 'website.landing.hero.headline': 'Hello',
83
+ 'website.landing.hero.subheadline': 'World',
84
+ 'website.landing.cta.headline': 'Try now',
85
+ 'website.landing.cta.primary-cta': 'Sign up',
86
+ 'website.landing.hero.stale-key-1': 'orphan',
87
+ 'website.landing.cta.stale-key-2': 'orphan',
88
+ };
89
+ const result = validateLabelPackAgainstManifest([MANIFEST], EXPECTED, pack);
90
+ expect(result.missing).toEqual([]);
91
+ expect(result.orphaned).toEqual([
92
+ 'website.landing.cta.stale-key-2',
93
+ 'website.landing.hero.stale-key-1',
94
+ ]);
95
+ });
96
+
97
+ it('surfaces unmapped section refs separately from missing keys', () => {
98
+ const expectedPartial: ExpectedLabelKeysBySectionRef = new Map([[HERO_REF, HERO_KEYS]]);
99
+ const pack: LabelPack = {
100
+ 'website.landing.meta.title': 'Welcome',
101
+ 'website.landing.meta.description': 'A great product',
102
+ 'website.landing.hero.headline': 'Hello',
103
+ 'website.landing.hero.subheadline': 'World',
104
+ };
105
+ const result = validateLabelPackAgainstManifest([MANIFEST], expectedPartial, pack);
106
+ expect(result.unmappedSectionRefs).toEqual([CTA_REF]);
107
+ expect(result.missing).toEqual([]);
108
+ expect(result.orphaned).toEqual([]);
109
+ });
110
+
111
+ it('treats chromeLabelKeys as expected — declared chrome keys are NOT orphaned', () => {
112
+ const chromeKeys: readonly WebsiteLabelKey[] = [
113
+ WebsiteLabelKeySchema.parse('website.chrome.header.brand'),
114
+ WebsiteLabelKeySchema.parse('website.chrome.footer.copyright'),
115
+ ];
116
+ const pack: LabelPack = {
117
+ 'website.landing.meta.title': 'Welcome',
118
+ 'website.landing.meta.description': 'A great product',
119
+ 'website.landing.hero.headline': 'Hello',
120
+ 'website.landing.hero.subheadline': 'World',
121
+ 'website.landing.cta.headline': 'Try now',
122
+ 'website.landing.cta.primary-cta': 'Sign up',
123
+ 'website.chrome.header.brand': 'Acme',
124
+ 'website.chrome.footer.copyright': '© 2026',
125
+ };
126
+ const result = validateLabelPackAgainstManifest([MANIFEST], EXPECTED, pack, chromeKeys);
127
+ expect(result.missing).toEqual([]);
128
+ expect(result.orphaned).toEqual([]);
129
+ });
130
+
131
+ it('reports missing chromeLabelKeys alongside missing section keys', () => {
132
+ const chromeKeys: readonly WebsiteLabelKey[] = [
133
+ WebsiteLabelKeySchema.parse('website.chrome.header.brand'),
134
+ ];
135
+ const pack: LabelPack = {
136
+ 'website.landing.meta.title': 'Welcome',
137
+ 'website.landing.meta.description': 'A great product',
138
+ 'website.landing.hero.headline': 'Hello',
139
+ 'website.landing.hero.subheadline': 'World',
140
+ 'website.landing.cta.headline': 'Try now',
141
+ 'website.landing.cta.primary-cta': 'Sign up',
142
+ };
143
+ const result = validateLabelPackAgainstManifest([MANIFEST], EXPECTED, pack, chromeKeys);
144
+ expect(result.missing).toEqual(['website.chrome.header.brand']);
145
+ });
146
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';
4
+ import { WebsitePageManifestSchema } from '../manifests/website-page-manifest.shared.schemas';
5
+ import { WebsitePageRefSchema } from '../refs/page-ref.schemas';
6
+ import { WebsiteSectionRefSchema } from '../refs/section-ref.schemas';
7
+
8
+ const NoopPageComponent = () => null;
9
+
10
+ const VALID_MANIFEST_INPUT = {
11
+ ref: WebsitePageRefSchema.parse('landing'),
12
+ routePath: '/' as unknown,
13
+ pageComponent: NoopPageComponent,
14
+ sectionRefs: [WebsiteSectionRefSchema.parse('hero'), WebsiteSectionRefSchema.parse('cta')],
15
+ metaLabelKeys: {
16
+ titleKey: WebsiteLabelKeySchema.parse('website.landing.meta.title'),
17
+ descriptionKey: WebsiteLabelKeySchema.parse('website.landing.meta.description'),
18
+ },
19
+ };
20
+
21
+ describe('WebsitePageManifestSchema', () => {
22
+ it('parses a well-formed manifest', () => {
23
+ const parsed = WebsitePageManifestSchema.parse(VALID_MANIFEST_INPUT);
24
+ expect(parsed.ref).toBe('landing');
25
+ expect(parsed.sectionRefs).toHaveLength(2);
26
+ });
27
+
28
+ it('parses Astro-style parameter routes', () => {
29
+ expect(() =>
30
+ WebsitePageManifestSchema.parse({
31
+ ...VALID_MANIFEST_INPUT,
32
+ routePath: '/blog/[slug]',
33
+ }),
34
+ ).not.toThrow();
35
+ });
36
+
37
+ it('rejects duplicate section refs', () => {
38
+ expect(() =>
39
+ WebsitePageManifestSchema.parse({
40
+ ...VALID_MANIFEST_INPUT,
41
+ sectionRefs: [WebsiteSectionRefSchema.parse('hero'), WebsiteSectionRefSchema.parse('hero')],
42
+ }),
43
+ ).toThrow(/Duplicate sectionRef/);
44
+ });
45
+
46
+ it('rejects meta label keys not rooted under the page ref', () => {
47
+ expect(() =>
48
+ WebsitePageManifestSchema.parse({
49
+ ...VALID_MANIFEST_INPUT,
50
+ metaLabelKeys: {
51
+ titleKey: WebsiteLabelKeySchema.parse('website.pricing.meta.title'),
52
+ descriptionKey: WebsiteLabelKeySchema.parse('website.landing.meta.description'),
53
+ },
54
+ }),
55
+ ).toThrow(/must be rooted under the page ref/);
56
+ });
57
+
58
+ it('rejects empty section ref lists', () => {
59
+ expect(() =>
60
+ WebsitePageManifestSchema.parse({
61
+ ...VALID_MANIFEST_INPUT,
62
+ sectionRefs: [],
63
+ }),
64
+ ).toThrow();
65
+ });
66
+ });