Skip to content

Commit ad26b91

Browse files
committed
chore: clean up
rm unused comments, rm env vars from example, rm redundant props, simplify to not require variant names (index order), update docs
1 parent 3ea9aac commit ad26b91

File tree

8 files changed

+69
-82
lines changed

8 files changed

+69
-82
lines changed

.env.example

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,6 @@
3030
NEXT_PUBLIC_MATOMO_URL=
3131
NEXT_PUBLIC_MATOMO_SITE_ID=
3232

33-
# A/B Testing Configuration - Server-side A/B testing using Matomo for tracking
34-
# Format for variants: "name1:weight1,name2:weight2,name3:weight3"
35-
# Weights can be any positive numbers (don't need to sum to 100)
36-
# Test names should be descriptive and match Matomo experiment names
37-
38-
# Template for new tests:
39-
# ABTEST_[TEST_NAME]_ENABLED=false
40-
# ABTEST_[TEST_NAME]_ID=[MATOMO_EXPERIMENT_ID]
41-
# ABTEST_[TEST_NAME]_VARIANTS="original:50,variant_b:50"
42-
43-
# Example: Homepage Persona CTAs Test
44-
# ABTEST_HOMEPAGE_PERSONA_CTAS_ENABLED=false
45-
# ABTEST_HOMEPAGE_PERSONA_CTAS_ID=1
46-
# ABTEST_HOMEPAGE_PERSONA_CTAS_VARIANTS="original:50,variant_b:50"
47-
48-
# Example: Multi-variant Wallet Layout Test
49-
# ABTEST_WALLET_LAYOUT_ENABLED=false
50-
# ABTEST_WALLET_LAYOUT_ID=2
51-
# ABTEST_WALLET_LAYOUT_VARIANTS="original:40,list_view:30,carousel:30"
52-
53-
5433
# Used to avoid loading Matomo in our preview deploys
5534
IS_PREVIEW_DEPLOY=false
5635

CLAUDE.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ The site uses a GDPR-compliant, cookie-less A/B testing system integrated with M
201201
### Key Features
202202

203203
- **Matomo API Integration** - Experiments configured in Matomo dashboard
204-
- **Cookie-less Tracking** - Uses deterministic IP + User-Agent fingerprinting
204+
- **Cookie-less Variant Persistence** - Uses deterministic IP + User-Agent fingerprinting for variant assignment
205205
- **Server-side Rendering** - No layout shifts, consistent variants on first load
206206
- **Real-time Updates** - Change weights instantly via Matomo (no deployments)
207207
- **Preview Mode** - Debug panel available in development and preview environments
@@ -222,14 +222,18 @@ The site uses a GDPR-compliant, cookie-less A/B testing system integrated with M
222222
<ABTestWrapper
223223
testKey="HomepageHero" // Must match Matomo experiment name exactly
224224
variants={[
225-
<OriginalComponent key="original" />,
226-
<NewComponent key="variant" />
225+
<OriginalComponent key="current-hero" />, // Index 0: Original
226+
<NewComponent key="redesigned-hero" /> // Index 1: Variation
227227
]}
228228
fallback={<OriginalComponent />}
229229
/>
230230
```
231231

232-
**No TypeScript changes required** - the system automatically fetches configuration from Matomo.
232+
**Important**:
233+
- Variants matched by **array index**, not names
234+
- Array order must match Matomo experiment order exactly
235+
- JSX `key` props become debug panel labels: `"redesigned-hero"``"Redesigned Hero"`
236+
- No TypeScript changes required - system fetches configuration from Matomo
233237

234238
### Architecture
235239

app/[locale]/stablecoins/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ async function Page({ params }: { params: Promise<{ locale: Lang }> }) {
594594
<ABTestWrapper
595595
testKey="AppTest"
596596
variants={[
597-
<div key="original" className="flex flex-wrap gap-4">
597+
<div key="two-buttons" className="flex flex-wrap gap-4">
598598
<ButtonLink href="/dapps/">
599599
{t("page-stablecoins-explore-dapps")}
600600
</ButtonLink>
@@ -607,7 +607,7 @@ async function Page({ params }: { params: Promise<{ locale: Lang }> }) {
607607
{t("page-stablecoins-more-defi-button")}
608608
</ButtonLink>
609609
</div>,
610-
<div key="Variation1" className="flex flex-wrap gap-4">
610+
<div key="single-button" className="flex flex-wrap gap-4">
611611
<ButtonLink href="/dapps/">
612612
{t("page-stablecoins-explore-apps")}
613613
</ButtonLink>

docs/ab-testing.md

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,21 @@ export default function MyPage() {
5555
<ABTestWrapper
5656
testKey="HomepageHero" // Must match your Matomo experiment name exactly
5757
variants={[
58-
<OriginalComponent key="original" />, // Index 0: Original/existing variant
59-
<NewDesignComponent key="newdesign" />, // Index 1: First test variation
58+
<OriginalComponent key="current-design" />, // Index 0: Original (matches Matomo order)
59+
<NewDesignComponent key="hero-redesign" />, // Index 1: Variation (matches Matomo order)
6060
]}
6161
/>
6262
</div>
6363
)
6464
}
6565
```
6666

