@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.
- package/dist/esm/astro/blog-post-bridge.d.ts +23 -20
- package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -1
- package/dist/esm/astro/blog-post-bridge.js +13 -6
- package/dist/esm/astro/blog-post-bridge.js.map +1 -1
- package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -1
- package/dist/esm/astro/bridge-runtime.d.ts +55 -26
- package/dist/esm/astro/bridge-runtime.d.ts.map +1 -1
- package/dist/esm/astro/bridge-runtime.js +21 -7
- package/dist/esm/astro/bridge-runtime.js.map +1 -1
- package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -1
- package/dist/esm/astro/headers-renderer.d.ts.map +1 -1
- package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -1
- package/dist/esm/astro/label-pack-loader.d.ts +4 -4
- package/dist/esm/astro/label-pack-loader.d.ts.map +1 -1
- package/dist/esm/astro/label-pack-loader.js +14 -4
- package/dist/esm/astro/label-pack-loader.js.map +1 -1
- package/dist/esm/astro/llms-txt-server.d.ts.map +1 -1
- package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -1
- package/dist/esm/astro/robots-renderer.d.ts +9 -8
- package/dist/esm/astro/robots-renderer.d.ts.map +1 -1
- package/dist/esm/astro/robots-renderer.js +15 -15
- package/dist/esm/astro/robots-renderer.js.map +1 -1
- package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -1
- package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -1
- package/dist/esm/astro/website-page-head.renderer.d.ts +1 -1
- package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -1
- package/dist/esm/astro/website-page-head.renderer.js +2 -1
- package/dist/esm/astro/website-page-head.renderer.js.map +1 -1
- package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -1
- package/dist/esm/astro/website-site-context.d.ts +5 -2
- package/dist/esm/astro/website-site-context.d.ts.map +1 -1
- package/dist/esm/astro/website-site-context.js.map +1 -1
- package/dist/esm/astro-island.d.ts.map +1 -1
- package/dist/esm/astro.d.ts.map +1 -1
- package/dist/esm/companion-exports.d.ts +8 -15
- package/dist/esm/companion-exports.d.ts.map +1 -1
- package/dist/esm/companion-exports.js +8 -15
- package/dist/esm/companion-exports.js.map +1 -1
- package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -1
- package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -1
- package/dist/esm/config/define-website-config.d.ts +3 -2
- package/dist/esm/config/define-website-config.d.ts.map +1 -1
- package/dist/esm/config/define-website-config.js +10 -10
- package/dist/esm/config/define-website-config.js.map +1 -1
- package/dist/esm/config/index.d.ts.map +1 -1
- package/dist/esm/config/load-website-config.d.ts +6 -0
- package/dist/esm/config/load-website-config.d.ts.map +1 -1
- package/dist/esm/config/load-website-config.js +20 -10
- package/dist/esm/config/load-website-config.js.map +1 -1
- package/dist/esm/config/load-website-pricing-catalog.d.ts +13 -0
- package/dist/esm/config/load-website-pricing-catalog.d.ts.map +1 -0
- package/dist/esm/config/load-website-pricing-catalog.js +34 -0
- package/dist/esm/config/load-website-pricing-catalog.js.map +1 -0
- package/dist/esm/config/load-website-site-context.d.ts +53 -0
- package/dist/esm/config/load-website-site-context.d.ts.map +1 -0
- package/dist/esm/config/load-website-site-context.js +98 -0
- package/dist/esm/config/load-website-site-context.js.map +1 -0
- package/dist/esm/config/wildo-website-config.schemas.d.ts +10 -5
- package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -1
- package/dist/esm/config/wildo-website-config.schemas.js +10 -5
- package/dist/esm/config/wildo-website-config.schemas.js.map +1 -1
- package/dist/esm/config-loader.d.ts +2 -0
- package/dist/esm/config-loader.d.ts.map +1 -1
- package/dist/esm/config-loader.js +2 -0
- package/dist/esm/config-loader.js.map +1 -1
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +26 -6
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/InboundContactForm.js +51 -33
- package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -1
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +14 -3
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js +28 -13
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -1
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts +24 -1
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js +46 -3
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -1
- package/dist/esm/core/anonymous-session/website-submission-challenge.d.ts +36 -0
- package/dist/esm/core/anonymous-session/website-submission-challenge.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/website-submission-challenge.js +69 -0
- package/dist/esm/core/anonymous-session/website-submission-challenge.js.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentBanner.d.ts +6 -0
- package/dist/esm/core/consent/WebsiteConsentBanner.d.ts.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentBanner.js +129 -0
- package/dist/esm/core/consent/WebsiteConsentBanner.js.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentContext.d.ts +85 -0
- package/dist/esm/core/consent/WebsiteConsentContext.d.ts.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentContext.js +89 -0
- package/dist/esm/core/consent/WebsiteConsentContext.js.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentGate.d.ts +27 -0
- package/dist/esm/core/consent/WebsiteConsentGate.d.ts.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentGate.js +9 -0
- package/dist/esm/core/consent/WebsiteConsentGate.js.map +1 -0
- package/dist/esm/core/consent/useWebsiteConsent.d.ts +10 -0
- package/dist/esm/core/consent/useWebsiteConsent.d.ts.map +1 -0
- package/dist/esm/core/consent/useWebsiteConsent.js +17 -0
- package/dist/esm/core/consent/useWebsiteConsent.js.map +1 -0
- package/dist/esm/core/consent/website-consent-storage.d.ts +22 -0
- package/dist/esm/core/consent/website-consent-storage.d.ts.map +1 -0
- package/dist/esm/core/consent/website-consent-storage.js +102 -0
- package/dist/esm/core/consent/website-consent-storage.js.map +1 -0
- package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsitePageContext.js.map +1 -1
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +33 -6
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -1
- package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsiteSectionContext.js.map +1 -1
- package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +18 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -2
- package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -1
- package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -1
- package/dist/esm/core/external-providers/WebsiteProviderComponent.d.ts +57 -0
- package/dist/esm/core/external-providers/WebsiteProviderComponent.d.ts.map +1 -0
- package/dist/esm/core/external-providers/WebsiteProviderComponent.js +56 -0
- package/dist/esm/core/external-providers/WebsiteProviderComponent.js.map +1 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts +1 -2
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -1
- package/dist/esm/core/external-providers/frontend-provider-registry.website.js.map +1 -1
- package/dist/esm/core/external-providers/provider-component-registry.website.d.ts +63 -0
- package/dist/esm/core/external-providers/provider-component-registry.website.d.ts.map +1 -0
- package/dist/esm/core/external-providers/provider-component-registry.website.js +68 -0
- package/dist/esm/core/external-providers/provider-component-registry.website.js.map +1 -0
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts +36 -0
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts.map +1 -0
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.js +59 -0
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.js.map +1 -0
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts +43 -10
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.js +92 -29
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.js.map +1 -1
- package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts +11 -0
- package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts.map +1 -0
- package/dist/esm/core/external-providers/website-provider-sdk-activations.js +14 -0
- package/dist/esm/core/external-providers/website-provider-sdk-activations.js.map +1 -0
- package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -1
- package/dist/esm/core/factories/define-website-section.d.ts +3 -3
- package/dist/esm/core/factories/define-website-section.d.ts.map +1 -1
- package/dist/esm/core/factories/define-website-section.js +3 -3
- package/dist/esm/core/factories/define-website-section.js.map +1 -1
- package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -1
- package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -1
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts +21 -1
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -1
- package/dist/esm/core/layouts/WebsitePageLayout.js +39 -8
- package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -1
- package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -1
- package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -1
- package/dist/esm/index.d.ts +36 -65
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +36 -65
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/mdx/BlogPost.d.ts.map +1 -1
- package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -1
- package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -1
- package/dist/esm/mdx/index.d.ts.map +1 -1
- package/dist/esm/mdx.d.ts.map +1 -1
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts +35 -6
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js +39 -5
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.d.ts +61 -0
- package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.js +73 -0
- package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.js.map +1 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.d.ts +32 -0
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js +36 -0
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js.map +1 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +6 -5
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js +8 -7
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-category.shared.js.map +1 -1
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +14 -7
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +14 -7
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.d.ts +57 -0
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.d.ts.map +1 -0
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js +76 -0
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js.map +1 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +23 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -1
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js +20 -2
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -1
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts +7 -5
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -29
- package/src/__tests__/bundle-isolation.test.ts +19 -22
- package/src/astro/__tests__/blog-post-bridge.test.tsx +58 -1
- package/src/astro/__tests__/bridge-runtime.test.tsx +44 -1
- package/src/astro/__tests__/label-pack-loader.test.ts +29 -1
- package/src/astro/blog-post-bridge.tsx +36 -26
- package/src/astro/bridge-runtime.tsx +79 -33
- package/src/astro/label-pack-loader.ts +12 -4
- package/src/astro/robots-renderer.ts +15 -15
- package/src/astro/website-page-head.renderer.ts +2 -2
- package/src/astro/website-site-context.ts +5 -2
- package/src/companion-exports.ts +8 -15
- package/src/config/__tests__/website-build-origins.test.ts +52 -0
- package/src/config/define-website-config.ts +10 -10
- package/src/config/load-website-config.ts +27 -15
- package/src/config/load-website-pricing-catalog.ts +52 -0
- package/src/config/load-website-site-context.ts +145 -0
- package/src/config/wildo-website-config.schemas.ts +10 -5
- package/src/config-loader.ts +2 -0
- package/src/core/__tests__/WebsitePageLayout.test.tsx +1 -1
- package/src/core/__tests__/contexts.test.tsx +20 -1
- package/src/core/__tests__/website-consent.test.tsx +312 -0
- package/src/core/anonymous-session/InboundContactForm.tsx +100 -45
- package/src/core/anonymous-session/__tests__/InboundContactForm.labels.test.tsx +108 -0
- package/src/core/anonymous-session/__tests__/InboundContactForm.submission-challenge.test.tsx +139 -0
- package/src/core/anonymous-session/__tests__/inbound-contact-form.schema.test.ts +11 -4
- package/src/core/anonymous-session/inbound-contact-form.schema.ts +30 -13
- package/src/core/anonymous-session/website-anonymous-session-client.ts +52 -3
- package/src/core/anonymous-session/website-submission-challenge.ts +99 -0
- package/src/core/consent/WebsiteConsentBanner.tsx +223 -0
- package/src/core/consent/WebsiteConsentContext.tsx +164 -0
- package/src/core/consent/WebsiteConsentGate.tsx +34 -0
- package/src/core/consent/useWebsiteConsent.ts +18 -0
- package/src/core/consent/website-consent-storage.ts +116 -0
- package/src/core/contexts/WebsiteRuntimeContext.tsx +33 -6
- package/src/core/contexts/useWebsiteRuntime.ts +33 -2
- package/src/core/external-providers/WebsiteProviderComponent.tsx +153 -0
- package/src/core/external-providers/__tests__/frontend-provider-registry.website.test.ts +4 -0
- package/src/core/external-providers/__tests__/website-provider-component.test.tsx +238 -0
- package/src/core/external-providers/__tests__/website-provider-sdks.test.tsx +72 -0
- package/src/core/external-providers/frontend-provider-registry.website.ts +1 -2
- package/src/core/external-providers/provider-component-registry.website.ts +115 -0
- package/src/core/external-providers/useWebsiteProviderComponent.ts +76 -0
- package/src/core/external-providers/useWebsiteProviderScripts.ts +114 -40
- package/src/core/external-providers/website-provider-sdk-activations.ts +23 -0
- package/src/core/factories/define-website-section.ts +3 -3
- package/src/core/layouts/WebsitePageLayout.tsx +75 -7
- package/src/index.ts +38 -65
- package/src/schemas/__tests__/design-token-css-property.test.ts +72 -0
- package/src/schemas/__tests__/pricing-offer-structured-data.test.ts +122 -0
- package/src/schemas/__tests__/structured-data-reconciliation.test.ts +35 -0
- package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +45 -5
- package/src/schemas/label-keys/__tests__/scaffolded-consent-label-pack.parity.test.ts +83 -0
- package/src/schemas/label-keys/website-consent-label-keys.schemas.ts +78 -0
- package/src/schemas/label-keys/website-provider-component-label-keys.schemas.ts +40 -0
- package/src/schemas/manifests/website-root-config.shared.schemas.ts +10 -9
- package/src/schemas/sections/website-section-definition.shared.schemas.ts +14 -7
- package/src/schemas/structured-data/pricing-offer-structured-data.shared.ts +144 -0
- package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +46 -1
- package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +7 -5
- package/dist/esm/core/external-providers/provider-scripts.website.d.ts +0 -68
- package/dist/esm/core/external-providers/provider-scripts.website.d.ts.map +0 -1
- package/dist/esm/core/external-providers/provider-scripts.website.js +0 -99
- package/dist/esm/core/external-providers/provider-scripts.website.js.map +0 -1
- package/src/core/external-providers/__tests__/provider-scripts.website.test.ts +0 -146
- 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.
|
|
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.
|
|
69
|
-
"@wildo-ai/platform-config-lib": "1.1.
|
|
70
|
-
"@wildo-ai/presets-components-models": "1.1.
|
|
71
|
-
"@wildo-ai/saas-models": "1.1.
|
|
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
|
-
"
|
|
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
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
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 {
|
|
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 {
|
|
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(
|
|
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 {
|
|
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
|
-
*
|
|
319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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 {
|
|
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 {
|
|
76
|
+
* import { WEBSITE_SITE_CONTEXT } from '../website-site-context';
|
|
74
77
|
*
|
|
75
78
|
* const runtime = loadWebsitePageRuntime({
|
|
76
79
|
* manifest: LANDING_PAGE_MANIFEST,
|
|
77
|
-
*
|
|
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
|
-
*
|
|
86
|
+
* siteContext: WEBSITE_SITE_CONTEXT,
|
|
84
87
|
* locale: runtime.locale,
|
|
85
88
|
* labelPack: runtime.labelPack,
|
|
86
89
|
* });
|
|
87
90
|
* const designTokensCss = renderWebsiteDesignTokensCss(
|
|
88
|
-
*
|
|
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={
|
|
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
|
|
295
|
-
|
|
296
|
-
|
|
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>
|