@se-studio/ab-testing 18.0.41 → 18.1.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 +22 -1
- package/dist/codegen/cli.js +44 -4
- package/dist/codegen/cli.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 +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +22 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @se-studio/ab-testing
|
|
2
2
|
|
|
3
|
+
## 18.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
## 18.0.42
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- @se-studio/core-ui@1.35.11
|
|
14
|
+
|
|
3
15
|
## 18.0.41
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -190,7 +190,28 @@ 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. Brief control→variant flash on the CTA after JS is expected in v1.
|
|
213
|
+
|
|
214
|
+
### 5. Create Your Analytics Reporter (Project-Specific)
|
|
194
215
|
|
|
195
216
|
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
217
|
|
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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"}
|
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,14 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
-
export type { ActiveAbTestAssignment, UseAbTestAssignmentsOptions } from './hooks/index.js';
|
|
9
|
-
export { useAbTestAssignments } from './hooks/index.js';
|
|
8
|
+
export type { ActiveAbTestAssignment, UseAbTestAssignmentsOptions, UseAbTestStickyNavOverridesOptions, } from './hooks/index.js';
|
|
9
|
+
export { applyNavLeafOverride, findNavLeafOverride, useAbTestAssignments, useAbTestStickyNavOverrides, } from './hooks/index.js';
|
|
10
10
|
export type { AbTestMiddlewareConfig, AbTestResult, CachedAbTest, StaticAbTestMiddlewareOptions, TestsCache, } from './middleware/index.js';
|
|
11
11
|
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';
|
|
12
|
+
export type { ContentfulEntryRef, IncludesMap } from './navLeaves.js';
|
|
13
|
+
export { collectNavigationLeaves, diffNavigationLeaves, resolveEffectiveMenu, } from './navLeaves.js';
|
|
12
14
|
export { buildExperimentImpression } from './reporting/buildExperimentImpression.js';
|
|
13
|
-
export type { AbTest, AbTestAssignment, AbTestCookie, AbTestVariant, AbTestVariantConfig, IBlobStore, } from './types.js';
|
|
15
|
+
export type { AbTest, AbTestAssignment, AbTestCookie, AbTestVariant, AbTestVariantConfig, IBlobStore, NavLeafOverride, } from './types.js';
|
|
14
16
|
export { CMS_HOMEPAGE_SLUG, controlSlugFromCanonicalPath, findCanonicalPath } from './utils.js';
|
|
15
17
|
export type { RawPageTest, WebhookHandlerConfig, WebhookResult, } from './webhook/index.js';
|
|
16
18
|
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;AAEH,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,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;AAE/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
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
8
|
// Re-export hooks
|
|
9
|
-
export { useAbTestAssignments } from './hooks/index.js';
|
|
9
|
+
export { applyNavLeafOverride, findNavLeafOverride, useAbTestAssignments, useAbTestStickyNavOverrides, } from './hooks/index.js';
|
|
10
10
|
// Re-export middleware utilities
|
|
11
11
|
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';
|
|
12
|
+
export { collectNavigationLeaves, diffNavigationLeaves, resolveEffectiveMenu, } from './navLeaves.js';
|
|
12
13
|
export { buildExperimentImpression } from './reporting/buildExperimentImpression.js';
|
|
13
14
|
// Utilities
|
|
14
15
|
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;AAOH,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;AAG/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,28 @@ 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
|
+
|
|
69
91
|
## Webhook Handler (Contentful → update test cache)
|
|
70
92
|
|
|
71
93
|
```ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@se-studio/ab-testing",
|
|
3
|
-
"version": "18.0
|
|
3
|
+
"version": "18.1.0",
|
|
4
4
|
"description": "Server-side A/B testing framework for Next.js applications with Contentful CMS",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -67,13 +67,13 @@
|
|
|
67
67
|
"url": "https://github.com/Something-Else-Studio/se-core-product/issues"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@se-studio/core-ui": "1.35.
|
|
70
|
+
"@se-studio/core-ui": "1.35.11",
|
|
71
71
|
"next": ">=15.5.0 <17",
|
|
72
72
|
"react": "^19.0.0"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@biomejs/biome": "^2.5.15",
|
|
76
|
-
"@se-studio/core-ui": "1.35.
|
|
76
|
+
"@se-studio/core-ui": "1.35.11",
|
|
77
77
|
"@types/node": "^24.13.3",
|
|
78
78
|
"@types/react": "^19.3.0",
|
|
79
79
|
"next": "^16.3.4",
|