@sentientui/react 0.8.3 → 0.8.5
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 +20 -1
- package/dist/index.d.cts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/next/adaptive-root-client.d.ts +1 -61
- package/dist/next/adaptive-root-client.js +1 -1
- package/dist/next/adaptive-root-client.js.map +1 -1
- package/dist/next/adaptive-root.d.ts +6 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -199,7 +199,26 @@ function Hero() {
|
|
|
199
199
|
}
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
-
When using `useAssignment` you are responsible for firing the goal — `<Adaptive>`'s automatic impression and goal tracking does not apply. Use `
|
|
202
|
+
When using `useAssignment` you are responsible for firing the goal — `<Adaptive>`'s automatic impression and goal tracking does not apply. Use [`useAdaptiveGoal`](#useadaptivegoalcomponentid) (recommended) or `client?.goal(name)` from `useSentient()`.
|
|
203
|
+
|
|
204
|
+
### `useAdaptiveGoal(componentId)`
|
|
205
|
+
|
|
206
|
+
Returns a `fireGoal(goalType, opts?)` callback that records a conversion **attributed to the variant currently served** for `componentId` — so it shows up in the per-variant CVR funnel with **no manual `variantId`/`projectId` plumbing**. Use it for imperative handlers (click, form submit, custom events) when you're not using the declarative `<Adaptive goal={…}>` prop.
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
import { useAdaptiveGoal } from '@sentientui/react';
|
|
210
|
+
|
|
211
|
+
function HeroContact({ method }: { method: string }) {
|
|
212
|
+
const fireContact = useAdaptiveGoal('hero_headline');
|
|
213
|
+
return (
|
|
214
|
+
<a href="tel:+1..." onClick={() => fireContact('hero_contact', { metadata: { method } })}>
|
|
215
|
+
Call us
|
|
216
|
+
</a>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The served variant is resolved from the SDK's assignment cache (the same one `<Adaptive id="hero_headline">` populates), so render that component before firing. `opts` accepts `reward` (0–1, default 1) and `metadata`. This replaces hand-rolled helpers that call `client.track({ eventType: 'goal_achieved', componentId, variantId, … })` — you no longer need to pass or track the variant yourself.
|
|
203
222
|
|
|
204
223
|
### `useLayoutOrder()`
|
|
205
224
|
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { SentientConfig, SentientClient, MicroSignalType } from '@sentientui/core';
|
|
3
|
+
import { SentientConfig, SentientClient, MicroSignalType, ComponentGoalOptions } from '@sentientui/core';
|
|
4
4
|
export { deriveSessionSegment as detectSegment } from '@sentientui/core';
|
|
5
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
6
6
|
|
|
@@ -61,6 +61,12 @@ type AdaptiveProviderProps = {
|
|
|
61
61
|
* used for variant assignment.
|
|
62
62
|
*/
|
|
63
63
|
ssrSessionId?: string;
|
|
64
|
+
/**
|
|
65
|
+
* ISO 3166-1 alpha-2 country code. Pass the value of the `CF-IPCountry`
|
|
66
|
+
* header from your Next.js server component to populate country on landing
|
|
67
|
+
* sessions without client-side geo lookup.
|
|
68
|
+
*/
|
|
69
|
+
country?: string;
|
|
64
70
|
children: ReactNode;
|
|
65
71
|
};
|
|
66
72
|
/**
|
|
@@ -173,6 +179,23 @@ type AssignmentState = {
|
|
|
173
179
|
*/
|
|
174
180
|
declare function useAssignment(componentId: string, variantIds: string[], agentData?: unknown, agentDataByVariant?: Record<string, unknown>): AssignmentState;
|
|
175
181
|
|
|
182
|
+
type FireGoal = (goalType: string, opts?: ComponentGoalOptions) => void;
|
|
183
|
+
/**
|
|
184
|
+
* Returns a `fireGoal(goalType, opts?)` callback that records a conversion
|
|
185
|
+
* attributed to the variant currently served for `componentId` — so it shows
|
|
186
|
+
* up in the per-variant CVR funnel with no manual variantId/projectId plumbing.
|
|
187
|
+
*
|
|
188
|
+
* The served variant is resolved from the SDK's assignment cache (the same one
|
|
189
|
+
* `<Adaptive id={componentId}>` populates), so render that component before
|
|
190
|
+
* firing. Use this for imperative handlers (click, form submit, custom events);
|
|
191
|
+
* for purely declarative goals prefer `<Adaptive goal={...}>`.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* const fireContact = useAdaptiveGoal('hero_headline');
|
|
195
|
+
* <button onClick={() => fireContact('hero_contact', { metadata: { method } })}>Call</button>
|
|
196
|
+
*/
|
|
197
|
+
declare function useAdaptiveGoal(componentId: string): FireGoal;
|
|
198
|
+
|
|
176
199
|
/** Per-component weights store with isolated subscriptions. */
|
|
177
200
|
type VariantWeight = {
|
|
178
201
|
variantId: string;
|
|
@@ -200,4 +223,4 @@ declare function update(componentId: string, weights: ComponentWeights): void;
|
|
|
200
223
|
*/
|
|
201
224
|
declare function getWeights(componentId: string): ComponentWeights | null;
|
|
202
225
|
|
|
203
|
-
export { Adaptive, type AdaptiveProps, AdaptiveProvider, type AdaptiveProviderProps, AdaptiveText, type AdaptiveTextProps, type AssignmentState, type ClickGoal, type ComponentWeights, type CompositeGoal, type FormSubmitGoal, type GoalConfig, type MicroSignalGoalConfig, type MicroSignalGoals, type ScrollDepthGoal, type SsrFallback, type VariantWeight, type WeightedCompositeGoal, type WeightedStep, getWeights, subscribe as subscribeWeights, update as updateWeights, useAssignment, useInitialAssignments, useLayoutOrder, useSentient };
|
|
226
|
+
export { Adaptive, type AdaptiveProps, AdaptiveProvider, type AdaptiveProviderProps, AdaptiveText, type AdaptiveTextProps, type AssignmentState, type ClickGoal, type ComponentWeights, type CompositeGoal, type FireGoal, type FormSubmitGoal, type GoalConfig, type MicroSignalGoalConfig, type MicroSignalGoals, type ScrollDepthGoal, type SsrFallback, type VariantWeight, type WeightedCompositeGoal, type WeightedStep, getWeights, subscribe as subscribeWeights, update as updateWeights, useAdaptiveGoal, useAssignment, useInitialAssignments, useLayoutOrder, useSentient };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { SentientConfig, SentientClient, MicroSignalType } from '@sentientui/core';
|
|
3
|
+
import { SentientConfig, SentientClient, MicroSignalType, ComponentGoalOptions } from '@sentientui/core';
|
|
4
4
|
export { deriveSessionSegment as detectSegment } from '@sentientui/core';
|
|
5
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
6
6
|
|
|
@@ -61,6 +61,12 @@ type AdaptiveProviderProps = {
|
|
|
61
61
|
* used for variant assignment.
|
|
62
62
|
*/
|
|
63
63
|
ssrSessionId?: string;
|
|
64
|
+
/**
|
|
65
|
+
* ISO 3166-1 alpha-2 country code. Pass the value of the `CF-IPCountry`
|
|
66
|
+
* header from your Next.js server component to populate country on landing
|
|
67
|
+
* sessions without client-side geo lookup.
|
|
68
|
+
*/
|
|
69
|
+
country?: string;
|
|
64
70
|
children: ReactNode;
|
|
65
71
|
};
|
|
66
72
|
/**
|
|
@@ -173,6 +179,23 @@ type AssignmentState = {
|
|
|
173
179
|
*/
|
|
174
180
|
declare function useAssignment(componentId: string, variantIds: string[], agentData?: unknown, agentDataByVariant?: Record<string, unknown>): AssignmentState;
|
|
175
181
|
|
|
182
|
+
type FireGoal = (goalType: string, opts?: ComponentGoalOptions) => void;
|
|
183
|
+
/**
|
|
184
|
+
* Returns a `fireGoal(goalType, opts?)` callback that records a conversion
|
|
185
|
+
* attributed to the variant currently served for `componentId` — so it shows
|
|
186
|
+
* up in the per-variant CVR funnel with no manual variantId/projectId plumbing.
|
|
187
|
+
*
|
|
188
|
+
* The served variant is resolved from the SDK's assignment cache (the same one
|
|
189
|
+
* `<Adaptive id={componentId}>` populates), so render that component before
|
|
190
|
+
* firing. Use this for imperative handlers (click, form submit, custom events);
|
|
191
|
+
* for purely declarative goals prefer `<Adaptive goal={...}>`.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* const fireContact = useAdaptiveGoal('hero_headline');
|
|
195
|
+
* <button onClick={() => fireContact('hero_contact', { metadata: { method } })}>Call</button>
|
|
196
|
+
*/
|
|
197
|
+
declare function useAdaptiveGoal(componentId: string): FireGoal;
|
|
198
|
+
|
|
176
199
|
/** Per-component weights store with isolated subscriptions. */
|
|
177
200
|
type VariantWeight = {
|
|
178
201
|
variantId: string;
|
|
@@ -200,4 +223,4 @@ declare function update(componentId: string, weights: ComponentWeights): void;
|
|
|
200
223
|
*/
|
|
201
224
|
declare function getWeights(componentId: string): ComponentWeights | null;
|
|
202
225
|
|
|
203
|
-
export { Adaptive, type AdaptiveProps, AdaptiveProvider, type AdaptiveProviderProps, AdaptiveText, type AdaptiveTextProps, type AssignmentState, type ClickGoal, type ComponentWeights, type CompositeGoal, type FormSubmitGoal, type GoalConfig, type MicroSignalGoalConfig, type MicroSignalGoals, type ScrollDepthGoal, type SsrFallback, type VariantWeight, type WeightedCompositeGoal, type WeightedStep, getWeights, subscribe as subscribeWeights, update as updateWeights, useAssignment, useInitialAssignments, useLayoutOrder, useSentient };
|
|
226
|
+
export { Adaptive, type AdaptiveProps, AdaptiveProvider, type AdaptiveProviderProps, AdaptiveText, type AdaptiveTextProps, type AssignmentState, type ClickGoal, type ComponentWeights, type CompositeGoal, type FireGoal, type FormSubmitGoal, type GoalConfig, type MicroSignalGoalConfig, type MicroSignalGoals, type ScrollDepthGoal, type SsrFallback, type VariantWeight, type WeightedCompositeGoal, type WeightedStep, getWeights, subscribe as subscribeWeights, update as updateWeights, useAdaptiveGoal, useAssignment, useInitialAssignments, useLayoutOrder, useSentient };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
"use strict";var K=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var pe=Object.getOwnPropertyNames,Z=Object.getOwnPropertySymbols;var te=Object.prototype.hasOwnProperty,ye=Object.prototype.propertyIsEnumerable;var ee=(e,t,n)=>t in e?K(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Q=(e,t)=>{for(var n in t||(t={}))te.call(t,n)&&ee(e,n,t[n]);if(Z)for(var n of Z(t))ye.call(t,n)&&ee(e,n,t[n]);return e};var he=(e,t)=>{for(var n in t)K(e,n,{get:t[n],enumerable:!0})},Se=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of pe(t))!te.call(e,i)&&i!==n&&K(e,i,{get:()=>t[i],enumerable:!(s=ve(t,i))||s.enumerable});return e};var Ae=e=>Se(K({},"__esModule",{value:!0}),e);var Le={};he(Le,{Adaptive:()=>ue,AdaptiveProvider:()=>ie,AdaptiveText:()=>ge,detectSegment:()=>Y.deriveSessionSegment,getWeights:()=>H,subscribeWeights:()=>j,updateWeights:()=>$,useAssignment:()=>U,useInitialAssignments:()=>X,useLayoutOrder:()=>oe,useSentient:()=>O});module.exports=Ae(Le);var p=require("react"),D=require("@sentientui/core");var ne=new Map,V=new Map;function j(e,t){let n=V.get(e);return n||(n=new Set,V.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&V.delete(e)}}function $(e,t){ne.set(e,t);let n=V.get(e);if(n)for(let s of n)try{s(t)}catch(i){}}function H(e){var t;return(t=ne.get(e))!=null?t:null}var se=require("react/jsx-runtime");function be(){var e,t;if(typeof window=="undefined")return"desktop:direct";try{let n=(0,D.detectDeviceClass)((e=navigator.userAgent)!=null?e:""),s=(0,D.detectTrafficSource)((t=document.referrer)!=null?t:"",window.location.origin);return`${n}:${s}`}catch(n){return"desktop:direct"}}var W=(0,p.createContext)({client:null,apiKey:"",initialAssignments:{},sessionSegment:"desktop:direct",ssrFallback:"first",onAssignment:void 0,initialLayoutOrder:null});function ie(e){var c;let[t,n]=(0,p.useState)(null),[s]=(0,p.useState)(()=>{var l;return(l=e.sessionSegment)!=null?l:be()});(0,p.useEffect)(()=>{if(e.consent===!1&&!e.preConsentBehavior){n(y=>(y==null||y.destroy(),null));return}let l=(0,D.init)({apiKey:e.apiKey,context:e.context,debug:e.debug,initialAssignments:e.initialAssignments,sessionSegment:s,consent:e.consent,preConsentBehavior:e.preConsentBehavior,ssrSessionId:e.ssrSessionId});return n(l),()=>{l.destroy()}},[e.consent]),(0,p.useEffect)(()=>{if(!t)return;let l=!1,y=async()=>{if(l)return;let k;try{k=await t.fetchWeights()}catch(a){return}if(!l)for(let a of k){let h={componentId:a.componentId,updatedAt:a.updatedAt,variants:a.variants.map(S=>{var g;return{variantId:S.variantId,pulls:S.pulls,avgReward:(g=S.avgReward)!=null?g:0}})};$(a.componentId,h)}};y();let I=setInterval(()=>{y()},6e4);return()=>{l=!0,clearInterval(I)}},[t]);let i=(c=e.ssrFallback)!=null?c:"first",x=(0,p.useMemo)(()=>{var l,y;return{client:t,apiKey:e.apiKey,initialAssignments:(l=e.initialAssignments)!=null?l:{},sessionSegment:s,ssrFallback:i,onAssignment:e.onAssignment,initialLayoutOrder:(y=e.initialLayoutOrder)!=null?y:null}},[t,e.apiKey,e.initialAssignments,s,i,e.onAssignment,e.initialLayoutOrder]);return(0,se.jsx)(W.Provider,{value:x,children:e.children})}function O(){return(0,p.useContext)(W).client}function B(){return(0,p.useContext)(W).apiKey}function X(){return(0,p.useContext)(W).initialAssignments}function z(){return(0,p.useContext)(W).sessionSegment}function re(){return(0,p.useContext)(W).ssrFallback}function J(){return(0,p.useContext)(W).onAssignment}function oe(){return(0,p.useContext)(W).initialLayoutOrder}var m=require("react"),ce=require("@sentientui/core");var R=require("react");function we(e){var n;if(typeof window=="undefined")return null;let t=(n=window.__sentient_overrides)==null?void 0:n[e];if(t)return t;try{let s=new URLSearchParams(window.location.search);for(let i of s.getAll("sentient_variant")){let x=i.indexOf(":");if(x!==-1&&i.slice(0,x)===e)return i.slice(x+1)}}catch(s){}return null}function ae(e,t){var s;let n=null;for(let i of e.variants)t.includes(i.variantId)&&(!n||i.avgReward>n.avgReward)&&(n={variantId:i.variantId,avgReward:i.avgReward});return(s=n==null?void 0:n.variantId)!=null?s:null}function U(e,t,n,s){let i=X(),x=re(),c=O(),l=z(),y=J(),I=(0,R.useRef)(null),k=we(e),a=k&&t.includes(k)?k:null,h=(0,R.useRef)(null);a&&h.current!==a&&(h.current=a,console.info(`[sentient] override active: ${e} -> ${a}`));let S=(()=>{var o,u;if(a)return{variantId:a,content:null,isLoading:!1};if(!c){let v=i[e];return v&&t.includes(v)?{variantId:v,content:null,isLoading:!1}:x==="first"&&t.length>0?{variantId:t[0],content:null,isLoading:!1}:{variantId:null,content:null,isLoading:!0}}let d=c.getAssignment(e,l);if(d&&(t.includes(d.variantId)||d.content))return{variantId:d.variantId,content:(o=d.content)!=null?o:null,isLoading:!1};let r=H(e);if(r){let v=ae(r,t);if(v)return{variantId:v,content:null,isLoading:!1}}return{variantId:(u=t[0])!=null?u:null,content:null,isLoading:!1}})(),[g,C]=(0,R.useState)(S),_=d=>{y&&I.current!==d&&(I.current=d,y(e,d))};return(0,R.useEffect)(()=>{var r;if(a||!c)return;let d=c.getAssignment(e,l);if(d&&(t.includes(d.variantId)||d.content)){C({variantId:d.variantId,content:(r=d.content)!=null?r:null,isLoading:!1}),_(d.variantId);return}C(o=>{var u;return o.variantId?o:{variantId:(u=t[0])!=null?u:null,content:null,isLoading:!1}})},[a,c,e,l]),(0,R.useEffect)(()=>{if(a||!c)return;let d=c.getAssignment(e,l);if(d&&t.includes(d.variantId))return;let r=!1;return c.assign(e,t,n,s).then(o=>{var u;r||o&&(!t.includes(o.variantId)&&!o.content||(C({variantId:o.variantId,content:(u=o.content)!=null?u:null,isLoading:!1}),_(o.variantId)))}),()=>{r=!0}},[a,c,e,l]),(0,R.useEffect)(()=>{if(!a&&c)return j(e,d=>{var u;let r=c.getAssignment(e,l);if(r&&(t.includes(r.variantId)||r.content)){C({variantId:r.variantId,content:(u=r.content)!=null?u:null,isLoading:!1});return}let o=ae(d,t);o&&C({variantId:o,content:null,isLoading:!1})})},[a,c,e,l]),a?{variantId:a,content:null,isLoading:!1}:g}var de=require("react/jsx-runtime");function xe(e){return typeof e=="string"?{type:"click"}:e}var Ce=3e4;function Ie(e,t){try{let n=`_snt_pv_${e}_${t}`;return sessionStorage.getItem(n)?!1:(sessionStorage.setItem(n,"1"),!0)}catch(n){return!0}}function ke(e){if(!(e instanceof Element))return!1;let t=e.tagName.toLowerCase();return t==="a"||t==="button"?!0:e.getAttribute("role")==="button"}function le(e,t,n){if(n){try{let i=e;for(;i&&i!==t;){if(i.matches(n))return!0;i=i.parentElement}}catch(i){}return!1}let s=e;for(;s&&s!==t;){if(ke(s))return!0;s=s.parentElement}return!1}function Ee(e){var _,d;let t=O(),n=B(),s=(0,m.useMemo)(()=>Object.keys(e.variants),[e.variants]),{variantId:i,content:x}=U(e.id,s,e.agentData,e.agentDataByVariant),c=(0,m.useRef)(null),[l,y]=(0,m.useState)(!1);(0,m.useEffect)(()=>{y(!0)},[]);let I=(0,m.useRef)(!1),k=(0,m.useRef)(new Set),a=(0,m.useRef)(null),h=(0,m.useMemo)(()=>xe(e.goal),[e.goal]),S=typeof e.goal=="string"?e.goal:h.type;if((0,m.useEffect)(()=>{var u,v;if(!t||!i||!n||a.current===i)return;let r=(v=(u=c.current)==null?void 0:u.innerHTML)!=null?v:"";if(!r&&!l)return;a.current=i;let o=r!==""&&Ie(e.id,i);t.track({projectId:n,componentId:e.id,variantId:i,eventType:"variant_assigned",payload:o?{previewHtml:r.slice(0,Ce)}:{}})},[t,i,n,e.id,l]),(0,m.useEffect)(()=>{I.current=!1,k.current=new Set},[i]),(0,m.useEffect)(()=>{if(!t||!i)return;let r=c.current;if(!r)return;let o=null,u=0,v=()=>{u=Date.now(),o=setTimeout(()=>{t.track({projectId:n,componentId:e.id,variantId:i,eventType:"cursor_signal",payload:{hoverDuration:Date.now()-u}}),o=null},800)},A=()=>{o!==null&&(clearTimeout(o),o=null)};return r.addEventListener("mouseenter",v),r.addEventListener("mouseleave",A),()=>{r.removeEventListener("mouseenter",v),r.removeEventListener("mouseleave",A),o!==null&&clearTimeout(o)}},[t,i,n,e.id]),(0,m.useEffect)(()=>{if(!t||!i)return;let r=c.current;if(!r)return;let o=Date.now();return(0,ce.attachMicroSignalDetectors)((u,v={})=>{var f,w,L;t.track({projectId:n,componentId:e.id,variantId:i,eventType:"micro_signal",payload:Q({signalType:u},v)});let A=(f=e.microSignalGoals)==null?void 0:f[u];if(!A||k.current.has(u))return;k.current.add(u);let P=typeof A=="string"?A:A.name,b=typeof A=="string"?1:(w=A.weight)!=null?w:1,E=typeof A=="string"?0:(L=A.stepIndex)!=null?L:0;t.goal(P,Q({signalType:u},v),b,E)},r,o)},[t,i,n,e.id,e.microSignalGoals]),(0,m.useEffect)(()=>{if(!t||!i)return;let r=c.current;if(!r)return;if(h.type==="weighted_composite"){let b=new Set,E=[];return h.steps.forEach(({goal:f,name:w,weight:L},F)=>{let q=()=>{b.has(F)||(b.add(F),t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:w,payload:{reward:L}}),t.goal(w,{},L,F))};if(f.type==="click"){let G=T=>{let N=T.target;N instanceof Element&&le(N,r,f.selector)&&q()};r.addEventListener("click",G),E.push(()=>r.removeEventListener("click",G));return}if(f.type==="form_submit"){let G=T=>{T.target instanceof HTMLFormElement&&r.contains(T.target)&&q()};r.addEventListener("submit",G),E.push(()=>r.removeEventListener("submit",G));return}if(f.type==="scroll_depth"){let G=Math.max(0,Math.min(1,f.threshold)),T=new IntersectionObserver(N=>{for(let me of N)if(me.intersectionRatio>=G){q(),T.disconnect();break}},{threshold:[G]});T.observe(r),E.push(()=>T.disconnect())}}),()=>{for(let f of E)f()}}let o=()=>{I.current||(I.current=!0,t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:S,payload:{reward:1}}))},u=h.type==="composite"?h.all:[h],v=new Set(u.map((b,E)=>E)),A=b=>{v.delete(b),v.size===0&&o()},P=[];return u.forEach((b,E)=>{if(b.type==="click"){let f=w=>{let L=w.target;L instanceof Element&&le(L,r,b.selector)&&(h.type==="composite"?A(E):o())};r.addEventListener("click",f),P.push(()=>r.removeEventListener("click",f));return}if(b.type==="form_submit"){let f=w=>{w.target instanceof HTMLFormElement&&r.contains(w.target)&&(h.type==="composite"?A(E):o())};r.addEventListener("submit",f),P.push(()=>r.removeEventListener("submit",f));return}if(b.type==="scroll_depth"){let f=Math.max(0,Math.min(1,b.threshold)),w=new IntersectionObserver(L=>{for(let F of L)if(F.intersectionRatio>=f){h.type==="composite"?A(E):o(),w.disconnect();break}},{threshold:[f]});w.observe(r),P.push(()=>w.disconnect());return}}),()=>{for(let b of P)b()}},[t,i,n,e.id,h,S]),e.clientOnly&&(!l||!t)||!i)return null;let g=(_=e.variants[i])!=null?_:null,C=g===null?x:null;return((d=process==null?void 0:process.env)==null?void 0:d.NODE_ENV)!=="production"&&g===null&&C===null&&console.warn(`[sentient] <Adaptive id="${e.id}"> was assigned variant "${i}" but no matching key exists in props.variants. If this is a dashboard-managed text variant, use <AdaptiveText id="${e.id}"> instead.`),(0,de.jsx)("div",{ref:c,"data-sentient-id":e.id,"data-sentient-variant":i,children:g!=null?g:C})}var ue=(0,m.memo)(Ee,(e,t)=>{if(e.id!==t.id||e.goal!==t.goal||e.microSignalGoals!==t.microSignalGoals)return!1;if(e.variants===t.variants)return!0;let n=Object.keys(e.variants),s=Object.keys(t.variants);return n.length!==s.length?!1:n.every(i=>i in t.variants)});var M=require("react");var fe=require("react/jsx-runtime");function ge({id:e,default:t,component:n="span",className:s}){let i=O(),x=B(),c=J(),l=z(),y=(0,M.useRef)(null),[I,k]=(0,M.useState)(()=>{var S,g;return(g=(S=i==null?void 0:i.getAssignment(e,l))==null?void 0:S.content)!=null?g:null}),[a,h]=(0,M.useState)(()=>{var S,g;return(g=(S=i==null?void 0:i.getAssignment(e,l))==null?void 0:S.variantId)!=null?g:null});return(0,M.useEffect)(()=>{var g;if(!i||((g=i.getAssignment(e,l))==null?void 0:g.content)!==void 0)return;let S=!1;return i.assign(e).then(C=>{var _;if(!S){if(!C){((_=process==null?void 0:process.env)==null?void 0:_.NODE_ENV)!=="production"&&console.warn(`[sentient] <AdaptiveText id="${e}"> assignment failed \u2014 showing default text.`);return}h(C.variantId),C.content&&k(C.content)}}),()=>{S=!0}},[i,e,l]),(0,M.useEffect)(()=>{!i||!a||!x||y.current!==a&&(y.current=a,i.track({projectId:x,componentId:e,variantId:a,eventType:"variant_assigned",payload:{}}),c==null||c(e,a))},[i,a,x,e,c]),(0,fe.jsx)(n,{className:s,children:I!=null?I:t})}var Y=require("@sentientui/core");0&&(module.exports={Adaptive,AdaptiveProvider,AdaptiveText,detectSegment,getWeights,subscribeWeights,updateWeights,useAssignment,useInitialAssignments,useLayoutOrder,useSentient});
|
|
2
|
+
"use strict";var V=Object.defineProperty;var he=Object.getOwnPropertyDescriptor;var Se=Object.getOwnPropertyNames,ee=Object.getOwnPropertySymbols;var ne=Object.prototype.hasOwnProperty,Ae=Object.prototype.propertyIsEnumerable;var te=(e,t,n)=>t in e?V(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Y=(e,t)=>{for(var n in t||(t={}))ne.call(t,n)&&te(e,n,t[n]);if(ee)for(var n of ee(t))Ae.call(t,n)&&te(e,n,t[n]);return e};var be=(e,t)=>{for(var n in t)V(e,n,{get:t[n],enumerable:!0})},we=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Se(t))!ne.call(e,i)&&i!==n&&V(e,i,{get:()=>t[i],enumerable:!(o=he(t,i))||o.enumerable});return e};var xe=e=>we(V({},"__esModule",{value:!0}),e);var _e={};be(_e,{Adaptive:()=>de,AdaptiveProvider:()=>re,AdaptiveText:()=>fe,detectSegment:()=>Z.deriveSessionSegment,getWeights:()=>B,subscribeWeights:()=>$,updateWeights:()=>H,useAdaptiveGoal:()=>ve,useAssignment:()=>q,useInitialAssignments:()=>X,useLayoutOrder:()=>se,useSentient:()=>G});module.exports=xe(_e);var p=require("react"),D=require("@sentientui/core");var ie=new Map,j=new Map;function $(e,t){let n=j.get(e);return n||(n=new Set,j.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&j.delete(e)}}function H(e,t){ie.set(e,t);let n=j.get(e);if(n)for(let o of n)try{o(t)}catch(i){}}function B(e){var t;return(t=ie.get(e))!=null?t:null}var ae=require("react/jsx-runtime");function Ce(){var e,t;if(typeof window=="undefined")return"desktop:direct";try{let n=(0,D.detectDeviceClass)((e=navigator.userAgent)!=null?e:""),o=(0,D.detectTrafficSource)((t=document.referrer)!=null?t:"",window.location.origin);return`${n}:${o}`}catch(n){return"desktop:direct"}}var M=(0,p.createContext)({client:null,apiKey:"",initialAssignments:{},sessionSegment:"desktop:direct",ssrFallback:"first",onAssignment:void 0,initialLayoutOrder:null});function re(e){var c;let[t,n]=(0,p.useState)(null),[o]=(0,p.useState)(()=>{var a;return(a=e.sessionSegment)!=null?a:Ce()});(0,p.useEffect)(()=>{if(e.consent===!1&&!e.preConsentBehavior){n(v=>(v==null||v.destroy(),null));return}let a=(0,D.init)({apiKey:e.apiKey,context:e.context,debug:e.debug,initialAssignments:e.initialAssignments,sessionSegment:o,consent:e.consent,preConsentBehavior:e.preConsentBehavior,ssrSessionId:e.ssrSessionId,country:e.country});return n(a),()=>{a.destroy()}},[e.consent]),(0,p.useEffect)(()=>{if(!t)return;let a=!1,v=async()=>{if(a)return;let I;try{I=await t.fetchWeights()}catch(s){return}if(!a)for(let s of I){let _={componentId:s.componentId,updatedAt:s.updatedAt,variants:s.variants.map(u=>{var y;return{variantId:u.variantId,pulls:u.pulls,avgReward:(y=u.avgReward)!=null?y:0}})};H(s.componentId,_)}};v();let C=setInterval(()=>{v()},6e4);return()=>{a=!0,clearInterval(C)}},[t]);let i=(c=e.ssrFallback)!=null?c:"first",w=(0,p.useMemo)(()=>{var a,v;return{client:t,apiKey:e.apiKey,initialAssignments:(a=e.initialAssignments)!=null?a:{},sessionSegment:o,ssrFallback:i,onAssignment:e.onAssignment,initialLayoutOrder:(v=e.initialLayoutOrder)!=null?v:null}},[t,e.apiKey,e.initialAssignments,o,i,e.onAssignment,e.initialLayoutOrder]);return(0,ae.jsx)(M.Provider,{value:w,children:e.children})}function G(){return(0,p.useContext)(M).client}function J(){return(0,p.useContext)(M).apiKey}function X(){return(0,p.useContext)(M).initialAssignments}function z(){return(0,p.useContext)(M).sessionSegment}function oe(){return(0,p.useContext)(M).ssrFallback}function U(){return(0,p.useContext)(M).onAssignment}function se(){return(0,p.useContext)(M).initialLayoutOrder}var m=require("react"),ue=require("@sentientui/core");var R=require("react");function Ie(e){var n;if(typeof window=="undefined")return null;let t=(n=window.__sentient_overrides)==null?void 0:n[e];if(t)return t;try{let o=new URLSearchParams(window.location.search);for(let i of o.getAll("sentient_variant")){let w=i.indexOf(":");if(w!==-1&&i.slice(0,w)===e)return i.slice(w+1)}}catch(o){}return null}function le(e,t){var o;let n=null;for(let i of e.variants)t.includes(i.variantId)&&(!n||i.avgReward>n.avgReward)&&(n={variantId:i.variantId,avgReward:i.avgReward});return(o=n==null?void 0:n.variantId)!=null?o:null}function q(e,t,n,o){let i=X(),w=oe(),c=G(),a=z(),v=U(),C=(0,R.useRef)(null),I=Ie(e),s=I&&t.includes(I)?I:null,_=(0,R.useRef)(null);s&&_.current!==s&&(_.current=s,console.info(`[sentient] override active: ${e} -> ${s}`));let u=(()=>{var r,l;if(s)return{variantId:s,content:null,isLoading:!1};if(!c){let g=i[e];return g&&t.includes(g)?{variantId:g,content:null,isLoading:!1}:w==="first"&&t.length>0?{variantId:t[0],content:null,isLoading:!1}:{variantId:null,content:null,isLoading:!0}}let d=c.getAssignment(e,a);if(d&&(t.includes(d.variantId)||d.content))return{variantId:d.variantId,content:(r=d.content)!=null?r:null,isLoading:!1};let h=B(e);if(h){let g=le(h,t);if(g)return{variantId:g,content:null,isLoading:!1}}return{variantId:(l=t[0])!=null?l:null,content:null,isLoading:!1}})(),[y,S]=(0,R.useState)(u),O=d=>{v&&C.current!==d&&(C.current=d,v(e,d))};return(0,R.useEffect)(()=>{var h;if(s||!c)return;let d=c.getAssignment(e,a);if(d&&(t.includes(d.variantId)||d.content)){S({variantId:d.variantId,content:(h=d.content)!=null?h:null,isLoading:!1}),O(d.variantId);return}S(r=>{var l;return r.variantId?r:{variantId:(l=t[0])!=null?l:null,content:null,isLoading:!1}})},[s,c,e,a]),(0,R.useEffect)(()=>{if(s||!c)return;let d=c.getAssignment(e,a);if(d&&t.includes(d.variantId))return;let h=!1;return c.assign(e,t,n,o).then(r=>{var l;h||r&&(!t.includes(r.variantId)&&!r.content||(S({variantId:r.variantId,content:(l=r.content)!=null?l:null,isLoading:!1}),O(r.variantId)))}),()=>{h=!0}},[s,c,e,a]),(0,R.useEffect)(()=>{if(!s&&c)return $(e,d=>{var l;let h=c.getAssignment(e,a);if(h&&(t.includes(h.variantId)||h.content)){S({variantId:h.variantId,content:(l=h.content)!=null?l:null,isLoading:!1});return}let r=le(d,t);r&&S({variantId:r,content:null,isLoading:!1})})},[s,c,e,a]),s?{variantId:s,content:null,isLoading:!1}:y}var ge=require("react/jsx-runtime");function ke(e){return typeof e=="string"?{type:"click"}:e}var Ee=3e4;function Le(e,t){try{let n=`_snt_pv_${e}_${t}`;return sessionStorage.getItem(n)?!1:(sessionStorage.setItem(n,"1"),!0)}catch(n){return!0}}function Ge(e){if(!(e instanceof Element))return!1;let t=e.tagName.toLowerCase();return t==="a"||t==="button"?!0:e.getAttribute("role")==="button"}function ce(e,t,n){if(n){try{let i=e;for(;i&&i!==t;){if(i.matches(n))return!0;i=i.parentElement}}catch(i){}return!1}let o=e;for(;o&&o!==t;){if(Ge(o))return!0;o=o.parentElement}return!1}function Re(e){var d,h;let t=G(),n=J(),o=(0,m.useMemo)(()=>Object.keys(e.variants),[e.variants]),{variantId:i,content:w}=q(e.id,o,e.agentData,e.agentDataByVariant),c=(0,m.useRef)(null),[a,v]=(0,m.useState)(!1);(0,m.useEffect)(()=>{v(!0)},[]);let C=(0,m.useRef)(!1),I=(0,m.useRef)(new Set),s=(0,m.useRef)(null),_=typeof e.goal=="string"?e.goal:JSON.stringify(e.goal),u=(0,m.useMemo)(()=>ke(e.goal),[_]),y=typeof e.goal=="string"?e.goal:u.type;if((0,m.useEffect)(()=>{var g,E;if(!t||!i||!n||s.current===i)return;let r=(E=(g=c.current)==null?void 0:g.innerHTML)!=null?E:"";if(!r)return;s.current=i;let l=r!==""&&Le(e.id,i);t.track({projectId:n,componentId:e.id,variantId:i,eventType:"variant_assigned",payload:l?{previewHtml:r.slice(0,Ee)}:{}})},[t,i,n,e.id,a,w]),(0,m.useEffect)(()=>{C.current=!1,I.current=new Set},[i,u]),(0,m.useEffect)(()=>{if(!t||!i)return;let r=c.current;if(!r)return;let l=null,g=0,E=()=>{g=Date.now(),l=setTimeout(()=>{t.track({projectId:n,componentId:e.id,variantId:i,eventType:"cursor_signal",payload:{hoverDuration:Date.now()-g}}),l=null},800)},A=()=>{l!==null&&(clearTimeout(l),l=null)};return r.addEventListener("mouseenter",E),r.addEventListener("mouseleave",A),()=>{r.removeEventListener("mouseenter",E),r.removeEventListener("mouseleave",A),l!==null&&clearTimeout(l)}},[t,i,n,e.id]),(0,m.useEffect)(()=>{if(!t||!i)return;let r=c.current;if(!r)return;let l=Date.now();return(0,ue.attachMicroSignalDetectors)((g,E={})=>{var f,x,L;t.track({projectId:n,componentId:e.id,variantId:i,eventType:"micro_signal",payload:Y({signalType:g},E)});let A=(f=e.microSignalGoals)==null?void 0:f[g];if(!A||I.current.has(g))return;I.current.add(g);let P=typeof A=="string"?A:A.name,b=typeof A=="string"?1:(x=A.weight)!=null?x:1,k=typeof A=="string"?0:(L=A.stepIndex)!=null?L:0;t.goal(P,Y({signalType:g},E),b,k)},r,l)},[t,i,n,e.id,e.microSignalGoals]),(0,m.useEffect)(()=>{if(!t||!i)return;let r=c.current;if(!r)return;if(u.type==="weighted_composite"){let b=new Set,k=[];return u.steps.forEach(({goal:f,name:x,weight:L},N)=>{let Q=()=>{b.has(N)||(b.add(N),t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:x,payload:{reward:L}}),t.goal(x,{},L,N))};if(f.type==="click"){let T=W=>{let K=W.target;K instanceof Element&&ce(K,r,f.selector)&&Q()};r.addEventListener("click",T),k.push(()=>r.removeEventListener("click",T));return}if(f.type==="form_submit"){let T=W=>{W.target instanceof HTMLFormElement&&r.contains(W.target)&&Q()};r.addEventListener("submit",T),k.push(()=>r.removeEventListener("submit",T));return}if(f.type==="scroll_depth"){let T=Math.max(0,Math.min(1,f.threshold)),W=new IntersectionObserver(K=>{for(let ye of K)if(ye.intersectionRatio>=T){Q(),W.disconnect();break}},{threshold:[T]});W.observe(r),k.push(()=>W.disconnect())}}),()=>{for(let f of k)f()}}let l=()=>{C.current||(C.current=!0,t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:y,payload:{reward:1}}),t.goal(y,{componentId:e.id,variantId:i},1,0))},g=u.type==="composite"?u.all:[u],E=new Set(g.map((b,k)=>k)),A=b=>{E.delete(b),E.size===0&&l()},P=[];return g.forEach((b,k)=>{if(b.type==="click"){let f=x=>{let L=x.target;L instanceof Element&&ce(L,r,b.selector)&&(u.type==="composite"?A(k):l())};r.addEventListener("click",f),P.push(()=>r.removeEventListener("click",f));return}if(b.type==="form_submit"){let f=x=>{x.target instanceof HTMLFormElement&&r.contains(x.target)&&(u.type==="composite"?A(k):l())};r.addEventListener("submit",f),P.push(()=>r.removeEventListener("submit",f));return}if(b.type==="scroll_depth"){let f=Math.max(0,Math.min(1,b.threshold)),x=new IntersectionObserver(L=>{for(let N of L)if(N.intersectionRatio>=f){u.type==="composite"?A(k):l(),x.disconnect();break}},{threshold:[f]});x.observe(r),P.push(()=>x.disconnect());return}}),()=>{for(let b of P)b()}},[t,i,n,e.id,u,y]),e.clientOnly&&(!a||!t)||!i)return null;let S=(d=e.variants[i])!=null?d:null,O=S===null?w:null;return((h=process==null?void 0:process.env)==null?void 0:h.NODE_ENV)!=="production"&&S===null&&O===null&&console.warn(`[sentient] <Adaptive id="${e.id}"> was assigned variant "${i}" but no matching key exists in props.variants. If this is a dashboard-managed text variant, use <AdaptiveText id="${e.id}"> instead.`),(0,ge.jsx)("div",{ref:c,"data-sentient-id":e.id,"data-sentient-variant":i,children:S!=null?S:O})}var de=(0,m.memo)(Re,(e,t)=>{if(e.id!==t.id||JSON.stringify(e.goal)!==JSON.stringify(t.goal)||e.microSignalGoals!==t.microSignalGoals)return!1;if(e.variants===t.variants)return!0;let n=Object.keys(e.variants),o=Object.keys(t.variants);return n.length!==o.length?!1:n.every(i=>i in t.variants)});var F=require("react");var me=require("react/jsx-runtime");function fe({id:e,default:t,component:n="span",className:o}){let i=G(),w=J(),c=U(),a=z(),v=(0,F.useRef)(null),[C,I]=(0,F.useState)(()=>{var u,y;return(y=(u=i==null?void 0:i.getAssignment(e,a))==null?void 0:u.content)!=null?y:null}),[s,_]=(0,F.useState)(()=>{var u,y;return(y=(u=i==null?void 0:i.getAssignment(e,a))==null?void 0:u.variantId)!=null?y:null});return(0,F.useEffect)(()=>{var y;if(!i||((y=i.getAssignment(e,a))==null?void 0:y.content)!==void 0)return;let u=!1;return i.assign(e).then(S=>{var O;if(!u){if(!S){((O=process==null?void 0:process.env)==null?void 0:O.NODE_ENV)!=="production"&&console.warn(`[sentient] <AdaptiveText id="${e}"> assignment failed \u2014 showing default text.`);return}_(S.variantId),S.content&&I(S.content)}}),()=>{u=!0}},[i,e,a]),(0,F.useEffect)(()=>{!i||!s||!w||v.current!==s&&(v.current=s,i.track({projectId:w,componentId:e,variantId:s,eventType:"variant_assigned",payload:{}}),c==null||c(e,s))},[i,s,w,e,c]),(0,me.jsx)(n,{className:o,children:C!=null?C:t})}var pe=require("react");function ve(e){let t=G();return(0,pe.useCallback)((n,o)=>{t==null||t.componentGoal(e,n,o)},[t,e])}var Z=require("@sentientui/core");0&&(module.exports={Adaptive,AdaptiveProvider,AdaptiveText,detectSegment,getWeights,subscribeWeights,updateWeights,useAdaptiveGoal,useAssignment,useInitialAssignments,useLayoutOrder,useSentient});
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/weights-store.ts","../src/adaptive.tsx","../src/use-assignment.ts","../src/adaptive-text.tsx","../src/segment.ts"],"sourcesContent":["export { AdaptiveProvider, useSentient, useInitialAssignments, useLayoutOrder } from './provider.js';\nexport type { AdaptiveProviderProps, SsrFallback } from './provider.js';\n\nexport { Adaptive } from './adaptive.js';\nexport type {\n AdaptiveProps,\n GoalConfig,\n ClickGoal,\n ScrollDepthGoal,\n FormSubmitGoal,\n CompositeGoal,\n WeightedStep,\n WeightedCompositeGoal,\n MicroSignalGoals,\n MicroSignalGoalConfig,\n} from './adaptive.js';\n\nexport { AdaptiveText } from './adaptive-text.js';\nexport type { AdaptiveTextProps } from './adaptive-text.js';\n\nexport { useAssignment } from './use-assignment.js';\nexport type { AssignmentState } from './use-assignment.js';\n\nexport {\n subscribe as subscribeWeights,\n update as updateWeights,\n getWeights,\n} from './weights-store.js';\nexport type { ComponentWeights, VariantWeight } from './weights-store.js';\n\nexport { detectSegment } from './segment.js';\n","'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';\nimport {\n detectDeviceClass,\n detectTrafficSource,\n init,\n type SentientClient,\n type SentientConfig,\n} from '@sentientui/core';\nimport { update as updateWeightsStore, type ComponentWeights } from './weights-store.js';\n\n/**\n * Mirrors the segment derivation inside core `init()` so the cache key used\n * by `useAssignment` always matches the key `assign()` writes under. Before\n * this, the context defaulted to 'desktop:direct' while core used the\n * detected segment — a systematic cache miss for every integration that\n * didn't pass `sessionSegment` explicitly.\n */\nfunction deriveDefaultSegment(): string {\n if (typeof window === 'undefined') return 'desktop:direct';\n try {\n const device = detectDeviceClass(navigator.userAgent ?? '');\n const source = detectTrafficSource(document.referrer ?? '', window.location.origin);\n return `${device}:${source}`;\n } catch {\n return 'desktop:direct';\n }\n}\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 * Behavior before consent is granted. Pass `'statistical_winner'` to serve the\n * best-performing variant via `GET /v1/winner` with zero tracking while the\n * consent banner is showing. Requires `consent: false`.\n * @see SentientConfig.preConsentBehavior\n */\n preConsentBehavior?: 'statistical_winner' | 'control';\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 /**\n * Session ID generated during SSR (the `sessionId` field returned by\n * `loadAdaptiveAssignments` / `loadAdaptiveDecision`). When provided and no\n * existing session cookie or localStorage entry is found, the client adopts\n * this ID so events and goals are attributed to the same session the server\n * used for variant assignment.\n */\n ssrSessionId?: string;\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 // Derived once per mount: identical to what core init() computes, so cache\n // reads (context segment) and cache writes (core segment) always agree.\n const [sessionSegment] = useState(() => props.sessionSegment ?? deriveDefaultSegment());\n\n useEffect(() => {\n // When consent is explicitly false with no preConsentBehavior, tear down any existing client.\n if (props.consent === false && !props.preConsentBehavior) {\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,\n consent: props.consent,\n preConsentBehavior: props.preConsentBehavior,\n ssrSessionId: props.ssrSessionId,\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 // Poll /v1/weights every 60 s so long-lived sessions see updated bandit weights\n // without a page reload. useAssignment subscribers react via weights-store.\n useEffect(() => {\n if (!client) return;\n let cancelled = false;\n const poll = async (): Promise<void> => {\n if (cancelled) return;\n let entries;\n try {\n entries = await client.fetchWeights();\n } catch {\n // Network/transient error: skip this cycle and retry on the next\n // interval. Swallowed deliberately so a failed poll never surfaces as\n // an unhandled rejection.\n return;\n }\n if (cancelled) return;\n for (const entry of entries) {\n const weights: ComponentWeights = {\n componentId: entry.componentId,\n updatedAt: entry.updatedAt,\n variants: entry.variants.map((v) => ({\n variantId: v.variantId,\n pulls: v.pulls,\n avgReward: v.avgReward ?? 0,\n })),\n };\n updateWeightsStore(entry.componentId, weights);\n }\n };\n void poll();\n const timerId = setInterval(() => void poll(), 60_000);\n return () => {\n cancelled = true;\n clearInterval(timerId);\n };\n }, [client]);\n\n const ssrFallback = props.ssrFallback ?? 'first';\n\n // Memoized so unrelated parent re-renders don't cascade through every\n // useSentient / useAssignment consumer via a fresh context object.\n const value = useMemo<AdaptiveContextValue>(\n () => ({\n client,\n apiKey: props.apiKey,\n initialAssignments: props.initialAssignments ?? {},\n sessionSegment,\n ssrFallback,\n onAssignment: props.onAssignment,\n initialLayoutOrder: props.initialLayoutOrder ?? null,\n }),\n [\n client,\n props.apiKey,\n props.initialAssignments,\n sessionSegment,\n ssrFallback,\n props.onAssignment,\n props.initialLayoutOrder,\n ],\n );\n\n return (\n <AdaptiveContext.Provider value={value}>\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 1.0.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 console.warn(\n '[@sentientui/react] useAdaptiveProjectId is deprecated; use useAdaptiveApiKey. Will be removed in 1.0.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","/** 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\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { memo, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport { attachMicroSignalDetectors, type MicroSignalType } from '@sentientui/core';\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 WeightedStep = { goal: GoalConfig; name: string; weight: number };\nexport type WeightedCompositeGoal = { type: 'weighted_composite'; steps: WeightedStep[] };\nexport type GoalConfig = ScrollDepthGoal | ClickGoal | FormSubmitGoal | CompositeGoal | WeightedCompositeGoal;\n\n/** Maps a detected micro-signal to a named session goal (`client.goal`). */\nexport type MicroSignalGoalConfig = string | { name: string; weight?: number; stepIndex?: number };\nexport type MicroSignalGoals = Partial<Record<MicroSignalType, MicroSignalGoalConfig>>;\n\nexport type AdaptiveProps = {\n id: string;\n variants: Record<string, ReactNode>;\n goal: string | GoalConfig;\n /**\n * When a passive micro-signal fires on this component, also record a named goal.\n * Use for inferred goals surfaced in the dashboard (e.g. rage_click → 'confused_by_hero').\n */\n microSignalGoals?: MicroSignalGoals;\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 * Variant-specific structured data for AI agent consumption via /sentient.json and\n * GET /v1/agent/layout. Keyed by variant ID — only the assigned variant's entry is stored,\n * so agents see only the content currently being served to visitors.\n *\n * Prefer this over `agentData` when variants have meaningfully different content.\n */\n agentDataByVariant?: Record<string, unknown>;\n /** @deprecated Use agentDataByVariant for variant-specific content. Stored as-is for the assigned variant. */\n agentData?: unknown;\n};\n\nfunction normalize(goal: string | GoalConfig): GoalConfig {\n if (typeof goal === 'string') return { type: 'click' };\n return goal;\n}\n\nconst PREVIEW_HTML_MAX_CHARS = 30_000;\n\n/**\n * previewHtml is the largest payload the SDK sends (up to 30 KB). The server\n * keeps the first-seen preview per (component, variant), so re-sending it on\n * every mount/navigation is pure waste — send it once per browser session.\n */\nfunction shouldSendPreviewHtml(componentId: string, variantId: string): boolean {\n try {\n const key = `_snt_pv_${componentId}_${variantId}`;\n if (sessionStorage.getItem(key)) return false;\n sessionStorage.setItem(key, '1');\n return true;\n } catch {\n // Storage unavailable (private mode, quota) — keep prior behavior.\n return true;\n }\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, selector?: string): boolean {\n if (selector) {\n try {\n let cursor: Element | null = start;\n while (cursor && cursor !== container) {\n if (cursor.matches(selector)) return true;\n cursor = cursor.parentElement;\n }\n } catch {\n // Invalid CSS selector — treat as no match rather than breaking all click handlers.\n }\n return false;\n }\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, props.agentData, props.agentDataByVariant);\n const containerRef = useRef<HTMLDivElement>(null);\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => { setMounted(true); }, []);\n const goalFiredRef = useRef(false);\n const microGoalFiredRef = useRef<Set<MicroSignalType>>(new Set());\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 const includePreview = rawHtml !== '' && shouldSendPreviewHtml(props.id, variantId);\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId,\n eventType: 'variant_assigned',\n payload: includePreview ? { previewHtml: rawHtml.slice(0, PREVIEW_HTML_MAX_CHARS) } : {},\n });\n }, [client, variantId, apiKey, props.id, mounted]);\n\n // Reset goal latch when variant changes.\n useEffect(() => {\n goalFiredRef.current = false;\n microGoalFiredRef.current = new Set();\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 micro-signal detectors passively — rage click, text copy, scroll hesitation, tab loss.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n const assignedAt = Date.now();\n return attachMicroSignalDetectors(\n (signalType, extra = {}) => {\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'micro_signal',\n payload: { signalType, ...extra },\n });\n\n const mapping = props.microSignalGoals?.[signalType];\n if (!mapping || microGoalFiredRef.current.has(signalType)) return;\n microGoalFiredRef.current.add(signalType);\n const name = typeof mapping === 'string' ? mapping : mapping.name;\n const weight = typeof mapping === 'string' ? 1.0 : (mapping.weight ?? 1.0);\n const stepIndex = typeof mapping === 'string' ? 0 : (mapping.stepIndex ?? 0);\n client.goal(name, { signalType, ...extra }, weight, stepIndex);\n },\n node,\n assignedAt,\n );\n }, [client, variantId, apiKey, props.id, props.microSignalGoals]);\n\n // Attach goal tracking.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n\n // --- Weighted composite: each step fires independently as it completes ---\n if (goal.type === 'weighted_composite') {\n const firedSteps = new Set<number>();\n const wcCleanups: Array<() => void> = [];\n\n goal.steps.forEach(({ goal: sub, name: stepName, weight: stepWeight }, idx) => {\n const fireStep = (): void => {\n if (firedSteps.has(idx)) return;\n firedSteps.add(idx);\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'goal_achieved',\n goalType: stepName,\n payload: { reward: stepWeight },\n });\n client.goal(stepName, {}, stepWeight, idx);\n };\n\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, sub.selector)) return;\n fireStep();\n };\n node.addEventListener('click', onClick);\n wcCleanups.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 fireStep();\n };\n node.addEventListener('submit', onSubmit);\n wcCleanups.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 fireStep();\n io.disconnect();\n break;\n }\n }\n },\n { threshold: [threshold] },\n );\n io.observe(node);\n wcCleanups.push(() => io.disconnect());\n }\n });\n\n return () => {\n for (const c of wcCleanups) c();\n };\n }\n // --- End weighted composite ---\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, sub.selector)) 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.microSignalGoals !== next.microSignalGoals) 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[], agentData?: unknown, agentDataByVariant?: Record<string, unknown>): 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 const overrideLoggedRef = useRef<string | null>(null);\n if (overrideVariant && overrideLoggedRef.current !== overrideVariant) {\n overrideLoggedRef.current = 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, agentData, agentDataByVariant).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","'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { AssignResult } from '@sentientui/core';\nimport { useSentient, useAdaptiveApiKey, useOnAssignment, useSessionSegment } 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 segment = useSessionSegment();\n const trackedRef = useRef<string | null>(null);\n\n // Seed from cache synchronously so remounts don't flash back to defaultText.\n const [text, setText] = useState<string | null>(() =>\n client?.getAssignment(id, segment)?.content ?? null\n );\n const [variantId, setVariantId] = useState<string | null>(() =>\n client?.getAssignment(id, segment)?.variantId ?? null\n );\n\n useEffect(() => {\n if (!client) return;\n // Skip if content already cached; assign() re-checks internally but this avoids the async round-trip on remount.\n if (client.getAssignment(id, segment)?.content !== undefined) return;\n\n let cancelled = false;\n void client.assign(id).then((result: AssignResult | null) => {\n if (cancelled) return;\n if (!result) {\n if (process?.env?.NODE_ENV !== 'production') {\n console.warn(`[sentient] <AdaptiveText id=\"${id}\"> assignment failed — showing default text.`);\n }\n return;\n }\n setVariantId(result.variantId);\n if (result.content) setText(result.content);\n });\n return () => {\n cancelled = true;\n };\n }, [client, id, segment]);\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":["src_exports","__export","Adaptive","AdaptiveProvider","AdaptiveText","getWeights","subscribe","update","useAssignment","useInitialAssignments","useLayoutOrder","useSentient","__toCommonJS","import_react","import_core","store","listeners","subscribe","componentId","cb","set","update","weights","e","getWeights","_a","import_jsx_runtime","deriveDefaultSegment","_a","_b","device","source","e","AdaptiveContext","AdaptiveProvider","props","client","setClient","sessionSegment","prev","c","cancelled","poll","entries","entry","weights","v","update","timerId","ssrFallback","value","useSentient","useAdaptiveApiKey","useInitialAssignments","AdaptiveContext","useSessionSegment","useSsrFallback","useOnAssignment","useLayoutOrder","import_react","import_core","import_react","getDevOverride","componentId","_a","global","params","raw","sep","e","pickFromWeights","weights","variantIds","best","v","useAssignment","agentData","agentDataByVariant","initialAssignments","useInitialAssignments","ssrFallback","useSsrFallback","client","useSentient","segment","useSessionSegment","onAssignment","useOnAssignment","assignmentReportedRef","devOverride","overrideVariant","overrideLoggedRef","initial","_b","preloaded","cached","getWeights","chosen","state","setState","reportAssignment","variantId","prev","cancelled","result","subscribe","import_jsx_runtime","normalize","goal","PREVIEW_HTML_MAX_CHARS","shouldSendPreviewHtml","componentId","variantId","key","e","isClickableTarget","el","tag","findClickable","start","container","selector","cursor","AdaptiveImpl","props","_a","_b","client","useSentient","apiKey","useAdaptiveApiKey","variantIds","content","useAssignment","containerRef","mounted","setMounted","goalFiredRef","microGoalFiredRef","assignTrackedRef","goalLabel","rawHtml","includePreview","node","timerId","hoverStart","onEnter","onLeave","assignedAt","signalType","extra","_c","__spreadValues","mapping","name","weight","stepIndex","firedSteps","wcCleanups","sub","stepName","stepWeight","idx","fireStep","onClick","target","onSubmit","threshold","io","entries","entry","c","fireGoal","subgoals","remaining","_","i","checkComposite","cleanups","jsxContent","managedContent","Adaptive","prev","next","prevKeys","nextKeys","k","import_react","import_jsx_runtime","AdaptiveText","id","defaultText","Tag","className","client","useSentient","apiKey","useAdaptiveApiKey","onAssignment","useOnAssignment","segment","useSessionSegment","trackedRef","text","setText","_a","_b","variantId","setVariantId","cancelled","result","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/weights-store.ts","../src/adaptive.tsx","../src/use-assignment.ts","../src/adaptive-text.tsx","../src/use-adaptive-goal.ts","../src/segment.ts"],"sourcesContent":["export { AdaptiveProvider, useSentient, useInitialAssignments, useLayoutOrder } from './provider.js';\nexport type { AdaptiveProviderProps, SsrFallback } from './provider.js';\n\nexport { Adaptive } from './adaptive.js';\nexport type {\n AdaptiveProps,\n GoalConfig,\n ClickGoal,\n ScrollDepthGoal,\n FormSubmitGoal,\n CompositeGoal,\n WeightedStep,\n WeightedCompositeGoal,\n MicroSignalGoals,\n MicroSignalGoalConfig,\n} from './adaptive.js';\n\nexport { AdaptiveText } from './adaptive-text.js';\nexport type { AdaptiveTextProps } from './adaptive-text.js';\n\nexport { useAssignment } from './use-assignment.js';\nexport type { AssignmentState } from './use-assignment.js';\n\nexport { useAdaptiveGoal } from './use-adaptive-goal.js';\nexport type { FireGoal } from './use-adaptive-goal.js';\n\nexport {\n subscribe as subscribeWeights,\n update as updateWeights,\n getWeights,\n} from './weights-store.js';\nexport type { ComponentWeights, VariantWeight } from './weights-store.js';\n\nexport { detectSegment } from './segment.js';\n","'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';\nimport {\n detectDeviceClass,\n detectTrafficSource,\n init,\n type SentientClient,\n type SentientConfig,\n} from '@sentientui/core';\nimport { update as updateWeightsStore, type ComponentWeights } from './weights-store.js';\n\n/**\n * Mirrors the segment derivation inside core `init()` so the cache key used\n * by `useAssignment` always matches the key `assign()` writes under. Before\n * this, the context defaulted to 'desktop:direct' while core used the\n * detected segment — a systematic cache miss for every integration that\n * didn't pass `sessionSegment` explicitly.\n */\nfunction deriveDefaultSegment(): string {\n if (typeof window === 'undefined') return 'desktop:direct';\n try {\n const device = detectDeviceClass(navigator.userAgent ?? '');\n const source = detectTrafficSource(document.referrer ?? '', window.location.origin);\n return `${device}:${source}`;\n } catch {\n return 'desktop:direct';\n }\n}\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 * Behavior before consent is granted. Pass `'statistical_winner'` to serve the\n * best-performing variant via `GET /v1/winner` with zero tracking while the\n * consent banner is showing. Requires `consent: false`.\n * @see SentientConfig.preConsentBehavior\n */\n preConsentBehavior?: 'statistical_winner' | 'control';\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 /**\n * Session ID generated during SSR (the `sessionId` field returned by\n * `loadAdaptiveAssignments` / `loadAdaptiveDecision`). When provided and no\n * existing session cookie or localStorage entry is found, the client adopts\n * this ID so events and goals are attributed to the same session the server\n * used for variant assignment.\n */\n ssrSessionId?: string;\n /**\n * ISO 3166-1 alpha-2 country code. Pass the value of the `CF-IPCountry`\n * header from your Next.js server component to populate country on landing\n * sessions without client-side geo lookup.\n */\n country?: string;\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 // Derived once per mount: identical to what core init() computes, so cache\n // reads (context segment) and cache writes (core segment) always agree.\n const [sessionSegment] = useState(() => props.sessionSegment ?? deriveDefaultSegment());\n\n useEffect(() => {\n // When consent is explicitly false with no preConsentBehavior, tear down any existing client.\n if (props.consent === false && !props.preConsentBehavior) {\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,\n consent: props.consent,\n preConsentBehavior: props.preConsentBehavior,\n ssrSessionId: props.ssrSessionId,\n country: props.country,\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 // Poll /v1/weights every 60 s so long-lived sessions see updated bandit weights\n // without a page reload. useAssignment subscribers react via weights-store.\n useEffect(() => {\n if (!client) return;\n let cancelled = false;\n const poll = async (): Promise<void> => {\n if (cancelled) return;\n let entries;\n try {\n entries = await client.fetchWeights();\n } catch {\n // Network/transient error: skip this cycle and retry on the next\n // interval. Swallowed deliberately so a failed poll never surfaces as\n // an unhandled rejection.\n return;\n }\n if (cancelled) return;\n for (const entry of entries) {\n const weights: ComponentWeights = {\n componentId: entry.componentId,\n updatedAt: entry.updatedAt,\n variants: entry.variants.map((v) => ({\n variantId: v.variantId,\n pulls: v.pulls,\n avgReward: v.avgReward ?? 0,\n })),\n };\n updateWeightsStore(entry.componentId, weights);\n }\n };\n void poll();\n const timerId = setInterval(() => void poll(), 60_000);\n return () => {\n cancelled = true;\n clearInterval(timerId);\n };\n }, [client]);\n\n const ssrFallback = props.ssrFallback ?? 'first';\n\n // Memoized so unrelated parent re-renders don't cascade through every\n // useSentient / useAssignment consumer via a fresh context object.\n const value = useMemo<AdaptiveContextValue>(\n () => ({\n client,\n apiKey: props.apiKey,\n initialAssignments: props.initialAssignments ?? {},\n sessionSegment,\n ssrFallback,\n onAssignment: props.onAssignment,\n initialLayoutOrder: props.initialLayoutOrder ?? null,\n }),\n [\n client,\n props.apiKey,\n props.initialAssignments,\n sessionSegment,\n ssrFallback,\n props.onAssignment,\n props.initialLayoutOrder,\n ],\n );\n\n return (\n <AdaptiveContext.Provider value={value}>\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 1.0.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 console.warn(\n '[@sentientui/react] useAdaptiveProjectId is deprecated; use useAdaptiveApiKey. Will be removed in 1.0.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","/** 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\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { memo, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport { attachMicroSignalDetectors, type MicroSignalType } from '@sentientui/core';\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 WeightedStep = { goal: GoalConfig; name: string; weight: number };\nexport type WeightedCompositeGoal = { type: 'weighted_composite'; steps: WeightedStep[] };\nexport type GoalConfig = ScrollDepthGoal | ClickGoal | FormSubmitGoal | CompositeGoal | WeightedCompositeGoal;\n\n/** Maps a detected micro-signal to a named session goal (`client.goal`). */\nexport type MicroSignalGoalConfig = string | { name: string; weight?: number; stepIndex?: number };\nexport type MicroSignalGoals = Partial<Record<MicroSignalType, MicroSignalGoalConfig>>;\n\nexport type AdaptiveProps = {\n id: string;\n variants: Record<string, ReactNode>;\n goal: string | GoalConfig;\n /**\n * When a passive micro-signal fires on this component, also record a named goal.\n * Use for inferred goals surfaced in the dashboard (e.g. rage_click → 'confused_by_hero').\n */\n microSignalGoals?: MicroSignalGoals;\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 * Variant-specific structured data for AI agent consumption via /sentient.json and\n * GET /v1/agent/layout. Keyed by variant ID — only the assigned variant's entry is stored,\n * so agents see only the content currently being served to visitors.\n *\n * Prefer this over `agentData` when variants have meaningfully different content.\n */\n agentDataByVariant?: Record<string, unknown>;\n /** @deprecated Use agentDataByVariant for variant-specific content. Stored as-is for the assigned variant. */\n agentData?: unknown;\n};\n\nfunction normalize(goal: string | GoalConfig): GoalConfig {\n if (typeof goal === 'string') return { type: 'click' };\n return goal;\n}\n\nconst PREVIEW_HTML_MAX_CHARS = 30_000;\n\n/**\n * previewHtml is the largest payload the SDK sends (up to 30 KB). The server\n * keeps the first-seen preview per (component, variant), so re-sending it on\n * every mount/navigation is pure waste — send it once per browser session.\n */\nfunction shouldSendPreviewHtml(componentId: string, variantId: string): boolean {\n try {\n const key = `_snt_pv_${componentId}_${variantId}`;\n if (sessionStorage.getItem(key)) return false;\n sessionStorage.setItem(key, '1');\n return true;\n } catch {\n // Storage unavailable (private mode, quota) — keep prior behavior.\n return true;\n }\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, selector?: string): boolean {\n if (selector) {\n try {\n let cursor: Element | null = start;\n while (cursor && cursor !== container) {\n if (cursor.matches(selector)) return true;\n cursor = cursor.parentElement;\n }\n } catch {\n // Invalid CSS selector — treat as no match rather than breaking all click handlers.\n }\n return false;\n }\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, props.agentData, props.agentDataByVariant);\n const containerRef = useRef<HTMLDivElement>(null);\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => { setMounted(true); }, []);\n const goalFiredRef = useRef(false);\n const microGoalFiredRef = useRef<Set<MicroSignalType>>(new Set());\n const assignTrackedRef = useRef<string | null>(null);\n const goalKey = typeof props.goal === 'string' ? props.goal : JSON.stringify(props.goal);\n const goal = useMemo(() => normalize(props.goal), [goalKey]);\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 // Wait until content is in the DOM; will re-run when mounted changes.\n if (!rawHtml) return;\n assignTrackedRef.current = variantId;\n const includePreview = rawHtml !== '' && shouldSendPreviewHtml(props.id, variantId);\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId,\n eventType: 'variant_assigned',\n payload: includePreview ? { previewHtml: rawHtml.slice(0, PREVIEW_HTML_MAX_CHARS) } : {},\n });\n }, [client, variantId, apiKey, props.id, mounted, content]);\n\n // Reset goal latch when variant or goal changes.\n useEffect(() => {\n goalFiredRef.current = false;\n microGoalFiredRef.current = new Set();\n }, [variantId, goal]);\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 micro-signal detectors passively — rage click, text copy, scroll hesitation, tab loss.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n const assignedAt = Date.now();\n return attachMicroSignalDetectors(\n (signalType, extra = {}) => {\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'micro_signal',\n payload: { signalType, ...extra },\n });\n\n const mapping = props.microSignalGoals?.[signalType];\n if (!mapping || microGoalFiredRef.current.has(signalType)) return;\n microGoalFiredRef.current.add(signalType);\n const name = typeof mapping === 'string' ? mapping : mapping.name;\n const weight = typeof mapping === 'string' ? 1.0 : (mapping.weight ?? 1.0);\n const stepIndex = typeof mapping === 'string' ? 0 : (mapping.stepIndex ?? 0);\n client.goal(name, { signalType, ...extra }, weight, stepIndex);\n },\n node,\n assignedAt,\n );\n }, [client, variantId, apiKey, props.id, props.microSignalGoals]);\n\n // Attach goal tracking.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n\n // --- Weighted composite: each step fires independently as it completes ---\n if (goal.type === 'weighted_composite') {\n const firedSteps = new Set<number>();\n const wcCleanups: Array<() => void> = [];\n\n goal.steps.forEach(({ goal: sub, name: stepName, weight: stepWeight }, idx) => {\n const fireStep = (): void => {\n if (firedSteps.has(idx)) return;\n firedSteps.add(idx);\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'goal_achieved',\n goalType: stepName,\n payload: { reward: stepWeight },\n });\n client.goal(stepName, {}, stepWeight, idx);\n };\n\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, sub.selector)) return;\n fireStep();\n };\n node.addEventListener('click', onClick);\n wcCleanups.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 fireStep();\n };\n node.addEventListener('submit', onSubmit);\n wcCleanups.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 fireStep();\n io.disconnect();\n break;\n }\n }\n },\n { threshold: [threshold] },\n );\n io.observe(node);\n wcCleanups.push(() => io.disconnect());\n }\n });\n\n return () => {\n for (const c of wcCleanups) c();\n };\n }\n // --- End weighted composite ---\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 client.goal(goalLabel, { componentId: props.id, variantId }, 1.0, 0);\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, sub.selector)) 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 (JSON.stringify(prev.goal) !== JSON.stringify(next.goal)) return false;\n if (prev.microSignalGoals !== next.microSignalGoals) 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[], agentData?: unknown, agentDataByVariant?: Record<string, unknown>): 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 const overrideLoggedRef = useRef<string | null>(null);\n if (overrideVariant && overrideLoggedRef.current !== overrideVariant) {\n overrideLoggedRef.current = 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, agentData, agentDataByVariant).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","'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { AssignResult } from '@sentientui/core';\nimport { useSentient, useAdaptiveApiKey, useOnAssignment, useSessionSegment } 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 segment = useSessionSegment();\n const trackedRef = useRef<string | null>(null);\n\n // Seed from cache synchronously so remounts don't flash back to defaultText.\n const [text, setText] = useState<string | null>(() =>\n client?.getAssignment(id, segment)?.content ?? null\n );\n const [variantId, setVariantId] = useState<string | null>(() =>\n client?.getAssignment(id, segment)?.variantId ?? null\n );\n\n useEffect(() => {\n if (!client) return;\n // Skip if content already cached; assign() re-checks internally but this avoids the async round-trip on remount.\n if (client.getAssignment(id, segment)?.content !== undefined) return;\n\n let cancelled = false;\n void client.assign(id).then((result: AssignResult | null) => {\n if (cancelled) return;\n if (!result) {\n if (process?.env?.NODE_ENV !== 'production') {\n console.warn(`[sentient] <AdaptiveText id=\"${id}\"> assignment failed — showing default text.`);\n }\n return;\n }\n setVariantId(result.variantId);\n if (result.content) setText(result.content);\n });\n return () => {\n cancelled = true;\n };\n }, [client, id, segment]);\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","import { useCallback } from 'react';\nimport type { ComponentGoalOptions } from '@sentientui/core';\nimport { useSentient } from './provider.js';\n\nexport type FireGoal = (goalType: string, opts?: ComponentGoalOptions) => void;\n\n/**\n * Returns a `fireGoal(goalType, opts?)` callback that records a conversion\n * attributed to the variant currently served for `componentId` — so it shows\n * up in the per-variant CVR funnel with no manual variantId/projectId plumbing.\n *\n * The served variant is resolved from the SDK's assignment cache (the same one\n * `<Adaptive id={componentId}>` populates), so render that component before\n * firing. Use this for imperative handlers (click, form submit, custom events);\n * for purely declarative goals prefer `<Adaptive goal={...}>`.\n *\n * @example\n * const fireContact = useAdaptiveGoal('hero_headline');\n * <button onClick={() => fireContact('hero_contact', { metadata: { method } })}>Call</button>\n */\nexport function useAdaptiveGoal(componentId: string): FireGoal {\n const client = useSentient();\n return useCallback<FireGoal>(\n (goalType, opts) => {\n client?.componentGoal(componentId, goalType, opts);\n },\n [client, componentId],\n );\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":["src_exports","__export","Adaptive","AdaptiveProvider","AdaptiveText","getWeights","subscribe","update","useAdaptiveGoal","useAssignment","useInitialAssignments","useLayoutOrder","useSentient","__toCommonJS","import_react","import_core","store","listeners","subscribe","componentId","cb","set","update","weights","e","getWeights","_a","import_jsx_runtime","deriveDefaultSegment","_a","_b","device","source","e","AdaptiveContext","AdaptiveProvider","props","client","setClient","sessionSegment","prev","c","cancelled","poll","entries","entry","weights","v","update","timerId","ssrFallback","value","useSentient","useAdaptiveApiKey","useInitialAssignments","AdaptiveContext","useSessionSegment","useSsrFallback","useOnAssignment","useLayoutOrder","import_react","import_core","import_react","getDevOverride","componentId","_a","global","params","raw","sep","e","pickFromWeights","weights","variantIds","best","v","useAssignment","agentData","agentDataByVariant","initialAssignments","useInitialAssignments","ssrFallback","useSsrFallback","client","useSentient","segment","useSessionSegment","onAssignment","useOnAssignment","assignmentReportedRef","devOverride","overrideVariant","overrideLoggedRef","initial","_b","preloaded","cached","getWeights","chosen","state","setState","reportAssignment","variantId","prev","cancelled","result","subscribe","import_jsx_runtime","normalize","goal","PREVIEW_HTML_MAX_CHARS","shouldSendPreviewHtml","componentId","variantId","key","e","isClickableTarget","el","tag","findClickable","start","container","selector","cursor","AdaptiveImpl","props","_a","_b","client","useSentient","apiKey","useAdaptiveApiKey","variantIds","content","useAssignment","containerRef","mounted","setMounted","goalFiredRef","microGoalFiredRef","assignTrackedRef","goalKey","goalLabel","rawHtml","includePreview","node","timerId","hoverStart","onEnter","onLeave","assignedAt","signalType","extra","_c","__spreadValues","mapping","name","weight","stepIndex","firedSteps","wcCleanups","sub","stepName","stepWeight","idx","fireStep","onClick","target","onSubmit","threshold","io","entries","entry","c","fireGoal","subgoals","remaining","_","i","checkComposite","cleanups","jsxContent","managedContent","Adaptive","prev","next","prevKeys","nextKeys","k","import_react","import_jsx_runtime","AdaptiveText","id","defaultText","Tag","className","client","useSentient","apiKey","useAdaptiveApiKey","onAssignment","useOnAssignment","segment","useSessionSegment","trackedRef","text","setText","_a","_b","variantId","setVariantId","cancelled","result","import_react","useAdaptiveGoal","componentId","client","useSentient","goalType","opts","import_core"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
var ce=Object.defineProperty;var q=Object.getOwnPropertySymbols;var ue=Object.prototype.hasOwnProperty,de=Object.prototype.propertyIsEnumerable;var Q=(e,t,n)=>t in e?ce(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,$=(e,t)=>{for(var n in t||(t={}))ue.call(t,n)&&Q(e,n,t[n]);if(q)for(var n of q(t))de.call(t,n)&&Q(e,n,t[n]);return e};import{createContext as ge,useContext as G,useEffect as Z,useMemo as fe,useState as ee}from"react";import{detectDeviceClass as me,detectTrafficSource as ve,init as pe}from"@sentientui/core";var Y=new Map,D=new Map;function H(e,t){let n=D.get(e);return n||(n=new Set,D.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&D.delete(e)}}function B(e,t){Y.set(e,t);let n=D.get(e);if(n)for(let l of n)try{l(t)}catch(i){}}function X(e){var t;return(t=Y.get(e))!=null?t:null}import{jsx as Ae}from"react/jsx-runtime";function ye(){var e,t;if(typeof window=="undefined")return"desktop:direct";try{let n=me((e=navigator.userAgent)!=null?e:""),l=ve((t=document.referrer)!=null?t:"",window.location.origin);return`${n}:${l}`}catch(n){return"desktop:direct"}}var _=ge({client:null,apiKey:"",initialAssignments:{},sessionSegment:"desktop:direct",ssrFallback:"first",onAssignment:void 0,initialLayoutOrder:null});function he(e){var c;let[t,n]=ee(null),[l]=ee(()=>{var a;return(a=e.sessionSegment)!=null?a:ye()});Z(()=>{if(e.consent===!1&&!e.preConsentBehavior){n(v=>(v==null||v.destroy(),null));return}let a=pe({apiKey:e.apiKey,context:e.context,debug:e.debug,initialAssignments:e.initialAssignments,sessionSegment:l,consent:e.consent,preConsentBehavior:e.preConsentBehavior,ssrSessionId:e.ssrSessionId});return n(a),()=>{a.destroy()}},[e.consent]),Z(()=>{if(!t)return;let a=!1,v=async()=>{if(a)return;let C;try{C=await t.fetchWeights()}catch(s){return}if(!a)for(let s of C){let p={componentId:s.componentId,updatedAt:s.updatedAt,variants:s.variants.map(y=>{var g;return{variantId:y.variantId,pulls:y.pulls,avgReward:(g=y.avgReward)!=null?g:0}})};B(s.componentId,p)}};v();let x=setInterval(()=>{v()},6e4);return()=>{a=!0,clearInterval(x)}},[t]);let i=(c=e.ssrFallback)!=null?c:"first",b=fe(()=>{var a,v;return{client:t,apiKey:e.apiKey,initialAssignments:(a=e.initialAssignments)!=null?a:{},sessionSegment:l,ssrFallback:i,onAssignment:e.onAssignment,initialLayoutOrder:(v=e.initialLayoutOrder)!=null?v:null}},[t,e.apiKey,e.initialAssignments,l,i,e.onAssignment,e.initialLayoutOrder]);return Ae(_.Provider,{value:b,children:e.children})}function T(){return G(_).client}function F(){return G(_).apiKey}function z(){return G(_).initialAssignments}function N(){return G(_).sessionSegment}function te(){return G(_).ssrFallback}function K(){return G(_).onAssignment}function Se(){return G(_).initialLayoutOrder}import{memo as xe,useEffect as O,useMemo as re,useRef as V,useState as Ce}from"react";import{attachMicroSignalDetectors as Ie}from"@sentientui/core";import{useEffect as J,useRef as ne,useState as be}from"react";function we(e){var n;if(typeof window=="undefined")return null;let t=(n=window.__sentient_overrides)==null?void 0:n[e];if(t)return t;try{let l=new URLSearchParams(window.location.search);for(let i of l.getAll("sentient_variant")){let b=i.indexOf(":");if(b!==-1&&i.slice(0,b)===e)return i.slice(b+1)}}catch(l){}return null}function ie(e,t){var l;let n=null;for(let i of e.variants)t.includes(i.variantId)&&(!n||i.avgReward>n.avgReward)&&(n={variantId:i.variantId,avgReward:i.avgReward});return(l=n==null?void 0:n.variantId)!=null?l:null}function U(e,t,n,l){let i=z(),b=te(),c=T(),a=N(),v=K(),x=ne(null),C=we(e),s=C&&t.includes(C)?C:null,p=ne(null);s&&p.current!==s&&(p.current=s,console.info(`[sentient] override active: ${e} -> ${s}`));let y=(()=>{var o,u;if(s)return{variantId:s,content:null,isLoading:!1};if(!c){let m=i[e];return m&&t.includes(m)?{variantId:m,content:null,isLoading:!1}:b==="first"&&t.length>0?{variantId:t[0],content:null,isLoading:!1}:{variantId:null,content:null,isLoading:!0}}let d=c.getAssignment(e,a);if(d&&(t.includes(d.variantId)||d.content))return{variantId:d.variantId,content:(o=d.content)!=null?o:null,isLoading:!1};let r=X(e);if(r){let m=ie(r,t);if(m)return{variantId:m,content:null,isLoading:!1}}return{variantId:(u=t[0])!=null?u:null,content:null,isLoading:!1}})(),[g,w]=be(y),E=d=>{v&&x.current!==d&&(x.current=d,v(e,d))};return J(()=>{var r;if(s||!c)return;let d=c.getAssignment(e,a);if(d&&(t.includes(d.variantId)||d.content)){w({variantId:d.variantId,content:(r=d.content)!=null?r:null,isLoading:!1}),E(d.variantId);return}w(o=>{var u;return o.variantId?o:{variantId:(u=t[0])!=null?u:null,content:null,isLoading:!1}})},[s,c,e,a]),J(()=>{if(s||!c)return;let d=c.getAssignment(e,a);if(d&&t.includes(d.variantId))return;let r=!1;return c.assign(e,t,n,l).then(o=>{var u;r||o&&(!t.includes(o.variantId)&&!o.content||(w({variantId:o.variantId,content:(u=o.content)!=null?u:null,isLoading:!1}),E(o.variantId)))}),()=>{r=!0}},[s,c,e,a]),J(()=>{if(!s&&c)return H(e,d=>{var u;let r=c.getAssignment(e,a);if(r&&(t.includes(r.variantId)||r.content)){w({variantId:r.variantId,content:(u=r.content)!=null?u:null,isLoading:!1});return}let o=ie(d,t);o&&w({variantId:o,content:null,isLoading:!1})})},[s,c,e,a]),s?{variantId:s,content:null,isLoading:!1}:g}import{jsx as Te}from"react/jsx-runtime";function ke(e){return typeof e=="string"?{type:"click"}:e}var Ee=3e4;function Le(e,t){try{let n=`_snt_pv_${e}_${t}`;return sessionStorage.getItem(n)?!1:(sessionStorage.setItem(n,"1"),!0)}catch(n){return!0}}function Re(e){if(!(e instanceof Element))return!1;let t=e.tagName.toLowerCase();return t==="a"||t==="button"?!0:e.getAttribute("role")==="button"}function oe(e,t,n){if(n){try{let i=e;for(;i&&i!==t;){if(i.matches(n))return!0;i=i.parentElement}}catch(i){}return!1}let l=e;for(;l&&l!==t;){if(Re(l))return!0;l=l.parentElement}return!1}function _e(e){var E,d;let t=T(),n=F(),l=re(()=>Object.keys(e.variants),[e.variants]),{variantId:i,content:b}=U(e.id,l,e.agentData,e.agentDataByVariant),c=V(null),[a,v]=Ce(!1);O(()=>{v(!0)},[]);let x=V(!1),C=V(new Set),s=V(null),p=re(()=>ke(e.goal),[e.goal]),y=typeof e.goal=="string"?e.goal:p.type;if(O(()=>{var u,m;if(!t||!i||!n||s.current===i)return;let r=(m=(u=c.current)==null?void 0:u.innerHTML)!=null?m:"";if(!r&&!a)return;s.current=i;let o=r!==""&&Le(e.id,i);t.track({projectId:n,componentId:e.id,variantId:i,eventType:"variant_assigned",payload:o?{previewHtml:r.slice(0,Ee)}:{}})},[t,i,n,e.id,a]),O(()=>{x.current=!1,C.current=new Set},[i]),O(()=>{if(!t||!i)return;let r=c.current;if(!r)return;let o=null,u=0,m=()=>{u=Date.now(),o=setTimeout(()=>{t.track({projectId:n,componentId:e.id,variantId:i,eventType:"cursor_signal",payload:{hoverDuration:Date.now()-u}}),o=null},800)},h=()=>{o!==null&&(clearTimeout(o),o=null)};return r.addEventListener("mouseenter",m),r.addEventListener("mouseleave",h),()=>{r.removeEventListener("mouseenter",m),r.removeEventListener("mouseleave",h),o!==null&&clearTimeout(o)}},[t,i,n,e.id]),O(()=>{if(!t||!i)return;let r=c.current;if(!r)return;let o=Date.now();return Ie((u,m={})=>{var f,A,k;t.track({projectId:n,componentId:e.id,variantId:i,eventType:"micro_signal",payload:$({signalType:u},m)});let h=(f=e.microSignalGoals)==null?void 0:f[u];if(!h||C.current.has(u))return;C.current.add(u);let W=typeof h=="string"?h:h.name,S=typeof h=="string"?1:(A=h.weight)!=null?A:1,I=typeof h=="string"?0:(k=h.stepIndex)!=null?k:0;t.goal(W,$({signalType:u},m),S,I)},r,o)},[t,i,n,e.id,e.microSignalGoals]),O(()=>{if(!t||!i)return;let r=c.current;if(!r)return;if(p.type==="weighted_composite"){let S=new Set,I=[];return p.steps.forEach(({goal:f,name:A,weight:k},M)=>{let j=()=>{S.has(M)||(S.add(M),t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:A,payload:{reward:k}}),t.goal(A,{},k,M))};if(f.type==="click"){let L=R=>{let P=R.target;P instanceof Element&&oe(P,r,f.selector)&&j()};r.addEventListener("click",L),I.push(()=>r.removeEventListener("click",L));return}if(f.type==="form_submit"){let L=R=>{R.target instanceof HTMLFormElement&&r.contains(R.target)&&j()};r.addEventListener("submit",L),I.push(()=>r.removeEventListener("submit",L));return}if(f.type==="scroll_depth"){let L=Math.max(0,Math.min(1,f.threshold)),R=new IntersectionObserver(P=>{for(let le of P)if(le.intersectionRatio>=L){j(),R.disconnect();break}},{threshold:[L]});R.observe(r),I.push(()=>R.disconnect())}}),()=>{for(let f of I)f()}}let o=()=>{x.current||(x.current=!0,t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:y,payload:{reward:1}}))},u=p.type==="composite"?p.all:[p],m=new Set(u.map((S,I)=>I)),h=S=>{m.delete(S),m.size===0&&o()},W=[];return u.forEach((S,I)=>{if(S.type==="click"){let f=A=>{let k=A.target;k instanceof Element&&oe(k,r,S.selector)&&(p.type==="composite"?h(I):o())};r.addEventListener("click",f),W.push(()=>r.removeEventListener("click",f));return}if(S.type==="form_submit"){let f=A=>{A.target instanceof HTMLFormElement&&r.contains(A.target)&&(p.type==="composite"?h(I):o())};r.addEventListener("submit",f),W.push(()=>r.removeEventListener("submit",f));return}if(S.type==="scroll_depth"){let f=Math.max(0,Math.min(1,S.threshold)),A=new IntersectionObserver(k=>{for(let M of k)if(M.intersectionRatio>=f){p.type==="composite"?h(I):o(),A.disconnect();break}},{threshold:[f]});A.observe(r),W.push(()=>A.disconnect());return}}),()=>{for(let S of W)S()}},[t,i,n,e.id,p,y]),e.clientOnly&&(!a||!t)||!i)return null;let g=(E=e.variants[i])!=null?E:null,w=g===null?b:null;return((d=process==null?void 0:process.env)==null?void 0:d.NODE_ENV)!=="production"&&g===null&&w===null&&console.warn(`[sentient] <Adaptive id="${e.id}"> was assigned variant "${i}" but no matching key exists in props.variants. If this is a dashboard-managed text variant, use <AdaptiveText id="${e.id}"> instead.`),Te("div",{ref:c,"data-sentient-id":e.id,"data-sentient-variant":i,children:g!=null?g:w})}var Ge=xe(_e,(e,t)=>{if(e.id!==t.id||e.goal!==t.goal||e.microSignalGoals!==t.microSignalGoals)return!1;if(e.variants===t.variants)return!0;let n=Object.keys(e.variants),l=Object.keys(t.variants);return n.length!==l.length?!1:n.every(i=>i in t.variants)});import{useEffect as se,useRef as We,useState as ae}from"react";import{jsx as Me}from"react/jsx-runtime";function Oe({id:e,default:t,component:n="span",className:l}){let i=T(),b=F(),c=K(),a=N(),v=We(null),[x,C]=ae(()=>{var y,g;return(g=(y=i==null?void 0:i.getAssignment(e,a))==null?void 0:y.content)!=null?g:null}),[s,p]=ae(()=>{var y,g;return(g=(y=i==null?void 0:i.getAssignment(e,a))==null?void 0:y.variantId)!=null?g:null});return se(()=>{var g;if(!i||((g=i.getAssignment(e,a))==null?void 0:g.content)!==void 0)return;let y=!1;return i.assign(e).then(w=>{var E;if(!y){if(!w){((E=process==null?void 0:process.env)==null?void 0:E.NODE_ENV)!=="production"&&console.warn(`[sentient] <AdaptiveText id="${e}"> assignment failed \u2014 showing default text.`);return}p(w.variantId),w.content&&C(w.content)}}),()=>{y=!0}},[i,e,a]),se(()=>{!i||!s||!b||v.current!==s&&(v.current=s,i.track({projectId:b,componentId:e,variantId:s,eventType:"variant_assigned",payload:{}}),c==null||c(e,s))},[i,s,b,e,c]),Me(n,{className:l,children:x!=null?x:t})}import{deriveSessionSegment as Pe}from"@sentientui/core";export{Ge as Adaptive,he as AdaptiveProvider,Oe as AdaptiveText,Pe as detectSegment,X as getWeights,H as subscribeWeights,B as updateWeights,U as useAssignment,z as useInitialAssignments,Se as useLayoutOrder,T as useSentient};
|
|
2
|
+
var ue=Object.defineProperty;var Q=Object.getOwnPropertySymbols;var de=Object.prototype.hasOwnProperty,ge=Object.prototype.propertyIsEnumerable;var Y=(e,t,n)=>t in e?ue(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,H=(e,t)=>{for(var n in t||(t={}))de.call(t,n)&&Y(e,n,t[n]);if(Q)for(var n of Q(t))ge.call(t,n)&&Y(e,n,t[n]);return e};import{createContext as fe,useContext as T,useEffect as ee,useMemo as me,useState as te}from"react";import{detectDeviceClass as pe,detectTrafficSource as ve,init as ye}from"@sentientui/core";var Z=new Map,D=new Map;function B(e,t){let n=D.get(e);return n||(n=new Set,D.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&D.delete(e)}}function J(e,t){Z.set(e,t);let n=D.get(e);if(n)for(let s of n)try{s(t)}catch(i){}}function X(e){var t;return(t=Z.get(e))!=null?t:null}import{jsx as be}from"react/jsx-runtime";function he(){var e,t;if(typeof window=="undefined")return"desktop:direct";try{let n=pe((e=navigator.userAgent)!=null?e:""),s=ve((t=document.referrer)!=null?t:"",window.location.origin);return`${n}:${s}`}catch(n){return"desktop:direct"}}var O=fe({client:null,apiKey:"",initialAssignments:{},sessionSegment:"desktop:direct",ssrFallback:"first",onAssignment:void 0,initialLayoutOrder:null});function Se(e){var c;let[t,n]=te(null),[s]=te(()=>{var a;return(a=e.sessionSegment)!=null?a:he()});ee(()=>{if(e.consent===!1&&!e.preConsentBehavior){n(m=>(m==null||m.destroy(),null));return}let a=ye({apiKey:e.apiKey,context:e.context,debug:e.debug,initialAssignments:e.initialAssignments,sessionSegment:s,consent:e.consent,preConsentBehavior:e.preConsentBehavior,ssrSessionId:e.ssrSessionId,country:e.country});return n(a),()=>{a.destroy()}},[e.consent]),ee(()=>{if(!t)return;let a=!1,m=async()=>{if(a)return;let x;try{x=await t.fetchWeights()}catch(o){return}if(!a)for(let o of x){let L={componentId:o.componentId,updatedAt:o.updatedAt,variants:o.variants.map(u=>{var p;return{variantId:u.variantId,pulls:u.pulls,avgReward:(p=u.avgReward)!=null?p:0}})};J(o.componentId,L)}};m();let w=setInterval(()=>{m()},6e4);return()=>{a=!0,clearInterval(w)}},[t]);let i=(c=e.ssrFallback)!=null?c:"first",A=me(()=>{var a,m;return{client:t,apiKey:e.apiKey,initialAssignments:(a=e.initialAssignments)!=null?a:{},sessionSegment:s,ssrFallback:i,onAssignment:e.onAssignment,initialLayoutOrder:(m=e.initialLayoutOrder)!=null?m:null}},[t,e.apiKey,e.initialAssignments,s,i,e.onAssignment,e.initialLayoutOrder]);return be(O.Provider,{value:A,children:e.children})}function E(){return T(O).client}function N(){return T(O).apiKey}function z(){return T(O).initialAssignments}function K(){return T(O).sessionSegment}function ne(){return T(O).ssrFallback}function V(){return T(O).onAssignment}function Ae(){return T(O).initialLayoutOrder}import{memo as Ce,useEffect as M,useMemo as oe,useRef as j,useState as Ie}from"react";import{attachMicroSignalDetectors as ke}from"@sentientui/core";import{useEffect as U,useRef as ie,useState as we}from"react";function xe(e){var n;if(typeof window=="undefined")return null;let t=(n=window.__sentient_overrides)==null?void 0:n[e];if(t)return t;try{let s=new URLSearchParams(window.location.search);for(let i of s.getAll("sentient_variant")){let A=i.indexOf(":");if(A!==-1&&i.slice(0,A)===e)return i.slice(A+1)}}catch(s){}return null}function re(e,t){var s;let n=null;for(let i of e.variants)t.includes(i.variantId)&&(!n||i.avgReward>n.avgReward)&&(n={variantId:i.variantId,avgReward:i.avgReward});return(s=n==null?void 0:n.variantId)!=null?s:null}function q(e,t,n,s){let i=z(),A=ne(),c=E(),a=K(),m=V(),w=ie(null),x=xe(e),o=x&&t.includes(x)?x:null,L=ie(null);o&&L.current!==o&&(L.current=o,console.info(`[sentient] override active: ${e} -> ${o}`));let u=(()=>{var r,l;if(o)return{variantId:o,content:null,isLoading:!1};if(!c){let g=i[e];return g&&t.includes(g)?{variantId:g,content:null,isLoading:!1}:A==="first"&&t.length>0?{variantId:t[0],content:null,isLoading:!1}:{variantId:null,content:null,isLoading:!0}}let d=c.getAssignment(e,a);if(d&&(t.includes(d.variantId)||d.content))return{variantId:d.variantId,content:(r=d.content)!=null?r:null,isLoading:!1};let v=X(e);if(v){let g=re(v,t);if(g)return{variantId:g,content:null,isLoading:!1}}return{variantId:(l=t[0])!=null?l:null,content:null,isLoading:!1}})(),[p,y]=we(u),G=d=>{m&&w.current!==d&&(w.current=d,m(e,d))};return U(()=>{var v;if(o||!c)return;let d=c.getAssignment(e,a);if(d&&(t.includes(d.variantId)||d.content)){y({variantId:d.variantId,content:(v=d.content)!=null?v:null,isLoading:!1}),G(d.variantId);return}y(r=>{var l;return r.variantId?r:{variantId:(l=t[0])!=null?l:null,content:null,isLoading:!1}})},[o,c,e,a]),U(()=>{if(o||!c)return;let d=c.getAssignment(e,a);if(d&&t.includes(d.variantId))return;let v=!1;return c.assign(e,t,n,s).then(r=>{var l;v||r&&(!t.includes(r.variantId)&&!r.content||(y({variantId:r.variantId,content:(l=r.content)!=null?l:null,isLoading:!1}),G(r.variantId)))}),()=>{v=!0}},[o,c,e,a]),U(()=>{if(!o&&c)return B(e,d=>{var l;let v=c.getAssignment(e,a);if(v&&(t.includes(v.variantId)||v.content)){y({variantId:v.variantId,content:(l=v.content)!=null?l:null,isLoading:!1});return}let r=re(d,t);r&&y({variantId:r,content:null,isLoading:!1})})},[o,c,e,a]),o?{variantId:o,content:null,isLoading:!1}:p}import{jsx as Te}from"react/jsx-runtime";function Ee(e){return typeof e=="string"?{type:"click"}:e}var Le=3e4;function Ge(e,t){try{let n=`_snt_pv_${e}_${t}`;return sessionStorage.getItem(n)?!1:(sessionStorage.setItem(n,"1"),!0)}catch(n){return!0}}function Re(e){if(!(e instanceof Element))return!1;let t=e.tagName.toLowerCase();return t==="a"||t==="button"?!0:e.getAttribute("role")==="button"}function se(e,t,n){if(n){try{let i=e;for(;i&&i!==t;){if(i.matches(n))return!0;i=i.parentElement}}catch(i){}return!1}let s=e;for(;s&&s!==t;){if(Re(s))return!0;s=s.parentElement}return!1}function _e(e){var d,v;let t=E(),n=N(),s=oe(()=>Object.keys(e.variants),[e.variants]),{variantId:i,content:A}=q(e.id,s,e.agentData,e.agentDataByVariant),c=j(null),[a,m]=Ie(!1);M(()=>{m(!0)},[]);let w=j(!1),x=j(new Set),o=j(null),L=typeof e.goal=="string"?e.goal:JSON.stringify(e.goal),u=oe(()=>Ee(e.goal),[L]),p=typeof e.goal=="string"?e.goal:u.type;if(M(()=>{var g,I;if(!t||!i||!n||o.current===i)return;let r=(I=(g=c.current)==null?void 0:g.innerHTML)!=null?I:"";if(!r)return;o.current=i;let l=r!==""&&Ge(e.id,i);t.track({projectId:n,componentId:e.id,variantId:i,eventType:"variant_assigned",payload:l?{previewHtml:r.slice(0,Le)}:{}})},[t,i,n,e.id,a,A]),M(()=>{w.current=!1,x.current=new Set},[i,u]),M(()=>{if(!t||!i)return;let r=c.current;if(!r)return;let l=null,g=0,I=()=>{g=Date.now(),l=setTimeout(()=>{t.track({projectId:n,componentId:e.id,variantId:i,eventType:"cursor_signal",payload:{hoverDuration:Date.now()-g}}),l=null},800)},h=()=>{l!==null&&(clearTimeout(l),l=null)};return r.addEventListener("mouseenter",I),r.addEventListener("mouseleave",h),()=>{r.removeEventListener("mouseenter",I),r.removeEventListener("mouseleave",h),l!==null&&clearTimeout(l)}},[t,i,n,e.id]),M(()=>{if(!t||!i)return;let r=c.current;if(!r)return;let l=Date.now();return ke((g,I={})=>{var f,b,k;t.track({projectId:n,componentId:e.id,variantId:i,eventType:"micro_signal",payload:H({signalType:g},I)});let h=(f=e.microSignalGoals)==null?void 0:f[g];if(!h||x.current.has(g))return;x.current.add(g);let W=typeof h=="string"?h:h.name,S=typeof h=="string"?1:(b=h.weight)!=null?b:1,C=typeof h=="string"?0:(k=h.stepIndex)!=null?k:0;t.goal(W,H({signalType:g},I),S,C)},r,l)},[t,i,n,e.id,e.microSignalGoals]),M(()=>{if(!t||!i)return;let r=c.current;if(!r)return;if(u.type==="weighted_composite"){let S=new Set,C=[];return u.steps.forEach(({goal:f,name:b,weight:k},F)=>{let $=()=>{S.has(F)||(S.add(F),t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:b,payload:{reward:k}}),t.goal(b,{},k,F))};if(f.type==="click"){let R=_=>{let P=_.target;P instanceof Element&&se(P,r,f.selector)&&$()};r.addEventListener("click",R),C.push(()=>r.removeEventListener("click",R));return}if(f.type==="form_submit"){let R=_=>{_.target instanceof HTMLFormElement&&r.contains(_.target)&&$()};r.addEventListener("submit",R),C.push(()=>r.removeEventListener("submit",R));return}if(f.type==="scroll_depth"){let R=Math.max(0,Math.min(1,f.threshold)),_=new IntersectionObserver(P=>{for(let ce of P)if(ce.intersectionRatio>=R){$(),_.disconnect();break}},{threshold:[R]});_.observe(r),C.push(()=>_.disconnect())}}),()=>{for(let f of C)f()}}let l=()=>{w.current||(w.current=!0,t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:p,payload:{reward:1}}),t.goal(p,{componentId:e.id,variantId:i},1,0))},g=u.type==="composite"?u.all:[u],I=new Set(g.map((S,C)=>C)),h=S=>{I.delete(S),I.size===0&&l()},W=[];return g.forEach((S,C)=>{if(S.type==="click"){let f=b=>{let k=b.target;k instanceof Element&&se(k,r,S.selector)&&(u.type==="composite"?h(C):l())};r.addEventListener("click",f),W.push(()=>r.removeEventListener("click",f));return}if(S.type==="form_submit"){let f=b=>{b.target instanceof HTMLFormElement&&r.contains(b.target)&&(u.type==="composite"?h(C):l())};r.addEventListener("submit",f),W.push(()=>r.removeEventListener("submit",f));return}if(S.type==="scroll_depth"){let f=Math.max(0,Math.min(1,S.threshold)),b=new IntersectionObserver(k=>{for(let F of k)if(F.intersectionRatio>=f){u.type==="composite"?h(C):l(),b.disconnect();break}},{threshold:[f]});b.observe(r),W.push(()=>b.disconnect());return}}),()=>{for(let S of W)S()}},[t,i,n,e.id,u,p]),e.clientOnly&&(!a||!t)||!i)return null;let y=(d=e.variants[i])!=null?d:null,G=y===null?A:null;return((v=process==null?void 0:process.env)==null?void 0:v.NODE_ENV)!=="production"&&y===null&&G===null&&console.warn(`[sentient] <Adaptive id="${e.id}"> was assigned variant "${i}" but no matching key exists in props.variants. If this is a dashboard-managed text variant, use <AdaptiveText id="${e.id}"> instead.`),Te("div",{ref:c,"data-sentient-id":e.id,"data-sentient-variant":i,children:y!=null?y:G})}var Oe=Ce(_e,(e,t)=>{if(e.id!==t.id||JSON.stringify(e.goal)!==JSON.stringify(t.goal)||e.microSignalGoals!==t.microSignalGoals)return!1;if(e.variants===t.variants)return!0;let n=Object.keys(e.variants),s=Object.keys(t.variants);return n.length!==s.length?!1:n.every(i=>i in t.variants)});import{useEffect as ae,useRef as We,useState as le}from"react";import{jsx as Fe}from"react/jsx-runtime";function Me({id:e,default:t,component:n="span",className:s}){let i=E(),A=N(),c=V(),a=K(),m=We(null),[w,x]=le(()=>{var u,p;return(p=(u=i==null?void 0:i.getAssignment(e,a))==null?void 0:u.content)!=null?p:null}),[o,L]=le(()=>{var u,p;return(p=(u=i==null?void 0:i.getAssignment(e,a))==null?void 0:u.variantId)!=null?p:null});return ae(()=>{var p;if(!i||((p=i.getAssignment(e,a))==null?void 0:p.content)!==void 0)return;let u=!1;return i.assign(e).then(y=>{var G;if(!u){if(!y){((G=process==null?void 0:process.env)==null?void 0:G.NODE_ENV)!=="production"&&console.warn(`[sentient] <AdaptiveText id="${e}"> assignment failed \u2014 showing default text.`);return}L(y.variantId),y.content&&x(y.content)}}),()=>{u=!0}},[i,e,a]),ae(()=>{!i||!o||!A||m.current!==o&&(m.current=o,i.track({projectId:A,componentId:e,variantId:o,eventType:"variant_assigned",payload:{}}),c==null||c(e,o))},[i,o,A,e,c]),Fe(n,{className:s,children:w!=null?w:t})}import{useCallback as Pe}from"react";function De(e){let t=E();return Pe((n,s)=>{t==null||t.componentGoal(e,n,s)},[t,e])}import{deriveSessionSegment as Ne}from"@sentientui/core";export{Oe as Adaptive,Se as AdaptiveProvider,Me as AdaptiveText,Ne as detectSegment,X as getWeights,B as subscribeWeights,J as updateWeights,De as useAdaptiveGoal,q as useAssignment,z as useInitialAssignments,Ae as useLayoutOrder,E as useSentient};
|
|
3
3
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.tsx","../src/weights-store.ts","../src/adaptive.tsx","../src/use-assignment.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, useMemo, useState, type ReactNode } from 'react';\nimport {\n detectDeviceClass,\n detectTrafficSource,\n init,\n type SentientClient,\n type SentientConfig,\n} from '@sentientui/core';\nimport { update as updateWeightsStore, type ComponentWeights } from './weights-store.js';\n\n/**\n * Mirrors the segment derivation inside core `init()` so the cache key used\n * by `useAssignment` always matches the key `assign()` writes under. Before\n * this, the context defaulted to 'desktop:direct' while core used the\n * detected segment — a systematic cache miss for every integration that\n * didn't pass `sessionSegment` explicitly.\n */\nfunction deriveDefaultSegment(): string {\n if (typeof window === 'undefined') return 'desktop:direct';\n try {\n const device = detectDeviceClass(navigator.userAgent ?? '');\n const source = detectTrafficSource(document.referrer ?? '', window.location.origin);\n return `${device}:${source}`;\n } catch {\n return 'desktop:direct';\n }\n}\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 * Behavior before consent is granted. Pass `'statistical_winner'` to serve the\n * best-performing variant via `GET /v1/winner` with zero tracking while the\n * consent banner is showing. Requires `consent: false`.\n * @see SentientConfig.preConsentBehavior\n */\n preConsentBehavior?: 'statistical_winner' | 'control';\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 /**\n * Session ID generated during SSR (the `sessionId` field returned by\n * `loadAdaptiveAssignments` / `loadAdaptiveDecision`). When provided and no\n * existing session cookie or localStorage entry is found, the client adopts\n * this ID so events and goals are attributed to the same session the server\n * used for variant assignment.\n */\n ssrSessionId?: string;\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 // Derived once per mount: identical to what core init() computes, so cache\n // reads (context segment) and cache writes (core segment) always agree.\n const [sessionSegment] = useState(() => props.sessionSegment ?? deriveDefaultSegment());\n\n useEffect(() => {\n // When consent is explicitly false with no preConsentBehavior, tear down any existing client.\n if (props.consent === false && !props.preConsentBehavior) {\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,\n consent: props.consent,\n preConsentBehavior: props.preConsentBehavior,\n ssrSessionId: props.ssrSessionId,\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 // Poll /v1/weights every 60 s so long-lived sessions see updated bandit weights\n // without a page reload. useAssignment subscribers react via weights-store.\n useEffect(() => {\n if (!client) return;\n let cancelled = false;\n const poll = async (): Promise<void> => {\n if (cancelled) return;\n let entries;\n try {\n entries = await client.fetchWeights();\n } catch {\n // Network/transient error: skip this cycle and retry on the next\n // interval. Swallowed deliberately so a failed poll never surfaces as\n // an unhandled rejection.\n return;\n }\n if (cancelled) return;\n for (const entry of entries) {\n const weights: ComponentWeights = {\n componentId: entry.componentId,\n updatedAt: entry.updatedAt,\n variants: entry.variants.map((v) => ({\n variantId: v.variantId,\n pulls: v.pulls,\n avgReward: v.avgReward ?? 0,\n })),\n };\n updateWeightsStore(entry.componentId, weights);\n }\n };\n void poll();\n const timerId = setInterval(() => void poll(), 60_000);\n return () => {\n cancelled = true;\n clearInterval(timerId);\n };\n }, [client]);\n\n const ssrFallback = props.ssrFallback ?? 'first';\n\n // Memoized so unrelated parent re-renders don't cascade through every\n // useSentient / useAssignment consumer via a fresh context object.\n const value = useMemo<AdaptiveContextValue>(\n () => ({\n client,\n apiKey: props.apiKey,\n initialAssignments: props.initialAssignments ?? {},\n sessionSegment,\n ssrFallback,\n onAssignment: props.onAssignment,\n initialLayoutOrder: props.initialLayoutOrder ?? null,\n }),\n [\n client,\n props.apiKey,\n props.initialAssignments,\n sessionSegment,\n ssrFallback,\n props.onAssignment,\n props.initialLayoutOrder,\n ],\n );\n\n return (\n <AdaptiveContext.Provider value={value}>\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 1.0.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 console.warn(\n '[@sentientui/react] useAdaptiveProjectId is deprecated; use useAdaptiveApiKey. Will be removed in 1.0.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","/** 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\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { memo, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport { attachMicroSignalDetectors, type MicroSignalType } from '@sentientui/core';\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 WeightedStep = { goal: GoalConfig; name: string; weight: number };\nexport type WeightedCompositeGoal = { type: 'weighted_composite'; steps: WeightedStep[] };\nexport type GoalConfig = ScrollDepthGoal | ClickGoal | FormSubmitGoal | CompositeGoal | WeightedCompositeGoal;\n\n/** Maps a detected micro-signal to a named session goal (`client.goal`). */\nexport type MicroSignalGoalConfig = string | { name: string; weight?: number; stepIndex?: number };\nexport type MicroSignalGoals = Partial<Record<MicroSignalType, MicroSignalGoalConfig>>;\n\nexport type AdaptiveProps = {\n id: string;\n variants: Record<string, ReactNode>;\n goal: string | GoalConfig;\n /**\n * When a passive micro-signal fires on this component, also record a named goal.\n * Use for inferred goals surfaced in the dashboard (e.g. rage_click → 'confused_by_hero').\n */\n microSignalGoals?: MicroSignalGoals;\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 * Variant-specific structured data for AI agent consumption via /sentient.json and\n * GET /v1/agent/layout. Keyed by variant ID — only the assigned variant's entry is stored,\n * so agents see only the content currently being served to visitors.\n *\n * Prefer this over `agentData` when variants have meaningfully different content.\n */\n agentDataByVariant?: Record<string, unknown>;\n /** @deprecated Use agentDataByVariant for variant-specific content. Stored as-is for the assigned variant. */\n agentData?: unknown;\n};\n\nfunction normalize(goal: string | GoalConfig): GoalConfig {\n if (typeof goal === 'string') return { type: 'click' };\n return goal;\n}\n\nconst PREVIEW_HTML_MAX_CHARS = 30_000;\n\n/**\n * previewHtml is the largest payload the SDK sends (up to 30 KB). The server\n * keeps the first-seen preview per (component, variant), so re-sending it on\n * every mount/navigation is pure waste — send it once per browser session.\n */\nfunction shouldSendPreviewHtml(componentId: string, variantId: string): boolean {\n try {\n const key = `_snt_pv_${componentId}_${variantId}`;\n if (sessionStorage.getItem(key)) return false;\n sessionStorage.setItem(key, '1');\n return true;\n } catch {\n // Storage unavailable (private mode, quota) — keep prior behavior.\n return true;\n }\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, selector?: string): boolean {\n if (selector) {\n try {\n let cursor: Element | null = start;\n while (cursor && cursor !== container) {\n if (cursor.matches(selector)) return true;\n cursor = cursor.parentElement;\n }\n } catch {\n // Invalid CSS selector — treat as no match rather than breaking all click handlers.\n }\n return false;\n }\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, props.agentData, props.agentDataByVariant);\n const containerRef = useRef<HTMLDivElement>(null);\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => { setMounted(true); }, []);\n const goalFiredRef = useRef(false);\n const microGoalFiredRef = useRef<Set<MicroSignalType>>(new Set());\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 const includePreview = rawHtml !== '' && shouldSendPreviewHtml(props.id, variantId);\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId,\n eventType: 'variant_assigned',\n payload: includePreview ? { previewHtml: rawHtml.slice(0, PREVIEW_HTML_MAX_CHARS) } : {},\n });\n }, [client, variantId, apiKey, props.id, mounted]);\n\n // Reset goal latch when variant changes.\n useEffect(() => {\n goalFiredRef.current = false;\n microGoalFiredRef.current = new Set();\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 micro-signal detectors passively — rage click, text copy, scroll hesitation, tab loss.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n const assignedAt = Date.now();\n return attachMicroSignalDetectors(\n (signalType, extra = {}) => {\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'micro_signal',\n payload: { signalType, ...extra },\n });\n\n const mapping = props.microSignalGoals?.[signalType];\n if (!mapping || microGoalFiredRef.current.has(signalType)) return;\n microGoalFiredRef.current.add(signalType);\n const name = typeof mapping === 'string' ? mapping : mapping.name;\n const weight = typeof mapping === 'string' ? 1.0 : (mapping.weight ?? 1.0);\n const stepIndex = typeof mapping === 'string' ? 0 : (mapping.stepIndex ?? 0);\n client.goal(name, { signalType, ...extra }, weight, stepIndex);\n },\n node,\n assignedAt,\n );\n }, [client, variantId, apiKey, props.id, props.microSignalGoals]);\n\n // Attach goal tracking.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n\n // --- Weighted composite: each step fires independently as it completes ---\n if (goal.type === 'weighted_composite') {\n const firedSteps = new Set<number>();\n const wcCleanups: Array<() => void> = [];\n\n goal.steps.forEach(({ goal: sub, name: stepName, weight: stepWeight }, idx) => {\n const fireStep = (): void => {\n if (firedSteps.has(idx)) return;\n firedSteps.add(idx);\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'goal_achieved',\n goalType: stepName,\n payload: { reward: stepWeight },\n });\n client.goal(stepName, {}, stepWeight, idx);\n };\n\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, sub.selector)) return;\n fireStep();\n };\n node.addEventListener('click', onClick);\n wcCleanups.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 fireStep();\n };\n node.addEventListener('submit', onSubmit);\n wcCleanups.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 fireStep();\n io.disconnect();\n break;\n }\n }\n },\n { threshold: [threshold] },\n );\n io.observe(node);\n wcCleanups.push(() => io.disconnect());\n }\n });\n\n return () => {\n for (const c of wcCleanups) c();\n };\n }\n // --- End weighted composite ---\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, sub.selector)) 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.microSignalGoals !== next.microSignalGoals) 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[], agentData?: unknown, agentDataByVariant?: Record<string, unknown>): 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 const overrideLoggedRef = useRef<string | null>(null);\n if (overrideVariant && overrideLoggedRef.current !== overrideVariant) {\n overrideLoggedRef.current = 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, agentData, agentDataByVariant).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","'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { AssignResult } from '@sentientui/core';\nimport { useSentient, useAdaptiveApiKey, useOnAssignment, useSessionSegment } 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 segment = useSessionSegment();\n const trackedRef = useRef<string | null>(null);\n\n // Seed from cache synchronously so remounts don't flash back to defaultText.\n const [text, setText] = useState<string | null>(() =>\n client?.getAssignment(id, segment)?.content ?? null\n );\n const [variantId, setVariantId] = useState<string | null>(() =>\n client?.getAssignment(id, segment)?.variantId ?? null\n );\n\n useEffect(() => {\n if (!client) return;\n // Skip if content already cached; assign() re-checks internally but this avoids the async round-trip on remount.\n if (client.getAssignment(id, segment)?.content !== undefined) return;\n\n let cancelled = false;\n void client.assign(id).then((result: AssignResult | null) => {\n if (cancelled) return;\n if (!result) {\n if (process?.env?.NODE_ENV !== 'production') {\n console.warn(`[sentient] <AdaptiveText id=\"${id}\"> assignment failed — showing default text.`);\n }\n return;\n }\n setVariantId(result.variantId);\n if (result.content) setText(result.content);\n });\n return () => {\n cancelled = true;\n };\n }, [client, id, segment]);\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":["createContext","useContext","useEffect","useMemo","useState","detectDeviceClass","detectTrafficSource","init","store","listeners","subscribe","componentId","cb","set","update","weights","e","getWeights","_a","jsx","deriveDefaultSegment","_a","_b","device","detectDeviceClass","source","detectTrafficSource","e","AdaptiveContext","createContext","AdaptiveProvider","props","client","setClient","useState","sessionSegment","useEffect","prev","c","init","cancelled","poll","entries","entry","weights","v","update","timerId","ssrFallback","value","useMemo","useSentient","useContext","useAdaptiveApiKey","useInitialAssignments","useContext","AdaptiveContext","useSessionSegment","useSsrFallback","useOnAssignment","useLayoutOrder","memo","useEffect","useMemo","useRef","useState","attachMicroSignalDetectors","useEffect","useRef","useState","getDevOverride","componentId","_a","global","params","raw","sep","e","pickFromWeights","weights","variantIds","best","v","useAssignment","agentData","agentDataByVariant","initialAssignments","useInitialAssignments","ssrFallback","useSsrFallback","client","useSentient","segment","useSessionSegment","onAssignment","useOnAssignment","assignmentReportedRef","useRef","devOverride","overrideVariant","overrideLoggedRef","initial","_b","preloaded","cached","getWeights","chosen","state","setState","useState","reportAssignment","variantId","useEffect","prev","cancelled","result","subscribe","jsx","normalize","goal","PREVIEW_HTML_MAX_CHARS","shouldSendPreviewHtml","componentId","variantId","key","e","isClickableTarget","el","tag","findClickable","start","container","selector","cursor","AdaptiveImpl","props","_a","_b","client","useSentient","apiKey","useAdaptiveApiKey","variantIds","useMemo","content","useAssignment","containerRef","useRef","mounted","setMounted","useState","useEffect","goalFiredRef","microGoalFiredRef","assignTrackedRef","goalLabel","rawHtml","includePreview","node","timerId","hoverStart","onEnter","onLeave","assignedAt","attachMicroSignalDetectors","signalType","extra","_c","__spreadValues","mapping","name","weight","stepIndex","firedSteps","wcCleanups","sub","stepName","stepWeight","idx","fireStep","onClick","target","onSubmit","threshold","io","entries","entry","c","fireGoal","subgoals","remaining","_","i","checkComposite","cleanups","jsxContent","managedContent","Adaptive","memo","prev","next","prevKeys","nextKeys","k","useEffect","useRef","useState","jsx","AdaptiveText","id","defaultText","Tag","className","client","useSentient","apiKey","useAdaptiveApiKey","onAssignment","useOnAssignment","segment","useSessionSegment","trackedRef","useRef","text","setText","useState","_a","_b","variantId","setVariantId","useEffect","cancelled","result","deriveSessionSegment"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx","../src/weights-store.ts","../src/adaptive.tsx","../src/use-assignment.ts","../src/adaptive-text.tsx","../src/use-adaptive-goal.ts","../src/segment.ts"],"sourcesContent":["'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';\nimport {\n detectDeviceClass,\n detectTrafficSource,\n init,\n type SentientClient,\n type SentientConfig,\n} from '@sentientui/core';\nimport { update as updateWeightsStore, type ComponentWeights } from './weights-store.js';\n\n/**\n * Mirrors the segment derivation inside core `init()` so the cache key used\n * by `useAssignment` always matches the key `assign()` writes under. Before\n * this, the context defaulted to 'desktop:direct' while core used the\n * detected segment — a systematic cache miss for every integration that\n * didn't pass `sessionSegment` explicitly.\n */\nfunction deriveDefaultSegment(): string {\n if (typeof window === 'undefined') return 'desktop:direct';\n try {\n const device = detectDeviceClass(navigator.userAgent ?? '');\n const source = detectTrafficSource(document.referrer ?? '', window.location.origin);\n return `${device}:${source}`;\n } catch {\n return 'desktop:direct';\n }\n}\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 * Behavior before consent is granted. Pass `'statistical_winner'` to serve the\n * best-performing variant via `GET /v1/winner` with zero tracking while the\n * consent banner is showing. Requires `consent: false`.\n * @see SentientConfig.preConsentBehavior\n */\n preConsentBehavior?: 'statistical_winner' | 'control';\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 /**\n * Session ID generated during SSR (the `sessionId` field returned by\n * `loadAdaptiveAssignments` / `loadAdaptiveDecision`). When provided and no\n * existing session cookie or localStorage entry is found, the client adopts\n * this ID so events and goals are attributed to the same session the server\n * used for variant assignment.\n */\n ssrSessionId?: string;\n /**\n * ISO 3166-1 alpha-2 country code. Pass the value of the `CF-IPCountry`\n * header from your Next.js server component to populate country on landing\n * sessions without client-side geo lookup.\n */\n country?: string;\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 // Derived once per mount: identical to what core init() computes, so cache\n // reads (context segment) and cache writes (core segment) always agree.\n const [sessionSegment] = useState(() => props.sessionSegment ?? deriveDefaultSegment());\n\n useEffect(() => {\n // When consent is explicitly false with no preConsentBehavior, tear down any existing client.\n if (props.consent === false && !props.preConsentBehavior) {\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,\n consent: props.consent,\n preConsentBehavior: props.preConsentBehavior,\n ssrSessionId: props.ssrSessionId,\n country: props.country,\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 // Poll /v1/weights every 60 s so long-lived sessions see updated bandit weights\n // without a page reload. useAssignment subscribers react via weights-store.\n useEffect(() => {\n if (!client) return;\n let cancelled = false;\n const poll = async (): Promise<void> => {\n if (cancelled) return;\n let entries;\n try {\n entries = await client.fetchWeights();\n } catch {\n // Network/transient error: skip this cycle and retry on the next\n // interval. Swallowed deliberately so a failed poll never surfaces as\n // an unhandled rejection.\n return;\n }\n if (cancelled) return;\n for (const entry of entries) {\n const weights: ComponentWeights = {\n componentId: entry.componentId,\n updatedAt: entry.updatedAt,\n variants: entry.variants.map((v) => ({\n variantId: v.variantId,\n pulls: v.pulls,\n avgReward: v.avgReward ?? 0,\n })),\n };\n updateWeightsStore(entry.componentId, weights);\n }\n };\n void poll();\n const timerId = setInterval(() => void poll(), 60_000);\n return () => {\n cancelled = true;\n clearInterval(timerId);\n };\n }, [client]);\n\n const ssrFallback = props.ssrFallback ?? 'first';\n\n // Memoized so unrelated parent re-renders don't cascade through every\n // useSentient / useAssignment consumer via a fresh context object.\n const value = useMemo<AdaptiveContextValue>(\n () => ({\n client,\n apiKey: props.apiKey,\n initialAssignments: props.initialAssignments ?? {},\n sessionSegment,\n ssrFallback,\n onAssignment: props.onAssignment,\n initialLayoutOrder: props.initialLayoutOrder ?? null,\n }),\n [\n client,\n props.apiKey,\n props.initialAssignments,\n sessionSegment,\n ssrFallback,\n props.onAssignment,\n props.initialLayoutOrder,\n ],\n );\n\n return (\n <AdaptiveContext.Provider value={value}>\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 1.0.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 console.warn(\n '[@sentientui/react] useAdaptiveProjectId is deprecated; use useAdaptiveApiKey. Will be removed in 1.0.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","/** 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\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { memo, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport { attachMicroSignalDetectors, type MicroSignalType } from '@sentientui/core';\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 WeightedStep = { goal: GoalConfig; name: string; weight: number };\nexport type WeightedCompositeGoal = { type: 'weighted_composite'; steps: WeightedStep[] };\nexport type GoalConfig = ScrollDepthGoal | ClickGoal | FormSubmitGoal | CompositeGoal | WeightedCompositeGoal;\n\n/** Maps a detected micro-signal to a named session goal (`client.goal`). */\nexport type MicroSignalGoalConfig = string | { name: string; weight?: number; stepIndex?: number };\nexport type MicroSignalGoals = Partial<Record<MicroSignalType, MicroSignalGoalConfig>>;\n\nexport type AdaptiveProps = {\n id: string;\n variants: Record<string, ReactNode>;\n goal: string | GoalConfig;\n /**\n * When a passive micro-signal fires on this component, also record a named goal.\n * Use for inferred goals surfaced in the dashboard (e.g. rage_click → 'confused_by_hero').\n */\n microSignalGoals?: MicroSignalGoals;\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 * Variant-specific structured data for AI agent consumption via /sentient.json and\n * GET /v1/agent/layout. Keyed by variant ID — only the assigned variant's entry is stored,\n * so agents see only the content currently being served to visitors.\n *\n * Prefer this over `agentData` when variants have meaningfully different content.\n */\n agentDataByVariant?: Record<string, unknown>;\n /** @deprecated Use agentDataByVariant for variant-specific content. Stored as-is for the assigned variant. */\n agentData?: unknown;\n};\n\nfunction normalize(goal: string | GoalConfig): GoalConfig {\n if (typeof goal === 'string') return { type: 'click' };\n return goal;\n}\n\nconst PREVIEW_HTML_MAX_CHARS = 30_000;\n\n/**\n * previewHtml is the largest payload the SDK sends (up to 30 KB). The server\n * keeps the first-seen preview per (component, variant), so re-sending it on\n * every mount/navigation is pure waste — send it once per browser session.\n */\nfunction shouldSendPreviewHtml(componentId: string, variantId: string): boolean {\n try {\n const key = `_snt_pv_${componentId}_${variantId}`;\n if (sessionStorage.getItem(key)) return false;\n sessionStorage.setItem(key, '1');\n return true;\n } catch {\n // Storage unavailable (private mode, quota) — keep prior behavior.\n return true;\n }\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, selector?: string): boolean {\n if (selector) {\n try {\n let cursor: Element | null = start;\n while (cursor && cursor !== container) {\n if (cursor.matches(selector)) return true;\n cursor = cursor.parentElement;\n }\n } catch {\n // Invalid CSS selector — treat as no match rather than breaking all click handlers.\n }\n return false;\n }\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, props.agentData, props.agentDataByVariant);\n const containerRef = useRef<HTMLDivElement>(null);\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => { setMounted(true); }, []);\n const goalFiredRef = useRef(false);\n const microGoalFiredRef = useRef<Set<MicroSignalType>>(new Set());\n const assignTrackedRef = useRef<string | null>(null);\n const goalKey = typeof props.goal === 'string' ? props.goal : JSON.stringify(props.goal);\n const goal = useMemo(() => normalize(props.goal), [goalKey]);\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 // Wait until content is in the DOM; will re-run when mounted changes.\n if (!rawHtml) return;\n assignTrackedRef.current = variantId;\n const includePreview = rawHtml !== '' && shouldSendPreviewHtml(props.id, variantId);\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId,\n eventType: 'variant_assigned',\n payload: includePreview ? { previewHtml: rawHtml.slice(0, PREVIEW_HTML_MAX_CHARS) } : {},\n });\n }, [client, variantId, apiKey, props.id, mounted, content]);\n\n // Reset goal latch when variant or goal changes.\n useEffect(() => {\n goalFiredRef.current = false;\n microGoalFiredRef.current = new Set();\n }, [variantId, goal]);\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 micro-signal detectors passively — rage click, text copy, scroll hesitation, tab loss.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n const assignedAt = Date.now();\n return attachMicroSignalDetectors(\n (signalType, extra = {}) => {\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'micro_signal',\n payload: { signalType, ...extra },\n });\n\n const mapping = props.microSignalGoals?.[signalType];\n if (!mapping || microGoalFiredRef.current.has(signalType)) return;\n microGoalFiredRef.current.add(signalType);\n const name = typeof mapping === 'string' ? mapping : mapping.name;\n const weight = typeof mapping === 'string' ? 1.0 : (mapping.weight ?? 1.0);\n const stepIndex = typeof mapping === 'string' ? 0 : (mapping.stepIndex ?? 0);\n client.goal(name, { signalType, ...extra }, weight, stepIndex);\n },\n node,\n assignedAt,\n );\n }, [client, variantId, apiKey, props.id, props.microSignalGoals]);\n\n // Attach goal tracking.\n useEffect(() => {\n if (!client || !variantId) return;\n const node = containerRef.current;\n if (!node) return;\n\n // --- Weighted composite: each step fires independently as it completes ---\n if (goal.type === 'weighted_composite') {\n const firedSteps = new Set<number>();\n const wcCleanups: Array<() => void> = [];\n\n goal.steps.forEach(({ goal: sub, name: stepName, weight: stepWeight }, idx) => {\n const fireStep = (): void => {\n if (firedSteps.has(idx)) return;\n firedSteps.add(idx);\n client.track({\n projectId: apiKey,\n componentId: props.id,\n variantId: variantId!,\n eventType: 'goal_achieved',\n goalType: stepName,\n payload: { reward: stepWeight },\n });\n client.goal(stepName, {}, stepWeight, idx);\n };\n\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, sub.selector)) return;\n fireStep();\n };\n node.addEventListener('click', onClick);\n wcCleanups.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 fireStep();\n };\n node.addEventListener('submit', onSubmit);\n wcCleanups.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 fireStep();\n io.disconnect();\n break;\n }\n }\n },\n { threshold: [threshold] },\n );\n io.observe(node);\n wcCleanups.push(() => io.disconnect());\n }\n });\n\n return () => {\n for (const c of wcCleanups) c();\n };\n }\n // --- End weighted composite ---\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 client.goal(goalLabel, { componentId: props.id, variantId }, 1.0, 0);\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, sub.selector)) 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 (JSON.stringify(prev.goal) !== JSON.stringify(next.goal)) return false;\n if (prev.microSignalGoals !== next.microSignalGoals) 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[], agentData?: unknown, agentDataByVariant?: Record<string, unknown>): 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 const overrideLoggedRef = useRef<string | null>(null);\n if (overrideVariant && overrideLoggedRef.current !== overrideVariant) {\n overrideLoggedRef.current = 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, agentData, agentDataByVariant).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","'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { AssignResult } from '@sentientui/core';\nimport { useSentient, useAdaptiveApiKey, useOnAssignment, useSessionSegment } 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 segment = useSessionSegment();\n const trackedRef = useRef<string | null>(null);\n\n // Seed from cache synchronously so remounts don't flash back to defaultText.\n const [text, setText] = useState<string | null>(() =>\n client?.getAssignment(id, segment)?.content ?? null\n );\n const [variantId, setVariantId] = useState<string | null>(() =>\n client?.getAssignment(id, segment)?.variantId ?? null\n );\n\n useEffect(() => {\n if (!client) return;\n // Skip if content already cached; assign() re-checks internally but this avoids the async round-trip on remount.\n if (client.getAssignment(id, segment)?.content !== undefined) return;\n\n let cancelled = false;\n void client.assign(id).then((result: AssignResult | null) => {\n if (cancelled) return;\n if (!result) {\n if (process?.env?.NODE_ENV !== 'production') {\n console.warn(`[sentient] <AdaptiveText id=\"${id}\"> assignment failed — showing default text.`);\n }\n return;\n }\n setVariantId(result.variantId);\n if (result.content) setText(result.content);\n });\n return () => {\n cancelled = true;\n };\n }, [client, id, segment]);\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","import { useCallback } from 'react';\nimport type { ComponentGoalOptions } from '@sentientui/core';\nimport { useSentient } from './provider.js';\n\nexport type FireGoal = (goalType: string, opts?: ComponentGoalOptions) => void;\n\n/**\n * Returns a `fireGoal(goalType, opts?)` callback that records a conversion\n * attributed to the variant currently served for `componentId` — so it shows\n * up in the per-variant CVR funnel with no manual variantId/projectId plumbing.\n *\n * The served variant is resolved from the SDK's assignment cache (the same one\n * `<Adaptive id={componentId}>` populates), so render that component before\n * firing. Use this for imperative handlers (click, form submit, custom events);\n * for purely declarative goals prefer `<Adaptive goal={...}>`.\n *\n * @example\n * const fireContact = useAdaptiveGoal('hero_headline');\n * <button onClick={() => fireContact('hero_contact', { metadata: { method } })}>Call</button>\n */\nexport function useAdaptiveGoal(componentId: string): FireGoal {\n const client = useSentient();\n return useCallback<FireGoal>(\n (goalType, opts) => {\n client?.componentGoal(componentId, goalType, opts);\n },\n [client, componentId],\n );\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":["createContext","useContext","useEffect","useMemo","useState","detectDeviceClass","detectTrafficSource","init","store","listeners","subscribe","componentId","cb","set","update","weights","e","getWeights","_a","jsx","deriveDefaultSegment","_a","_b","device","detectDeviceClass","source","detectTrafficSource","e","AdaptiveContext","createContext","AdaptiveProvider","props","client","setClient","useState","sessionSegment","useEffect","prev","c","init","cancelled","poll","entries","entry","weights","v","update","timerId","ssrFallback","value","useMemo","useSentient","useContext","useAdaptiveApiKey","useInitialAssignments","useContext","AdaptiveContext","useSessionSegment","useSsrFallback","useOnAssignment","useLayoutOrder","memo","useEffect","useMemo","useRef","useState","attachMicroSignalDetectors","useEffect","useRef","useState","getDevOverride","componentId","_a","global","params","raw","sep","e","pickFromWeights","weights","variantIds","best","v","useAssignment","agentData","agentDataByVariant","initialAssignments","useInitialAssignments","ssrFallback","useSsrFallback","client","useSentient","segment","useSessionSegment","onAssignment","useOnAssignment","assignmentReportedRef","useRef","devOverride","overrideVariant","overrideLoggedRef","initial","_b","preloaded","cached","getWeights","chosen","state","setState","useState","reportAssignment","variantId","useEffect","prev","cancelled","result","subscribe","jsx","normalize","goal","PREVIEW_HTML_MAX_CHARS","shouldSendPreviewHtml","componentId","variantId","key","e","isClickableTarget","el","tag","findClickable","start","container","selector","cursor","AdaptiveImpl","props","_a","_b","client","useSentient","apiKey","useAdaptiveApiKey","variantIds","useMemo","content","useAssignment","containerRef","useRef","mounted","setMounted","useState","useEffect","goalFiredRef","microGoalFiredRef","assignTrackedRef","goalKey","goalLabel","rawHtml","includePreview","node","timerId","hoverStart","onEnter","onLeave","assignedAt","attachMicroSignalDetectors","signalType","extra","_c","__spreadValues","mapping","name","weight","stepIndex","firedSteps","wcCleanups","sub","stepName","stepWeight","idx","fireStep","onClick","target","onSubmit","threshold","io","entries","entry","c","fireGoal","subgoals","remaining","_","i","checkComposite","cleanups","jsxContent","managedContent","Adaptive","memo","prev","next","prevKeys","nextKeys","k","useEffect","useRef","useState","jsx","AdaptiveText","id","defaultText","Tag","className","client","useSentient","apiKey","useAdaptiveApiKey","onAssignment","useOnAssignment","segment","useSessionSegment","trackedRef","useRef","text","setText","useState","_a","_b","variantId","setVariantId","useEffect","cancelled","result","useCallback","useAdaptiveGoal","componentId","client","useSentient","useCallback","goalType","opts","deriveSessionSegment"]}
|
|
@@ -1,65 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
/** How to render adaptive slots during SSR when assignments are not preloaded. */
|
|
5
|
-
type SsrFallback = 'first' | 'none';
|
|
6
|
-
type AdaptiveProviderProps = {
|
|
7
|
-
apiKey: string;
|
|
8
|
-
context: SentientConfig['context'];
|
|
9
|
-
debug?: boolean;
|
|
10
|
-
/**
|
|
11
|
-
* SSR-preloaded assignments from `preloadAssignments()` / `loadAdaptiveAssignments()`.
|
|
12
|
-
* Passed through to `useAssignment` as synchronous initial state so crawlers and
|
|
13
|
-
* the first paint see real content (recommended for SEO).
|
|
14
|
-
*/
|
|
15
|
-
initialAssignments?: Record<string, string>;
|
|
16
|
-
/**
|
|
17
|
-
* Bandit segment from SSR (`device:source`). Keeps cache, assign, and worker
|
|
18
|
-
* weights on one row — must match `loadAdaptiveAssignments` / session upsert.
|
|
19
|
-
*/
|
|
20
|
-
sessionSegment?: string;
|
|
21
|
-
/**
|
|
22
|
-
* When no `initialAssignments` exist for a component, `'first'` renders
|
|
23
|
-
* `variantIds[0]` in server HTML (safe default for SEO). Use `'none'` only for
|
|
24
|
-
* decorative slots marked `clientOnly`.
|
|
25
|
-
* @default 'first'
|
|
26
|
-
*/
|
|
27
|
-
ssrFallback?: SsrFallback;
|
|
28
|
-
/**
|
|
29
|
-
* Consent gate. When `false` the SDK is not initialised and no events are
|
|
30
|
-
* sent. Flip to `true` (e.g. after the user accepts the cookie banner) to
|
|
31
|
-
* initialise and begin tracking.
|
|
32
|
-
*/
|
|
33
|
-
consent?: boolean;
|
|
34
|
-
/**
|
|
35
|
-
* Behavior before consent is granted. Pass `'statistical_winner'` to serve the
|
|
36
|
-
* best-performing variant via `GET /v1/winner` with zero tracking while the
|
|
37
|
-
* consent banner is showing. Requires `consent: false`.
|
|
38
|
-
* @see SentientConfig.preConsentBehavior
|
|
39
|
-
*/
|
|
40
|
-
preConsentBehavior?: 'statistical_winner' | 'control';
|
|
41
|
-
/**
|
|
42
|
-
* Called once per component the first time a variant is resolved for that
|
|
43
|
-
* component in this session. Use to forward assignments to your own analytics
|
|
44
|
-
* (Mixpanel, PostHog, Segment, etc.) without having to wrap `useAssignment`.
|
|
45
|
-
*/
|
|
46
|
-
onAssignment?: (componentId: string, variantId: string) => void;
|
|
47
|
-
/**
|
|
48
|
-
* SSR-preloaded section order from `loadAdaptiveDecision()`.
|
|
49
|
-
* Pass the `layoutOrder` field from `DecideResult`. When set,
|
|
50
|
-
* `useLayoutOrder()` returns this on first render so there is no layout shift.
|
|
51
|
-
*/
|
|
52
|
-
initialLayoutOrder?: string[] | null;
|
|
53
|
-
/**
|
|
54
|
-
* Session ID generated during SSR (the `sessionId` field returned by
|
|
55
|
-
* `loadAdaptiveAssignments` / `loadAdaptiveDecision`). When provided and no
|
|
56
|
-
* existing session cookie or localStorage entry is found, the client adopts
|
|
57
|
-
* this ID so events and goals are attributed to the same session the server
|
|
58
|
-
* used for variant assignment.
|
|
59
|
-
*/
|
|
60
|
-
ssrSessionId?: string;
|
|
61
|
-
children: ReactNode;
|
|
62
|
-
};
|
|
2
|
+
import { AdaptiveProviderProps } from '@sentientui/react';
|
|
63
3
|
|
|
64
4
|
type AdaptiveRootClientProps = AdaptiveProviderProps & {
|
|
65
5
|
children: ReactNode;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
"use client";var
|
|
2
|
+
"use client";var d=Object.defineProperty;var r=Object.getOwnPropertySymbols;var v=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;var p=(o,e,t)=>e in o?d(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t,i=(o,e)=>{for(var t in e||(e={}))v.call(e,t)&&p(o,t,e[t]);if(r)for(var t of r(e))a.call(e,t)&&p(o,t,e[t]);return o};import{AdaptiveProvider as n}from"@sentientui/react";import{jsx as P}from"react/jsx-runtime";function c(o){return P(n,i({},o))}export{c as AdaptiveRootClient};
|
|
3
3
|
//# sourceMappingURL=adaptive-root-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/provider.tsx","../../src/weights-store.ts","../../src/next/adaptive-root-client.tsx"],"sourcesContent":["'use client';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nimport { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';\nimport {\n detectDeviceClass,\n detectTrafficSource,\n init,\n type SentientClient,\n type SentientConfig,\n} from '@sentientui/core';\nimport { update as updateWeightsStore, type ComponentWeights } from './weights-store.js';\n\n/**\n * Mirrors the segment derivation inside core `init()` so the cache key used\n * by `useAssignment` always matches the key `assign()` writes under. Before\n * this, the context defaulted to 'desktop:direct' while core used the\n * detected segment — a systematic cache miss for every integration that\n * didn't pass `sessionSegment` explicitly.\n */\nfunction deriveDefaultSegment(): string {\n if (typeof window === 'undefined') return 'desktop:direct';\n try {\n const device = detectDeviceClass(navigator.userAgent ?? '');\n const source = detectTrafficSource(document.referrer ?? '', window.location.origin);\n return `${device}:${source}`;\n } catch {\n return 'desktop:direct';\n }\n}\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 * Behavior before consent is granted. Pass `'statistical_winner'` to serve the\n * best-performing variant via `GET /v1/winner` with zero tracking while the\n * consent banner is showing. Requires `consent: false`.\n * @see SentientConfig.preConsentBehavior\n */\n preConsentBehavior?: 'statistical_winner' | 'control';\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 /**\n * Session ID generated during SSR (the `sessionId` field returned by\n * `loadAdaptiveAssignments` / `loadAdaptiveDecision`). When provided and no\n * existing session cookie or localStorage entry is found, the client adopts\n * this ID so events and goals are attributed to the same session the server\n * used for variant assignment.\n */\n ssrSessionId?: string;\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 // Derived once per mount: identical to what core init() computes, so cache\n // reads (context segment) and cache writes (core segment) always agree.\n const [sessionSegment] = useState(() => props.sessionSegment ?? deriveDefaultSegment());\n\n useEffect(() => {\n // When consent is explicitly false with no preConsentBehavior, tear down any existing client.\n if (props.consent === false && !props.preConsentBehavior) {\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,\n consent: props.consent,\n preConsentBehavior: props.preConsentBehavior,\n ssrSessionId: props.ssrSessionId,\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 // Poll /v1/weights every 60 s so long-lived sessions see updated bandit weights\n // without a page reload. useAssignment subscribers react via weights-store.\n useEffect(() => {\n if (!client) return;\n let cancelled = false;\n const poll = async (): Promise<void> => {\n if (cancelled) return;\n let entries;\n try {\n entries = await client.fetchWeights();\n } catch {\n // Network/transient error: skip this cycle and retry on the next\n // interval. Swallowed deliberately so a failed poll never surfaces as\n // an unhandled rejection.\n return;\n }\n if (cancelled) return;\n for (const entry of entries) {\n const weights: ComponentWeights = {\n componentId: entry.componentId,\n updatedAt: entry.updatedAt,\n variants: entry.variants.map((v) => ({\n variantId: v.variantId,\n pulls: v.pulls,\n avgReward: v.avgReward ?? 0,\n })),\n };\n updateWeightsStore(entry.componentId, weights);\n }\n };\n void poll();\n const timerId = setInterval(() => void poll(), 60_000);\n return () => {\n cancelled = true;\n clearInterval(timerId);\n };\n }, [client]);\n\n const ssrFallback = props.ssrFallback ?? 'first';\n\n // Memoized so unrelated parent re-renders don't cascade through every\n // useSentient / useAssignment consumer via a fresh context object.\n const value = useMemo<AdaptiveContextValue>(\n () => ({\n client,\n apiKey: props.apiKey,\n initialAssignments: props.initialAssignments ?? {},\n sessionSegment,\n ssrFallback,\n onAssignment: props.onAssignment,\n initialLayoutOrder: props.initialLayoutOrder ?? null,\n }),\n [\n client,\n props.apiKey,\n props.initialAssignments,\n sessionSegment,\n ssrFallback,\n props.onAssignment,\n props.initialLayoutOrder,\n ],\n );\n\n return (\n <AdaptiveContext.Provider value={value}>\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 1.0.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 console.warn(\n '[@sentientui/react] useAdaptiveProjectId is deprecated; use useAdaptiveApiKey. Will be removed in 1.0.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","/** 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 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":"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","names":["createContext","useContext","useEffect","useMemo","useState","detectDeviceClass","detectTrafficSource","init","store","listeners","update","componentId","weights","store","set","listeners","cb","e","jsx","deriveDefaultSegment","_a","_b","device","detectDeviceClass","source","detectTrafficSource","e","AdaptiveContext","createContext","AdaptiveProvider","props","client","setClient","useState","sessionSegment","useEffect","prev","c","init","cancelled","poll","entries","entry","weights","v","update","timerId","ssrFallback","value","useMemo","jsx","AdaptiveRootClient","props","AdaptiveProvider","__spreadValues"]}
|
|
1
|
+
{"version":3,"sources":["../../src/next/adaptive-root-client.tsx"],"sourcesContent":["'use client';\n\nimport type { ReactNode } from 'react';\n// Import from the package entry (kept `external` in tsup) — NOT the relative\n// '../provider.js'. A relative import makes tsup inline a second copy of\n// provider.js into this /next bundle, which runs createContext() again and\n// yields a DISTINCT AdaptiveContext. The result: <AdaptiveRoot> populates its\n// own context while useSentient()/<Adaptive> (from the main entry) read a\n// different one that stays {client:null} forever — so no events/goals ever\n// fire. Importing the package specifier shares the single context singleton.\nimport { AdaptiveProvider, type AdaptiveProviderProps } from '@sentientui/react';\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":";sWAUA,OAAS,oBAAAA,MAAoD,oBAQpD,cAAAC,MAAA,oBADF,SAASC,EAAmBC,EAA6C,CAC9E,OAAOF,EAACG,EAAAC,EAAA,GAAqBF,EAAO,CACtC","names":["AdaptiveProvider","jsx","AdaptiveRootClient","props","AdaptiveProvider","__spreadValues"]}
|
|
@@ -59,6 +59,12 @@ type AdaptiveProviderProps = {
|
|
|
59
59
|
* used for variant assignment.
|
|
60
60
|
*/
|
|
61
61
|
ssrSessionId?: string;
|
|
62
|
+
/**
|
|
63
|
+
* ISO 3166-1 alpha-2 country code. Pass the value of the `CF-IPCountry`
|
|
64
|
+
* header from your Next.js server component to populate country on landing
|
|
65
|
+
* sessions without client-side geo lookup.
|
|
66
|
+
*/
|
|
67
|
+
country?: string;
|
|
62
68
|
children: ReactNode;
|
|
63
69
|
};
|
|
64
70
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentientui/react",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dist"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@sentientui/core": "0.8.
|
|
30
|
+
"@sentientui/core": "0.8.2"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": ">=18.0.0",
|