@wildo-ai/saas-website 1.1.3 → 1.1.5
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 +14 -20
- package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -1
- package/dist/esm/astro/blog-post-bridge.js +4 -2
- package/dist/esm/astro/blog-post-bridge.js.map +1 -1
- package/dist/esm/astro/bridge-runtime.d.ts +30 -20
- package/dist/esm/astro/bridge-runtime.d.ts.map +1 -1
- package/dist/esm/astro/bridge-runtime.js +13 -3
- package/dist/esm/astro/bridge-runtime.js.map +1 -1
- package/dist/esm/astro/label-pack-loader.d.ts.map +1 -1
- package/dist/esm/astro/label-pack-loader.js +5 -0
- package/dist/esm/astro/label-pack-loader.js.map +1 -1
- package/dist/esm/config/load-website-config.d.ts.map +1 -1
- package/dist/esm/config/load-website-config.js +2 -1
- package/dist/esm/config/load-website-config.js.map +1 -1
- 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/core/anonymous-session/inbound-contact-form.schema.d.ts +3 -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 +13 -9
- 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 +11 -0
- 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 +14 -1
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -1
- 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 +128 -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/WebsiteRuntimeContext.d.ts +8 -1
- 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/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/useWebsiteProviderScripts.d.ts +33 -10
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.js +64 -16
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.js.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 +32 -4
- package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -1
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.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/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -29
- package/src/astro/__tests__/blog-post-bridge.test.tsx +41 -0
- package/src/astro/__tests__/bridge-runtime.test.tsx +25 -0
- package/src/astro/__tests__/label-pack-loader.test.ts +26 -1
- package/src/astro/blog-post-bridge.tsx +18 -22
- package/src/astro/bridge-runtime.tsx +45 -23
- package/src/astro/label-pack-loader.ts +4 -0
- package/src/config/load-website-config.ts +2 -1
- package/src/config/wildo-website-config.schemas.ts +10 -5
- package/src/core/__tests__/WebsitePageLayout.test.tsx +1 -1
- package/src/core/__tests__/website-consent.test.tsx +311 -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 +14 -9
- package/src/core/anonymous-session/website-anonymous-session-client.ts +15 -1
- package/src/core/consent/WebsiteConsentBanner.tsx +222 -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 +8 -1
- package/src/core/external-providers/frontend-provider-registry.website.ts +1 -2
- package/src/core/external-providers/useWebsiteProviderScripts.ts +72 -17
- package/src/core/layouts/WebsitePageLayout.tsx +66 -3
- package/src/index.ts +7 -0
- package/src/schemas/label-keys/__tests__/scaffolded-consent-label-pack.parity.test.ts +71 -0
- package/src/schemas/label-keys/website-consent-label-keys.schemas.ts +78 -0
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontend-provider-registry.website.js","sourceRoot":"","sources":["../../../../../src/core/external-providers/frontend-provider-registry.website.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EACL,uCAAuC,GAExC,MAAM,8CAA8C,CAAC;
|
|
1
|
+
{"version":3,"file":"frontend-provider-registry.website.js","sourceRoot":"","sources":["../../../../../src/core/external-providers/frontend-provider-registry.website.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EACL,uCAAuC,GAExC,MAAM,8CAA8C,CAAC;AAwBtD;;GAEG;AACH,MAAM,UAAU,qCAAqC,CACnD,KAAkD;IAElD,OAAO,uCAAuC,CAAC;QAC7C,OAAO,EAAE,KAAK,EAAE,OAAO;QACvB,OAAO,EAAE,KAAK,EAAE,OAAO;QACvB,YAAY,EAAE,KAAK,EAAE,YAAY;KAClC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The website's provider registry — now composed from BOTH channels.\n *\n * It used to build itself by casting each served `FrontendProviderEntry` to a\n * module type that was an alias of that entry, which made the cast true and\n * useless: the registry could never hold anything a JSON payload could not\n * carry, so a provider's SDK loader had nowhere to arrive.\n *\n * - **DATA** — the materialized frontend-services block for THIS website\n * service. Authoritative for which providers are on, in this environment.\n * - **CODE** — `generatedFrontendProviderModules` from the website's own\n * `src/generated/frontend-providers.generated.ts`, written by\n * `wildo config sync` and handed in by the Astro bridge.\n *\n * The types are now the engine's own website-facet types rather than local\n * aliases, so a provider module authored against\n * `@wildo-ai/external-connectors-public/website` is the same type this registry\n * holds. Re-exported under the same names so every consumer's import is\n * unchanged.\n */\n\nimport {\n composeHydratedFrontendWebsiteProviders,\n type GeneratedWebsiteProviderModules,\n} from '@wildo-ai/external-connectors-public/website';\nimport type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';\nimport type { FrontendProviderHydrationDiagnostic } from '@wildo-ai/external-connectors-public/website';\n\nexport type {\n FrontendWebsiteProviderModule,\n FrontendWebsiteProviderRef,\n FrontendWebsiteProviderRegistry,\n GeneratedWebsiteProviderModules,\n} from '@wildo-ai/external-connectors-public/website';\n\nimport type { FrontendWebsiteProviderRegistry } from '@wildo-ai/external-connectors-public/website';\n\nexport interface CreateFrontendWebsiteProviderRegistryInput {\n /** The DATA channel: the materialized block for this website service. */\n readonly entries?: FrontendProvidersBlock;\n /**\n * The CODE channel. Absent is the ordinary case for a website that declares\n * no providers. Config sync still writes an empty map so startup imports remain stable.\n */\n readonly modules?: GeneratedWebsiteProviderModules;\n readonly onDiagnostic?: (diagnostic: FrontendProviderHydrationDiagnostic) => void;\n}\n\n/**\n * Website-side public-provider registry, hydrated from both channels.\n */\nexport function createFrontendWebsiteProviderRegistry(\n input?: CreateFrontendWebsiteProviderRegistryInput,\n): FrontendWebsiteProviderRegistry {\n return composeHydratedFrontendWebsiteProviders({\n entries: input?.entries,\n modules: input?.modules,\n onDiagnostic: input?.onDiagnostic,\n });\n}\n"]}
|
|
@@ -12,17 +12,33 @@
|
|
|
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
|
*/
|
|
33
|
+
import { type ConsentPurpose } from '@wildo-ai/saas-models/public-runtime';
|
|
24
34
|
import type { FrontendWebsiteProviderRegistry } from './frontend-provider-registry.website';
|
|
25
|
-
export declare function useWebsiteProviderScripts(registry: FrontendWebsiteProviderRegistry | undefined
|
|
35
|
+
export declare function useWebsiteProviderScripts(registry: FrontendWebsiteProviderRegistry | undefined,
|
|
36
|
+
/**
|
|
37
|
+
* The purposes the visitor granted (#534). A provider resting on an ungranted purpose injects nothing;
|
|
38
|
+
* granting it later injects its script then. A withdrawal cannot remove an executed script, so it takes
|
|
39
|
+
* effect on the next page load.
|
|
40
|
+
*/
|
|
41
|
+
grantedConsentPurposes: readonly ConsentPurpose[]): void;
|
|
26
42
|
/**
|
|
27
43
|
* Loads and starts the npm SDK of every live provider that declares one.
|
|
28
44
|
*
|
|
@@ -30,5 +46,12 @@ export declare function useWebsiteProviderScripts(registry: FrontendWebsiteProvi
|
|
|
30
46
|
* offers a teardown — an analytics client that cannot be stopped cannot honour
|
|
31
47
|
* a consent withdrawal, and accumulates one instance per view transition.
|
|
32
48
|
*/
|
|
33
|
-
export declare function useWebsiteProviderSdks(registry: FrontendWebsiteProviderRegistry | undefined
|
|
49
|
+
export declare function useWebsiteProviderSdks(registry: FrontendWebsiteProviderRegistry | undefined,
|
|
50
|
+
/**
|
|
51
|
+
* The purposes the visitor granted (#534). Providers are activated in TWO groups on purpose: those that
|
|
52
|
+
* rest on no purpose (error monitoring, captcha) are started once and never restarted by a consent
|
|
53
|
+
* change, while the consent-based ones start when their purpose is granted and are DEACTIVATED when it
|
|
54
|
+
* is withdrawn — which is the only way a withdrawal stops a running analytics client.
|
|
55
|
+
*/
|
|
56
|
+
grantedConsentPurposes: readonly ConsentPurpose[]): void;
|
|
34
57
|
//# sourceMappingURL=useWebsiteProviderScripts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebsiteProviderScripts.d.ts","sourceRoot":"","sources":["../../../../../src/core/external-providers/useWebsiteProviderScripts.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useWebsiteProviderScripts.d.ts","sourceRoot":"","sources":["../../../../../src/core/external-providers/useWebsiteProviderScripts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAUH,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAC5F,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,sCAAsC,CAAC;AAU5F,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,+BAA+B,GAAG,SAAS;AACrD;;;;GAIG;AACH,sBAAsB,EAAE,SAAS,cAAc,EAAE,GAChD,IAAI,CAeN;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,+BAA+B,GAAG,SAAS;AACrD;;;;;GAKG;AACH,sBAAsB,EAAE,SAAS,cAAc,EAAE,GAChD,IAAI,CAGN"}
|
|
@@ -12,24 +12,46 @@
|
|
|
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
|
import { useEffect } from 'react';
|
|
25
|
-
import { activateFrontendProviderSdks, } from '@wildo-ai/external-connectors-public/website';
|
|
26
|
-
import { collectDeclaredProviderScripts, injectProviderScripts, } from './provider-scripts.website.js';
|
|
34
|
+
import { activateFrontendProviderSdks, collectDeclaredProviderScripts, consentPurposesRequiredByProvider, injectProviderScripts, } from '@wildo-ai/external-connectors-public/website';
|
|
27
35
|
import { AppFrontendType } from '@wildo-ai/saas-models/public-runtime';
|
|
28
|
-
|
|
36
|
+
/**
|
|
37
|
+
* A stable key for a granted-purpose set, so an effect re-runs when the SET changes and not when a new
|
|
38
|
+
* array with the same members is handed in.
|
|
39
|
+
*/
|
|
40
|
+
function grantedKey(grantedConsentPurposes) {
|
|
41
|
+
return [...grantedConsentPurposes].sort().join(',');
|
|
42
|
+
}
|
|
43
|
+
export function useWebsiteProviderScripts(registry,
|
|
44
|
+
/**
|
|
45
|
+
* The purposes the visitor granted (#534). A provider resting on an ungranted purpose injects nothing;
|
|
46
|
+
* granting it later injects its script then. A withdrawal cannot remove an executed script, so it takes
|
|
47
|
+
* effect on the next page load.
|
|
48
|
+
*/
|
|
49
|
+
grantedConsentPurposes) {
|
|
50
|
+
const key = grantedKey(grantedConsentPurposes);
|
|
29
51
|
useEffect(() => {
|
|
30
52
|
if (typeof document === 'undefined')
|
|
31
53
|
return;
|
|
32
|
-
const scripts = collectDeclaredProviderScripts(registry);
|
|
54
|
+
const scripts = collectDeclaredProviderScripts(registry?.modules.values(), grantedConsentPurposes);
|
|
33
55
|
if (scripts.length === 0)
|
|
34
56
|
return;
|
|
35
57
|
injectProviderScripts(scripts, document);
|
|
@@ -37,7 +59,8 @@ export function useWebsiteProviderScripts(registry) {
|
|
|
37
59
|
// work immediately; removing the element undoes neither, so a cleanup that
|
|
38
60
|
// removed it would only make the next mount inject a SECOND copy of an
|
|
39
61
|
// already-running script. Idempotence by `src` is the real guard.
|
|
40
|
-
|
|
62
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- keyed by the granted SET, see grantedKey.
|
|
63
|
+
}, [registry, key]);
|
|
41
64
|
}
|
|
42
65
|
/**
|
|
43
66
|
* Loads and starts the npm SDK of every live provider that declares one.
|
|
@@ -46,15 +69,39 @@ export function useWebsiteProviderScripts(registry) {
|
|
|
46
69
|
* offers a teardown — an analytics client that cannot be stopped cannot honour
|
|
47
70
|
* a consent withdrawal, and accumulates one instance per view transition.
|
|
48
71
|
*/
|
|
49
|
-
export function useWebsiteProviderSdks(registry
|
|
72
|
+
export function useWebsiteProviderSdks(registry,
|
|
73
|
+
/**
|
|
74
|
+
* The purposes the visitor granted (#534). Providers are activated in TWO groups on purpose: those that
|
|
75
|
+
* rest on no purpose (error monitoring, captcha) are started once and never restarted by a consent
|
|
76
|
+
* change, while the consent-based ones start when their purpose is granted and are DEACTIVATED when it
|
|
77
|
+
* is withdrawn — which is the only way a withdrawal stops a running analytics client.
|
|
78
|
+
*/
|
|
79
|
+
grantedConsentPurposes) {
|
|
80
|
+
useProviderSdkGroup(registry, false, []);
|
|
81
|
+
useProviderSdkGroup(registry, true, grantedConsentPurposes);
|
|
82
|
+
}
|
|
83
|
+
function useProviderSdkGroup(registry, consentBased, grantedConsentPurposes) {
|
|
84
|
+
// Keyed by the grants this group's providers actually rest on, so changing an unrelated purpose does
|
|
85
|
+
// not stop and restart a running analytics client.
|
|
86
|
+
const relevant = registry === undefined
|
|
87
|
+
? []
|
|
88
|
+
: [...registry.modules.values()]
|
|
89
|
+
.filter((module) => (consentPurposesRequiredByProvider(module).length > 0) === consentBased)
|
|
90
|
+
.flatMap((module) => consentPurposesRequiredByProvider(module))
|
|
91
|
+
.filter((purpose) => grantedConsentPurposes.includes(purpose));
|
|
92
|
+
const key = grantedKey(relevant);
|
|
50
93
|
useEffect(() => {
|
|
51
94
|
if (registry === undefined)
|
|
52
95
|
return;
|
|
96
|
+
const modules = [...registry.modules.values()].filter((module) => (consentPurposesRequiredByProvider(module).length > 0) === consentBased);
|
|
97
|
+
if (modules.length === 0)
|
|
98
|
+
return;
|
|
53
99
|
let unmounted = false;
|
|
54
100
|
let activated;
|
|
55
101
|
void activateFrontendProviderSdks({
|
|
56
102
|
surface: AppFrontendType.STATIC_WEBSITE,
|
|
57
|
-
modules
|
|
103
|
+
modules,
|
|
104
|
+
grantedConsentPurposes,
|
|
58
105
|
onDiagnostic: (diagnostic) => {
|
|
59
106
|
// The website runtime has no logger service, and a provider that cannot
|
|
60
107
|
// start is a developer-facing fact that must not be silent.
|
|
@@ -73,6 +120,7 @@ export function useWebsiteProviderSdks(registry) {
|
|
|
73
120
|
unmounted = true;
|
|
74
121
|
void activated?.deactivateAll();
|
|
75
122
|
};
|
|
76
|
-
|
|
123
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- keyed by the granted SET, see grantedKey.
|
|
124
|
+
}, [registry, consentBased, key]);
|
|
77
125
|
}
|
|
78
126
|
//# sourceMappingURL=useWebsiteProviderScripts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebsiteProviderScripts.js","sourceRoot":"","sources":["../../../../../src/core/external-providers/useWebsiteProviderScripts.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useWebsiteProviderScripts.js","sourceRoot":"","sources":["../../../../../src/core/external-providers/useWebsiteProviderScripts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The website's executable-surface hooks: load what the live providers declared.\n *\n * Two mechanisms, one per kind of vendor. `useWebsiteProviderScripts` adds the\n * `<script>` tags a provider declared — the case a dynamic `import()` cannot\n * serve, and the dominant one on a marketing site. `useWebsiteProviderSdks`\n * activates the npm SDK of any provider that ships one, which reaches this\n * bundle through the CODE channel.\n *\n * Both are effect-only, and that is not incidental: this is an Astro site, so\n * page modules execute in Node at build time. Injecting into a document that\n * does not exist would throw, and starting a vendor SDK there would run a live\n * analytics client inside a build.\n *\n * Both come from `@wildo-ai/external-connectors-public/website`. The script\n * injector lives there rather than here so the SaaS application, the technical\n * doc and this site run ONE copy of it: a second copy of a DOM mutation\n * eventually disagrees about idempotence, which is how a vendor tag gets loaded\n * twice. It first shipped here alone, and that is how the application came to\n * declare its Drive Picker's Google API script and never inject it.\n *\n * ## Client-side injection, and the alternative that was rejected\n *\n * This is an Astro static site, so the \"obvious\" home for a third-party tag is\n * the built HTML `<head>`. That was rejected for now: the set of live providers\n * is per SERVICE and per ENVIRONMENT, resolved from the materialized artifact,\n * while the built HTML is one artifact shared across deploys of the same build.\n * Baking the tags in would make the build environment-specific — the same class\n * of coupling the DATA channel exists to avoid. Injecting from the client keeps\n * one build correct everywhere, at the cost of the tag arriving after hydration.\n * Revisit if a provider ever needs to run before first paint.\n */\n\nimport { useEffect } from 'react';\nimport {\n activateFrontendProviderSdks,\n collectDeclaredProviderScripts,\n consentPurposesRequiredByProvider,\n injectProviderScripts,\n type ActivatedFrontendProviderSdks,\n} from '@wildo-ai/external-connectors-public/website';\nimport { AppFrontendType, type ConsentPurpose } from '@wildo-ai/saas-models/public-runtime';\nimport type { FrontendWebsiteProviderRegistry } from './frontend-provider-registry.website';\n\n/**\n * A stable key for a granted-purpose set, so an effect re-runs when the SET changes and not when a new\n * array with the same members is handed in.\n */\nfunction grantedKey(grantedConsentPurposes: readonly ConsentPurpose[]): string {\n return [...grantedConsentPurposes].sort().join(',');\n}\n\nexport function useWebsiteProviderScripts(\n registry: FrontendWebsiteProviderRegistry | undefined,\n /**\n * The purposes the visitor granted (#534). A provider resting on an ungranted purpose injects nothing;\n * granting it later injects its script then. A withdrawal cannot remove an executed script, so it takes\n * effect on the next page load.\n */\n grantedConsentPurposes: readonly ConsentPurpose[],\n): void {\n const key = grantedKey(grantedConsentPurposes);\n useEffect(() => {\n if (typeof document === 'undefined') return;\n\n const scripts = collectDeclaredProviderScripts(registry?.modules.values(), grantedConsentPurposes);\n if (scripts.length === 0) return;\n\n injectProviderScripts(scripts, document);\n // Deliberately no teardown. A vendor tag installs a global and often starts\n // work immediately; removing the element undoes neither, so a cleanup that\n // removed it would only make the next mount inject a SECOND copy of an\n // already-running script. Idempotence by `src` is the real guard.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- keyed by the granted SET, see grantedKey.\n }, [registry, key]);\n}\n\n/**\n * Loads and starts the npm SDK of every live provider that declares one.\n *\n * Unlike a script tag, an SDK activation IS undone on unmount where the vendor\n * offers a teardown — an analytics client that cannot be stopped cannot honour\n * a consent withdrawal, and accumulates one instance per view transition.\n */\nexport function useWebsiteProviderSdks(\n registry: FrontendWebsiteProviderRegistry | undefined,\n /**\n * The purposes the visitor granted (#534). Providers are activated in TWO groups on purpose: those that\n * rest on no purpose (error monitoring, captcha) are started once and never restarted by a consent\n * change, while the consent-based ones start when their purpose is granted and are DEACTIVATED when it\n * is withdrawn — which is the only way a withdrawal stops a running analytics client.\n */\n grantedConsentPurposes: readonly ConsentPurpose[],\n): void {\n useProviderSdkGroup(registry, false, []);\n useProviderSdkGroup(registry, true, grantedConsentPurposes);\n}\n\nfunction useProviderSdkGroup(\n registry: FrontendWebsiteProviderRegistry | undefined,\n consentBased: boolean,\n grantedConsentPurposes: readonly ConsentPurpose[],\n): void {\n // Keyed by the grants this group's providers actually rest on, so changing an unrelated purpose does\n // not stop and restart a running analytics client.\n const relevant = registry === undefined\n ? []\n : [...registry.modules.values()]\n .filter((module) => (consentPurposesRequiredByProvider(module).length > 0) === consentBased)\n .flatMap((module) => consentPurposesRequiredByProvider(module))\n .filter((purpose) => grantedConsentPurposes.includes(purpose));\n const key = grantedKey(relevant);\n useEffect(() => {\n if (registry === undefined) return;\n const modules = [...registry.modules.values()].filter(\n (module) => (consentPurposesRequiredByProvider(module).length > 0) === consentBased,\n );\n if (modules.length === 0) return;\n\n let unmounted = false;\n let activated: ActivatedFrontendProviderSdks | undefined;\n\n void activateFrontendProviderSdks({\n surface: AppFrontendType.STATIC_WEBSITE,\n modules,\n grantedConsentPurposes,\n onDiagnostic: (diagnostic) => {\n // The website runtime has no logger service, and a provider that cannot\n // start is a developer-facing fact that must not be silent.\n // eslint-disable-next-line no-console\n console.warn(`[wildo-provider-sdk] ${diagnostic.kind}: ${diagnostic.message}`);\n },\n }).then((result) => {\n activated = result;\n // Activation is asynchronous and unmount is not: if the surface went away\n // while a vendor bundle was still downloading, stop what we just started\n // rather than leaving a client nobody can reach.\n if (unmounted) void result.deactivateAll();\n });\n\n return () => {\n unmounted = true;\n void activated?.deactivateAll();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- keyed by the granted SET, see grantedKey.\n }, [registry, consentBased, key]);\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
+
import { type WebsiteConsentRecorder } from '../consent/WebsiteConsentContext';
|
|
2
3
|
import { type WebsitePageRef } from '../../schemas/refs/page-ref.schemas';
|
|
3
4
|
/**
|
|
4
5
|
* @wildo_source:part:start saas.website.page-layout.component facet:layer:core facet:family:website
|
|
@@ -28,6 +29,14 @@ import { type WebsitePageRef } from '../../schemas/refs/page-ref.schemas';
|
|
|
28
29
|
* captcha so non-React tooling can detect the configured vendor
|
|
29
30
|
* without reverse-engineering app code.
|
|
30
31
|
*
|
|
32
|
+
* 4. **Provides the visitor's consent state and enforces it** (#534) —
|
|
33
|
+
* mounts `WebsiteConsentProvider`, starts a provider SDK or script only
|
|
34
|
+
* once the purpose it rests on is granted (and stops the SDK when it is
|
|
35
|
+
* withdrawn), and renders the consent panel when the site runs a
|
|
36
|
+
* consent-based provider and the visitor has not decided. Here rather
|
|
37
|
+
* than in each bridge because every page — including every blog post —
|
|
38
|
+
* renders through this layout, so no page can be mounted without it.
|
|
39
|
+
*
|
|
31
40
|
* **Why semantic `<header>` / `<main>` / `<footer>` elements**:
|
|
32
41
|
* marketing-site SEO scoring penalizes pages that ship a single
|
|
33
42
|
* unstructured `<div>` tree. Using semantic landmarks costs nothing
|
|
@@ -73,13 +82,24 @@ export interface WebsitePageLayoutProps {
|
|
|
73
82
|
* vertical-rhythm overrides on long-scroll landing pages.
|
|
74
83
|
*/
|
|
75
84
|
mainClassName?: string;
|
|
85
|
+
/**
|
|
86
|
+
* OPTIONAL anonymous-session client. When supplied, a visitor's consent decision is ALSO recorded
|
|
87
|
+
* server-side against their session, which is what lets it follow them into the account they go on
|
|
88
|
+
* to create (#1258). Pass the SAME instance the page's capture sections use, so one `uuidRef`
|
|
89
|
+
* carries the decision, the lead and the draft note together.
|
|
90
|
+
*
|
|
91
|
+
* Absent keeps today's behaviour exactly: the decision lives in this browser and nowhere else.
|
|
92
|
+
* The engine cannot construct the client here — it needs the application's own
|
|
93
|
+
* `frontendServiceName` — which is why it arrives as a prop rather than off the runtime context.
|
|
94
|
+
*/
|
|
95
|
+
consentRecorder?: WebsiteConsentRecorder;
|
|
76
96
|
/**
|
|
77
97
|
* The page's section composition. Typically a sequence of
|
|
78
98
|
* `<WebsiteSection>` wrappers around concrete section components.
|
|
79
99
|
*/
|
|
80
100
|
children: ReactNode;
|
|
81
101
|
}
|
|
82
|
-
export declare function WebsitePageLayout(
|
|
102
|
+
export declare function WebsitePageLayout(props: WebsitePageLayoutProps): ReactNode;
|
|
83
103
|
export declare namespace WebsitePageLayout {
|
|
84
104
|
var displayName: string;
|
|
85
105
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsitePageLayout.d.ts","sourceRoot":"","sources":["../../../../../src/core/layouts/WebsitePageLayout.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"WebsitePageLayout.d.ts","sourceRoot":"","sources":["../../../../../src/core/layouts/WebsitePageLayout.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAYvD,OAAO,EAAgD,KAAK,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAG7H,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,qCAAqC,CAAC;AA6F7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,OAAO,EAAE,cAAc,GAAG,MAAM,CAAC;IACjC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,SAAS,CAY1E;yBAZe,iBAAiB;;;AA8KjC,gEAAgE"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { BUILTIN_PROVIDER_CAPABILITY } from '@wildo-ai/saas-models/public-runtime';
|
|
4
|
+
import { consentPurposesInUseByProviders } from '@wildo-ai/external-connectors-public/website';
|
|
4
5
|
import { WebsitePageContextProvider } from '../contexts/WebsitePageContext.js';
|
|
5
6
|
import { useWebsiteProviderScripts, useWebsiteProviderSdks, } from '../external-providers/useWebsiteProviderScripts.js';
|
|
6
7
|
import { useWebsiteRuntime } from '../contexts/useWebsiteRuntime.js';
|
|
7
8
|
import { useWebsiteLabelByKey } from '../hooks/useWebsiteLabel.js';
|
|
9
|
+
import { WebsiteConsentProvider, WebsiteConsentStatus } from '../consent/WebsiteConsentContext.js';
|
|
10
|
+
import { useWebsiteConsent } from '../consent/useWebsiteConsent.js';
|
|
11
|
+
import { WebsiteConsentBanner, WebsiteConsentSettingsButton } from '../consent/WebsiteConsentBanner.js';
|
|
8
12
|
import { WebsitePageRefSchema, } from '../../schemas/refs/page-ref.schemas.js';
|
|
9
13
|
/**
|
|
10
14
|
* Stable DOM id stamped on the `<main>` element so the framework's
|
|
@@ -94,8 +98,15 @@ const SKIP_LINK_FOCUS_CSS = `
|
|
|
94
98
|
text-decoration: none !important;
|
|
95
99
|
}
|
|
96
100
|
`.trim();
|
|
97
|
-
export function WebsitePageLayout(
|
|
101
|
+
export function WebsitePageLayout(props) {
|
|
98
102
|
const runtime = useWebsiteRuntime();
|
|
103
|
+
// Derived from the LIVE providers, so the panel asks about exactly what this site processes.
|
|
104
|
+
const purposesInUse = useMemo(() => consentPurposesInUseByProviders(runtime.frontendProviderRegistry?.modules.values()), [runtime.frontendProviderRegistry]);
|
|
105
|
+
return (_jsx(WebsiteConsentProvider, { purposesInUse: purposesInUse, recorder: props.consentRecorder, children: _jsx(WebsitePageLayoutContent, { ...props }) }));
|
|
106
|
+
}
|
|
107
|
+
function WebsitePageLayoutContent({ pageRef, header, footer, className, mainClassName, children, }) {
|
|
108
|
+
const runtime = useWebsiteRuntime();
|
|
109
|
+
const consent = useWebsiteConsent();
|
|
99
110
|
/**
|
|
100
111
|
* Defensive parse: callers should pass a `WebsitePageManifest.ref`
|
|
101
112
|
* (already branded), but accepting raw strings + parsing here means
|
|
@@ -135,8 +146,11 @@ export function WebsitePageLayout({ pageRef, header, footer, className, mainClas
|
|
|
135
146
|
// resolved provider refs and stamped them into the attributes below and
|
|
136
147
|
// stopped there — a provider could be declared, enabled and hydrated without
|
|
137
148
|
// anything of it ever running.
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
//
|
|
150
|
+
// Consent-based providers start only once their purpose is granted (#534). Before the stored decision is
|
|
151
|
+
// read, nothing is granted — the static HTML and the first render start nothing optional.
|
|
152
|
+
useWebsiteProviderScripts(runtime.frontendProviderRegistry, consent.grantedPurposes);
|
|
153
|
+
useWebsiteProviderSdks(runtime.frontendProviderRegistry, consent.grantedPurposes);
|
|
140
154
|
const providerRootAttributes = useMemo(() => {
|
|
141
155
|
const attrs = {};
|
|
142
156
|
const productAnalyticsProviderRef = runtime.frontendProviderRegistry
|
|
@@ -171,7 +185,21 @@ export function WebsitePageLayout({ pageRef, header, footer, className, mainClas
|
|
|
171
185
|
* ship an English skip link to a non-EN visitor.
|
|
172
186
|
*/
|
|
173
187
|
const skipLinkLabel = useWebsiteLabelByKey(runtime.skipToMainContentLabelKey);
|
|
174
|
-
return (_jsx(WebsitePageContextProvider, { value: pageContextValue, children: _jsxs("div", { "data-website-page-ref": safePageRef, className: className, ...providerRootAttributes, children: [_jsx("style", { children: SKIP_LINK_FOCUS_CSS }), _jsx("a", { href: `#${MAIN_CONTENT_DOM_ID}`, "data-website-skip-link": "", style: SKIP_LINK_HIDDEN_STYLE, children: skipLinkLabel }), resolvedHeader !== null ? _jsx("header", { children: resolvedHeader }) : null, _jsx("main", { id: MAIN_CONTENT_DOM_ID, className: mainClassName, tabIndex: -1, children: children }), resolvedFooter !== null ? _jsx("footer", { children: resolvedFooter }) : null] }) }));
|
|
188
|
+
return (_jsx(WebsitePageContextProvider, { value: pageContextValue, children: _jsxs("div", { "data-website-page-ref": safePageRef, className: className, ...providerRootAttributes, children: [_jsx("style", { children: SKIP_LINK_FOCUS_CSS }), _jsx("a", { href: `#${MAIN_CONTENT_DOM_ID}`, "data-website-skip-link": "", style: SKIP_LINK_HIDDEN_STYLE, children: skipLinkLabel }), resolvedHeader !== null ? _jsx("header", { children: resolvedHeader }) : null, _jsx("main", { id: MAIN_CONTENT_DOM_ID, className: mainClassName, tabIndex: -1, children: children }), resolvedFooter !== null ? _jsx("footer", { children: resolvedFooter }) : null, renderConsentSurface(consent, runtime.consentBannerComponent)] }) }));
|
|
175
189
|
}
|
|
176
190
|
WebsitePageLayout.displayName = 'WebsitePageLayout';
|
|
191
|
+
/**
|
|
192
|
+
* The panel while a decision is owed (or re-opened), the reopen button once one is made, and nothing at
|
|
193
|
+
* all on a site whose live providers rest on no consent. Nothing renders before the stored decision is
|
|
194
|
+
* read, so a visitor who already decided never sees the panel flash.
|
|
195
|
+
*/
|
|
196
|
+
function renderConsentSurface(consent, BannerOverride) {
|
|
197
|
+
if (!consent.isResolved || consent.purposesInUse.length === 0)
|
|
198
|
+
return null;
|
|
199
|
+
if (consent.status === WebsiteConsentStatus.UNDECIDED || consent.isSettingsOpen) {
|
|
200
|
+
const Banner = BannerOverride ?? WebsiteConsentBanner;
|
|
201
|
+
return _jsx(Banner, {});
|
|
202
|
+
}
|
|
203
|
+
return (_jsx("div", { "data-website-consent-settings": "", children: _jsx(WebsiteConsentSettingsButton, {}) }));
|
|
204
|
+
}
|
|
177
205
|
//# sourceMappingURL=WebsitePageLayout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsitePageLayout.js","sourceRoot":"","sources":["../../../../../src/core/layouts/WebsitePageLayout.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useMemo, type ReactNode } from 'react';\nimport { BUILTIN_PROVIDER_CAPABILITY } from '@wildo-ai/saas-models/public-runtime';\n\nimport { WebsitePageContextProvider } from '../contexts/WebsitePageContext';\nimport {\n useWebsiteProviderScripts,\n useWebsiteProviderSdks,\n} from '../external-providers/useWebsiteProviderScripts';\nimport type { WebsitePageContextValue } from '../contexts/WebsitePageContext';\nimport { useWebsiteRuntime } from '../contexts/useWebsiteRuntime';\nimport { useWebsiteLabelByKey } from '../hooks/useWebsiteLabel';\nimport {\n WebsitePageRefSchema,\n type WebsitePageRef,\n} from '../../schemas/refs/page-ref.schemas';\n\n/**\n * Stable DOM id stamped on the `<main>` element so the framework's\n * skip-link `<a href=\"#main-content\">` can move keyboard focus past\n * the chrome on every page (Phase 6 audit fix M-α). Co-located here\n * (rather than exposed as a prop) because:\n * - The skip-link target is a framework concern — exposing it as a\n * consumer-tunable id would invite drift between the link's href\n * and the main element's id.\n * - Screen readers announce the heading inside `<main id=\"...\">` on\n * focus; a stable, framework-owned id keeps that affordance\n * deterministic across every page on every site.\n */\nconst MAIN_CONTENT_DOM_ID = 'main-content';\n\n/**\n * Inline style for the skip link's \"visually hidden by default,\n * visible on focus\" pattern. Inlined (NOT shipped as a CSS class)\n * because:\n * - The framework intentionally avoids shipping any CSS file the\n * consumer must opt in to — the only stylesheet emitted is the\n * `:root { --website-*: … }` design-token block, and the rest of\n * the styling surface lives in consumer code (see Phase 5\n * `renderWebsiteDesignTokensCss`). Inlining keeps the skip link\n * self-sufficient and side-effect-free.\n * - The \"skip-link\" pattern is a small, well-known a11y idiom; the\n * handful of style declarations here do not benefit from being\n * reorganized into a class with a custom-property hook.\n *\n * The \"focused\" variant uses BOTH `:focus` AND `:focus-visible`\n * pseudo-classes via a scoped `<style>` tag inside the layout output.\n * We cannot apply pseudo-class styles via a `style` attribute, but\n * emitting a tiny scoped block once at the layout root is safe (one\n * block per page render, ~120 bytes) and avoids importing a CSS-in-JS\n * runtime.\n *\n * **Why both selectors and not just `:focus-visible`** (Phase 6 fourth\n * audit fix M-Η): `:focus-visible` is the modern UA-heuristic-driven\n * selector that hides focus rings on mouse clicks, and it is the\n * RIGHT default for buttons / links. For a skip link though we WANT\n * the affordance to appear on EVERY focus event — the link is\n * invisible by default and a keyboard-only user (the only intended\n * audience) is the only one who can reach it. Older browsers that\n * predate `:focus-visible` (and Safari < 15.4 with the feature\n * disabled) would skip the rule entirely with a `:focus-visible`-only\n * declaration, leaving the keyboard user looking at an apparently\n * empty page when they pressed Tab. Pairing `:focus, :focus-visible`\n * is the documented WCAG 2.1 fallback pattern.\n */\nconst SKIP_LINK_HIDDEN_STYLE: React.CSSProperties = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '1px',\n height: '1px',\n padding: 0,\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0 0 0 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * Single scoped style block for the skip link's `:focus-visible`\n * affordance. Targets the `data-website-skip-link` attribute (NOT a\n * class) so consumer Tailwind / utility classes cannot accidentally\n * shadow it. Stays trivially small to keep payload under the\n * \"framework rendering\" implicit budget.\n */\nconst SKIP_LINK_FOCUS_CSS = `\n[data-website-skip-link]:focus,\n[data-website-skip-link]:focus-visible {\n position: fixed !important;\n top: 0.5rem !important;\n left: 0.5rem !important;\n width: auto !important;\n height: auto !important;\n padding: 0.75rem 1rem !important;\n margin: 0 !important;\n overflow: visible !important;\n clip: auto !important;\n white-space: normal !important;\n background: var(--website-colors-background, #fff) !important;\n color: var(--website-colors-foreground, #000) !important;\n border: 2px solid var(--website-colors-primary, #000) !important;\n border-radius: var(--website-radii-md, 0.375rem) !important;\n z-index: 9999 !important;\n text-decoration: none !important;\n}\n`.trim();\n\n/**\n * @wildo_source:part:start saas.website.page-layout.component facet:layer:core facet:family:website\n *\n * `WebsitePageLayout` — the per-page structural primitive.\n *\n * **Three responsibilities** (each carrying its own semantic weight):\n *\n * 1. **Provides `WebsitePageContext`** — registers the active page\n * ref so child sections (`<WebsiteSection>`) and label hooks\n * (`useWebsiteLabel`) can derive label keys without re-typing\n * the page ref at every leaf.\n * 2. **Renders the site-wide chrome** — pulls\n * `headerComponent` and `footerComponent` from the runtime\n * navigation config and wraps the page's main content in the\n * site's standard header/main/footer scaffold. Per-page\n * overrides (`header={…}` / `footer={…}` props) take precedence\n * over the runtime defaults so individual pages can opt into a\n * custom chrome (eg. a campaign landing page running with\n * header-only minimal chrome).\n * 3. **Stamps DOM landmarks** — emits `data-website-page-ref` on\n * the layout root for analytics / SEO crawlers / overlay\n * tooling that needs to identify the active page from the DOM\n * without booting React. When the website facet hydrates\n * frontend-provider bootstraps, the same root also carries the\n * active public provider refs for analytics / browser-monitoring /\n * captcha so non-React tooling can detect the configured vendor\n * without reverse-engineering app code.\n *\n * **Why semantic `<header>` / `<main>` / `<footer>` elements**:\n * marketing-site SEO scoring penalizes pages that ship a single\n * unstructured `<div>` tree. Using semantic landmarks costs nothing\n * and improves crawler accessibility AND screen-reader\n * navigability — both are first-class concerns for a public\n * marketing surface.\n *\n * **Why this lives in `core/`** (not `components/`): the layout is\n * structural framework machinery (it owns the page-context\n * provisioning + the navigation slot resolution), not a styled\n * primitive. Designers override `header` / `footer` via runtime\n * config or per-page props, NOT by replacing this component.\n */\nexport interface WebsitePageLayoutProps {\n /**\n * The active page's ref. Accepts either an already-branded\n * `WebsitePageRef` or a raw string — the layout parses through\n * `WebsitePageRefSchema` so a malformed ref fails loud at render.\n *\n * Typically sourced from the page's `WebsitePageManifest.ref`\n * (eg. `<WebsitePageLayout pageRef={LANDING_PAGE_MANIFEST.ref}>`).\n */\n pageRef: WebsitePageRef | string;\n /**\n * Per-page header override. When supplied, takes precedence over\n * `WebsiteRuntimeContext.designTokens` navigation config's\n * `headerComponent`. Pass `null` explicitly to suppress the header\n * entirely (campaign landing pages without site-wide nav).\n */\n header?: ReactNode | null;\n /**\n * Per-page footer override. Same semantics as `header`.\n */\n footer?: ReactNode | null;\n /**\n * Optional className applied to the root `<div data-website-page-ref=\"…\">`\n * wrapper. Use for page-level layout tweaks (eg. `min-h-screen` on\n * landing pages).\n */\n className?: string;\n /**\n * Optional className applied to the inner `<main>` element. Use for\n * vertical-rhythm overrides on long-scroll landing pages.\n */\n mainClassName?: string;\n /**\n * The page's section composition. Typically a sequence of\n * `<WebsiteSection>` wrappers around concrete section components.\n */\n children: ReactNode;\n}\n\nexport function WebsitePageLayout({\n pageRef,\n header,\n footer,\n className,\n mainClassName,\n children,\n}: WebsitePageLayoutProps): ReactNode {\n const runtime = useWebsiteRuntime();\n\n /**\n * Defensive parse: callers should pass a `WebsitePageManifest.ref`\n * (already branded), but accepting raw strings + parsing here means\n * a hand-typed call site can't smuggle a malformed ref into the\n * label-key derivation chain.\n */\n const safePageRef = useMemo<WebsitePageRef>(\n () => WebsitePageRefSchema.parse(pageRef),\n [pageRef],\n );\n\n const pageContextValue = useMemo<WebsitePageContextValue>(\n () => ({ pageRef: safePageRef }),\n [safePageRef],\n );\n\n /**\n * Header resolution rules:\n * - `header === null` => explicit suppression\n * - `header !== undefined` => per-page override (any ReactNode)\n * - `header === undefined` => fall back to the runtime navigation\n * config's `headerComponent`\n *\n * The fallback is the common path: every page renders the same\n * site-wide chrome. The override path is the escape hatch (campaign\n * landing pages with custom header layouts).\n */\n const resolvedHeader = useMemo<ReactNode>(() => {\n if (header === null) return null;\n if (header !== undefined) return header;\n const HeaderComponent = runtime.navigation.headerComponent;\n return <HeaderComponent />;\n }, [header, runtime.navigation.headerComponent]);\n\n const resolvedFooter = useMemo<ReactNode>(() => {\n if (footer === null) return null;\n if (footer !== undefined) return footer;\n const FooterComponent = runtime.navigation.footerComponent;\n return <FooterComponent />;\n }, [footer, runtime.navigation.footerComponent]);\n\n // Load whatever the live providers declared. Until this landed, the surface\n // resolved provider refs and stamped them into the attributes below and\n // stopped there — a provider could be declared, enabled and hydrated without\n // anything of it ever running.\n useWebsiteProviderScripts(runtime.frontendProviderRegistry);\n useWebsiteProviderSdks(runtime.frontendProviderRegistry);\n\n const providerRootAttributes = useMemo<Record<string, string>>(() => {\n const attrs: Record<string, string> = {};\n const productAnalyticsProviderRef = runtime.frontendProviderRegistry\n ?.findProviderByProviderCapability(\n BUILTIN_PROVIDER_CAPABILITY.FRONTEND_PRODUCT_ANALYTICS,\n )\n ?.metadata.ref;\n const errorMonitoringProviderRef = runtime.frontendProviderRegistry\n ?.findProviderByProviderCapability(\n BUILTIN_PROVIDER_CAPABILITY.FRONTEND_ERROR_MONITORING,\n )\n ?.metadata.ref;\n const captchaProviderRef = runtime.frontendProviderRegistry\n ?.findProviderByProviderCapability(\n BUILTIN_PROVIDER_CAPABILITY.FRONTEND_CAPTCHA,\n )\n ?.metadata.ref;\n\n if (productAnalyticsProviderRef) {\n attrs['data-website-product-analytics-provider-ref'] = productAnalyticsProviderRef;\n }\n if (errorMonitoringProviderRef) {\n attrs['data-website-error-monitoring-provider-ref'] = errorMonitoringProviderRef;\n }\n if (captchaProviderRef) {\n attrs['data-website-captcha-provider-ref'] = captchaProviderRef;\n }\n\n return attrs;\n }, [runtime.frontendProviderRegistry]);\n\n /**\n * Resolved skip-link label. Reads through the runtime label pack\n * via the standard `useWebsiteLabelByKey` hook so a missing\n * translation surfaces the same diagnostic shape as any other\n * missing chrome label. The framework guarantees the key reaches\n * the bridge (see `pickLabelsForPage`) and the consumer's\n * label-pack validator (see `composeEffectiveChromeLabelKeys` in\n * `label-pack-loader.ts`) ensures it is present in every locale\n * pack — there is therefore no silent-fallback path that would\n * ship an English skip link to a non-EN visitor.\n */\n const skipLinkLabel = useWebsiteLabelByKey(runtime.skipToMainContentLabelKey);\n\n return (\n <WebsitePageContextProvider value={pageContextValue}>\n <div\n data-website-page-ref={safePageRef as unknown as string}\n className={className}\n {...providerRootAttributes}\n >\n {/*\n * Skip link rendered as the FIRST focusable element on the\n * page so a keyboard / AT user can bypass the chrome on every\n * navigation (WCAG 2.1 §2.4.1 \"Bypass Blocks\", Level A).\n * Visually hidden by default; revealed on focus via the\n * scoped `<style>` block below.\n */}\n <style>{SKIP_LINK_FOCUS_CSS}</style>\n <a\n href={`#${MAIN_CONTENT_DOM_ID}`}\n data-website-skip-link=\"\"\n style={SKIP_LINK_HIDDEN_STYLE}\n >\n {skipLinkLabel}\n </a>\n {resolvedHeader !== null ? <header>{resolvedHeader}</header> : null}\n <main id={MAIN_CONTENT_DOM_ID} className={mainClassName} tabIndex={-1}>\n {children}\n </main>\n {resolvedFooter !== null ? <footer>{resolvedFooter}</footer> : null}\n </div>\n </WebsitePageContextProvider>\n );\n}\nWebsitePageLayout.displayName = 'WebsitePageLayout';\n/** @wildo_source:part:end saas.website.page-layout.component */\n"]}
|
|
1
|
+
{"version":3,"file":"WebsitePageLayout.js","sourceRoot":"","sources":["../../../../../src/core/layouts/WebsitePageLayout.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useMemo, type ReactNode } from 'react';\nimport { BUILTIN_PROVIDER_CAPABILITY } from '@wildo-ai/saas-models/public-runtime';\nimport { consentPurposesInUseByProviders } from '@wildo-ai/external-connectors-public/website';\n\nimport { WebsitePageContextProvider } from '../contexts/WebsitePageContext';\nimport {\n useWebsiteProviderScripts,\n useWebsiteProviderSdks,\n} from '../external-providers/useWebsiteProviderScripts';\nimport type { WebsitePageContextValue } from '../contexts/WebsitePageContext';\nimport { useWebsiteRuntime } from '../contexts/useWebsiteRuntime';\nimport { useWebsiteLabelByKey } from '../hooks/useWebsiteLabel';\nimport { WebsiteConsentProvider, WebsiteConsentStatus, type WebsiteConsentRecorder } from '../consent/WebsiteConsentContext';\nimport { useWebsiteConsent } from '../consent/useWebsiteConsent';\nimport { WebsiteConsentBanner, WebsiteConsentSettingsButton } from '../consent/WebsiteConsentBanner';\nimport {\n WebsitePageRefSchema,\n type WebsitePageRef,\n} from '../../schemas/refs/page-ref.schemas';\n\n/**\n * Stable DOM id stamped on the `<main>` element so the framework's\n * skip-link `<a href=\"#main-content\">` can move keyboard focus past\n * the chrome on every page (Phase 6 audit fix M-α). Co-located here\n * (rather than exposed as a prop) because:\n * - The skip-link target is a framework concern — exposing it as a\n * consumer-tunable id would invite drift between the link's href\n * and the main element's id.\n * - Screen readers announce the heading inside `<main id=\"...\">` on\n * focus; a stable, framework-owned id keeps that affordance\n * deterministic across every page on every site.\n */\nconst MAIN_CONTENT_DOM_ID = 'main-content';\n\n/**\n * Inline style for the skip link's \"visually hidden by default,\n * visible on focus\" pattern. Inlined (NOT shipped as a CSS class)\n * because:\n * - The framework intentionally avoids shipping any CSS file the\n * consumer must opt in to — the only stylesheet emitted is the\n * `:root { --website-*: … }` design-token block, and the rest of\n * the styling surface lives in consumer code (see Phase 5\n * `renderWebsiteDesignTokensCss`). Inlining keeps the skip link\n * self-sufficient and side-effect-free.\n * - The \"skip-link\" pattern is a small, well-known a11y idiom; the\n * handful of style declarations here do not benefit from being\n * reorganized into a class with a custom-property hook.\n *\n * The \"focused\" variant uses BOTH `:focus` AND `:focus-visible`\n * pseudo-classes via a scoped `<style>` tag inside the layout output.\n * We cannot apply pseudo-class styles via a `style` attribute, but\n * emitting a tiny scoped block once at the layout root is safe (one\n * block per page render, ~120 bytes) and avoids importing a CSS-in-JS\n * runtime.\n *\n * **Why both selectors and not just `:focus-visible`** (Phase 6 fourth\n * audit fix M-Η): `:focus-visible` is the modern UA-heuristic-driven\n * selector that hides focus rings on mouse clicks, and it is the\n * RIGHT default for buttons / links. For a skip link though we WANT\n * the affordance to appear on EVERY focus event — the link is\n * invisible by default and a keyboard-only user (the only intended\n * audience) is the only one who can reach it. Older browsers that\n * predate `:focus-visible` (and Safari < 15.4 with the feature\n * disabled) would skip the rule entirely with a `:focus-visible`-only\n * declaration, leaving the keyboard user looking at an apparently\n * empty page when they pressed Tab. Pairing `:focus, :focus-visible`\n * is the documented WCAG 2.1 fallback pattern.\n */\nconst SKIP_LINK_HIDDEN_STYLE: React.CSSProperties = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '1px',\n height: '1px',\n padding: 0,\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0 0 0 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * Single scoped style block for the skip link's `:focus-visible`\n * affordance. Targets the `data-website-skip-link` attribute (NOT a\n * class) so consumer Tailwind / utility classes cannot accidentally\n * shadow it. Stays trivially small to keep payload under the\n * \"framework rendering\" implicit budget.\n */\nconst SKIP_LINK_FOCUS_CSS = `\n[data-website-skip-link]:focus,\n[data-website-skip-link]:focus-visible {\n position: fixed !important;\n top: 0.5rem !important;\n left: 0.5rem !important;\n width: auto !important;\n height: auto !important;\n padding: 0.75rem 1rem !important;\n margin: 0 !important;\n overflow: visible !important;\n clip: auto !important;\n white-space: normal !important;\n background: var(--website-colors-background, #fff) !important;\n color: var(--website-colors-foreground, #000) !important;\n border: 2px solid var(--website-colors-primary, #000) !important;\n border-radius: var(--website-radii-md, 0.375rem) !important;\n z-index: 9999 !important;\n text-decoration: none !important;\n}\n`.trim();\n\n/**\n * @wildo_source:part:start saas.website.page-layout.component facet:layer:core facet:family:website\n *\n * `WebsitePageLayout` — the per-page structural primitive.\n *\n * **Three responsibilities** (each carrying its own semantic weight):\n *\n * 1. **Provides `WebsitePageContext`** — registers the active page\n * ref so child sections (`<WebsiteSection>`) and label hooks\n * (`useWebsiteLabel`) can derive label keys without re-typing\n * the page ref at every leaf.\n * 2. **Renders the site-wide chrome** — pulls\n * `headerComponent` and `footerComponent` from the runtime\n * navigation config and wraps the page's main content in the\n * site's standard header/main/footer scaffold. Per-page\n * overrides (`header={…}` / `footer={…}` props) take precedence\n * over the runtime defaults so individual pages can opt into a\n * custom chrome (eg. a campaign landing page running with\n * header-only minimal chrome).\n * 3. **Stamps DOM landmarks** — emits `data-website-page-ref` on\n * the layout root for analytics / SEO crawlers / overlay\n * tooling that needs to identify the active page from the DOM\n * without booting React. When the website facet hydrates\n * frontend-provider bootstraps, the same root also carries the\n * active public provider refs for analytics / browser-monitoring /\n * captcha so non-React tooling can detect the configured vendor\n * without reverse-engineering app code.\n *\n * 4. **Provides the visitor's consent state and enforces it** (#534) —\n * mounts `WebsiteConsentProvider`, starts a provider SDK or script only\n * once the purpose it rests on is granted (and stops the SDK when it is\n * withdrawn), and renders the consent panel when the site runs a\n * consent-based provider and the visitor has not decided. Here rather\n * than in each bridge because every page — including every blog post —\n * renders through this layout, so no page can be mounted without it.\n *\n * **Why semantic `<header>` / `<main>` / `<footer>` elements**:\n * marketing-site SEO scoring penalizes pages that ship a single\n * unstructured `<div>` tree. Using semantic landmarks costs nothing\n * and improves crawler accessibility AND screen-reader\n * navigability — both are first-class concerns for a public\n * marketing surface.\n *\n * **Why this lives in `core/`** (not `components/`): the layout is\n * structural framework machinery (it owns the page-context\n * provisioning + the navigation slot resolution), not a styled\n * primitive. Designers override `header` / `footer` via runtime\n * config or per-page props, NOT by replacing this component.\n */\nexport interface WebsitePageLayoutProps {\n /**\n * The active page's ref. Accepts either an already-branded\n * `WebsitePageRef` or a raw string — the layout parses through\n * `WebsitePageRefSchema` so a malformed ref fails loud at render.\n *\n * Typically sourced from the page's `WebsitePageManifest.ref`\n * (eg. `<WebsitePageLayout pageRef={LANDING_PAGE_MANIFEST.ref}>`).\n */\n pageRef: WebsitePageRef | string;\n /**\n * Per-page header override. When supplied, takes precedence over\n * `WebsiteRuntimeContext.designTokens` navigation config's\n * `headerComponent`. Pass `null` explicitly to suppress the header\n * entirely (campaign landing pages without site-wide nav).\n */\n header?: ReactNode | null;\n /**\n * Per-page footer override. Same semantics as `header`.\n */\n footer?: ReactNode | null;\n /**\n * Optional className applied to the root `<div data-website-page-ref=\"…\">`\n * wrapper. Use for page-level layout tweaks (eg. `min-h-screen` on\n * landing pages).\n */\n className?: string;\n /**\n * Optional className applied to the inner `<main>` element. Use for\n * vertical-rhythm overrides on long-scroll landing pages.\n */\n mainClassName?: string;\n /**\n * OPTIONAL anonymous-session client. When supplied, a visitor's consent decision is ALSO recorded\n * server-side against their session, which is what lets it follow them into the account they go on\n * to create (#1258). Pass the SAME instance the page's capture sections use, so one `uuidRef`\n * carries the decision, the lead and the draft note together.\n *\n * Absent keeps today's behaviour exactly: the decision lives in this browser and nowhere else.\n * The engine cannot construct the client here — it needs the application's own\n * `frontendServiceName` — which is why it arrives as a prop rather than off the runtime context.\n */\n consentRecorder?: WebsiteConsentRecorder;\n /**\n * The page's section composition. Typically a sequence of\n * `<WebsiteSection>` wrappers around concrete section components.\n */\n children: ReactNode;\n}\n\nexport function WebsitePageLayout(props: WebsitePageLayoutProps): ReactNode {\n const runtime = useWebsiteRuntime();\n // Derived from the LIVE providers, so the panel asks about exactly what this site processes.\n const purposesInUse = useMemo(\n () => consentPurposesInUseByProviders(runtime.frontendProviderRegistry?.modules.values()),\n [runtime.frontendProviderRegistry],\n );\n return (\n <WebsiteConsentProvider purposesInUse={purposesInUse} recorder={props.consentRecorder}>\n <WebsitePageLayoutContent {...props} />\n </WebsiteConsentProvider>\n );\n}\n\nfunction WebsitePageLayoutContent({\n pageRef,\n header,\n footer,\n className,\n mainClassName,\n children,\n}: WebsitePageLayoutProps): ReactNode {\n const runtime = useWebsiteRuntime();\n const consent = useWebsiteConsent();\n\n /**\n * Defensive parse: callers should pass a `WebsitePageManifest.ref`\n * (already branded), but accepting raw strings + parsing here means\n * a hand-typed call site can't smuggle a malformed ref into the\n * label-key derivation chain.\n */\n const safePageRef = useMemo<WebsitePageRef>(\n () => WebsitePageRefSchema.parse(pageRef),\n [pageRef],\n );\n\n const pageContextValue = useMemo<WebsitePageContextValue>(\n () => ({ pageRef: safePageRef }),\n [safePageRef],\n );\n\n /**\n * Header resolution rules:\n * - `header === null` => explicit suppression\n * - `header !== undefined` => per-page override (any ReactNode)\n * - `header === undefined` => fall back to the runtime navigation\n * config's `headerComponent`\n *\n * The fallback is the common path: every page renders the same\n * site-wide chrome. The override path is the escape hatch (campaign\n * landing pages with custom header layouts).\n */\n const resolvedHeader = useMemo<ReactNode>(() => {\n if (header === null) return null;\n if (header !== undefined) return header;\n const HeaderComponent = runtime.navigation.headerComponent;\n return <HeaderComponent />;\n }, [header, runtime.navigation.headerComponent]);\n\n const resolvedFooter = useMemo<ReactNode>(() => {\n if (footer === null) return null;\n if (footer !== undefined) return footer;\n const FooterComponent = runtime.navigation.footerComponent;\n return <FooterComponent />;\n }, [footer, runtime.navigation.footerComponent]);\n\n // Load whatever the live providers declared. Until this landed, the surface\n // resolved provider refs and stamped them into the attributes below and\n // stopped there — a provider could be declared, enabled and hydrated without\n // anything of it ever running.\n //\n // Consent-based providers start only once their purpose is granted (#534). Before the stored decision is\n // read, nothing is granted — the static HTML and the first render start nothing optional.\n useWebsiteProviderScripts(runtime.frontendProviderRegistry, consent.grantedPurposes);\n useWebsiteProviderSdks(runtime.frontendProviderRegistry, consent.grantedPurposes);\n\n const providerRootAttributes = useMemo<Record<string, string>>(() => {\n const attrs: Record<string, string> = {};\n const productAnalyticsProviderRef = runtime.frontendProviderRegistry\n ?.findProviderByProviderCapability(\n BUILTIN_PROVIDER_CAPABILITY.FRONTEND_PRODUCT_ANALYTICS,\n )\n ?.metadata.ref;\n const errorMonitoringProviderRef = runtime.frontendProviderRegistry\n ?.findProviderByProviderCapability(\n BUILTIN_PROVIDER_CAPABILITY.FRONTEND_ERROR_MONITORING,\n )\n ?.metadata.ref;\n const captchaProviderRef = runtime.frontendProviderRegistry\n ?.findProviderByProviderCapability(\n BUILTIN_PROVIDER_CAPABILITY.FRONTEND_CAPTCHA,\n )\n ?.metadata.ref;\n\n if (productAnalyticsProviderRef) {\n attrs['data-website-product-analytics-provider-ref'] = productAnalyticsProviderRef;\n }\n if (errorMonitoringProviderRef) {\n attrs['data-website-error-monitoring-provider-ref'] = errorMonitoringProviderRef;\n }\n if (captchaProviderRef) {\n attrs['data-website-captcha-provider-ref'] = captchaProviderRef;\n }\n\n return attrs;\n }, [runtime.frontendProviderRegistry]);\n\n /**\n * Resolved skip-link label. Reads through the runtime label pack\n * via the standard `useWebsiteLabelByKey` hook so a missing\n * translation surfaces the same diagnostic shape as any other\n * missing chrome label. The framework guarantees the key reaches\n * the bridge (see `pickLabelsForPage`) and the consumer's\n * label-pack validator (see `composeEffectiveChromeLabelKeys` in\n * `label-pack-loader.ts`) ensures it is present in every locale\n * pack — there is therefore no silent-fallback path that would\n * ship an English skip link to a non-EN visitor.\n */\n const skipLinkLabel = useWebsiteLabelByKey(runtime.skipToMainContentLabelKey);\n\n return (\n <WebsitePageContextProvider value={pageContextValue}>\n <div\n data-website-page-ref={safePageRef as unknown as string}\n className={className}\n {...providerRootAttributes}\n >\n {/*\n * Skip link rendered as the FIRST focusable element on the\n * page so a keyboard / AT user can bypass the chrome on every\n * navigation (WCAG 2.1 §2.4.1 \"Bypass Blocks\", Level A).\n * Visually hidden by default; revealed on focus via the\n * scoped `<style>` block below.\n */}\n <style>{SKIP_LINK_FOCUS_CSS}</style>\n <a\n href={`#${MAIN_CONTENT_DOM_ID}`}\n data-website-skip-link=\"\"\n style={SKIP_LINK_HIDDEN_STYLE}\n >\n {skipLinkLabel}\n </a>\n {resolvedHeader !== null ? <header>{resolvedHeader}</header> : null}\n <main id={MAIN_CONTENT_DOM_ID} className={mainClassName} tabIndex={-1}>\n {children}\n </main>\n {resolvedFooter !== null ? <footer>{resolvedFooter}</footer> : null}\n {renderConsentSurface(consent, runtime.consentBannerComponent)}\n </div>\n </WebsitePageContextProvider>\n );\n}\nWebsitePageLayout.displayName = 'WebsitePageLayout';\n\n/**\n * The panel while a decision is owed (or re-opened), the reopen button once one is made, and nothing at\n * all on a site whose live providers rest on no consent. Nothing renders before the stored decision is\n * read, so a visitor who already decided never sees the panel flash.\n */\nfunction renderConsentSurface(\n consent: ReturnType<typeof useWebsiteConsent>,\n BannerOverride: React.ComponentType | undefined,\n): ReactNode {\n if (!consent.isResolved || consent.purposesInUse.length === 0) return null;\n if (consent.status === WebsiteConsentStatus.UNDECIDED || consent.isSettingsOpen) {\n const Banner = BannerOverride ?? WebsiteConsentBanner;\n return <Banner />;\n }\n return (\n <div data-website-consent-settings=\"\">\n <WebsiteConsentSettingsButton />\n </div>\n );\n}\n/** @wildo_source:part:end saas.website.page-layout.component */\n"]}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
export * from './schemas/refs/page-ref.schemas';
|
|
95
95
|
export * from './schemas/refs/section-ref.schemas';
|
|
96
96
|
export * from './schemas/label-keys/website-label-key.schemas';
|
|
97
|
+
export * from './schemas/label-keys/website-consent-label-keys.schemas';
|
|
97
98
|
export * from './schemas/sections/website-section-category.shared';
|
|
98
99
|
export * from './schemas/sections/website-section-definition.shared.schemas';
|
|
99
100
|
export * from './schemas/structured-data/website-structured-data.shared.schemas';
|
|
@@ -115,6 +116,11 @@ export * from './core/factories/define-website-section';
|
|
|
115
116
|
export * from './core/factories/define-website-page-manifest';
|
|
116
117
|
export * from './core/layouts/WebsitePageLayout';
|
|
117
118
|
export * from './core/layouts/WebsiteSection';
|
|
119
|
+
export * from './core/consent/website-consent-storage';
|
|
120
|
+
export * from './core/consent/WebsiteConsentContext';
|
|
121
|
+
export * from './core/consent/useWebsiteConsent';
|
|
122
|
+
export * from './core/consent/WebsiteConsentGate';
|
|
123
|
+
export * from './core/consent/WebsiteConsentBanner';
|
|
118
124
|
export * from './core/anonymous-session/website-anonymous-session-client';
|
|
119
125
|
export * from './core/anonymous-session/inbound-contact-form.schema';
|
|
120
126
|
export * from './core/anonymous-session/InboundContactForm';
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4FG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,kEAAkE,CAAC;AACjF,cAAc,iEAAiE,CAAC;AAChF,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC;AAE1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAE9C,cAAc,2DAA2D,CAAC;AAC1E,cAAc,sDAAsD,CAAC;AACrE,cAAc,6CAA6C,CAAC;AAE5D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,qCAAqC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4FG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,yDAAyD,CAAC;AACxE,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,kEAAkE,CAAC;AACjF,cAAc,iEAAiE,CAAC;AAChF,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC;AAE1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAE9C,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AAEpD,cAAc,2DAA2D,CAAC;AAC1E,cAAc,sDAAsD,CAAC;AACrE,cAAc,6CAA6C,CAAC;AAE5D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,qCAAqC,CAAC"}
|
package/dist/esm/index.js
CHANGED
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
export * from './schemas/refs/page-ref.schemas.js';
|
|
95
95
|
export * from './schemas/refs/section-ref.schemas.js';
|
|
96
96
|
export * from './schemas/label-keys/website-label-key.schemas.js';
|
|
97
|
+
export * from './schemas/label-keys/website-consent-label-keys.schemas.js';
|
|
97
98
|
export * from './schemas/sections/website-section-category.shared.js';
|
|
98
99
|
export * from './schemas/sections/website-section-definition.shared.schemas.js';
|
|
99
100
|
export * from './schemas/structured-data/website-structured-data.shared.schemas.js';
|
|
@@ -115,6 +116,11 @@ export * from './core/factories/define-website-section.js';
|
|
|
115
116
|
export * from './core/factories/define-website-page-manifest.js';
|
|
116
117
|
export * from './core/layouts/WebsitePageLayout.js';
|
|
117
118
|
export * from './core/layouts/WebsiteSection.js';
|
|
119
|
+
export * from './core/consent/website-consent-storage.js';
|
|
120
|
+
export * from './core/consent/WebsiteConsentContext.js';
|
|
121
|
+
export * from './core/consent/useWebsiteConsent.js';
|
|
122
|
+
export * from './core/consent/WebsiteConsentGate.js';
|
|
123
|
+
export * from './core/consent/WebsiteConsentBanner.js';
|
|
118
124
|
export * from './core/anonymous-session/website-anonymous-session-client.js';
|
|
119
125
|
export * from './core/anonymous-session/inbound-contact-form.schema.js';
|
|
120
126
|
export * from './core/anonymous-session/InboundContactForm.js';
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4FG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,kEAAkE,CAAC;AACjF,cAAc,iEAAiE,CAAC;AAChF,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC;AAE1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAE9C,cAAc,2DAA2D,CAAC;AAC1E,cAAc,sDAAsD,CAAC;AACrE,cAAc,6CAA6C,CAAC;AAE5D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,qCAAqC,CAAC","sourcesContent":["/**\n * @wildo-package @wildo-ai/saas-website\n *\n * SaaS marketing/landing website engine — schemas + helpers + layout\n * placeholders + Astro/MDX adapters + SEO/AEO/GEO builder library.\n *\n * Per `saas-website-slice.md` D6/D15: this engine ships **NO concrete\n * business sections** (no Hero, no CTA, no Pricing). It ships the\n * structural and helper machinery; consumers author every business\n * section themselves via the `defineWebsiteSection` factory and the\n * accompanying SKILLs in `framework-knowledge`.\n *\n * @wildo-boundary\n * - This barrel MAY re-export from any subdirectory of `src/`.\n * - Subdirectories MUST observe their own boundary contracts (see\n * each subdirectory's own `index.ts` `@wildo-boundary` block):\n * - `schemas/` : NO React, NO Astro, NO `~wildo-frontend-lib/**`\n * - `seo/` : NO React, NO Astro\n * - `core/` : MAY use React; NO `~wildo-frontend-lib/**` (Q17=B)\n * - `astro/` : MAY use Astro; NO React component runtime imports\n * - `mdx/` : MAY use `@mdx-js/*`\n * - `integration/` : MAY use React; MAY import the\n * `@wildo-ai/saas-models/public-runtime` SUBPATH,\n * the `@wildo-ai/presets-components-models/public-runtime`\n * SUBPATH, `@wildo-ai/zod-decorators`,\n * plus the SSR / Astro-middleware tier\n * (`axios`, `jsonwebtoken`, `socket.io-client`,\n * `reflect-metadata`) — D12 revised\n * 2026-04-19 (second same-day loosening);\n * the ROOT barrels of `@wildo-ai/saas-models`\n * and `@wildo-ai/presets-components-models`\n * are FORBIDDEN (they boot the decorator\n * framework / pull the heavy registry);\n * the saas-app DI / backend stack\n * (`inversify`, `mongoose`, `express`,\n * `pino`, `helmet`, `argon2`, `bcrypt`,\n * the `@wildo-ai/saas-frontend-lib` /\n * `@wildo-ai/saas-backend-lib` /\n * `@wildo-ai/external-connectors-models`\n * root + subpath surfaces) is FORBIDDEN;\n * MUST NOT import `~wildo-frontend-lib/**`\n * (Q17=B / D16)\n *\n * D12 (client-bundle import discipline, all `core/`/`schemas/`/`integration/`\n * files): the entire saas-website source tree ships into a browser bundle\n * (Astro SSG + React islands). Every shared model atom MUST come from a\n * side-effect-free `/public-runtime` subpath:\n * - `@wildo-ai/saas-models/public-runtime` is the primary door\n * (saas-models-public-runtime.md K-6 — consumers see one path).\n * - `@wildo-ai/presets-components-models/public-runtime` is the\n * internal seam for symbols not (yet) re-exported by saas-models.\n * - `@wildo-ai/zod-decorators` is allowed to import (first revision\n * 2026-04-19). The package itself is side-effect-free; only an\n * explicit `initZodDecorators()` call mutates `z.ZodType.prototype`,\n * which neither saas-website nor the saas-models /public-runtime\n * barrel does.\n * - `axios`, `jsonwebtoken`, `socket.io-client`, `reflect-metadata` are\n * allowed for SSR / Astro-middleware code paths (second same-day\n * revision 2026-04-19). The website engine needs them to provide\n * middleware utilities (HTTP proxying to the SaaS backend, JWT\n * verification of cross-frontend handoff codes, real-time push,\n * decorator-metadata reads). They are SSR-tier — discipline\n * (and a future strict/loose entry split for React islands vs\n * middleware) keeps them out of the React-island bundle.\n *\n * The ROOT barrel of `@wildo-ai/saas-models` is forbidden everywhere in\n * this package — it calls `initZodDecorators()` at module load and pulls\n * `@wildo-ai/external-connectors-models` + the full schema graph into\n * the bundle. Same forbid applies to the root barrel of\n * `@wildo-ai/presets-components-models`, the saas-app DI runtime\n * (`inversify`, `@wildo-ai/saas-frontend-lib`, `@wildo-ai/saas-backend-lib`),\n * and pure-backend libs (`mongoose`, `express`, `pino`, `helmet`,\n * `argon2`, `bcrypt`). Enforced by `src/__tests__/bundle-isolation.test.ts`.\n *\n * These boundaries are enforced by review + the bundle-isolation test\n * above until Phase 8 promotes them to a programmatic ESLint check\n * (cf. `saas-website-slice.md`\n * §\"DEFERRED to Phase 8 — boundary lint mechanism design\").\n *\n * Phase 1+2 status: schemas + runtime contexts/hooks + layout\n * primitives + L1 placeholders + factories are all exported. Phase 5\n * adds the Astro adapter; Phase 5.7 the `integration/` layer.\n *\n * D18 (L1 boundary, Phase 2): the L1 primitives (`WebsiteHeading`,\n * `WebsiteText`, `WebsiteButton`, `WebsiteLink`, `WebsiteImage`) are\n * self-contained placeholders — they do NOT consume\n * `ComponentRegistryService` from `saas-frontend-lib` and do NOT\n * re-export `saas-frontend-lib` primitives. Consumers override at the\n * L1 boundary by barrel-re-exporting their preferred styled variant\n * from their app's `app/website/primitives/<Primitive>.tsx`. Phase 6\n * dogfood will revisit and decide whether to promote a registry-based\n * override mechanism.\n */\n\nexport * from './schemas/refs/page-ref.schemas';\nexport * from './schemas/refs/section-ref.schemas';\nexport * from './schemas/label-keys/website-label-key.schemas';\nexport * from './schemas/sections/website-section-category.shared';\nexport * from './schemas/sections/website-section-definition.shared.schemas';\nexport * from './schemas/structured-data/website-structured-data.shared.schemas';\nexport * from './schemas/structured-data/structured-data-reconciliation.shared';\nexport * from './schemas/design-tokens/website-design-tokens.shared.schemas';\nexport * from './schemas/manifests/website-page-manifest.shared.schemas';\nexport * from './schemas/manifests/website-root-config.shared.schemas';\nexport * from './schemas/blog/blog-post-frontmatter.shared.schemas';\nexport * from './schemas/validators/label-pack.validator';\n\nexport * from './core/contexts/WebsiteRuntimeContext';\nexport * from './core/contexts/useWebsiteRuntime';\nexport * from './core/contexts/WebsitePageContext';\nexport * from './core/contexts/useWebsitePage';\nexport * from './core/contexts/WebsiteSectionContext';\nexport * from './core/contexts/useWebsiteSection';\nexport * from './core/hooks/useWebsiteLabel';\nexport * from './core/hooks/useWebsiteDesignTokens';\nexport * from './core/factories/define-website-section';\nexport * from './core/factories/define-website-page-manifest';\nexport * from './core/layouts/WebsitePageLayout';\nexport * from './core/layouts/WebsiteSection';\n\nexport * from './core/anonymous-session/website-anonymous-session-client';\nexport * from './core/anonymous-session/inbound-contact-form.schema';\nexport * from './core/anonymous-session/InboundContactForm';\n\nexport * from './components/low-level/WebsiteHeading';\nexport * from './components/low-level/WebsiteText';\nexport * from './components/low-level/WebsiteLink';\nexport * from './components/low-level/WebsiteInternalLink';\nexport * from './core/routing/internal-routing.utils';\nexport * from './components/low-level/WebsiteButton';\nexport * from './components/low-level/WebsiteInternalButton';\nexport * from './components/low-level/WebsiteImage';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4FG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,yDAAyD,CAAC;AACxE,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,kEAAkE,CAAC;AACjF,cAAc,iEAAiE,CAAC;AAChF,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC;AAE1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAE9C,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AAEpD,cAAc,2DAA2D,CAAC;AAC1E,cAAc,sDAAsD,CAAC;AACrE,cAAc,6CAA6C,CAAC;AAE5D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,qCAAqC,CAAC","sourcesContent":["/**\n * @wildo-package @wildo-ai/saas-website\n *\n * SaaS marketing/landing website engine — schemas + helpers + layout\n * placeholders + Astro/MDX adapters + SEO/AEO/GEO builder library.\n *\n * Per `saas-website-slice.md` D6/D15: this engine ships **NO concrete\n * business sections** (no Hero, no CTA, no Pricing). It ships the\n * structural and helper machinery; consumers author every business\n * section themselves via the `defineWebsiteSection` factory and the\n * accompanying SKILLs in `framework-knowledge`.\n *\n * @wildo-boundary\n * - This barrel MAY re-export from any subdirectory of `src/`.\n * - Subdirectories MUST observe their own boundary contracts (see\n * each subdirectory's own `index.ts` `@wildo-boundary` block):\n * - `schemas/` : NO React, NO Astro, NO `~wildo-frontend-lib/**`\n * - `seo/` : NO React, NO Astro\n * - `core/` : MAY use React; NO `~wildo-frontend-lib/**` (Q17=B)\n * - `astro/` : MAY use Astro; NO React component runtime imports\n * - `mdx/` : MAY use `@mdx-js/*`\n * - `integration/` : MAY use React; MAY import the\n * `@wildo-ai/saas-models/public-runtime` SUBPATH,\n * the `@wildo-ai/presets-components-models/public-runtime`\n * SUBPATH, `@wildo-ai/zod-decorators`,\n * plus the SSR / Astro-middleware tier\n * (`axios`, `jsonwebtoken`, `socket.io-client`,\n * `reflect-metadata`) — D12 revised\n * 2026-04-19 (second same-day loosening);\n * the ROOT barrels of `@wildo-ai/saas-models`\n * and `@wildo-ai/presets-components-models`\n * are FORBIDDEN (they boot the decorator\n * framework / pull the heavy registry);\n * the saas-app DI / backend stack\n * (`inversify`, `mongoose`, `express`,\n * `pino`, `helmet`, `argon2`, `bcrypt`,\n * the `@wildo-ai/saas-frontend-lib` /\n * `@wildo-ai/saas-backend-lib` /\n * `@wildo-ai/external-connectors-models`\n * root + subpath surfaces) is FORBIDDEN;\n * MUST NOT import `~wildo-frontend-lib/**`\n * (Q17=B / D16)\n *\n * D12 (client-bundle import discipline, all `core/`/`schemas/`/`integration/`\n * files): the entire saas-website source tree ships into a browser bundle\n * (Astro SSG + React islands). Every shared model atom MUST come from a\n * side-effect-free `/public-runtime` subpath:\n * - `@wildo-ai/saas-models/public-runtime` is the primary door\n * (saas-models-public-runtime.md K-6 — consumers see one path).\n * - `@wildo-ai/presets-components-models/public-runtime` is the\n * internal seam for symbols not (yet) re-exported by saas-models.\n * - `@wildo-ai/zod-decorators` is allowed to import (first revision\n * 2026-04-19). The package itself is side-effect-free; only an\n * explicit `initZodDecorators()` call mutates `z.ZodType.prototype`,\n * which neither saas-website nor the saas-models /public-runtime\n * barrel does.\n * - `axios`, `jsonwebtoken`, `socket.io-client`, `reflect-metadata` are\n * allowed for SSR / Astro-middleware code paths (second same-day\n * revision 2026-04-19). The website engine needs them to provide\n * middleware utilities (HTTP proxying to the SaaS backend, JWT\n * verification of cross-frontend handoff codes, real-time push,\n * decorator-metadata reads). They are SSR-tier — discipline\n * (and a future strict/loose entry split for React islands vs\n * middleware) keeps them out of the React-island bundle.\n *\n * The ROOT barrel of `@wildo-ai/saas-models` is forbidden everywhere in\n * this package — it calls `initZodDecorators()` at module load and pulls\n * `@wildo-ai/external-connectors-models` + the full schema graph into\n * the bundle. Same forbid applies to the root barrel of\n * `@wildo-ai/presets-components-models`, the saas-app DI runtime\n * (`inversify`, `@wildo-ai/saas-frontend-lib`, `@wildo-ai/saas-backend-lib`),\n * and pure-backend libs (`mongoose`, `express`, `pino`, `helmet`,\n * `argon2`, `bcrypt`). Enforced by `src/__tests__/bundle-isolation.test.ts`.\n *\n * These boundaries are enforced by review + the bundle-isolation test\n * above until Phase 8 promotes them to a programmatic ESLint check\n * (cf. `saas-website-slice.md`\n * §\"DEFERRED to Phase 8 — boundary lint mechanism design\").\n *\n * Phase 1+2 status: schemas + runtime contexts/hooks + layout\n * primitives + L1 placeholders + factories are all exported. Phase 5\n * adds the Astro adapter; Phase 5.7 the `integration/` layer.\n *\n * D18 (L1 boundary, Phase 2): the L1 primitives (`WebsiteHeading`,\n * `WebsiteText`, `WebsiteButton`, `WebsiteLink`, `WebsiteImage`) are\n * self-contained placeholders — they do NOT consume\n * `ComponentRegistryService` from `saas-frontend-lib` and do NOT\n * re-export `saas-frontend-lib` primitives. Consumers override at the\n * L1 boundary by barrel-re-exporting their preferred styled variant\n * from their app's `app/website/primitives/<Primitive>.tsx`. Phase 6\n * dogfood will revisit and decide whether to promote a registry-based\n * override mechanism.\n */\n\nexport * from './schemas/refs/page-ref.schemas';\nexport * from './schemas/refs/section-ref.schemas';\nexport * from './schemas/label-keys/website-label-key.schemas';\nexport * from './schemas/label-keys/website-consent-label-keys.schemas';\nexport * from './schemas/sections/website-section-category.shared';\nexport * from './schemas/sections/website-section-definition.shared.schemas';\nexport * from './schemas/structured-data/website-structured-data.shared.schemas';\nexport * from './schemas/structured-data/structured-data-reconciliation.shared';\nexport * from './schemas/design-tokens/website-design-tokens.shared.schemas';\nexport * from './schemas/manifests/website-page-manifest.shared.schemas';\nexport * from './schemas/manifests/website-root-config.shared.schemas';\nexport * from './schemas/blog/blog-post-frontmatter.shared.schemas';\nexport * from './schemas/validators/label-pack.validator';\n\nexport * from './core/contexts/WebsiteRuntimeContext';\nexport * from './core/contexts/useWebsiteRuntime';\nexport * from './core/contexts/WebsitePageContext';\nexport * from './core/contexts/useWebsitePage';\nexport * from './core/contexts/WebsiteSectionContext';\nexport * from './core/contexts/useWebsiteSection';\nexport * from './core/hooks/useWebsiteLabel';\nexport * from './core/hooks/useWebsiteDesignTokens';\nexport * from './core/factories/define-website-section';\nexport * from './core/factories/define-website-page-manifest';\nexport * from './core/layouts/WebsitePageLayout';\nexport * from './core/layouts/WebsiteSection';\n\nexport * from './core/consent/website-consent-storage';\nexport * from './core/consent/WebsiteConsentContext';\nexport * from './core/consent/useWebsiteConsent';\nexport * from './core/consent/WebsiteConsentGate';\nexport * from './core/consent/WebsiteConsentBanner';\n\nexport * from './core/anonymous-session/website-anonymous-session-client';\nexport * from './core/anonymous-session/inbound-contact-form.schema';\nexport * from './core/anonymous-session/InboundContactForm';\n\nexport * from './components/low-level/WebsiteHeading';\nexport * from './components/low-level/WebsiteText';\nexport * from './components/low-level/WebsiteLink';\nexport * from './components/low-level/WebsiteInternalLink';\nexport * from './core/routing/internal-routing.utils';\nexport * from './components/low-level/WebsiteButton';\nexport * from './components/low-level/WebsiteInternalButton';\nexport * from './components/low-level/WebsiteImage';\n"]}
|