@sentientui/react 0.27.0 → 0.29.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.
Files changed (44) hide show
  1. package/dist/devtools.js +1 -1
  2. package/dist/devtools.js.map +1 -1
  3. package/dist/devtools.mjs +1 -1
  4. package/dist/devtools.mjs.map +1 -1
  5. package/dist/index.d.cts +32 -2
  6. package/dist/index.d.ts +32 -2
  7. package/dist/index.js +2 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2 -2
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/next/adaptive-root.d.ts +10 -1
  12. package/dist/next/adaptive-root.js +2 -2
  13. package/dist/next/adaptive-root.js.map +1 -1
  14. package/dist/server.d.cts +3 -0
  15. package/dist/server.d.ts +3 -0
  16. package/dist/server.js +1 -1
  17. package/dist/server.js.map +1 -1
  18. package/dist/server.mjs +1 -1
  19. package/dist/server.mjs.map +1 -1
  20. package/dist/testing/msw.d.cts +3 -0
  21. package/dist/testing/msw.d.ts +3 -0
  22. package/dist/testing/msw.js +1 -1
  23. package/dist/testing/msw.js.map +1 -1
  24. package/dist/testing/msw.mjs +1 -1
  25. package/dist/testing/msw.mjs.map +1 -1
  26. package/dist/testing/node.d.cts +3 -0
  27. package/dist/testing/node.d.ts +3 -0
  28. package/dist/testing/node.js +1 -1
  29. package/dist/testing/node.js.map +1 -1
  30. package/dist/testing/node.mjs +1 -1
  31. package/dist/testing/node.mjs.map +1 -1
  32. package/dist/testing/react.d.cts +3 -0
  33. package/dist/testing/react.d.ts +3 -0
  34. package/dist/testing/react.js +1 -1
  35. package/dist/testing/react.js.map +1 -1
  36. package/dist/testing/react.mjs +1 -1
  37. package/dist/testing/react.mjs.map +1 -1
  38. package/dist/testing.d.cts +3 -0
  39. package/dist/testing.d.ts +3 -0
  40. package/dist/testing.js +1 -1
  41. package/dist/testing.js.map +1 -1
  42. package/dist/testing.mjs +1 -1
  43. package/dist/testing.mjs.map +1 -1
  44. package/package.json +4 -3
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/override-events.ts","../src/testing/scenario.ts","../src/testing/events.ts","../src/testing/resolve.ts","../src/testing/playwright.ts","../src/testing/cypress.ts","../src/testing/index.tsx"],"sourcesContent":["/**\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","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n","import { resolveScenario } from './resolve.js';\nimport { getSentientEvents, type CapturedEvent } from './events.js';\nimport type { SentientScenario } from './scenario.js';\n\ntype InitData = {\n overrides: Record<string, string>;\n layout: string[] | null;\n slots: Record<string, string | Record<string, string>> | null;\n persona: string | null;\n confidence: number;\n};\n\ntype PwRoute = {\n request(): { method(): string; url(): string; postData(): string | null };\n fulfill(r: { status: number; contentType?: string; body?: string }): Promise<void>;\n continue(): Promise<void>;\n};\n\n/** Structural subset of Playwright's `Page` — avoids a hard dependency on @playwright/test. */\ntype PwPage = {\n // Return types are intentionally `Promise<unknown>`: the helper never uses\n // them, and pinning `Promise<void>` breaks against Playwright versions whose\n // `addInitScript`/`route` resolve to `Disposable` rather than `void`.\n addInitScript(script: (arg: InitData) => void, arg: InitData): Promise<unknown>;\n route(url: string, handler: (route: PwRoute) => unknown): Promise<unknown>;\n};\n\n/**\n * Make a Playwright `page` serve a SentientUI scenario: forces\n * variants/layout/slots/persona before load (including the persona html\n * attributes) and stubs every `/v1/*` request from the scenario, capturing\n * posted events. Returns a handle exposing `.events()`.\n */\nexport async function mockSentient(\n page: PwPage,\n scenario: SentientScenario = {},\n): Promise<{ events: () => CapturedEvent[] }> {\n const initData: InitData = {\n overrides: scenario.variants ?? {},\n layout: scenario.layout ?? null,\n slots: scenario.slots ?? null,\n persona: scenario.persona ?? null,\n confidence: scenario.confidence ?? 1,\n };\n await page.addInitScript((data: InitData) => {\n const w = window as unknown as Record<string, unknown>;\n w.__sentient_overrides = data.overrides;\n if (data.layout) w.__sentient_layout_override = data.layout;\n if (data.slots) w.__sentient_slot_overrides = data.slots;\n if (data.persona) {\n w.__sentient_persona_override = { persona: data.persona, confidence: data.confidence };\n try {\n // Inline banding: this function is SERIALIZED into the page context,\n // so it cannot close over imports. Cutoffs pinned to policy\n // confidenceBand (<0.3 low, <0.7 medium, else high).\n const band = data.confidence < 0.3 ? 'low' : data.confidence < 0.7 ? 'medium' : 'high';\n document.documentElement.setAttribute('data-sentient-persona', data.persona);\n document.documentElement.setAttribute('data-sentient-confidence', band);\n } catch {\n /* document not ready — the SDK adopts the override globals instead */\n }\n }\n }, initData);\n\n await page.route('**/v1/**', async (route) => {\n const req = route.request();\n const resolved = await resolveScenario(scenario, req.method(), req.url(), req.postData());\n if (!resolved) return route.continue();\n await route.fulfill({\n status: resolved.status,\n contentType: 'application/json',\n body: JSON.stringify(resolved.json ?? {}),\n });\n });\n\n return { events: () => getSentientEvents() };\n}\n","import { resolveScenario } from './resolve.js';\nimport { confidenceBandOf, type SentientScenario } from './scenario.js';\n\ntype CyReq = {\n method: string;\n url: string;\n body: unknown;\n reply(r: { statusCode: number; body?: unknown }): void;\n};\n\n/** Structural subset of Cypress's `cy` — avoids a hard dependency on cypress. */\ntype Cy = {\n intercept(url: string, handler: (req: CyReq) => void | Promise<void>): unknown;\n on(event: string, cb: (win: Record<string, unknown>) => void): unknown;\n};\n\n/**\n * Make Cypress serve a SentientUI scenario: forces variants/layout/slots/persona\n * on the app window before load (including the persona html attributes) and\n * stubs every `/v1/*` request from the scenario. Call in a `beforeEach` before\n * `cy.visit`.\n *\n * Note: for event assertions in Cypress, alias the intercept (`cy.intercept(...).as('ev')`)\n * and `cy.wait('@ev')` — captured module state does not cross the browser/Node boundary.\n */\nexport function mockSentientCypress(cy: Cy, scenario: SentientScenario = {}): void {\n const overrides = scenario.variants ?? {};\n const layout = scenario.layout ?? null;\n const slots = scenario.slots ?? null;\n const persona = scenario.persona ?? null;\n const confidence = scenario.confidence ?? 1;\n\n cy.on('window:before:load', (win) => {\n win.__sentient_overrides = overrides;\n if (layout) win.__sentient_layout_override = layout;\n if (slots) win.__sentient_slot_overrides = slots;\n if (persona) {\n win.__sentient_persona_override = { persona, confidence };\n try {\n const doc = (win as { document?: Document }).document;\n doc?.documentElement?.setAttribute('data-sentient-persona', persona);\n doc?.documentElement?.setAttribute('data-sentient-confidence', confidenceBandOf(confidence));\n } catch {\n /* document not ready — the SDK adopts the override globals instead */\n }\n }\n });\n\n cy.intercept('**/v1/**', async (req) => {\n const resolved = await resolveScenario(\n scenario,\n req.method,\n req.url,\n req.body != null ? JSON.stringify(req.body) : null,\n );\n if (!resolved) return; // passthrough\n req.reply({ statusCode: resolved.status, body: (resolved.json ?? '') as unknown });\n });\n}\n","import { applyScenario, resetScenario, type SentientScenario } from './scenario.js';\n\nexport type { SentientScenario };\nexport { applyScenario, resetScenario };\n// NOT re-exported here: `scenarioToHandlers` (msw) lives at\n// `@sentientui/react/testing/msw`, and `renderWithSentient`\n// (@testing-library/react) at `@sentientui/react/testing/react`. Both are\n// OPTIONAL peers, and their former top-level imports from this entry crashed\n// any consumer without them installed the moment they imported ANY testing\n// helper — the same reason msw/node already lives at `/testing/node`.\nexport { resolveScenario, type ResolvedResponse } from './resolve.js';\nexport { getSentientEvents, clearSentientEvents, hasFiredGoal, type CapturedEvent } from './events.js';\nexport type { ScenarioWeight, ScenarioApiOverride } from './scenario.js';\nexport { mockSentient } from './playwright.js';\nexport { mockSentientCypress } from './cypress.js';\n\n/** Call in a test setup file to reset forced state after each test. */\nexport function setupSentientTests(): void {\n const g = globalThis as unknown as { afterEach?: (fn: () => void) => void };\n g.afterEach?.(() => resetScenario());\n}\n"],"mappings":"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","names":["EVENT","notifyOverridesChanged","_a","w","EVENT","confidenceBandOf","c","applyScenario","scenario","_a","_b","_c","w","__spreadValues","d","e","notifyOverridesChanged","resetScenario","captured","recordEvent","getSentientEvents","clearSentientEvents","hasFiredGoal","events","goalName","e","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","mockSentient","page","scenario","_a","_b","_c","_d","_e","initData","data","w","band","e","route","req","resolved","resolveScenario","getSentientEvents","mockSentientCypress","cy","scenario","_a","_b","_c","_d","_e","overrides","layout","slots","persona","confidence","win","doc","confidenceBandOf","e","req","resolved","resolveScenario","setupSentientTests","_a","g","resetScenario"]}
1
+ {"version":3,"sources":["../src/override-events.ts","../src/testing/scenario.ts","../src/testing/events.ts","../src/testing/resolve.ts","../src/testing/playwright.ts","../src/testing/cypress.ts","../src/testing/index.tsx"],"sourcesContent":["/**\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","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n /** Forced registry slot config (AdaptiveSlot content/blocks): slot id → entry.\n * Pair with `slots` to name which arm is \"served\" (blocks are keyed by arm). */\n slotConfig?: Record<string, unknown>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_slot_config_overrides?: Record<string, unknown>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.slotConfig) w.__sentient_slot_config_overrides = { ...scenario.slotConfig };\n else delete w.__sentient_slot_config_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_slot_config_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n","import { resolveScenario } from './resolve.js';\nimport { getSentientEvents, type CapturedEvent } from './events.js';\nimport type { SentientScenario } from './scenario.js';\n\ntype InitData = {\n overrides: Record<string, string>;\n layout: string[] | null;\n slots: Record<string, string | Record<string, string>> | null;\n persona: string | null;\n confidence: number;\n};\n\ntype PwRoute = {\n request(): { method(): string; url(): string; postData(): string | null };\n fulfill(r: { status: number; contentType?: string; body?: string }): Promise<void>;\n continue(): Promise<void>;\n};\n\n/** Structural subset of Playwright's `Page` — avoids a hard dependency on @playwright/test. */\ntype PwPage = {\n // Return types are intentionally `Promise<unknown>`: the helper never uses\n // them, and pinning `Promise<void>` breaks against Playwright versions whose\n // `addInitScript`/`route` resolve to `Disposable` rather than `void`.\n addInitScript(script: (arg: InitData) => void, arg: InitData): Promise<unknown>;\n route(url: string, handler: (route: PwRoute) => unknown): Promise<unknown>;\n};\n\n/**\n * Make a Playwright `page` serve a SentientUI scenario: forces\n * variants/layout/slots/persona before load (including the persona html\n * attributes) and stubs every `/v1/*` request from the scenario, capturing\n * posted events. Returns a handle exposing `.events()`.\n */\nexport async function mockSentient(\n page: PwPage,\n scenario: SentientScenario = {},\n): Promise<{ events: () => CapturedEvent[] }> {\n const initData: InitData = {\n overrides: scenario.variants ?? {},\n layout: scenario.layout ?? null,\n slots: scenario.slots ?? null,\n persona: scenario.persona ?? null,\n confidence: scenario.confidence ?? 1,\n };\n await page.addInitScript((data: InitData) => {\n const w = window as unknown as Record<string, unknown>;\n w.__sentient_overrides = data.overrides;\n if (data.layout) w.__sentient_layout_override = data.layout;\n if (data.slots) w.__sentient_slot_overrides = data.slots;\n if (data.persona) {\n w.__sentient_persona_override = { persona: data.persona, confidence: data.confidence };\n try {\n // Inline banding: this function is SERIALIZED into the page context,\n // so it cannot close over imports. Cutoffs pinned to policy\n // confidenceBand (<0.3 low, <0.7 medium, else high).\n const band = data.confidence < 0.3 ? 'low' : data.confidence < 0.7 ? 'medium' : 'high';\n document.documentElement.setAttribute('data-sentient-persona', data.persona);\n document.documentElement.setAttribute('data-sentient-confidence', band);\n } catch {\n /* document not ready — the SDK adopts the override globals instead */\n }\n }\n }, initData);\n\n await page.route('**/v1/**', async (route) => {\n const req = route.request();\n const resolved = await resolveScenario(scenario, req.method(), req.url(), req.postData());\n if (!resolved) return route.continue();\n await route.fulfill({\n status: resolved.status,\n contentType: 'application/json',\n body: JSON.stringify(resolved.json ?? {}),\n });\n });\n\n return { events: () => getSentientEvents() };\n}\n","import { resolveScenario } from './resolve.js';\nimport { confidenceBandOf, type SentientScenario } from './scenario.js';\n\ntype CyReq = {\n method: string;\n url: string;\n body: unknown;\n reply(r: { statusCode: number; body?: unknown }): void;\n};\n\n/** Structural subset of Cypress's `cy` — avoids a hard dependency on cypress. */\ntype Cy = {\n intercept(url: string, handler: (req: CyReq) => void | Promise<void>): unknown;\n on(event: string, cb: (win: Record<string, unknown>) => void): unknown;\n};\n\n/**\n * Make Cypress serve a SentientUI scenario: forces variants/layout/slots/persona\n * on the app window before load (including the persona html attributes) and\n * stubs every `/v1/*` request from the scenario. Call in a `beforeEach` before\n * `cy.visit`.\n *\n * Note: for event assertions in Cypress, alias the intercept (`cy.intercept(...).as('ev')`)\n * and `cy.wait('@ev')` — captured module state does not cross the browser/Node boundary.\n */\nexport function mockSentientCypress(cy: Cy, scenario: SentientScenario = {}): void {\n const overrides = scenario.variants ?? {};\n const layout = scenario.layout ?? null;\n const slots = scenario.slots ?? null;\n const persona = scenario.persona ?? null;\n const confidence = scenario.confidence ?? 1;\n\n cy.on('window:before:load', (win) => {\n win.__sentient_overrides = overrides;\n if (layout) win.__sentient_layout_override = layout;\n if (slots) win.__sentient_slot_overrides = slots;\n if (persona) {\n win.__sentient_persona_override = { persona, confidence };\n try {\n const doc = (win as { document?: Document }).document;\n doc?.documentElement?.setAttribute('data-sentient-persona', persona);\n doc?.documentElement?.setAttribute('data-sentient-confidence', confidenceBandOf(confidence));\n } catch {\n /* document not ready — the SDK adopts the override globals instead */\n }\n }\n });\n\n cy.intercept('**/v1/**', async (req) => {\n const resolved = await resolveScenario(\n scenario,\n req.method,\n req.url,\n req.body != null ? JSON.stringify(req.body) : null,\n );\n if (!resolved) return; // passthrough\n req.reply({ statusCode: resolved.status, body: (resolved.json ?? '') as unknown });\n });\n}\n","import { applyScenario, resetScenario, type SentientScenario } from './scenario.js';\n\nexport type { SentientScenario };\nexport { applyScenario, resetScenario };\n// NOT re-exported here: `scenarioToHandlers` (msw) lives at\n// `@sentientui/react/testing/msw`, and `renderWithSentient`\n// (@testing-library/react) at `@sentientui/react/testing/react`. Both are\n// OPTIONAL peers, and their former top-level imports from this entry crashed\n// any consumer without them installed the moment they imported ANY testing\n// helper — the same reason msw/node already lives at `/testing/node`.\nexport { resolveScenario, type ResolvedResponse } from './resolve.js';\nexport { getSentientEvents, clearSentientEvents, hasFiredGoal, type CapturedEvent } from './events.js';\nexport type { ScenarioWeight, ScenarioApiOverride } from './scenario.js';\nexport { mockSentient } from './playwright.js';\nexport { mockSentientCypress } from './cypress.js';\n\n/** Call in a test setup file to reset forced state after each test. */\nexport function setupSentientTests(): void {\n const g = globalThis as unknown as { afterEach?: (fn: () => void) => void };\n g.afterEach?.(() => resetScenario());\n}\n"],"mappings":";yVAKA,IAAMA,EAAQ,6BASP,SAASC,GAA+B,CAd/C,IAAAC,EAeE,GAAI,OAAO,QAAW,YAAa,OACnC,IAAMC,EAAI,OACVA,EAAE,+BAAgCD,EAAAC,EAAE,+BAAF,KAAAD,EAAkC,GAAK,EACzE,OAAO,cAAc,IAAI,MAAME,CAAK,CAAC,CACvC,CCmBO,SAASC,EAAiBC,EAAsC,CACrE,OAAOA,EAAI,GAAM,MAAQA,EAAI,GAAM,SAAW,MAChD,CAGO,SAASC,EAAcC,EAA6B,CAAC,EAAS,CA3CrE,IAAAC,EAAAC,EAAAC,EA4CE,IAAMC,EAAI,OAQV,GAPAA,EAAE,qBAAuBC,EAAA,IAAMJ,EAAAD,EAAS,WAAT,KAAAC,EAAqB,CAAC,GACjDD,EAAS,OAAQI,EAAE,2BAA6BJ,EAAS,OACxD,OAAOI,EAAE,2BACVJ,EAAS,MAAOI,EAAE,0BAA4BC,EAAA,GAAKL,EAAS,OAC3D,OAAOI,EAAE,0BACVJ,EAAS,WAAYI,EAAE,iCAAmCC,EAAA,GAAKL,EAAS,YACvE,OAAOI,EAAE,iCACVJ,EAAS,QAAS,CACpBI,EAAE,4BAA8B,CAC9B,QAASJ,EAAS,QAClB,YAAYE,EAAAF,EAAS,aAAT,KAAAE,EAAuB,CACrC,EACA,GAAI,CACF,IAAMI,EAAI,SAAS,gBACnBA,EAAE,aAAa,wBAAyBN,EAAS,OAAO,EACxDM,EAAE,aAAa,2BAA4BT,GAAiBM,EAAAH,EAAS,aAAT,KAAAG,EAAuB,CAAC,CAAC,CACvF,OAAQI,EAAA,CAER,CACF,MACE,OAAOH,EAAE,4BAMXI,EAAuB,CACzB,CAGO,SAASC,GAAsB,CACpC,IAAML,EAAI,OACV,OAAOA,EAAE,qBACT,OAAOA,EAAE,2BACT,OAAOA,EAAE,0BACT,OAAOA,EAAE,iCACT,OAAOA,EAAE,4BACT,GAAI,CACF,SAAS,gBAAgB,gBAAgB,uBAAuB,EAChE,SAAS,gBAAgB,gBAAgB,0BAA0B,CACrE,OAAQG,EAAA,CAER,CACAC,EAAuB,CACzB,CCjFA,IAAME,EAA4B,CAAC,EAE5B,SAASC,EAAY,EAAwB,CAClDD,EAAS,KAAK,CAAC,CACjB,CAEO,SAASE,GAAqC,CACnD,MAAO,CAAC,GAAGF,CAAQ,CACrB,CAEO,SAASG,GAA4B,CAC1CH,EAAS,OAAS,CACpB,CAGO,SAASI,EAAaC,EAAyBC,EAA2B,CAC/E,OAAOD,EAAO,KACXE,IAAOA,EAAE,YAAc,iBAAmBA,EAAE,YAAc,SAAWA,EAAE,WAAaD,CACvF,CACF,CCrBA,SAASE,GAAOC,EAAqB,CANrC,IAAAC,EAOE,GAAI,CAAE,OAAO,IAAI,IAAID,CAAG,EAAE,QAAU,OAAQE,EAAA,CAAE,OAAOD,EAAAD,EAAI,MAAM,GAAG,EAAE,CAAC,IAAhB,KAAAC,EAAqBD,CAAK,CACjF,CAEA,eAAeG,GAAYC,EAA4BC,EAAiD,CAVxG,IAAAJ,EAAAK,EAAAC,EAWE,IAAMC,GAAqCP,EAAAG,EAAS,MAAT,YAAAH,EAAeI,GAC1D,OAAIG,IAAM,OAAkB,KACxBA,IAAM,QAAgB,CAAE,OAAQ,GAAI,EACpC,OAAOA,GAAM,SAAiB,CAAE,OAAQA,CAAE,GAC1CA,EAAE,SAAS,MAAM,IAAI,QAASC,GAAM,WAAWA,EAAGD,EAAE,OAAO,CAAC,EACzD,CAAE,QAAQF,EAAAE,EAAE,SAAF,KAAAF,EAAY,IAAK,MAAMC,EAAAC,EAAE,OAAF,KAAAD,EAAU,CAAC,CAAE,EACvD,CAOA,eAAsBG,EACpBN,EACAO,EACAX,EACAY,EACkC,CA7BpC,IAAAX,EAAAK,EAAAC,EAAAM,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EA8BE,IAAMC,EAAOhC,GAAOC,CAAG,EACvB,GAAI,CAAC+B,EAAK,SAAS,MAAM,EAAG,OAAO,KAEnC,IAAM1B,EAAQ,SAAUJ,EAAA8B,EAAK,MAAM,MAAM,EAAE,CAAC,IAApB,KAAA9B,EAAyB,IAC3C+B,EAAW,MAAM7B,GAAYC,EAAUC,CAAK,EAClD,GAAI2B,EAAU,OAAOA,EAErB,IAAMC,EAAOrB,EAAY,KAAK,MAAMA,CAAQ,EAAgB,CAAC,EAE7D,GAAIP,IAAU,eAAgB,MAAO,CAAE,OAAQ,GAAI,EAEnD,GAAIA,IAAU,aAAc,CAC1B,QAAWH,KAAK+B,EAAyBC,EAAYhC,CAAC,EACtD,MAAO,CAAE,OAAQ,GAAI,CACvB,CAEA,GAAIG,IAAU,YACZ,OAAA6B,EAAY,CAAE,UAAW,OAAQ,SAAWD,EAA2B,IAAK,CAAC,EACtE,CAAE,OAAQ,GAAI,EAGvB,GAAI5B,IAAU,aAAc,CAC1B,IAAM8B,EAAIF,EAEV,MAAO,CAAE,OAAQ,IAAK,KAAM,CAAE,WADZnB,GAAAD,GAAAP,EAAAF,EAAS,WAAT,YAAAE,EAAoB6B,EAAE,eAAtB,KAAAtB,GAAsCN,EAAA4B,EAAE,aAAF,YAAA5B,EAAe,KAArD,KAAAO,EAA2D,UACpC,gBAAiB,GAAO,CAAE,CACrE,CAEA,GAAIT,IAAU,aAAc,CAC1B,IAAM8B,EAAIF,EAUJG,EAAgC,CACpC,aAFkBpB,EAAAZ,EAAS,SAAT,KAAAY,IAAoBD,EAAAoB,EAAE,WAAF,KAAApB,EAAc,CAAC,GAAG,IAAKsB,GAAMA,EAAE,EAAE,EAGvE,aAAapB,EAAAb,EAAS,WAAT,KAAAa,EAAqB,CAAC,EACnC,SAASC,EAAAd,EAAS,UAAT,KAAAc,EAAoB,UAC7B,YAAYC,EAAAf,EAAS,aAAT,KAAAe,EAAuB,CACrC,EAEA,GAAIgB,EAAE,OAASA,EAAE,MAAM,OAAS,EAAG,CACjC,IAAMG,EAAiC,CAAC,EACxC,QAAWC,KAAQJ,EAAE,MACnBG,EAAMC,EAAK,EAAE,GAAIlB,GAAAD,EAAAhB,EAAS,QAAT,YAAAgB,EAAiBmB,EAAK,MAAtB,KAAAlB,EAA6BmB,EAAkBD,CAAI,EAEtEH,EAAK,MAAQE,CACf,CACA,MAAO,CAAE,OAAQ,IAAK,KAAAF,CAAK,CAC7B,CAEA,GAAI/B,IAAU,cAAe,CAC3B,IAAM8B,EAAIF,EAUJQ,GAAclB,EAAAnB,EAAS,SAAT,KAAAmB,IAAoBD,EAAAa,EAAE,WAAF,KAAAb,EAAc,CAAC,GAAG,IAAKe,GAAMA,EAAE,EAAE,EACnEK,GAAUjB,GAAAD,EAAAW,EAAE,UAAF,KAAAX,EAAapB,EAAS,UAAtB,KAAAqB,EAAiC,UAC3CkB,GAAajB,EAAAtB,EAAS,aAAT,KAAAsB,EAAuB,EACpCU,EAAgC,CACpC,YAAAK,EACA,aAAad,EAAAvB,EAAS,WAAT,KAAAuB,EAAqB,CAAC,EACnC,QAAAe,EACA,QAAS,CAAC,EACV,kBAAmB,CAAE,QAAAA,EAAS,WAAYE,EAAiBD,CAAU,CAAE,CACzE,EACA,GAAIR,EAAE,OAASA,EAAE,MAAM,OAAS,EAAG,CACjC,IAAMG,EAAiC,CAAC,EACxC,QAAWC,KAAQJ,EAAE,MACnBG,EAAMC,EAAK,EAAE,GAAIV,GAAAD,EAAAxB,EAAS,QAAT,YAAAwB,EAAiBW,EAAK,MAAtB,KAAAV,EAA6BW,EAAkBD,CAAI,EAEtEH,EAAK,MAAQE,CACf,CACA,MAAO,CAAE,OAAQ,IAAK,KAAAF,CAAK,CAC7B,CAEA,OAAI/B,IAAU,cAEL,CAAE,OAAQ,IAAK,KAAM,CAAE,WADX,OAAO,SAAQyB,EAAA1B,EAAS,UAAT,KAAA0B,EAAoB,CAAC,CAAC,EAAE,IAAI,CAAC,CAACe,EAAaC,CAAQ,KAAO,CAAE,YAAAD,EAAa,UAAW,EAAG,SAAAC,CAAS,EAAE,CAC3F,CAAE,EAGtC,IACT,CAGA,SAASN,EAAkBD,EAIS,CAjIpC,IAAAtC,EAAAK,EAAAC,EAkIE,GAAIgC,EAAK,KACP,OAAO,OAAOA,EAAK,UAAa,SAAWA,EAAK,UAAWtC,EAAAsC,EAAK,KAAK,CAAC,IAAX,KAAAtC,EAAgB,WAE7E,IAAM8C,EAA8B,CAAC,EACrC,OAAW,CAACC,EAAKC,CAAM,IAAK,OAAO,SAAQ3C,EAAAiC,EAAK,OAAL,KAAAjC,EAAa,CAAC,CAAC,EAAG,CAC3D,IAAM4C,EACJ,OAAOX,EAAK,UAAa,UAAYA,EAAK,WAAa,KAAOA,EAAK,SAASS,CAAG,EAAI,OACrFD,EAAIC,CAAG,GAAIzC,EAAA2C,GAAA,KAAAA,EAAYD,EAAO,CAAC,IAApB,KAAA1C,EAAyB,EACtC,CACA,OAAOwC,CACT,CC3GA,eAAsBI,GACpBC,EACAC,EAA6B,CAAC,EACc,CApC9C,IAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAqCE,IAAMC,EAAqB,CACzB,WAAWL,EAAAD,EAAS,WAAT,KAAAC,EAAqB,CAAC,EACjC,QAAQC,EAAAF,EAAS,SAAT,KAAAE,EAAmB,KAC3B,OAAOC,EAAAH,EAAS,QAAT,KAAAG,EAAkB,KACzB,SAASC,EAAAJ,EAAS,UAAT,KAAAI,EAAoB,KAC7B,YAAYC,EAAAL,EAAS,aAAT,KAAAK,EAAuB,CACrC,EACA,aAAMN,EAAK,cAAeQ,GAAmB,CAC3C,IAAMC,EAAI,OAIV,GAHAA,EAAE,qBAAuBD,EAAK,UAC1BA,EAAK,SAAQC,EAAE,2BAA6BD,EAAK,QACjDA,EAAK,QAAOC,EAAE,0BAA4BD,EAAK,OAC/CA,EAAK,QAAS,CAChBC,EAAE,4BAA8B,CAAE,QAASD,EAAK,QAAS,WAAYA,EAAK,UAAW,EACrF,GAAI,CAIF,IAAME,EAAOF,EAAK,WAAa,GAAM,MAAQA,EAAK,WAAa,GAAM,SAAW,OAChF,SAAS,gBAAgB,aAAa,wBAAyBA,EAAK,OAAO,EAC3E,SAAS,gBAAgB,aAAa,2BAA4BE,CAAI,CACxE,OAAQC,EAAA,CAER,CACF,CACF,EAAGJ,CAAQ,EAEX,MAAMP,EAAK,MAAM,WAAY,MAAOY,GAAU,CAhEhD,IAAAV,EAiEI,IAAMW,EAAMD,EAAM,QAAQ,EACpBE,EAAW,MAAMC,EAAgBd,EAAUY,EAAI,OAAO,EAAGA,EAAI,IAAI,EAAGA,EAAI,SAAS,CAAC,EACxF,GAAI,CAACC,EAAU,OAAOF,EAAM,SAAS,EACrC,MAAMA,EAAM,QAAQ,CAClB,OAAQE,EAAS,OACjB,YAAa,mBACb,KAAM,KAAK,WAAUZ,EAAAY,EAAS,OAAT,KAAAZ,EAAiB,CAAC,CAAC,CAC1C,CAAC,CACH,CAAC,EAEM,CAAE,OAAQ,IAAMc,EAAkB,CAAE,CAC7C,CCnDO,SAASC,GAAoBC,EAAQC,EAA6B,CAAC,EAAS,CAzBnF,IAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EA0BE,IAAMC,GAAYL,EAAAD,EAAS,WAAT,KAAAC,EAAqB,CAAC,EAClCM,GAASL,EAAAF,EAAS,SAAT,KAAAE,EAAmB,KAC5BM,GAAQL,EAAAH,EAAS,QAAT,KAAAG,EAAkB,KAC1BM,GAAUL,EAAAJ,EAAS,UAAT,KAAAI,EAAoB,KAC9BM,GAAaL,EAAAL,EAAS,aAAT,KAAAK,EAAuB,EAE1CN,EAAG,GAAG,qBAAuBY,GAAQ,CAhCvC,IAAAV,EAAAC,EAoCI,GAHAS,EAAI,qBAAuBL,EACvBC,IAAQI,EAAI,2BAA6BJ,GACzCC,IAAOG,EAAI,0BAA4BH,GACvCC,EAAS,CACXE,EAAI,4BAA8B,CAAE,QAAAF,EAAS,WAAAC,CAAW,EACxD,GAAI,CACF,IAAME,EAAOD,EAAgC,UAC7CV,EAAAW,GAAA,YAAAA,EAAK,kBAAL,MAAAX,EAAsB,aAAa,wBAAyBQ,IAC5DP,EAAAU,GAAA,YAAAA,EAAK,kBAAL,MAAAV,EAAsB,aAAa,2BAA4BW,EAAiBH,CAAU,EAC5F,OAAQI,EAAA,CAER,CACF,CACF,CAAC,EAEDf,EAAG,UAAU,WAAY,MAAOgB,GAAQ,CAhD1C,IAAAd,EAiDI,IAAMe,EAAW,MAAMC,EACrBjB,EACAe,EAAI,OACJA,EAAI,IACJA,EAAI,MAAQ,KAAO,KAAK,UAAUA,EAAI,IAAI,EAAI,IAChD,EACKC,GACLD,EAAI,MAAM,CAAE,WAAYC,EAAS,OAAQ,MAAOf,EAAAe,EAAS,OAAT,KAAAf,EAAiB,EAAe,CAAC,CACnF,CAAC,CACH,CCzCO,SAASiB,IAA2B,CAjB3C,IAAAC,EAkBE,IAAMC,EAAI,YACVD,EAAAC,EAAE,YAAF,MAAAD,EAAA,KAAAC,EAAc,IAAMC,EAAc,EACpC","names":["EVENT","notifyOverridesChanged","_a","w","EVENT","confidenceBandOf","c","applyScenario","scenario","_a","_b","_c","w","__spreadValues","d","e","notifyOverridesChanged","resetScenario","captured","recordEvent","getSentientEvents","clearSentientEvents","hasFiredGoal","events","goalName","e","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","mockSentient","page","scenario","_a","_b","_c","_d","_e","initData","data","w","band","e","route","req","resolved","resolveScenario","getSentientEvents","mockSentientCypress","cy","scenario","_a","_b","_c","_d","_e","overrides","layout","slots","persona","confidence","win","doc","confidenceBandOf","e","req","resolved","resolveScenario","setupSentientTests","_a","g","resetScenario"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentientui/react",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "sideEffects": false,
@@ -28,6 +28,7 @@
28
28
  "module": "./dist/index.mjs",
29
29
  "types": "./dist/index.d.ts",
30
30
  "exports": {
31
+ "./package.json": "./package.json",
31
32
  ".": {
32
33
  "types": "./dist/index.d.ts",
33
34
  "import": "./dist/index.mjs",
@@ -74,8 +75,8 @@
74
75
  "llms.txt"
75
76
  ],
76
77
  "dependencies": {
77
- "@sentientui/policy": "0.9.0",
78
- "@sentientui/core": "0.25.0"
78
+ "@sentientui/core": "0.27.0",
79
+ "@sentientui/policy": "0.9.0"
79
80
  },
80
81
  "peerDependencies": {
81
82
  "@testing-library/react": ">=14.0.0",