@wildo-ai/saas-website 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/dist/esm/astro/blog-post-bridge.d.ts +23 -20
  2. package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -1
  3. package/dist/esm/astro/blog-post-bridge.js +13 -6
  4. package/dist/esm/astro/blog-post-bridge.js.map +1 -1
  5. package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -1
  6. package/dist/esm/astro/bridge-runtime.d.ts +55 -26
  7. package/dist/esm/astro/bridge-runtime.d.ts.map +1 -1
  8. package/dist/esm/astro/bridge-runtime.js +21 -7
  9. package/dist/esm/astro/bridge-runtime.js.map +1 -1
  10. package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -1
  11. package/dist/esm/astro/headers-renderer.d.ts.map +1 -1
  12. package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -1
  13. package/dist/esm/astro/label-pack-loader.d.ts +4 -4
  14. package/dist/esm/astro/label-pack-loader.d.ts.map +1 -1
  15. package/dist/esm/astro/label-pack-loader.js +14 -4
  16. package/dist/esm/astro/label-pack-loader.js.map +1 -1
  17. package/dist/esm/astro/llms-txt-server.d.ts.map +1 -1
  18. package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -1
  19. package/dist/esm/astro/robots-renderer.d.ts +9 -8
  20. package/dist/esm/astro/robots-renderer.d.ts.map +1 -1
  21. package/dist/esm/astro/robots-renderer.js +15 -15
  22. package/dist/esm/astro/robots-renderer.js.map +1 -1
  23. package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -1
  24. package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -1
  25. package/dist/esm/astro/website-page-head.renderer.d.ts +1 -1
  26. package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -1
  27. package/dist/esm/astro/website-page-head.renderer.js +2 -1
  28. package/dist/esm/astro/website-page-head.renderer.js.map +1 -1
  29. package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -1
  30. package/dist/esm/astro/website-site-context.d.ts +5 -2
  31. package/dist/esm/astro/website-site-context.d.ts.map +1 -1
  32. package/dist/esm/astro/website-site-context.js.map +1 -1
  33. package/dist/esm/astro-island.d.ts.map +1 -1
  34. package/dist/esm/astro.d.ts.map +1 -1
  35. package/dist/esm/companion-exports.d.ts +8 -15
  36. package/dist/esm/companion-exports.d.ts.map +1 -1
  37. package/dist/esm/companion-exports.js +8 -15
  38. package/dist/esm/companion-exports.js.map +1 -1
  39. package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -1
  40. package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -1
  41. package/dist/esm/config/define-website-config.d.ts +3 -2
  42. package/dist/esm/config/define-website-config.d.ts.map +1 -1
  43. package/dist/esm/config/define-website-config.js +10 -10
  44. package/dist/esm/config/define-website-config.js.map +1 -1
  45. package/dist/esm/config/index.d.ts.map +1 -1
  46. package/dist/esm/config/load-website-config.d.ts +6 -0
  47. package/dist/esm/config/load-website-config.d.ts.map +1 -1
  48. package/dist/esm/config/load-website-config.js +20 -10
  49. package/dist/esm/config/load-website-config.js.map +1 -1
  50. package/dist/esm/config/load-website-pricing-catalog.d.ts +13 -0
  51. package/dist/esm/config/load-website-pricing-catalog.d.ts.map +1 -0
  52. package/dist/esm/config/load-website-pricing-catalog.js +34 -0
  53. package/dist/esm/config/load-website-pricing-catalog.js.map +1 -0
  54. package/dist/esm/config/load-website-site-context.d.ts +53 -0
  55. package/dist/esm/config/load-website-site-context.d.ts.map +1 -0
  56. package/dist/esm/config/load-website-site-context.js +98 -0
  57. package/dist/esm/config/load-website-site-context.js.map +1 -0
  58. package/dist/esm/config/wildo-website-config.schemas.d.ts +10 -5
  59. package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -1
  60. package/dist/esm/config/wildo-website-config.schemas.js +10 -5
  61. package/dist/esm/config/wildo-website-config.schemas.js.map +1 -1
  62. package/dist/esm/config-loader.d.ts +2 -0
  63. package/dist/esm/config-loader.d.ts.map +1 -1
  64. package/dist/esm/config-loader.js +2 -0
  65. package/dist/esm/config-loader.js.map +1 -1
  66. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +26 -6
  67. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -1
  68. package/dist/esm/core/anonymous-session/InboundContactForm.js +51 -33
  69. package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -1
  70. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +14 -3
  71. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -1
  72. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js +28 -13
  73. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -1
  74. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts +24 -1
  75. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -1
  76. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js +46 -3
  77. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -1
  78. package/dist/esm/core/anonymous-session/website-submission-challenge.d.ts +36 -0
  79. package/dist/esm/core/anonymous-session/website-submission-challenge.d.ts.map +1 -0
  80. package/dist/esm/core/anonymous-session/website-submission-challenge.js +69 -0
  81. package/dist/esm/core/anonymous-session/website-submission-challenge.js.map +1 -0
  82. package/dist/esm/core/consent/WebsiteConsentBanner.d.ts +6 -0
  83. package/dist/esm/core/consent/WebsiteConsentBanner.d.ts.map +1 -0
  84. package/dist/esm/core/consent/WebsiteConsentBanner.js +129 -0
  85. package/dist/esm/core/consent/WebsiteConsentBanner.js.map +1 -0
  86. package/dist/esm/core/consent/WebsiteConsentContext.d.ts +85 -0
  87. package/dist/esm/core/consent/WebsiteConsentContext.d.ts.map +1 -0
  88. package/dist/esm/core/consent/WebsiteConsentContext.js +89 -0
  89. package/dist/esm/core/consent/WebsiteConsentContext.js.map +1 -0
  90. package/dist/esm/core/consent/WebsiteConsentGate.d.ts +27 -0
  91. package/dist/esm/core/consent/WebsiteConsentGate.d.ts.map +1 -0
  92. package/dist/esm/core/consent/WebsiteConsentGate.js +9 -0
  93. package/dist/esm/core/consent/WebsiteConsentGate.js.map +1 -0
  94. package/dist/esm/core/consent/useWebsiteConsent.d.ts +10 -0
  95. package/dist/esm/core/consent/useWebsiteConsent.d.ts.map +1 -0
  96. package/dist/esm/core/consent/useWebsiteConsent.js +17 -0
  97. package/dist/esm/core/consent/useWebsiteConsent.js.map +1 -0
  98. package/dist/esm/core/consent/website-consent-storage.d.ts +22 -0
  99. package/dist/esm/core/consent/website-consent-storage.d.ts.map +1 -0
  100. package/dist/esm/core/consent/website-consent-storage.js +102 -0
  101. package/dist/esm/core/consent/website-consent-storage.js.map +1 -0
  102. package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -1
  103. package/dist/esm/core/contexts/WebsitePageContext.js.map +1 -1
  104. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +33 -6
  105. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -1
  106. package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -1
  107. package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -1
  108. package/dist/esm/core/contexts/WebsiteSectionContext.js.map +1 -1
  109. package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -1
  110. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +18 -0
  111. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -1
  112. package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -2
  113. package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -1
  114. package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -1
  115. package/dist/esm/core/external-providers/WebsiteProviderComponent.d.ts +57 -0
  116. package/dist/esm/core/external-providers/WebsiteProviderComponent.d.ts.map +1 -0
  117. package/dist/esm/core/external-providers/WebsiteProviderComponent.js +56 -0
  118. package/dist/esm/core/external-providers/WebsiteProviderComponent.js.map +1 -0
  119. package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts +1 -2
  120. package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -1
  121. package/dist/esm/core/external-providers/frontend-provider-registry.website.js.map +1 -1
  122. package/dist/esm/core/external-providers/provider-component-registry.website.d.ts +63 -0
  123. package/dist/esm/core/external-providers/provider-component-registry.website.d.ts.map +1 -0
  124. package/dist/esm/core/external-providers/provider-component-registry.website.js +68 -0
  125. package/dist/esm/core/external-providers/provider-component-registry.website.js.map +1 -0
  126. package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts +36 -0
  127. package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts.map +1 -0
  128. package/dist/esm/core/external-providers/useWebsiteProviderComponent.js +59 -0
  129. package/dist/esm/core/external-providers/useWebsiteProviderComponent.js.map +1 -0
  130. package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts +43 -10
  131. package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts.map +1 -1
  132. package/dist/esm/core/external-providers/useWebsiteProviderScripts.js +92 -29
  133. package/dist/esm/core/external-providers/useWebsiteProviderScripts.js.map +1 -1
  134. package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts +11 -0
  135. package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts.map +1 -0
  136. package/dist/esm/core/external-providers/website-provider-sdk-activations.js +14 -0
  137. package/dist/esm/core/external-providers/website-provider-sdk-activations.js.map +1 -0
  138. package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -1
  139. package/dist/esm/core/factories/define-website-section.d.ts +3 -3
  140. package/dist/esm/core/factories/define-website-section.d.ts.map +1 -1
  141. package/dist/esm/core/factories/define-website-section.js +3 -3
  142. package/dist/esm/core/factories/define-website-section.js.map +1 -1
  143. package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -1
  144. package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -1
  145. package/dist/esm/core/layouts/WebsitePageLayout.d.ts +21 -1
  146. package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -1
  147. package/dist/esm/core/layouts/WebsitePageLayout.js +39 -8
  148. package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -1
  149. package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -1
  150. package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -1
  151. package/dist/esm/index.d.ts +36 -65
  152. package/dist/esm/index.d.ts.map +1 -1
  153. package/dist/esm/index.js +36 -65
  154. package/dist/esm/index.js.map +1 -1
  155. package/dist/esm/mdx/BlogPost.d.ts.map +1 -1
  156. package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -1
  157. package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -1
  158. package/dist/esm/mdx/index.d.ts.map +1 -1
  159. package/dist/esm/mdx.d.ts.map +1 -1
  160. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -1
  161. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts +35 -6
  162. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -1
  163. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js +39 -5
  164. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -1
  165. package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.d.ts +61 -0
  166. package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.d.ts.map +1 -0
  167. package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.js +73 -0
  168. package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.js.map +1 -0
  169. package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -1
  170. package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.d.ts +32 -0
  171. package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.d.ts.map +1 -0
  172. package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js +36 -0
  173. package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js.map +1 -0
  174. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -1
  175. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +6 -5
  176. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -1
  177. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js +8 -7
  178. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -1
  179. package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -1
  180. package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -1
  181. package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -1
  182. package/dist/esm/schemas/sections/website-section-category.shared.js.map +1 -1
  183. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +14 -7
  184. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -1
  185. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +14 -7
  186. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -1
  187. package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.d.ts +57 -0
  188. package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.d.ts.map +1 -0
  189. package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js +76 -0
  190. package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js.map +1 -0
  191. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +23 -0
  192. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -1
  193. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js +20 -2
  194. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -1
  195. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts +7 -5
  196. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -1
  197. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -1
  198. package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -1
  199. package/dist/tsconfig.build.tsbuildinfo +1 -1
  200. package/package.json +6 -29
  201. package/src/__tests__/bundle-isolation.test.ts +19 -22
  202. package/src/astro/__tests__/blog-post-bridge.test.tsx +58 -1
  203. package/src/astro/__tests__/bridge-runtime.test.tsx +44 -1
  204. package/src/astro/__tests__/label-pack-loader.test.ts +29 -1
  205. package/src/astro/blog-post-bridge.tsx +36 -26
  206. package/src/astro/bridge-runtime.tsx +79 -33
  207. package/src/astro/label-pack-loader.ts +12 -4
  208. package/src/astro/robots-renderer.ts +15 -15
  209. package/src/astro/website-page-head.renderer.ts +2 -2
  210. package/src/astro/website-site-context.ts +5 -2
  211. package/src/companion-exports.ts +8 -15
  212. package/src/config/__tests__/website-build-origins.test.ts +52 -0
  213. package/src/config/define-website-config.ts +10 -10
  214. package/src/config/load-website-config.ts +27 -15
  215. package/src/config/load-website-pricing-catalog.ts +52 -0
  216. package/src/config/load-website-site-context.ts +145 -0
  217. package/src/config/wildo-website-config.schemas.ts +10 -5
  218. package/src/config-loader.ts +2 -0
  219. package/src/core/__tests__/WebsitePageLayout.test.tsx +1 -1
  220. package/src/core/__tests__/contexts.test.tsx +20 -1
  221. package/src/core/__tests__/website-consent.test.tsx +312 -0
  222. package/src/core/anonymous-session/InboundContactForm.tsx +100 -45
  223. package/src/core/anonymous-session/__tests__/InboundContactForm.labels.test.tsx +108 -0
  224. package/src/core/anonymous-session/__tests__/InboundContactForm.submission-challenge.test.tsx +139 -0
  225. package/src/core/anonymous-session/__tests__/inbound-contact-form.schema.test.ts +11 -4
  226. package/src/core/anonymous-session/inbound-contact-form.schema.ts +30 -13
  227. package/src/core/anonymous-session/website-anonymous-session-client.ts +52 -3
  228. package/src/core/anonymous-session/website-submission-challenge.ts +99 -0
  229. package/src/core/consent/WebsiteConsentBanner.tsx +223 -0
  230. package/src/core/consent/WebsiteConsentContext.tsx +164 -0
  231. package/src/core/consent/WebsiteConsentGate.tsx +34 -0
  232. package/src/core/consent/useWebsiteConsent.ts +18 -0
  233. package/src/core/consent/website-consent-storage.ts +116 -0
  234. package/src/core/contexts/WebsiteRuntimeContext.tsx +33 -6
  235. package/src/core/contexts/useWebsiteRuntime.ts +33 -2
  236. package/src/core/external-providers/WebsiteProviderComponent.tsx +153 -0
  237. package/src/core/external-providers/__tests__/frontend-provider-registry.website.test.ts +4 -0
  238. package/src/core/external-providers/__tests__/website-provider-component.test.tsx +238 -0
  239. package/src/core/external-providers/__tests__/website-provider-sdks.test.tsx +72 -0
  240. package/src/core/external-providers/frontend-provider-registry.website.ts +1 -2
  241. package/src/core/external-providers/provider-component-registry.website.ts +115 -0
  242. package/src/core/external-providers/useWebsiteProviderComponent.ts +76 -0
  243. package/src/core/external-providers/useWebsiteProviderScripts.ts +114 -40
  244. package/src/core/external-providers/website-provider-sdk-activations.ts +23 -0
  245. package/src/core/factories/define-website-section.ts +3 -3
  246. package/src/core/layouts/WebsitePageLayout.tsx +75 -7
  247. package/src/index.ts +38 -65
  248. package/src/schemas/__tests__/design-token-css-property.test.ts +72 -0
  249. package/src/schemas/__tests__/pricing-offer-structured-data.test.ts +122 -0
  250. package/src/schemas/__tests__/structured-data-reconciliation.test.ts +35 -0
  251. package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +45 -5
  252. package/src/schemas/label-keys/__tests__/scaffolded-consent-label-pack.parity.test.ts +83 -0
  253. package/src/schemas/label-keys/website-consent-label-keys.schemas.ts +78 -0
  254. package/src/schemas/label-keys/website-provider-component-label-keys.schemas.ts +40 -0
  255. package/src/schemas/manifests/website-root-config.shared.schemas.ts +10 -9
  256. package/src/schemas/sections/website-section-definition.shared.schemas.ts +14 -7
  257. package/src/schemas/structured-data/pricing-offer-structured-data.shared.ts +144 -0
  258. package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +46 -1
  259. package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +7 -5
  260. package/dist/esm/core/external-providers/provider-scripts.website.d.ts +0 -68
  261. package/dist/esm/core/external-providers/provider-scripts.website.d.ts.map +0 -1
  262. package/dist/esm/core/external-providers/provider-scripts.website.js +0 -99
  263. package/dist/esm/core/external-providers/provider-scripts.website.js.map +0 -1
  264. package/src/core/external-providers/__tests__/provider-scripts.website.test.ts +0 -146
  265. package/src/core/external-providers/provider-scripts.website.ts +0 -126
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wildo-ai/saas-website",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "SaaS marketing/landing website engine — schemas, helpers, layout/primitive placeholders, Astro/MDX adapters, and SEO/AEO/GEO builder library. Consumers author all business sections themselves via the defineWebsiteSection factory.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Wildo AI",
@@ -65,10 +65,10 @@
65
65
  "test:watch": "vitest"
