@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
|
@@ -12,35 +12,68 @@
|
|
|
12
12
|
* does not exist would throw, and starting a vendor SDK there would run a live
|
|
13
13
|
* analytics client inside a build.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
15
|
+
* Both come from `@wildo-ai/external-connectors-public/website`. The script
|
|
16
|
+
* injector lives there rather than here so the SaaS application, the technical
|
|
17
|
+
* doc and this site run ONE copy of it: a second copy of a DOM mutation
|
|
18
|
+
* eventually disagrees about idempotence, which is how a vendor tag gets loaded
|
|
19
|
+
* twice. It first shipped here alone, and that is how the application came to
|
|
20
|
+
* declare its Drive Picker's Google API script and never inject it.
|
|
21
|
+
*
|
|
22
|
+
* ## Client-side injection, and the alternative that was rejected
|
|
23
|
+
*
|
|
24
|
+
* This is an Astro static site, so the "obvious" home for a third-party tag is
|
|
25
|
+
* the built HTML `<head>`. That was rejected for now: the set of live providers
|
|
26
|
+
* is per SERVICE and per ENVIRONMENT, resolved from the materialized artifact,
|
|
27
|
+
* while the built HTML is one artifact shared across deploys of the same build.
|
|
28
|
+
* Baking the tags in would make the build environment-specific — the same class
|
|
29
|
+
* of coupling the DATA channel exists to avoid. Injecting from the client keeps
|
|
30
|
+
* one build correct everywhere, at the cost of the tag arriving after hydration.
|
|
31
|
+
* Revisit if a provider ever needs to run before first paint.
|
|
23
32
|
*/
|
|
24
33
|
|
|
25
|
-
import { useEffect } from 'react';
|
|
34
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
26
35
|
import {
|
|
27
36
|
activateFrontendProviderSdks,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
collectDeclaredProviderScripts,
|
|
37
|
+
collectProviderScriptsWithoutComponents,
|
|
38
|
+
consentPurposesRequiredByProvider,
|
|
39
|
+
createFrontendProviderSdkActivationLane,
|
|
32
40
|
injectProviderScripts,
|
|
33
|
-
} from '
|
|
34
|
-
import { AppFrontendType } from '@wildo-ai/saas-models/public-runtime';
|
|
41
|
+
} from '@wildo-ai/external-connectors-public/website';
|
|
42
|
+
import { AppFrontendType, type ConsentPurpose } from '@wildo-ai/saas-models/public-runtime';
|
|
35
43
|
import type { FrontendWebsiteProviderRegistry } from './frontend-provider-registry.website';
|
|
44
|
+
import type { WebsiteProviderComponentRegistry } from './provider-component-registry.website';
|
|
45
|
+
import type { WebsiteProviderSdkActivation } from './website-provider-sdk-activations';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A stable key for a granted-purpose set, so an effect re-runs when the SET changes and not when a new
|
|
49
|
+
* array with the same members is handed in.
|
|
50
|
+
*/
|
|
51
|
+
function grantedKey(grantedConsentPurposes: readonly ConsentPurpose[]): string {
|
|
52
|
+
return [...grantedConsentPurposes].sort().join(',');
|
|
53
|
+
}
|
|
36
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Injects the `<script>` tags of the live providers that rest on NO consent purpose and render no component.
|
|
57
|
+
*
|
|
58
|
+
* No consent input, deliberately (#1704): an executed `<script>` cannot be stopped, so a provider that rests on a
|
|
59
|
+
* purpose must ship as a stoppable SDK (started on the grant and stopped on withdrawal by
|
|
60
|
+
* {@link useWebsiteProviderSdks}); a consent-based module declaring `scripts` is refused at projection and skipped by
|
|
61
|
+
* the collector. No grant could change what this injects.
|
|
62
|
+
*/
|
|
37
63
|
export function useWebsiteProviderScripts(
|
|
38
64
|
registry: FrontendWebsiteProviderRegistry | undefined,
|
|
65
|
+
/**
|
|
66
|
+
* The website's provider-component registry (#1703). A provider with a registered component loads its
|
|
67
|
+
* scripts WITH that component, when a page first shows it — so an address lookup's vendor loader is
|
|
68
|
+
* not downloaded on every page of the site, only on the one with the form. This hook injects the rest.
|
|
69
|
+
*/
|
|
70
|
+
componentRegistry?: WebsiteProviderComponentRegistry,
|
|
39
71
|
): void {
|
|
40
72
|
useEffect(() => {
|
|
41
73
|
if (typeof document === 'undefined') return;
|
|
42
74
|
|
|
43
|
-
const
|
|
75
|
+
const withComponents = [...(componentRegistry?.providersWithComponents() ?? [])].map((providerRef) => ({ providerRef }));
|
|
76
|
+
const scripts = collectProviderScriptsWithoutComponents(registry?.modules.values() ?? [], withComponents);
|
|
44
77
|
if (scripts.length === 0) return;
|
|
45
78
|
|
|
46
79
|
injectProviderScripts(scripts, document);
|
|
@@ -48,7 +81,7 @@ export function useWebsiteProviderScripts(
|
|
|
48
81
|
// work immediately; removing the element undoes neither, so a cleanup that
|
|
49
82
|
// removed it would only make the next mount inject a SECOND copy of an
|
|
50
83
|
// already-running script. Idempotence by `src` is the real guard.
|
|
51
|
-
}, [registry]);
|
|
84
|
+
}, [registry, componentRegistry]);
|
|
52
85
|
}
|
|
53
86
|
|
|
54
87
|
/**
|
|
@@ -60,33 +93,74 @@ export function useWebsiteProviderScripts(
|
|
|
60
93
|
*/
|
|
61
94
|
export function useWebsiteProviderSdks(
|
|
62
95
|
registry: FrontendWebsiteProviderRegistry | undefined,
|
|
63
|
-
|
|
96
|
+
/**
|
|
97
|
+
* The purposes the visitor granted (#534). Providers are activated in TWO groups on purpose: those that
|
|
98
|
+
* rest on no purpose (error monitoring, captcha) are started once and never restarted by a consent
|
|
99
|
+
* change, while the consent-based ones start when their purpose is granted and are DEACTIVATED when it
|
|
100
|
+
* is withdrawn — which is the only way a withdrawal stops a running analytics client.
|
|
101
|
+
*/
|
|
102
|
+
grantedConsentPurposes: readonly ConsentPurpose[],
|
|
103
|
+
): ReadonlyArray<WebsiteProviderSdkActivation> {
|
|
104
|
+
const consentFree = useProviderSdkGroup(registry, false, []);
|
|
105
|
+
const consentBased = useProviderSdkGroup(registry, true, grantedConsentPurposes);
|
|
106
|
+
// The providers RUNNING on this page, both groups, published by the page layout (#1735): a form reads the captcha
|
|
107
|
+
// vendor's handle from here. Emptied the moment a group stops, so nothing holds a handle past a withdrawal.
|
|
108
|
+
return useMemo(() => [...consentFree, ...consentBased], [consentFree, consentBased]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function useProviderSdkGroup(
|
|
112
|
+
registry: FrontendWebsiteProviderRegistry | undefined,
|
|
113
|
+
consentBased: boolean,
|
|
114
|
+
grantedConsentPurposes: readonly ConsentPurpose[],
|
|
115
|
+
): ReadonlyArray<WebsiteProviderSdkActivation> {
|
|
116
|
+
const [running, setRunning] = useState<ReadonlyArray<WebsiteProviderSdkActivation>>([]);
|
|
117
|
+
// Keyed by the grants this group's providers actually rest on, so changing an unrelated purpose does
|
|
118
|
+
// not stop and restart a running analytics client.
|
|
119
|
+
const relevant = registry === undefined
|
|
120
|
+
? []
|
|
121
|
+
: [...registry.modules.values()]
|
|
122
|
+
.filter((module) => (consentPurposesRequiredByProvider(module).length > 0) === consentBased)
|
|
123
|
+
.flatMap((module) => consentPurposesRequiredByProvider(module))
|
|
124
|
+
.filter((purpose) => grantedConsentPurposes.includes(purpose));
|
|
125
|
+
const key = grantedKey(relevant);
|
|
126
|
+
// One lane per group for the page's lifetime: a start waits for the previous stop.
|
|
127
|
+
const lane = useRef(createFrontendProviderSdkActivationLane());
|
|
64
128
|
useEffect(() => {
|
|
65
129
|
if (registry === undefined) return;
|
|
130
|
+
const modules = [...registry.modules.values()].filter(
|
|
131
|
+
(module) => (consentPurposesRequiredByProvider(module).length > 0) === consentBased,
|
|
132
|
+
);
|
|
133
|
+
if (modules.length === 0) return;
|
|
66
134
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
135
|
+
// Through the group's lane, so a start never overlaps the previous stop of the same page-global vendor (#1704): a
|
|
136
|
+
// visitor who grants, withdraws and grants again while the bundle downloads must not have the first run's stop
|
|
137
|
+
// switch off the second run's client. The stop also covers an unmount mid-download.
|
|
138
|
+
const stop = lane.current.start(
|
|
139
|
+
() => activateFrontendProviderSdks({
|
|
140
|
+
surface: AppFrontendType.STATIC_WEBSITE,
|
|
141
|
+
modules,
|
|
142
|
+
grantedConsentPurposes,
|
|
143
|
+
onDiagnostic: (diagnostic) => {
|
|
144
|
+
// The website runtime has no logger service, and a provider that cannot
|
|
145
|
+
// start is a developer-facing fact that must not be silent.
|
|
146
|
+
// eslint-disable-next-line no-console
|
|
147
|
+
console.warn(`[wildo-provider-sdk] ${diagnostic.kind}: ${diagnostic.message}`);
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
(result) => {
|
|
151
|
+
setRunning(modules.flatMap((module) => {
|
|
152
|
+
const activation = result.activations.get(module.metadata.ref);
|
|
153
|
+
return activation === undefined
|
|
154
|
+
? []
|
|
155
|
+
: [{ providerRef: module.metadata.ref, providerCapabilities: [...module.providerCapabilities], activation }];
|
|
156
|
+
}));
|
|
78
157
|
},
|
|
79
|
-
|
|
80
|
-
activated = result;
|
|
81
|
-
// Activation is asynchronous and unmount is not: if the surface went away
|
|
82
|
-
// while a vendor bundle was still downloading, stop what we just started
|
|
83
|
-
// rather than leaving a client nobody can reach.
|
|
84
|
-
if (unmounted) void result.deactivateAll();
|
|
85
|
-
});
|
|
86
|
-
|
|
158
|
+
);
|
|
87
159
|
return () => {
|
|
88
|
-
|
|
89
|
-
|
|
160
|
+
setRunning([]);
|
|
161
|
+
stop();
|
|
90
162
|
};
|
|
91
|
-
|
|
163
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- keyed by the granted SET, see grantedKey.
|
|
164
|
+
}, [registry, consentBased, key]);
|
|
165
|
+
return running;
|
|
92
166
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider SDKs RUNNING on this website page (#1735), published by the page layout.
|
|
3
|
+
*
|
|
4
|
+
* A section reaches a running vendor through here rather than through a global — the contact form asks the consented
|
|
5
|
+
* captcha vendor for a token this way. The list is emptied the moment a provider is stopped (a consent withdrawal, a
|
|
6
|
+
* navigation), so a consumer that reads it at the moment it needs a handle never holds one past a withdrawal.
|
|
7
|
+
*/
|
|
8
|
+
import { createContext, useContext } from 'react';
|
|
9
|
+
import type { ProviderFrontendSdkActivation } from '@wildo-ai/external-connectors-public/website';
|
|
10
|
+
|
|
11
|
+
/** One running provider: its ref, the capabilities it serves, and its activation (whose `handle` is the vendor's client). */
|
|
12
|
+
export interface WebsiteProviderSdkActivation {
|
|
13
|
+
readonly providerRef: string;
|
|
14
|
+
readonly providerCapabilities: ReadonlyArray<string>;
|
|
15
|
+
readonly activation: ProviderFrontendSdkActivation;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const WebsiteProviderSdkActivationsContext = createContext<ReadonlyArray<WebsiteProviderSdkActivation>>([]);
|
|
19
|
+
|
|
20
|
+
/** The provider SDKs running on this page, in declaration order. Empty outside a page layout and before any start. */
|
|
21
|
+
export function useWebsiteProviderSdkActivations(): ReadonlyArray<WebsiteProviderSdkActivation> {
|
|
22
|
+
return useContext(WebsiteProviderSdkActivationsContext);
|
|
23
|
+
}
|
|
@@ -71,9 +71,9 @@ export interface TypedWebsiteSectionDefinition<TProps>
|
|
|
71
71
|
* **Why a factory instead of constructing the literal directly**:
|
|
72
72
|
* - Consumers don't have to remember to parse refs / keys
|
|
73
73
|
* themselves before assembling the literal.
|
|
74
|
-
* - The schema parse provides a single audited validation chokepoint
|
|
75
|
-
*
|
|
76
|
-
* without re-entering the React graph.
|
|
74
|
+
* - The schema parse provides a single audited validation chokepoint,
|
|
75
|
+
* and the parsed `expectedLabelKeys` are what `wildo website audit`
|
|
76
|
+
* checks every locale pack against, without re-entering the React graph.
|
|
77
77
|
* - The factory is the documented authoring API the
|
|
78
78
|
* `website_factory_authoring` skill targets — keeping the
|
|
79
79
|
* authoring surface narrow lets the framework evolve the
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import React, { useMemo, type ReactNode } from 'react';
|
|
2
2
|
import { BUILTIN_PROVIDER_CAPABILITY } from '@wildo-ai/saas-models/public-runtime';
|
|
3
|
+
import { consentPurposesInUseByProviders } from '@wildo-ai/external-connectors-public/website';
|
|
3
4
|
|
|
4
5
|
import { WebsitePageContextProvider } from '../contexts/WebsitePageContext';
|
|
5
6
|
import {
|
|
6
7
|
useWebsiteProviderScripts,
|
|
7
8
|
useWebsiteProviderSdks,
|
|
8
9
|
} from '../external-providers/useWebsiteProviderScripts';
|
|
10
|
+
import { WebsiteProviderSdkActivationsContext } from '../external-providers/website-provider-sdk-activations';
|
|
9
11
|
import type { WebsitePageContextValue } from '../contexts/WebsitePageContext';
|
|
10
12
|
import { useWebsiteRuntime } from '../contexts/useWebsiteRuntime';
|
|
13
|
+
import { websiteDesignTokenCssVar } from '../../schemas/design-tokens/website-design-tokens.shared.schemas';
|
|
11
14
|
import { useWebsiteLabelByKey } from '../hooks/useWebsiteLabel';
|
|
15
|
+
import { WebsiteConsentProvider, WebsiteConsentStatus, type WebsiteConsentRecorder } from '../consent/WebsiteConsentContext';
|
|
16
|
+
import { useWebsiteConsent } from '../consent/useWebsiteConsent';
|
|
17
|
+
import { WebsiteConsentBanner, WebsiteConsentSettingsButton } from '../consent/WebsiteConsentBanner';
|
|
12
18
|
import {
|
|
13
19
|
WebsitePageRefSchema,
|
|
14
20
|
type WebsitePageRef,
|
|
@@ -96,10 +102,10 @@ const SKIP_LINK_FOCUS_CSS = `
|
|
|
96
102
|
overflow: visible !important;
|
|
97
103
|
clip: auto !important;
|
|
98
104
|
white-space: normal !important;
|
|
99
|
-
background:
|
|
100
|
-
color:
|
|
101
|
-
border: 2px solid
|
|
102
|
-
border-radius:
|
|
105
|
+
background: ${websiteDesignTokenCssVar('colors', 'background', '#fff')} !important;
|
|
106
|
+
color: ${websiteDesignTokenCssVar('colors', 'foreground', '#000')} !important;
|
|
107
|
+
border: 2px solid ${websiteDesignTokenCssVar('colors', 'primary', '#000')} !important;
|
|
108
|
+
border-radius: ${websiteDesignTokenCssVar('radii', 'md', '0.375rem')} !important;
|
|
103
109
|
z-index: 9999 !important;
|
|
104
110
|
text-decoration: none !important;
|
|
105
111
|
}
|
|
@@ -133,6 +139,14 @@ const SKIP_LINK_FOCUS_CSS = `
|
|
|
133
139
|
* captcha so non-React tooling can detect the configured vendor
|
|
134
140
|
* without reverse-engineering app code.
|
|
135
141
|
*
|
|
142
|
+
* 4. **Provides the visitor's consent state and enforces it** (#534) —
|
|
143
|
+
* mounts `WebsiteConsentProvider`, starts a provider SDK or script only
|
|
144
|
+
* once the purpose it rests on is granted (and stops the SDK when it is
|
|
145
|
+
* withdrawn), and renders the consent panel when the site runs a
|
|
146
|
+
* consent-based provider and the visitor has not decided. Here rather
|
|
147
|
+
* than in each bridge because every page — including every blog post —
|
|
148
|
+
* renders through this layout, so no page can be mounted without it.
|
|
149
|
+
*
|
|
136
150
|
* **Why semantic `<header>` / `<main>` / `<footer>` elements**:
|
|
137
151
|
* marketing-site SEO scoring penalizes pages that ship a single
|
|
138
152
|
* unstructured `<div>` tree. Using semantic landmarks costs nothing
|
|
@@ -178,6 +192,17 @@ export interface WebsitePageLayoutProps {
|
|
|
178
192
|
* vertical-rhythm overrides on long-scroll landing pages.
|
|
179
193
|
*/
|
|
180
194
|
mainClassName?: string;
|
|
195
|
+
/**
|
|
196
|
+
* OPTIONAL anonymous-session client. When supplied, a visitor's consent decision is ALSO recorded
|
|
197
|
+
* server-side against their session, which is what lets it follow them into the account they go on
|
|
198
|
+
* to create (#1258). Pass the SAME instance the page's capture sections use, so one `uuidRef`
|
|
199
|
+
* carries the decision, the lead and the draft note together.
|
|
200
|
+
*
|
|
201
|
+
* Absent keeps today's behaviour exactly: the decision lives in this browser and nowhere else.
|
|
202
|
+
* The engine cannot construct the client here — it needs the application's own
|
|
203
|
+
* `frontendServiceName` — which is why it arrives as a prop rather than off the runtime context.
|
|
204
|
+
*/
|
|
205
|
+
consentRecorder?: WebsiteConsentRecorder;
|
|
181
206
|
/**
|
|
182
207
|
* The page's section composition. Typically a sequence of
|
|
183
208
|
* `<WebsiteSection>` wrappers around concrete section components.
|
|
@@ -185,7 +210,21 @@ export interface WebsitePageLayoutProps {
|
|
|
185
210
|
children: ReactNode;
|
|
186
211
|
}
|
|
187
212
|
|
|
188
|
-
export function WebsitePageLayout({
|
|
213
|
+
export function WebsitePageLayout(props: WebsitePageLayoutProps): ReactNode {
|
|
214
|
+
const runtime = useWebsiteRuntime();
|
|
215
|
+
// Derived from the LIVE providers, so the panel asks about exactly what this site processes.
|
|
216
|
+
const purposesInUse = useMemo(
|
|
217
|
+
() => consentPurposesInUseByProviders(runtime.frontendProviderRegistry?.modules.values()),
|
|
218
|
+
[runtime.frontendProviderRegistry],
|
|
219
|
+
);
|
|
220
|
+
return (
|
|
221
|
+
<WebsiteConsentProvider purposesInUse={purposesInUse} recorder={props.consentRecorder}>
|
|
222
|
+
<WebsitePageLayoutContent {...props} />
|
|
223
|
+
</WebsiteConsentProvider>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function WebsitePageLayoutContent({
|
|
189
228
|
pageRef,
|
|
190
229
|
header,
|
|
191
230
|
footer,
|
|
@@ -194,6 +233,7 @@ export function WebsitePageLayout({
|
|
|
194
233
|
children,
|
|
195
234
|
}: WebsitePageLayoutProps): ReactNode {
|
|
196
235
|
const runtime = useWebsiteRuntime();
|
|
236
|
+
const consent = useWebsiteConsent();
|
|
197
237
|
|
|
198
238
|
/**
|
|
199
239
|
* Defensive parse: callers should pass a `WebsitePageManifest.ref`
|
|
@@ -240,8 +280,12 @@ export function WebsitePageLayout({
|
|
|
240
280
|
// resolved provider refs and stamped them into the attributes below and
|
|
241
281
|
// stopped there — a provider could be declared, enabled and hydrated without
|
|
242
282
|
// anything of it ever running.
|
|
243
|
-
|
|
244
|
-
|
|
283
|
+
//
|
|
284
|
+
// Consent-based providers start only once their purpose is granted (#534). Before the stored decision is
|
|
285
|
+
// read, nothing is granted — the static HTML and the first render start nothing optional.
|
|
286
|
+
useWebsiteProviderScripts(runtime.frontendProviderRegistry, runtime.providerComponentRegistry);
|
|
287
|
+
// The running SDKs are published to the page (#1735): the contact form asks the consented captcha vendor for a token.
|
|
288
|
+
const runningProviderSdks = useWebsiteProviderSdks(runtime.frontendProviderRegistry, consent.grantedPurposes);
|
|
245
289
|
|
|
246
290
|
const providerRootAttributes = useMemo<Record<string, string>>(() => {
|
|
247
291
|
const attrs: Record<string, string> = {};
|
|
@@ -289,6 +333,7 @@ export function WebsitePageLayout({
|
|
|
289
333
|
|
|
290
334
|
return (
|
|
291
335
|
<WebsitePageContextProvider value={pageContextValue}>
|
|
336
|
+
<WebsiteProviderSdkActivationsContext.Provider value={runningProviderSdks}>
|
|
292
337
|
<div
|
|
293
338
|
data-website-page-ref={safePageRef as unknown as string}
|
|
294
339
|
className={className}
|
|
@@ -314,9 +359,32 @@ export function WebsitePageLayout({
|
|
|
314
359
|
{children}
|
|
315
360
|
</main>
|
|
316
361
|
{resolvedFooter !== null ? <footer>{resolvedFooter}</footer> : null}
|
|
362
|
+
{renderConsentSurface(consent, runtime.consentBannerComponent)}
|
|
317
363
|
</div>
|
|
364
|
+
</WebsiteProviderSdkActivationsContext.Provider>
|
|
318
365
|
</WebsitePageContextProvider>
|
|
319
366
|
);
|
|
320
367
|
}
|
|
321
368
|
WebsitePageLayout.displayName = 'WebsitePageLayout';
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* The panel while a decision is owed (or re-opened), the reopen button once one is made, and nothing at
|
|
372
|
+
* all on a site whose live providers rest on no consent. Nothing renders before the stored decision is
|
|
373
|
+
* read, so a visitor who already decided never sees the panel flash.
|
|
374
|
+
*/
|
|
375
|
+
function renderConsentSurface(
|
|
376
|
+
consent: ReturnType<typeof useWebsiteConsent>,
|
|
377
|
+
BannerOverride: React.ComponentType | undefined,
|
|
378
|
+
): ReactNode {
|
|
379
|
+
if (!consent.isResolved || consent.purposesInUse.length === 0) return null;
|
|
380
|
+
if (consent.status === WebsiteConsentStatus.UNDECIDED || consent.isSettingsOpen) {
|
|
381
|
+
const Banner = BannerOverride ?? WebsiteConsentBanner;
|
|
382
|
+
return <Banner />;
|
|
383
|
+
}
|
|
384
|
+
return (
|
|
385
|
+
<div data-website-consent-settings="">
|
|
386
|
+
<WebsiteConsentSettingsButton />
|
|
387
|
+
</div>
|
|
388
|
+
);
|
|
389
|
+
}
|
|
322
390
|
/** @wildo_source:part:end saas.website.page-layout.component */
|
package/src/index.ts
CHANGED
|
@@ -11,75 +11,33 @@
|
|
|
11
11
|
* accompanying SKILLs in `framework-knowledge`.
|
|
12
12
|
*
|
|
13
13
|
* @wildo-boundary
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* This barrel re-exports the React-side surface: `schemas/`, `core/`,
|
|
15
|
+
* `components/`. The other entries are separate subpaths with their own
|
|
16
|
+
* audiences — `/astro` (Node, Astro frontmatter), `/astro-island` (the
|
|
17
|
+
* bridge factory), `/config` and `/config-loader` (Node), `/mdx`,
|
|
18
|
+
* `/companion` (React-free). Per directory:
|
|
17
19
|
* - `schemas/` : NO React, NO Astro, NO `~wildo-frontend-lib/**`
|
|
18
|
-
* - `
|
|
19
|
-
* - `
|
|
20
|
-
* - `
|
|
20
|
+
* - `core/`, `components/`: MAY use React; NO `~wildo-frontend-lib/**` (Q17=B)
|
|
21
|
+
* - `astro/` : MAY use Astro and Node; NO React component runtime imports
|
|
22
|
+
* - `config/` : Node-side loaders; never reachable from a React-island entry
|
|
21
23
|
* - `mdx/` : MAY use `@mdx-js/*`
|
|
22
|
-
* - `integration/` : MAY use React; MAY import the
|
|
23
|
-
* `@wildo-ai/saas-models/public-runtime` SUBPATH,
|
|
24
|
-
* the `@wildo-ai/presets-components-models/public-runtime`
|
|
25
|
-
* SUBPATH, `@wildo-ai/zod-decorators`,
|
|
26
|
-
* plus the SSR / Astro-middleware tier
|
|
27
|
-
* (`axios`, `jsonwebtoken`, `socket.io-client`,
|
|
28
|
-
* `reflect-metadata`) — D12 revised
|
|
29
|
-
* 2026-04-19 (second same-day loosening);
|
|
30
|
-
* the ROOT barrels of `@wildo-ai/saas-models`
|
|
31
|
-
* and `@wildo-ai/presets-components-models`
|
|
32
|
-
* are FORBIDDEN (they boot the decorator
|
|
33
|
-
* framework / pull the heavy registry);
|
|
34
|
-
* the saas-app DI / backend stack
|
|
35
|
-
* (`inversify`, `mongoose`, `express`,
|
|
36
|
-
* `pino`, `helmet`, `argon2`, `bcrypt`,
|
|
37
|
-
* the `@wildo-ai/saas-frontend-lib` /
|
|
38
|
-
* `@wildo-ai/saas-backend-lib` /
|
|
39
|
-
* `@wildo-ai/external-connectors-models`
|
|
40
|
-
* root + subpath surfaces) is FORBIDDEN;
|
|
41
|
-
* MUST NOT import `~wildo-frontend-lib/**`
|
|
42
|
-
* (Q17=B / D16)
|
|
43
24
|
*
|
|
44
|
-
* D12 (client-bundle import discipline
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* internal seam for symbols not (yet) re-exported by saas-models.
|
|
52
|
-
* - `@wildo-ai/zod-decorators` is allowed to import (first revision
|
|
53
|
-
* 2026-04-19). The package itself is side-effect-free; only an
|
|
54
|
-
* explicit `initZodDecorators()` call mutates `z.ZodType.prototype`,
|
|
55
|
-
* which neither saas-website nor the saas-models /public-runtime
|
|
56
|
-
* barrel does.
|
|
57
|
-
* - `axios`, `jsonwebtoken`, `socket.io-client`, `reflect-metadata` are
|
|
58
|
-
* allowed for SSR / Astro-middleware code paths (second same-day
|
|
59
|
-
* revision 2026-04-19). The website engine needs them to provide
|
|
60
|
-
* middleware utilities (HTTP proxying to the SaaS backend, JWT
|
|
61
|
-
* verification of cross-frontend handoff codes, real-time push,
|
|
62
|
-
* decorator-metadata reads). They are SSR-tier — discipline
|
|
63
|
-
* (and a future strict/loose entry split for React islands vs
|
|
64
|
-
* middleware) keeps them out of the React-island bundle.
|
|
25
|
+
* D12 (client-bundle import discipline): everything reachable from a
|
|
26
|
+
* React-island entry ships to the browser, so every shared model atom
|
|
27
|
+
* comes from a side-effect-free `/public-runtime` subpath —
|
|
28
|
+
* `@wildo-ai/saas-models/public-runtime` first,
|
|
29
|
+
* `@wildo-ai/presets-components-models/public-runtime` for symbols it does
|
|
30
|
+
* not re-export yet — and `@wildo-ai/zod-decorators` is allowed because
|
|
31
|
+
* only an explicit `initZodDecorators()` mutates Zod's prototype.
|
|
65
32
|
*
|
|
66
|
-
* The ROOT
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* `@wildo-ai/
|
|
71
|
-
* (`
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* These boundaries are enforced by review + the bundle-isolation test
|
|
76
|
-
* above until Phase 8 promotes them to a programmatic ESLint check
|
|
77
|
-
* (cf. `saas-website-slice.md`
|
|
78
|
-
* §"DEFERRED to Phase 8 — boundary lint mechanism design").
|
|
79
|
-
*
|
|
80
|
-
* Phase 1+2 status: schemas + runtime contexts/hooks + layout
|
|
81
|
-
* primitives + L1 placeholders + factories are all exported. Phase 5
|
|
82
|
-
* adds the Astro adapter; Phase 5.7 the `integration/` layer.
|
|
33
|
+
* The ROOT barrels of `@wildo-ai/saas-models` and
|
|
34
|
+
* `@wildo-ai/presets-components-models` are forbidden everywhere in this
|
|
35
|
+
* package (they boot the decorator framework and pull the full schema
|
|
36
|
+
* graph), as are the saas-app DI runtime (`inversify`,
|
|
37
|
+
* `@wildo-ai/saas-frontend-lib`, `@wildo-ai/saas-backend-lib`) and
|
|
38
|
+
* pure-backend libs (`mongoose`, `express`, `pino`, `helmet`, `argon2`,
|
|
39
|
+
* `bcrypt`). Enforced by `src/__tests__/bundle-isolation.test.ts`, which
|
|
40
|
+
* walks every entry's import graph against an allow-list.
|
|
83
41
|
*
|
|
84
42
|
* D18 (L1 boundary, Phase 2): the L1 primitives (`WebsiteHeading`,
|
|
85
43
|
* `WebsiteText`, `WebsiteButton`, `WebsiteLink`, `WebsiteImage`) are
|
|
@@ -95,10 +53,13 @@
|
|
|
95
53
|
export * from './schemas/refs/page-ref.schemas';
|
|
96
54
|
export * from './schemas/refs/section-ref.schemas';
|
|
97
55
|
export * from './schemas/label-keys/website-label-key.schemas';
|
|
56
|
+
export * from './schemas/label-keys/website-consent-label-keys.schemas';
|
|
57
|
+
export * from './schemas/label-keys/website-provider-component-label-keys.schemas';
|
|
98
58
|
export * from './schemas/sections/website-section-category.shared';
|
|
99
59
|
export * from './schemas/sections/website-section-definition.shared.schemas';
|
|
100
60
|
export * from './schemas/structured-data/website-structured-data.shared.schemas';
|
|
101
61
|
export * from './schemas/structured-data/structured-data-reconciliation.shared';
|
|
62
|
+
export * from './schemas/structured-data/pricing-offer-structured-data.shared';
|
|
102
63
|
export * from './schemas/design-tokens/website-design-tokens.shared.schemas';
|
|
103
64
|
export * from './schemas/manifests/website-page-manifest.shared.schemas';
|
|
104
65
|
export * from './schemas/manifests/website-root-config.shared.schemas';
|
|
@@ -118,9 +79,21 @@ export * from './core/factories/define-website-page-manifest';
|
|
|
118
79
|
export * from './core/layouts/WebsitePageLayout';
|
|
119
80
|
export * from './core/layouts/WebsiteSection';
|
|
120
81
|
|
|
82
|
+
export * from './core/consent/website-consent-storage';
|
|
83
|
+
export * from './core/consent/WebsiteConsentContext';
|
|
84
|
+
export * from './core/consent/useWebsiteConsent';
|
|
85
|
+
export * from './core/consent/WebsiteConsentGate';
|
|
86
|
+
export * from './core/consent/WebsiteConsentBanner';
|
|
87
|
+
|
|
88
|
+
export * from './core/external-providers/provider-component-registry.website';
|
|
89
|
+
export * from './core/external-providers/useWebsiteProviderComponent';
|
|
90
|
+
export * from './core/external-providers/WebsiteProviderComponent';
|
|
91
|
+
export * from './core/external-providers/website-provider-sdk-activations';
|
|
92
|
+
|
|
121
93
|
export * from './core/anonymous-session/website-anonymous-session-client';
|
|
122
94
|
export * from './core/anonymous-session/inbound-contact-form.schema';
|
|
123
95
|
export * from './core/anonymous-session/InboundContactForm';
|
|
96
|
+
export * from './core/anonymous-session/website-submission-challenge';
|
|
124
97
|
|
|
125
98
|
export * from './components/low-level/WebsiteHeading';
|
|
126
99
|
export * from './components/low-level/WebsiteText';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { renderWebsiteDesignTokensCss } from '../../astro/website-page-head.renderer';
|
|
7
|
+
import {
|
|
8
|
+
WebsiteDesignTokensSchema,
|
|
9
|
+
websiteDesignTokenCssProperty,
|
|
10
|
+
websiteDesignTokenCssVar,
|
|
11
|
+
type WebsiteDesignTokens,
|
|
12
|
+
} from '../design-tokens/website-design-tokens.shared.schemas';
|
|
13
|
+
|
|
14
|
+
const TOKENS: WebsiteDesignTokens = WebsiteDesignTokensSchema.parse({
|
|
15
|
+
colors: {
|
|
16
|
+
background: '#fff',
|
|
17
|
+
foreground: '#111',
|
|
18
|
+
primary: '#222',
|
|
19
|
+
primaryForeground: '#fff',
|
|
20
|
+
accent: '#333',
|
|
21
|
+
accentForeground: '#fff',
|
|
22
|
+
surface: '#eee',
|
|
23
|
+
surfaceForeground: '#444',
|
|
24
|
+
border: '#ccc',
|
|
25
|
+
muted: '#ddd',
|
|
26
|
+
mutedForeground: '#555',
|
|
27
|
+
},
|
|
28
|
+
radii: { none: '0', sm: '0.25rem', md: '0.5rem', lg: '0.75rem', xl: '1rem', full: '9999px' },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const SOURCE_ROOT = join(__dirname, '..', '..');
|
|
32
|
+
|
|
33
|
+
function sourceFiles(dir: string): string[] {
|
|
34
|
+
return readdirSync(dir).flatMap((entry) => {
|
|
35
|
+
const full = join(dir, entry);
|
|
36
|
+
if (statSync(full).isDirectory()) return entry === '__tests__' ? [] : sourceFiles(full);
|
|
37
|
+
return /\.(ts|tsx)$/.test(entry) ? [full] : [];
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('design-token CSS property naming', () => {
|
|
42
|
+
it('the renderer emits every token under the name the readers reference', () => {
|
|
43
|
+
const css = renderWebsiteDesignTokensCss(TOKENS);
|
|
44
|
+
for (const [group, values] of Object.entries(TOKENS)) {
|
|
45
|
+
for (const token of Object.keys(values)) {
|
|
46
|
+
expect(css).toContain(`${websiteDesignTokenCssProperty(group, token)}:`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// The key is verbatim: the reader the consent banner used to be spelled `surface-foreground`.
|
|
50
|
+
expect(css).toContain('--website-colors-surfaceForeground: #444;');
|
|
51
|
+
expect(websiteDesignTokenCssVar('colors', 'surfaceForeground', '#000')).toBe('var(--website-colors-surfaceForeground, #000)');
|
|
52
|
+
expect(websiteDesignTokenCssVar('radii', 'md')).toBe('var(--website-radii-md)');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('no engine source spells a token custom property by hand', () => {
|
|
56
|
+
// Positive control: the walk reaches the component that used to carry the defect.
|
|
57
|
+
const files = sourceFiles(SOURCE_ROOT);
|
|
58
|
+
expect(files.some((file) => file.endsWith('WebsiteConsentBanner.tsx'))).toBe(true);
|
|
59
|
+
|
|
60
|
+
const offenders = files
|
|
61
|
+
.filter((file) => !file.endsWith('website-design-tokens.shared.schemas.ts'))
|
|
62
|
+
.flatMap((file) =>
|
|
63
|
+
readFileSync(file, 'utf8')
|
|
64
|
+
.split('\n')
|
|
65
|
+
.map((line, index) => ({ line, index }))
|
|
66
|
+
// A `var(--website-…)` in code, not in a comment explaining the naming rule.
|
|
67
|
+
.filter(({ line }) => /var\(--website-/.test(line) && !/^\s*(\*|\/\/)/.test(line))
|
|
68
|
+
.map(({ index }) => `${relative(SOURCE_ROOT, file)}:${index + 1}`),
|
|
69
|
+
);
|
|
70
|
+
expect(offenders).toEqual([]);
|
|
71
|
+
});
|
|
72
|
+
});
|