@sentientui/react 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,33 +1,34 @@
1
1
  # @sentientui/react
2
2
 
3
- React SDK for [SentientUI](https://sentient-ui.com) — drop-in components and hooks that let a Thompson Sampling bandit, persona clusters, and visitor portraits automatically serve the best-performing UI variant (and section order) for each visitor.
4
-
5
- Learning, assignment, portraits, clustering, and graph storage all run on the SentientUI hosted API. You only install the SDK and add API keys from the dashboard.
3
+ React SDK for [SentientUI](https://sentient-ui.com) — the adaptive ladder. Declare bounded
4
+ variations (styles, content, arrangement order); a persona-keyed optimizer on the hosted API
5
+ learns which one converts best for each visitor type. Visit 1 learns; Visit 2 converts.
6
6
 
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install @sentientui/react
11
- # or
12
- pnpm add @sentientui/react
10
+ npm install @sentientui/react # or: npx sentientui init (scaffolds everything)
13
11
  ```
14
12
 
15
- `@sentientui/core` is a transitive dependency — you do not need to install it directly. Only import from `@sentientui/react`.
13
+ ## 60-second start (no account)
16
14
 
17
- ## API key
15
+ ```bash
16
+ npx sentientui init && npm run dev
17
+ # open http://localhost:3000?sentient_persona=buyer — watch the page adapt
18
+ ```
18
19
 
19
- Create a project at [sentient-ui.com](https://sentient-ui.com) and copy the API key shown once at project creation. It looks like `pk_xxxxxxxx…`. The key is safe to ship in browser bundles the API enforces an allowed-origins allowlist on every request. Add your production domain in Project → Settings → Allowed origins.
20
+ With no API key the SDK runs in keyless **local mode**: deterministic simulated decisions, zero
21
+ network. Add a `pk_…` key from [sentient-ui.com](https://sentient-ui.com) to learn from real
22
+ traffic:
20
23
 
21
24
  ```bash
22
25
  # .env.local
23
26
  NEXT_PUBLIC_SENTIENT_API_KEY=pk_your_key
24
27
  ```
25
28
 
26
- One env var is all you need. The SDK points at `https://api.sentient-ui.com` automatically — no ingest URL required.
27
-
28
- ## Quick start — Next.js App Router (recommended)
29
+ ## Setup
29
30
 
30
- Wrap your root layout with `<AdaptiveRoot>`. It is a Server Component: variant assignments and persona-specific layout order are resolved server-side before HTML is sent, so the first paint has the right content with no layout shift.
31
+ Wrap your root layout (Next App Router server component):
31
32
 
32
33
  ```tsx
33
34
  // app/layout.tsx
@@ -35,14 +36,9 @@ import { AdaptiveRoot } from '@sentientui/react/next';
35
36
 
36
37
  export default function RootLayout({ children }: { children: React.ReactNode }) {
37
38
  return (
38
- <html lang="en">
39
+ <html lang="en" suppressHydrationWarning>
39
40
  <body>
40
41
  <AdaptiveRoot
41
- components={[
42
- { id: 'hero_cta', variantIds: ['control', 'variant_a'] },
43
- { id: 'pricing', variantIds: ['monthly', 'annual_first'] },
44
- ]}
45
- sections={['hero', 'pricing', 'features', 'social_proof']}
46
42
  apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}
47
43
  appOrigin={process.env.NEXT_PUBLIC_APP_URL!}
48
44
  context="saas"
@@ -55,44 +51,116 @@ export default function RootLayout({ children }: { children: React.ReactNode })
55
51
  }
56
52
  ```
57
53
 
58
- Then drop `<Adaptive>` anywhere you want a tested component:
54
+ `suppressHydrationWarning` on `<html>` is required: an inline script (rendered by
55
+ `AdaptiveRoot` as its first child) sets the persona attributes before first paint, exactly like
56
+ the `next-themes` pattern. Other React apps (Vite, CRA, Remix, Pages Router) use
57
+ `<AdaptiveProvider>` from `@sentientui/react` with the same props.
58
+
59
+ ## The adaptive ladder
60
+
61
+ ### Rung 0 — Observe
62
+
63
+ Install is the integration. The dashboard immediately shows who is arriving: persona mix, device
64
+ and traffic-source segments, engagement signals. The "Suggested next step" card tells you which
65
+ rung to climb next, with a copy-pasteable snippet.
66
+
67
+ ### Rung 1 — Style (CSS only)
68
+
69
+ **1a. Persona attributes — zero declaration.** The SDK sets on `<html>`:
70
+
71
+ ```
72
+ data-sentient-persona = buyer | researcher | deal_seeker | browser | unknown
73
+ data-sentient-confidence = low | medium | high
74
+ ```
75
+
76
+ Write plain CSS against them (the canonical block — safe defaults for every persona):
77
+
78
+ ```css
79
+ /* Show each visitor type what it cares about. Confidence-gate bold treatments. */
80
+ html[data-sentient-persona='buyer'] .cta-primary { font-weight: 700; }
81
+ html[data-sentient-persona='researcher'] .spec-details { display: block; }
82
+ html[data-sentient-persona='deal_seeker'] .discount-banner { display: block; }
83
+ html[data-sentient-persona='browser'] .newsletter-nudge { display: block; }
84
+ html[data-sentient-confidence='low'] .discount-banner,
85
+ html[data-sentient-confidence='low'] .newsletter-nudge { display: none; }
86
+ ```
87
+
88
+ Force any persona in dev: `?sentient_persona=deal_seeker`.
89
+
90
+ **1b. Adaptive tokens — learned.** Declare a bounded design space; the optimizer picks per
91
+ visitor type; values arrive as element-scoped `data-*` props (SSR-serialized — zero flicker):
59
92
 
60
93
  ```tsx
61
- import { Adaptive } from '@sentientui/react';
94
+ import { useAdaptiveTokens } from '@sentientui/react';
62
95
 
63
- export default function Hero() {
64
- return (
65
- <Adaptive
66
- id="hero_cta"
67
- goal="signup_click"
68
- variants={{
69
- control: <button className="btn-dark">Start free trial</button>,
70
- variant_a: <button className="btn-blue">Get instant access →</button>,
71
- }}
72
- />
73
- );
96
+ function Hero() {
97
+ const t = useAdaptiveTokens('hero', {
98
+ tone: ['calm', 'urgent'], // first value = baseline (what you show today)
99
+ motion: ['none', 'pulse'],
100
+ });
101
+ return <section {...t.props} className="hero">…</section>;
102
+ // t.props → { 'data-tone': 'urgent', 'data-motion': 'pulse' }
103
+ // t.tokens → { tone: 'urgent', motion: 'pulse' }
104
+ }
105
+ ```
106
+
107
+ ```css
108
+ .hero[data-tone='urgent'] .cta { font-weight: 700; }
109
+
110
+ /* Animation values are enum values whose CSS you own — always respect reduced motion: */
111
+ .hero[data-motion='pulse'] .cta { animation: pulse 2s infinite; }
112
+ @media (prefers-reduced-motion: reduce) {
113
+ .hero[data-motion='pulse'] .cta { animation: none; }
74
114
  }
75
115
  ```
76
116
 
77
- ## Quick start other React apps (Vite, CRA, Remix, Pages Router)
117
+ Constraints: 1–4 dims, 2–6 values each, combinations 64. Enum values only — no arbitrary CSS.
118
+ Optional `{ goal: 'buy_click' }` third argument credits conversions to this element directly.
78
119
 
79
- Use `<AdaptiveProvider>` directly. Assignments are made on the client after mount no SSR preloading, but the bandit still learns normally.
120
+ ### Rung 2Swap (alternate content)
80
121
 
81
122
  ```tsx
82
- import { AdaptiveProvider } from '@sentientui/react';
83
-
84
- export default function App({ children }: { children: React.ReactNode }) {
85
- return (
86
- <AdaptiveProvider
87
- apiKey={import.meta.env.VITE_SENTIENT_API_KEY}
88
- context="saas"
89
- >
90
- {children}
91
- </AdaptiveProvider>
92
- );
123
+ import { useAdaptive } from '@sentientui/react';
124
+
125
+ function BuyBox() {
126
+ const { variant, value, bind, fireGoal } = useAdaptive('buy-box', {
127
+ variants: { calm: <CalmBuyBox />, urgent: <UrgentBuyBox /> }, // first key = baseline
128
+ goal: 'buy_click', // required
129
+ });
130
+ return <div {...bind}>{value}</div>;
93
131
  }
94
132
  ```
95
133
 
134
+ `bind` (ref + data attributes) wires exposure tracking, goal listeners, and engagement signals —
135
+ attach it or the slot cannot learn (dev mode warns loudly if you don't). `<Adaptive>` is the
136
+ wrapper form of the same rung; `<AdaptiveText>` swaps dashboard-managed text.
137
+
138
+ ### Rung 3 — Reorder (structure)
139
+
140
+ Region-scope — declared arrangements of keyed children only, never free permutation:
141
+
142
+ ```tsx
143
+ import { AdaptiveGroup } from '@sentientui/react';
144
+
145
+ <AdaptiveGroup
146
+ id="pricing-area"
147
+ arrangements={{
148
+ standard: ['plans', 'faq', 'social'], // first key = baseline
149
+ social_first: ['social', 'plans', 'faq'],
150
+ }}
151
+ goal="plan_selected"
152
+ >
153
+ <PlanGrid key="plans" />
154
+ <Faq key="faq" />
155
+ <Testimonials key="social" />
156
+ </AdaptiveGroup>
157
+ ```
158
+
159
+ Page-scope — declare `sections` on `AdaptiveRoot` and read the order with `useLayoutOrder()`
160
+ (see the API reference below). Use `AdaptiveGroup` for a region, `sections` for the page.
161
+
162
+ Every decision is locked for the session — visitors never see the page reshuffle under them.
163
+
96
164
  ## API
97
165
 
98
166
  ### `<AdaptiveRoot>` (Next.js App Router — server component)
@@ -109,7 +177,7 @@ Imported from `@sentientui/react/next`.
109
177
  | `consent` | `boolean` *(default `true`)* | Set `false` to skip SDK init (no cookies, no events). Flip to `true` after the visitor accepts. |
110
178
  | `respectDoNotTrack` | `boolean` *(default `true`)* | Honor the browser's Do Not Track signal. When on and DNT is enabled, the SDK sets no cookies and sends no tracking data (overriding `consent: true`), and `grantConsent()` won't re-enable it. Set `false` to make your own consent gate authoritative. |
111
179
  | `ssrFallback` | `'first' \| 'none'` *(default `'first'`)* | What to render in SSR HTML for components not in `components`. `'first'` is safe for SEO. |
112
- | `timeoutMs` | `number` *(default `1500`)* | Server-side fetch timeout before falling back to the first variant. |
180
+ | `timeoutMs` | `number` *(default `1000`)* | Server-side fetch timeout before falling back to the first variant. Typical decide is well under 150 ms; the full budget is only reached on a cold start or an API distant from your SSR host. |
113
181
  | `debug` | `boolean` | Log assignment and event activity to the console. |
114
182
 
115
183
  ### `<AdaptiveProvider>` (any React app)
@@ -184,8 +252,59 @@ import { AdaptiveText } from '@sentientui/react';
184
252
  </h1>
185
253
  ```
186
254
 
255
+ ### `useAdaptiveTokens(id, dims, opts?)`
256
+
257
+ ```ts
258
+ function useAdaptiveTokens(
259
+ id: string,
260
+ dims: Record<string, readonly string[]>, // 1–4 dims × 2–6 values; first value = baseline
261
+ opts?: { goal?: string | GoalConfig },
262
+ ): { tokens: Record<string, string>; props: Record<string, string> }; // props keys: `data-${dim}`
263
+ ```
264
+
265
+ Spread `props` onto the element you style. Values serialize through SSR markup — no flicker, no
266
+ hydration mismatch. Renaming a value is a cold start for that value's learning.
267
+
268
+ ### `useAdaptive(id, config)`
269
+
270
+ ```ts
271
+ function useAdaptive<T>(
272
+ id: string,
273
+ config: { variants: Record<string, T>; goal: string | GoalConfig }, // first key = baseline
274
+ ): {
275
+ variant: string;
276
+ value: T;
277
+ bind: { ref: (el: HTMLElement | null) => void; 'data-sentient-id': string; 'data-sentient-variant': string };
278
+ fireGoal: (goalType?: string, opts?: ComponentGoalOptions) => void;
279
+ };
280
+ ```
281
+
282
+ Headless Swap-rung hook. `goal` is required and `bind` must be attached to a rendered element —
283
+ learning needs both. Supersedes `useAssignment`.
284
+
285
+ ### `<AdaptiveGroup>`
286
+
287
+ | Prop | Type | Description |
288
+ |------|------|-------------|
289
+ | `id` | `string` | Unique group identifier. |
290
+ | `arrangements` | `Record<string, string[]>` | Arrangement id → ordered child keys. First entry = baseline. |
291
+ | `baseline` | `string` *(optional)* | Explicit baseline arrangement id (defaults to the first key). |
292
+ | `goal` | `string \| GoalConfig` *(optional)* | Conversion goal credited to this group. |
293
+ | `children` | keyed `ReactNode`s | Every key referenced by an arrangement must exist. |
294
+
295
+ Reorders via React keys (DOM moves, state preserved). Declared orders only.
296
+
297
+ ### Persona attributes (Rung 1a)
298
+
299
+ `AdaptiveRoot` renders an inline script (its first child) that sets `data-sentient-persona` and
300
+ `data-sentient-confidence` on `<html>` before first paint; the client SDK adopts those values
301
+ and never rewrites them mid-session. Requirements: `suppressHydrationWarning` on `<html>`,
302
+ `AdaptiveRoot` at the top of the tree. Override in dev with `?sentient_persona=<persona>`.
303
+
187
304
  ### `useAssignment(componentId, variantIds?)`
188
305
 
306
+ > **Deprecated** — use [`useAdaptive`](#useadaptiveid-config) instead; it carries the goal and exposure wiring `useAssignment` leaves to you. Kept for backward compatibility.
307
+
189
308
  Lower-level hook when you need the variant ID inside your own render logic (e.g. full-page layout tests where `<Adaptive>`'s wrapper `<div>` would break a flex/grid layout).
190
309
 
191
310
  ```tsx
@@ -1,5 +1,6 @@
1
+ declare const LOCAL_MODE_DEVTOOLS_BANNER = "Local mode \u2014 decisions are simulated; add a key to learn from real traffic";
1
2
  declare function AdaptiveDevtools({ apiKey }?: {
2
3
  apiKey?: string;
3
4
  }): JSX.Element | null;
4
5
 
5
- export { AdaptiveDevtools };
6
+ export { AdaptiveDevtools, LOCAL_MODE_DEVTOOLS_BANNER };
@@ -1,5 +1,6 @@
1
+ declare const LOCAL_MODE_DEVTOOLS_BANNER = "Local mode \u2014 decisions are simulated; add a key to learn from real traffic";
1
2
  declare function AdaptiveDevtools({ apiKey }?: {
2
3
  apiKey?: string;
3
4
  }): JSX.Element | null;
4
5
 
5
- export { AdaptiveDevtools };
6
+ export { AdaptiveDevtools, LOCAL_MODE_DEVTOOLS_BANNER };
package/dist/devtools.js CHANGED
@@ -1,3 +1,3 @@
1
1
  'use client';
2
- "use strict";"use client";var l=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames,x=Object.getOwnPropertySymbols;var h=Object.prototype.hasOwnProperty,B=Object.prototype.propertyIsEnumerable;var w=(e,t,i)=>t in e?l(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,R=(e,t)=>{for(var i in t||(t={}))h.call(t,i)&&w(e,i,t[i]);if(x)for(var i of x(t))B.call(t,i)&&w(e,i,t[i]);return e};var I=(e,t)=>{for(var i in t)l(e,i,{get:t[i],enumerable:!0})},N=(e,t,i,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of E(t))!h.call(e,r)&&r!==i&&l(e,r,{get:()=>t[r],enumerable:!(s=P(t,r))||s.enumerable});return e};var D=e=>N(l({},"__esModule",{value:!0}),e);var z={};I(z,{AdaptiveDevtools:()=>j});module.exports=D(z);var p=require("react");var V=new Map,k=new Set;function c(){return[...V.values()]}function S(e){return k.add(e),()=>{k.delete(e)}}function f(){let e=window;return e.__sentient_overrides||(e.__sentient_overrides={}),e.__sentient_overrides}function m(e,t){f()[e]=t}function O(e){delete f()[e]}function b(){return R({},f())}var y=!1,M=new Set;function u(e){if(y!==e){y=e;for(let t of M)t()}}function _(){return y}var n=require("react/jsx-runtime"),C,A=typeof process!="undefined"&&((C=process.env)==null?void 0:C.NODE_ENV)==="production",T=["browsers","buyers","researchers","deal-seekers"];async function W(e,t){let i=c().map(d=>({id:d.id})),s=c().map(d=>({id:d.id,variantIds:d.variantIds})),r=await fetch("/v1/explain",{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e}`},body:JSON.stringify({persona:t,sections:i,components:s})});if(!r.ok)return;let g=await r.json();for(let[d,v]of Object.entries(g.assignments))m(d,v);u(!0)}function j({apiKey:e}={}){let[t,i]=(0,p.useState)(!1),[,s]=(0,p.useReducer)(o=>o+1,0);if((0,p.useEffect)(()=>S(s),[]),A)return null;let r=c(),g=b();function d(o,a){m(o,a),u(!0),s()}function v(o){O(o),Object.keys(b()).length===0&&u(!1),s()}return(0,n.jsxs)("div",{style:{position:"fixed",bottom:16,right:16,zIndex:2147483647,fontFamily:"system-ui"},children:[t&&(0,n.jsxs)("div",{style:{width:300,maxHeight:420,overflowY:"auto",background:"#111",color:"#eee",borderRadius:8,padding:12,marginBottom:8,boxShadow:"0 8px 30px rgba(0,0,0,.4)",fontSize:12},children:[(0,n.jsxs)("div",{style:{opacity:.7,marginBottom:8},children:[r.length," component",r.length===1?"":"s"," \xB7 ",_()?"preview \u2014 writing nothing":"live"]}),e&&(0,n.jsxs)("div",{style:{borderBottom:"1px solid #333",paddingBottom:8,marginBottom:8},children:[(0,n.jsx)("div",{style:{opacity:.7,marginBottom:4},children:"Preview persona"}),(0,n.jsx)("div",{style:{display:"flex",flexWrap:"wrap",gap:4},children:T.map(o=>(0,n.jsx)("button",{onClick:()=>{W(e,o).then(s)},style:{padding:"2px 8px",borderRadius:4,border:"1px solid #444",background:"#222",color:"#eee",cursor:"pointer"},children:o},o))})]}),r.length===0&&(0,n.jsx)("div",{style:{opacity:.6},children:"No components on this page yet."}),r.map(o=>(0,n.jsxs)("div",{style:{borderTop:"1px solid #333",padding:"8px 0"},children:[(0,n.jsxs)("div",{style:{fontWeight:600},children:[o.id,o.goal?` \xB7 goal: ${o.goal}`:""]}),(0,n.jsxs)("div",{style:{display:"flex",flexWrap:"wrap",gap:4,marginTop:4},children:[o.variantIds.map(a=>(0,n.jsx)("button",{onClick:()=>d(o.id,a),style:{padding:"2px 8px",borderRadius:4,border:"1px solid #444",background:g[o.id]===a?"#3b82f6":"#222",color:"#eee",cursor:"pointer"},children:a},a)),g[o.id]&&(0,n.jsx)("button",{onClick:()=>v(o.id),style:{padding:"2px 8px",borderRadius:4,border:"1px solid #444",background:"#222",color:"#aaa",cursor:"pointer"},children:"reset"})]})]},o.id))]}),(0,n.jsx)("button",{"aria-label":"Sentient DevTools",onClick:()=>i(o=>!o),style:{width:40,height:40,borderRadius:20,border:"none",background:"#3b82f6",color:"#fff",cursor:"pointer",boxShadow:"0 4px 14px rgba(0,0,0,.3)"},children:"\u25E7"})]})}0&&(module.exports={AdaptiveDevtools});
2
+ "use strict";"use client";var Y=Object.create;var u=Object.defineProperty,$=Object.defineProperties,H=Object.getOwnPropertyDescriptor,J=Object.getOwnPropertyDescriptors,X=Object.getOwnPropertyNames,E=Object.getOwnPropertySymbols,q=Object.getPrototypeOf,k=Object.prototype.hasOwnProperty,Q=Object.prototype.propertyIsEnumerable;var A=(e,t,n)=>t in e?u(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,a=(e,t)=>{for(var n in t||(t={}))k.call(t,n)&&A(e,n,t[n]);if(E)for(var n of E(t))Q.call(t,n)&&A(e,n,t[n]);return e},_=(e,t)=>$(e,J(t));var Z=(e,t)=>{for(var n in t)u(e,n,{get:t[n],enumerable:!0})},L=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of X(t))!k.call(e,r)&&r!==n&&u(e,r,{get:()=>t[r],enumerable:!(i=H(t,r))||i.enumerable});return e};var ee=(e,t,n)=>(n=e!=null?Y(q(e)):{},L(t||!e||!e.__esModule?u(n,"default",{value:e,enumerable:!0}):n,e)),te=e=>L(u({},"__esModule",{value:!0}),e);var pe={};Z(pe,{AdaptiveDevtools:()=>ge,LOCAL_MODE_DEVTOOLS_BANNER:()=>M});module.exports=te(pe);var g=require("react"),p=require("@sentientui/policy"),T=require("@sentientui/core");var ne={components:new Map,slots:new Map,sections:[],listeners:new Set};function w(){if(typeof window=="undefined")return ne;let e=window;return e.__sentient_registry||(e.__sentient_registry={components:new Map,slots:new Map,sections:[],listeners:new Set}),e.__sentient_registry}function y(){return[...w().components.values()]}function D(){return[...w().slots.values()]}function R(){return[...w().sections]}function I(e){let t=w().listeners;return t.add(e),()=>{t.delete(e)}}function x(){let e=window;return e.__sentient_overrides||(e.__sentient_overrides={}),e.__sentient_overrides}function O(e,t){x()[e]=t}function C(e){delete x()[e]}function b(){return a({},x())}var oe={on:!1,listeners:new Set};function W(){if(typeof window=="undefined")return oe;let e=window;return e.__sentient_preview||(e.__sentient_preview={on:!1,listeners:new Set}),e.__sentient_preview}function f(e){let t=W();if(t.on!==e){t.on=e;for(let n of t.listeners)n()}}function B(){return W().on}var ie="sentient:overrides-changed";function v(){var t;if(typeof window=="undefined")return;let e=window;e.__sentient_overrides_version=((t=e.__sentient_overrides_version)!=null?t:0)+1,window.dispatchEvent(new Event(ie))}function N(){var e;return typeof window=="undefined"?null:(e=window.__sentient_devtools_config)!=null?e:null}var s=require("react/jsx-runtime"),V,se=typeof process!="undefined"&&((V=process.env)==null?void 0:V.NODE_ENV)==="production",re="https://api.sentient-ui.com/v1",M="Local mode \u2014 decisions are simulated; add a key to learn from real traffic";function U(e){var n,i;for(let[r,l]of Object.entries((n=e.assignments)!=null?n:{}))O(r,l);let t=window;e.layoutOrder&&e.layoutOrder.length>0&&(t.__sentient_layout_override=e.layoutOrder),e.slots&&(t.__sentient_slot_overrides=a(a({},(i=t.__sentient_slot_overrides)!=null?i:{}),e.slots)),e.personaAttributes&&(document.documentElement.dataset.sentientPersona=e.personaAttributes.persona,document.documentElement.dataset.sentientConfidence=e.personaAttributes.confidence),f(!0),v()}function de(){try{let e=document.cookie.match(/(?:^|; )_snt_uid=([^;]*)/);if(e)return decodeURIComponent(e[1])}catch(e){}return"devtools-preview"}function j(){return D().map(e=>a(a({id:e.id},e.arms?{arms:e.arms}:{}),e.dims?{dims:e.dims}:{}))}async function ae(e,t,n){var l;let i=await fetch(`${t}/explain`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e}`},body:JSON.stringify({persona:n,sections:R().map(d=>({id:d})),components:y().map(d=>({id:d.id,variantIds:d.variantIds})),slots:j()})});if(!i.ok)return;let r=await i.json();U(_(a({},r),{personaAttributes:(l=r.personaAttributes)!=null?l:{persona:n,confidence:"high"}}))}async function le(e){let t=await import("@sentientui/core/local");if(!t.LOCAL_ENGINE_AVAILABLE)return;let n=t.createLocalEngine({sessionId:de(),forcedPersona:e}).decide({sections:R(),components:y().map(i=>({id:i.id,variantIds:i.variantIds})),slots:j()});U({assignments:n.assignments,layoutOrder:n.layoutOrder,slots:n.slots,personaAttributes:{persona:n.persona,confidence:(0,p.confidenceBand)(n.confidence)}})}function ce(){for(let t of Object.keys(b()))C(t);let e=window;delete e.__sentient_layout_override,delete e.__sentient_slot_overrides,delete document.documentElement.dataset.sentientPersona,delete document.documentElement.dataset.sentientConfidence;try{let t=[];for(let n=0;n<localStorage.length;n++){let i=localStorage.key(n);i&&i.startsWith(T.SNAPSHOT_STORAGE_KEY_PREFIX)&&t.push(i)}for(let n of t)localStorage.removeItem(n)}catch(t){}f(!1),v();try{window.location.reload()}catch(t){}}var S=e=>({padding:"2px 8px",borderRadius:4,border:"1px solid #444",background:e?"#3b82f6":"#222",color:"#eee",cursor:"pointer"});function ge({apiKey:e}={}){var P;let[t,n]=(0,g.useState)(!1),[i,r]=(0,g.useState)(null),[,l]=(0,g.useReducer)(o=>o+1,0);if((0,g.useEffect)(()=>I(l),[]),se)return null;let d=(P=N())!=null?P:{apiKey:e!=null?e:"",apiBaseUrl:re,isLocal:!1},F=d.isLocal||!d.apiKey,m=y(),h=b();function G(o,c){O(o,c),f(!0),v(),l()}function z(o){C(o),Object.keys(b()).length===0&&f(!1),v(),l()}function K(o){r(o),(F?le(o):ae(d.apiKey,d.apiBaseUrl,o)).then(l)}return(0,s.jsxs)("div",{style:{position:"fixed",bottom:16,right:16,zIndex:2147483647,fontFamily:"system-ui"},children:[t&&(0,s.jsxs)("div",{style:{width:300,maxHeight:460,overflowY:"auto",background:"#111",color:"#eee",borderRadius:8,padding:12,marginBottom:8,boxShadow:"0 8px 30px rgba(0,0,0,.4)",fontSize:12},children:[d.isLocal&&(0,s.jsx)("div",{style:{background:"#1e293b",border:"1px solid #334155",borderRadius:4,padding:"6px 8px",marginBottom:8},children:M}),(0,s.jsxs)("div",{style:{opacity:.7,marginBottom:8},children:[m.length," component",m.length===1?"":"s"," \xB7 ",B()?"preview \u2014 writing nothing":"live"]}),(0,s.jsxs)("div",{style:{borderBottom:"1px solid #333",paddingBottom:8,marginBottom:8},children:[(0,s.jsx)("div",{style:{opacity:.7,marginBottom:4},children:"Preview persona"}),(0,s.jsxs)("div",{style:{display:"flex",flexWrap:"wrap",gap:4},children:[p.PERSONAS.map(o=>(0,s.jsx)("button",{onClick:()=>K(o),style:S(i===o),children:p.PERSONA_DISPLAY[o]},o)),(i!==null||Object.keys(h).length>0)&&(0,s.jsx)("button",{onClick:ce,style:_(a({},S(!1)),{color:"#aaa"}),children:"Reset"})]})]}),m.length===0&&(0,s.jsx)("div",{style:{opacity:.6},children:"No components on this page yet."}),m.map(o=>(0,s.jsxs)("div",{style:{borderTop:"1px solid #333",padding:"8px 0"},children:[(0,s.jsxs)("div",{style:{fontWeight:600},children:[o.id,o.goal?` \xB7 goal: ${o.goal}`:""]}),(0,s.jsxs)("div",{style:{display:"flex",flexWrap:"wrap",gap:4,marginTop:4},children:[o.variantIds.map(c=>(0,s.jsx)("button",{onClick:()=>G(o.id,c),style:S(h[o.id]===c),children:c},c)),h[o.id]&&(0,s.jsx)("button",{onClick:()=>z(o.id),style:_(a({},S(!1)),{color:"#aaa"}),children:"reset"})]})]},o.id))]}),(0,s.jsx)("button",{"aria-label":"Sentient DevTools",onClick:()=>n(o=>!o),style:{width:40,height:40,borderRadius:20,border:"none",background:"#3b82f6",color:"#fff",cursor:"pointer",boxShadow:"0 4px 14px rgba(0,0,0,.3)"},children:"\u25E7"})]})}0&&(module.exports={AdaptiveDevtools,LOCAL_MODE_DEVTOOLS_BANNER});
3
3
  //# sourceMappingURL=devtools.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/devtools/index.tsx","../src/devtools-registry.ts","../src/devtools-overrides.ts","../src/preview-mode.ts"],"sourcesContent":["'use client';\nimport { useEffect, useReducer, useState } from 'react';\nimport { getRegistered, subscribeRegistry } from '../devtools-registry.js';\nimport { setVariantOverride, clearVariantOverride, getOverrides } from '../devtools-overrides.js';\nimport { setPreviewMode, getPreviewMode } from '../preview-mode.js';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\nconst IS_PROD = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';\n\nconst SEED_PERSONAS = ['browsers', 'buyers', 'researchers', 'deal-seekers'];\n\n/** Simulate a persona via /v1/explain (read-only) and apply the result as overrides. */\nasync function forcePersona(apiKey: string, persona: string): Promise<void> {\n const sections = getRegistered().map((c) => ({ id: c.id }));\n const components = getRegistered().map((c) => ({ id: c.id, variantIds: c.variantIds }));\n const res = await fetch('/v1/explain', {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}` },\n body: JSON.stringify({ persona, sections, components }),\n });\n if (!res.ok) return;\n const data = (await res.json()) as { assignments: Record<string, string> };\n for (const [id, variantId] of Object.entries(data.assignments)) setVariantOverride(id, variantId);\n setPreviewMode(true);\n}\n\nexport function AdaptiveDevtools({ apiKey }: { apiKey?: string } = {}): JSX.Element | null {\n const [open, setOpen] = useState(false);\n const [, force] = useReducer((n: number) => n + 1, 0);\n useEffect(() => subscribeRegistry(force), []);\n\n // Never render in production, even if imported by mistake. (Hooks run first\n // so the rules of hooks hold regardless of this compile-time constant.)\n if (IS_PROD) return null;\n\n const components = getRegistered();\n const overrides = getOverrides();\n\n function choose(id: string, variantId: string): void {\n setVariantOverride(id, variantId);\n setPreviewMode(true); // suppress events while previewing\n force();\n }\n function reset(id: string): void {\n clearVariantOverride(id);\n if (Object.keys(getOverrides()).length === 0) setPreviewMode(false);\n force();\n }\n\n return (\n <div style={{ position: 'fixed', bottom: 16, right: 16, zIndex: 2147483647, fontFamily: 'system-ui' }}>\n {open && (\n <div style={{ width: 300, maxHeight: 420, overflowY: 'auto', background: '#111', color: '#eee',\n borderRadius: 8, padding: 12, marginBottom: 8, boxShadow: '0 8px 30px rgba(0,0,0,.4)', fontSize: 12 }}>\n <div style={{ opacity: .7, marginBottom: 8 }}>\n {components.length} component{components.length === 1 ? '' : 's'} · {getPreviewMode() ? 'preview — writing nothing' : 'live'}\n </div>\n {apiKey && (\n <div style={{ borderBottom: '1px solid #333', paddingBottom: 8, marginBottom: 8 }}>\n <div style={{ opacity: .7, marginBottom: 4 }}>Preview persona</div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>\n {SEED_PERSONAS.map((p) => (\n <button key={p} onClick={() => void forcePersona(apiKey, p).then(force)}\n style={{ padding: '2px 8px', borderRadius: 4, border: '1px solid #444', background: '#222', color: '#eee', cursor: 'pointer' }}>\n {p}\n </button>\n ))}\n </div>\n </div>\n )}\n {components.length === 0 && <div style={{ opacity: .6 }}>No components on this page yet.</div>}\n {components.map((c) => (\n <div key={c.id} style={{ borderTop: '1px solid #333', padding: '8px 0' }}>\n <div style={{ fontWeight: 600 }}>{c.id}{c.goal ? ` · goal: ${c.goal}` : ''}</div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 }}>\n {c.variantIds.map((v) => (\n <button key={v} onClick={() => choose(c.id, v)}\n style={{ padding: '2px 8px', borderRadius: 4, border: '1px solid #444',\n background: overrides[c.id] === v ? '#3b82f6' : '#222', color: '#eee', cursor: 'pointer' }}>\n {v}\n </button>\n ))}\n {overrides[c.id] && (\n <button onClick={() => reset(c.id)}\n style={{ padding: '2px 8px', borderRadius: 4, border: '1px solid #444', background: '#222', color: '#aaa', cursor: 'pointer' }}>\n reset\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n <button aria-label=\"Sentient DevTools\" onClick={() => setOpen((o) => !o)}\n style={{ width: 40, height: 40, borderRadius: 20, border: 'none', background: '#3b82f6', color: '#fff',\n cursor: 'pointer', boxShadow: '0 4px 14px rgba(0,0,0,.3)' }}>◧</button>\n </div>\n );\n}\n","export type RegisteredComponent = { id: string; variantIds: string[]; goal?: string };\n\nconst registry = new Map<string, RegisteredComponent>();\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const fn of listeners) fn();\n}\n\n/** Register (or re-register) a component. Returns an unregister function. */\nexport function registerComponent(c: RegisteredComponent): () => void {\n registry.set(c.id, c);\n emit();\n return () => {\n registry.delete(c.id);\n emit();\n };\n}\n\nexport function getRegistered(): RegisteredComponent[] {\n return [...registry.values()];\n}\n\nexport function subscribeRegistry(fn: () => void): () => void {\n listeners.add(fn);\n return () => {\n listeners.delete(fn);\n };\n}\n","function store(): Record<string, string> {\n const w = window as unknown as { __sentient_overrides?: Record<string, string> };\n if (!w.__sentient_overrides) w.__sentient_overrides = {};\n return w.__sentient_overrides;\n}\n\n/** Force `componentId` to render `variantId` — read by `useAssignment`. */\nexport function setVariantOverride(componentId: string, variantId: string): void {\n store()[componentId] = variantId;\n}\n\nexport function clearVariantOverride(componentId: string): void {\n delete store()[componentId];\n}\n\nexport function getOverrides(): Record<string, string> {\n return { ...store() };\n}\n","import type { SentientClient } from '@sentientui/core';\n\nlet previewOn = false;\nconst listeners = new Set<() => void>();\n\nexport function setPreviewMode(on: boolean): void {\n if (previewOn === on) return;\n previewOn = on;\n for (const fn of listeners) fn();\n}\n\nexport function getPreviewMode(): boolean {\n return previewOn;\n}\n\nexport function subscribePreview(fn: () => void): () => void {\n listeners.add(fn);\n return () => {\n listeners.delete(fn);\n };\n}\n\n/**\n * Wraps a client so it writes nothing: reads pass through, all emitters no-op.\n * Used while previewing variants/personas so no `variant_assigned`, goal, or\n * session events are sent.\n */\nexport function createPreviewClient(inner: SentientClient): SentientClient {\n return {\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n fetchWeights: () => Promise.resolve([]),\n getAssignment: (componentId, segment) => inner.getAssignment(componentId, segment),\n assign: (componentId, variantIds, agentData, agentDataByVariant) =>\n inner.assign(componentId, variantIds, agentData, agentDataByVariant),\n getGraph: () => inner.getGraph(),\n destroy: () => inner.destroy(),\n };\n}\n"],"mappings":"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","names":["devtools_exports","__export","AdaptiveDevtools","__toCommonJS","import_react","registry","listeners","getRegistered","registry","subscribeRegistry","fn","listeners","store","w","setVariantOverride","componentId","variantId","clearVariantOverride","getOverrides","__spreadValues","previewOn","listeners","setPreviewMode","on","fn","getPreviewMode","import_jsx_runtime","_a","IS_PROD","SEED_PERSONAS","forcePersona","apiKey","persona","sections","getRegistered","c","components","res","data","id","variantId","setVariantOverride","setPreviewMode","AdaptiveDevtools","open","setOpen","force","n","subscribeRegistry","overrides","getOverrides","choose","reset","clearVariantOverride","getPreviewMode","p","v"]}
1
+ {"version":3,"sources":["../src/devtools/index.tsx","../src/devtools-registry.ts","../src/devtools-overrides.ts","../src/preview-mode.ts","../src/override-events.ts","../src/devtools-config.ts"],"sourcesContent":["'use client';\nimport { useEffect, useReducer, useState, type CSSProperties } from 'react';\nimport { PERSONAS, PERSONA_DISPLAY, confidenceBand } from '@sentientui/policy';\nimport { SNAPSHOT_STORAGE_KEY_PREFIX } from '@sentientui/core';\nimport {\n getRegistered,\n getRegisteredSlots,\n getRegisteredSections,\n subscribeRegistry,\n type RegisteredSlot,\n} from '../devtools-registry.js';\nimport { setVariantOverride, clearVariantOverride, getOverrides } from '../devtools-overrides.js';\nimport { setPreviewMode, getPreviewMode } from '../preview-mode.js';\nimport { notifyOverridesChanged } from '../override-events.js';\nimport { readDevtoolsConfig, type DevtoolsConfig } from '../devtools-config.js';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\nconst IS_PROD = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';\nconst DEFAULT_API_BASE_URL = 'https://api.sentient-ui.com/v1';\n\nexport const LOCAL_MODE_DEVTOOLS_BANNER =\n 'Local mode — decisions are simulated; add a key to learn from real traffic';\n\ntype OutcomeToApply = {\n assignments?: Record<string, string>;\n layoutOrder?: string[] | null;\n slots?: Record<string, string | Record<string, string>>;\n personaAttributes?: { persona: string; confidence: 'low' | 'medium' | 'high' };\n};\n\ntype OverrideWindow = Window & {\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n};\n\n/** Apply a simulated outcome across every surface: variants, layout, slots/tokens, persona attrs. */\nfunction applyOutcome(result: OutcomeToApply): void {\n for (const [id, variantId] of Object.entries(result.assignments ?? {})) {\n setVariantOverride(id, variantId);\n }\n const w = window as unknown as OverrideWindow;\n if (result.layoutOrder && result.layoutOrder.length > 0) {\n w.__sentient_layout_override = result.layoutOrder;\n }\n if (result.slots) {\n w.__sentient_slot_overrides = { ...(w.__sentient_slot_overrides ?? {}), ...result.slots };\n }\n if (result.personaAttributes) {\n document.documentElement.dataset.sentientPersona = result.personaAttributes.persona;\n document.documentElement.dataset.sentientConfidence = result.personaAttributes.confidence;\n }\n setPreviewMode(true); // suppress events while previewing\n notifyOverridesChanged(); // re-render layout/slot consumers\n}\n\nfunction readSessionId(): string {\n try {\n const match = document.cookie.match(/(?:^|; )_snt_uid=([^;]*)/);\n if (match) return decodeURIComponent(match[1]);\n } catch {\n /* ignore */\n }\n return 'devtools-preview';\n}\n\nfunction slotDecls(): Array<{ id: string; arms?: string[]; dims?: RegisteredSlot['dims'] }> {\n return getRegisteredSlots().map((s) => ({\n id: s.id,\n ...(s.arms ? { arms: s.arms } : {}),\n ...(s.dims ? { dims: s.dims } : {}),\n }));\n}\n\n/** Keyed mode: simulate via /v1/explain (read-only, event-free). */\nasync function forcePersonaKeyed(apiKey: string, apiBaseUrl: string, persona: string): Promise<void> {\n const res = await fetch(`${apiBaseUrl}/explain`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}` },\n body: JSON.stringify({\n persona,\n sections: getRegisteredSections().map((id) => ({ id })),\n components: getRegistered().map((c) => ({ id: c.id, variantIds: c.variantIds })),\n slots: slotDecls(),\n }),\n });\n if (!res.ok) return;\n const data = (await res.json()) as OutcomeToApply;\n applyOutcome({\n ...data,\n personaAttributes: data.personaAttributes ?? { persona, confidence: 'high' },\n });\n}\n\n/** Local mode: simulate via the deterministic local engine — zero network. */\nasync function forcePersonaLocal(persona: string): Promise<void> {\n const mod = await import('@sentientui/core/local');\n if (!mod.LOCAL_ENGINE_AVAILABLE) return;\n const outcome = mod\n .createLocalEngine({ sessionId: readSessionId(), forcedPersona: persona })\n .decide({\n sections: getRegisteredSections(),\n components: getRegistered().map((c) => ({ id: c.id, variantIds: c.variantIds })),\n slots: slotDecls(),\n });\n applyOutcome({\n assignments: outcome.assignments,\n layoutOrder: outcome.layoutOrder,\n slots: outcome.slots,\n personaAttributes: {\n persona: outcome.persona,\n confidence: confidenceBand(outcome.confidence),\n },\n });\n}\n\n/** Reset: clear the decision snapshot + every override, then reload to re-decide. */\nfunction resetAll(): void {\n for (const id of Object.keys(getOverrides())) clearVariantOverride(id);\n const w = window as unknown as OverrideWindow;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete document.documentElement.dataset.sentientPersona;\n delete document.documentElement.dataset.sentientConfidence;\n try {\n const stale: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key && key.startsWith(SNAPSHOT_STORAGE_KEY_PREFIX)) stale.push(key);\n }\n for (const key of stale) localStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n setPreviewMode(false);\n notifyOverridesChanged();\n try {\n window.location.reload();\n } catch {\n /* jsdom */\n }\n}\n\nconst btn = (active: boolean): CSSProperties => ({\n padding: '2px 8px',\n borderRadius: 4,\n border: '1px solid #444',\n background: active ? '#3b82f6' : '#222',\n color: '#eee',\n cursor: 'pointer',\n});\n\nexport function AdaptiveDevtools({ apiKey }: { apiKey?: string } = {}): JSX.Element | null {\n const [open, setOpen] = useState(false);\n const [activePersona, setActivePersona] = useState<string | null>(null);\n const [, force] = useReducer((n: number) => n + 1, 0);\n useEffect(() => subscribeRegistry(force), []);\n\n // Never render in production, even if imported by mistake. (Hooks run first\n // so the rules of hooks hold regardless of this compile-time constant.)\n if (IS_PROD) return null;\n\n const config: DevtoolsConfig = readDevtoolsConfig() ?? {\n apiKey: apiKey ?? '',\n apiBaseUrl: DEFAULT_API_BASE_URL,\n isLocal: false,\n };\n const useLocalEngine = config.isLocal || !config.apiKey;\n const components = getRegistered();\n const overrides = getOverrides();\n\n function choose(id: string, variantId: string): void {\n setVariantOverride(id, variantId);\n setPreviewMode(true);\n notifyOverridesChanged();\n force();\n }\n function resetVariant(id: string): void {\n clearVariantOverride(id);\n if (Object.keys(getOverrides()).length === 0) setPreviewMode(false);\n notifyOverridesChanged();\n force();\n }\n function choosePersona(persona: string): void {\n setActivePersona(persona);\n const apply = useLocalEngine\n ? forcePersonaLocal(persona)\n : forcePersonaKeyed(config.apiKey, config.apiBaseUrl, persona);\n void apply.then(force);\n }\n\n return (\n <div style={{ position: 'fixed', bottom: 16, right: 16, zIndex: 2147483647, fontFamily: 'system-ui' }}>\n {open && (\n <div style={{ width: 300, maxHeight: 460, overflowY: 'auto', background: '#111', color: '#eee',\n borderRadius: 8, padding: 12, marginBottom: 8, boxShadow: '0 8px 30px rgba(0,0,0,.4)', fontSize: 12 }}>\n {config.isLocal && (\n <div style={{ background: '#1e293b', border: '1px solid #334155', borderRadius: 4,\n padding: '6px 8px', marginBottom: 8 }}>\n {LOCAL_MODE_DEVTOOLS_BANNER}\n </div>\n )}\n <div style={{ opacity: .7, marginBottom: 8 }}>\n {components.length} component{components.length === 1 ? '' : 's'} · {getPreviewMode() ? 'preview — writing nothing' : 'live'}\n </div>\n <div style={{ borderBottom: '1px solid #333', paddingBottom: 8, marginBottom: 8 }}>\n <div style={{ opacity: .7, marginBottom: 4 }}>Preview persona</div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>\n {PERSONAS.map((p) => (\n <button key={p} onClick={() => choosePersona(p)} style={btn(activePersona === p)}>\n {PERSONA_DISPLAY[p]}\n </button>\n ))}\n {(activePersona !== null || Object.keys(overrides).length > 0) && (\n <button onClick={resetAll} style={{ ...btn(false), color: '#aaa' }}>\n Reset\n </button>\n )}\n </div>\n </div>\n {components.length === 0 && <div style={{ opacity: .6 }}>No components on this page yet.</div>}\n {components.map((c) => (\n <div key={c.id} style={{ borderTop: '1px solid #333', padding: '8px 0' }}>\n <div style={{ fontWeight: 600 }}>{c.id}{c.goal ? ` · goal: ${c.goal}` : ''}</div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 }}>\n {c.variantIds.map((v) => (\n <button key={v} onClick={() => choose(c.id, v)} style={btn(overrides[c.id] === v)}>\n {v}\n </button>\n ))}\n {overrides[c.id] && (\n <button onClick={() => resetVariant(c.id)} style={{ ...btn(false), color: '#aaa' }}>\n reset\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n <button aria-label=\"Sentient DevTools\" onClick={() => setOpen((o) => !o)}\n style={{ width: 40, height: 40, borderRadius: 20, border: 'none', background: '#3b82f6', color: '#fff',\n cursor: 'pointer', boxShadow: '0 4px 14px rgba(0,0,0,.3)' }}>◧</button>\n </div>\n );\n}\n","export type RegisteredComponent = { id: string; variantIds: string[]; goal?: string };\nexport type RegisteredSlot = {\n id: string;\n arms?: string[];\n dims?: Record<string, readonly string[]>;\n};\n\ntype RegistryState = {\n components: Map<string, RegisteredComponent>;\n slots: Map<string, RegisteredSlot>;\n sections: string[];\n listeners: Set<() => void>;\n};\n\n// Shared through a window global: the main entry and the /devtools entry are\n// separate bundles, each with its own copy of this module — module-local\n// state would give the devtools an always-empty registry in published apps.\nconst ssrFallback: RegistryState = {\n components: new Map(),\n slots: new Map(),\n sections: [],\n listeners: new Set(),\n};\n\nfunction state(): RegistryState {\n if (typeof window === 'undefined') return ssrFallback;\n const w = window as unknown as { __sentient_registry?: RegistryState };\n if (!w.__sentient_registry) {\n w.__sentient_registry = {\n components: new Map(),\n slots: new Map(),\n sections: [],\n listeners: new Set(),\n };\n }\n return w.__sentient_registry;\n}\n\nfunction emit(): void {\n for (const fn of state().listeners) fn();\n}\n\n/** Register (or re-register) a component. Returns an unregister function. */\nexport function registerComponent(c: RegisteredComponent): () => void {\n state().components.set(c.id, c);\n emit();\n return () => {\n state().components.delete(c.id);\n emit();\n };\n}\n\n/** Register (or re-register) a slot declaration. Returns an unregister function. */\nexport function registerSlot(s: RegisteredSlot): () => void {\n state().slots.set(s.id, s);\n emit();\n return () => {\n state().slots.delete(s.id);\n emit();\n };\n}\n\n/** Register the page's declared section ids (from AdaptiveRoot/provider). */\nexport function registerSections(sections: string[]): void {\n state().sections = [...sections];\n emit();\n}\n\nexport function getRegistered(): RegisteredComponent[] {\n return [...state().components.values()];\n}\n\nexport function getRegisteredSlots(): RegisteredSlot[] {\n return [...state().slots.values()];\n}\n\nexport function getRegisteredSections(): string[] {\n return [...state().sections];\n}\n\nexport function subscribeRegistry(fn: () => void): () => void {\n const listeners = state().listeners;\n listeners.add(fn);\n return () => {\n listeners.delete(fn);\n };\n}\n","function store(): Record<string, string> {\n const w = window as unknown as { __sentient_overrides?: Record<string, string> };\n if (!w.__sentient_overrides) w.__sentient_overrides = {};\n return w.__sentient_overrides;\n}\n\n/** Force `componentId` to render `variantId` — read by `useAssignment`. */\nexport function setVariantOverride(componentId: string, variantId: string): void {\n store()[componentId] = variantId;\n}\n\nexport function clearVariantOverride(componentId: string): void {\n delete store()[componentId];\n}\n\nexport function getOverrides(): Record<string, string> {\n return { ...store() };\n}\n","import type { SentientClient } from '@sentientui/core';\n\ntype PreviewState = { on: boolean; listeners: Set<() => void> };\nconst ssrFallback: PreviewState = { on: false, listeners: new Set() };\n\n// Window-backed: the /devtools entry (a separate bundle) toggles preview mode\n// and the provider (main bundle) must observe it.\nfunction state(): PreviewState {\n if (typeof window === 'undefined') return ssrFallback;\n const w = window as unknown as { __sentient_preview?: PreviewState };\n if (!w.__sentient_preview) w.__sentient_preview = { on: false, listeners: new Set() };\n return w.__sentient_preview;\n}\n\nexport function setPreviewMode(on: boolean): void {\n const s = state();\n if (s.on === on) return;\n s.on = on;\n for (const fn of s.listeners) fn();\n}\n\nexport function getPreviewMode(): boolean {\n return state().on;\n}\n\nexport function subscribePreview(fn: () => void): () => void {\n const listeners = state().listeners;\n listeners.add(fn);\n return () => {\n listeners.delete(fn);\n };\n}\n\n/**\n * Wraps a client so it writes nothing: reads pass through, all emitters no-op.\n * Used while previewing variants/personas so no `variant_assigned`, goal, or\n * session events are sent.\n */\nexport function createPreviewClient(inner: SentientClient): SentientClient {\n return {\n isLocal: inner.isLocal,\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n fetchWeights: () => Promise.resolve([]),\n getAssignment: (componentId, segment) => inner.getAssignment(componentId, segment),\n assign: (componentId, variantIds, agentData, agentDataByVariant) =>\n inner.assign(componentId, variantIds, agentData, agentDataByVariant),\n // Reads pass through; decide is a write (slot decisions persist server-side)\n // so preview mode never issues it.\n decide: () => Promise.resolve(null),\n getSlotResult: (slotId) => inner.getSlotResult(slotId),\n getPersona: () => inner.getPersona(),\n getGraph: () => inner.getGraph(),\n destroy: () => inner.destroy(),\n };\n}\n","/**\n * Cross-bundle re-render bus for devtools overrides. The main entry and the\n * /devtools entry are separate bundles, so state and notifications go through\n * window (a version counter + a DOM event) — never module-local state.\n */\nconst EVENT = 'sentient:overrides-changed';\n\ntype VersionWindow = Window & { __sentient_overrides_version?: number };\n\nexport function getOverridesVersion(): number {\n if (typeof window === 'undefined') return 0;\n return (window as VersionWindow).__sentient_overrides_version ?? 0;\n}\n\nexport function notifyOverridesChanged(): void {\n if (typeof window === 'undefined') return;\n const w = window as VersionWindow;\n w.__sentient_overrides_version = (w.__sentient_overrides_version ?? 0) + 1;\n window.dispatchEvent(new Event(EVENT));\n}\n\nexport function subscribeOverridesChanged(fn: () => void): () => void {\n if (typeof window === 'undefined') return () => undefined;\n window.addEventListener(EVENT, fn);\n return () => window.removeEventListener(EVENT, fn);\n}\n","/** Provider → devtools config handoff. Window-backed: the /devtools entry is a\n * separate bundle and cannot share the provider's React context instance. */\nexport type DevtoolsConfig = {\n apiKey: string;\n apiBaseUrl: string;\n isLocal: boolean;\n};\n\ntype ConfigWindow = Window & { __sentient_devtools_config?: DevtoolsConfig };\n\nexport function publishDevtoolsConfig(config: DevtoolsConfig): void {\n if (typeof window === 'undefined') return;\n (window as ConfigWindow).__sentient_devtools_config = config;\n}\n\nexport function readDevtoolsConfig(): DevtoolsConfig | null {\n if (typeof window === 'undefined') return null;\n return (window as ConfigWindow).__sentient_devtools_config ?? null;\n}\n"],"mappings":"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","names":["devtools_exports","__export","AdaptiveDevtools","LOCAL_MODE_DEVTOOLS_BANNER","__toCommonJS","import_react","import_policy","import_core","ssrFallback","state","w","getRegistered","state","getRegisteredSlots","getRegisteredSections","subscribeRegistry","fn","listeners","store","w","setVariantOverride","componentId","variantId","clearVariantOverride","getOverrides","__spreadValues","ssrFallback","state","w","setPreviewMode","on","s","fn","getPreviewMode","EVENT","notifyOverridesChanged","_a","w","EVENT","readDevtoolsConfig","_a","import_jsx_runtime","_a","IS_PROD","DEFAULT_API_BASE_URL","LOCAL_MODE_DEVTOOLS_BANNER","applyOutcome","result","_b","id","variantId","setVariantOverride","w","__spreadValues","setPreviewMode","notifyOverridesChanged","readSessionId","match","slotDecls","getRegisteredSlots","s","forcePersonaKeyed","apiKey","apiBaseUrl","persona","res","getRegisteredSections","getRegistered","c","data","__spreadProps","forcePersonaLocal","mod","outcome","resetAll","getOverrides","clearVariantOverride","stale","i","key","e","btn","active","AdaptiveDevtools","open","setOpen","activePersona","setActivePersona","force","n","subscribeRegistry","config","readDevtoolsConfig","useLocalEngine","components","overrides","choose","resetVariant","choosePersona","getPreviewMode","p","v"]}
package/dist/devtools.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  'use client';
2
- "use client";var _=Object.defineProperty;var y=Object.getOwnPropertySymbols;var C=Object.prototype.hasOwnProperty,P=Object.prototype.propertyIsEnumerable;var x=(e,o,i)=>o in e?_(e,o,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[o]=i,w=(e,o)=>{for(var i in o||(o={}))C.call(o,i)&&x(e,i,o[i]);if(y)for(var i of y(o))P.call(o,i)&&x(e,i,o[i]);return e};import{useEffect as I,useReducer as N,useState as D}from"react";var E=new Map,h=new Set;function l(){return[...E.values()]}function R(e){return h.add(e),()=>{h.delete(e)}}function v(){let e=window;return e.__sentient_overrides||(e.__sentient_overrides={}),e.__sentient_overrides}function f(e,o){v()[e]=o}function k(e){delete v()[e]}function m(){return w({},v())}var b=!1,B=new Set;function c(e){if(b!==e){b=e;for(let o of B)o()}}function S(){return b}import{jsx as d,jsxs as s}from"react/jsx-runtime";var O,V=typeof process!="undefined"&&((O=process.env)==null?void 0:O.NODE_ENV)==="production",M=["browsers","buyers","researchers","deal-seekers"];async function A(e,o){let i=l().map(n=>({id:n.id})),a=l().map(n=>({id:n.id,variantIds:n.variantIds})),r=await fetch("/v1/explain",{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e}`},body:JSON.stringify({persona:o,sections:i,components:a})});if(!r.ok)return;let g=await r.json();for(let[n,u]of Object.entries(g.assignments))f(n,u);c(!0)}function X({apiKey:e}={}){let[o,i]=D(!1),[,a]=N(t=>t+1,0);if(I(()=>R(a),[]),V)return null;let r=l(),g=m();function n(t,p){f(t,p),c(!0),a()}function u(t){k(t),Object.keys(m()).length===0&&c(!1),a()}return s("div",{style:{position:"fixed",bottom:16,right:16,zIndex:2147483647,fontFamily:"system-ui"},children:[o&&s("div",{style:{width:300,maxHeight:420,overflowY:"auto",background:"#111",color:"#eee",borderRadius:8,padding:12,marginBottom:8,boxShadow:"0 8px 30px rgba(0,0,0,.4)",fontSize:12},children:[s("div",{style:{opacity:.7,marginBottom:8},children:[r.length," component",r.length===1?"":"s"," \xB7 ",S()?"preview \u2014 writing nothing":"live"]}),e&&s("div",{style:{borderBottom:"1px solid #333",paddingBottom:8,marginBottom:8},children:[d("div",{style:{opacity:.7,marginBottom:4},children:"Preview persona"}),d("div",{style:{display:"flex",flexWrap:"wrap",gap:4},children:M.map(t=>d("button",{onClick:()=>{A(e,t).then(a)},style:{padding:"2px 8px",borderRadius:4,border:"1px solid #444",background:"#222",color:"#eee",cursor:"pointer"},children:t},t))})]}),r.length===0&&d("div",{style:{opacity:.6},children:"No components on this page yet."}),r.map(t=>s("div",{style:{borderTop:"1px solid #333",padding:"8px 0"},children:[s("div",{style:{fontWeight:600},children:[t.id,t.goal?` \xB7 goal: ${t.goal}`:""]}),s("div",{style:{display:"flex",flexWrap:"wrap",gap:4,marginTop:4},children:[t.variantIds.map(p=>d("button",{onClick:()=>n(t.id,p),style:{padding:"2px 8px",borderRadius:4,border:"1px solid #444",background:g[t.id]===p?"#3b82f6":"#222",color:"#eee",cursor:"pointer"},children:p},p)),g[t.id]&&d("button",{onClick:()=>u(t.id),style:{padding:"2px 8px",borderRadius:4,border:"1px solid #444",background:"#222",color:"#aaa",cursor:"pointer"},children:"reset"})]})]},t.id))]}),d("button",{"aria-label":"Sentient DevTools",onClick:()=>i(t=>!t),style:{width:40,height:40,borderRadius:20,border:"none",background:"#3b82f6",color:"#fff",cursor:"pointer",boxShadow:"0 4px 14px rgba(0,0,0,.3)"},children:"\u25E7"})]})}export{X as AdaptiveDevtools};
2
+ "use client";var j=Object.defineProperty,F=Object.defineProperties;var G=Object.getOwnPropertyDescriptors;var C=Object.getOwnPropertySymbols;var z=Object.prototype.hasOwnProperty,K=Object.prototype.propertyIsEnumerable;var P=(e,t,n)=>t in e?j(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,r=(e,t)=>{for(var n in t||(t={}))z.call(t,n)&&P(e,n,t[n]);if(C)for(var n of C(t))K.call(t,n)&&P(e,n,t[n]);return e},v=(e,t)=>F(e,G(t));import{useEffect as J,useReducer as X,useState as I}from"react";import{PERSONAS as q,PERSONA_DISPLAY as Q,confidenceBand as Z}from"@sentientui/policy";import{SNAPSHOT_STORAGE_KEY_PREFIX as ee}from"@sentientui/core";var Y={components:new Map,slots:new Map,sections:[],listeners:new Set};function m(){if(typeof window=="undefined")return Y;let e=window;return e.__sentient_registry||(e.__sentient_registry={components:new Map,slots:new Map,sections:[],listeners:new Set}),e.__sentient_registry}function _(){return[...m().components.values()]}function E(){return[...m().slots.values()]}function S(){return[...m().sections]}function A(e){let t=m().listeners;return t.add(e),()=>{t.delete(e)}}function h(){let e=window;return e.__sentient_overrides||(e.__sentient_overrides={}),e.__sentient_overrides}function R(e,t){h()[e]=t}function x(e){delete h()[e]}function w(){return r({},h())}var $={on:!1,listeners:new Set};function k(){if(typeof window=="undefined")return $;let e=window;return e.__sentient_preview||(e.__sentient_preview={on:!1,listeners:new Set}),e.__sentient_preview}function p(e){let t=k();if(t.on!==e){t.on=e;for(let n of t.listeners)n()}}function L(){return k().on}var H="sentient:overrides-changed";function u(){var t;if(typeof window=="undefined")return;let e=window;e.__sentient_overrides_version=((t=e.__sentient_overrides_version)!=null?t:0)+1,window.dispatchEvent(new Event(H))}function D(){var e;return typeof window=="undefined"?null:(e=window.__sentient_devtools_config)!=null?e:null}import{jsx as a,jsxs as l}from"react/jsx-runtime";var W,te=typeof process!="undefined"&&((W=process.env)==null?void 0:W.NODE_ENV)==="production",ne="https://api.sentient-ui.com/v1",oe="Local mode \u2014 decisions are simulated; add a key to learn from real traffic";function B(e){var n,i;for(let[g,d]of Object.entries((n=e.assignments)!=null?n:{}))R(g,d);let t=window;e.layoutOrder&&e.layoutOrder.length>0&&(t.__sentient_layout_override=e.layoutOrder),e.slots&&(t.__sentient_slot_overrides=r(r({},(i=t.__sentient_slot_overrides)!=null?i:{}),e.slots)),e.personaAttributes&&(document.documentElement.dataset.sentientPersona=e.personaAttributes.persona,document.documentElement.dataset.sentientConfidence=e.personaAttributes.confidence),p(!0),u()}function ie(){try{let e=document.cookie.match(/(?:^|; )_snt_uid=([^;]*)/);if(e)return decodeURIComponent(e[1])}catch(e){}return"devtools-preview"}function N(){return E().map(e=>r(r({id:e.id},e.arms?{arms:e.arms}:{}),e.dims?{dims:e.dims}:{}))}async function se(e,t,n){var d;let i=await fetch(`${t}/explain`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e}`},body:JSON.stringify({persona:n,sections:S().map(s=>({id:s})),components:_().map(s=>({id:s.id,variantIds:s.variantIds})),slots:N()})});if(!i.ok)return;let g=await i.json();B(v(r({},g),{personaAttributes:(d=g.personaAttributes)!=null?d:{persona:n,confidence:"high"}}))}async function re(e){let t=await import("@sentientui/core/local");if(!t.LOCAL_ENGINE_AVAILABLE)return;let n=t.createLocalEngine({sessionId:ie(),forcedPersona:e}).decide({sections:S(),components:_().map(i=>({id:i.id,variantIds:i.variantIds})),slots:N()});B({assignments:n.assignments,layoutOrder:n.layoutOrder,slots:n.slots,personaAttributes:{persona:n.persona,confidence:Z(n.confidence)}})}function de(){for(let t of Object.keys(w()))x(t);let e=window;delete e.__sentient_layout_override,delete e.__sentient_slot_overrides,delete document.documentElement.dataset.sentientPersona,delete document.documentElement.dataset.sentientConfidence;try{let t=[];for(let n=0;n<localStorage.length;n++){let i=localStorage.key(n);i&&i.startsWith(ee)&&t.push(i)}for(let n of t)localStorage.removeItem(n)}catch(t){}p(!1),u();try{window.location.reload()}catch(t){}}var y=e=>({padding:"2px 8px",borderRadius:4,border:"1px solid #444",background:e?"#3b82f6":"#222",color:"#eee",cursor:"pointer"});function Re({apiKey:e}={}){var O;let[t,n]=I(!1),[i,g]=I(null),[,d]=X(o=>o+1,0);if(J(()=>A(d),[]),te)return null;let s=(O=D())!=null?O:{apiKey:e!=null?e:"",apiBaseUrl:ne,isLocal:!1},V=s.isLocal||!s.apiKey,f=_(),b=w();function T(o,c){R(o,c),p(!0),u(),d()}function M(o){x(o),Object.keys(w()).length===0&&p(!1),u(),d()}function U(o){g(o),(V?re(o):se(s.apiKey,s.apiBaseUrl,o)).then(d)}return l("div",{style:{position:"fixed",bottom:16,right:16,zIndex:2147483647,fontFamily:"system-ui"},children:[t&&l("div",{style:{width:300,maxHeight:460,overflowY:"auto",background:"#111",color:"#eee",borderRadius:8,padding:12,marginBottom:8,boxShadow:"0 8px 30px rgba(0,0,0,.4)",fontSize:12},children:[s.isLocal&&a("div",{style:{background:"#1e293b",border:"1px solid #334155",borderRadius:4,padding:"6px 8px",marginBottom:8},children:oe}),l("div",{style:{opacity:.7,marginBottom:8},children:[f.length," component",f.length===1?"":"s"," \xB7 ",L()?"preview \u2014 writing nothing":"live"]}),l("div",{style:{borderBottom:"1px solid #333",paddingBottom:8,marginBottom:8},children:[a("div",{style:{opacity:.7,marginBottom:4},children:"Preview persona"}),l("div",{style:{display:"flex",flexWrap:"wrap",gap:4},children:[q.map(o=>a("button",{onClick:()=>U(o),style:y(i===o),children:Q[o]},o)),(i!==null||Object.keys(b).length>0)&&a("button",{onClick:de,style:v(r({},y(!1)),{color:"#aaa"}),children:"Reset"})]})]}),f.length===0&&a("div",{style:{opacity:.6},children:"No components on this page yet."}),f.map(o=>l("div",{style:{borderTop:"1px solid #333",padding:"8px 0"},children:[l("div",{style:{fontWeight:600},children:[o.id,o.goal?` \xB7 goal: ${o.goal}`:""]}),l("div",{style:{display:"flex",flexWrap:"wrap",gap:4,marginTop:4},children:[o.variantIds.map(c=>a("button",{onClick:()=>T(o.id,c),style:y(b[o.id]===c),children:c},c)),b[o.id]&&a("button",{onClick:()=>M(o.id),style:v(r({},y(!1)),{color:"#aaa"}),children:"reset"})]})]},o.id))]}),a("button",{"aria-label":"Sentient DevTools",onClick:()=>n(o=>!o),style:{width:40,height:40,borderRadius:20,border:"none",background:"#3b82f6",color:"#fff",cursor:"pointer",boxShadow:"0 4px 14px rgba(0,0,0,.3)"},children:"\u25E7"})]})}export{Re as AdaptiveDevtools,oe as LOCAL_MODE_DEVTOOLS_BANNER};
3
3
  //# sourceMappingURL=devtools.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/devtools/index.tsx","../src/devtools-registry.ts","../src/devtools-overrides.ts","../src/preview-mode.ts"],"sourcesContent":["'use client';\nimport { useEffect, useReducer, useState } from 'react';\nimport { getRegistered, subscribeRegistry } from '../devtools-registry.js';\nimport { setVariantOverride, clearVariantOverride, getOverrides } from '../devtools-overrides.js';\nimport { setPreviewMode, getPreviewMode } from '../preview-mode.js';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\nconst IS_PROD = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';\n\nconst SEED_PERSONAS = ['browsers', 'buyers', 'researchers', 'deal-seekers'];\n\n/** Simulate a persona via /v1/explain (read-only) and apply the result as overrides. */\nasync function forcePersona(apiKey: string, persona: string): Promise<void> {\n const sections = getRegistered().map((c) => ({ id: c.id }));\n const components = getRegistered().map((c) => ({ id: c.id, variantIds: c.variantIds }));\n const res = await fetch('/v1/explain', {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}` },\n body: JSON.stringify({ persona, sections, components }),\n });\n if (!res.ok) return;\n const data = (await res.json()) as { assignments: Record<string, string> };\n for (const [id, variantId] of Object.entries(data.assignments)) setVariantOverride(id, variantId);\n setPreviewMode(true);\n}\n\nexport function AdaptiveDevtools({ apiKey }: { apiKey?: string } = {}): JSX.Element | null {\n const [open, setOpen] = useState(false);\n const [, force] = useReducer((n: number) => n + 1, 0);\n useEffect(() => subscribeRegistry(force), []);\n\n // Never render in production, even if imported by mistake. (Hooks run first\n // so the rules of hooks hold regardless of this compile-time constant.)\n if (IS_PROD) return null;\n\n const components = getRegistered();\n const overrides = getOverrides();\n\n function choose(id: string, variantId: string): void {\n setVariantOverride(id, variantId);\n setPreviewMode(true); // suppress events while previewing\n force();\n }\n function reset(id: string): void {\n clearVariantOverride(id);\n if (Object.keys(getOverrides()).length === 0) setPreviewMode(false);\n force();\n }\n\n return (\n <div style={{ position: 'fixed', bottom: 16, right: 16, zIndex: 2147483647, fontFamily: 'system-ui' }}>\n {open && (\n <div style={{ width: 300, maxHeight: 420, overflowY: 'auto', background: '#111', color: '#eee',\n borderRadius: 8, padding: 12, marginBottom: 8, boxShadow: '0 8px 30px rgba(0,0,0,.4)', fontSize: 12 }}>\n <div style={{ opacity: .7, marginBottom: 8 }}>\n {components.length} component{components.length === 1 ? '' : 's'} · {getPreviewMode() ? 'preview — writing nothing' : 'live'}\n </div>\n {apiKey && (\n <div style={{ borderBottom: '1px solid #333', paddingBottom: 8, marginBottom: 8 }}>\n <div style={{ opacity: .7, marginBottom: 4 }}>Preview persona</div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>\n {SEED_PERSONAS.map((p) => (\n <button key={p} onClick={() => void forcePersona(apiKey, p).then(force)}\n style={{ padding: '2px 8px', borderRadius: 4, border: '1px solid #444', background: '#222', color: '#eee', cursor: 'pointer' }}>\n {p}\n </button>\n ))}\n </div>\n </div>\n )}\n {components.length === 0 && <div style={{ opacity: .6 }}>No components on this page yet.</div>}\n {components.map((c) => (\n <div key={c.id} style={{ borderTop: '1px solid #333', padding: '8px 0' }}>\n <div style={{ fontWeight: 600 }}>{c.id}{c.goal ? ` · goal: ${c.goal}` : ''}</div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 }}>\n {c.variantIds.map((v) => (\n <button key={v} onClick={() => choose(c.id, v)}\n style={{ padding: '2px 8px', borderRadius: 4, border: '1px solid #444',\n background: overrides[c.id] === v ? '#3b82f6' : '#222', color: '#eee', cursor: 'pointer' }}>\n {v}\n </button>\n ))}\n {overrides[c.id] && (\n <button onClick={() => reset(c.id)}\n style={{ padding: '2px 8px', borderRadius: 4, border: '1px solid #444', background: '#222', color: '#aaa', cursor: 'pointer' }}>\n reset\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n <button aria-label=\"Sentient DevTools\" onClick={() => setOpen((o) => !o)}\n style={{ width: 40, height: 40, borderRadius: 20, border: 'none', background: '#3b82f6', color: '#fff',\n cursor: 'pointer', boxShadow: '0 4px 14px rgba(0,0,0,.3)' }}>◧</button>\n </div>\n );\n}\n","export type RegisteredComponent = { id: string; variantIds: string[]; goal?: string };\n\nconst registry = new Map<string, RegisteredComponent>();\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const fn of listeners) fn();\n}\n\n/** Register (or re-register) a component. Returns an unregister function. */\nexport function registerComponent(c: RegisteredComponent): () => void {\n registry.set(c.id, c);\n emit();\n return () => {\n registry.delete(c.id);\n emit();\n };\n}\n\nexport function getRegistered(): RegisteredComponent[] {\n return [...registry.values()];\n}\n\nexport function subscribeRegistry(fn: () => void): () => void {\n listeners.add(fn);\n return () => {\n listeners.delete(fn);\n };\n}\n","function store(): Record<string, string> {\n const w = window as unknown as { __sentient_overrides?: Record<string, string> };\n if (!w.__sentient_overrides) w.__sentient_overrides = {};\n return w.__sentient_overrides;\n}\n\n/** Force `componentId` to render `variantId` — read by `useAssignment`. */\nexport function setVariantOverride(componentId: string, variantId: string): void {\n store()[componentId] = variantId;\n}\n\nexport function clearVariantOverride(componentId: string): void {\n delete store()[componentId];\n}\n\nexport function getOverrides(): Record<string, string> {\n return { ...store() };\n}\n","import type { SentientClient } from '@sentientui/core';\n\nlet previewOn = false;\nconst listeners = new Set<() => void>();\n\nexport function setPreviewMode(on: boolean): void {\n if (previewOn === on) return;\n previewOn = on;\n for (const fn of listeners) fn();\n}\n\nexport function getPreviewMode(): boolean {\n return previewOn;\n}\n\nexport function subscribePreview(fn: () => void): () => void {\n listeners.add(fn);\n return () => {\n listeners.delete(fn);\n };\n}\n\n/**\n * Wraps a client so it writes nothing: reads pass through, all emitters no-op.\n * Used while previewing variants/personas so no `variant_assigned`, goal, or\n * session events are sent.\n */\nexport function createPreviewClient(inner: SentientClient): SentientClient {\n return {\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n fetchWeights: () => Promise.resolve([]),\n getAssignment: (componentId, segment) => inner.getAssignment(componentId, segment),\n assign: (componentId, variantIds, agentData, agentDataByVariant) =>\n inner.assign(componentId, variantIds, agentData, agentDataByVariant),\n getGraph: () => inner.getGraph(),\n destroy: () => inner.destroy(),\n };\n}\n"],"mappings":"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","names":["useEffect","useReducer","useState","registry","listeners","getRegistered","registry","subscribeRegistry","fn","listeners","store","w","setVariantOverride","componentId","variantId","clearVariantOverride","getOverrides","__spreadValues","previewOn","listeners","setPreviewMode","on","fn","getPreviewMode","jsx","jsxs","_a","IS_PROD","SEED_PERSONAS","forcePersona","apiKey","persona","sections","getRegistered","c","components","res","data","id","variantId","setVariantOverride","setPreviewMode","AdaptiveDevtools","open","setOpen","useState","force","useReducer","n","useEffect","subscribeRegistry","overrides","getOverrides","choose","reset","clearVariantOverride","getPreviewMode","p","v","o"]}