67+
**Important Notes:**
68+
69+
- Variants are matched by **array index**, not by name
70+
- Array order must match the exact order of variations in your Matomo experiment
71+
- JSX `key` props serve as array keys, and human-readable labels in the debug panel (parsed from kebab-case to Title Case)
72+
6773
### 4. Experiment Activation
6874

6975
The experiment will automatically start running when:
@@ -73,6 +79,7 @@ The experiment will automatically start running when:
7379
3. **Matomo detects the experiment** and begins tracking
7480

7581
**Manual Control:**
82+
7683
- Use the **Schedule** settings in Matomo to control start/end dates
7784
- Experiments respect their configured schedule automatically
7885
- You can pause/resume experiments anytime in the Matomo dashboard
@@ -82,24 +89,28 @@ The experiment will automatically start running when:
8289
Support for 3+ variants:
8390

8491
```tsx
85-
// Matomo experiment with:
86-
// - Original: 40% (implicit)
87-
// - ListLayout: 30%
88-
// - GridLayout: 20%
89-
// - CarouselLayout: 10%
92+
// Matomo experiment configured with variations in this exact order:
93+
// Index 0: Original (implicit) - 40% weight
94+
// Index 1: List Layout - 30% weight
95+
// Index 2: Grid Layout - 20% weight
96+
// Index 3: Carousel Layout - 10% weight
9097

9198
<ABTestWrapper
9299
testKey="WalletLayout"
93100
variants={[
94-
<OriginalLayout />, // Index 0: Original (40% weight)
95-
<ListLayout />, // Index 1: ListLayout (30% weight)
96-
<GridLayout />, // Index 2: GridLayout (20% weight)
97-
<CarouselLayout />, // Index 3: CarouselLayout (10% weight)
101+
<OriginalLayout key="original-layout" />, // Index 0
102+
<ListLayout key="list-layout" />, // Index 1
103+
<GridLayout key="grid-layout" />, // Index 2
104+
<CarouselLayout key="carousel-layout" />, // Index 3
98105
]}
99106
/>
100107
```
101108

102-
**Important**: Variant array order must match the order of variations in your Matomo experiment.
109+
**Important**:
110+
111+
- Variant array order must exactly **match the order in your Matomo experiment**
112+
- Assignment is by index (0, 1, 2, 3...), not by name matching
113+
- Debug panel shows formatted key names: `"list-layout"``"List Layout"`
103114

104115
## How It Works
105116

@@ -169,6 +180,8 @@ IS_PREVIEW_DEPLOY=false
169180

170181
- Keep variants as similar as possible (same props, structure)
171182
- Always provide a meaningful fallback component
183+
- Use descriptive kebab-case keys: `key="simplified-checkout"` becomes `"Simplified Checkout"` in debug panel
184+
- Ensure variant array order matches Matomo experiment order exactly
172185
- Test all variants in Storybook before deploying
173186

174187
### Testing Strategy
@@ -230,10 +243,10 @@ The panel helps verify your test is working correctly before production deployme
230243
### Core Files
231244

232245
- `app/api/ab-config/route.ts` - Matomo API integration
233-
- `src/lib/ab-testing/config.ts` - Configuration management and caching
234-
- `src/lib/ab-testing/server.ts` - Assignment logic and fingerprinting
246+
- `src/lib/ab-testing/server.ts` - Assignment logic and fingerprinting (index-based)
235247
- `src/components/AB/TestWrapper.tsx` - Main React component
236248
- `src/components/AB/TestDebugPanel.tsx` - Development debug interface
249+
- `src/components/AB/ClientABTestWrapper.tsx` - Client-side rendering with localStorage overrides
237250

238251
### Data Flow
239252

src/components/AB/TestWrapper.tsx

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@ import { ClientABTestWrapper } from "./ClientABTestWrapper"
66
import { ABTestDebugPanel } from "./TestDebugPanel"
77
import { ABTestTracker } from "./TestTracker"
88

9-
import {
10-
getABTestAssignment,
11-
getABTestConfigs,
12-
getVariantIndex,
13-
} from "@/lib/ab-testing/server"
9+
import { getABTestAssignment } from "@/lib/ab-testing/server"
1410
import { ABTestVariants } from "@/lib/ab-testing/types"
1511