66
66
  },
67
67
  "dependencies": {
68
- "@wildo-ai/external-connectors-public": "1.1.4",
69
- "@wildo-ai/platform-config-lib": "1.1.4",
70
- "@wildo-ai/presets-components-models": "1.1.4",
71
- "@wildo-ai/saas-models": "1.1.4",
68
+ "@wildo-ai/external-connectors-public": "1.1.6",
69
+ "@wildo-ai/platform-config-lib": "1.1.6",
70
+ "@wildo-ai/presets-components-models": "1.1.6",
71
+ "@wildo-ai/saas-models": "1.1.6",
72
72
  "yaml": "^2.3.4"
73
73
  },
74
74
  "devDependencies": {
@@ -96,19 +96,11 @@
96
96
  },
97
97
  "peerDependencies": {
98
98
  "@astrojs/react": "^3.0.0",
99
- "@astrojs/sitemap": "^3.7.2",
100
99
  "@mdx-js/react": "^3.0.0",
101
100
  "astro": "^4.0.0",
102
- "axios": "^1.6.8",
103
- "clsx": "^2.1.0",
104
- "jsonwebtoken": "^9.0.2",
105
101
  "react": "^19.0.0",
106
102
  "react-dom": "^19.0.0",
107
- "react-intl": "^8.0.0",
108
- "reflect-metadata": "^0.2.2",
109
- "socket.io-client": "^4.7.5",
110
- "tailwind-merge": "^3.3.1",
111
- "tailwind-variants": "^3.1.0"
103
+ "zod": "^4.3.6"
112
104
  },
