@sentientui/react 0.3.1 → 0.4.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/README.md CHANGED
@@ -1,30 +1,65 @@
1
1
  # @sentientui/react
2
2
 
3
- React SDK for [SentientUI](https://sentientui.dev) — drop-in components and hooks that let a contextual bandit automatically serve the best-performing UI variant for each visitor.
3
+ React SDK for [SentientUI](https://sentient-ui.com) — drop-in components and hooks that let a Thompson Sampling bandit, persona clusters, and visitor portraits automatically serve the best-performing UI variant (and section order) for each visitor.
4
+
5
+ Learning, assignment, portraits, clustering, and graph storage all run on the SentientUI hosted API. You only install the SDK and add API keys from the dashboard.
4
6
 
5
7
  ## Installation
6
8
 
7
9
  ```bash
8
- npm install @sentientui/react @sentientui/core
10
+ npm install @sentientui/react
11
+ # or
12
+ pnpm add @sentientui/react
13
+ ```
14
+
15
+ `@sentientui/core` is a transitive dependency — you do not need to install it directly. Only import from `@sentientui/react`.
16
+
17
+ ## API key
18
+
19
+ Create a project at [app.sentient-ui.com](https://app.sentient-ui.com) and copy the API key shown once at project creation. It looks like `pk_xxxxxxxx…`. The key is safe to ship in browser bundles — the API enforces an allowed-origins allowlist on every request. Add your production domain in Project → Settings → Allowed origins.
20
+
21
+ For Next.js, set the same key in two env vars (one for the server, one for the browser):
22
+
23
+ ```bash
24
+ # .env.local
25
+ SENTIENT_API_KEY=pk_your_key
26
+ NEXT_PUBLIC_SENTIENT_API_KEY=pk_your_key
9
27
  ```
10
28
 
11
- ## Quick start
29
+ The SDK points at `https://sentient-api.fly.dev` (the hosted API) automatically — no ingest URL configuration required.
12
30
 
13
- ### 1. Wrap your app with `AdaptiveProvider`
31
+ ## Quick start Next.js App Router (recommended)
32
+
33
+ Wrap your root layout with `<AdaptiveRoot>`. It is a Server Component: variant assignments and persona-specific layout order are resolved server-side before HTML is sent, so the first paint has the right content with no layout shift.
14
34
 
15
35
  ```tsx
16
- import { AdaptiveProvider } from '@sentientui/react';
36
+ // app/layout.tsx
37
+ import { AdaptiveRoot } from '@sentientui/react/next';
17
38
 
18
- export default function App({ children }) {
39
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
19
40
  return (
20
- <AdaptiveProvider apiKey="pk_your_key" context="saas">
21
- {children}
22
- </AdaptiveProvider>
41
+ <html lang="en">
42
+ <body>
43
+ <AdaptiveRoot
44
+ components={[
45
+ { id: 'hero_cta', variantIds: ['control', 'variant_a'] },
46
+ { id: 'pricing', variantIds: ['monthly', 'annual_first'] },
47
+ ]}
48
+ sections={['hero', 'pricing', 'features', 'social_proof']}
49
+ serverApiKey={process.env.SENTIENT_API_KEY!}
50
+ apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}
51
+ appOrigin={process.env.NEXT_PUBLIC_APP_URL!}
52
+ context="saas"
53
+ >
54
+ {children}
55
+ </AdaptiveRoot>
56
+ </body>
57
+ </html>
23
58
  );
24
59
  }
25
60
  ```
26
61
 
27
- ### 2. Add an `<Adaptive>` component
62
+ Then drop `<Adaptive>` anywhere you want a tested component:
28
63
 
29
64
  ```tsx
30
65
  import { Adaptive } from '@sentientui/react';
@@ -32,118 +67,261 @@ import { Adaptive } from '@sentientui/react';
32
67
  export default function Hero() {
33
68
  return (
34
69
  <Adaptive
35
- id="hero-headline"
36
- goal="click"
70
+ id="hero_cta"
71
+ goal="signup_click"
37
72
  variants={{
38
- control: <h1>Ship faster with SentientUI</h1>,
39
- variant_b: <h1>Your UI, optimised automatically</h1>,
73
+ control: <button className="btn-dark">Start free trial</button>,
74
+ variant_a: <button className="btn-blue">Get instant access →</button>,
40
75
  }}
41
76
  />
42
77
  );
43
78
  }
44
79
  ```
45
80
 
46
- The bandit tracks which variant drives more clicks and gradually shifts traffic toward the winner.
47
-
48
- ## Next.js (App Router)
81
+ ## Quick start other React apps (Vite, CRA, Remix, Pages Router)
49
82
 
50
- Use `AdaptiveRoot` in your layout for SEO-safe server-rendered assignments with no variant flash:
83
+ Use `<AdaptiveProvider>` directly. Assignments are made on the client after mount — no SSR preloading, but the bandit still learns normally.
51
84
 
52
85
  ```tsx
53
- // app/layout.tsx
54
- import { AdaptiveRoot } from '@sentientui/react/next';
86
+ import { AdaptiveProvider } from '@sentientui/react';
55
87
 
56
- export default function Layout({ children }) {
88
+ export default function App({ children }: { children: React.ReactNode }) {
57
89
  return (
58
- <AdaptiveRoot
59
- components={[
60
- { id: 'hero-headline', variantIds: ['control', 'variant_b'] },
61
- { id: 'pricing-cta', variantIds: ['control', 'variant_b'] },
62
- ]}
63
- serverApiKey={process.env.SENTIENT_API_KEY!}
64
- apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}
90
+ <AdaptiveProvider
91
+ apiKey={import.meta.env.VITE_SENTIENT_API_KEY}
65
92
  context="saas"
66
93
  >
67
94
  {children}
68
- </AdaptiveRoot>
95
+ </AdaptiveProvider>
69
96
  );
70
97
  }
71
98
  ```
72
99
 
73
- `AdaptiveRoot` is a Server Component that fetches assignments before HTML is sent, then passes them to `AdaptiveProvider` as `initialAssignments` so crawlers and hydration see real content.
74
-
75
100
  ## API
76
101
 
77
- ### `<AdaptiveProvider>`
102
+ ### `<AdaptiveRoot>` (Next.js App Router — server component)
103
+
104
+ Imported from `@sentientui/react/next`.
78
105
 
79
106
  | Prop | Type | Description |
80
107
  |------|------|-------------|
81
- | `apiKey` | `string` | Your public API key (`pk_…`) |
82
- | `context` | `string` | Site context `'landing'`, `'ecommerce'`, `'saas'`, or `'marketplace'` |
83
- | `initialAssignments` | `Record<string, string>` | SSR-preloaded assignments (prevents hydration mismatch) |
84
- | `sessionSegment` | `string` | Segment string from SSR (`device:source`) |
85
- | `ssrFallback` | `'first' \| 'none'` | How to render adaptive slots without preloaded assignments. `'first'` renders the first variant (good for SEO). Defaults to `'first'` |
86
- | `consent` | `boolean` | Set to `false` to disable tracking before cookie consent |
87
- | `onAssignment` | `(componentId, variantId) => void` | Called once per component per session forward to Mixpanel, PostHog, Segment, etc. |
88
- | `debug` | `boolean` | Logs events to the console |
108
+ | `components` | `Array<{ id: string; variantIds: string[] }>` | Components to preload server-side. `id` must match `<Adaptive id="…">`. |
109
+ | `sections` | `string[]` *(optional)* | Page section IDs in default order. When provided, a single `POST /v1/decide` returns both layout order and assignments; `useLayoutOrder()` becomes available. |
110
+ | `serverApiKey` | `string` | Same `pk_…` key as `apiKey`, but read from the non-public env var so it never reaches the client bundle. |
111
+ | `apiKey` | `string` | `pk_…` key used by the browser SDK for event tracking and cache hydration. |
112
+ | `appOrigin` | `string` *(default `http://localhost:3001`)* | Your app origin (e.g. `https://yourapp.com`). Must be on the project's allowed-origins list. Always set in production. |
113
+ | `context` | `'landing' \| 'ecommerce' \| 'saas' \| 'marketplace'` | Type of product. Used for segment weighting and analytics grouping. |
114
+ | `consent` | `boolean` *(default `true`)* | Set `false` to skip SDK init (no cookies, no events). Flip to `true` after the visitor accepts. |
115
+ | `ssrFallback` | `'first' \| 'none'` *(default `'first'`)* | What to render in SSR HTML for components not in `components`. `'first'` is safe for SEO. |
116
+ | `onAssignment` | `(componentId, variantId) => void` | Called once per component the first time a variant resolves. Forward to Mixpanel/PostHog/Segment/GA4. |
117
+ | `timeoutMs` | `number` *(default `1500`)* | Server-side fetch timeout before falling back to the first variant. |
118
+ | `debug` | `boolean` | Log assignment and event activity to the console. |
119
+
120
+ ### `<AdaptiveProvider>` (any React app)
121
+
122
+ Accepts the same `apiKey`, `context`, `consent`, `ssrFallback`, `onAssignment`, `debug` props as `<AdaptiveRoot>`, plus:
123
+
124
+ | Prop | Type | Description |
125
+ |------|------|-------------|
126
+ | `initialAssignments` | `Record<string, string>` | SSR-preloaded assignments (e.g. from `loadAdaptiveAssignments` in `getServerSideProps`). |
127
+ | `sessionSegment` | `string` | Segment from SSR (`device:source`). Must match the value used in `loadAdaptiveAssignments`. |
128
+ | `initialLayoutOrder` | `string[] \| null` | Preloaded section order from `loadAdaptiveDecision` (for Pages-Router-style SSR with sections). |
89
129
 
90
130
  ### `<Adaptive>`
91
131
 
92
132
  | Prop | Type | Description |
93
133
  |------|------|-------------|
94
- | `id` | `string` | Unique component identifier |
95
- | `variants` | `Record<string, ReactNode>` | Map of variant ID → content |
96
- | `goal` | `string \| GoalConfig` | Conversion goal. Pass a string label for click tracking, or a `GoalConfig` object for scroll depth, form submit, or composite goals |
97
- | `clientOnly` | `boolean` | Skip SSR rendering use for decorative slots where a blank placeholder is preferable to a hydration mismatch |
134
+ | `id` | `string` | Unique component identifier within your project. |
135
+ | `variants` | `Record<string, ReactNode>` | Map of variant ID → content. Any two or more keys; the bandit explores them all. |
136
+ | `goal` | `string \| GoalConfig` | Conversion goal. A string is a click-goal label; an object is an explicit `GoalConfig`. |
137
+ | `clientOnly` | `boolean` | Render nothing on the server; resolve on the client only. Use for cookie-dependent slots. |
98
138
 
99
139
  #### Goal types
100
140
 
101
141
  ```ts
102
- // Click on anything inside the variant
103
- goal="click"
142
+ // Any click inside the variant (button, a, role=button). The string is the analytics label.
143
+ goal="signup_click"
104
144
 
105
- // Click on a specific element
145
+ // Click only on elements matching a CSS selector inside the variant.
106
146
  goal={{ type: 'click', selector: 'button.cta' }}
107
147
 
108
- // User scrolls past 80% of the page
109
- goal={{ type: 'scroll_depth', threshold: 80 }}
148
+ // 80% of the component visible in the viewport (IntersectionObserver, 0–1 scale).
149
+ goal={{ type: 'scroll_depth', threshold: 0.8 }}
110
150
 
111
- // A form inside the variant is submitted
151
+ // A <form> inside the variant fires submit.
112
152
  goal={{ type: 'form_submit' }}
113
153
 
114
- // Multiple goals must all fire
115
- goal={{ type: 'composite', all: [{ type: 'scroll_depth', threshold: 50 }, { type: 'click' }] }}
154
+ // Composite — all sub-goals must fire (in any order) before the reward is recorded.
155
+ goal={{
156
+ type: 'composite',
157
+ all: [
158
+ { type: 'scroll_depth', threshold: 0.8 },
159
+ { type: 'click' },
160
+ ],
161
+ }}
162
+ ```
163
+
164
+ Each goal fires at most once per variant mount.
165
+
166
+ ### `<AdaptiveText>`
167
+
168
+ Lightweight text-only variant (no wrapper element, no automatic goal wiring). Useful when you publish text variants from the dashboard WYSIWYG.
169
+
170
+ ```tsx
171
+ import { AdaptiveText } from '@sentientui/react';
172
+
173
+ <h1>
174
+ <AdaptiveText id="hero_headline" default="Ship faster with SentientUI" />
175
+ </h1>
116
176
  ```
117
177
 
118
- ### `useAssignment(componentId, variantIds)`
178
+ ### `useAssignment(componentId, variantIds?)`
119
179
 
120
- Lower-level hook for manual variant resolution when you need full control over rendering.
180
+ Lower-level hook when you need the variant ID inside your own render logic (e.g. full-page layout tests where `<Adaptive>`'s wrapper `<div>` would break a flex/grid layout).
121
181
 
122
182
  ```tsx
123
- import { useAssignment } from '@sentientui/react';
183
+ import { useAssignment, useSentient } from '@sentientui/react';
124
184
 
125
- function MyComponent() {
126
- const { variantId, isLoading } = useAssignment('hero', ['control', 'variant_b']);
185
+ function Hero() {
186
+ const client = useSentient();
187
+ const { variantId, isLoading } = useAssignment('hero_cta', ['control', 'variant_a']);
127
188
 
128
189
  if (isLoading) return <Skeleton />;
129
- return variantId === 'variant_b' ? <NewHero /> : <DefaultHero />;
190
+ return variantId === 'variant_a' ? <AccentHero /> : <DefaultHero />;
130
191
  }
131
192
  ```
132
193
 
133
- ### `onAssignment` — forwarding to analytics
194
+ When using `useAssignment` you are responsible for firing the goal `<Adaptive>`'s automatic impression and goal tracking does not apply. Use `client?.track(…)` or `client?.goal(name)` from `useSentient()`.
195
+
196
+ ### `useLayoutOrder()`
197
+
198
+ Returns the resolved persona-specific section order, or `null` when not configured / below confidence threshold. Always fall back to your default order.
199
+
200
+ ```tsx
201
+ import { useLayoutOrder } from '@sentientui/react';
202
+
203
+ function Page() {
204
+ const order = useLayoutOrder();
205
+ const defaultOrder = ['hero', 'pricing', 'features', 'social_proof'];
206
+ const sections: Record<string, React.ReactNode> = {
207
+ hero: <Hero />, pricing: <Pricing />, features: <Features />, social_proof: <SocialProof />,
208
+ };
209
+ return <main>{(order ?? defaultOrder).map((id) => <Fragment key={id}>{sections[id]}</Fragment>)}</main>;
210
+ }
211
+ ```
212
+
213
+ ### SSR for Pages Router / custom SSR
214
+
215
+ ```ts
216
+ import { loadAdaptiveAssignments } from '@sentientui/react/server';
217
+
218
+ export async function getServerSideProps({ req }) {
219
+ const assignments = await loadAdaptiveAssignments(
220
+ [{ id: 'hero_cta', variantIds: ['control', 'variant_a'] }],
221
+ {
222
+ cookies: req.cookies,
223
+ apiKey: process.env.SENTIENT_API_KEY!,
224
+ baseUrl: 'https://sentient-api.fly.dev/v1',
225
+ origin: process.env.NEXT_PUBLIC_APP_URL,
226
+ },
227
+ );
228
+ return { props: { initialAssignments: assignments } };
229
+ }
230
+ ```
231
+
232
+ Pass the result as `initialAssignments` on `<AdaptiveProvider>` in `_app.tsx`. For pages with a `sections` layout, use `loadAdaptiveDecision` (same options shape, plus `sections: string[]`) — its return value carries both `assignments` and `layoutOrder`.
233
+
234
+ ### Forwarding to your own analytics
134
235
 
135
236
  ```tsx
136
237
  <AdaptiveProvider
137
- apiKey="pk_…"
238
+ apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}
138
239
  context="saas"
139
240
  onAssignment={(componentId, variantId) => {
140
- posthog.capture('$experiment_started', {
141
- experiment_name: componentId,
142
- variant: variantId,
143
- });
241
+ posthog.capture('$feature_flag_called', { $feature_flag: componentId, $feature_flag_response: variantId });
242
+ mixpanel.register({ [`variant_${componentId}`]: variantId });
144
243
  }}
145
244
  >
245
+ {children}
246
+ </AdaptiveProvider>
247
+ ```
248
+
249
+ Fires at most once per component ID per page load. Full Segment / GA4 / Mixpanel recipes: see [sentient-ui.com/docs/integrations](https://sentient-ui.com/docs/integrations).
250
+
251
+ ### Local overrides (development)
252
+
253
+ Force a variant without touching the bandit:
254
+
146
255
  ```
256
+ # URL parameter (stackable)
257
+ https://yourapp.com?sentient_variant=hero_cta:variant_a&sentient_variant=pricing:annual_first
258
+
259
+ # Or, before SDK init:
260
+ window.__sentient_overrides = { hero_cta: 'variant_a' };
261
+ ```
262
+
263
+ Overrides bypass the bandit entirely — no events recorded, weights unchanged.
264
+
265
+ ## Consent management (GDPR)
266
+
267
+ SentientUI does not track visitors until consent is granted. To serve the best-performing variant while the cookie banner is pending (rather than freezing the UI), use `preConsentBehavior: 'statistical_winner'`:
268
+
269
+ ```tsx
270
+ <AdaptiveProvider
271
+ apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}
272
+ context="saas"
273
+ consent={hasConsent}
274
+ preConsentBehavior="statistical_winner"
275
+ >
276
+ {children}
277
+ </AdaptiveProvider>
278
+ ```
279
+
280
+ Set `hasConsent` to `false` until the user accepts your cookie banner, then flip it to `true`. The provider re-initialises automatically and begins full tracking.
281
+
282
+ ### Without React
283
+
284
+ ```ts
285
+ import { init, grantConsent } from '@sentientui/core';
286
+
287
+ const client = init({
288
+ apiKey: 'pk_...',
289
+ context: 'saas',
290
+ consent: false,
291
+ preConsentBehavior: 'statistical_winner',
292
+ });
293
+
294
+ // After the user accepts the cookie banner:
295
+ grantConsent(); // upgrades client in place — no need to reassign
296
+ ```
297
+
298
+ ### OneTrust integration
299
+
300
+ ```ts
301
+ window.addEventListener('OneTrustGroupsUpdated', () => {
302
+ // C0002 = Analytics/Performance category in OneTrust
303
+ if (window.OnetrustActiveGroups?.includes('C0002')) {
304
+ grantConsent();
305
+ }
306
+ });
307
+ ```
308
+
309
+ ### Cookiebot integration
310
+
311
+ ```ts
312
+ window.addEventListener('CookiebotOnAccept', () => {
313
+ if (window.Cookiebot?.consent?.statistics) {
314
+ grantConsent();
315
+ }
316
+ });
317
+ ```
318
+
319
+ When `consent: false` without `preConsentBehavior`, the SDK is a complete no-op — no API calls, no cookies, nothing. The `preConsentBehavior: 'statistical_winner'` mode calls only `GET /v1/winner`, a read-only endpoint that returns the best variant without storing any visitor data.
320
+
321
+ ## Docs
322
+
323
+ Full SDK reference: [sentient-ui.com/docs](https://sentient-ui.com/docs).
324
+ Integrations: [sentient-ui.com/docs/integrations](https://sentient-ui.com/docs/integrations).
147
325
 
148
326
  ## License
149
327
 
package/dist/index.d.cts CHANGED
@@ -34,6 +34,13 @@ type AdaptiveProviderProps = {
34
34
  * initialise and begin tracking.
35
35
  */
36
36
  consent?: boolean;
37
+ /**
38
+ * Behavior before consent is granted. Pass `'statistical_winner'` to serve the
39
+ * best-performing variant via `GET /v1/winner` with zero tracking while the
40
+ * consent banner is showing. Requires `consent: false`.
41
+ * @see SentientConfig.preConsentBehavior
42
+ */
43
+ preConsentBehavior?: 'statistical_winner' | 'control';
37
44
  /**
38
45
  * Called once per component the first time a variant is resolved for that
39
46
  * component in this session. Use to forward assignments to your own analytics
@@ -92,6 +99,8 @@ type AdaptiveProps = {
92
99
  * a hydration mismatch. Tradeoff: minor CLS on first paint.
93
100
  */
94
101
  clientOnly?: boolean;
102
+ /** Structured data for AI agent consumption via GET /v1/agent/layout. Stored per assigned variant. */
103
+ agentData?: unknown;
95
104
  };
96
105
  declare function AdaptiveImpl(props: AdaptiveProps): JSX.Element | null;
97
106
  /** Re-renders only when the chosen variant changes. */
@@ -125,7 +134,7 @@ type AssignmentState = {
125
134
  * on the next render. Subsequent paints read synchronously from cache —
126
135
  * no flicker, no loading state after first paint.
127
136
  */
128
- declare function useAssignment(componentId: string, variantIds: string[]): AssignmentState;
137
+ declare function useAssignment(componentId: string, variantIds: string[], agentData?: unknown): AssignmentState;
129
138
 
130
139
  /** Per-component weights store with isolated subscriptions. */
131
140
  type VariantWeight = {
package/dist/index.d.ts CHANGED
@@ -34,6 +34,13 @@ type AdaptiveProviderProps = {
34
34
  * initialise and begin tracking.
35
35
  */
36
36
  consent?: boolean;
37
+ /**
38
+ * Behavior before consent is granted. Pass `'statistical_winner'` to serve the
39
+ * best-performing variant via `GET /v1/winner` with zero tracking while the
40
+ * consent banner is showing. Requires `consent: false`.
41
+ * @see SentientConfig.preConsentBehavior
42
+ */
43
+ preConsentBehavior?: 'statistical_winner' | 'control';
37
44
  /**
38
45
  * Called once per component the first time a variant is resolved for that
39
46
  * component in this session. Use to forward assignments to your own analytics
@@ -92,6 +99,8 @@ type AdaptiveProps = {
92
99
  * a hydration mismatch. Tradeoff: minor CLS on first paint.
93
100
  */
94
101
  clientOnly?: boolean;
102
+ /** Structured data for AI agent consumption via GET /v1/agent/layout. Stored per assigned variant. */
103
+ agentData?: unknown;
95
104
  };
96
105
  declare function AdaptiveImpl(props: AdaptiveProps): JSX.Element | null;
97
106
  /** Re-renders only when the chosen variant changes. */
@@ -125,7 +134,7 @@ type AssignmentState = {
125
134
  * on the next render. Subsequent paints read synchronously from cache —
126
135
  * no flicker, no loading state after first paint.
127
136
  */
128
- declare function useAssignment(componentId: string, variantIds: string[]): AssignmentState;
137
+ declare function useAssignment(componentId: string, variantIds: string[], agentData?: unknown): AssignmentState;
129
138
 
130
139
  /** Per-component weights store with isolated subscriptions. */
131
140
  type VariantWeight = {
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ function AdaptiveProvider(props) {
52
52
  var _a, _b, _c, _d;
53
53
  const [client, setClient] = (0, import_react.useState)(null);
54
54
  (0, import_react.useEffect)(() => {
55
- if (props.consent === false) {
55
+ if (props.consent === false && !props.preConsentBehavior) {
56
56
  setClient((prev) => {
57
57
  prev == null ? void 0 : prev.destroy();
58
58
  return null;
@@ -65,7 +65,8 @@ function AdaptiveProvider(props) {
65
65
  debug: props.debug,
66
66
  initialAssignments: props.initialAssignments,
67
67
  sessionSegment: props.sessionSegment,
68
- consent: props.consent
68
+ consent: props.consent,
69
+ preConsentBehavior: props.preConsentBehavior
69
70
  });
70
71
  setClient(c);
71
72
  return () => {
@@ -176,7 +177,7 @@ function pickFromWeights(weights, variantIds) {
176
177
  }
177
178
  return (_a = best == null ? void 0 : best.variantId) != null ? _a : null;
178
179
  }
179
- function useAssignment(componentId, variantIds) {
180
+ function useAssignment(componentId, variantIds, agentData) {
180
181
  const initialAssignments = useInitialAssignments();
181
182
  const ssrFallback = useSsrFallback();
182
183
  const client = useSentient();
@@ -242,7 +243,7 @@ function useAssignment(componentId, variantIds) {
242
243
  const cached = client.getAssignment(componentId, segment);
243
244
  if (cached && variantIds.includes(cached.variantId)) return;
244
245
  let cancelled = false;
245
- void client.assign(componentId, variantIds).then((result) => {
246
+ void client.assign(componentId, variantIds, agentData).then((result) => {
246
247
  var _a;
247
248
  if (cancelled) return;
248
249
  if (!result) return;
@@ -300,7 +301,7 @@ function AdaptiveImpl(props) {
300
301
  const client = useSentient();
301
302
  const apiKey = useAdaptiveApiKey();
302
303
  const variantIds = (0, import_react3.useMemo)(() => Object.keys(props.variants), [props.variants]);
303
- const { variantId, content } = useAssignment(props.id, variantIds);
304
+ const { variantId, content } = useAssignment(props.id, variantIds, props.agentData);
304
305
  const containerRef = (0, import_react3.useRef)(null);
305
306
  const [mounted, setMounted] = (0, import_react3.useState)(false);
306
307
  (0, import_react3.useEffect)(() => {