1612
type ABTestWrapperProps = {
@@ -25,18 +21,28 @@ const ABTestWrapper = async ({
2521
fallback,
2622
}: ABTestWrapperProps) => {
2723
try {
28-
// Get deterministic assignment and configs
29-
const [assignment, configs] = await Promise.all([
30-
getABTestAssignment(testKey),
31-
getABTestConfigs(),
32-
])
24+
// Get deterministic assignment
25+
const assignment = await getABTestAssignment(testKey)
3326

3427
if (!assignment) throw new Error("No AB test assignment found")
3528

36-
// Find the variant index based on the assignment
37-
const variantIndex = getVariantIndex(assignment.variant, configs, testKey)
38-
const availableVariants =
39-
configs[testKey]?.variants.map((v) => v.name) || []
29+
// Use assignment's variant index directly
30+
const variantIndex = assignment.variantIndex
31+
32+
// Extract labels from React element keys or fall back to defaults
33+
const availableVariants = variants.map((variant, i) => {
34+
if (
35+
variant &&
36+
typeof variant === "object" &&
37+
"key" in variant &&
38+
variant.key
39+
) {
40+
return String(variant.key)
41+
.replace(/-/g, " ")
42+
.replace(/\b\w/g, (l) => l.toUpperCase())
43+
}
44+
return `Variant ${i}`
45+
})
4046

4147
return (
4248
<>

src/lib/ab-testing/index.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22
export type { ABTestAssignment, ABTestConfig, ABTestVariants } from "./types"
33

44
// Server utilities (only import these in server components)
5-
export {
6-
getABTestAssignment,
7-
getABTestConfigs,
8-
getVariantIndex,
9-
} from "./server"
5+
export { getABTestAssignment, getABTestConfigs } from "./server"
106

117
// Note: Server actions are exported from ./actions, not here
128
// This prevents client components from accidentally importing server-only code

src/lib/ab-testing/server.ts

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,42 +33,30 @@ export const getABTestAssignment = async (
3333
headers.get("x-forwarded-for") || headers.get("x-real-ip") || "unknown"
3434
const fingerprint = `${forwardedFor}-${userAgent}`
3535

36-
const variant = assignVariantDeterministic(testConfig, fingerprint)
36+
const variantIndex = assignVariantIndexDeterministic(testConfig, fingerprint)
37+
const variant = testConfig.variants[variantIndex]
3738

3839
return {
3940
experimentId: testConfig.id,
4041
experimentName: testConfig.name || testKey,
4142
variant: variant.name,
43+
variantIndex,
4244
assignedAt: Date.now(),
4345
}
4446
}
4547

46-
export const getVariantIndex = (
47-
variantName: string,
48-
configs: Record<string, ABTestConfig>,
49-
testKey: string
50-
): number => {
51-
const testConfig = configs[testKey]
52-
if (!testConfig) return 0
53-
54-
const variantIndex = testConfig.variants.findIndex(
55-
(v) => v.name === variantName
56-
)
57-
return variantIndex >= 0 ? variantIndex : 0
58-
}
59-
6048
// Deterministic assignment based on user fingerprint (cookie-less)
61-
const assignVariantDeterministic = (
49+
const assignVariantIndexDeterministic = (
6250
config: ABTestConfig,
6351
fingerprint: string
64-
) => {
52+
): number => {
6553
const totalWeight = config.variants.reduce(
6654
(sum, variant) => sum + variant.weight,
6755
0
6856
)
6957

7058
// Handle case where total weight is 0
71-
if (totalWeight === 0) return config.variants[0]
59+
if (totalWeight === 0) return 0
7260

7361
// Use a better hash function for more uniform distribution
7462
// This is a simple implementation of djb2 hash algorithm
@@ -82,12 +70,12 @@ const assignVariantDeterministic = (
8270
const weighted = normalized * totalWeight
8371

8472
let cumulativeWeight = 0
85-
for (const variant of config.variants) {
86-
cumulativeWeight += variant.weight
73+
for (let i = 0; i < config.variants.length; i++) {
74+
cumulativeWeight += config.variants[i].weight
8775
if (weighted <= cumulativeWeight) {
88-
return variant
76+
return i
8977
}
9078
}
9179

92-
return config.variants[0]
80+
return 0
9381
}

src/lib/ab-testing/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type ABTestAssignment = {
1414
experimentId: string
1515
experimentName: string
1616
variant: string
17+
variantIndex: number
1718
assignedAt: number
1819
}
1920

0 commit comments

Comments
 (0)