1
+ {"version":3,"sources":["../src/devtools/index.tsx","../src/devtools-registry.ts","../src/devtools-overrides.ts","../src/preview-mode.ts","../src/override-events.ts","../src/devtools-config.ts"],"sourcesContent":["'use client';\nimport { useEffect, useReducer, useState, type CSSProperties } from 'react';\nimport { PERSONAS, PERSONA_DISPLAY, confidenceBand } from '@sentientui/policy';\nimport { SNAPSHOT_STORAGE_KEY_PREFIX } from '@sentientui/core';\nimport {\n getRegistered,\n getRegisteredSlots,\n getRegisteredSections,\n subscribeRegistry,\n type RegisteredSlot,\n} from '../devtools-registry.js';\nimport { setVariantOverride, clearVariantOverride, getOverrides } from '../devtools-overrides.js';\nimport { setPreviewMode, getPreviewMode } from '../preview-mode.js';\nimport { notifyOverridesChanged } from '../override-events.js';\nimport { readDevtoolsConfig, type DevtoolsConfig } from '../devtools-config.js';\n\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\nconst IS_PROD = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';\nconst DEFAULT_API_BASE_URL = 'https://api.sentient-ui.com/v1';\n\nexport const LOCAL_MODE_DEVTOOLS_BANNER =\n 'Local mode — decisions are simulated; add a key to learn from real traffic';\n\ntype OutcomeToApply = {\n assignments?: Record<string, string>;\n layoutOrder?: string[] | null;\n slots?: Record<string, string | Record<string, string>>;\n personaAttributes?: { persona: string; confidence: 'low' | 'medium' | 'high' };\n};\n\ntype OverrideWindow = Window & {\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n};\n\n/** Apply a simulated outcome across every surface: variants, layout, slots/tokens, persona attrs. */\nfunction applyOutcome(result: OutcomeToApply): void {\n for (const [id, variantId] of Object.entries(result.assignments ?? {})) {\n setVariantOverride(id, variantId);\n }\n const w = window as unknown as OverrideWindow;\n if (result.layoutOrder && result.layoutOrder.length > 0) {\n w.__sentient_layout_override = result.layoutOrder;\n }\n if (result.slots) {\n w.__sentient_slot_overrides = { ...(w.__sentient_slot_overrides ?? {}), ...result.slots };\n }\n if (result.personaAttributes) {\n document.documentElement.dataset.sentientPersona = result.personaAttributes.persona;\n document.documentElement.dataset.sentientConfidence = result.personaAttributes.confidence;\n }\n setPreviewMode(true); // suppress events while previewing\n notifyOverridesChanged(); // re-render layout/slot consumers\n}\n\nfunction readSessionId(): string {\n try {\n const match = document.cookie.match(/(?:^|; )_snt_uid=([^;]*)/);\n if (match) return decodeURIComponent(match[1]);\n } catch {\n /* ignore */\n }\n return 'devtools-preview';\n}\n\nfunction slotDecls(): Array<{ id: string; arms?: string[]; dims?: RegisteredSlot['dims'] }> {\n return getRegisteredSlots().map((s) => ({\n id: s.id,\n ...(s.arms ? { arms: s.arms } : {}),\n ...(s.dims ? { dims: s.dims } : {}),\n }));\n}\n\n/** Keyed mode: simulate via /v1/explain (read-only, event-free). */\nasync function forcePersonaKeyed(apiKey: string, apiBaseUrl: string, persona: string): Promise<void> {\n const res = await fetch(`${apiBaseUrl}/explain`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}` },\n body: JSON.stringify({\n persona,\n sections: getRegisteredSections().map((id) => ({ id })),\n components: getRegistered().map((c) => ({ id: c.id, variantIds: c.variantIds })),\n slots: slotDecls(),\n }),\n });\n if (!res.ok) return;\n const data = (await res.json()) as OutcomeToApply;\n applyOutcome({\n ...data,\n personaAttributes: data.personaAttributes ?? { persona, confidence: 'high' },\n });\n}\n\n/** Local mode: simulate via the deterministic local engine — zero network. */\nasync function forcePersonaLocal(persona: string): Promise<void> {\n const mod = await import('@sentientui/core/local');\n if (!mod.LOCAL_ENGINE_AVAILABLE) return;\n const outcome = mod\n .createLocalEngine({ sessionId: readSessionId(), forcedPersona: persona })\n .decide({\n sections: getRegisteredSections(),\n components: getRegistered().map((c) => ({ id: c.id, variantIds: c.variantIds })),\n slots: slotDecls(),\n });\n applyOutcome({\n assignments: outcome.assignments,\n layoutOrder: outcome.layoutOrder,\n slots: outcome.slots,\n personaAttributes: {\n persona: outcome.persona,\n confidence: confidenceBand(outcome.confidence),\n },\n });\n}\n\n/** Reset: clear the decision snapshot + every override, then reload to re-decide. */\nfunction resetAll(): void {\n for (const id of Object.keys(getOverrides())) clearVariantOverride(id);\n const w = window as unknown as OverrideWindow;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete document.documentElement.dataset.sentientPersona;\n delete document.documentElement.dataset.sentientConfidence;\n try {\n const stale: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key && key.startsWith(SNAPSHOT_STORAGE_KEY_PREFIX)) stale.push(key);\n }\n for (const key of stale) localStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n setPreviewMode(false);\n notifyOverridesChanged();\n try {\n window.location.reload();\n } catch {\n /* jsdom */\n }\n}\n\nconst btn = (active: boolean): CSSProperties => ({\n padding: '2px 8px',\n borderRadius: 4,\n border: '1px solid #444',\n background: active ? '#3b82f6' : '#222',\n color: '#eee',\n cursor: 'pointer',\n});\n\nexport function AdaptiveDevtools({ apiKey }: { apiKey?: string } = {}): JSX.Element | null {\n const [open, setOpen] = useState(false);\n const [activePersona, setActivePersona] = useState<string | null>(null);\n const [, force] = useReducer((n: number) => n + 1, 0);\n useEffect(() => subscribeRegistry(force), []);\n\n // Never render in production, even if imported by mistake. (Hooks run first\n // so the rules of hooks hold regardless of this compile-time constant.)\n if (IS_PROD) return null;\n\n const config: DevtoolsConfig = readDevtoolsConfig() ?? {\n apiKey: apiKey ?? '',\n apiBaseUrl: DEFAULT_API_BASE_URL,\n isLocal: false,\n };\n const useLocalEngine = config.isLocal || !config.apiKey;\n const components = getRegistered();\n const overrides = getOverrides();\n\n function choose(id: string, variantId: string): void {\n setVariantOverride(id, variantId);\n setPreviewMode(true);\n notifyOverridesChanged();\n force();\n }\n function resetVariant(id: string): void {\n clearVariantOverride(id);\n if (Object.keys(getOverrides()).length === 0) setPreviewMode(false);\n notifyOverridesChanged();\n force();\n }\n function choosePersona(persona: string): void {\n setActivePersona(persona);\n const apply = useLocalEngine\n ? forcePersonaLocal(persona)\n : forcePersonaKeyed(config.apiKey, config.apiBaseUrl, persona);\n void apply.then(force);\n }\n\n return (\n <div style={{ position: 'fixed', bottom: 16, right: 16, zIndex: 2147483647, fontFamily: 'system-ui' }}>\n {open && (\n <div style={{ width: 300, maxHeight: 460, overflowY: 'auto', background: '#111', color: '#eee',\n borderRadius: 8, padding: 12, marginBottom: 8, boxShadow: '0 8px 30px rgba(0,0,0,.4)', fontSize: 12 }}>\n {config.isLocal && (\n <div style={{ background: '#1e293b', border: '1px solid #334155', borderRadius: 4,\n padding: '6px 8px', marginBottom: 8 }}>\n {LOCAL_MODE_DEVTOOLS_BANNER}\n </div>\n )}\n <div style={{ opacity: .7, marginBottom: 8 }}>\n {components.length} component{components.length === 1 ? '' : 's'} · {getPreviewMode() ? 'preview — writing nothing' : 'live'}\n </div>\n <div style={{ borderBottom: '1px solid #333', paddingBottom: 8, marginBottom: 8 }}>\n <div style={{ opacity: .7, marginBottom: 4 }}>Preview persona</div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>\n {PERSONAS.map((p) => (\n <button key={p} onClick={() => choosePersona(p)} style={btn(activePersona === p)}>\n {PERSONA_DISPLAY[p]}\n </button>\n ))}\n {(activePersona !== null || Object.keys(overrides).length > 0) && (\n <button onClick={resetAll} style={{ ...btn(false), color: '#aaa' }}>\n Reset\n </button>\n )}\n </div>\n </div>\n {components.length === 0 && <div style={{ opacity: .6 }}>No components on this page yet.</div>}\n {components.map((c) => (\n <div key={c.id} style={{ borderTop: '1px solid #333', padding: '8px 0' }}>\n <div style={{ fontWeight: 600 }}>{c.id}{c.goal ? ` · goal: ${c.goal}` : ''}</div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 }}>\n {c.variantIds.map((v) => (\n <button key={v} onClick={() => choose(c.id, v)} style={btn(overrides[c.id] === v)}>\n {v}\n </button>\n ))}\n {overrides[c.id] && (\n <button onClick={() => resetVariant(c.id)} style={{ ...btn(false), color: '#aaa' }}>\n reset\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n <button aria-label=\"Sentient DevTools\" onClick={() => setOpen((o) => !o)}\n style={{ width: 40, height: 40, borderRadius: 20, border: 'none', background: '#3b82f6', color: '#fff',\n cursor: 'pointer', boxShadow: '0 4px 14px rgba(0,0,0,.3)' }}>◧</button>\n </div>\n );\n}\n","export type RegisteredComponent = { id: string; variantIds: string[]; goal?: string };\nexport type RegisteredSlot = {\n id: string;\n arms?: string[];\n dims?: Record<string, readonly string[]>;\n};\n\ntype RegistryState = {\n components: Map<string, RegisteredComponent>;\n slots: Map<string, RegisteredSlot>;\n sections: string[];\n listeners: Set<() => void>;\n};\n\n// Shared through a window global: the main entry and the /devtools entry are\n// separate bundles, each with its own copy of this module — module-local\n// state would give the devtools an always-empty registry in published apps.\nconst ssrFallback: RegistryState = {\n components: new Map(),\n slots: new Map(),\n sections: [],\n listeners: new Set(),\n};\n\nfunction state(): RegistryState {\n if (typeof window === 'undefined') return ssrFallback;\n const w = window as unknown as { __sentient_registry?: RegistryState };\n if (!w.__sentient_registry) {\n w.__sentient_registry = {\n components: new Map(),\n slots: new Map(),\n sections: [],\n listeners: new Set(),\n };\n }\n return w.__sentient_registry;\n}\n\nfunction emit(): void {\n for (const fn of state().listeners) fn();\n}\n\n/** Register (or re-register) a component. Returns an unregister function. */\nexport function registerComponent(c: RegisteredComponent): () => void {\n state().components.set(c.id, c);\n emit();\n return () => {\n state().components.delete(c.id);\n emit();\n };\n}\n\n/** Register (or re-register) a slot declaration. Returns an unregister function. */\nexport function registerSlot(s: RegisteredSlot): () => void {\n state().slots.set(s.id, s);\n emit();\n return () => {\n state().slots.delete(s.id);\n emit();\n };\n}\n\n/** Register the page's declared section ids (from AdaptiveRoot/provider). */\nexport function registerSections(sections: string[]): void {\n state().sections = [...sections];\n emit();\n}\n\nexport function getRegistered(): RegisteredComponent[] {\n return [...state().components.values()];\n}\n\nexport function getRegisteredSlots(): RegisteredSlot[] {\n return [...state().slots.values()];\n}\n\nexport function getRegisteredSections(): string[] {\n return [...state().sections];\n}\n\nexport function subscribeRegistry(fn: () => void): () => void {\n const listeners = state().listeners;\n listeners.add(fn);\n return () => {\n listeners.delete(fn);\n };\n}\n","function store(): Record<string, string> {\n const w = window as unknown as { __sentient_overrides?: Record<string, string> };\n if (!w.__sentient_overrides) w.__sentient_overrides = {};\n return w.__sentient_overrides;\n}\n\n/** Force `componentId` to render `variantId` — read by `useAssignment`. */\nexport function setVariantOverride(componentId: string, variantId: string): void {\n store()[componentId] = variantId;\n}\n\nexport function clearVariantOverride(componentId: string): void {\n delete store()[componentId];\n}\n\nexport function getOverrides(): Record<string, string> {\n return { ...store() };\n}\n","import type { SentientClient } from '@sentientui/core';\n\ntype PreviewState = { on: boolean; listeners: Set<() => void> };\nconst ssrFallback: PreviewState = { on: false, listeners: new Set() };\n\n// Window-backed: the /devtools entry (a separate bundle) toggles preview mode\n// and the provider (main bundle) must observe it.\nfunction state(): PreviewState {\n if (typeof window === 'undefined') return ssrFallback;\n const w = window as unknown as { __sentient_preview?: PreviewState };\n if (!w.__sentient_preview) w.__sentient_preview = { on: false, listeners: new Set() };\n return w.__sentient_preview;\n}\n\nexport function setPreviewMode(on: boolean): void {\n const s = state();\n if (s.on === on) return;\n s.on = on;\n for (const fn of s.listeners) fn();\n}\n\nexport function getPreviewMode(): boolean {\n return state().on;\n}\n\nexport function subscribePreview(fn: () => void): () => void {\n const listeners = state().listeners;\n listeners.add(fn);\n return () => {\n listeners.delete(fn);\n };\n}\n\n/**\n * Wraps a client so it writes nothing: reads pass through, all emitters no-op.\n * Used while previewing variants/personas so no `variant_assigned`, goal, or\n * session events are sent.\n */\nexport function createPreviewClient(inner: SentientClient): SentientClient {\n return {\n isLocal: inner.isLocal,\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n fetchWeights: () => Promise.resolve([]),\n getAssignment: (componentId, segment) => inner.getAssignment(componentId, segment),\n assign: (componentId, variantIds, agentData, agentDataByVariant) =>\n inner.assign(componentId, variantIds, agentData, agentDataByVariant),\n // Reads pass through; decide is a write (slot decisions persist server-side)\n // so preview mode never issues it.\n decide: () => Promise.resolve(null),\n getSlotResult: (slotId) => inner.getSlotResult(slotId),\n getPersona: () => inner.getPersona(),\n getGraph: () => inner.getGraph(),\n destroy: () => inner.destroy(),\n };\n}\n","/**\n * Cross-bundle re-render bus for devtools overrides. The main entry and the\n * /devtools entry are separate bundles, so state and notifications go through\n * window (a version counter + a DOM event) — never module-local state.\n */\nconst EVENT = 'sentient:overrides-changed';\n\ntype VersionWindow = Window & { __sentient_overrides_version?: number };\n\nexport function getOverridesVersion(): number {\n if (typeof window === 'undefined') return 0;\n return (window as VersionWindow).__sentient_overrides_version ?? 0;\n}\n\nexport function notifyOverridesChanged(): void {\n if (typeof window === 'undefined') return;\n const w = window as VersionWindow;\n w.__sentient_overrides_version = (w.__sentient_overrides_version ?? 0) + 1;\n window.dispatchEvent(new Event(EVENT));\n}\n\nexport function subscribeOverridesChanged(fn: () => void): () => void {\n if (typeof window === 'undefined') return () => undefined;\n window.addEventListener(EVENT, fn);\n return () => window.removeEventListener(EVENT, fn);\n}\n","/** Provider → devtools config handoff. Window-backed: the /devtools entry is a\n * separate bundle and cannot share the provider's React context instance. */\nexport type DevtoolsConfig = {\n apiKey: string;\n apiBaseUrl: string;\n isLocal: boolean;\n};\n\ntype ConfigWindow = Window & { __sentient_devtools_config?: DevtoolsConfig };\n\nexport function publishDevtoolsConfig(config: DevtoolsConfig): void {\n if (typeof window === 'undefined') return;\n (window as ConfigWindow).__sentient_devtools_config = config;\n}\n\nexport function readDevtoolsConfig(): DevtoolsConfig | null {\n if (typeof window === 'undefined') return null;\n return (window as ConfigWindow).__sentient_devtools_config ?? null;\n}\n"],"mappings":"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","names":["useEffect","useReducer","useState","PERSONAS","PERSONA_DISPLAY","confidenceBand","SNAPSHOT_STORAGE_KEY_PREFIX","ssrFallback","state","w","getRegistered","state","getRegisteredSlots","getRegisteredSections","subscribeRegistry","fn","listeners","store","w","setVariantOverride","componentId","variantId","clearVariantOverride","getOverrides","__spreadValues","ssrFallback","state","w","setPreviewMode","on","s","fn","getPreviewMode","EVENT","notifyOverridesChanged","_a","w","EVENT","readDevtoolsConfig","_a","jsx","jsxs","_a","IS_PROD","DEFAULT_API_BASE_URL","LOCAL_MODE_DEVTOOLS_BANNER","applyOutcome","result","_b","id","variantId","setVariantOverride","w","__spreadValues","setPreviewMode","notifyOverridesChanged","readSessionId","match","slotDecls","getRegisteredSlots","s","forcePersonaKeyed","apiKey","apiBaseUrl","persona","res","getRegisteredSections","getRegistered","c","data","__spreadProps","forcePersonaLocal","mod","outcome","confidenceBand","resetAll","getOverrides","clearVariantOverride","stale","i","key","SNAPSHOT_STORAGE_KEY_PREFIX","e","btn","active","AdaptiveDevtools","open","setOpen","useState","activePersona","setActivePersona","force","useReducer","n","useEffect","subscribeRegistry","config","readDevtoolsConfig","useLocalEngine","components","overrides","choose","resetVariant","choosePersona","getPreviewMode","PERSONAS","p","PERSONA_DISPLAY","v"]}