113
105
  "peerDependenciesMeta": {
114
106
  "astro": {
@@ -117,23 +109,8 @@
117
109
  "@astrojs/react": {
118
110
  "optional": true
119
111
  },
120
- "@astrojs/sitemap": {
121
- "optional": true
122
- },
123
112
  "@mdx-js/react": {
124
113
  "optional": true
125
- },
126
- "axios": {
127
- "optional": true
128
- },
129
- "jsonwebtoken": {
130
- "optional": true
131
- },
132
- "reflect-metadata": {
133
- "optional": true
134
- },
135
- "socket.io-client": {
136
- "optional": true
137
114
  }
138
115
  },
139
116
  "repository": {
@@ -279,29 +279,14 @@ const ALLOWED_BARE_SPECIFIERS: ReadonlySet<string> = new Set([
279
279
  * `FORBIDDEN_SPECIFIERS`.
280
280
  */
281
281
  '@wildo-ai/zod-decorators',
282
- /**
283
- * SSR / Astro-middleware tier. These four specifiers support server-side code paths
284
- * the marketing engine genuinely needs:
285
- * - `axios` — outbound HTTP from Astro middleware (proxy to
286
- * the SaaS backend, contact-form forwarding, webhook fan-out).
287
- * - `jsonwebtoken` — verify cross-frontend handoff JWTs in
288
- * middleware before letting a request reach a logged-in surface.
289
- * - `socket.io-client` — real-time push (e.g. live preview / status
290
- * events from the SaaS app to the marketing site).
291
- * - `reflect-metadata` — decorator metadata runtime; required
292
- * by any future build-time consumer of `@wildo-ai/zod-decorators`
293
- * metadata (the import IS side-effecting — it mutates
294
- * `globalThis.Reflect`; that side effect is acceptable in an
295
- * SSR context but would still be objectionable in a React
296
- * island bundle).
297
- * Allowing them here is a deliberate trade-off: this test no
298
- * longer guarantees a hard browser-bundle size budget on its own.
299
- * Application import discipline keeps the React-island side lean.
282
+ /*
283
+ * `axios`, `jsonwebtoken`, `socket.io-client` and `reflect-metadata` were allowed here from
284
+ * 2026-04-19 for an SSR middleware tier (backend proxying, handoff-JWT verification, live push)
285
+ * that was never built. Removed 2026-09-25: no source in this package imports any of them, and an
286
+ * allow-list wider than the code is a door nothing guards — the next import of one would pass
287
+ * this test unexamined. The tier that does exist (contact forms, anonymous sessions, consent
288
+ * recording) uses `fetch`. Re-add a specifier in the change that first imports it.
300
289
  */
301
- 'axios',
302
- 'jsonwebtoken',
303
- 'socket.io-client',
304
- 'reflect-metadata',
305
290
  /**
306
291
  * SSR / Astro-build-time tier. The
307
292
  * `astro.ts` sub-entry exposes the label-pack loader, which
@@ -498,6 +483,18 @@ describe('saas-website — bundle-isolation boundary (D12)', () => {
498
483
  }
499
484
  });
500
485
 
486
+ it('walks the provider-component loader (#1703), so everything it imports is held to this boundary', () => {
487
+ // The loader reaches vendor code only through a provider module's DYNAMIC `import()`; these files
488
+ // must stay inside the walk so a static vendor import added to them is caught here.
489
+ for (const file of [
490
+ 'core/external-providers/provider-component-registry.website.ts',
491
+ 'core/external-providers/useWebsiteProviderComponent.ts',
492
+ 'core/external-providers/WebsiteProviderComponent.tsx',
493
+ ]) {
494
+ expect(visited.has(resolve(SRC_ROOT, file)), `Closure did not visit ${file}`).toBe(true);
495
+ }
496
+ });
497
+
501
498
  it.each([...FORBIDDEN_SPECIFIERS])(
502
499
  'no file in the saas-website closure imports the forbidden specifier "%s"',
503
500
  (forbidden) => {
@@ -1,10 +1,18 @@
1
+ import type { GeneratedWebsiteProviderModules } from '../../core/external-providers/frontend-provider-registry.website';
1
2
  import '@testing-library/jest-dom/vitest';
2
3
  import { render, screen } from '@testing-library/react';
3
4
  import { useMDXComponents } from '@mdx-js/react';
4
5
  import React, { type ComponentType } from 'react';
5
6
  import { describe, expect, it } from 'vitest';
6
7
 
7
- import { BUILTIN_PROVIDER_CAPABILITY, defineProviderMetadata, ProviderOriginKind, ProviderTier } from '@wildo-ai/saas-models/public-runtime';
8
+ import {
9
+ BUILTIN_PROVIDER_CAPABILITY,
10
+ defineFrontendPublicConfigSchema,
11
+ defineProviderMetadata,
12
+ FrontendPublicConfigValueKind,
13
+ ProviderOriginKind,
14
+ ProviderTier,
15
+ } from '@wildo-ai/saas-models/public-runtime';
8
16
  import type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';
9
17
  import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
10
18
 
@@ -56,6 +64,15 @@ const SKIP_LABEL_PACK = {
56
64
  // Typed as the real block rather than `as const`: the prop takes `FrontendProvidersBlock`,
57
65
  // whose `providerCapabilities` / `protocols` are mutable `string[]`, so a `readonly` tuple
58
66
  // fixture is not assignable to the surface under test.
67
+ /**
68
+ * The key list every real frontend provider module declares (#1705): hydration withholds the code of a module
69
+ * without one, so a fixture module without it would never reach the closure this test observes.
70
+ */
71
+ const PUBLIC_CONFIG_SCHEMA = defineFrontendPublicConfigSchema({
72
+ integrationKind: { kind: FrontendPublicConfigValueKind.STRING, description: 'Which integration.' },
73
+ websiteRuntime: { kind: FrontendPublicConfigValueKind.BOOLEAN, description: 'Serves the website runtime.' },
74
+ });
75
+
59
76
  const FRONTEND_PROVIDERS: FrontendProvidersBlock = {
60
77
  providers: {
61
78
  sentry: {
@@ -94,6 +111,46 @@ function ProviderAwareMdxBody(): React.ReactElement {
94
111
  }
95
112
 
96
113
  describe('createWebsiteBlogPostBridgeIsland', () => {
114
+ it('keeps executable provider code in the bridge closure while props survive JSON serialization', () => {
115
+ const activate = async () => ({ handle: 'test' });
116
+ let observed: unknown;
117
+ function ProviderProbe() {
118
+ const registry = useWebsiteRuntime().frontendProviderRegistry;
119
+ observed = registry?.modules.values().next().value?.sdk?.activate;
120
+ return <span>provider closure</span>;
121
+ }
122
+ const entry = { ...FRONTEND_PROVIDERS.providers.sentry, sdk: { packageName: '@test/sdk' } };
123
+ const modules = { sentry: { ...entry, publicConfigSchema: PUBLIC_CONFIG_SCHEMA, sdk: { descriptor: entry.sdk, activate } } } as GeneratedWebsiteProviderModules;
124
+ const Island = createWebsiteBlogPostBridgeIsland({
125
+ slugToMdxBody: { demo: ProviderProbe },
126
+ navigation: buildNavigation(),
127
+ frontendProviderModules: modules,
128
+ });
129
+ const props = JSON.parse(JSON.stringify({
130
+ locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN,
131
+ labelPack: SKIP_LABEL_PACK, designTokens: DESIGN_TOKENS, skipToMainContentLabelKey: SKIP_KEY,
132
+ frontendProviders: { providers: { sentry: entry } }, slug: 'demo', rawFrontmatter: VALID_FRONTMATTER,
133
+ }));
134
+ render(<Island {...props} />);
135
+ expect(observed).toBe(activate);
136
+ });
137
+
138
+ it('carries the configured product origin into blog navigation and MDX content', () => {
139
+ function OriginProbe() {
140
+ return <span data-testid="blog-app-origin">{useWebsiteRuntime().appOrigin}</span>;
141
+ }
142
+ const Island = createWebsiteBlogPostBridgeIsland({
143
+ slugToMdxBody: { demo: OriginProbe }, navigation: buildNavigation(),
144
+ });
145
+ render(<Island
146
+ slug="demo" rawFrontmatter={VALID_FRONTMATTER}
147
+ locale={AvailableLanguage.EN} defaultLocale={AvailableLanguage.EN}
148
+ labelPack={SKIP_LABEL_PACK} designTokens={DESIGN_TOKENS} skipToMainContentLabelKey={SKIP_KEY}
149
+ appOrigin="https://product.example"
150
+ />);
151
+ expect(screen.getByTestId('blog-app-origin').textContent).toBe('https://product.example');
152
+ });
153
+
97
154
  it('returns a React component (closes over deps; never crosses prop boundary)', () => {
98
155
  const Island = createWebsiteBlogPostBridgeIsland({
99
156
  slugToMdxBody: { 'why-context-beats-content': MdxBodyA },
@@ -1,9 +1,17 @@
1
+ import type { GeneratedWebsiteProviderModules } from '../../core/external-providers/frontend-provider-registry.website';
1
2
  import '@testing-library/jest-dom/vitest';
2
3
  import { render, screen } from '@testing-library/react';
3
4
  import React from 'react';
4
5
  import { describe, expect, it } from 'vitest';
5
6
 
6
- import { BUILTIN_PROVIDER_CAPABILITY, defineProviderMetadata, ProviderOriginKind, ProviderTier } from '@wildo-ai/saas-models/public-runtime';
7
+ import {
8
+ BUILTIN_PROVIDER_CAPABILITY,
9
+ defineFrontendPublicConfigSchema,
10
+ defineProviderMetadata,
11
+ FrontendPublicConfigValueKind,
12
+ ProviderOriginKind,
13
+ ProviderTier,
14
+ } from '@wildo-ai/saas-models/public-runtime';
7
15
  import type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';
8
16
  import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
9
17
 
@@ -72,6 +80,15 @@ const SKIP_LABEL_PACK = { 'website.chrome.layout.skip-to-main-content': 'Skip to
72
80
  // Typed as the real block rather than `as const`: the prop takes `FrontendProvidersBlock`,
73
81
  // whose `providerCapabilities` / `protocols` are mutable `string[]`, so a `readonly` tuple
74
82
  // fixture is not assignable to the surface under test.
83
+ /**
84
+ * The key list every real frontend provider module declares (#1705): hydration withholds the code of a module
85
+ * without one, so a fixture module without it would never reach the closure this test observes.
86
+ */
87
+ const PUBLIC_CONFIG_SCHEMA = defineFrontendPublicConfigSchema({
88
+ integrationKind: { kind: FrontendPublicConfigValueKind.STRING, description: 'Which integration.' },
89
+ websiteRuntime: { kind: FrontendPublicConfigValueKind.BOOLEAN, description: 'Serves the website runtime.' },
90
+ });
91
+
75
92
  const FRONTEND_PROVIDERS: FrontendProvidersBlock = {
76
93
  providers: {
77
94
  posthog: {
@@ -92,6 +109,32 @@ function RuntimeProbe(): React.ReactNode {
92
109
  }
93
110
 
94
111
  describe('createWebsitePageBridgeIsland', () => {
112
+ it('keeps executable provider code in the bridge closure while props survive JSON serialization', () => {
113
+ // PostHog rests on analytics consent, so like the real module it declares `stoppable` and returns the
114
+ // `deactivate` that promise requires (#1704); without it hydration withholds the code this test observes.
115
+ const activate = async () => ({ handle: 'test', deactivate: () => undefined });
116
+ let observed: unknown;
117
+ function ProviderProbe() {
118
+ const registry = useWebsiteRuntime().frontendProviderRegistry;
119
+ observed = registry?.modules.values().next().value?.sdk?.activate;
120
+ return <span>provider closure</span>;
121
+ }
122
+ const entry = { ...FRONTEND_PROVIDERS.providers.posthog, sdk: { packageName: '@test/sdk' } };
123
+ const modules = { posthog: { ...entry, publicConfigSchema: PUBLIC_CONFIG_SCHEMA, sdk: { descriptor: entry.sdk, activate, stoppable: true } } } as GeneratedWebsiteProviderModules;
124
+ const Island = createWebsitePageBridgeIsland({
125
+ PageComponent: ProviderProbe, pageRef: PAGE_REF,
126
+ navigation: buildNavigation(),
127
+ frontendProviderModules: modules,
128
+ });
129
+ const props = JSON.parse(JSON.stringify({
130
+ locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN,
131
+ labelPack: SKIP_LABEL_PACK, designTokens: DESIGN_TOKENS, skipToMainContentLabelKey: SKIP_KEY,
132
+ frontendProviders: { providers: { posthog: entry } },
133
+ }));
134
+ render(<Island {...props} />);
135
+ expect(observed).toBe(activate);
136
+ });
137
+
95
138
  it('returns a React component (closes over deps; never crosses prop boundary)', () => {
96
139
  const Island = createWebsitePageBridgeIsland({
97
140
  PageComponent: PageProbe,
@@ -1,3 +1,5 @@
1
+ import { WEBSITE_CONSENT_LABEL_KEYS } from '../../schemas/label-keys/website-consent-label-keys.schemas';
2
+ import { WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS } from '../../schemas/label-keys/website-provider-component-label-keys.schemas';
1
3
  import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
4
  import { tmpdir } from 'node:os';
3
5
  import { resolve } from 'node:path';
@@ -60,6 +62,10 @@ const buildManifest = () =>
60
62
  });
61
63
 
62
64
  const SKIP_KEY_STR = 'website.chrome.layout.skip-to-main-content';
65
+ // Framework-owned consent-panel keys (#534): required in every pack, like the skip link.
66
+ // The provider-component slot's refusal wording (#1703) is framework-owned the same way.
67
+ const FRAMEWORK_OWNED_KEYS = [...WEBSITE_CONSENT_LABEL_KEYS, ...WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS];
68
+ const CONSENT_LABELS: Record<string, string> = Object.fromEntries(FRAMEWORK_OWNED_KEYS.map((key) => [key as unknown as string, 'X']));
63
69
 
64
70
  const buildRootConfig = (): WebsiteRootConfig =>
65
71
  WebsiteRootConfigSchema.parse({
@@ -207,7 +213,7 @@ describe('label-pack-loader', () => {
207
213
  writeFileSync(resolve(tmpDir, 'fr.json'), JSON.stringify({}));
208
214
  expect(() =>
209
215
  assertLabelPacksValid(tmpDir, buildSiteContext(), buildExpectedKeys()),
210
- ).toThrow(/Locale "fr".*missing 4 required key\(s\)/s);
216
+ ).toThrow(new RegExp(`Locale "fr".*missing ${4 + FRAMEWORK_OWNED_KEYS.length} required key\\(s\\)`, 's'));
211
217
  });
212
218
 
213
219
  it('annotates each missing key with its declaring page / section origin', () => {
@@ -261,6 +267,7 @@ describe('label-pack-loader', () => {
261
267
  'website.landing.meta.title': 'X',
262
268
  'website.landing.meta.description': 'X',
263
269
  [SKIP_KEY_STR]: 'Skip to main content',
270
+ ...CONSENT_LABELS,
264
271
  });
265
272
  writeFileSync(resolve(tmpDir, 'en.json'), fullPack);
266
273
  writeFileSync(resolve(tmpDir, 'fr.json'), fullPack);
@@ -277,6 +284,7 @@ describe('label-pack-loader', () => {
277
284
  'website.landing.meta.description': 'X',
278
285
  'website.landing.hero.unused-orphan': 'X',
279
286
  [SKIP_KEY_STR]: 'Skip to main content',
287
+ ...CONSENT_LABELS,
280
288
  });
281
289
  writeFileSync(resolve(tmpDir, 'en.json'), packWithOrphan);
282
290
  writeFileSync(resolve(tmpDir, 'fr.json'), packWithOrphan);
@@ -289,6 +297,26 @@ describe('label-pack-loader', () => {
289
297
  warnSpy.mockRestore();
290
298
  });
291
299
 
300
+ it('requires every framework-owned consent-panel (#534) and provider-component (#1703) key in every locale pack', () => {
301
+ const withoutConsent = JSON.stringify({
302
+ 'website.landing.hero.headline': 'X',
303
+ 'website.landing.meta.title': 'X',
304
+ 'website.landing.meta.description': 'X',
305
+ [SKIP_KEY_STR]: 'Skip to main content',
306
+ });
307
+ writeFileSync(resolve(tmpDir, 'en.json'), withoutConsent);
308
+ writeFileSync(resolve(tmpDir, 'fr.json'), JSON.stringify({ ...JSON.parse(withoutConsent), ...CONSENT_LABELS }));
309
+ let captured = '';
310
+ try {
311
+ assertLabelPacksValid(tmpDir, buildSiteContext(), buildExpectedKeys());
312
+ } catch (err) {
313
+ captured = (err as Error).message;
314
+ }
315
+ expect(captured).toMatch(/Locale "en":/);
316
+ expect(captured).not.toMatch(/Locale "fr":/);
317
+ for (const key of FRAMEWORK_OWNED_KEYS) expect(captured).toContain(key as unknown as string);
318
+ });
319
+
292
320
  it('treats WebsiteRootConfig.skipToMainContentLabelKey as required (Phase 6 third audit fix M-α)', () => {
293
321
  const partialPack = JSON.stringify({
294
322
  'website.landing.hero.headline': 'X',
@@ -7,7 +7,8 @@ import {
7
7
  WebsiteRuntimeContextProvider,
8
8
  type WebsiteRuntimeContextValue,
9
9
  } from '../core/contexts/WebsiteRuntimeContext';
10
- import { createFrontendWebsiteProviderRegistry } from '../core/external-providers/frontend-provider-registry.website';
10
+ import { useWebsiteProviderRegistries } from '../core/external-providers/provider-component-registry.website';
11
+ import type { ProviderComponentLoaderContribution } from '@wildo-ai/external-connectors-public/website';
11
12
  import type { GeneratedWebsiteProviderModules } from '../core/external-providers/frontend-provider-registry.website';
12
13
  import { BlogPost, BLOG_POST_PAGE_REF } from '../mdx/BlogPost';
13
14
  import type { WebsiteMdxComponentsMap } from '../mdx/website-mdx-provider';
@@ -222,6 +223,20 @@ import type { LabelPack } from '../schemas/validators/label-pack.validator';
222
223
  * island. NEVER cross the hydration boundary as serialized props.
223
224
  */
224
225
  export interface CreateWebsiteBlogPostBridgeIslandDeps {
226
+ /**
227
+ * Browser CODE imported by the consumer bridge, closed over at module load.
228
+ * Never pass executable modules through Astro island props: those are serialized.
229
+ * Config sync emits a stable map, including an empty map when no providers are declared.
230
+ */
231
+ frontendProviderModules?: GeneratedWebsiteProviderModules;
232
+ /**
233
+ * Loaders for provider components an ENGINE provider module declares but cannot import (#1703) —
234
+ * the website's facet-library door, the equivalent of the application's
235
+ * `contributeProviderComponentLoaders`. A provider PACKAGE that ships its own component needs
236
+ * nothing here: its module's `components[capability].load` is used. A DEPS entry because a loader
237
+ * is a function, and island props are serialized.
238
+ */
239
+ providerComponentLoaders?: ReadonlyArray<ProviderComponentLoaderContribution>;
225
240
  /**
226
241
  * Per-slug map of compiled MDX bodies (each entry is a React
227
242
  * component — the `.default` export of the MDX module). Typically
@@ -256,6 +271,11 @@ export interface CreateWebsiteBlogPostBridgeIslandDeps {
256
271
  * serialization boundary.
257
272
  */
258
273
  navigation: WebsiteNavigationConfig;
274
+ /**
275
+ * Optional replacement for the framework's visitor consent panel (#534). Closed over for the same
276
+ * reason as `navigation`. See `WebsiteRuntimeContextValue.consentBannerComponent`.
277
+ */
278
+ consentBannerComponent?: ComponentType;
259
279
  /**
260
280
  * MDX-tag → React-component map forwarded into `<BlogPost>` and
261
281
  * scoped via `<WebsiteMdxProvider>`. Typically a small object
@@ -314,26 +334,9 @@ export interface WebsiteBlogPostBridgeIslandProps {
314
334
  skipToMainContentLabelKey: WebsiteLabelKey;
315
335
  /** Optional website-facing public providers materialized at build time. */
316
336
  frontendProviders?: FrontendProvidersBlock;
317
- /**
318
- * The CODE channel for those providers (#332): the
319
- * `generatedFrontendProviderModules` export of this website service's own
320
- * `src/generated/frontend-providers.generated.ts`, written by
321
- * `wildo config sync`.
322
- *
323
- * `frontendProviders` above says WHICH providers this service turned on, in
324
- * this environment; this says what code the bundle actually contains for
325
- * them. Both are needed, because a JSON payload cannot carry an SDK loader
326
- * and a bundle cannot know which environment it is running in.
327
- *
328
- * NOT a JSON-serializable island prop like its siblings, and it does not need
329
- * to be: the bridge module is bundled with the page, so the generated module
330
- * map is an ordinary import in the same graph rather than something that has
331
- * to cross the hydration boundary.
332
- *
333
- * Omitted is the ordinary case for a website declaring no package-shipped
334
- * provider: no such file is generated, so there is nothing to import.
335
- */
336
- frontendProviderModules?: GeneratedWebsiteProviderModules;
337
+ /** Product origin for navigation and call-to-action components inside the blog island.
338
+ * Supplied by the Astro page from websiteConfig, just as on ordinary page bridges. */
339
+ appOrigin?: string;
337
340
  }
338
341
 
339
342
  const EMPTY_MDX_COMPONENTS: WebsiteMdxComponentsMap = Object.freeze({});
@@ -364,7 +367,7 @@ export function createWebsiteBlogPostBridgeIsland(
364
367
  designTokens,
365
368
  skipToMainContentLabelKey,
366
369
  frontendProviders,
367
- frontendProviderModules,
370
+ appOrigin,
368
371
  }: WebsiteBlogPostBridgeIslandProps): ReactNode {
369
372
  const MdxBody = slugToMdxBody[slug];
370
373
  if (MdxBody === undefined) {
@@ -389,6 +392,13 @@ export function createWebsiteBlogPostBridgeIsland(
389
392
  );
390
393
  }
391
394
 
395
+ // After the missing-slug throw on purpose: that throw ends the render, so it never changes the
396
+ // hook order of a render that completes, and the build-time error stays reachable.
397
+ const { frontendProviderRegistry, providerComponentRegistry } = useWebsiteProviderRegistries({
398
+ frontendProviders,
399
+ frontendProviderModules: deps.frontendProviderModules,
400
+ providerComponentLoaders: deps.providerComponentLoaders,
401
+ });
392
402
  const runtimeValue: WebsiteRuntimeContextValue = {
393
403
  locale,
394
404
  defaultLocale,
@@ -396,10 +406,10 @@ export function createWebsiteBlogPostBridgeIsland(
396
406
  designTokens,
397
407
  navigation,
398
408
  skipToMainContentLabelKey,
399
- frontendProviderRegistry: createFrontendWebsiteProviderRegistry({
400
- entries: frontendProviders,
401
- modules: frontendProviderModules,
402
- }),
409
+ appOrigin,
410
+ ...(deps.consentBannerComponent !== undefined ? { consentBannerComponent: deps.consentBannerComponent } : {}),
411
+ frontendProviderRegistry,
412
+ providerComponentRegistry,
403
413
  };
404
414
  return (
405
415
  <WebsiteRuntimeContextProvider value={runtimeValue}>
@@ -1,15 +1,18 @@
1
+ import { useMemo } from 'react';
1
2
  import type { ComponentType, FunctionComponent, ReactNode } from 'react';
2
3
 
3
- import type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';
4
+ import type { FrontendProvidersBlock, PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';
4
5
  import type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
5
6
 
6
7
  import {
7
8
  WebsiteRuntimeContextProvider,
8
9
  type WebsiteRuntimeContextValue,
9
10
  } from '../core/contexts/WebsiteRuntimeContext';
10
- import { createFrontendWebsiteProviderRegistry } from '../core/external-providers/frontend-provider-registry.website';
11
+ import { useWebsiteProviderRegistries } from '../core/external-providers/provider-component-registry.website';
12
+ import type { ProviderComponentLoaderContribution } from '@wildo-ai/external-connectors-public/website';
11
13
  import type { GeneratedWebsiteProviderModules } from '../core/external-providers/frontend-provider-registry.website';
12
14
  import { WebsitePageLayout } from '../core/layouts/WebsitePageLayout';
15
+ import type { WebsiteConsentRecorder } from '../core/consent/WebsiteConsentContext';
13
16
  import type { WebsiteDesignTokens } from '../schemas/design-tokens/website-design-tokens.shared.schemas';
14
17
  import type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';
15
18
  import type { WebsiteNavigationConfig } from '../schemas/manifests/website-root-config.shared.schemas';
@@ -70,22 +73,22 @@ import type { LabelPack } from '../schemas/validators/label-pack.validator';
70
73
  * } from '@wildo-ai/saas-website/astro';
71
74
  * import { LandingBridgeIsland } from '../pages-impl/landing.bridge';
72
75
  * import { LANDING_PAGE_MANIFEST } from '../pages-impl/landing.page';
73
- * import { WEBSITE_ROOT_CONFIG } from '../website-root.config';
76
+ * import { WEBSITE_SITE_CONTEXT } from '../website-site-context';
74
77
  *
75
78
  * const runtime = loadWebsitePageRuntime({
76
79
  * manifest: LANDING_PAGE_MANIFEST,
77
- * rootConfig: WEBSITE_ROOT_CONFIG,
80
+ * siteContext: WEBSITE_SITE_CONTEXT,
78
81
  * labelPacksDir: './src/i18n',
79
82
  * astroLocale: Astro.currentLocale,
80
83
  * });
81
84
  * const headHtml = renderWebsitePageHeadHtml({
82
85
  * manifest: LANDING_PAGE_MANIFEST,
83
- * rootConfig: WEBSITE_ROOT_CONFIG,
86
+ * siteContext: WEBSITE_SITE_CONTEXT,
84
87
  * locale: runtime.locale,
85
88
  * labelPack: runtime.labelPack,
86
89
  * });
87
90
  * const designTokensCss = renderWebsiteDesignTokensCss(
88
- * WEBSITE_ROOT_CONFIG.designTokens,
91
+ * WEBSITE_SITE_CONTEXT.rootConfig.designTokens,
89
92
  * );
90
93
  * ---
91
94
  * <!doctype html>
@@ -98,8 +101,11 @@ import type { LabelPack } from '../schemas/validators/label-pack.validator';
98
101
  * <LandingBridgeIsland
99
102
  * client:idle
100
103
  * locale={runtime.locale}
104
+ * defaultLocale={runtime.defaultLocale}
105
+ * appOrigin={WEBSITE_SITE_CONTEXT.websiteConfig.appOrigin}
106
+ * apiOrigin={WEBSITE_SITE_CONTEXT.websiteConfig.apiOrigin}
101
107
  * labelPack={runtime.labelPack}
102
- * designTokens={WEBSITE_ROOT_CONFIG.designTokens}
108
+ * designTokens={WEBSITE_SITE_CONTEXT.rootConfig.designTokens}
103
109
  * />
104
110
  * </body>
105
111
  * </html>
@@ -144,6 +150,20 @@ import type { LabelPack } from '../schemas/validators/label-pack.validator';
144
150
  * island. NEVER cross the hydration boundary as serialized props.
145
151
  */
146
152
  export interface CreateWebsitePageBridgeIslandDeps {
153
+ /**
154
+ * Browser CODE imported by the consumer bridge, closed over at module load.
155
+ * Never pass executable modules through Astro island props: those are serialized.
156
+ * Config sync emits a stable map, including an empty map when no providers are declared.
157
+ */
158
+ frontendProviderModules?: GeneratedWebsiteProviderModules;
159
+ /**
160
+ * Loaders for provider components an ENGINE provider module declares but cannot import (#1703) —
161
+ * the website's facet-library door, the equivalent of the application's
162
+ * `contributeProviderComponentLoaders`. A provider PACKAGE that ships its own component needs
163
+ * nothing here: its module's `components[capability].load` is used. A DEPS entry because a loader
164
+ * is a function, and island props are serialized.
165
+ */
166
+ providerComponentLoaders?: ReadonlyArray<ProviderComponentLoaderContribution>;
147
167
  /**
148
168
  * The page's React component. Typically the same value that was
149
169
  * passed to `defineWebsitePageManifest({ pageComponent })` — the
@@ -160,6 +180,24 @@ export interface CreateWebsitePageBridgeIslandDeps {
160
180
  * runtime-safe.
161
181
  */
162
182
  PageComponent: ComponentType<any>;
183
+ /**
184
+ * Builds the recorder that persists a VISITOR's consent decision server-side, from the island's
185
+ * runtime `apiOrigin` (#1258).
186
+ *
187
+ * A DEPS entry rather than an island prop, for the reason this whole interface exists: it is a
188
+ * function, and island props are serialized. A factory rather than an instance, because the
189
+ * `apiOrigin` it needs is resolved per environment by the `.astro` page and only reaches the island
190
+ * as a prop.
191
+ *
192
+ * The engine cannot build this itself — a client needs the application's own
193
+ * `frontendServiceName`, which the engine has no way to know. The application already constructs
194
+ * one for its capture forms; passing THAT factory here is what makes a visitor's consent decision,
195
+ * their captured lead and their draft note share a single `uuidRef` and transpose together.
196
+ *
197
+ * Optional. Omitted, a decision lives in the visitor's browser and nowhere else — the behaviour
198
+ * before this existed, and correct for a site with no anonymous-session wiring.
199
+ */
200
+ consentRecorderFactory?: (apiOrigin: string) => WebsiteConsentRecorder;
163
201
  /**
164
202
  * The active page's ref. Carried as a primitive (string-branded)
165
203
  * value so the inner `WebsitePageLayout` can register the
@@ -173,6 +211,11 @@ export interface CreateWebsitePageBridgeIslandDeps {
173
211
  * serialization boundary.
174
212
  */
175
213
  navigation: WebsiteNavigationConfig;
214
+ /**
215
+ * Optional replacement for the framework's visitor consent panel (#534). Closed over for the same
216
+ * reason as `navigation`. See `WebsiteRuntimeContextValue.consentBannerComponent`.
217
+ */
218
+ consentBannerComponent?: ComponentType;
176
219
  }
177
220
 
178
221
  /**
@@ -212,26 +255,6 @@ export interface WebsitePageBridgeIslandProps {
212
255
  skipToMainContentLabelKey: WebsiteLabelKey;
213
256
  /** Optional website-facing public providers materialized at build time. */
214
257
  frontendProviders?: FrontendProvidersBlock;
215
- /**
216
- * The CODE channel for those providers (#332): the
217
- * `generatedFrontendProviderModules` export of this website service's own
218
- * `src/generated/frontend-providers.generated.ts`, written by
219
- * `wildo config sync`.
220
- *
221
- * `frontendProviders` above says WHICH providers this service turned on, in
222
- * this environment; this says what code the bundle actually contains for
223
- * them. Both are needed, because a JSON payload cannot carry an SDK loader
224
- * and a bundle cannot know which environment it is running in.
225
- *
226
- * NOT a JSON-serializable island prop like its siblings, and it does not need
227
- * to be: the bridge module is bundled with the page, so the generated module
228
- * map is an ordinary import in the same graph rather than something that has
229
- * to cross the hydration boundary.
230
- *
231
- * Omitted is the ordinary case for a website declaring no package-shipped
232
- * provider: no such file is generated, so there is nothing to import.
233
- */
234
- frontendProviderModules?: GeneratedWebsiteProviderModules;
235
258
  /**
236
259
  * Optional backend API origin (no trailing slash) forwarded into
237
260
  * `WebsiteRuntimeContext.apiOrigin` for anonymous-capture sections
@@ -259,6 +282,13 @@ export interface WebsitePageBridgeIslandProps {
259
282
  * the consumer's job and the whole point of removing the fallback.
260
283
  */
261
284
  appOrigin?: string;
285
+ /**
286
+ * The application's public pricing catalogue (#1835), forwarded into
287
+ * `WebsiteRuntimeContext.pricingCatalog` for `useWebsitePricingCatalog()`. Plain JSON-safe data
288
+ * (amounts travel as decimal strings, never `bigint`), loaded in the `.astro` frontmatter with
289
+ * `loadWebsitePricingCatalog()`. Passed only by pages with a pricing section.
290
+ */
291
+ pricingCatalog?: PublicPricingCatalog;
262
292
  }
263
293
 
264
294
  /**
@@ -280,10 +310,26 @@ export function createWebsitePageBridgeIsland(
280
310
  designTokens,
281
311
  skipToMainContentLabelKey,
282
312
  frontendProviders,
283
- frontendProviderModules,
284
313
  apiOrigin,
285
314
  appOrigin,
315
+ pricingCatalog,
286
316
  }: WebsitePageBridgeIslandProps): ReactNode {
317
+ /*
318
+ * Both must be present: the factory is build-time wiring and `apiOrigin` is per-environment, and
319
+ * a page that declares no capture section legitimately has neither. Memoised on `apiOrigin` so a
320
+ * re-render cannot hand the provider a new identity and re-post a decision nobody retook.
321
+ */
322
+ const consentRecorder = useMemo(
323
+ () => (deps.consentRecorderFactory !== undefined && apiOrigin !== undefined
324
+ ? deps.consentRecorderFactory(apiOrigin)
325
+ : undefined),
326
+ [apiOrigin],
327
+ );
328
+ const { frontendProviderRegistry, providerComponentRegistry } = useWebsiteProviderRegistries({
329
+ frontendProviders,
330
+ frontendProviderModules: deps.frontendProviderModules,
331
+ providerComponentLoaders: deps.providerComponentLoaders,
332
+ });
287
333
  const runtimeValue: WebsiteRuntimeContextValue = {
288
334
  locale,
289
335
  defaultLocale,
@@ -291,16 +337,16 @@ export function createWebsitePageBridgeIsland(
291
337
  designTokens,
292
338
  navigation,
293
339
  skipToMainContentLabelKey,
294
- frontendProviderRegistry: createFrontendWebsiteProviderRegistry({
295
- entries: frontendProviders,
296
- modules: frontendProviderModules,
297
- }),
340
+ frontendProviderRegistry,
341
+ providerComponentRegistry,
342
+ ...(deps.consentBannerComponent !== undefined ? { consentBannerComponent: deps.consentBannerComponent } : {}),
298
343
  apiOrigin,
299
344
  appOrigin,
345
+ ...(pricingCatalog !== undefined ? { pricingCatalog } : {}),
300
346
  };
301
347
  return (
302
348
  <WebsiteRuntimeContextProvider value={runtimeValue}>
303
- <WebsitePageLayout pageRef={pageRef}>
349
+ <WebsitePageLayout pageRef={pageRef} {...(consentRecorder !== undefined ? { consentRecorder } : {})}>
304
350
  <PageComponent />
305
351
  </WebsitePageLayout>
306
352
  </WebsiteRuntimeContextProvider>