@se-studio/ab-testing 18.0.42 → 18.2.0
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/CHANGELOG.md +12 -0
- package/README.md +34 -1
- package/dist/buildNavLeafOverridesBySlug.d.ts +13 -0
- package/dist/buildNavLeafOverridesBySlug.d.ts.map +1 -0
- package/dist/buildNavLeafOverridesBySlug.js +24 -0
- package/dist/buildNavLeafOverridesBySlug.js.map +1 -0
- package/dist/codegen/cli.js +44 -4
- package/dist/codegen/cli.js.map +1 -1
- package/dist/components/AbTestStickyNavScript.d.ts +25 -0
- package/dist/components/AbTestStickyNavScript.d.ts.map +1 -0
- package/dist/components/AbTestStickyNavScript.js +93 -0
- package/dist/components/AbTestStickyNavScript.js.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useAbTestStickyNavOverrides.d.ts +34 -0
- package/dist/hooks/useAbTestStickyNavOverrides.d.ts.map +1 -0
- package/dist/hooks/useAbTestStickyNavOverrides.js +75 -0
- package/dist/hooks/useAbTestStickyNavOverrides.js.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/navLeaves.d.ts +39 -0
- package/dist/navLeaves.d.ts.map +1 -0
- package/dist/navLeaves.js +149 -0
- package/dist/navLeaves.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/llms.md +23 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @se-studio/ab-testing
|
|
2
2
|
|
|
3
|
+
## 18.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3177455: AbTestStickyNavScript: beforeInteractive DOM patch for sticky nav CTA leaves (data-ab-sticky-cta), plus buildNavLeafOverridesBySlug helper — same early-apply pattern as AbTestUtmScript.
|
|
8
|
+
|
|
9
|
+
## 18.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 39220e7: Sticky navigation leaf overrides: codegen diffs `pageVariant.menu` vs control effective menu into `navLeafOverrides`, plus `useAbTestStickyNavOverrides` for site-wide CTA/chrome patches from the assignment cookie.
|
|
14
|
+
|
|
3
15
|
## 18.0.42
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -190,7 +190,40 @@ export const POST = createWebhookHandler({
|
|
|
190
190
|
});
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
### 4.
|
|
193
|
+
### 4. Sticky navigation (optional)
|
|
194
|
+
|
|
195
|
+
If a page variant sets `menu` and the leaf label/href differs from the control’s effective menu, codegen writes `navLeafOverrides` into the baked config. Sites apply those in the **existing** nav client (patch CTA leaves — do not remount the whole nav):
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
'use client';
|
|
199
|
+
|
|
200
|
+
import {
|
|
201
|
+
applyNavLeafOverride,
|
|
202
|
+
findNavLeafOverride,
|
|
203
|
+
useAbTestStickyNavOverrides,
|
|
204
|
+
} from '@se-studio/ab-testing';
|
|
205
|
+
import { testsByPath } from '@/generated/abTests';
|
|
206
|
+
|
|
207
|
+
// ctaPath e.g. "1.3" = second top-level nav entry, last child (header CTA)
|
|
208
|
+
const overrides = useAbTestStickyNavOverrides({ testsByPath });
|
|
209
|
+
const cta = applyNavLeafOverride(ssrCta, findNavLeafOverride(overrides, ctaPath));
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Editor pattern: clone the Navigation, change the CTA item, link it on `pageVariant.menu`. After deploy, that CTA follows the visitor site-wide for the assigned bucket.
|
|
213
|
+
|
|
214
|
+
Optional early paint (same pattern as `AbTestUtmScript`): mark CTA hosts with `data-ab-sticky-cta="{indexPath}"` and mount in the root layout:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { AbTestStickyNavScript } from '@se-studio/ab-testing/components';
|
|
218
|
+
import { testsByPath } from '@/generated/abTests';
|
|
219
|
+
|
|
220
|
+
// root layout <head>:
|
|
221
|
+
<AbTestStickyNavScript testsByPath={testsByPath} />
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Keep the React hook path so HubSpot click props match after hydration. Brief control→variant flash is still possible if the script is omitted.
|
|
225
|
+
|
|
226
|
+
### 5. Create Your Analytics Reporter (Project-Specific)
|
|
194
227
|
|
|
195
228
|
The package provides a `useAbTestAssignments` hook that returns test assignments for the current page. You create your own reporter component with your project's specific analytics integrations.
|
|
196
229
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CachedAbTest } from './middleware/types.js';
|
|
2
|
+
import type { NavLeafOverride } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Collapse baked `navLeafOverrides` across all tests into a slug → leaves map
|
|
5
|
+
* for {@link AbTestStickyNavScript} (beforeInteractive DOM patch).
|
|
6
|
+
*
|
|
7
|
+
* Later tests win on duplicate paths within the same slug; callers that need
|
|
8
|
+
* assignment-scoped merges should keep using {@link useAbTestStickyNavOverrides}.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildNavLeafOverridesBySlug(testsByPath: Record<string, CachedAbTest[]>): Record<string, NavLeafOverride[]>;
|
|
11
|
+
/** DOM attribute sites put on sticky CTA hosts for {@link AbTestStickyNavScript}. */
|
|
12
|
+
export declare const STICKY_NAV_CTA_ATTR = "data-ab-sticky-cta";
|
|
13
|
+
//# sourceMappingURL=buildNavLeafOverridesBySlug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildNavLeafOverridesBySlug.d.ts","sourceRoot":"","sources":["../src/buildNavLeafOverridesBySlug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,GAC1C,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAYnC;AAED,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapse baked `navLeafOverrides` across all tests into a slug → leaves map
|
|
3
|
+
* for {@link AbTestStickyNavScript} (beforeInteractive DOM patch).
|
|
4
|
+
*
|
|
5
|
+
* Later tests win on duplicate paths within the same slug; callers that need
|
|
6
|
+
* assignment-scoped merges should keep using {@link useAbTestStickyNavOverrides}.
|
|
7
|
+
*/
|
|
8
|
+
export function buildNavLeafOverridesBySlug(testsByPath) {
|
|
9
|
+
const overridesBySlug = {};
|
|
10
|
+
for (const tests of Object.values(testsByPath)) {
|
|
11
|
+
for (const test of tests) {
|
|
12
|
+
if (!test.navLeafOverrides)
|
|
13
|
+
continue;
|
|
14
|
+
for (const [slug, leaves] of Object.entries(test.navLeafOverrides)) {
|
|
15
|
+
const existing = overridesBySlug[slug] ?? [];
|
|
16
|
+
overridesBySlug[slug] = [...existing, ...leaves];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return overridesBySlug;
|
|
21
|
+
}
|
|
22
|
+
/** DOM attribute sites put on sticky CTA hosts for {@link AbTestStickyNavScript}. */
|
|
23
|
+
export const STICKY_NAV_CTA_ATTR = 'data-ab-sticky-cta';
|
|
24
|
+
//# sourceMappingURL=buildNavLeafOverridesBySlug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildNavLeafOverridesBySlug.js","sourceRoot":"","sources":["../src/buildNavLeafOverridesBySlug.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CACzC,WAA2C;IAE3C,MAAM,eAAe,GAAsC,EAAE,CAAC;IAC9D,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,gBAAgB;gBAAE,SAAS;YACrC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBACnE,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC7C,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,MAAM,mBAAmB,GAAG,oBAAoB,CAAC"}
|
package/dist/codegen/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/** biome-ignore-all lint/suspicious/noConsole: CLI script */
|
|
3
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { diffNavigationLeaves, resolveEffectiveMenu, } from '../navLeaves.js';
|
|
5
6
|
import { transformPageTest } from '../webhook/handler.js';
|
|
6
7
|
import { buildStaticTestsByPath, generateAbTestsFileContent } from './generator.js';
|
|
7
8
|
async function loadEnvFile(path) {
|
|
@@ -48,7 +49,8 @@ async function main() {
|
|
|
48
49
|
process.exit(1);
|
|
49
50
|
}
|
|
50
51
|
const host = draftOnly ? 'preview.contentful.com' : 'cdn.contentful.com';
|
|
51
|
-
|
|
52
|
+
// include=10 so page → template → menu → nav items → nested items → internal link targets resolve.
|
|
53
|
+
const url = `https://${host}/spaces/${spaceId}/environments/${environment}/entries?content_type=pageTest&include=10`;
|
|
52
54
|
console.log(`Fetching A/B tests from Contentful (environment: ${environment}, preview: ${String(draftOnly)})...`);
|
|
53
55
|
const response = await fetch(url, {
|
|
54
56
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
@@ -65,6 +67,10 @@ async function main() {
|
|
|
65
67
|
for (const entry of data.includes?.Entry ?? []) {
|
|
66
68
|
includesMap.set(entry.sys.id, entry);
|
|
67
69
|
}
|
|
70
|
+
// PageTest items themselves are not in includes — index them too for id lookups.
|
|
71
|
+
for (const item of data.items ?? []) {
|
|
72
|
+
includesMap.set(item.sys.id, item);
|
|
73
|
+
}
|
|
68
74
|
const rawTests = (data.items ?? []).map((item) => {
|
|
69
75
|
const { fields, sys } = item;
|
|
70
76
|
const controlRef = fields['control'];
|
|
@@ -96,11 +102,45 @@ async function main() {
|
|
|
96
102
|
? { sys: controlRef.sys, slug: controlEntry.fields['slug'] }
|
|
97
103
|
: null,
|
|
98
104
|
variantsCollection: { items: variantItems },
|
|
105
|
+
controlEntry,
|
|
106
|
+
variantEntries: variantRefs
|
|
107
|
+
.map((ref) => includesMap.get(ref.sys.id))
|
|
108
|
+
.filter((entry) => entry !== undefined),
|
|
99
109
|
};
|
|
100
110
|
});
|
|
101
|
-
const tests =
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
const tests = [];
|
|
112
|
+
for (const raw of rawTests) {
|
|
113
|
+
const test = transformPageTest(raw, (id, reason) => console.warn(` → Skipping test ${id}: ${reason}`));
|
|
114
|
+
if (!test)
|
|
115
|
+
continue;
|
|
116
|
+
const controlMenu = resolveEffectiveMenu(raw.controlEntry, includesMap);
|
|
117
|
+
const navLeafOverrides = {};
|
|
118
|
+
for (const variantEntry of raw.variantEntries) {
|
|
119
|
+
const slug = variantEntry.fields['slug'];
|
|
120
|
+
if (typeof slug !== 'string' || slug.length === 0)
|
|
121
|
+
continue;
|
|
122
|
+
// Only sticky-chrome when the variant sets its own menu (page-scoped override).
|
|
123
|
+
const variantMenuLink = variantEntry.fields['menu'];
|
|
124
|
+
if (!variantMenuLink)
|
|
125
|
+
continue;
|
|
126
|
+
const variantMenu = resolveEffectiveMenu(variantEntry, includesMap);
|
|
127
|
+
const overrides = diffNavigationLeaves(controlMenu, variantMenu, includesMap);
|
|
128
|
+
if (overrides === null) {
|
|
129
|
+
console.warn(` → Test "${test.cmsLabel}": sticky nav skipped for variant "${slug}" (menu tree missing or shape mismatch)`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (overrides.length === 0) {
|
|
133
|
+
console.log(` → Test "${test.cmsLabel}": variant "${slug}" menu present but no leaf label/href diffs`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
navLeafOverrides[slug] = overrides;
|
|
137
|
+
console.log(` → Test "${test.cmsLabel}": sticky nav ${overrides.length} leaf override(s) for variant "${slug}"`);
|
|
138
|
+
}
|
|
139
|
+
if (Object.keys(navLeafOverrides).length > 0) {
|
|
140
|
+
test.navLeafOverrides = navLeafOverrides;
|
|
141
|
+
}
|
|
142
|
+
tests.push(test);
|
|
143
|
+
}
|
|
104
144
|
console.log(`Found ${tests.length} active test(s)`);
|
|
105
145
|
for (const test of tests) {
|
|
106
146
|
console.log(` → "${test.cmsLabel}" on /${test.controlSlug} with ${test.variants.length} variant(s)`);
|
package/dist/codegen/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/codegen/cli.ts"],"names":[],"mappings":";AACA,6DAA6D;AAC7D,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAEpF,KAAK,UAAU,WAAW,CAAC,IAAY;IACrC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC9C,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAClD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,EAAE,KAAK,CAAC,CAAC;gBAAE,SAAS;YACxB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,GAAG,GAAG,OAAO;iBAChB,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;iBACb,IAAI,EAAE;iBACN,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;YAC/B,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;QAC3D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;AACH,CAAC;AAED,SAAS,aAAa;IACpB,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IACtE,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,4BAA4B,CAAC;AACxE,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,WAAW,CAAC,YAAY,CAAC,CAAC;IAEhC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,MAAM,CAAC;IACvD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,SAAS;QAC3B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC;QAChD,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IAC3C,MAAM,WAAW,GACf,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,IAAI,QAAQ,CAAC;IAElG,IAAI,CAAC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CACX,SAAS;YACP,CAAC,CAAC,gEAAgE;YAClE,CAAC,CAAC,wDAAwD,CAC7D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,oBAAoB,CAAC;IACzE,MAAM,GAAG,GAAG,WAAW,IAAI,WAAW,OAAO,iBAAiB,WAAW,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/codegen/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CachedAbTest } from '../middleware/types.js';
|
|
2
|
+
export interface AbTestStickyNavScriptProps {
|
|
3
|
+
/**
|
|
4
|
+
* Baked testsByPath map from generate-ab-tests (same as the sticky hook).
|
|
5
|
+
*/
|
|
6
|
+
testsByPath: Record<string, CachedAbTest[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Cookie name to read assignments from.
|
|
9
|
+
* @default "ab-test-info"
|
|
10
|
+
*/
|
|
11
|
+
cookieName?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Inline script that patches sticky nav CTA leaves from the assignment cookie
|
|
15
|
+
* as early as possible (before React hydration), matching {@link AbTestUtmScript}.
|
|
16
|
+
*
|
|
17
|
+
* Sites mark CTA hosts with `data-ab-sticky-cta="{indexPath}"` (see
|
|
18
|
+
* {@link STICKY_NAV_CTA_ATTR}). Keep {@link useAbTestStickyNavOverrides} in the
|
|
19
|
+
* nav client so React props (and HubSpot click text) match after hydration.
|
|
20
|
+
*
|
|
21
|
+
* Must be rendered in the root layout only — Next.js requires
|
|
22
|
+
* `strategy="beforeInteractive"` scripts to live in the root layout.
|
|
23
|
+
*/
|
|
24
|
+
export declare function AbTestStickyNavScript({ testsByPath, cookieName, }: AbTestStickyNavScriptProps): import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=AbTestStickyNavScript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AbTestStickyNavScript.d.ts","sourceRoot":"","sources":["../../src/components/AbTestStickyNavScript.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,WAAW,0BAA0B;IACzC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IAC5C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,WAAW,EACX,UAA2B,GAC5B,EAAE,0BAA0B,+BAgF5B"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import Script from 'next/script';
|
|
3
|
+
import { buildNavLeafOverridesBySlug, STICKY_NAV_CTA_ATTR, } from '../buildNavLeafOverridesBySlug.js';
|
|
4
|
+
/**
|
|
5
|
+
* Inline script that patches sticky nav CTA leaves from the assignment cookie
|
|
6
|
+
* as early as possible (before React hydration), matching {@link AbTestUtmScript}.
|
|
7
|
+
*
|
|
8
|
+
* Sites mark CTA hosts with `data-ab-sticky-cta="{indexPath}"` (see
|
|
9
|
+
* {@link STICKY_NAV_CTA_ATTR}). Keep {@link useAbTestStickyNavOverrides} in the
|
|
10
|
+
* nav client so React props (and HubSpot click text) match after hydration.
|
|
11
|
+
*
|
|
12
|
+
* Must be rendered in the root layout only — Next.js requires
|
|
13
|
+
* `strategy="beforeInteractive"` scripts to live in the root layout.
|
|
14
|
+
*/
|
|
15
|
+
export function AbTestStickyNavScript({ testsByPath, cookieName = 'ab-test-info', }) {
|
|
16
|
+
const overridesBySlug = buildNavLeafOverridesBySlug(testsByPath);
|
|
17
|
+
const payload = JSON.stringify(overridesBySlug);
|
|
18
|
+
const attr = STICKY_NAV_CTA_ATTR;
|
|
19
|
+
return (_jsx(Script, { id: "ab-test-sticky-nav", strategy: "beforeInteractive", children: `
|
|
20
|
+
(function() {
|
|
21
|
+
try {
|
|
22
|
+
var overridesBySlug = ${payload};
|
|
23
|
+
var cookieName = ${JSON.stringify(cookieName)};
|
|
24
|
+
var attr = ${JSON.stringify(attr)};
|
|
25
|
+
function readLeaves() {
|
|
26
|
+
var m = document.cookie.match(new RegExp('(?:^| )' + cookieName + '=([^;]+)'));
|
|
27
|
+
if (!m) return [];
|
|
28
|
+
var assignments = JSON.parse(decodeURIComponent(m[1]));
|
|
29
|
+
var leaves = [];
|
|
30
|
+
var seen = {};
|
|
31
|
+
for (var testId in assignments) {
|
|
32
|
+
if (!Object.prototype.hasOwnProperty.call(assignments, testId)) continue;
|
|
33
|
+
var slug = assignments[testId] && assignments[testId].variant_slug;
|
|
34
|
+
if (!slug || slug === 'control') continue;
|
|
35
|
+
var list = overridesBySlug[slug];
|
|
36
|
+
if (!list || !list.length) continue;
|
|
37
|
+
for (var i = 0; i < list.length; i++) {
|
|
38
|
+
var leaf = list[i];
|
|
39
|
+
if (!leaf || !leaf.path || seen[leaf.path]) continue;
|
|
40
|
+
seen[leaf.path] = true;
|
|
41
|
+
leaves.push(leaf);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return leaves;
|
|
45
|
+
}
|
|
46
|
+
var applying = false;
|
|
47
|
+
function applyLeaves(leaves) {
|
|
48
|
+
if (!leaves.length || applying) return;
|
|
49
|
+
applying = true;
|
|
50
|
+
try {
|
|
51
|
+
var nodes = document.querySelectorAll('[' + attr + ']');
|
|
52
|
+
for (var n = 0; n < nodes.length; n++) {
|
|
53
|
+
var host = nodes[n];
|
|
54
|
+
var path = host.getAttribute(attr);
|
|
55
|
+
if (!path) continue;
|
|
56
|
+
var el =
|
|
57
|
+
host.tagName === 'A' || host.tagName === 'BUTTON'
|
|
58
|
+
? host
|
|
59
|
+
: host.querySelector('a,button');
|
|
60
|
+
if (!el) continue;
|
|
61
|
+
for (var i = 0; i < leaves.length; i++) {
|
|
62
|
+
if (leaves[i].path !== path) continue;
|
|
63
|
+
if (leaves[i].label && el.textContent !== leaves[i].label) {
|
|
64
|
+
el.textContent = leaves[i].label;
|
|
65
|
+
}
|
|
66
|
+
if (
|
|
67
|
+
leaves[i].href != null &&
|
|
68
|
+
el.tagName === 'A' &&
|
|
69
|
+
el.getAttribute('href') !== leaves[i].href
|
|
70
|
+
) {
|
|
71
|
+
el.setAttribute('href', leaves[i].href);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
applying = false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function run() {
|
|
81
|
+
if (applying) return;
|
|
82
|
+
applyLeaves(readLeaves());
|
|
83
|
+
}
|
|
84
|
+
run();
|
|
85
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
86
|
+
var obs = new MutationObserver(run);
|
|
87
|
+
obs.observe(document.documentElement, { childList: true, subtree: true });
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {}
|
|
90
|
+
})();
|
|
91
|
+
` }));
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=AbTestStickyNavScript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AbTestStickyNavScript.js","sourceRoot":"","sources":["../../src/components/AbTestStickyNavScript.tsx"],"names":[],"mappings":";AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,mCAAmC,CAAC;AAe3C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,WAAW,EACX,UAAU,GAAG,cAAc,GACA;IAC3B,MAAM,eAAe,GAAG,2BAA2B,CAAC,WAAW,CAAC,CAAC;IACjE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,mBAAmB,CAAC;IAEjC,OAAO,CACL,KAAC,MAAM,IAAC,EAAE,EAAC,oBAAoB,EAAC,QAAQ,EAAC,mBAAmB,YAAE;;;4BAGtC,OAAO;uBACZ,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;iBAChC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAmEhC,GAAU,CACZ,CAAC;AACJ,CAAC"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { AbTestReporter } from './AbTestReporter';
|
|
2
|
+
export type { AbTestStickyNavScriptProps } from './AbTestStickyNavScript';
|
|
3
|
+
export { AbTestStickyNavScript } from './AbTestStickyNavScript';
|
|
2
4
|
export { AbTestUtmScript } from './AbTestUtmScript';
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export type { ActiveAbTestAssignment, UseAbTestAssignmentsOptions } from './useAbTestAssignments';
|
|
7
7
|
export { useAbTestAssignments } from './useAbTestAssignments';
|
|
8
|
+
export type { UseAbTestStickyNavOverridesOptions } from './useAbTestStickyNavOverrides';
|
|
9
|
+
export { applyNavLeafOverride, findNavLeafOverride, useAbTestStickyNavOverrides, } from './useAbTestStickyNavOverrides';
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAClG,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAClG,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,kCAAkC,EAAE,MAAM,+BAA+B,CAAC;AACxF,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,+BAA+B,CAAC"}
|
package/dist/hooks/index.js
CHANGED
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* React hooks for client-side A/B test reporting.
|
|
5
5
|
*/
|
|
6
6
|
export { useAbTestAssignments } from './useAbTestAssignments';
|
|
7
|
+
export { applyNavLeafOverride, findNavLeafOverride, useAbTestStickyNavOverrides, } from './useAbTestStickyNavOverrides';
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
package/dist/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CachedAbTest } from '../middleware/types';
|
|
2
|
+
import type { NavLeafOverride } from '../types';
|
|
3
|
+
export interface UseAbTestStickyNavOverridesOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Baked testsByPath map from generate-ab-tests.
|
|
6
|
+
*/
|
|
7
|
+
testsByPath: Record<string, CachedAbTest[]>;
|
|
8
|
+
/**
|
|
9
|
+
* Cookie name to read assignments from.
|
|
10
|
+
* @default "ab-test-info"
|
|
11
|
+
*/
|
|
12
|
+
cookieName?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolve sticky navigation leaf overrides for the visitor's current assignments.
|
|
16
|
+
*
|
|
17
|
+
* Unlike {@link useAbTestAssignments}, this is **not** filtered by the current path —
|
|
18
|
+
* the assignment cookie is site-wide and chrome should follow the bucket everywhere.
|
|
19
|
+
*
|
|
20
|
+
* @returns `null` while reading; then a (possibly empty) array of leaf overrides.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useAbTestStickyNavOverrides(options: UseAbTestStickyNavOverridesOptions): NavLeafOverride[] | null;
|
|
23
|
+
/**
|
|
24
|
+
* Apply sticky leaf overrides onto a slim nav item (label/href only).
|
|
25
|
+
*/
|
|
26
|
+
export declare function applyNavLeafOverride<T extends {
|
|
27
|
+
label: string;
|
|
28
|
+
href?: string | null;
|
|
29
|
+
}>(item: T, override: NavLeafOverride | undefined): T;
|
|
30
|
+
/**
|
|
31
|
+
* Find an override for an exact index path (e.g. header CTA at `"1.3"`).
|
|
32
|
+
*/
|
|
33
|
+
export declare function findNavLeafOverride(overrides: readonly NavLeafOverride[] | null | undefined, path: string): NavLeafOverride | undefined;
|
|
34
|
+
//# sourceMappingURL=useAbTestStickyNavOverrides.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAbTestStickyNavOverrides.d.ts","sourceRoot":"","sources":["../../src/hooks/useAbTestStickyNavOverrides.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,MAAM,WAAW,kCAAkC;IACjD;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IAC5C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,eAAe,EAAE,GAAG,IAAI,CA8C1B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACpF,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,eAAe,GAAG,SAAS,GACpC,CAAC,CAOH;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,eAAe,EAAE,GAAG,IAAI,GAAG,SAAS,EACxD,IAAI,EAAE,MAAM,GACX,eAAe,GAAG,SAAS,CAG7B"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { DEFAULT_COOKIE_NAME, readCookieFromDocument } from '../middleware/cookies';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve sticky navigation leaf overrides for the visitor's current assignments.
|
|
6
|
+
*
|
|
7
|
+
* Unlike {@link useAbTestAssignments}, this is **not** filtered by the current path —
|
|
8
|
+
* the assignment cookie is site-wide and chrome should follow the bucket everywhere.
|
|
9
|
+
*
|
|
10
|
+
* @returns `null` while reading; then a (possibly empty) array of leaf overrides.
|
|
11
|
+
*/
|
|
12
|
+
export function useAbTestStickyNavOverrides(options) {
|
|
13
|
+
const { testsByPath, cookieName = DEFAULT_COOKIE_NAME } = options;
|
|
14
|
+
const [overrides, setOverrides] = useState(null);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
try {
|
|
17
|
+
const assignments = readCookieFromDocument(document.cookie, cookieName);
|
|
18
|
+
if (Object.keys(assignments).length === 0) {
|
|
19
|
+
setOverrides([]);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const byTestId = new Map();
|
|
23
|
+
for (const tests of Object.values(testsByPath)) {
|
|
24
|
+
for (const test of tests) {
|
|
25
|
+
byTestId.set(test.id, test);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const merged = [];
|
|
29
|
+
const seenPaths = new Set();
|
|
30
|
+
for (const [testId, assignment] of Object.entries(assignments)) {
|
|
31
|
+
const slug = assignment.variant_slug;
|
|
32
|
+
if (!slug || slug === 'control')
|
|
33
|
+
continue;
|
|
34
|
+
const test = byTestId.get(testId);
|
|
35
|
+
const leafOverrides = test?.navLeafOverrides?.[slug];
|
|
36
|
+
if (!leafOverrides || leafOverrides.length === 0)
|
|
37
|
+
continue;
|
|
38
|
+
for (const leaf of leafOverrides) {
|
|
39
|
+
if (seenPaths.has(leaf.path))
|
|
40
|
+
continue;
|
|
41
|
+
seenPaths.add(leaf.path);
|
|
42
|
+
merged.push(leaf);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
setOverrides(merged);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
// biome-ignore lint/suspicious/noConsole: Error logging
|
|
49
|
+
console.error('[useAbTestStickyNavOverrides] Error reading A/B test cookie', e);
|
|
50
|
+
setOverrides([]);
|
|
51
|
+
}
|
|
52
|
+
}, [cookieName, testsByPath]);
|
|
53
|
+
return overrides;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Apply sticky leaf overrides onto a slim nav item (label/href only).
|
|
57
|
+
*/
|
|
58
|
+
export function applyNavLeafOverride(item, override) {
|
|
59
|
+
if (!override)
|
|
60
|
+
return item;
|
|
61
|
+
return {
|
|
62
|
+
...item,
|
|
63
|
+
label: override.label ?? item.label,
|
|
64
|
+
href: override.href !== undefined ? override.href : item.href,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Find an override for an exact index path (e.g. header CTA at `"1.3"`).
|
|
69
|
+
*/
|
|
70
|
+
export function findNavLeafOverride(overrides, path) {
|
|
71
|
+
if (!overrides)
|
|
72
|
+
return undefined;
|
|
73
|
+
return overrides.find((leaf) => leaf.path === path);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=useAbTestStickyNavOverrides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAbTestStickyNavOverrides.js","sourceRoot":"","sources":["../../src/hooks/useAbTestStickyNavOverrides.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
-
export
|
|
9
|
-
export {
|
|
8
|
+
export { buildNavLeafOverridesBySlug, STICKY_NAV_CTA_ATTR, } from './buildNavLeafOverridesBySlug.js';
|
|
9
|
+
export type { ActiveAbTestAssignment, UseAbTestAssignmentsOptions, UseAbTestStickyNavOverridesOptions, } from './hooks/index.js';
|
|
10
|
+
export { applyNavLeafOverride, findNavLeafOverride, useAbTestAssignments, useAbTestStickyNavOverrides, } from './hooks/index.js';
|
|
10
11
|
export type { AbTestMiddlewareConfig, AbTestResult, CachedAbTest, StaticAbTestMiddlewareOptions, TestsCache, } from './middleware/index.js';
|
|
11
12
|
export { applyUrlParams, buildVariantId, clearTestCache, computeCachedTest, createAbTestMiddleware, createAssignment, createStaticAbTestMiddleware, DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getActiveTestIds, getAssignment, getCachedTests, getTestCacheState, isValidAssignment, normalizePath, parseCookie, processAbTestRequest, pruneStaleAssignments, readCookieFromDocument, selectVariant, serializeCookie, setAssignment, } from './middleware/index.js';
|
|
13
|
+
export type { ContentfulEntryRef, IncludesMap } from './navLeaves.js';
|
|
14
|
+
export { collectNavigationLeaves, diffNavigationLeaves, resolveEffectiveMenu, } from './navLeaves.js';
|
|
12
15
|
export { buildExperimentImpression } from './reporting/buildExperimentImpression.js';
|
|
13
|
-
export type { AbTest, AbTestAssignment, AbTestCookie, AbTestVariant, AbTestVariantConfig, IBlobStore, } from './types.js';
|
|
16
|
+
export type { AbTest, AbTestAssignment, AbTestCookie, AbTestVariant, AbTestVariantConfig, IBlobStore, NavLeafOverride, } from './types.js';
|
|
14
17
|
export { CMS_HOMEPAGE_SLUG, controlSlugFromCanonicalPath, findCanonicalPath } from './utils.js';
|
|
15
18
|
export type { RawPageTest, WebhookHandlerConfig, WebhookResult, } from './webhook/index.js';
|
|
16
19
|
export { createWebhookHandler, normalizeVariantConfig, processAbTestWebhook, transformPageTest, } from './webhook/index.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EACV,sBAAsB,EACtB,2BAA2B,EAC3B,kCAAkC,GACnC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,sBAAsB,EACtB,YAAY,EACZ,YAAY,EACZ,6BAA6B,EAC7B,UAAU,GACX,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,cAAc,EACd,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AAErF,YAAY,EACV,MAAM,EACN,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,UAAU,EACV,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAChG,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,aAAa,GACd,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
+
// Sticky nav leaf helpers (codegen + sites)
|
|
9
|
+
export { buildNavLeafOverridesBySlug, STICKY_NAV_CTA_ATTR, } from './buildNavLeafOverridesBySlug.js';
|
|
8
10
|
// Re-export hooks
|
|
9
|
-
export { useAbTestAssignments } from './hooks/index.js';
|
|
11
|
+
export { applyNavLeafOverride, findNavLeafOverride, useAbTestAssignments, useAbTestStickyNavOverrides, } from './hooks/index.js';
|
|
10
12
|
// Re-export middleware utilities
|
|
11
13
|
export { applyUrlParams, buildVariantId, clearTestCache, computeCachedTest, createAbTestMiddleware, createAssignment, createStaticAbTestMiddleware, DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getActiveTestIds, getAssignment, getCachedTests, getTestCacheState, isValidAssignment, normalizePath, parseCookie, processAbTestRequest, pruneStaleAssignments, readCookieFromDocument, selectVariant, serializeCookie, setAssignment, } from './middleware/index.js';
|
|
14
|
+
export { collectNavigationLeaves, diffNavigationLeaves, resolveEffectiveMenu, } from './navLeaves.js';
|
|
12
15
|
export { buildExperimentImpression } from './reporting/buildExperimentImpression.js';
|
|
13
16
|
// Utilities
|
|
14
17
|
export { CMS_HOMEPAGE_SLUG, controlSlugFromCanonicalPath, findCanonicalPath } from './utils.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,4CAA4C;AAC5C,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,kCAAkC,CAAC;AAM1C,kBAAkB;AAClB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAQ1B,iCAAiC;AACjC,OAAO,EACL,cAAc,EACd,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AAWrF,YAAY;AACZ,OAAO,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAMhG,8BAA8B;AAC9B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NavLeafOverride } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sticky navigation leaf overrides — compare control vs variant Navigation trees
|
|
4
|
+
* and emit label/href diffs keyed by index path.
|
|
5
|
+
*/
|
|
6
|
+
export type { NavLeafOverride };
|
|
7
|
+
/** Minimal Contentful entry shape used while walking menus. */
|
|
8
|
+
export interface ContentfulEntryRef {
|
|
9
|
+
sys: {
|
|
10
|
+
id: string;
|
|
11
|
+
contentType?: {
|
|
12
|
+
sys?: {
|
|
13
|
+
id?: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
fields: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export type IncludesMap = Map<string, ContentfulEntryRef>;
|
|
20
|
+
interface NavLeafSnapshot {
|
|
21
|
+
path: string;
|
|
22
|
+
label: string;
|
|
23
|
+
href?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Walk a Navigation entry and collect leaf snapshots (items with no children).
|
|
27
|
+
*/
|
|
28
|
+
export declare function collectNavigationLeaves(navigation: ContentfulEntryRef | undefined, includes: IncludesMap): NavLeafSnapshot[] | null;
|
|
29
|
+
/**
|
|
30
|
+
* Diff control vs variant navigation leaves by index path.
|
|
31
|
+
* Returns null when trees cannot be compared (missing menu or shape mismatch).
|
|
32
|
+
*/
|
|
33
|
+
export declare function diffNavigationLeaves(controlNav: ContentfulEntryRef | undefined, variantNav: ContentfulEntryRef | undefined, includes: IncludesMap): NavLeafOverride[] | null;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve effective menu for a page or pageVariant:
|
|
36
|
+
* entry.menu, else entry.template.menu.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveEffectiveMenu(pageOrVariant: ContentfulEntryRef | undefined, includes: IncludesMap): ContentfulEntryRef | undefined;
|
|
39
|
+
//# sourceMappingURL=navLeaves.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navLeaves.d.ts","sourceRoot":"","sources":["../src/navLeaves.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;GAGG;AAEH,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE;gBAAE,EAAE,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;KAAE,CAAC;IAC7D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAE1D,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA8CD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,kBAAkB,GAAG,SAAS,EAC1C,QAAQ,EAAE,WAAW,GACpB,eAAe,EAAE,GAAG,IAAI,CAqD1B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,kBAAkB,GAAG,SAAS,EAC1C,UAAU,EAAE,kBAAkB,GAAG,SAAS,EAC1C,QAAQ,EAAE,WAAW,GACpB,eAAe,EAAE,GAAG,IAAI,CA0B1B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,kBAAkB,GAAG,SAAS,EAC7C,QAAQ,EAAE,WAAW,GACpB,kBAAkB,GAAG,SAAS,CAShC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
function asLinkId(value) {
|
|
2
|
+
if (!value || typeof value !== 'object')
|
|
3
|
+
return undefined;
|
|
4
|
+
const sys = value.sys;
|
|
5
|
+
if (!sys?.id)
|
|
6
|
+
return undefined;
|
|
7
|
+
return sys.id;
|
|
8
|
+
}
|
|
9
|
+
function resolveEntry(value, includes) {
|
|
10
|
+
const id = asLinkId(value);
|
|
11
|
+
if (!id)
|
|
12
|
+
return undefined;
|
|
13
|
+
return includes.get(id);
|
|
14
|
+
}
|
|
15
|
+
function leafLabel(fields) {
|
|
16
|
+
const title = typeof fields['title'] === 'string' ? fields['title'] : '';
|
|
17
|
+
const text = typeof fields['text'] === 'string' ? fields['text'] : '';
|
|
18
|
+
const useTitle = fields['useTitle'] === true;
|
|
19
|
+
if (useTitle)
|
|
20
|
+
return title;
|
|
21
|
+
return text || title;
|
|
22
|
+
}
|
|
23
|
+
function leafHref(fields, includes) {
|
|
24
|
+
if (typeof fields['link'] === 'string' && fields['link'].length > 0) {
|
|
25
|
+
return fields['link'];
|
|
26
|
+
}
|
|
27
|
+
const internal = resolveEntry(fields['internal'], includes);
|
|
28
|
+
if (!internal)
|
|
29
|
+
return undefined;
|
|
30
|
+
const slug = internal.fields['slug'];
|
|
31
|
+
if (typeof slug !== 'string' || slug.length === 0)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (slug === 'index')
|
|
34
|
+
return '/';
|
|
35
|
+
return slug.startsWith('/') ? slug : `/${slug}`;
|
|
36
|
+
}
|
|
37
|
+
function childLinks(fields) {
|
|
38
|
+
const navItems = fields['navigationItems'];
|
|
39
|
+
if (Array.isArray(navItems))
|
|
40
|
+
return navItems;
|
|
41
|
+
// Some spaces historically used "entries" on nested items — accept both.
|
|
42
|
+
const entries = fields['entries'];
|
|
43
|
+
if (Array.isArray(entries))
|
|
44
|
+
return entries;
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Walk a Navigation entry and collect leaf snapshots (items with no children).
|
|
49
|
+
*/
|
|
50
|
+
export function collectNavigationLeaves(navigation, includes) {
|
|
51
|
+
if (!navigation)
|
|
52
|
+
return null;
|
|
53
|
+
const topEntries = navigation.fields['entries'];
|
|
54
|
+
if (!Array.isArray(topEntries))
|
|
55
|
+
return null;
|
|
56
|
+
const leaves = [];
|
|
57
|
+
const walk = (itemRef, pathParts) => {
|
|
58
|
+
const item = resolveEntry(itemRef, includes);
|
|
59
|
+
if (!item)
|
|
60
|
+
return false;
|
|
61
|
+
const children = childLinks(item.fields);
|
|
62
|
+
if (children.length > 0) {
|
|
63
|
+
for (let i = 0; i < children.length; i++) {
|
|
64
|
+
if (!walk(children[i], [...pathParts, i]))
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
const label = leafLabel(item.fields);
|
|
70
|
+
const href = leafHref(item.fields, includes);
|
|
71
|
+
const snapshot = {
|
|
72
|
+
path: pathParts.join('.'),
|
|
73
|
+
label,
|
|
74
|
+
};
|
|
75
|
+
if (href !== undefined) {
|
|
76
|
+
snapshot.href = href;
|
|
77
|
+
}
|
|
78
|
+
leaves.push(snapshot);
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
for (let i = 0; i < topEntries.length; i++) {
|
|
82
|
+
const entry = resolveEntry(topEntries[i], includes);
|
|
83
|
+
if (!entry)
|
|
84
|
+
return null;
|
|
85
|
+
const children = childLinks(entry.fields);
|
|
86
|
+
if (children.length === 0) {
|
|
87
|
+
const label = leafLabel(entry.fields);
|
|
88
|
+
const href = leafHref(entry.fields, includes);
|
|
89
|
+
const snapshot = { path: String(i), label };
|
|
90
|
+
if (href !== undefined)
|
|
91
|
+
snapshot.href = href;
|
|
92
|
+
leaves.push(snapshot);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (let j = 0; j < children.length; j++) {
|
|
96
|
+
if (!walk(children[j], [i, j]))
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return leaves;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Diff control vs variant navigation leaves by index path.
|
|
104
|
+
* Returns null when trees cannot be compared (missing menu or shape mismatch).
|
|
105
|
+
*/
|
|
106
|
+
export function diffNavigationLeaves(controlNav, variantNav, includes) {
|
|
107
|
+
const controlLeaves = collectNavigationLeaves(controlNav, includes);
|
|
108
|
+
const variantLeaves = collectNavigationLeaves(variantNav, includes);
|
|
109
|
+
if (!controlLeaves || !variantLeaves)
|
|
110
|
+
return null;
|
|
111
|
+
if (controlLeaves.length !== variantLeaves.length)
|
|
112
|
+
return null;
|
|
113
|
+
const overrides = [];
|
|
114
|
+
for (let i = 0; i < controlLeaves.length; i++) {
|
|
115
|
+
const control = controlLeaves[i];
|
|
116
|
+
const variant = variantLeaves[i];
|
|
117
|
+
if (!control || !variant)
|
|
118
|
+
return null;
|
|
119
|
+
if (control.path !== variant.path)
|
|
120
|
+
return null;
|
|
121
|
+
const labelChanged = variant.label !== control.label;
|
|
122
|
+
const hrefChanged = (variant.href ?? '') !== (control.href ?? '');
|
|
123
|
+
if (!labelChanged && !hrefChanged)
|
|
124
|
+
continue;
|
|
125
|
+
const override = { path: variant.path };
|
|
126
|
+
if (labelChanged)
|
|
127
|
+
override.label = variant.label;
|
|
128
|
+
if (hrefChanged && variant.href !== undefined)
|
|
129
|
+
override.href = variant.href;
|
|
130
|
+
overrides.push(override);
|
|
131
|
+
}
|
|
132
|
+
return overrides;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Resolve effective menu for a page or pageVariant:
|
|
136
|
+
* entry.menu, else entry.template.menu.
|
|
137
|
+
*/
|
|
138
|
+
export function resolveEffectiveMenu(pageOrVariant, includes) {
|
|
139
|
+
if (!pageOrVariant)
|
|
140
|
+
return undefined;
|
|
141
|
+
const direct = resolveEntry(pageOrVariant.fields['menu'], includes);
|
|
142
|
+
if (direct)
|
|
143
|
+
return direct;
|
|
144
|
+
const template = resolveEntry(pageOrVariant.fields['template'], includes);
|
|
145
|
+
if (!template)
|
|
146
|
+
return undefined;
|
|
147
|
+
return resolveEntry(template.fields['menu'], includes);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=navLeaves.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navLeaves.js","sourceRoot":"","sources":["../src/navLeaves.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Core type definitions for server-side A/B testing.
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* Sticky chrome override for one navigation leaf (label and/or href).
|
|
8
|
+
* `path` is an index path into Navigation.entries / nested navigationItems
|
|
9
|
+
* (e.g. `"1.3"` = second top-level entry, fourth child — often the header CTA).
|
|
10
|
+
*/
|
|
11
|
+
export interface NavLeafOverride {
|
|
12
|
+
path: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
href?: string;
|
|
15
|
+
}
|
|
6
16
|
/**
|
|
7
17
|
* Represents a single variant in an A/B test.
|
|
8
18
|
*/
|
|
@@ -51,6 +61,12 @@ export interface AbTest {
|
|
|
51
61
|
configuration: AbTestVariantConfig[];
|
|
52
62
|
/** Array of variants to test against the control */
|
|
53
63
|
variants: AbTestVariant[];
|
|
64
|
+
/**
|
|
65
|
+
* Per-variant sticky nav leaf overrides (keyed by variant slug).
|
|
66
|
+
* Derived at codegen from pageVariant.menu vs control effective menu.
|
|
67
|
+
* Applied site-wide by client nav when the assignment cookie matches.
|
|
68
|
+
*/
|
|
69
|
+
navLeafOverrides?: Record<string, NavLeafOverride[]>;
|
|
54
70
|
}
|
|
55
71
|
/**
|
|
56
72
|
* Assignment data stored in the A/B test cookie.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qFAAqF;IACrF,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,oDAAoD;IACpD,QAAQ,EAAE,aAAa,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/docs/llms.md
CHANGED
|
@@ -66,6 +66,29 @@ const { assignments, isLoading } = useAbTestAssignments({ testIds: ['hero-test']
|
|
|
66
66
|
const variant = assignments['hero-test']; // AbTestAssignment | undefined
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
## Sticky navigation leaf overrides
|
|
70
|
+
|
|
71
|
+
When a `pageVariant.menu` differs from the control page’s effective menu (page.menu → else template.menu), `generate-ab-tests` bakes `navLeafOverrides` onto the test (keyed by variant slug). Paths are index paths into `Navigation.entries` / nested `navigationItems` (e.g. `"1.3"` = header CTA on a typical two-column nav).
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import {
|
|
75
|
+
useAbTestStickyNavOverrides,
|
|
76
|
+
findNavLeafOverride,
|
|
77
|
+
applyNavLeafOverride,
|
|
78
|
+
} from '@se-studio/ab-testing';
|
|
79
|
+
import { testsByPath } from '@/generated/abTests';
|
|
80
|
+
|
|
81
|
+
// In the existing nav client island (do not redraw the whole nav):
|
|
82
|
+
const overrides = useAbTestStickyNavOverrides({ testsByPath });
|
|
83
|
+
const cta = applyNavLeafOverride(ssrCta, findNavLeafOverride(overrides, ctaPath));
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- Not filtered by current path — assignment cookie is site-wide; chrome follows the bucket.
|
|
87
|
+
- Control bucket → empty overrides (SSR default nav).
|
|
88
|
+
- Shape mismatch / missing menu → codegen skips sticky nav for that variant (warns, does not fail build).
|
|
89
|
+
- `experiment_impression` stays path-scoped via `useAbTestAssignments`; sticky chrome does not re-fire exposure.
|
|
90
|
+
- Optional early DOM patch: `AbTestStickyNavScript` from `@se-studio/ab-testing/components` with `testsByPath`, plus `data-ab-sticky-cta` markers on CTA hosts (`STICKY_NAV_CTA_ATTR`). Mount next to `AbTestUtmScript` in the root layout. Keep the hook so React/HubSpot props stay aligned after hydration.
|
|
91
|
+
|
|
69
92
|
## Webhook Handler (Contentful → update test cache)
|
|
70
93
|
|
|
71
94
|
```ts
|