@sentientui/react 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +102 -40
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +100 -40
- package/dist/index.mjs.map +1 -1
- package/dist/next/adaptive-root-client.d.ts +6 -0
- package/dist/next/adaptive-root-client.js +7 -5
- package/dist/next/adaptive-root-client.js.map +1 -1
- package/dist/next/adaptive-root.d.ts +36 -19
- package/dist/next/adaptive-root.js +51 -8
- package/dist/next/adaptive-root.js.map +1 -1
- package/dist/server.d.cts +18 -6
- package/dist/server.d.ts +18 -6
- package/dist/server.js +34 -0
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +22 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -6,14 +6,15 @@ import { init } from "@sentientui/core";
|
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
var AdaptiveContext = createContext({
|
|
8
8
|
client: null,
|
|
9
|
-
|
|
9
|
+
apiKey: "",
|
|
10
10
|
initialAssignments: {},
|
|
11
11
|
sessionSegment: "desktop:direct",
|
|
12
12
|
ssrFallback: "first",
|
|
13
|
-
onAssignment: void 0
|
|
13
|
+
onAssignment: void 0,
|
|
14
|
+
initialLayoutOrder: null
|
|
14
15
|
});
|
|
15
16
|
function AdaptiveProvider(props) {
|
|
16
|
-
var _a, _b, _c;
|
|
17
|
+
var _a, _b, _c, _d;
|
|
17
18
|
const [client, setClient] = useState(null);
|
|
18
19
|
useEffect(() => {
|
|
19
20
|
if (props.consent === false) {
|
|
@@ -42,11 +43,12 @@ function AdaptiveProvider(props) {
|
|
|
42
43
|
{
|
|
43
44
|
value: {
|
|
44
45
|
client,
|
|
45
|
-
|
|
46
|
+
apiKey: props.apiKey,
|
|
46
47
|
initialAssignments: (_b = props.initialAssignments) != null ? _b : {},
|
|
47
48
|
sessionSegment: (_c = props.sessionSegment) != null ? _c : "desktop:direct",
|
|
48
49
|
ssrFallback,
|
|
49
|
-
onAssignment: props.onAssignment
|
|
50
|
+
onAssignment: props.onAssignment,
|
|
51
|
+
initialLayoutOrder: (_d = props.initialLayoutOrder) != null ? _d : null
|
|
50
52
|
},
|
|
51
53
|
children: props.children
|
|
52
54
|
}
|
|
@@ -55,8 +57,8 @@ function AdaptiveProvider(props) {
|
|
|
55
57
|
function useSentient() {
|
|
56
58
|
return useContext(AdaptiveContext).client;
|
|
57
59
|
}
|
|
58
|
-
function
|
|
59
|
-
return useContext(AdaptiveContext).
|
|
60
|
+
function useAdaptiveApiKey() {
|
|
61
|
+
return useContext(AdaptiveContext).apiKey;
|
|
60
62
|
}
|
|
61
63
|
function useInitialAssignments() {
|
|
62
64
|
return useContext(AdaptiveContext).initialAssignments;
|
|
@@ -70,6 +72,9 @@ function useSsrFallback() {
|
|
|
70
72
|
function useOnAssignment() {
|
|
71
73
|
return useContext(AdaptiveContext).onAssignment;
|
|
72
74
|
}
|
|
75
|
+
function useLayoutOrder() {
|
|
76
|
+
return useContext(AdaptiveContext).initialLayoutOrder;
|
|
77
|
+
}
|
|
73
78
|
|
|
74
79
|
// src/adaptive.tsx
|
|
75
80
|
import { memo, useEffect as useEffect3, useMemo, useRef as useRef2, useState as useState3 } from "react";
|
|
@@ -149,30 +154,30 @@ function useAssignment(componentId, variantIds) {
|
|
|
149
154
|
console.info(`[sentient] override active: ${componentId} -> ${overrideVariant}`);
|
|
150
155
|
}
|
|
151
156
|
const initial = (() => {
|
|
152
|
-
var _a;
|
|
157
|
+
var _a, _b;
|
|
153
158
|
if (overrideVariant) {
|
|
154
|
-
return { variantId: overrideVariant, isLoading: false };
|
|
159
|
+
return { variantId: overrideVariant, content: null, isLoading: false };
|
|
155
160
|
}
|
|
156
161
|
if (!client) {
|
|
157
162
|
const preloaded = initialAssignments[componentId];
|
|
158
163
|
if (preloaded && variantIds.includes(preloaded)) {
|
|
159
|
-
return { variantId: preloaded, isLoading: false };
|
|
164
|
+
return { variantId: preloaded, content: null, isLoading: false };
|
|
160
165
|
}
|
|
161
166
|
if (ssrFallback === "first" && variantIds.length > 0) {
|
|
162
|
-
return { variantId: variantIds[0], isLoading: false };
|
|
167
|
+
return { variantId: variantIds[0], content: null, isLoading: false };
|
|
163
168
|
}
|
|
164
|
-
return { variantId: null, isLoading: true };
|
|
169
|
+
return { variantId: null, content: null, isLoading: true };
|
|
165
170
|
}
|
|
166
171
|
const cached = client.getAssignment(componentId, segment);
|
|
167
|
-
if (cached && variantIds.includes(cached.variantId)) {
|
|
168
|
-
return { variantId: cached.variantId, isLoading: false };
|
|
172
|
+
if (cached && (variantIds.includes(cached.variantId) || cached.content)) {
|
|
173
|
+
return { variantId: cached.variantId, content: (_a = cached.content) != null ? _a : null, isLoading: false };
|
|
169
174
|
}
|
|
170
175
|
const weights = getWeights(componentId);
|
|
171
176
|
if (weights) {
|
|
172
177
|
const chosen = pickFromWeights(weights, variantIds);
|
|
173
|
-
if (chosen) return { variantId: chosen, isLoading: false };
|
|
178
|
+
if (chosen) return { variantId: chosen, content: null, isLoading: false };
|
|
174
179
|
}
|
|
175
|
-
return { variantId: (
|
|
180
|
+
return { variantId: (_b = variantIds[0]) != null ? _b : null, content: null, isLoading: false };
|
|
176
181
|
})();
|
|
177
182
|
const [state, setState] = useState2(initial);
|
|
178
183
|
const reportAssignment = (variantId) => {
|
|
@@ -182,17 +187,18 @@ function useAssignment(componentId, variantIds) {
|
|
|
182
187
|
onAssignment(componentId, variantId);
|
|
183
188
|
};
|
|
184
189
|
useEffect2(() => {
|
|
190
|
+
var _a;
|
|
185
191
|
if (overrideVariant) return;
|
|
186
192
|
if (!client) return;
|
|
187
193
|
const cached = client.getAssignment(componentId, segment);
|
|
188
|
-
if (cached && variantIds.includes(cached.variantId)) {
|
|
189
|
-
setState({ variantId: cached.variantId, isLoading: false });
|
|
194
|
+
if (cached && (variantIds.includes(cached.variantId) || cached.content)) {
|
|
195
|
+
setState({ variantId: cached.variantId, content: (_a = cached.content) != null ? _a : null, isLoading: false });
|
|
190
196
|
reportAssignment(cached.variantId);
|
|
191
197
|
return;
|
|
192
198
|
}
|
|
193
199
|
setState((prev) => {
|
|
194
|
-
var
|
|
195
|
-
return prev.variantId ? prev : { variantId: (
|
|
200
|
+
var _a2;
|
|
201
|
+
return prev.variantId ? prev : { variantId: (_a2 = variantIds[0]) != null ? _a2 : null, content: null, isLoading: false };
|
|
196
202
|
});
|
|
197
203
|
}, [overrideVariant, client, componentId, segment]);
|
|
198
204
|
useEffect2(() => {
|
|
@@ -202,9 +208,11 @@ function useAssignment(componentId, variantIds) {
|
|
|
202
208
|
if (cached && variantIds.includes(cached.variantId)) return;
|
|
203
209
|
let cancelled = false;
|
|
204
210
|
void client.assign(componentId, variantIds).then((result) => {
|
|
211
|
+
var _a;
|
|
205
212
|
if (cancelled) return;
|
|
206
|
-
if (!result
|
|
207
|
-
|
|
213
|
+
if (!result) return;
|
|
214
|
+
if (!variantIds.includes(result.variantId) && !result.content) return;
|
|
215
|
+
setState({ variantId: result.variantId, content: (_a = result.content) != null ? _a : null, isLoading: false });
|
|
208
216
|
reportAssignment(result.variantId);
|
|
209
217
|
});
|
|
210
218
|
return () => {
|
|
@@ -215,17 +223,18 @@ function useAssignment(componentId, variantIds) {
|
|
|
215
223
|
if (overrideVariant) return;
|
|
216
224
|
if (!client) return;
|
|
217
225
|
return subscribe(componentId, (weights) => {
|
|
226
|
+
var _a;
|
|
218
227
|
const cached = client.getAssignment(componentId, segment);
|
|
219
|
-
if (cached && variantIds.includes(cached.variantId)) {
|
|
220
|
-
setState({ variantId: cached.variantId, isLoading: false });
|
|
228
|
+
if (cached && (variantIds.includes(cached.variantId) || cached.content)) {
|
|
229
|
+
setState({ variantId: cached.variantId, content: (_a = cached.content) != null ? _a : null, isLoading: false });
|
|
221
230
|
return;
|
|
222
231
|
}
|
|
223
232
|
const chosen = pickFromWeights(weights, variantIds);
|
|
224
|
-
if (chosen) setState({ variantId: chosen, isLoading: false });
|
|
233
|
+
if (chosen) setState({ variantId: chosen, content: null, isLoading: false });
|
|
225
234
|
});
|
|
226
235
|
}, [overrideVariant, client, componentId, segment]);
|
|
227
236
|
if (overrideVariant) {
|
|
228
|
-
return { variantId: overrideVariant, isLoading: false };
|
|
237
|
+
return { variantId: overrideVariant, content: null, isLoading: false };
|
|
229
238
|
}
|
|
230
239
|
return state;
|
|
231
240
|
}
|
|
@@ -252,11 +261,11 @@ function findClickable(start, container) {
|
|
|
252
261
|
return false;
|
|
253
262
|
}
|
|
254
263
|
function AdaptiveImpl(props) {
|
|
255
|
-
var _a;
|
|
264
|
+
var _a, _b;
|
|
256
265
|
const client = useSentient();
|
|
257
|
-
const
|
|
266
|
+
const apiKey = useAdaptiveApiKey();
|
|
258
267
|
const variantIds = useMemo(() => Object.keys(props.variants), [props.variants]);
|
|
259
|
-
const { variantId } = useAssignment(props.id, variantIds);
|
|
268
|
+
const { variantId, content } = useAssignment(props.id, variantIds);
|
|
260
269
|
const containerRef = useRef2(null);
|
|
261
270
|
const [mounted, setMounted] = useState3(false);
|
|
262
271
|
useEffect3(() => {
|
|
@@ -267,20 +276,20 @@ function AdaptiveImpl(props) {
|
|
|
267
276
|
const goal = useMemo(() => normalize(props.goal), [props.goal]);
|
|
268
277
|
const goalLabel = typeof props.goal === "string" ? props.goal : goal.type;
|
|
269
278
|
useEffect3(() => {
|
|
270
|
-
var _a2,
|
|
271
|
-
if (!client || !variantId || !
|
|
279
|
+
var _a2, _b2;
|
|
280
|
+
if (!client || !variantId || !apiKey) return;
|
|
272
281
|
if (assignTrackedRef.current === variantId) return;
|
|
273
|
-
const rawHtml = (
|
|
282
|
+
const rawHtml = (_b2 = (_a2 = containerRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b2 : "";
|
|
274
283
|
if (!rawHtml && !mounted) return;
|
|
275
284
|
assignTrackedRef.current = variantId;
|
|
276
285
|
client.track({
|
|
277
|
-
projectId,
|
|
286
|
+
projectId: apiKey,
|
|
278
287
|
componentId: props.id,
|
|
279
288
|
variantId,
|
|
280
289
|
eventType: "variant_assigned",
|
|
281
290
|
payload: rawHtml ? { previewHtml: rawHtml.slice(0, 3e4) } : {}
|
|
282
291
|
});
|
|
283
|
-
}, [client, variantId,
|
|
292
|
+
}, [client, variantId, apiKey, props.id, mounted]);
|
|
284
293
|
useEffect3(() => {
|
|
285
294
|
goalFiredRef.current = false;
|
|
286
295
|
}, [variantId]);
|
|
@@ -294,7 +303,7 @@ function AdaptiveImpl(props) {
|
|
|
294
303
|
hoverStart = Date.now();
|
|
295
304
|
timerId = setTimeout(() => {
|
|
296
305
|
client.track({
|
|
297
|
-
projectId,
|
|
306
|
+
projectId: apiKey,
|
|
298
307
|
componentId: props.id,
|
|
299
308
|
variantId,
|
|
300
309
|
eventType: "cursor_signal",
|
|
@@ -316,7 +325,7 @@ function AdaptiveImpl(props) {
|
|
|
316
325
|
node.removeEventListener("mouseleave", onLeave);
|
|
317
326
|
if (timerId !== null) clearTimeout(timerId);
|
|
318
327
|
};
|
|
319
|
-
}, [client, variantId,
|
|
328
|
+
}, [client, variantId, apiKey, props.id]);
|
|
320
329
|
useEffect3(() => {
|
|
321
330
|
if (!client || !variantId) return;
|
|
322
331
|
const node = containerRef.current;
|
|
@@ -325,7 +334,7 @@ function AdaptiveImpl(props) {
|
|
|
325
334
|
if (goalFiredRef.current) return;
|
|
326
335
|
goalFiredRef.current = true;
|
|
327
336
|
client.track({
|
|
328
|
-
projectId,
|
|
337
|
+
projectId: apiKey,
|
|
329
338
|
componentId: props.id,
|
|
330
339
|
variantId,
|
|
331
340
|
eventType: "goal_achieved",
|
|
@@ -387,11 +396,17 @@ function AdaptiveImpl(props) {
|
|
|
387
396
|
return () => {
|
|
388
397
|
for (const c of cleanups) c();
|
|
389
398
|
};
|
|
390
|
-
}, [client, variantId,
|
|
399
|
+
}, [client, variantId, apiKey, props.id, goal, goalLabel]);
|
|
391
400
|
if (props.clientOnly && (!mounted || !client)) return null;
|
|
392
401
|
if (!variantId) return null;
|
|
393
|
-
const
|
|
394
|
-
|
|
402
|
+
const jsxContent = (_a = props.variants[variantId]) != null ? _a : null;
|
|
403
|
+
const managedContent = jsxContent === null ? content : null;
|
|
404
|
+
if (((_b = process == null ? void 0 : process.env) == null ? void 0 : _b.NODE_ENV) !== "production" && jsxContent === null && managedContent === null) {
|
|
405
|
+
console.warn(
|
|
406
|
+
`[sentient] <Adaptive id="${props.id}"> was assigned variant "${variantId}" but no matching key exists in props.variants. If this is a dashboard-managed text variant, use <AdaptiveText id="${props.id}"> instead.`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
return /* @__PURE__ */ jsx2("div", { ref: containerRef, "data-sentient-id": props.id, "data-sentient-variant": variantId, children: jsxContent != null ? jsxContent : managedContent });
|
|
395
410
|
}
|
|
396
411
|
var Adaptive = memo(AdaptiveImpl, (prev, next) => {
|
|
397
412
|
if (prev.id !== next.id) return false;
|
|
@@ -403,17 +418,62 @@ var Adaptive = memo(AdaptiveImpl, (prev, next) => {
|
|
|
403
418
|
return prevKeys.every((k) => k in next.variants);
|
|
404
419
|
});
|
|
405
420
|
|
|
421
|
+
// src/adaptive-text.tsx
|
|
422
|
+
import { useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
|
|
423
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
424
|
+
function AdaptiveText({
|
|
425
|
+
id,
|
|
426
|
+
default: defaultText,
|
|
427
|
+
component: Tag = "span",
|
|
428
|
+
className
|
|
429
|
+
}) {
|
|
430
|
+
const client = useSentient();
|
|
431
|
+
const apiKey = useAdaptiveApiKey();
|
|
432
|
+
const onAssignment = useOnAssignment();
|
|
433
|
+
const [text, setText] = useState4(null);
|
|
434
|
+
const [variantId, setVariantId] = useState4(null);
|
|
435
|
+
const trackedRef = useRef3(null);
|
|
436
|
+
useEffect4(() => {
|
|
437
|
+
if (!client) return;
|
|
438
|
+
let cancelled = false;
|
|
439
|
+
void client.assign(id).then((result) => {
|
|
440
|
+
if (cancelled || !result) return;
|
|
441
|
+
setVariantId(result.variantId);
|
|
442
|
+
if (result.content) setText(result.content);
|
|
443
|
+
});
|
|
444
|
+
return () => {
|
|
445
|
+
cancelled = true;
|
|
446
|
+
};
|
|
447
|
+
}, [client, id]);
|
|
448
|
+
useEffect4(() => {
|
|
449
|
+
if (!client || !variantId || !apiKey) return;
|
|
450
|
+
if (trackedRef.current === variantId) return;
|
|
451
|
+
trackedRef.current = variantId;
|
|
452
|
+
client.track({
|
|
453
|
+
projectId: apiKey,
|
|
454
|
+
componentId: id,
|
|
455
|
+
variantId,
|
|
456
|
+
eventType: "variant_assigned",
|
|
457
|
+
payload: {}
|
|
458
|
+
});
|
|
459
|
+
onAssignment == null ? void 0 : onAssignment(id, variantId);
|
|
460
|
+
}, [client, variantId, apiKey, id, onAssignment]);
|
|
461
|
+
return /* @__PURE__ */ jsx3(Tag, { className, children: text != null ? text : defaultText });
|
|
462
|
+
}
|
|
463
|
+
|
|
406
464
|
// src/segment.ts
|
|
407
465
|
import { deriveSessionSegment } from "@sentientui/core";
|
|
408
466
|
export {
|
|
409
467
|
Adaptive,
|
|
410
468
|
AdaptiveProvider,
|
|
469
|
+
AdaptiveText,
|
|
411
470
|
deriveSessionSegment as detectSegment,
|
|
412
471
|
getWeights,
|
|
413
472
|
subscribe as subscribeWeights,
|
|
414
473
|
update as updateWeights,
|
|
415
474
|
useAssignment,
|
|
416
475
|
useInitialAssignments,
|
|
476
|
+
useLayoutOrder,
|
|
417
477
|
useSentient
|
|
418
478
|
};
|
|
419
479
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.tsx","../src/adaptive.tsx","../src/use-assignment.ts","../src/weights-store.ts","../src/segment.ts"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\nimport { init, type SentientClient, type SentientConfig } from '@sentientui/core';\n\n/** How to render adaptive slots during SSR when assignments are not preloaded. */\nexport type SsrFallback = 'first' | 'none';\n\ntype AdaptiveContextValue = {\n client: SentientClient | null;\n projectId: string;\n initialAssignments: Record<string, string>;\n sessionSegment: string;\n ssrFallback: SsrFallback;\n onAssignment: ((componentId: string, variantId: string) => void) | undefined;\n};\n\nconst AdaptiveContext = createContext<AdaptiveContextValue>({\n client: null,\n projectId: '',\n initialAssignments: {},\n sessionSegment: 'desktop:direct',\n ssrFallback: 'first',\n onAssignment: undefined,\n});\n\nexport type AdaptiveProviderProps = {\n apiKey: string;\n context: SentientConfig['context'];\n debug?: boolean;\n /**\n * SSR-preloaded assignments from `preloadAssignments()` / `loadAdaptiveAssignments()`.\n * Passed through to `useAssignment` as synchronous initial state so crawlers and\n * the first paint see real content (recommended for SEO).\n */\n initialAssignments?: Record<string, string>;\n /**\n * Bandit segment from SSR (`device:source`). Keeps cache, assign, and worker\n * weights on one row — must match `loadAdaptiveAssignments` / session upsert.\n */\n sessionSegment?: string;\n /**\n * When no `initialAssignments` exist for a component, `'first'` renders\n * `variantIds[0]` in server HTML (safe default for SEO). Use `'none'` only for\n * decorative slots marked `clientOnly`.\n * @default 'first'\n */\n ssrFallback?: SsrFallback;\n /**\n * Consent gate. When `false` the SDK is not initialised and no events are\n * sent. Flip to `true` (e.g. after the user accepts the cookie banner) to\n * initialise and begin tracking.\n */\n consent?: boolean;\n /**\n * Called once per component the first time a variant is resolved for that\n * component in this session. Use to forward assignments to your own analytics\n * (Mixpanel, PostHog, Segment, etc.) without having to wrap `useAssignment`.\n */\n onAssignment?: (componentId: string, variantId: string) => void;\n children: ReactNode;\n};\n\n/**\n * Initialises the Sentient core SDK in a useEffect (SSR-safe) and exposes the\n * client via React context. Re-initialises when `consent` changes.\n */\nexport function AdaptiveProvider(props: AdaptiveProviderProps): JSX.Element {\n const [client, setClient] = useState<SentientClient | null>(null);\n\n useEffect(() => {\n // When consent is explicitly false, tear down any existing client.\n if (props.consent === false) {\n setClient((prev: SentientClient | null) => {\n prev?.destroy();\n return null;\n });\n return;\n }\n\n const c = init({\n apiKey: props.apiKey,\n context: props.context,\n debug: props.debug,\n initialAssignments: props.initialAssignments,\n sessionSegment: props.sessionSegment,\n consent: props.consent,\n });\n setClient(c);\n return () => {\n c.destroy();\n };\n // Re-init when consent changes. Other props are intentionally stable.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.consent]);\n\n const ssrFallback = props.ssrFallback ?? 'first';\n\n return (\n <AdaptiveContext.Provider\n value={{\n client,\n projectId: props.apiKey,\n initialAssignments: props.initialAssignments ?? {},\n sessionSegment: props.sessionSegment ?? 'desktop:direct',\n ssrFallback,\n onAssignment: props.onAssignment,\n }}\n >\n {props.children}\n </AdaptiveContext.Provider>\n );\n}\n\n/**\n * Returns the SentientClient, or null until the provider has finished\n * initialising on the client.\n */\nexport function useSentient(): SentientClient | null {\n return useContext(AdaptiveContext).client;\n}\n\n/** Internal: project identifier carried alongside the client. */\nexport function useAdaptiveProjectId(): string {\n return useContext(AdaptiveContext).projectId;\n}\n\n/** Internal: SSR-preloaded assignments for hydration-safe first render. */\nexport function useInitialAssignments(): Record<string, string> {\n return useContext(AdaptiveContext).initialAssignments;\n}\n\n/** Internal: bandit segment aligned with SSR session upsert. */\nexport function useSessionSegment(): string {\n return useContext(AdaptiveContext).sessionSegment;\n}\n\n/** Internal: SSR fallback strategy when a slot has no preloaded assignment. */\nexport function useSsrFallback(): SsrFallback {\n return useContext(AdaptiveContext).ssrFallback;\n}\n\n/** Internal: forwarding hook for consumer analytics integration. */\nexport function useOnAssignment(): ((componentId: string, variantId: string) => void) | undefined {\n return useContext(AdaptiveContext).onAssignment;\n}\n","'use client';\n\nimport { memo, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport { useAdaptiveProjectId, useSentient } from './provider.js';\nimport { useAssignment } from './use-assignment.js';\n\nexport type ScrollDepthGoal = { type: 'scroll_depth'; threshold: number };\nexport type ClickGoal = { type: 'click'; selector?: string };\nexport type FormSubmitGoal = { type: 'form_submit' };\nexport type CompositeGoal = { type: 'composite'; all: GoalConfig[] };\nexport type GoalConfig = ScrollDepthGoal | ClickGoal | FormSubmitGoal | CompositeGoal;\n\nexport type AdaptiveProps = {\n id: string;\n variants: Record<string, ReactNode>;\n goal: string | GoalConfig;\n /**\n * When true, renders nothing during SSR and before client hydration.\n * Use when you cannot pass `initialAssignments` and prefer a blank slot over\n * a hydration mismatch. Tradeoff: minor CLS on first paint.\n */\n clientOnly?: boolean;\n};\n\nfunction normalize(goal: string | GoalConfig): GoalConfig {\n if (typeof goal === 'string') return { type: 'click' };\n return goal;\n}\n\nfunction isClickableTarget(el: EventTarget | null): boolean {\n if (!(el instanceof Element)) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === 'a' || tag === 'button') return true;\n const role = el.getAttribute('role');\n return role === 'button';\n}\n\nfunction findClickable(start: Element, container: Element): boolean {\n let cursor: Element | null = start;\n while (cursor && cursor !== container) {\n if (isClickableTarget(cursor)) return true;\n cursor = cursor.parentElement;\n }\n return false;\n}\n\nfunction AdaptiveImpl(props: AdaptiveProps): JSX.Element | null {\n const client = useSentient();\n const projectId = useAdaptiveProjectId();\n const variantIds = useMemo(() => Object.keys(props.variants), [props.variants]);\n const { variantId } = useAssignment(props.id, variantIds);\n const containerRef = useRef<HTMLDivElement>(null);\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => { setMounted(true); }, []);\n const goalFiredRef = useRef(false);\n const assignTrackedRef = useRef<string | null>(null);\n const goal = useMemo(() => normalize(props.goal), [props.goal]);\n const goalLabel = typeof props.goal === 'string' ? props.goal : goal.type;\n\n // Track variant_assigned exactly once per (component, variant) mount.\n // Captures innerHTML as previewHtml so the dashboard can render a live preview\n // without requiring manual registry calls. First-seen wins on the server.\n // `mounted` is in deps so the effect re-runs after clientOnly components hydrate\n // and their container div is actually in the DOM.\n useEffect(() => {\n if (!client || !variantId || !projectId) return;\n if (assignTrackedRef.current === variantId) return;\n const rawHtml = containerRef.current?.innerHTML ?? '';\n // For clientOnly, the container is null until mounted — defer until it exists.\n if (!rawHtml && !mounted) return;\n assignTrackedRef.current = variantId;\n client.track({\n projectId,\n componentId: props.id,\n variantId,\n eventType: 'variant_assigned',\n payload: rawHtml ? { previewHtml: rawHtml.slice(0, 30_000) } : {},\n });\n }, [client, variantId, projectId, props.id, mounted]);\n\n // Reset goal latch when variant changes.\n useEffect(() => {\n goalFiredRef.current = false;\n }, [variantId]);\n\n // Emit cursor_signal after 800 ms of continuous hover.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n\n let timerId: ReturnType<typeof setTimeout> | null = null;\n let hoverStart = 0;\n\n const onEnter = (): void => {\n hoverStart = Date.now();\n timerId = setTimeout(() => {\n client.track({\n projectId,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'cursor_signal',\n payload: { hoverDuration: Date.now() - hoverStart },\n });\n timerId = null;\n }, 800);\n };\n\n const onLeave = (): void => {\n if (timerId !== null) {\n clearTimeout(timerId);\n timerId = null;\n }\n };\n\n node.addEventListener('mouseenter', onEnter);\n node.addEventListener('mouseleave', onLeave);\n return () => {\n node.removeEventListener('mouseenter', onEnter);\n node.removeEventListener('mouseleave', onLeave);\n if (timerId !== null) clearTimeout(timerId);\n };\n }, [client, variantId, projectId, props.id]);\n\n // Attach goal tracking.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n\n const fireGoal = (): void => {\n if (goalFiredRef.current) return;\n goalFiredRef.current = true;\n client.track({\n projectId,\n componentId: props.id,\n variantId,\n eventType: 'goal_achieved',\n goalType: goalLabel,\n payload: { reward: 1.0 },\n });\n };\n\n const subgoals: GoalConfig[] = goal.type === 'composite' ? goal.all : [goal];\n const remaining = new Set<number>(subgoals.map((_, i) => i));\n const checkComposite = (idx: number): void => {\n remaining.delete(idx);\n if (remaining.size === 0) fireGoal();\n };\n\n const cleanups: Array<() => void> = [];\n\n subgoals.forEach((sub, idx) => {\n if (sub.type === 'click') {\n const onClick = (e: MouseEvent): void => {\n const target = e.target;\n if (!(target instanceof Element)) return;\n if (!findClickable(target, node)) return;\n if (goal.type === 'composite') checkComposite(idx);\n else fireGoal();\n };\n node.addEventListener('click', onClick);\n cleanups.push(() => node.removeEventListener('click', onClick));\n return;\n }\n\n if (sub.type === 'form_submit') {\n const onSubmit = (e: Event): void => {\n if (!(e.target instanceof HTMLFormElement)) return;\n if (!node.contains(e.target)) return;\n if (goal.type === 'composite') checkComposite(idx);\n else fireGoal();\n };\n node.addEventListener('submit', onSubmit);\n cleanups.push(() => node.removeEventListener('submit', onSubmit));\n return;\n }\n\n if (sub.type === 'scroll_depth') {\n const threshold = Math.max(0, Math.min(1, sub.threshold));\n const io = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.intersectionRatio >= threshold) {\n if (goal.type === 'composite') checkComposite(idx);\n else fireGoal();\n io.disconnect();\n break;\n }\n }\n },\n { threshold: [threshold] },\n );\n io.observe(node);\n cleanups.push(() => io.disconnect());\n return;\n }\n });\n\n return () => {\n for (const c of cleanups) c();\n };\n }, [client, variantId, projectId, props.id, goal, goalLabel]);\n\n // Decorative slots: empty in SSR HTML and until the client has mounted.\n if (props.clientOnly && (!mounted || !client)) return null;\n if (!variantId) return null;\n const content = props.variants[variantId] ?? null;\n\n return (\n <div ref={containerRef} data-sentient-id={props.id} data-sentient-variant={variantId}>\n {content}\n </div>\n );\n}\n\n/** Re-renders only when the chosen variant changes. */\nexport const Adaptive = memo(AdaptiveImpl, (prev, next) => {\n if (prev.id !== next.id) return false;\n if (prev.goal !== next.goal) return false;\n if (prev.variants === next.variants) return true;\n const prevKeys = Object.keys(prev.variants);\n const nextKeys = Object.keys(next.variants);\n if (prevKeys.length !== nextKeys.length) return false;\n return prevKeys.every((k) => k in next.variants);\n});\n","import { useEffect, useRef, useState } from 'react';\nimport { type AssignResult } from '@sentientui/core';\nimport { useSentient, useInitialAssignments, useSessionSegment, useSsrFallback, useOnAssignment } from './provider.js';\nimport { subscribe, getWeights, type ComponentWeights } from './weights-store.js';\n\ndeclare global {\n interface Window { __sentient_overrides?: Record<string, string>; }\n}\n\nfunction getDevOverride(componentId: string): string | null {\n if (typeof window === 'undefined') return null;\n const global = window.__sentient_overrides?.[componentId];\n if (global) return global;\n try {\n const params = new URLSearchParams(window.location.search);\n for (const raw of params.getAll('sentient_variant')) {\n // sentient_variant=componentId:variantId (repeatable for multiple components)\n const sep = raw.indexOf(':');\n if (sep === -1) continue;\n if (raw.slice(0, sep) === componentId) return raw.slice(sep + 1);\n }\n } catch { /* non-browser env */ }\n return null;\n}\n\nexport type AssignmentState = {\n variantId: string | null;\n isLoading: boolean;\n};\n\nfunction pickFromWeights(weights: ComponentWeights, variantIds: string[]): string | null {\n let best: { variantId: string; avgReward: number } | null = null;\n for (const v of weights.variants) {\n if (!variantIds.includes(v.variantId)) continue;\n if (!best || v.avgReward > best.avgReward) {\n best = { variantId: v.variantId, avgReward: v.avgReward };\n }\n }\n return best?.variantId ?? null;\n}\n\n/**\n * Returns a sticky variant assignment for a component.\n *\n * First render reads the local SDK cache; if empty, falls back to a\n * deterministic default and asynchronously calls `/v1/assign`. The server\n * picks the actual variant via ε-greedy and the result replaces the fallback\n * on the next render. Subsequent paints read synchronously from cache —\n * no flicker, no loading state after first paint.\n */\nexport function useAssignment(componentId: string, variantIds: string[]): AssignmentState {\n const initialAssignments = useInitialAssignments();\n const ssrFallback = useSsrFallback();\n const client = useSentient();\n const segment = useSessionSegment();\n const onAssignment = useOnAssignment();\n const assignmentReportedRef = useRef<string | null>(null);\n\n // Dev override: URL param ?sentient_variant=componentId:variantId\n // or window.__sentient_overrides[componentId]. Short-circuits the bandit entirely.\n const devOverride = getDevOverride(componentId);\n const overrideVariant = devOverride && variantIds.includes(devOverride) ? devOverride : null;\n if (overrideVariant) {\n console.info(`[sentient] override active: ${componentId} -> ${overrideVariant}`);\n }\n\n const initial = (() => {\n if (overrideVariant) {\n return { variantId: overrideVariant, isLoading: false };\n }\n // SSR / pre-hydration: no client yet. Use initialAssignments if provided so\n // the server and client first render agree on the same variant (no mismatch).\n if (!client) {\n const preloaded = initialAssignments[componentId];\n if (preloaded && variantIds.includes(preloaded)) {\n return { variantId: preloaded, isLoading: false };\n }\n if (ssrFallback === 'first' && variantIds.length > 0) {\n return { variantId: variantIds[0], isLoading: false };\n }\n return { variantId: null, isLoading: true };\n }\n const cached = client.getAssignment(componentId, segment);\n if (cached && variantIds.includes(cached.variantId)) {\n return { variantId: cached.variantId, isLoading: false };\n }\n const weights = getWeights(componentId);\n if (weights) {\n const chosen = pickFromWeights(weights, variantIds);\n if (chosen) return { variantId: chosen, isLoading: false };\n }\n return { variantId: variantIds[0] ?? null, isLoading: false };\n })();\n\n const [state, setState] = useState<AssignmentState>(initial);\n\n // Helper: call onAssignment at most once per resolved variant.\n const reportAssignment = (variantId: string): void => {\n if (!onAssignment) return;\n if (assignmentReportedRef.current === variantId) return;\n assignmentReportedRef.current = variantId;\n onAssignment(componentId, variantId);\n };\n\n // As soon as the client is ready, unblock the UI immediately with variantIds[0]\n // (or a cached value) so the component never stays invisible while assign is\n // in-flight. The async assign call below then swaps to the bandit-chosen variant.\n useEffect(() => {\n if (overrideVariant) return;\n if (!client) return;\n const cached = client.getAssignment(componentId, segment);\n if (cached && variantIds.includes(cached.variantId)) {\n setState({ variantId: cached.variantId, isLoading: false });\n reportAssignment(cached.variantId);\n return;\n }\n setState((prev) => prev.variantId ? prev : { variantId: variantIds[0] ?? null, isLoading: false });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [overrideVariant, client, componentId, segment]);\n\n // Ask the server for a real assignment when we have a client but no cached one.\n useEffect(() => {\n if (overrideVariant) return;\n if (!client) return;\n const cached = client.getAssignment(componentId, segment);\n if (cached && variantIds.includes(cached.variantId)) return;\n\n let cancelled = false;\n void client.assign(componentId, variantIds).then((result: AssignResult | null) => {\n if (cancelled) return;\n // If the API is unreachable or returns null, the fallback set above stays.\n if (!result || !variantIds.includes(result.variantId)) return;\n setState({ variantId: result.variantId, isLoading: false });\n reportAssignment(result.variantId);\n });\n return () => { cancelled = true; };\n // variantIds intentionally excluded — changing variants mid-mount is unsupported\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [overrideVariant, client, componentId, segment]);\n\n // Live weight updates from the dashboard SSE stream (when wired).\n useEffect(() => {\n if (overrideVariant) return;\n if (!client) return;\n return subscribe(componentId, (weights) => {\n const cached = client.getAssignment(componentId, segment);\n if (cached && variantIds.includes(cached.variantId)) {\n setState({ variantId: cached.variantId, isLoading: false });\n return;\n }\n const chosen = pickFromWeights(weights, variantIds);\n if (chosen) setState({ variantId: chosen, isLoading: false });\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [overrideVariant, client, componentId, segment]);\n\n if (overrideVariant) {\n return { variantId: overrideVariant, isLoading: false };\n }\n\n return state;\n}\n","/** Per-component weights store with isolated subscriptions. */\n\nexport type VariantWeight = {\n variantId: string;\n pulls: number;\n avgReward: number;\n};\n\nexport type ComponentWeights = {\n componentId: string;\n variants: VariantWeight[];\n updatedAt: number;\n};\n\ntype Listener = (weights: ComponentWeights) => void;\n\nconst store = new Map<string, ComponentWeights>();\nconst listeners = new Map<string, Set<Listener>>();\n\n/**\n * Subscribes a listener to a single component. Returns an unsubscribe function.\n * Updates to other components never trigger this listener.\n */\nexport function subscribe(componentId: string, cb: Listener): () => void {\n let set = listeners.get(componentId);\n if (!set) {\n set = new Set();\n listeners.set(componentId, set);\n }\n set.add(cb);\n return () => {\n set!.delete(cb);\n if (set!.size === 0) listeners.delete(componentId);\n };\n}\n\n/**\n * Replaces the weights for a component and notifies only that component's\n * subscribers.\n */\nexport function update(componentId: string, weights: ComponentWeights): void {\n store.set(componentId, weights);\n const set = listeners.get(componentId);\n if (!set) return;\n for (const cb of set) {\n try {\n cb(weights);\n } catch {\n /* never throw to other listeners */\n }\n }\n}\n\n/**\n * Returns the latest known weights for a component, or null if none seen.\n */\nexport function getWeights(componentId: string): ComponentWeights | null {\n return store.get(componentId) ?? null;\n}\n\n/** Test-only: wipe the entire store. */\nexport function _resetWeightsStore(): void {\n store.clear();\n listeners.clear();\n}\n","/** @deprecated Use `useSessionSegment()` from the provider, or `deriveSessionSegment` from `@sentientui/core`. */\nexport { deriveSessionSegment as detectSegment } from '@sentientui/core';\n"],"mappings":"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","names":["useEffect","useRef","useState","useEffect","useState","useState","useEffect","jsx","useRef","useState","useEffect","_a"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx","../src/adaptive.tsx","../src/use-assignment.ts","../src/weights-store.ts","../src/adaptive-text.tsx","../src/segment.ts"],"sourcesContent":["'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\nimport { init, type SentientClient, type SentientConfig } from '@sentientui/core';\n\n/** How to render adaptive slots during SSR when assignments are not preloaded. */\nexport type SsrFallback = 'first' | 'none';\n\ntype AdaptiveContextValue = {\n client: SentientClient | null;\n // The publishable API key (pk_…). Historically named `projectId` because the\n // API uses it as the project identifier on the wire, but it is the API key,\n // not the project UUID. Field renamed for clarity.\n apiKey: string;\n initialAssignments: Record<string, string>;\n sessionSegment: string;\n ssrFallback: SsrFallback;\n onAssignment: ((componentId: string, variantId: string) => void) | undefined;\n initialLayoutOrder: string[] | null;\n};\n\nconst AdaptiveContext = createContext<AdaptiveContextValue>({\n client: null,\n apiKey: '',\n initialAssignments: {},\n sessionSegment: 'desktop:direct',\n ssrFallback: 'first',\n onAssignment: undefined,\n initialLayoutOrder: null,\n});\n\nexport type AdaptiveProviderProps = {\n apiKey: string;\n context: SentientConfig['context'];\n debug?: boolean;\n /**\n * SSR-preloaded assignments from `preloadAssignments()` / `loadAdaptiveAssignments()`.\n * Passed through to `useAssignment` as synchronous initial state so crawlers and\n * the first paint see real content (recommended for SEO).\n */\n initialAssignments?: Record<string, string>;\n /**\n * Bandit segment from SSR (`device:source`). Keeps cache, assign, and worker\n * weights on one row — must match `loadAdaptiveAssignments` / session upsert.\n */\n sessionSegment?: string;\n /**\n * When no `initialAssignments` exist for a component, `'first'` renders\n * `variantIds[0]` in server HTML (safe default for SEO). Use `'none'` only for\n * decorative slots marked `clientOnly`.\n * @default 'first'\n */\n ssrFallback?: SsrFallback;\n /**\n * Consent gate. When `false` the SDK is not initialised and no events are\n * sent. Flip to `true` (e.g. after the user accepts the cookie banner) to\n * initialise and begin tracking.\n */\n consent?: boolean;\n /**\n * Called once per component the first time a variant is resolved for that\n * component in this session. Use to forward assignments to your own analytics\n * (Mixpanel, PostHog, Segment, etc.) without having to wrap `useAssignment`.\n */\n onAssignment?: (componentId: string, variantId: string) => void;\n /**\n * SSR-preloaded section order from `loadAdaptiveDecision()`.\n * Pass the `layoutOrder` field from `DecideResult`. When set,\n * `useLayoutOrder()` returns this on first render so there is no layout shift.\n */\n initialLayoutOrder?: string[] | null;\n children: ReactNode;\n};\n\n/**\n * Initialises the Sentient core SDK in a useEffect (SSR-safe) and exposes the\n * client via React context. Re-initialises when `consent` changes.\n */\nexport function AdaptiveProvider(props: AdaptiveProviderProps): JSX.Element {\n const [client, setClient] = useState<SentientClient | null>(null);\n\n useEffect(() => {\n // When consent is explicitly false, tear down any existing client.\n if (props.consent === false) {\n setClient((prev: SentientClient | null) => {\n prev?.destroy();\n return null;\n });\n return;\n }\n\n const c = init({\n apiKey: props.apiKey,\n context: props.context,\n debug: props.debug,\n initialAssignments: props.initialAssignments,\n sessionSegment: props.sessionSegment,\n consent: props.consent,\n });\n setClient(c);\n return () => {\n c.destroy();\n };\n // Re-init when consent changes. Other props are intentionally stable.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.consent]);\n\n const ssrFallback = props.ssrFallback ?? 'first';\n\n return (\n <AdaptiveContext.Provider\n value={{\n client,\n apiKey: props.apiKey,\n initialAssignments: props.initialAssignments ?? {},\n sessionSegment: props.sessionSegment ?? 'desktop:direct',\n ssrFallback,\n onAssignment: props.onAssignment,\n initialLayoutOrder: props.initialLayoutOrder ?? null,\n }}\n >\n {props.children}\n </AdaptiveContext.Provider>\n );\n}\n\n/**\n * Returns the SentientClient, or null until the provider has finished\n * initialising on the client.\n */\nexport function useSentient(): SentientClient | null {\n return useContext(AdaptiveContext).client;\n}\n\n/** Internal: publishable API key carried alongside the client. */\nexport function useAdaptiveApiKey(): string {\n return useContext(AdaptiveContext).apiKey;\n}\n\n/**\n * @deprecated Renamed to `useAdaptiveApiKey`. Kept as an alias so external\n * imports from earlier versions of this package keep working. Will be removed\n * in 0.4.0.\n */\nlet warnedProjectIdAlias = false;\nexport function useAdaptiveProjectId(): string {\n if (\n !warnedProjectIdAlias &&\n typeof process !== 'undefined' &&\n process.env?.NODE_ENV !== 'production'\n ) {\n warnedProjectIdAlias = true;\n // eslint-disable-next-line no-console\n console.warn(\n '[@sentientui/react] useAdaptiveProjectId is deprecated; use useAdaptiveApiKey. Will be removed in 0.4.0.',\n );\n }\n return useAdaptiveApiKey();\n}\n\n/** Internal: SSR-preloaded assignments for hydration-safe first render. */\nexport function useInitialAssignments(): Record<string, string> {\n return useContext(AdaptiveContext).initialAssignments;\n}\n\n/** Internal: bandit segment aligned with SSR session upsert. */\nexport function useSessionSegment(): string {\n return useContext(AdaptiveContext).sessionSegment;\n}\n\n/** Internal: SSR fallback strategy when a slot has no preloaded assignment. */\nexport function useSsrFallback(): SsrFallback {\n return useContext(AdaptiveContext).ssrFallback;\n}\n\n/** Internal: forwarding hook for consumer analytics integration. */\nexport function useOnAssignment(): ((componentId: string, variantId: string) => void) | undefined {\n return useContext(AdaptiveContext).onAssignment;\n}\n\n/**\n * Returns the persona-specific section order from SSR, or null when no\n * sections were declared on AdaptiveRoot or reliability is below threshold.\n */\nexport function useLayoutOrder(): string[] | null {\n return useContext(AdaptiveContext).initialLayoutOrder;\n}\n","'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { memo, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport { useAdaptiveApiKey, useSentient } from './provider.js';\nimport { useAssignment } from './use-assignment.js';\n\nexport type ScrollDepthGoal = { type: 'scroll_depth'; threshold: number };\nexport type ClickGoal = { type: 'click'; selector?: string };\nexport type FormSubmitGoal = { type: 'form_submit' };\nexport type CompositeGoal = { type: 'composite'; all: GoalConfig[] };\nexport type GoalConfig = ScrollDepthGoal | ClickGoal | FormSubmitGoal | CompositeGoal;\n\nexport type AdaptiveProps = {\n id: string;\n variants: Record<string, ReactNode>;\n goal: string | GoalConfig;\n /**\n * When true, renders nothing during SSR and before client hydration.\n * Use when you cannot pass `initialAssignments` and prefer a blank slot over\n * a hydration mismatch. Tradeoff: minor CLS on first paint.\n */\n clientOnly?: boolean;\n};\n\nfunction normalize(goal: string | GoalConfig): GoalConfig {\n if (typeof goal === 'string') return { type: 'click' };\n return goal;\n}\n\nfunction isClickableTarget(el: EventTarget | null): boolean {\n if (!(el instanceof Element)) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === 'a' || tag === 'button') return true;\n const role = el.getAttribute('role');\n return role === 'button';\n}\n\nfunction findClickable(start: Element, container: Element): boolean {\n let cursor: Element | null = start;\n while (cursor && cursor !== container) {\n if (isClickableTarget(cursor)) return true;\n cursor = cursor.parentElement;\n }\n return false;\n}\n\nfunction AdaptiveImpl(props: AdaptiveProps): JSX.Element | null {\n const client = useSentient();\n const apiKey = useAdaptiveApiKey();\n const variantIds = useMemo(() => Object.keys(props.variants), [props.variants]);\n const { variantId, content } = useAssignment(props.id, variantIds);\n const containerRef = useRef<HTMLDivElement>(null);\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => { setMounted(true); }, []);\n const goalFiredRef = useRef(false);\n const assignTrackedRef = useRef<string | null>(null);\n const goal = useMemo(() => normalize(props.goal), [props.goal]);\n const goalLabel = typeof props.goal === 'string' ? props.goal : goal.type;\n\n // Track variant_assigned exactly once per (component, variant) mount.\n // Captures innerHTML as previewHtml so the dashboard can render a live preview\n // without requiring manual registry calls. First-seen wins on the server.\n // `mounted` is in deps so the effect re-runs after clientOnly components hydrate\n // and their container div is actually in the DOM.\n useEffect(() => {\n if (!client || !variantId || !apiKey) return;\n if (assignTrackedRef.current === variantId) return;\n const rawHtml = containerRef.current?.innerHTML ?? '';\n // For clientOnly, the container is null until mounted — defer until it exists.\n if (!rawHtml && !mounted) return;\n assignTrackedRef.current = variantId;\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId,\n eventType: 'variant_assigned',\n payload: rawHtml ? { previewHtml: rawHtml.slice(0, 30_000) } : {},\n });\n }, [client, variantId, apiKey, props.id, mounted]);\n\n // Reset goal latch when variant changes.\n useEffect(() => {\n goalFiredRef.current = false;\n }, [variantId]);\n\n // Emit cursor_signal after 800 ms of continuous hover.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n\n let timerId: ReturnType<typeof setTimeout> | null = null;\n let hoverStart = 0;\n\n const onEnter = (): void => {\n hoverStart = Date.now();\n timerId = setTimeout(() => {\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'cursor_signal',\n payload: { hoverDuration: Date.now() - hoverStart },\n });\n timerId = null;\n }, 800);\n };\n\n const onLeave = (): void => {\n if (timerId !== null) {\n clearTimeout(timerId);\n timerId = null;\n }\n };\n\n node.addEventListener('mouseenter', onEnter);\n node.addEventListener('mouseleave', onLeave);\n return () => {\n node.removeEventListener('mouseenter', onEnter);\n node.removeEventListener('mouseleave', onLeave);\n if (timerId !== null) clearTimeout(timerId);\n };\n }, [client, variantId, apiKey, props.id]);\n\n // Attach goal tracking.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n\n const fireGoal = (): void => {\n if (goalFiredRef.current) return;\n goalFiredRef.current = true;\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId,\n eventType: 'goal_achieved',\n goalType: goalLabel,\n payload: { reward: 1.0 },\n });\n };\n\n const subgoals: GoalConfig[] = goal.type === 'composite' ? goal.all : [goal];\n const remaining = new Set<number>(subgoals.map((_, i) => i));\n const checkComposite = (idx: number): void => {\n remaining.delete(idx);\n if (remaining.size === 0) fireGoal();\n };\n\n const cleanups: Array<() => void> = [];\n\n subgoals.forEach((sub, idx) => {\n if (sub.type === 'click') {\n const onClick = (e: MouseEvent): void => {\n const target = e.target;\n if (!(target instanceof Element)) return;\n if (!findClickable(target, node)) return;\n if (goal.type === 'composite') checkComposite(idx);\n else fireGoal();\n };\n node.addEventListener('click', onClick);\n cleanups.push(() => node.removeEventListener('click', onClick));\n return;\n }\n\n if (sub.type === 'form_submit') {\n const onSubmit = (e: Event): void => {\n if (!(e.target instanceof HTMLFormElement)) return;\n if (!node.contains(e.target)) return;\n if (goal.type === 'composite') checkComposite(idx);\n else fireGoal();\n };\n node.addEventListener('submit', onSubmit);\n cleanups.push(() => node.removeEventListener('submit', onSubmit));\n return;\n }\n\n if (sub.type === 'scroll_depth') {\n const threshold = Math.max(0, Math.min(1, sub.threshold));\n const io = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.intersectionRatio >= threshold) {\n if (goal.type === 'composite') checkComposite(idx);\n else fireGoal();\n io.disconnect();\n break;\n }\n }\n },\n { threshold: [threshold] },\n );\n io.observe(node);\n cleanups.push(() => io.disconnect());\n return;\n }\n });\n\n return () => {\n for (const c of cleanups) c();\n };\n }, [client, variantId, apiKey, props.id, goal, goalLabel]);\n\n // Decorative slots: empty in SSR HTML and until the client has mounted.\n if (props.clientOnly && (!mounted || !client)) return null;\n if (!variantId) return null;\n\n const jsxContent = props.variants[variantId] ?? null;\n const managedContent = jsxContent === null ? content : null;\n\n if (process?.env?.NODE_ENV !== 'production' && jsxContent === null && managedContent === null) {\n console.warn(\n `[sentient] <Adaptive id=\"${props.id}\"> was assigned variant \"${variantId}\" but no matching key exists in props.variants.` +\n ` If this is a dashboard-managed text variant, use <AdaptiveText id=\"${props.id}\"> instead.`,\n );\n }\n\n return (\n <div ref={containerRef} data-sentient-id={props.id} data-sentient-variant={variantId}>\n {jsxContent ?? managedContent}\n </div>\n );\n}\n\n/** Re-renders only when the chosen variant changes. */\nexport const Adaptive = memo(AdaptiveImpl, (prev, next) => {\n if (prev.id !== next.id) return false;\n if (prev.goal !== next.goal) return false;\n if (prev.variants === next.variants) return true;\n const prevKeys = Object.keys(prev.variants);\n const nextKeys = Object.keys(next.variants);\n if (prevKeys.length !== nextKeys.length) return false;\n return prevKeys.every((k) => k in next.variants);\n});\n","import { useEffect, useRef, useState } from 'react';\nimport { type AssignResult } from '@sentientui/core';\nimport { useSentient, useInitialAssignments, useSessionSegment, useSsrFallback, useOnAssignment } from './provider.js';\nimport { subscribe, getWeights, type ComponentWeights } from './weights-store.js';\n\ndeclare global {\n interface Window { __sentient_overrides?: Record<string, string>; }\n}\n\nfunction getDevOverride(componentId: string): string | null {\n if (typeof window === 'undefined') return null;\n const global = window.__sentient_overrides?.[componentId];\n if (global) return global;\n try {\n const params = new URLSearchParams(window.location.search);\n for (const raw of params.getAll('sentient_variant')) {\n // sentient_variant=componentId:variantId (repeatable for multiple components)\n const sep = raw.indexOf(':');\n if (sep === -1) continue;\n if (raw.slice(0, sep) === componentId) return raw.slice(sep + 1);\n }\n } catch { /* non-browser env */ }\n return null;\n}\n\nexport type AssignmentState = {\n variantId: string | null;\n /** Populated when the assigned variant is a dashboard-managed text variant. */\n content: string | null;\n isLoading: boolean;\n};\n\nfunction pickFromWeights(weights: ComponentWeights, variantIds: string[]): string | null {\n let best: { variantId: string; avgReward: number } | null = null;\n for (const v of weights.variants) {\n if (!variantIds.includes(v.variantId)) continue;\n if (!best || v.avgReward > best.avgReward) {\n best = { variantId: v.variantId, avgReward: v.avgReward };\n }\n }\n return best?.variantId ?? null;\n}\n\n/**\n * Returns a sticky variant assignment for a component.\n *\n * First render reads the local SDK cache; if empty, falls back to a\n * deterministic default and asynchronously calls `/v1/assign`. The server\n * picks the actual variant via Thompson Sampling and the result replaces the fallback\n * on the next render. Subsequent paints read synchronously from cache —\n * no flicker, no loading state after first paint.\n */\nexport function useAssignment(componentId: string, variantIds: string[]): AssignmentState {\n const initialAssignments = useInitialAssignments();\n const ssrFallback = useSsrFallback();\n const client = useSentient();\n const segment = useSessionSegment();\n const onAssignment = useOnAssignment();\n const assignmentReportedRef = useRef<string | null>(null);\n\n // Dev override: URL param ?sentient_variant=componentId:variantId\n // or window.__sentient_overrides[componentId]. Short-circuits the bandit entirely.\n const devOverride = getDevOverride(componentId);\n const overrideVariant = devOverride && variantIds.includes(devOverride) ? devOverride : null;\n if (overrideVariant) {\n console.info(`[sentient] override active: ${componentId} -> ${overrideVariant}`);\n }\n\n const initial = (() => {\n if (overrideVariant) {\n return { variantId: overrideVariant, content: null, isLoading: false };\n }\n // SSR / pre-hydration: no client yet. Use initialAssignments if provided so\n // the server and client first render agree on the same variant (no mismatch).\n if (!client) {\n const preloaded = initialAssignments[componentId];\n if (preloaded && variantIds.includes(preloaded)) {\n return { variantId: preloaded, content: null, isLoading: false };\n }\n if (ssrFallback === 'first' && variantIds.length > 0) {\n return { variantId: variantIds[0], content: null, isLoading: false };\n }\n return { variantId: null, content: null, isLoading: true };\n }\n const cached = client.getAssignment(componentId, segment);\n // Allow cached managed variants (content present) even if not in variantIds.\n if (cached && (variantIds.includes(cached.variantId) || cached.content)) {\n return { variantId: cached.variantId, content: cached.content ?? null, isLoading: false };\n }\n const weights = getWeights(componentId);\n if (weights) {\n const chosen = pickFromWeights(weights, variantIds);\n if (chosen) return { variantId: chosen, content: null, isLoading: false };\n }\n return { variantId: variantIds[0] ?? null, content: null, isLoading: false };\n })();\n\n const [state, setState] = useState<AssignmentState>(initial);\n\n // Helper: call onAssignment at most once per resolved variant.\n const reportAssignment = (variantId: string): void => {\n if (!onAssignment) return;\n if (assignmentReportedRef.current === variantId) return;\n assignmentReportedRef.current = variantId;\n onAssignment(componentId, variantId);\n };\n\n // As soon as the client is ready, unblock the UI immediately with variantIds[0]\n // (or a cached value) so the component never stays invisible while assign is\n // in-flight. The async assign call below then swaps to the bandit-chosen variant.\n useEffect(() => {\n if (overrideVariant) return;\n if (!client) return;\n const cached = client.getAssignment(componentId, segment);\n if (cached && (variantIds.includes(cached.variantId) || cached.content)) {\n setState({ variantId: cached.variantId, content: cached.content ?? null, isLoading: false });\n reportAssignment(cached.variantId);\n return;\n }\n setState((prev) => prev.variantId ? prev : { variantId: variantIds[0] ?? null, content: null, isLoading: false });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [overrideVariant, client, componentId, segment]);\n\n // Ask the server for a real assignment when we have a client but no cached one.\n useEffect(() => {\n if (overrideVariant) return;\n if (!client) return;\n const cached = client.getAssignment(componentId, segment);\n if (cached && variantIds.includes(cached.variantId)) return;\n\n let cancelled = false;\n void client.assign(componentId, variantIds).then((result: AssignResult | null) => {\n if (cancelled) return;\n if (!result) return;\n // Allow the result if it's a known code variant OR a managed text variant (has content).\n if (!variantIds.includes(result.variantId) && !result.content) return;\n setState({ variantId: result.variantId, content: result.content ?? null, isLoading: false });\n reportAssignment(result.variantId);\n });\n return () => { cancelled = true; };\n // variantIds intentionally excluded — changing variants mid-mount is unsupported\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [overrideVariant, client, componentId, segment]);\n\n // Live weight updates from the dashboard SSE stream (when wired).\n useEffect(() => {\n if (overrideVariant) return;\n if (!client) return;\n return subscribe(componentId, (weights) => {\n const cached = client.getAssignment(componentId, segment);\n if (cached && (variantIds.includes(cached.variantId) || cached.content)) {\n setState({ variantId: cached.variantId, content: cached.content ?? null, isLoading: false });\n return;\n }\n const chosen = pickFromWeights(weights, variantIds);\n if (chosen) setState({ variantId: chosen, content: null, isLoading: false });\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [overrideVariant, client, componentId, segment]);\n\n if (overrideVariant) {\n return { variantId: overrideVariant, content: null, isLoading: false };\n }\n\n return state;\n}\n","/** Per-component weights store with isolated subscriptions. */\n\nexport type VariantWeight = {\n variantId: string;\n pulls: number;\n avgReward: number;\n};\n\nexport type ComponentWeights = {\n componentId: string;\n variants: VariantWeight[];\n updatedAt: number;\n};\n\ntype Listener = (weights: ComponentWeights) => void;\n\nconst store = new Map<string, ComponentWeights>();\nconst listeners = new Map<string, Set<Listener>>();\n\n/**\n * Subscribes a listener to a single component. Returns an unsubscribe function.\n * Updates to other components never trigger this listener.\n */\nexport function subscribe(componentId: string, cb: Listener): () => void {\n let set = listeners.get(componentId);\n if (!set) {\n set = new Set();\n listeners.set(componentId, set);\n }\n set.add(cb);\n return () => {\n set!.delete(cb);\n if (set!.size === 0) listeners.delete(componentId);\n };\n}\n\n/**\n * Replaces the weights for a component and notifies only that component's\n * subscribers.\n */\nexport function update(componentId: string, weights: ComponentWeights): void {\n store.set(componentId, weights);\n const set = listeners.get(componentId);\n if (!set) return;\n for (const cb of set) {\n try {\n cb(weights);\n } catch {\n /* never throw to other listeners */\n }\n }\n}\n\n/**\n * Returns the latest known weights for a component, or null if none seen.\n */\nexport function getWeights(componentId: string): ComponentWeights | null {\n return store.get(componentId) ?? null;\n}\n\n/** Test-only: wipe the entire store. */\nexport function _resetWeightsStore(): void {\n store.clear();\n listeners.clear();\n}\n","'use client';\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { AssignResult } from '@sentientui/core';\nimport { useSentient, useAdaptiveApiKey, useOnAssignment } from './provider.js';\n\nexport type AdaptiveTextProps = {\n id: string;\n default: string;\n component?: keyof JSX.IntrinsicElements;\n className?: string;\n};\n\nexport function AdaptiveText({\n id,\n default: defaultText,\n component: Tag = 'span',\n className,\n}: AdaptiveTextProps) {\n const client = useSentient();\n const apiKey = useAdaptiveApiKey();\n const onAssignment = useOnAssignment();\n const [text, setText] = useState<string | null>(null);\n const [variantId, setVariantId] = useState<string | null>(null);\n const trackedRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!client) return;\n let cancelled = false;\n void client.assign(id).then((result: AssignResult | null) => {\n if (cancelled || !result) return;\n setVariantId(result.variantId);\n if (result.content) setText(result.content);\n });\n return () => {\n cancelled = true;\n };\n }, [client, id]);\n\n useEffect(() => {\n if (!client || !variantId || !apiKey) return;\n if (trackedRef.current === variantId) return;\n trackedRef.current = variantId;\n client.track({\n projectId: apiKey,\n componentId: id,\n variantId,\n eventType: 'variant_assigned',\n payload: {},\n });\n onAssignment?.(id, variantId);\n }, [client, variantId, apiKey, id, onAssignment]);\n\n return <Tag className={className}>{text ?? defaultText}</Tag>;\n}\n","/** @deprecated Use `useSessionSegment()` from the provider, or `deriveSessionSegment` from `@sentientui/core`. */\nexport { deriveSessionSegment as detectSegment } from '@sentientui/core';\n"],"mappings":"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","names":["useEffect","useRef","useState","useEffect","useState","useState","useEffect","_a","jsx","useRef","useState","useEffect","_a","_b","useEffect","useRef","useState","jsx","useState","useRef","useEffect"]}
|
|
@@ -37,6 +37,12 @@ type AdaptiveProviderProps = {
|
|
|
37
37
|
* (Mixpanel, PostHog, Segment, etc.) without having to wrap `useAssignment`.
|
|
38
38
|
*/
|
|
39
39
|
onAssignment?: (componentId: string, variantId: string) => void;
|
|
40
|
+
/**
|
|
41
|
+
* SSR-preloaded section order from `loadAdaptiveDecision()`.
|
|
42
|
+
* Pass the `layoutOrder` field from `DecideResult`. When set,
|
|
43
|
+
* `useLayoutOrder()` returns this on first render so there is no layout shift.
|
|
44
|
+
*/
|
|
45
|
+
initialLayoutOrder?: string[] | null;
|
|
40
46
|
children: ReactNode;
|
|
41
47
|
};
|
|
42
48
|
|
|
@@ -23,14 +23,15 @@ import { init } from "@sentientui/core";
|
|
|
23
23
|
import { jsx } from "react/jsx-runtime";
|
|
24
24
|
var AdaptiveContext = createContext({
|
|
25
25
|
client: null,
|
|
26
|
-
|
|
26
|
+
apiKey: "",
|
|
27
27
|
initialAssignments: {},
|
|
28
28
|
sessionSegment: "desktop:direct",
|
|
29
29
|
ssrFallback: "first",
|
|
30
|
-
onAssignment: void 0
|
|
30
|
+
onAssignment: void 0,
|
|
31
|
+
initialLayoutOrder: null
|
|
31
32
|
});
|
|
32
33
|
function AdaptiveProvider(props) {
|
|
33
|
-
var _a, _b, _c;
|
|
34
|
+
var _a, _b, _c, _d;
|
|
34
35
|
const [client, setClient] = useState(null);
|
|
35
36
|
useEffect(() => {
|
|
36
37
|
if (props.consent === false) {
|
|
@@ -59,11 +60,12 @@ function AdaptiveProvider(props) {
|
|
|
59
60
|
{
|
|
60
61
|
value: {
|
|
61
62
|
client,
|
|
62
|
-
|
|
63
|
+
apiKey: props.apiKey,
|
|
63
64
|
initialAssignments: (_b = props.initialAssignments) != null ? _b : {},
|
|
64
65
|
sessionSegment: (_c = props.sessionSegment) != null ? _c : "desktop:direct",
|
|
65
66
|
ssrFallback,
|
|
66
|
-
onAssignment: props.onAssignment
|
|
67
|
+
onAssignment: props.onAssignment,
|
|
68
|
+
initialLayoutOrder: (_d = props.initialLayoutOrder) != null ? _d : null
|
|
67
69
|
},
|
|
68
70
|
children: props.children
|
|
69
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/provider.tsx","../../src/next/adaptive-root-client.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\nimport { init, type SentientClient, type SentientConfig } from '@sentientui/core';\n\n/** How to render adaptive slots during SSR when assignments are not preloaded. */\nexport type SsrFallback = 'first' | 'none';\n\ntype AdaptiveContextValue = {\n client: SentientClient | null;\n projectId: string;\n initialAssignments: Record<string, string>;\n sessionSegment: string;\n ssrFallback: SsrFallback;\n onAssignment: ((componentId: string, variantId: string) => void) | undefined;\n};\n\nconst AdaptiveContext = createContext<AdaptiveContextValue>({\n client: null,\n
|
|
1
|
+
{"version":3,"sources":["../../src/provider.tsx","../../src/next/adaptive-root-client.tsx"],"sourcesContent":["'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\nimport { init, type SentientClient, type SentientConfig } from '@sentientui/core';\n\n/** How to render adaptive slots during SSR when assignments are not preloaded. */\nexport type SsrFallback = 'first' | 'none';\n\ntype AdaptiveContextValue = {\n client: SentientClient | null;\n // The publishable API key (pk_…). Historically named `projectId` because the\n // API uses it as the project identifier on the wire, but it is the API key,\n // not the project UUID. Field renamed for clarity.\n apiKey: string;\n initialAssignments: Record<string, string>;\n sessionSegment: string;\n ssrFallback: SsrFallback;\n onAssignment: ((componentId: string, variantId: string) => void) | undefined;\n initialLayoutOrder: string[] | null;\n};\n\nconst AdaptiveContext = createContext<AdaptiveContextValue>({\n client: null,\n apiKey: '',\n initialAssignments: {},\n sessionSegment: 'desktop:direct',\n ssrFallback: 'first',\n onAssignment: undefined,\n initialLayoutOrder: null,\n});\n\nexport type AdaptiveProviderProps = {\n apiKey: string;\n context: SentientConfig['context'];\n debug?: boolean;\n /**\n * SSR-preloaded assignments from `preloadAssignments()` / `loadAdaptiveAssignments()`.\n * Passed through to `useAssignment` as synchronous initial state so crawlers and\n * the first paint see real content (recommended for SEO).\n */\n initialAssignments?: Record<string, string>;\n /**\n * Bandit segment from SSR (`device:source`). Keeps cache, assign, and worker\n * weights on one row — must match `loadAdaptiveAssignments` / session upsert.\n */\n sessionSegment?: string;\n /**\n * When no `initialAssignments` exist for a component, `'first'` renders\n * `variantIds[0]` in server HTML (safe default for SEO). Use `'none'` only for\n * decorative slots marked `clientOnly`.\n * @default 'first'\n */\n ssrFallback?: SsrFallback;\n /**\n * Consent gate. When `false` the SDK is not initialised and no events are\n * sent. Flip to `true` (e.g. after the user accepts the cookie banner) to\n * initialise and begin tracking.\n */\n consent?: boolean;\n /**\n * Called once per component the first time a variant is resolved for that\n * component in this session. Use to forward assignments to your own analytics\n * (Mixpanel, PostHog, Segment, etc.) without having to wrap `useAssignment`.\n */\n onAssignment?: (componentId: string, variantId: string) => void;\n /**\n * SSR-preloaded section order from `loadAdaptiveDecision()`.\n * Pass the `layoutOrder` field from `DecideResult`. When set,\n * `useLayoutOrder()` returns this on first render so there is no layout shift.\n */\n initialLayoutOrder?: string[] | null;\n children: ReactNode;\n};\n\n/**\n * Initialises the Sentient core SDK in a useEffect (SSR-safe) and exposes the\n * client via React context. Re-initialises when `consent` changes.\n */\nexport function AdaptiveProvider(props: AdaptiveProviderProps): JSX.Element {\n const [client, setClient] = useState<SentientClient | null>(null);\n\n useEffect(() => {\n // When consent is explicitly false, tear down any existing client.\n if (props.consent === false) {\n setClient((prev: SentientClient | null) => {\n prev?.destroy();\n return null;\n });\n return;\n }\n\n const c = init({\n apiKey: props.apiKey,\n context: props.context,\n debug: props.debug,\n initialAssignments: props.initialAssignments,\n sessionSegment: props.sessionSegment,\n consent: props.consent,\n });\n setClient(c);\n return () => {\n c.destroy();\n };\n // Re-init when consent changes. Other props are intentionally stable.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.consent]);\n\n const ssrFallback = props.ssrFallback ?? 'first';\n\n return (\n <AdaptiveContext.Provider\n value={{\n client,\n apiKey: props.apiKey,\n initialAssignments: props.initialAssignments ?? {},\n sessionSegment: props.sessionSegment ?? 'desktop:direct',\n ssrFallback,\n onAssignment: props.onAssignment,\n initialLayoutOrder: props.initialLayoutOrder ?? null,\n }}\n >\n {props.children}\n </AdaptiveContext.Provider>\n );\n}\n\n/**\n * Returns the SentientClient, or null until the provider has finished\n * initialising on the client.\n */\nexport function useSentient(): SentientClient | null {\n return useContext(AdaptiveContext).client;\n}\n\n/** Internal: publishable API key carried alongside the client. */\nexport function useAdaptiveApiKey(): string {\n return useContext(AdaptiveContext).apiKey;\n}\n\n/**\n * @deprecated Renamed to `useAdaptiveApiKey`. Kept as an alias so external\n * imports from earlier versions of this package keep working. Will be removed\n * in 0.4.0.\n */\nlet warnedProjectIdAlias = false;\nexport function useAdaptiveProjectId(): string {\n if (\n !warnedProjectIdAlias &&\n typeof process !== 'undefined' &&\n process.env?.NODE_ENV !== 'production'\n ) {\n warnedProjectIdAlias = true;\n // eslint-disable-next-line no-console\n console.warn(\n '[@sentientui/react] useAdaptiveProjectId is deprecated; use useAdaptiveApiKey. Will be removed in 0.4.0.',\n );\n }\n return useAdaptiveApiKey();\n}\n\n/** Internal: SSR-preloaded assignments for hydration-safe first render. */\nexport function useInitialAssignments(): Record<string, string> {\n return useContext(AdaptiveContext).initialAssignments;\n}\n\n/** Internal: bandit segment aligned with SSR session upsert. */\nexport function useSessionSegment(): string {\n return useContext(AdaptiveContext).sessionSegment;\n}\n\n/** Internal: SSR fallback strategy when a slot has no preloaded assignment. */\nexport function useSsrFallback(): SsrFallback {\n return useContext(AdaptiveContext).ssrFallback;\n}\n\n/** Internal: forwarding hook for consumer analytics integration. */\nexport function useOnAssignment(): ((componentId: string, variantId: string) => void) | undefined {\n return useContext(AdaptiveContext).onAssignment;\n}\n\n/**\n * Returns the persona-specific section order from SSR, or null when no\n * sections were declared on AdaptiveRoot or reliability is below threshold.\n */\nexport function useLayoutOrder(): string[] | null {\n return useContext(AdaptiveContext).initialLayoutOrder;\n}\n","'use client';\n\nimport type { ReactNode } from 'react';\nimport { AdaptiveProvider, type AdaptiveProviderProps } from '../provider.js';\n\nexport type AdaptiveRootClientProps = AdaptiveProviderProps & {\n children: ReactNode;\n};\n\n/** Client boundary for {@link AdaptiveRoot} — keeps context/hooks out of the server bundle. */\nexport function AdaptiveRootClient(props: AdaptiveRootClientProps): JSX.Element {\n return <AdaptiveProvider {...props} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAIA,SAAS,eAAe,YAAY,WAAW,gBAAgC;AAC/E,SAAS,YAAsD;AA2G3D;AAzFJ,IAAM,kBAAkB,cAAoC;AAAA,EAC1D,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,oBAAoB,CAAC;AAAA,EACrB,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,oBAAoB;AACtB,CAAC;AAiDM,SAAS,iBAAiB,OAA2C;AAhF5E;AAiFE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAgC,IAAI;AAEhE,YAAU,MAAM;AAEd,QAAI,MAAM,YAAY,OAAO;AAC3B,gBAAU,CAAC,SAAgC;AACzC,qCAAM;AACN,eAAO;AAAA,MACT,CAAC;AACD;AAAA,IACF;AAEA,UAAM,IAAI,KAAK;AAAA,MACb,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,MACb,oBAAoB,MAAM;AAAA,MAC1B,gBAAgB,MAAM;AAAA,MACtB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,cAAU,CAAC;AACX,WAAO,MAAM;AACX,QAAE,QAAQ;AAAA,IACZ;AAAA,EAGF,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,QAAM,eAAc,WAAM,gBAAN,YAAqB;AAEzC,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,OAAO;AAAA,QACL;AAAA,QACA,QAAQ,MAAM;AAAA,QACd,qBAAoB,WAAM,uBAAN,YAA4B,CAAC;AAAA,QACjD,iBAAgB,WAAM,mBAAN,YAAwB;AAAA,QACxC;AAAA,QACA,cAAc,MAAM;AAAA,QACpB,qBAAoB,WAAM,uBAAN,YAA4B;AAAA,MAClD;AAAA,MAEC,gBAAM;AAAA;AAAA,EACT;AAEJ;;;ACnHS,gBAAAA,YAAA;AADF,SAAS,mBAAmB,OAA6C;AAC9E,SAAO,gBAAAA,KAAC,qCAAqB,MAAO;AACtC;","names":["jsx"]}
|