@sentientui/core 0.26.0 → 0.27.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index-graph.ts","../src/uuid.ts","../src/storage-key.ts","../src/session.ts","../src/durable.ts","../src/queue.ts","../src/goal-queue.ts","../src/cache.ts","../src/session-meta.ts","../src/slots.ts","../src/snapshot.ts","../src/index.ts","../src/local-mode.ts","../src/blocks.ts","../src/micro-signals.ts","../src/graph.ts","../src/locator-from-dom.ts","../src/engagement/classify.ts","../src/scanner.ts"],"sourcesContent":["/**\n * Graph-capable entry point for @sentientui/core.\n *\n * Import from `@sentientui/core/graph` when you need DOM graph scanning and\n * page-structure sync. This entry pulls in `scanner.ts` and `graph.ts` at\n * build time, giving bundlers a real tree-shaking boundary. Standard A/B tests\n * should use `@sentientui/core` (the lean entry) instead.\n */\n\n// Re-export everything from the lean entry except `init`, which we override\n// below — so a graph consumer never needs dual-entry imports (importing the\n// lean entry alongside this one risks initialising two clients).\nexport {\n detectDeviceClass,\n detectTrafficSource,\n detectTimeOfDay,\n deriveSessionSegment,\n referrerDomainFromReferer,\n grantConsent,\n} from './index.js';\n// Sourced from their own modules (identical bindings to the lean barrel's):\n// snapshot/pre-paint helpers, slot helpers, blocks, micro-signals, and the\n// session cookie name — all were missing here, which made the comment above\n// a lie and forced consumers into dual-entry imports.\nexport {\n SNAPSHOT_STORAGE_KEY_PREFIX,\n readSnapshot,\n writeSnapshot,\n renderPrePaintScript,\n} from './snapshot.js';\nexport type { DecisionSnapshot, SlotConfigEntry, SlotOps, CompoundLocator } from './snapshot.js';\nexport { armOfResult, baselineResultFor, baselineSlots, toWireSlot } from './slots.js';\nexport type { SlotDeclInput, SlotResult } from './slots.js';\nexport * from './blocks.js';\nexport { attachMicroSignalDetectors } from './micro-signals.js';\nexport type { MicroSignalEmitter, MicroSignalType } from './micro-signals.js';\nexport { sessionCookieName, LEGACY_SESSION_COOKIE_NAME } from './storage-key.js';\n// SSR preload helpers moved to `@sentientui/core/server` in 0.6.0.\nexport type {\n SentientConfig,\n AssignResult,\n SentientClient,\n} from './index.js';\nexport type {\n SessionConfig,\n SessionManager,\n EventType,\n SentientEvent,\n QueueConfig,\n Assignment,\n} from './index.js';\n// EventQueue/AssignmentCache are no longer on the lean barrel — source them from\n// their own modules so the `/graph` subpath keeps exposing them unchanged.\nexport type { EventQueue } from './queue.js';\nexport type { AssignmentCache } from './cache.js';\n// Scanner + graph types (live in this entry only).\nexport type {\n ScannedNode,\n ScanResult,\n ContentAddedEvent,\n DOMScanner,\n} from './scanner.js';\nexport type {\n PageNode,\n GraphSnapshot,\n GraphConfig,\n GraphClient,\n} from './graph.js';\nexport { sanitizePageUrl } from './graph.js';\nexport { locatorFromElement } from './locator-from-dom.js';\n\nimport {\n init as initLean,\n isDoNotTrackEnabled,\n _registerConsentUpgradeInit,\n type SentientConfig,\n type SentientClient,\n} from './index.js';\nexport { isDoNotTrackEnabled };\nimport { LEGACY_SESSION_COOKIE_NAME, sessionCookieName } from './storage-key.js';\n\nconst DEFAULT_INGEST_URL = 'https://api.sentient-ui.com/v1/events';\nimport { createDOMScanner } from './scanner.js';\nimport { createGraphClient } from './graph.js';\n\nexport type GraphSentientConfig = SentientConfig & {\n /**\n * Enable DOM graph scanning and page-structure sync. Wires a MutationObserver\n * and a localStorage-backed graph. When `false` (default) this entry behaves\n * identically to `@sentientui/core`.\n */\n graph?: boolean;\n /**\n * Include captured heading / DOM text in graph sync payloads. OFF by default —\n * headings can contain account names or user-generated content. Structure\n * (component ids, semantic types, prominence) still syncs when `graph: true`.\n */\n captureDomText?: boolean;\n};\n\n// The client writes the per-project SUFFIXED cookie (sessionCookieName in\n// storage-key.ts). This reader kept the bare `_snt_uid` after namespacing\n// landed, so graph sync sent `sessionId: undefined` for every keyed project.\n// The bare name stays as a fallback for pre-namespacing identities.\nfunction readSntUid(apiKey?: string): string | undefined {\n const read = (name: string): string | undefined => {\n try {\n // Cookie names are `_snt_uid` + `_` + a pk_ key prefix — no regex\n // metacharacters, so interpolation is safe (same pattern as session.ts).\n const m = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));\n return m ? decodeURIComponent(m[1]!) : undefined;\n } catch {\n return undefined;\n }\n };\n return (apiKey ? read(sessionCookieName(apiKey)) : undefined) ?? read(LEGACY_SESSION_COOKIE_NAME);\n}\n\n/**\n * Graph-capable variant of `init()`. Identical to the lean `init` when\n * `config.graph` is `false` (or omitted). When `config.graph: true`, mounts\n * a DOM scanner + graph client and enables `client.getGraph()`.\n *\n * Import from `@sentientui/core/graph` — do not call the lean `init` alongside\n * this function as that would initialise two clients.\n */\n// Teardown for the graph resources (scanner + MutationObserver + debounce\n// timer + graph client) bound to each apiKey. initLean already disposes the\n// lean client's own timers/listeners on re-init, but it knows nothing about\n// these graph resources, so this entry tracks and tears them down itself —\n// otherwise an HMR / consent-toggle / provider-remount re-init would leak a\n// live MutationObserver and a pending sync timer per mount.\nconst _graphTeardowns = new Map<string, () => void>();\n\nexport function init(config: GraphSentientConfig): SentientClient {\n const client = initLean(config);\n\n // Mirror initLean's opt-out gate: a DNT/GPC visitor (or consent:false) must\n // not get the DOM scanner + graph sync, which POST page-structure beacons and\n // read `_snt_uid` — tracking that bypasses the lean client's own gating, even\n // under `preConsentBehavior: 'statistical_winner'` (audit P1).\n const dntBlocked = config.respectDoNotTrack !== false && isDoNotTrackEnabled();\n const gated = config.consent === false || dntBlocked;\n\n // Zero-network contract, mirroring the lean init's own gate: `localMode:\n // true` forces the on-device engine, and a missing OR INVALID (non-`pk_`)\n // key means the lean client is keyless-local or disabled. This used to check\n // only `!apiKey`, so a typo'd key — which the React provider's default\n // `graph: true` reaches — still mounted the scanner and POSTed\n // /v1/graph/sync into a client that discards everything.\n const keyValid = typeof config.apiKey === 'string' && config.apiKey.startsWith('pk_');\n const zeroNetwork = config.localMode === true || !keyValid;\n if (!config.graph || zeroNetwork || typeof window === 'undefined') return client;\n\n if (gated) {\n // Consent may still be granted later: grantConsent() must re-init through\n // THIS entry so the post-consent client mounts the scanner — the lean init\n // it upgraded through before knows nothing about graph resources, so a\n // consent grant used to lose graph capture for the session. DNT-blocked\n // clients register nothing: consent cannot override a global opt-out.\n if (!dntBlocked) {\n _registerConsentUpgradeInit(config.apiKey, (c) => init(c as GraphSentientConfig));\n }\n return client;\n }\n\n // A prior graph mount for this key is now superseded — tear it down first so\n // its observer/timer don't leak alongside the new mount's.\n const prevTeardown = _graphTeardowns.get(config.apiKey);\n if (prevTeardown) {\n try {\n prevTeardown();\n } catch {\n /* teardown must never throw on re-init */\n }\n }\n\n const domScanner = createDOMScanner();\n const resolvedIngestUrl = config.ingestUrl ?? DEFAULT_INGEST_URL;\n const graphClient = createGraphClient({\n syncUrl: resolvedIngestUrl.replace(/\\/events\\/?$/, '/graph/sync'),\n apiKey: config.apiKey,\n projectId: config.apiKey,\n sessionId: readSntUid(config.apiKey),\n });\n\n // Persisted page-node state from a previous page load is restored by the graph\n // client's own constructor (it reads `_snt_graph_nodes` on creation). We no\n // longer re-read the key and call restore() here — that was a redundant second\n // load path that cleared and reloaded identical data.\n\n void domScanner.scan().then((result) => {\n for (const node of result.nodes) {\n graphClient.addPageNode({\n id: node.componentId,\n componentId: node.componentId,\n semanticType: node.semanticType,\n answers: config.captureDomText && node.headingText ? [node.headingText] : [],\n prominenceScore: node.prominenceScore,\n depth: node.depth,\n });\n }\n for (const edge of result.edges) {\n graphClient.addStructuralEdge(edge);\n }\n graphClient.syncOnce();\n });\n\n let syncDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n const debouncedSync = (): void => {\n if (syncDebounceTimer !== null) clearTimeout(syncDebounceTimer);\n syncDebounceTimer = setTimeout(() => {\n syncDebounceTimer = null;\n graphClient.syncOnce();\n }, 500);\n };\n\n domScanner.observe((event) => {\n for (const node of event.nodes) {\n graphClient.addPageNode({\n id: node.componentId,\n componentId: node.componentId,\n semanticType: node.semanticType,\n answers: config.captureDomText && node.headingText ? [node.headingText] : [],\n prominenceScore: node.prominenceScore,\n depth: node.depth,\n });\n }\n for (const edge of event.edges) {\n graphClient.addStructuralEdge(edge);\n }\n debouncedSync();\n });\n\n // Tears down only the graph resources; the caller pairs it with the lean\n // client's own dispose/destroy. Idempotent, and de-registers itself so a\n // later re-init or teardown can't run it twice on already-freed resources.\n const teardownGraph = (): void => {\n if (syncDebounceTimer !== null) {\n clearTimeout(syncDebounceTimer);\n syncDebounceTimer = null;\n }\n domScanner.destroy();\n graphClient.destroy();\n if (_graphTeardowns.get(config.apiKey) === teardownGraph) {\n _graphTeardowns.delete(config.apiKey);\n }\n };\n _graphTeardowns.set(config.apiKey, teardownGraph);\n\n return {\n ...client,\n getGraph: () => graphClient.snapshot(),\n dispose: () => {\n teardownGraph();\n client.dispose();\n },\n destroy: () => {\n teardownGraph();\n client.destroy();\n },\n };\n}\n","/** Shared RFC 4122 v4 UUID generator. */\n\n/**\n * Returns a random RFC 4122 v4 UUID.\n *\n * Resolution order, each falling through only on absence/throw:\n * 1. `crypto.randomUUID()` — requires a **secure context** (HTTPS/localhost),\n * so it is absent on plain `http://` (non-localhost) pages.\n * 2. `crypto.getRandomValues()` — a CSPRNG that, unlike `randomUUID`, *is*\n * available in insecure contexts, so ids stay cryptographically random\n * exactly where step 1 is unavailable.\n * 3. `Math.random()` — last resort only when no Web Crypto exists at all.\n * These ids are anonymous analytics session/event keys (not secrets or\n * authenticators), so a well-formed value the Postgres `uuid` column accepts\n * matters more than PRNG quality here — and the generator must never throw\n * (a missing/malformed id would break session creation and the host page).\n */\nexport function randomUuidV4(): string {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n } catch {\n /* fall through to getRandomValues */\n }\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const buf = new Uint8Array(16);\n crypto.getRandomValues(buf);\n buf[6] = (buf[6]! & 0x0f) | 0x40; // version 4\n buf[8] = (buf[8]! & 0x3f) | 0x80; // variant 10\n let out = '';\n for (let i = 0; i < 16; i++) {\n out += buf[i]!.toString(16).padStart(2, '0');\n if (i === 3 || i === 5 || i === 7 || i === 9) out += '-';\n }\n return out;\n }\n } catch {\n /* fall through to the Math.random() builder */\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n","/**\n * Per-project browser-storage namespace suffix, derived from the public apiKey.\n *\n * Multiple SentientUI projects (different `pk_` keys) can run on the same exact\n * origin. Storage keyed only by name (`_snt_uid`, `_snt_asgn_*`,\n * `_snt_graph_nodes`) would then collide across projects — the visitor id in\n * particular, whose session row is keyed globally server-side, would let one\n * project's traffic land on another's session. Suffixing every browser key with\n * the apiKey prefix isolates projects, matching the queue's existing\n * `_snt_retry_${apiKey.slice(0,12)}` convention.\n *\n * Returns `''` when no apiKey is available (local mode) so keys stay stable\n * there.\n */\nexport function storageSuffix(apiKey?: string): string {\n return apiKey ? `_${apiKey.slice(0, 12)}` : '';\n}\n\n/**\n * The bare, pre-namespacing session cookie name. Still read as a FALLBACK\n * everywhere the suffixed name is read, so visitors who got their identity\n * before per-project namespacing keep it instead of being minted a fresh one.\n */\nexport const LEGACY_SESSION_COOKIE_NAME = '_snt_uid';\n\n/**\n * The one place the session cookie's name is decided. The writer (session.ts)\n * and every reader (core/server.ts SSR helper, graph sync, react devtools) must\n * call THIS — when namespacing landed, the writer moved to the suffixed name\n * while three readers kept the bare `_snt_uid`, so every SSR request for a\n * returning visitor missed the cookie and minted a fresh orphan session (quota\n * inflation, broken sticky assignments and persona continuity), and graph sync\n * sent `sessionId: undefined`. Deriving both sides from one function makes that\n * drift impossible, and index.test.ts pins the reader against the writer.\n */\nexport function sessionCookieName(apiKey?: string): string {\n return `${LEGACY_SESSION_COOKIE_NAME}${storageSuffix(apiKey)}`;\n}\n","/** Manages anonymous session identity with cookie + localStorage layers. */\n\nimport { randomUuidV4 } from './uuid.js';\nimport { sessionCookieName, storageSuffix } from './storage-key.js';\n\nexport type SessionConfig = {\n cookieName?: string;\n cookieTTLDays?: number;\n /**\n * Public apiKey — namespaces the `_snt_uid` cookie + storage per project, so\n * two projects on the same exact origin don't share a visitor id (which would\n * cross-contaminate sessions server-side). Omit in local mode.\n */\n apiKey?: string;\n /**\n * Session ID generated during SSR (e.g. from `loadAdaptiveAssignments`).\n * Used as the fallback when no existing cookie or localStorage entry is found,\n * so the client adopts the same session the server used for variant assignment\n * on first visit rather than generating a new, orphaned ID.\n */\n ssrSessionId?: string;\n};\n\nexport type SessionManager = {\n getSessionId(): string | null;\n /** True when neither cookie nor localStorage could be written — id is in-memory only. */\n isEphemeral(): boolean;\n destroy(): void;\n};\n\nconst DEFAULT_COOKIE_NAME = '_snt_uid';\nconst DEFAULT_COOKIE_TTL_DAYS = 365;\nconst STORAGE_KEY = '_snt_uid';\n\n/**\n * Generates a unique session identifier. Always an RFC 4122 v4 UUID — the id is\n * stored server-side in a Postgres `uuid` column, so the insecure-context\n * fallback must not emit a malformed value (see `randomUuidV4`).\n */\nfunction generateSessionId(): string {\n return randomUuidV4();\n}\n\nfunction readCookie(name: string): string | null {\n try {\n const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));\n return match ? decodeURIComponent(match[1]) : null;\n } catch {\n return null;\n }\n}\n\nfunction writeCookie(name: string, value: string, maxAgeSeconds: number): void {\n try {\n document.cookie = `${name}=${encodeURIComponent(value)}; max-age=${maxAgeSeconds}; SameSite=strict; path=/`;\n } catch {\n /* ignore */\n }\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): boolean {\n try {\n localStorage.setItem(key, value);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction readSessionStorage(key: string): string | null {\n try {\n return sessionStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeSessionStorage(key: string, value: string): boolean {\n try {\n sessionStorage.setItem(key, value);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction removeSessionStorage(key: string): void {\n try {\n sessionStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n}\n\nfunction probeCookieWritable(name: string): boolean {\n try {\n document.cookie = `${name}_probe=1; max-age=1; SameSite=strict; path=/`;\n return document.cookie.indexOf(`${name}_probe=1`) !== -1;\n } catch {\n return false;\n }\n}\n\nfunction removeLocalStorage(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n}\n\nfunction clearCookie(name: string): void {\n try {\n document.cookie = `${name}=; max-age=0; SameSite=strict; path=/`;\n } catch {\n /* ignore */\n }\n}\n\nconst SSR_MANAGER: SessionManager = {\n getSessionId: () => null,\n isEphemeral: () => false,\n destroy: () => undefined,\n};\n\n/**\n * Initializes session management. Returns a no-op manager in SSR environments.\n */\nexport function initSession(config?: SessionConfig): SessionManager {\n if (typeof window === 'undefined') {\n return SSR_MANAGER;\n }\n\n // Namespace the visitor-id keys per project so multiple keys on one origin\n // don't share a `_snt_uid` (see storage-key.ts). An explicit cookieName still\n // wins for callers that manage their own naming. sessionCookieName is the\n // shared writer/reader name — the SSR reader and graph sync derive the same\n // string from it, so they can't drift back to the bare name.\n const suffix = storageSuffix(config?.apiKey);\n const cookieName = config?.cookieName ?? sessionCookieName(config?.apiKey);\n const storageKey = `${STORAGE_KEY}${suffix}`;\n // Forget-me tombstone for the legacy fallback below. destroy() deletes only\n // this project's SUFFIXED keys — deleting the bare pre-namespacing ones\n // would reset every other project on a shared origin — so without a marker\n // the next init()'s readLegacy() re-adopted the exact identity the visitor\n // had just asked to forget. The marker carries the same per-project suffix,\n // so other projects keep adopting the bare id exactly as before.\n const legacyTombstoneKey = `${STORAGE_KEY}_tomb${suffix}`;\n const cookieTTLDays = config?.cookieTTLDays ?? DEFAULT_COOKIE_TTL_DAYS;\n const maxAgeSeconds = cookieTTLDays * 24 * 60 * 60;\n\n // Treat an empty string from any layer as absent. A `_snt_uid=` cookie with no\n // value (or an empty localStorage/sessionStorage entry) decodes to '', which is\n // not nullish — a plain `??` chain would keep it and persist it for a year,\n // leaving getSessionId() falsy so every track/goal/upsert bails out and the\n // visitor is permanently muted with no way to regenerate. Coercing '' to null\n // lets the chain fall through to a real id (or a freshly generated one).\n const nonEmpty = (value: string | null | undefined): string | null =>\n value && value.length > 0 ? value : null;\n\n // Legacy fallback: visitors identified BEFORE per-project namespacing carry\n // their id under the bare `_snt_uid` names. Without this read, the rollout of\n // the suffixed names reset every existing visitor's identity — a fresh session\n // row per visitor, broken sticky assignments, persona continuity lost. The id\n // is adopted (re-written under the suffixed names below) but the legacy keys\n // are left in place: deleting them would reset the OTHER projects on a shared\n // origin that haven't migrated it yet. Skipped when the caller manages its own\n // cookieName — the bare `_snt_uid` was never theirs — and when there is no\n // suffix (local mode still uses the bare names directly).\n // The tombstone (written by destroy()) blocks this fallback for THIS project\n // only — a forgotten visitor must come back a stranger, not resurrected from\n // the bare keys that other projects still legitimately share.\n const readLegacy = (): string | null =>\n suffix && !config?.cookieName && readLocalStorage(legacyTombstoneKey) === null\n ? nonEmpty(readCookie(DEFAULT_COOKIE_NAME)) ??\n nonEmpty(readLocalStorage(STORAGE_KEY)) ??\n nonEmpty(readSessionStorage(STORAGE_KEY))\n : null;\n\n let sessionId: string | null =\n nonEmpty(readCookie(cookieName)) ??\n nonEmpty(readLocalStorage(storageKey)) ??\n nonEmpty(readSessionStorage(storageKey)) ??\n readLegacy() ??\n nonEmpty(config?.ssrSessionId) ??\n generateSessionId();\n\n writeCookie(cookieName, sessionId, maxAgeSeconds);\n const lsOk = writeLocalStorage(storageKey, sessionId);\n const cookieOk = probeCookieWritable(cookieName);\n // Always write sessionStorage when localStorage fails — it covers same-tab\n // navigation even in strict storage environments.\n const ssOk = !lsOk ? writeSessionStorage(storageKey, sessionId) : false;\n const ephemeral = !lsOk && !cookieOk && !ssOk;\n\n return {\n getSessionId: () => sessionId,\n isEphemeral: () => ephemeral,\n destroy: () => {\n sessionId = null;\n clearCookie(cookieName);\n removeLocalStorage(storageKey);\n removeSessionStorage(storageKey);\n // See legacyTombstoneKey: the bare `_snt_uid` keys stay for the other\n // projects on this origin, but this project's next init() must not\n // re-adopt them — that quietly undid the forget-me it just performed.\n if (suffix && !config?.cookieName) writeLocalStorage(legacyTombstoneKey, '1');\n },\n };\n}\n","/**\n * Reliability primitives shared by every outbound SDK transport.\n *\n * Extracted from queue.ts so the goal sender cannot drift from the event\n * queue on the three decisions that decide whether data survives: which\n * responses are worth retrying, how long to wait, and how the cross-reload\n * bucket is read and written. Behaviour is byte-identical to the versions\n * these replace.\n */\n\nconst MAX_BACKOFF_MS = 60_000;\n/** 2^6 s = 64 s, already past MAX_BACKOFF_MS — the cap that stops the shift overflowing. */\nconst BACKOFF_EXPONENT_CAP = 6;\n\n/** Delay before the next attempt after `consecutiveFailures` failed ones (1-based). */\nexport function backoffDelayMs(consecutiveFailures: number): number {\n return Math.min(MAX_BACKOFF_MS, 1000 * 2 ** Math.min(consecutiveFailures, BACKOFF_EXPONENT_CAP));\n}\n\n/** What one delivery attempt achieved. `dropped` is terminal but unsuccessful. */\nexport type DeliveryOutcome = 'delivered' | 'dropped' | 'retry';\n\n/**\n * Classifies a response into the three outcomes that matter to a transport.\n *\n * 2xx delivered. A 4xx other than 429 will never succeed however many times we\n * try it (bad key, rejected body, unknown session), so retrying it loops\n * forever and starves real data out of the bucket — drop it, but distinctly,\n * so callers can tell the developer. 429 and 5xx are transient.\n *\n * `ok` is consulted before `status` deliberately: it is the semantic check, and\n * it keeps stubs that set only `ok` (used throughout the SDK test suite)\n * behaving as they did before this was extracted. An unrecognizable response\n * with neither is treated as retryable, matching the original queue.\n */\nexport function classifyResponse(res: { ok?: boolean; status?: number }): DeliveryOutcome {\n if (res.ok === true) return 'delivered';\n const { status } = res;\n if (typeof status !== 'number') return 'retry';\n if (status >= 200 && status < 300) return 'delivered';\n if (status >= 400 && status < 500 && status !== 429) return 'dropped';\n return 'retry';\n}\n\n/** Anything a persisted bucket can hold: it needs a stable server-side dedupe id. */\nexport type Identified = { id: string };\n\n/**\n * Reads a persisted bucket AND clears it — the caller takes ownership of the\n * items and is responsible for re-persisting any that fail again. Returns the\n * newest `max` entries; a corrupt or absent bucket reads as empty.\n */\nexport function drainBucket<T extends Identified>(storageKey: string, max: number): T[] {\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return [];\n const parsed = JSON.parse(raw) as T[];\n if (!Array.isArray(parsed)) return [];\n localStorage.removeItem(storageKey);\n return parsed.slice(-max);\n } catch {\n return [];\n }\n}\n\n/**\n * Merges `items` into the persisted bucket, de-duped by id (last write wins)\n * before the size cap. The dedupe matters: a batch that 5xx's repeatedly\n * in-session hands the same ids back on every retry, and without it each retry\n * appends another copy and `slice(-max)` evicts other distinct failed items to\n * make room for the duplicates.\n */\nexport function writeBucket<T extends Identified>(items: T[], max: number, storageKey: string): void {\n try {\n const existing = (() => {\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return [] as T[];\n const parsed = JSON.parse(raw) as T[];\n return Array.isArray(parsed) ? parsed : ([] as T[]);\n } catch {\n return [] as T[];\n }\n })();\n const byId = new Map<string, T>();\n for (const e of existing) byId.set(e.id, e);\n for (const e of items) byId.set(e.id, e);\n localStorage.setItem(storageKey, JSON.stringify([...byId.values()].slice(-max)));\n } catch {\n /* storage unavailable — the in-memory retry path still applies */\n }\n}\n\n/**\n * Removes the given ids from the persisted bucket. Called once an item is\n * acknowledged so a transient failure that was written to localStorage isn't\n * replayed on the next page load after the in-session retry succeeded.\n */\nexport function purgeBucket(ids: string[], storageKey: string): void {\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return;\n const parsed = JSON.parse(raw) as Identified[];\n if (!Array.isArray(parsed)) return;\n const drop = new Set(ids);\n const remaining = parsed.filter((e) => !drop.has(e.id));\n if (remaining.length === parsed.length) return; // nothing to remove\n if (remaining.length === 0) localStorage.removeItem(storageKey);\n else localStorage.setItem(storageKey, JSON.stringify(remaining));\n } catch {\n /* ignore */\n }\n}\n","/** Batched event queue with reliable transport (fetch + keepalive, localStorage retry). */\n\nimport { backoffDelayMs, classifyResponse, drainBucket, purgeBucket, writeBucket } from './durable.js';\n\nexport type EventType =\n | 'variant_assigned'\n | 'goal_achieved'\n | 'scroll_depth'\n | 'dwell'\n | 'cursor_signal'\n | 'component_visible'\n | 'component_exited'\n | 'micro_signal'\n // A funnel DECLARATION, not telemetry: the server upserts the funnel entity\n // and never stores it in raw_events. Listed here so callers can express it\n // without casting through the track() signature.\n | 'funnel_declared'\n // One per page load and per SPA route change, so a visit's journey through the\n // site is reconstructable. Carries `path` and nothing else.\n | 'pageview';\n\nexport type SentientEvent = {\n id: string;\n sessionId: string;\n projectId: string;\n componentId: string;\n variantId?: string;\n eventType: EventType;\n goalType?: string;\n /**\n * Page path the event happened on, e.g. `/pricing`. Set automatically from\n * `location.pathname` — deliberately NOT `location.href`, so query strings\n * (which routinely carry emails, reset tokens and order ids) never leave the\n * browser. The server re-strips them anyway; this is the first of two gates.\n * Undefined outside a browser (SSR), where there is no page to name.\n */\n path?: string;\n payload: Record<string, unknown>;\n timestamp: number;\n timeInSession: number;\n};\n\nexport type QueueConfig = {\n ingestUrl: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBatchSize?: number;\n maxRetrySize?: number;\n};\n\nexport type EventQueue = {\n push(event: SentientEvent): void;\n flush(): void;\n destroy(): void;\n};\n\nconst MAX_SENT_IDS = 500;\n// 64 KB is the per-origin keepalive budget on every modern browser. We split unload\n// flushes into chunks below this to avoid the whole batch being dropped.\nconst KEEPALIVE_BUDGET_BYTES = 56 * 1024;\n\n/**\n * Persisted retry-bucket key, namespaced by apiKey prefix so multiple projects\n * on the same origin each get their own bucket. Exported so the client's\n * forget-me teardown can remove it.\n */\nexport function retryStorageKey(apiKey: string): string {\n return `_snt_retry_${apiKey.slice(0, 12)}`;\n}\n\nconst SSR_QUEUE: EventQueue = {\n push: () => undefined,\n flush: () => undefined,\n destroy: () => undefined,\n};\n\n/**\n * Creates a batched event queue with periodic and lifecycle-triggered flushes.\n */\nexport function createEventQueue(config: QueueConfig): EventQueue {\n if (typeof window === 'undefined') {\n return SSR_QUEUE;\n }\n\n const flushIntervalMs = config.flushIntervalMs ?? 5000;\n const maxBatchSize = config.maxBatchSize ?? 20;\n const maxRetrySize = config.maxRetrySize ?? 100;\n const ingestUrl = config.ingestUrl;\n const apiKey = config.apiKey;\n const RETRY_KEY = retryStorageKey(apiKey);\n\n const queue: SentientEvent[] = [];\n const sentIds = new Set<string>();\n const sentIdOrder: string[] = [];\n\n const markSent = (ids: string[]): void => {\n for (const id of ids) {\n queuedIds.delete(id); // free the in-flight slot\n if (sentIds.has(id)) continue;\n sentIds.add(id);\n sentIdOrder.push(id);\n }\n while (sentIdOrder.length > MAX_SENT_IDS) {\n const oldest = sentIdOrder.shift();\n if (oldest) sentIds.delete(oldest);\n }\n // A batch that previously 5xx'd was persisted to the cross-reload retry\n // backstop. Now that it's acknowledged (delivered, or 4xx-dropped as\n // unretryable), drop those ids from localStorage too — otherwise the next\n // page load would replay an already-handled event. `sentIds` is in-memory\n // only, so it can't suppress that cross-reload duplicate on its own.\n purgeBucket(ids, RETRY_KEY);\n };\n\n // Tracks IDs currently in `queue` or in flight (handed to transportBatch\n // but not yet confirmed). Entries are removed on markSent or when an event\n // is dropped, so the set stays bounded by in-flight + queued size.\n const queuedIds = new Set<string>();\n\n // Bound the in-memory queue at the same cap as the persisted retry bucket\n // (maxRetrySize). During a sustained outage flush() keeps re-enqueueing every\n // failed batch while the page keeps producing events, and only the\n // localStorage bucket was capped — so a multi-hour outage on a long-lived\n // SPA tab grew this array without limit. Drop-oldest, matching writeBucket's\n // slice(-max) shed policy: the newest events are the ones a recovering\n // server can still use.\n const capQueue = (): void => {\n while (queue.length > maxRetrySize) {\n const dropped = queue.shift();\n if (dropped) queuedIds.delete(dropped.id);\n }\n };\n\n const enqueue = (event: SentientEvent): void => {\n if (sentIds.has(event.id) || queuedIds.has(event.id)) return;\n queuedIds.add(event.id);\n queue.push(event);\n capQueue();\n };\n\n // A retryable failure hands the batch back to the in-memory queue so the\n // backoff-gated interval flush retries it in-session (localStorage is only the\n // cross-reload backstop). The ids are still tracked in queuedIds — they were\n // pulled from `queue` on flush but never markSent — so we re-add them WITHOUT\n // going through enqueue (which would skip them as already-queued) and without\n // deleting/re-adding queuedIds. Already-sent events are never re-queued, and a\n // single event stays a single copy: `queue` is fully drained each flush.\n const requeueFailed = (batch: SentientEvent[]): void => {\n for (const event of batch) {\n if (sentIds.has(event.id)) continue;\n queuedIds.add(event.id); // idempotent — keeps id-tracking consistent\n queue.push(event);\n }\n // Failed batches count against the same bound as fresh pushes — this path\n // is exactly the one that grew unbounded during an outage (see capQueue).\n capQueue();\n };\n\n const retryEvents = drainBucket<SentientEvent>(RETRY_KEY, maxRetrySize);\n for (const event of retryEvents) {\n enqueue(event);\n }\n\n // Pause flushing until backoff expires.\n let backoffUntil = 0;\n let consecutiveFailures = 0;\n\n // Authenticated transport. keepalive: true gives unload-survival; we still await\n // the response so 5xx/429 actually surface and we can retry.\n const transportBatch = (batch: SentientEvent[], salvage = true): void => {\n if (batch.length === 0) return;\n const body = JSON.stringify(batch);\n const ids = batch.map((e) => e.id);\n\n let pending: Promise<Response> | Response;\n try {\n pending = fetch(ingestUrl, {\n method: 'POST',\n keepalive: true,\n body,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n });\n } catch {\n // Synchronous throw (typically jsdom in tests, or extreme browser failure).\n writeBucket(batch, maxRetrySize, RETRY_KEY);\n requeueFailed(batch);\n consecutiveFailures++;\n backoffUntil = Date.now() + backoffDelayMs(consecutiveFailures);\n return;\n }\n\n // fetch can sometimes return a value directly (tests using stubGlobal). Handle both.\n const handleResponse = (res: Response): void => {\n if (classifyResponse(res) !== 'retry') {\n // An API older than migration 108 400s the whole batch over the unknown\n // 'pageview' type, and a 4xx is terminal — which silently dropped the\n // co-batched exposures and goals too. Re-send once without the\n // pageviews (valid on the old API); the pageviews themselves are\n // unsendable there and are dropped as delivered.\n if (!res.ok && salvage) {\n const rest = batch.filter((e) => e.eventType !== 'pageview');\n if (rest.length > 0 && rest.length < batch.length) {\n markSent(batch.filter((e) => e.eventType === 'pageview').map((e) => e.id));\n transportBatch(rest, false);\n return;\n }\n }\n // 2xx = success. 4xx (except 429) will never succeed — drop them rather than loop.\n markSent(ids);\n consecutiveFailures = 0;\n backoffUntil = 0;\n return;\n }\n // 5xx / 429 → retry with backoff.\n writeBucket(batch, maxRetrySize, RETRY_KEY);\n requeueFailed(batch);\n consecutiveFailures++;\n backoffUntil = Date.now() + backoffDelayMs(consecutiveFailures);\n };\n\n if (pending instanceof Promise) {\n pending.then(handleResponse).catch(() => {\n writeBucket(batch, maxRetrySize, RETRY_KEY);\n requeueFailed(batch);\n consecutiveFailures++;\n backoffUntil = Date.now() + backoffDelayMs(consecutiveFailures);\n });\n } else {\n handleResponse(pending);\n }\n };\n\n // Pack events into a batch that stays under the keepalive byte budget AND the\n // configured event-count cap. Returns the prefix of `pool` that fits.\n // Uses UTF-8 byte length (not char count) — emoji/CJK payloads can be 3–4×\n // larger as bytes and would otherwise blow the keepalive budget.\n const encoder = typeof TextEncoder !== 'undefined' ? new TextEncoder() : null;\n const byteLength = (s: string): number =>\n encoder ? encoder.encode(s).length : s.length;\n const packBatch = (pool: SentientEvent[]): SentientEvent[] => {\n const out: SentientEvent[] = [];\n let bytes = 2; // for the surrounding []\n for (const e of pool) {\n const size = byteLength(JSON.stringify(e)) + 1; // +1 for comma\n if (out.length > 0 && bytes + size > KEEPALIVE_BUDGET_BYTES) break;\n if (out.length >= maxBatchSize) break;\n out.push(e);\n bytes += size;\n }\n return out;\n };\n\n const flush = (): void => {\n try {\n if (Date.now() < backoffUntil) return;\n while (queue.length > 0) {\n // Re-check inside the loop: transportBatch can set backoffUntil\n // synchronously (a sync throw or a same-tick Response) and re-enqueue the\n // failed batch, so a mid-drain backoff must stop further chunks this tick\n // (otherwise the just-re-enqueued batch would immediately retry-loop).\n if (Date.now() < backoffUntil) break;\n const pending = queue.filter((e) => !sentIds.has(e.id));\n queue.length = 0;\n if (pending.length === 0) break;\n const batch = packBatch(pending);\n if (batch.length === 0) break;\n // Put anything we didn't pack back on the queue for the next tick.\n if (batch.length < pending.length) {\n queue.push(...pending.slice(batch.length));\n }\n transportBatch(batch);\n }\n } catch {\n /* never throw */\n }\n };\n\n let flushTimerActive = true;\n let intervalId: ReturnType<typeof setInterval> | null = null;\n\n intervalId = setInterval(() => {\n if (!flushTimerActive) return;\n flush();\n }, flushIntervalMs);\n\n const onVisibilityChange = (): void => {\n if (document.visibilityState === 'hidden') {\n flush();\n }\n };\n\n // `pagehide` (not `beforeunload`): a `beforeunload` listener can disqualify a\n // page from the back/forward cache. `pagehide` fires on real unloads AND on\n // bfcache eviction, and `visibilitychange:hidden` already covers most leaves,\n // so flush-on-leave stays covered without inhibiting bfcache.\n const onPageHide = (): void => {\n flush();\n };\n\n document.addEventListener('visibilitychange', onVisibilityChange);\n window.addEventListener('pagehide', onPageHide);\n\n return {\n push(event: SentientEvent): void {\n enqueue(event);\n if (queue.length >= maxBatchSize) {\n flush();\n }\n },\n flush,\n destroy(): void {\n flushTimerActive = false;\n if (intervalId !== null) {\n clearInterval(intervalId);\n intervalId = null;\n }\n document.removeEventListener('visibilitychange', onVisibilityChange);\n window.removeEventListener('pagehide', onPageHide);\n flush();\n },\n };\n}\n","/**\n * Durable transport for `POST /v1/goals`.\n *\n * A conversion is the single most valuable event the SDK emits, and it used to\n * be the least reliable one: `goal()` fired a bare `fetch(...).catch(() => {})`,\n * which only observes network-layer failures. A 429 (the per-IP limit is 100\n * req/min, and shared egress — corporate NAT, carriers, storefront proxies —\n * hits it routinely), a 5xx, or a 400 all RESOLVE, so the catch never ran and\n * the response was discarded unread. The conversion was gone with no retry and\n * nothing surfaced to the developer.\n *\n * This gives goals the same guarantees the event queue has always had: retry\n * with backoff in-session, a localStorage bucket that survives reload, and\n * dedupe by id. Retry is safe because `goalId` is a client-generated UUID that\n * the server dedupes on (`ON CONFLICT DO NOTHING`) — the safety was already\n * there, it just wasn't used.\n *\n * Unlike events, `/v1/goals` takes ONE goal per request, so this is a serial\n * sender rather than a batcher, and a flush sends at most `maxPerFlush` so a\n * backlog drains at a pace the rate limiter tolerates instead of re-triggering\n * the 429 that created it.\n */\n\nimport { backoffDelayMs, classifyResponse, drainBucket, purgeBucket, writeBucket } from './durable.js';\n\n/** One queued conversion. `id` is the goalId — the server's dedupe key. */\nexport type PendingGoal = {\n id: string;\n /** Pre-serialized request body, so a persisted goal replays byte-identically. */\n body: string;\n};\n\nexport type GoalQueueConfig = {\n /** Absolute URL of the goals endpoint. */\n url: string;\n apiKey: string;\n headers: Record<string, string>;\n flushIntervalMs?: number;\n maxRetrySize?: number;\n maxPerFlush?: number;\n /** Called when a goal is permanently dropped (non-retryable status), so the\n * client can warn in debug mode. Never called for retryable failures. */\n onDrop?: (goal: PendingGoal, status: number) => void;\n};\n\nexport type GoalQueue = {\n /** Sends immediately; on a retryable failure the goal is queued and retried. */\n send(goal: PendingGoal): void;\n flush(): void;\n destroy(): void;\n};\n\nconst MAX_SENT_IDS = 200;\n\n/**\n * Persisted goal-retry bucket key, namespaced by apiKey prefix so multiple\n * projects on one origin keep separate buckets. Exported so the client's\n * forget-me teardown can remove it.\n */\nexport function goalRetryStorageKey(apiKey: string): string {\n return `_snt_goal_retry_${apiKey.slice(0, 12)}`;\n}\n\nconst SSR_GOAL_QUEUE: GoalQueue = {\n send: () => undefined,\n flush: () => undefined,\n destroy: () => undefined,\n};\n\nexport function createGoalQueue(config: GoalQueueConfig): GoalQueue {\n if (typeof window === 'undefined') return SSR_GOAL_QUEUE;\n\n const flushIntervalMs = config.flushIntervalMs ?? 5000;\n const maxRetrySize = config.maxRetrySize ?? 100;\n const maxPerFlush = config.maxPerFlush ?? 5;\n const RETRY_KEY = goalRetryStorageKey(config.apiKey);\n\n const pending: PendingGoal[] = [];\n const pendingIds = new Set<string>();\n const sentIds = new Set<string>();\n const sentIdOrder: string[] = [];\n\n let backoffUntil = 0;\n let consecutiveFailures = 0;\n let disposed = false;\n\n const markSent = (goal: PendingGoal): void => {\n pendingIds.delete(goal.id);\n if (!sentIds.has(goal.id)) {\n sentIds.add(goal.id);\n sentIdOrder.push(goal.id);\n }\n while (sentIdOrder.length > MAX_SENT_IDS) {\n const oldest = sentIdOrder.shift();\n if (oldest) sentIds.delete(oldest);\n }\n // Acknowledged (delivered, or dropped as unretryable) — clear it from the\n // cross-reload bucket so the next page load doesn't replay it.\n purgeBucket([goal.id], RETRY_KEY);\n };\n\n const markFailed = (goal: PendingGoal): void => {\n writeBucket([goal], maxRetrySize, RETRY_KEY);\n if (!sentIds.has(goal.id) && !pendingIds.has(goal.id)) {\n pendingIds.add(goal.id);\n pending.push(goal);\n }\n // One failed ROUND counts once, however many goals were in flight. flush()\n // launches up to maxPerFlush transports synchronously, so counting per goal\n // turned a single bad tick into consecutiveFailures=5 and a 32s backoff\n // where the first failure warrants 2s. queue.ts counts per batch; match it.\n if (Date.now() >= backoffUntil) {\n consecutiveFailures++;\n backoffUntil = Date.now() + backoffDelayMs(consecutiveFailures);\n }\n };\n\n const transport = (goal: PendingGoal): void => {\n let res: Promise<Response> | Response;\n try {\n res = fetch(config.url, {\n method: 'POST',\n keepalive: true,\n body: goal.body,\n headers: config.headers,\n });\n } catch {\n // Synchronous throw (jsdom in tests, or extreme browser failure).\n markFailed(goal);\n return;\n }\n\n const handle = (r: Response): void => {\n const outcome = classifyResponse(r);\n if (outcome === 'retry') {\n markFailed(goal);\n return;\n }\n // Terminal either way, but a drop is a wiring bug the developer can fix —\n // a 400 \"session not found\", a 401 from a misconfigured origin allowlist —\n // and used to be swallowed entirely. Say so instead of failing silently.\n if (outcome === 'dropped') config.onDrop?.(goal, r.status);\n markSent(goal);\n consecutiveFailures = 0;\n backoffUntil = 0;\n };\n\n // fetch can return a plain value under test stubs. Handle both.\n if (res instanceof Promise) res.then(handle).catch(() => markFailed(goal));\n else handle(res);\n };\n\n const flush = (): void => {\n try {\n if (disposed) return;\n if (Date.now() < backoffUntil) return;\n let sentThisTick = 0;\n while (pending.length > 0 && sentThisTick < maxPerFlush) {\n if (Date.now() < backoffUntil) break; // a same-tick failure re-armed backoff\n const goal = pending.shift()!;\n pendingIds.delete(goal.id);\n if (sentIds.has(goal.id)) continue;\n sentThisTick++;\n transport(goal);\n }\n } catch {\n /* never throw from a lifecycle handler */\n }\n };\n\n // Replay anything a previous page load failed to deliver. Left for the first\n // interval tick rather than sent now, so a page that reloads under an ongoing\n // outage doesn't stampede the endpoint during init.\n const restored = drainBucket<PendingGoal>(RETRY_KEY, maxRetrySize);\n for (const goal of restored) {\n if (!pendingIds.has(goal.id)) {\n pendingIds.add(goal.id);\n pending.push(goal);\n }\n }\n // drainBucket CLEARS storage as it reads, but only maxPerFlush goals leave per\n // tick — so a 40-goal backlog moved to memory and the visitor navigating two\n // seconds later lost the 35 that hadn't been sent yet. Put them straight back;\n // markSent purges each id individually once it is actually acknowledged.\n if (restored.length > 0) writeBucket(restored, maxRetrySize, RETRY_KEY);\n\n const intervalId = setInterval(flush, flushIntervalMs);\n const onVisibilityChange = (): void => {\n if (document.visibilityState === 'hidden') flush();\n };\n const onPageHide = (): void => flush();\n document.addEventListener('visibilitychange', onVisibilityChange);\n window.addEventListener('pagehide', onPageHide);\n\n return {\n send(goal: PendingGoal): void {\n if (disposed) return;\n if (sentIds.has(goal.id) || pendingIds.has(goal.id)) return;\n // A conversion goes out now — it is often the last thing that happens\n // before a redirect to a thank-you page. Only a failure makes it queued.\n if (Date.now() < backoffUntil) {\n // Persist BEFORE parking it in memory. A goal only ever reached the\n // cross-reload bucket via markFailed — i.e. only after a failed attempt\n // — so a conversion queued during someone else's backoff lived in memory\n // alone, and flush() early-returns while backoff is armed, so pagehide\n // could not rescue it either. A purchase firing 300ms after a rate-limited\n // add_to_cart was lost on the checkout redirect: exactly the outage this\n // queue exists to survive.\n writeBucket([goal], maxRetrySize, RETRY_KEY);\n pendingIds.add(goal.id);\n pending.push(goal);\n return;\n }\n transport(goal);\n },\n flush,\n destroy(): void {\n clearInterval(intervalId);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n window.removeEventListener('pagehide', onPageHide);\n flush();\n disposed = true;\n },\n };\n}\n","/** Synchronous variant assignment cache (memory + localStorage). */\n\nimport { storageSuffix } from './storage-key.js';\n\nexport type Assignment = {\n variantId: string;\n assignedAt: number;\n segment: string;\n confidence: number;\n content?: string;\n /**\n * Per-entry expiry (ms from `assignedAt`). When present it overrides the\n * cache-wide default TTL — lets the server-provided `assignmentTtlMs` govern\n * how long this specific assignment stays valid. Persists across reloads.\n */\n ttlMs?: number;\n};\n\nexport type AssignmentCache = {\n get(componentId: string, segment: string): Assignment | null;\n set(componentId: string, segment: string, assignment: Assignment): void;\n /** Drops every entry, memory and localStorage — the forget-me path. */\n clear(): void;\n};\n\nconst DEFAULT_TTL_MS = 30 * 60 * 1000;\n\nfunction cacheKey(componentId: string, segment: string): string {\n // URI-encode each part (same scheme as storageKey) and join with a literal\n // ':'. Since encodeURIComponent escapes ':', the separator is unambiguous —\n // otherwise a segment like `device:source` could collide two distinct\n // (componentId, segment) pairs onto the same raw `${id}:${segment}` string.\n return `${encodeURIComponent(componentId)}:${encodeURIComponent(segment)}`;\n}\n\n/**\n * Creates an assignment cache with optional TTL (default 30 minutes). Pass the\n * project's `apiKey` so the localStorage keys are namespaced per project —\n * otherwise two projects on the same origin share cached assignments.\n */\nexport function createAssignmentCache(ttlMs: number = DEFAULT_TTL_MS, apiKey?: string): AssignmentCache {\n const memory = new Map<string, Assignment>();\n\n // Per-project localStorage prefix. No apiKey (local mode) → the legacy\n // `_snt_asgn_` prefix so single-project behavior is unchanged.\n const keyPrefix = `_snt_asgn${storageSuffix(apiKey)}_`;\n\n const storageKey = (componentId: string, segment: string): string =>\n `${keyPrefix}${encodeURIComponent(componentId)}:${encodeURIComponent(segment)}`;\n\n const parseStorageKey = (key: string): { componentId: string; segment: string } | null => {\n const suffix = key.slice(keyPrefix.length);\n const sep = suffix.indexOf(':');\n if (sep < 0) return null;\n try {\n return {\n componentId: decodeURIComponent(suffix.slice(0, sep)),\n segment: decodeURIComponent(suffix.slice(sep + 1)),\n };\n } catch {\n return null;\n }\n };\n\n const listStorageKeys = (): string[] => {\n try {\n const keys: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(keyPrefix)) {\n keys.push(key);\n }\n }\n return keys;\n } catch {\n return [];\n }\n };\n\n // Honor a per-entry TTL (server-provided assignmentTtlMs) when present; fall\n // back to the cache-wide default otherwise.\n const isExpired = (assignment: Assignment): boolean =>\n assignment.assignedAt + (assignment.ttlMs && assignment.ttlMs > 0 ? assignment.ttlMs : ttlMs) < Date.now();\n\n const restoreFromStorage = (): void => {\n for (const key of listStorageKeys()) {\n try {\n const raw = localStorage.getItem(key);\n if (!raw) continue;\n const assignment = JSON.parse(raw) as Assignment;\n if (isExpired(assignment)) {\n localStorage.removeItem(key);\n continue;\n }\n const parsed = parseStorageKey(key);\n if (!parsed) continue;\n memory.set(cacheKey(parsed.componentId, parsed.segment), assignment);\n } catch {\n /* ignore corrupt entries */\n }\n }\n };\n\n if (typeof window !== 'undefined') {\n restoreFromStorage();\n }\n\n return {\n get(componentId: string, segment: string): Assignment | null {\n const entry = memory.get(cacheKey(componentId, segment));\n if (!entry) return null;\n if (isExpired(entry)) {\n memory.delete(cacheKey(componentId, segment));\n return null;\n }\n return entry;\n },\n\n set(componentId: string, segment: string, assignment: Assignment): void {\n const key = cacheKey(componentId, segment);\n memory.set(key, assignment);\n try {\n localStorage.setItem(storageKey(componentId, segment), JSON.stringify(assignment));\n } catch {\n /* ignore */\n }\n },\n\n clear(): void {\n memory.clear();\n for (const storageK of listStorageKeys()) {\n try {\n localStorage.removeItem(storageK);\n } catch {\n /* ignore */\n }\n }\n },\n };\n}\n","/** Session metadata helpers (browser + Node). No DOM APIs. */\n\n/**\n * Known AI-agent / crawler user-agent tokens. Matched case-insensitively as\n * substrings. This is maintained data — bot lists move monthly. Used both to\n * flag automation on sessions (agentic browsers that leak a token) and by the\n * server middleware / agent-feed route to identify crawler HTTP reads.\n */\nexport const agentUaList: readonly string[] = [\n 'GPTBot',\n 'ChatGPT-User',\n 'OAI-SearchBot',\n 'ClaudeBot',\n 'Claude-User',\n 'Claude-SearchBot',\n 'PerplexityBot',\n 'Perplexity-User',\n 'Google-Extended',\n 'Applebot-Extended',\n 'Meta-ExternalAgent',\n 'Bytespider',\n 'CCBot',\n 'Amazonbot',\n 'cohere-ai',\n 'Diffbot',\n];\n\n/** True when the user-agent contains a known AI-agent / crawler token. */\nexport function uaTokenMatch(userAgent: string): boolean {\n return matchedAgentToken(userAgent) !== null;\n}\n\n/** The first known agent token found in the user-agent, or null. */\nexport function matchedAgentToken(userAgent: string): string | null {\n if (!userAgent) return null;\n const s = userAgent.toLowerCase();\n return agentUaList.find((token) => s.includes(token.toLowerCase())) ?? null;\n}\n\n/**\n * Purpose category for an agent fetch, inferred from its published user-agent:\n * - `user` — a person asked an assistant to read the page, live (…-User UAs)\n * - `search` — indexing for an AI answer engine (…-SearchBot / SearchBot)\n * - `training` — model-training crawl (GPTBot, ClaudeBot, CCBot, …)\n * - `other` — not clearly AI, or an unknown/new token\n */\nexport type AgentIntent = 'user' | 'search' | 'training' | 'other';\n\n/** Intent per agent token. Maintained beside `agentUaList` — when you add or\n * rename a token above, set its intent here (a unit test enforces coverage). */\nexport const AGENT_INTENTS: Record<string, AgentIntent> = {\n 'GPTBot': 'training',\n 'ChatGPT-User': 'user',\n 'OAI-SearchBot': 'search',\n 'ClaudeBot': 'training',\n 'Claude-User': 'user',\n 'Claude-SearchBot': 'search',\n 'PerplexityBot': 'search',\n 'Perplexity-User': 'user',\n 'Google-Extended': 'training',\n 'Applebot-Extended': 'training',\n 'Meta-ExternalAgent': 'training',\n 'Bytespider': 'training',\n 'CCBot': 'training',\n 'Amazonbot': 'other',\n 'cohere-ai': 'training',\n 'Diffbot': 'other',\n};\n\n/** Intent for a matched bot token (case-insensitive); `other` for null/unknown. */\nexport function agentIntent(botName: string | null): AgentIntent {\n if (!botName) return 'other';\n const hit = agentUaList.find((t) => t.toLowerCase() === botName.toLowerCase());\n return (hit && AGENT_INTENTS[hit]) || 'other';\n}\n\n/** `agentUaList` grouped by intent — the \"which crawlers do you classify?\" reference. */\nexport function classifiedAgents(): Record<AgentIntent, string[]> {\n const out: Record<AgentIntent, string[]> = { user: [], search: [], training: [], other: [] };\n for (const t of agentUaList) out[AGENT_INTENTS[t] ?? 'other'].push(t);\n return out;\n}\n\nexport function detectDeviceClass(userAgent: string): string {\n const s = userAgent.toLowerCase();\n if (/ipad|tablet|playbook|kindle|silk/.test(s)) return 'tablet';\n if (/mobi|iphone|ipod|android.*mobile|phone/.test(s)) return 'mobile';\n return 'desktop';\n}\n\nexport function detectTrafficSource(referrer: string, appOrigin?: string): string {\n if (!referrer) return 'direct';\n try {\n const refUrl = new URL(referrer);\n if (appOrigin) {\n try {\n if (new URL(appOrigin).host === refUrl.host) return 'direct';\n } catch {\n /* ignore invalid appOrigin */\n }\n }\n const host = refUrl.hostname.toLowerCase();\n if (/(^|\\.)(google|bing|duckduckgo|yahoo)\\./.test(host)) return 'search';\n // Anchor to the registrable domain (exact host or a subdomain of it) so\n // hosts like `x.company.com`, `t.company.io` or `linkedinsights.com` are not\n // misclassified as social by an unbounded substring match.\n if (/(^|\\.)(twitter\\.com|x\\.com|facebook\\.com|linkedin\\.com|reddit\\.com|t\\.co)$/.test(host)) return 'social';\n return 'referral';\n } catch {\n return 'direct';\n }\n}\n\nexport function referrerDomainFromReferer(referrer: string): string | null {\n if (!referrer) return null;\n try {\n return new URL(referrer).hostname;\n } catch {\n return null;\n }\n}\n\n/**\n * Ad-platform click-ID query params worth keeping. An ALLOWLIST, unlike the\n * `utm_` prefix match: unknown query keys here are unbounded-cardinality junk\n * (session tokens, cache busters) that would bloat the sessions table, so only\n * the IDs the major ad platforms actually append survive.\n *\n * gclid / gbraid / wbraid — Google Ads auto-tagging (search, YouTube, Display;\n * gbraid/wbraid are the iOS-14 privacy variants)\n * fbclid — Meta (Facebook + Instagram). Appended to EVERY outbound Meta\n * click, paid and organic alike — it identifies the platform, never spend.\n * ttclid — TikTok Ads\n * msclkid — Microsoft Ads (Bing)\n * twclid — X/Twitter Ads\n * li_fat_id — LinkedIn Ads\n */\nexport const CLICK_ID_KEYS: readonly string[] = [\n 'gclid',\n 'gbraid',\n 'wbraid',\n 'fbclid',\n 'ttclid',\n 'msclkid',\n 'twclid',\n 'li_fat_id',\n];\n\n/**\n * Splits a URL query into the attribution params the session upsert carries:\n * every `utm_`-prefixed key, plus allowlisted ad click IDs (CLICK_ID_KEYS).\n * Accepts a raw search string (\"?a=b\" or \"a=b\"), a URLSearchParams, or a\n * Next.js `searchParams` object (whose values may be string arrays — the\n * first occurrence wins, matching URLSearchParams iteration order).\n * Node-safe: no DOM APIs.\n */\nexport function extractTrackedParams(\n search: string | URLSearchParams | Record<string, string | string[] | undefined>,\n): { utmParams: Record<string, string>; clickIds: Record<string, string> } {\n const utmParams: Record<string, string> = {};\n const clickIds: Record<string, string> = {};\n try {\n const entries: Iterable<[string, string]> =\n typeof search === 'string' || search instanceof URLSearchParams\n ? new URLSearchParams(search)\n : Object.entries(search).flatMap(([k, v]): Array<[string, string]> => {\n const first = Array.isArray(v) ? v[0] : v;\n return first === undefined ? [] : [[k, first]];\n });\n for (const [k, v] of entries) {\n // First occurrence wins for duplicates — a repeated gclid in a mangled\n // URL must not let the later value silently replace the real one.\n if (k.startsWith('utm_')) {\n if (!(k in utmParams)) utmParams[k] = v;\n } else if (CLICK_ID_KEYS.includes(k)) {\n if (!(k in clickIds)) clickIds[k] = v;\n }\n }\n } catch {\n /* malformed input → empty attribution, never a throw at init() */\n }\n return { utmParams, clickIds };\n}\n\nexport function detectTimeOfDay(d: Date): string {\n const h = d.getHours();\n if (h < 6) return 'night';\n if (h < 12) return 'morning';\n if (h < 18) return 'afternoon';\n return 'evening';\n}\n\nexport type SessionUpsertPayload = {\n sessionId: string;\n ephemeral: boolean;\n utmParams: Record<string, string>;\n /** Allowlisted ad-platform click IDs from the landing URL (CLICK_ID_KEYS). */\n clickIds: Record<string, string>;\n deviceClass: string;\n trafficSource: string;\n referrerDomain: string | null;\n timeOfDay: string;\n dayOfWeek: string;\n /**\n * True when this session is likely driven by automation — either\n * `navigator.webdriver` was set, or the user-agent carried a known agent\n * token. Probabilistic: a flag for metrics + bandit exclusion, never a gate.\n */\n automation: boolean;\n};\n\n/** Bandit segment key: `<device_class>:<traffic_source>`. */\nexport function deriveSessionSegment(opts?: {\n userAgent?: string;\n referer?: string;\n appOrigin?: string;\n}): string {\n const body = buildSessionUpsertPayload('__segment__', opts);\n return `${body.deviceClass}:${body.trafficSource}`;\n}\n\n/**\n * Builds a session upsert body aligned with the browser SDK so SSR assign uses\n * the same segment key (`device:source`) as the client after hydration.\n */\nexport function buildSessionUpsertPayload(\n sessionId: string,\n opts?: {\n userAgent?: string;\n referer?: string;\n appOrigin?: string;\n utmParams?: Record<string, string>;\n clickIds?: Record<string, string>;\n now?: Date;\n /** `navigator.webdriver` value from the browser, when available. */\n webdriver?: boolean;\n },\n): SessionUpsertPayload {\n const ua = opts?.userAgent?.trim() ?? '';\n const referer = opts?.referer?.trim() ?? '';\n const now = opts?.now ?? new Date();\n return {\n sessionId,\n ephemeral: false,\n utmParams: opts?.utmParams ?? {},\n clickIds: opts?.clickIds ?? {},\n deviceClass: ua ? detectDeviceClass(ua) : 'desktop',\n trafficSource: referer\n ? detectTrafficSource(referer, opts?.appOrigin)\n : 'direct',\n referrerDomain: referrerDomainFromReferer(referer),\n timeOfDay: detectTimeOfDay(now),\n dayOfWeek: ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][now.getDay()] ?? 'sun',\n automation: opts?.webdriver === true || uaTokenMatch(ua),\n };\n}\n","/**\n * Slot declaration helpers shared by the browser client (decide) and the\n * server preload path. Pure wrappers over @sentientui/policy.\n */\nimport {\n canonicalArm,\n slotBaselineArm,\n slotResultFor,\n type SlotDecl,\n type SlotResult,\n} from '@sentientui/policy';\n\n/** SDK-facing slot declaration. `dims` accepts readonly arrays (`as const`). */\nexport type SlotDeclInput = {\n id: string;\n arms?: string[];\n dims?: Record<string, readonly string[]>;\n baseline?: string | Record<string, string>;\n};\n\nexport type { SlotResult };\n\n/**\n * Whitelists the wire fields of a slot declaration. Anything an SDK layer\n * attached (goal configs, refs, …) is stripped so it never reaches the zod\n * schema on the API. Also normalizes readonly arrays to mutable ones.\n */\nexport function toWireSlot(d: SlotDeclInput): SlotDecl {\n return {\n id: d.id,\n ...(d.arms ? { arms: [...d.arms] } : {}),\n ...(d.dims\n ? {\n dims: Object.fromEntries(\n Object.entries(d.dims).map(([dim, values]) => [dim, [...values]]),\n ),\n }\n : {}),\n ...(d.baseline !== undefined ? { baseline: d.baseline } : {}),\n };\n}\n\n/** The declared (or default first-declared) baseline result for a slot. */\nexport function baselineResultFor(d: SlotDeclInput): SlotResult {\n const decl = toWireSlot(d);\n return slotResultFor(decl, slotBaselineArm(decl));\n}\n\n/** Baseline results for a whole declaration list, keyed by slot id. */\nexport function baselineSlots(decls: SlotDeclInput[]): Record<string, SlotResult> {\n const out: Record<string, SlotResult> = {};\n for (const d of decls) out[d.id] = baselineResultFor(d);\n return out;\n}\n\n/**\n * Canonical arm string of a slot result: dims results encode as sorted\n * `dim=value` pairs joined with '|'; arms results are the arm id verbatim.\n */\nexport function armOfResult(result: SlotResult): string {\n return typeof result === 'string' ? result : canonicalArm(result);\n}\n","/**\n * Decision snapshot: the SPA / return-visit pre-paint source. Written after\n * every successful decide; read by the inline pre-paint script (before any\n * framework code runs) and by init() to seed slot/persona state.\n */\nimport type { SlotResult } from './slots.js';\nimport type { BlockNode, SitePalette } from './blocks.js';\n\nexport const SNAPSHOT_STORAGE_KEY_PREFIX = '_snt_snap:';\n\n/** Versioned compound locator: resolve id → dataAttr → selector, then verify\n * against fingerprint. Lets a slot survive DOM/markup drift. */\nexport type CompoundLocator = {\n v?: number;\n id?: string;\n dataAttr?: { name: string; value: string };\n selector?: string;\n urlMatch?: string;\n fingerprint?: { tag?: string; text?: string };\n semanticId?: string;\n};\n\n/** Bounded, declarative operations a registry arm may apply to its element.\n * The style set is a fixed whitelist (validated server-side); no arbitrary CSS,\n * HTML, or JS ever. `text` is applied via textContent; https-only URLs.\n * moveBefore/moveAfter (exactly one) reposition the element relative to a\n * uniquely-resolving sibling anchor — post-decide only, never pre-paint. */\nexport type SlotOps = {\n text?: string;\n style?: Record<string, string>;\n hidden?: boolean;\n href?: string;\n imageSrc?: string;\n imageAlt?: string;\n moveBefore?: CompoundLocator;\n moveAfter?: CompoundLocator;\n};\n\n/** Registry-mode apply info per slot: where to apply and what to set. Stored so\n * a returning visitor's pre-paint can reapply it. `target` is the Phase-2 bare\n * selector; `locator` (Phase 3) is the compound locator, preferred when present. */\nexport type SlotConfigEntry = {\n kind: 'tokens' | 'arms';\n target?: string;\n locator?: CompoundLocator;\n content?: string;\n ops?: SlotOps;\n /** Composition Blocks per arm — ALL arms, not just the served one, because\n * Option-B rendering pre-paints every arm hidden and reveals the served one\n * (spec §6). Holdout sessions receive the baseline arm's tree only, so the\n * control group's DOM stays meaningful. Absent for non-composition slots. */\n blocks?: Record<string, BlockNode>;\n};\n\nexport type DecisionSnapshot = {\n v: 1;\n persona: string;\n band: 'low' | 'medium' | 'high';\n slots: Record<string, SlotResult>;\n layoutOrder: string[] | null;\n savedAt: number;\n // Additive (kept at v:1 so existing snapshots stay valid — bumping the version\n // would flush every returning visitor's snapshot and flash the baseline once).\n // Present only for registry-mode (no-code) installs.\n slotConfig?: Record<string, SlotConfigEntry>;\n /** Derived site palette for Composition Block rendering — cached so the\n * pre-paint render already looks native (a palette that pops in post-decide\n * would be its own flash). */\n palette?: SitePalette;\n};\n\nconst BANDS = ['low', 'medium', 'high'];\n\n/** Returns null on missing, corrupt, or wrong-version data — never throws. */\nexport function readSnapshot(apiKey: string): DecisionSnapshot | null {\n try {\n const raw = localStorage.getItem(SNAPSHOT_STORAGE_KEY_PREFIX + apiKey);\n if (!raw) return null;\n const p = JSON.parse(raw) as Partial<DecisionSnapshot> | null;\n if (\n !p ||\n typeof p !== 'object' ||\n p.v !== 1 ||\n typeof p.persona !== 'string' ||\n typeof p.band !== 'string' ||\n !BANDS.includes(p.band) ||\n typeof p.slots !== 'object' ||\n p.slots === null ||\n Array.isArray(p.slots) ||\n !(p.layoutOrder === null || Array.isArray(p.layoutOrder)) ||\n typeof p.savedAt !== 'number' ||\n // slotConfig is optional; when present it must be a plain object.\n !(p.slotConfig === undefined || (typeof p.slotConfig === 'object' && p.slotConfig !== null && !Array.isArray(p.slotConfig)))\n ) {\n return null;\n }\n return p as DecisionSnapshot;\n } catch {\n return null;\n }\n}\n\n/** Best-effort persist — storage failures are swallowed. */\nexport function writeSnapshot(apiKey: string, snap: DecisionSnapshot): void {\n try {\n localStorage.setItem(SNAPSHOT_STORAGE_KEY_PREFIX + apiKey, JSON.stringify(snap));\n } catch {\n /* private mode / quota — the snapshot is an optimization, never a requirement */\n }\n}\n\n/**\n * Inline pre-paint script (Rung 1a): reads the snapshot and sets\n * `data-sentient-persona` / `data-sentient-confidence` on <html> before\n * first paint. Single-writer: it never overwrites attributes already set.\n *\n * Safety properties (pinned by tests):\n * - apiKey goes through JSON.stringify, then '<' is escaped to <, so a\n * hostile key can neither break the JS string nor terminate the <script>.\n * - Built by string concatenation and contains no backticks, so the output\n * survives being embedded in template-literal-based renderers.\n */\nexport function renderPrePaintScript(apiKey: string): string {\n const key = JSON.stringify(SNAPSHOT_STORAGE_KEY_PREFIX + apiKey).replace(/</g, '\\\\u003c');\n return (\n '(function(){try{' +\n 'var r=localStorage.getItem(' + key + ');if(!r)return;' +\n 'var s=JSON.parse(r);' +\n 'if(!s||s.v!==1||typeof s.persona!==\"string\"||typeof s.band!==\"string\")return;' +\n 'var d=document.documentElement;' +\n 'if(d.hasAttribute(\"data-sentient-persona\"))return;' +\n 'd.setAttribute(\"data-sentient-persona\",s.persona);' +\n 'd.setAttribute(\"data-sentient-confidence\",s.band);' +\n '}catch(e){}})();'\n );\n}\n","import { initSession, type SessionConfig, type SessionManager } from './session';\nimport {\n createEventQueue,\n retryStorageKey,\n type EventQueue,\n type EventType,\n type QueueConfig,\n type SentientEvent,\n} from './queue';\nimport {\n createGoalQueue,\n goalRetryStorageKey,\n type GoalQueue,\n} from './goal-queue';\nimport {\n createAssignmentCache,\n type Assignment,\n} from './cache';\nimport type {\n GraphConfig,\n GraphSnapshot,\n} from './graph';\nimport {\n detectDeviceClass,\n detectTrafficSource,\n detectTimeOfDay,\n extractTrackedParams,\n referrerDomainFromReferer,\n uaTokenMatch,\n} from './session-meta.js';\nimport {\n toWireSlot,\n baselineResultFor,\n armOfResult,\n type SlotDeclInput,\n type SlotResult,\n} from './slots.js';\nimport { readSnapshot, writeSnapshot, SNAPSHOT_STORAGE_KEY_PREFIX, type SlotConfigEntry, type CompoundLocator } from './snapshot.js';\nimport { confidenceBand } from '@sentientui/policy';\nimport { createLocalModeClient } from './local-mode.js';\nimport { randomUuidV4 } from './uuid.js';\nimport { backoffDelayMs, classifyResponse } from './durable.js';\n\nexport { PROD_KEYLESS_ERROR, LOCAL_MODE_BANNER } from './local-mode.js';\n\n// The one source of truth for the session cookie's name — every out-of-package\n// reader (react devtools, integrator SSR code) must derive the name from this\n// instead of hard-coding `_snt_uid`, which is only the pre-namespacing fallback.\nexport { sessionCookieName, LEGACY_SESSION_COOKIE_NAME } from './storage-key.js';\n\nexport {\n detectDeviceClass,\n detectTrafficSource,\n detectTimeOfDay,\n deriveSessionSegment,\n extractTrackedParams,\n CLICK_ID_KEYS,\n referrerDomainFromReferer,\n uaTokenMatch,\n matchedAgentToken,\n agentUaList,\n agentIntent,\n classifiedAgents,\n AGENT_INTENTS,\n} from './session-meta.js';\nexport type { AgentIntent } from './session-meta.js';\n\nconst DEFAULT_INGEST_URL = 'https://api.sentient-ui.com/v1/events';\n\n// Keyed by apiKey so multiple init() calls (HMR, multi-project) don't collide.\nconst _clients = new Map<string, {\n config: SentientConfig;\n upgrade: ((c: SentientClient) => void) | null;\n // Why grantConsent() cannot upgrade this entry (keyless/local mode, invalid\n // key). grantConsent() warns with this instead of silently no-oping — a CMP\n // callback wired to it otherwise LOOKED like it worked while nothing ever\n // started tracking. Absent for entries where the silent no-op IS the\n // documented contract (DNT-blocked, already upgraded).\n upgradeBlockedReason?: string;\n // Set by an alternate entry point (the /graph entry) whose gated init\n // deferred extra resources: grantConsent() must re-run THAT entry's init —\n // upgrading through the lean init() produced a post-consent client that\n // never mounted the DOM scanner, silently losing graph capture.\n reinit?: (c: SentientConfig) => SentientClient;\n // Teardown for the live client bound to this key (stops its queue interval +\n // unload listeners). Present only for the full tracking client; the no-op /\n // pre-consent / local entries have nothing to tear down. Called before a\n // re-init for the same key replaces it, so timers/listeners can't leak.\n dispose?: () => void;\n}>();\nlet _lastApiKey: string | null = null;\n\n/**\n * @internal Wires an alternate entry point's init as grantConsent()'s upgrade\n * path for `apiKey`. The /graph entry calls this when its init is gated on\n * consent: without it, grantConsent() upgraded through the LEAN init, so a\n * graph-configured page granted consent but never mounted the scanner (the\n * graph resources exist only in the /graph entry). No-op unless the entry is\n * actually upgradeable — DNT-blocked and local entries register no hook.\n */\nexport function _registerConsentUpgradeInit(\n apiKey: string,\n reinit: (config: SentientConfig) => SentientClient,\n): void {\n const entry = _clients.get(apiKey);\n if (entry && entry.upgrade) entry.reinit = reinit;\n}\n\nexport type SentientConfig = {\n apiKey: string;\n context: 'landing' | 'ecommerce' | 'saas' | 'marketplace';\n /** @internal — not exposed to users; defaults to the hosted SentientUI API. */\n ingestUrl?: string;\n debug?: boolean;\n /**\n * Pre-seeded assignments from `preloadAssignments()` (SSR).\n * Seeds the local cache so `assign()` returns without a network call for\n * listed code variants, guaranteeing server and client render the same\n * variant on first paint. Managed-text components (assign with no\n * variantIds) still fetch once when the seed carries no content.\n */\n initialAssignments?: Record<string, string>;\n /**\n * Segment used for SSR preload (`device:source`). When set with `initialAssignments`,\n * seeds the assignment cache under this key so hydration matches the server bandit row.\n */\n sessionSegment?: string;\n /**\n * Consent gate. When `false`, returns a no-op client and performs no tracking.\n * Defaults to `true`. Re-call `init()` (via `AdaptiveProvider` consent prop) when\n * the user grants or revokes consent mid-session.\n */\n consent?: boolean;\n /**\n * Behavior before consent is granted. `'statistical_winner'` fetches the\n * best-performing variant via `GET /v1/winner` — no session or tracking data\n * is stored. `'control'` (default) shows `variantIds[0]` with no API call.\n * Applies when tracking is gated off — either `consent: false` or an active\n * Do Not Track signal.\n */\n preConsentBehavior?: 'statistical_winner' | 'control';\n /**\n * Whether to honor the browser's Do Not Track (DNT) signal. Defaults to `true`.\n * When `true` and the visitor has DNT enabled, the SDK sets no cookies and\n * sends no tracking data — behaving exactly as `consent: false` (still serving\n * the read-only `preConsentBehavior` winner if configured), and `grantConsent()`\n * will not upgrade it. Set `false` to make your own consent gate authoritative.\n */\n respectDoNotTrack?: boolean;\n userId?: string;\n /**\n * Declared persona — the role your app already knows for this visitor\n * (e.g. 'admin', 'evaluator'). Must be a key in the project's persona\n * vocabulary (dashboard → Settings → Personas); unrecognized values are\n * ignored server-side and surfaced in the dashboard so you can add them.\n * Declared personas are served at full confidence, overriding the inferred\n * one. Keep it a low-cardinality role label — never a user id or email.\n */\n persona?: string;\n /**\n * Session ID generated server-side (from `loadAdaptiveAssignments` / `loadAdaptiveDecision`).\n * When provided, the client adopts this ID on first visit instead of generating a new one,\n * ensuring events and goals are attributed to the same session the server used for assignment.\n */\n ssrSessionId?: string;\n /**\n * ISO 3166-1 alpha-2 country code for the visitor. When provided (e.g. from\n * the `CF-IPCountry` header in a Next.js server component), it is included in\n * the session upsert so country-based segmentation works without client-side\n * geo lookup.\n */\n country?: string;\n /**\n * Pre-seeded slot results from `preloadDecisions()` / `loadAdaptiveDecision()`\n * (SSR). Seeds the local slot state so `getSlotResult()` agrees with the\n * server-rendered markup on first paint.\n */\n initialSlots?: Record<string, SlotResult>;\n /**\n * Persona decided during SSR. Takes priority over the html-attribute\n * adoption and the local snapshot.\n */\n initialPersona?: { persona: string; confidence: number };\n /**\n * Keyless local mode. 'auto' (default) simulates decisions on-device when no\n * valid API key is configured — but only in development builds (the\n * `development` export condition); production bundles physically exclude the\n * engine. `true` forces the local engine regardless of key (escape hatch);\n * `false` restores the silent keyless no-op.\n */\n localMode?: 'auto' | boolean;\n /**\n * Which integration surface is driving this client, and its released build\n * version — `{ name: 'react', version: '0.27.0' }`. Set by the wrapper\n * package (@sentientui/react, @sentientui/snippet), never by application\n * code: core is a dependency of both, so its own version says nothing about\n * what the customer installed.\n *\n * Rides the session upsert (the one call EVERY integration makes, unlike\n * decide, which only the slot paths use) so the dashboard can tell a project\n * its SDK is behind. Additive and best-effort: older API deployments ignore\n * the fields, and omitting it changes nothing.\n */\n sdk?: { name: string; version: string };\n};\n\nexport type AssignResult = { variantId: string; assignmentTtlMs: number; content?: string };\n\nexport type { SlotDeclInput, SlotResult };\nexport { armOfResult, baselineResultFor, baselineSlots, toWireSlot } from './slots.js';\n\nexport {\n SNAPSHOT_STORAGE_KEY_PREFIX,\n readSnapshot,\n writeSnapshot,\n renderPrePaintScript,\n} from './snapshot.js';\nexport type { DecisionSnapshot, SlotConfigEntry, SlotOps, CompoundLocator } from './snapshot.js';\nexport * from './blocks.js';\n\n/** An editor-defined goal delivered with a registry-mode decision, for the\n * snippet to install delegated listeners from. */\nexport type GoalDefinition = {\n goalId: string;\n event: 'click' | 'form_submit' | 'url_reached' | 'scroll_depth';\n locator?: CompoundLocator;\n urlPattern?: string;\n slotId?: string;\n /** scroll_depth only: fraction of the page (0–1] that counts as read. */\n threshold?: number;\n};\n\n/** One served section-classification row (registry mode): where it is on the\n * page (url match + compound locator) and its semantic type. The snippet\n * resolves the locator to build capture's `typeOf` hook. */\nexport type SectionMapEntry = {\n urlMatch: string;\n locator: CompoundLocator;\n type: string;\n};\n\nexport type DecideOutcome = {\n layoutOrder: string[] | null;\n assignments: Record<string, string>;\n slots: Record<string, SlotResult>;\n persona: string;\n confidence: number;\n // Registry mode only: where/what to apply for server-defined slots.\n slotConfig?: Record<string, SlotConfigEntry>;\n // Registry mode only: editor-defined goals to wire up.\n goals?: GoalDefinition[];\n // Registry mode only: served section-classification map the snippet turns\n // into capture's `typeOf` hook.\n sectionMap?: SectionMapEntry[];\n // Registry mode only: derived site palette for Composition Block rendering.\n palette?: import('./blocks.js').SitePalette;\n};\n\nexport type DecideInput = {\n sections?: string[];\n components?: Array<{ id: string; variantIds?: string[] }>;\n slots?: SlotDeclInput[];\n // 'registry' → serve the project's published slot_definitions in addition to\n // any declared slots (registry wins on id collision). Default 'request'.\n slotsFrom?: 'request' | 'registry';\n /**\n * Caller's build version (e.g. the snippet's `__SNIPPET_VERSION__`), sent\n * as `v` on the wire. Additive/best-effort: the server persists it for\n * version-skew reporting (see apps/api decide route) and ignores it\n * entirely on older deployments. Omit if the caller has no version to report.\n */\n v?: string;\n};\n\nexport type WeightEntry = { variantId: string; pulls: number; avgReward: number | null };\nexport type ComponentWeightEntry = { componentId: string; updatedAt: number; variants: WeightEntry[] };\n\n/** Options accepted by goal() (and inherited by componentGoal / the React\n * hooks / the snippet) — one shape everywhere (spec §5). */\nexport type GoalOptions = {\n /** Revenue of this conversion, in the project currency. */\n value?: number;\n /** ISO-4217 code, only when it differs from the project currency. */\n currency?: string;\n /** Merchant order/transaction id — dedupes retries, enables refunds later. */\n externalId?: string;\n /** Extra fields merged into the event payload / goal metadata. */\n metadata?: Record<string, unknown>;\n /** Advanced: partial-credit weight in [0,1] (composite steps). */\n weight?: number;\n /** Advanced: funnel step index (0-based). */\n stepIndex?: number;\n};\n\n/** goal()'s second arg is the options object iff it carries a reserved key;\n * anything else keeps the legacy bare-metadata interpretation. Reserved keys\n * inside legacy metadata were inert on the server, so reinterpretation is the\n * upgrade the sender wanted (spec §5). */\nfunction isGoalOptions(v: Record<string, unknown>): boolean {\n return 'value' in v || 'currency' in v || 'externalId' in v || 'metadata' in v || 'weight' in v || 'stepIndex' in v;\n}\n\nexport type ComponentGoalOptions = GoalOptions & {\n /** Reward credited to the served variant (0–1). Defaults to 1. */\n reward?: number;\n};\n\nexport type SentientClient = {\n track(\n event: Omit<SentientEvent, 'id' | 'sessionId' | 'timestamp' | 'timeInSession'>,\n ): void;\n goal(name: string, options?: GoalOptions): void;\n /** @deprecated positional form — prefer goal(name, options). */\n goal(name: string, metadata?: Record<string, unknown>, weight?: number, stepIndex?: number): void;\n /**\n * Records a conversion attributed to the variant currently served for\n * `componentId`, so it feeds the per-variant CVR funnel. Resolves the served\n * variant from the local assignment cache — no need to pass variantId or\n * projectId. No-ops if the component has not been assigned yet (render its\n * `<Adaptive>`/call `assign()` first). Prefer this over bare `goal()` for\n * variant experiments; `goal()` is session-level only (no component attribution).\n */\n componentGoal(componentId: string, goalType: string, opts?: ComponentGoalOptions): void;\n identify(userId: string): void;\n getAssignment(componentId: string, segment: string): Assignment | null;\n /** Server-side variant assignment. Caches the result locally per (component, segment). */\n assign(componentId: string, variantIds?: string[], agentData?: unknown, agentDataByVariant?: Record<string, unknown>): Promise<AssignResult | null>;\n /**\n * Single-roundtrip decision for layout sections, component variants, and\n * adaptive slots. Awaits the session upsert (like `assign`) so the server\n * never decides for a session row that doesn't exist yet. A response\n * without a `slots` field means the server predates slots — every declared\n * slot resolves to its baseline and no retry is made.\n */\n decide(input: DecideInput): Promise<DecideOutcome | null>;\n /** Slot result served this session (decide result, SSR seed, snapshot, or failure baseline). Null when unknown. */\n getSlotResult(slotId: string): SlotResult | null;\n /** Current persona estimate. Band is always `confidenceBand(confidence)`. Null when nothing is known yet. */\n getPersona(): { persona: string; confidence: number; band: 'low' | 'medium' | 'high' } | null;\n /** Fetches current bandit weights for all components in this project. Used by the provider to keep live-weight polling fresh. */\n fetchWeights(): Promise<ComponentWeightEntry[]>;\n getGraph(): GraphSnapshot;\n /**\n * Routine teardown: stops timers/listeners and flushes pending events, but\n * KEEPS the visitor identity, decision snapshot, and retry bucket. Use for\n * component unmount / re-init (framework providers call this on cleanup).\n */\n dispose(): void;\n /**\n * Consent-revocation / forget-me teardown: everything `dispose()` does,\n * plus deletion of the visitor identity (`_snt_uid`), the decision\n * snapshot, and the persisted retry bucket. The next visit starts as a\n * brand-new visitor.\n */\n destroy(): void;\n /** True when this client is the keyless local-mode client (dev only). */\n readonly isLocal?: boolean;\n};\n\n// SSR preload helpers moved to the `@sentientui/core/server` entry in 0.6.0 so\n// ~200 lines of Node-only fetch logic stop shipping in the browser bundle.\n\nexport { attachMicroSignalDetectors } from './micro-signals.js';\nexport type { MicroSignalEmitter, MicroSignalType } from './micro-signals.js';\n\nexport type {\n SessionConfig,\n SessionManager,\n EventType,\n SentientEvent,\n QueueConfig,\n Assignment,\n GraphSnapshot,\n GraphConfig,\n};\n\n/** Attempts after the first for the session upsert — see upsertSession. */\nconst SESSION_UPSERT_RETRIES = 3;\n\nfunction generateEventId(): string {\n return randomUuidV4();\n}\n\n/**\n * The page an event happened on.\n *\n * `location.pathname` ONLY — never `href` or `search`. Query strings on real\n * sites carry emails, reset tokens, order ids and session ids, and none of that\n * should leave the browser for analytics. The server strips them again\n * (domain/page-path.ts) because a hand-rolled integration can post whatever it\n * likes; this is the first of the two gates, and the one that means the data\n * never travels at all.\n *\n * Undefined outside a browser: during SSR there is no page to name, and an\n * invented one would be wrong for every reader.\n */\nfunction currentPath(): string | undefined {\n if (typeof window === 'undefined') return undefined;\n return window.location?.pathname || undefined;\n}\n\n/**\n * Decides whether a goal() call belongs to a user action already recorded.\n *\n * This used to be a clock: a latch cleared on the next macrotask, so anything\n * inside that window counted as \"the same action\". A clock cannot tell the two\n * cases apart, and it got the expensive one wrong. The window can be JUMPED —\n * a macrotask scheduled to close it runs after any timer already armed, which\n * every real page and every sibling test in a worker has — so a genuinely\n * separate conversion landed in a window that should have shut, and was\n * swallowed rather than queued: gone, with no retry able to rescue it because\n * nothing was ever handed to the queue.\n *\n * So ask the question the clock was approximating. Two nested components\n * reacting to one click are, exactly, two listeners in one event DISPATCH, and\n * the platform already hands us that identity: `window.event` is the same Event\n * object for every listener of one dispatch, a different object for the next\n * click, and undefined outside dispatch entirely. Keying on the event object\n * makes \"one action\" a fact rather than a deadline — no timer to lose a race\n * with, no fake-timer or background-throttle hazard, and no way for a second\n * click to be mistaken for the first however long the page stalls between them.\n *\n * Goals fired outside any dispatch (an effect on mount, a page goal) have no\n * event to key on, and fall back to one SYNCHRONOUS flush — closed on a\n * microtask. That is exact too, and for the same reason the old macrotask\n * window was not: microtasks always drain before the next macrotask, so no\n * pending timer can jump this window. A user cannot perform two actions inside\n * one synchronous block, so anything sharing it is a double-fire, not a repeat.\n *\n * A microtask is deliberately NOT used for the dispatch case: the HTML spec\n * runs a microtask checkpoint between listeners once the JS stack empties, so\n * it would split one click into two and double-count the revenue this collapse\n * exists to protect. That is why the dispatch case keys on the event instead,\n * and why a host without `window.event` keeps the old macrotask window — too\n * loose, but erring toward the old behaviour rather than toward double-counting.\n */\nfunction createActionLatch(): { firedBefore(actionKey: string, valueKey: string | null): boolean } {\n // Per window, per action key: the set of value identities already recorded.\n // An entry with an empty set means a VALUELESS fire was recorded. The split\n // exists because value cannot simply live inside one flat key: an inner\n // component declaring `value: 50` nested in an outer wrapper with the same\n // goal but NO value produced two distinct keys — two rows for one click.\n // Within a window, a valueless fire is absorbed by ANY record of the same\n // action (the valued row already carries the order, and a valueless\n // duplicate would inflate Hits), while a valued fire is collapsed only by an\n // IDENTICAL (value, currency) record — $50 and $70 stay two orders\n // (CONTRACTS §1). A valued fire landing AFTER a valueless one still records:\n // the valueless row has already been handed to the queue (often already on\n // the wire) and cannot be retracted, and losing the money would be the worse\n // error — server-side credit clamps the extra valueless hit at min(1, MAX\n // weight), so the cost is one inflated Hit, not corrupted revenue. Listener\n // order makes the benign ordering the common one: the inner (valued)\n // component's listener runs before the wrapper's in a bubbling dispatch.\n const byEvent = new WeakMap<object, Map<string, Set<string>>>();\n const byFlush = new Map<string, Set<string>>();\n let flushOpen = false;\n\n const alreadyRecorded = (map: Map<string, Set<string>>, actionKey: string, valueKey: string | null): boolean => {\n const values = map.get(actionKey);\n if (valueKey === null) {\n if (values) return true;\n map.set(actionKey, new Set());\n return false;\n }\n if (!values) {\n map.set(actionKey, new Set([valueKey]));\n return false;\n }\n if (values.has(valueKey)) return true;\n values.add(valueKey);\n return false;\n };\n\n // Standardised as Window.event and present in every current browser, but a\n // capability check keeps exotic hosts on the path they have always had.\n const hasWindowEvent = typeof window !== 'undefined' && 'event' in window;\n\n const currentEvent = (): object | undefined => {\n if (!hasWindowEvent) return undefined;\n const ev = (window as unknown as { event?: unknown }).event;\n // Must be a real same-realm Event, not merely an object. Window.event is\n // [Replaceable]: a classic script doing `event = {...}` at top level (an\n // implicit or sloppy global on plenty of host pages) permanently shadows\n // the accessor with a data property. Accepting any object then returned\n // that SAME object forever — its WeakMap entry never died, so every later\n // conversion looked like a re-fire of the first action and ALL repeat\n // conversions were silently dropped for the session. `instanceof Event`\n // cannot be true for such a literal; a cross-realm Event (iframe) fails it\n // too and merely falls back to the flush window, which is safe.\n // (typeof guard: a host with `window` but no Event constructor must fall\n // back, not throw a ReferenceError from inside goal().)\n return typeof Event === 'function' && ev instanceof Event ? ev : undefined;\n };\n\n const closeFlushWindow = (): void => {\n if (hasWindowEvent) {\n // A promise microtask, not queueMicrotask: fake-timer setups can replace\n // queueMicrotask, and this window closing is what keeps repeat\n // conversions from being swallowed.\n void Promise.resolve().then(() => {\n flushOpen = false;\n byFlush.clear();\n });\n return;\n }\n let done = false;\n const clear = (): void => {\n if (done) return;\n done = true;\n flushOpen = false;\n byFlush.clear();\n };\n setTimeout(clear, 0);\n if (typeof MessageChannel === 'function') {\n const ch = new MessageChannel();\n ch.port1.onmessage = () => {\n ch.port1.close();\n ch.port2.close();\n clear();\n };\n ch.port2.postMessage(0);\n }\n };\n\n return {\n firedBefore(actionKey: string, valueKey: string | null): boolean {\n const ev = currentEvent();\n if (ev) {\n let keys = byEvent.get(ev);\n if (!keys) {\n keys = new Map<string, Set<string>>();\n // Keyed weakly: the Map dies with the Event object, so a long session\n // of clicks accumulates nothing.\n byEvent.set(ev, keys);\n }\n return alreadyRecorded(keys, actionKey, valueKey);\n }\n const collapsed = alreadyRecorded(byFlush, actionKey, valueKey);\n if (!collapsed && !flushOpen) {\n flushOpen = true;\n closeFlushWindow();\n }\n return collapsed;\n },\n };\n}\n\n/**\n * Emits one `pageview` per page load and per SPA route change.\n *\n * Patches pushState/replaceState rather than polling: a route change is a\n * discrete event, and polling would either miss fast back-to-back navigations or\n * burn a timer on every page for the entire session. popstate covers\n * back/forward, which history patching does not see.\n *\n * Deduplicates on pathname, because frameworks routinely replaceState several\n * times for one navigation (query/hash updates, scroll restoration) and each\n * would otherwise look like another page in the visitor's journey.\n */\n/** Pages already recorded this page lifetime, keyed project:path. dispose()\n * flushes, so without this a consent-change or StrictMode re-init delivered a\n * second landing pageview for a page the previous client already sent. */\nconst emittedPages = new Set<string>();\n\nfunction startPageviewTracking(\n client: SentientClient,\n projectId: string,\n // Called with the page key after emitting; the caller marks emittedPages only\n // once the event reached a LIVE queue. Marking at emit time instead turned\n // StrictMode's mount→dispose→mount into zero delivered landings: the first\n // mount's event dies in the destroyed queue, and the mark suppressed the\n // second mount's — the one that actually ships.\n markDelivered: (key: string) => void,\n): () => void {\n const h = typeof window === 'undefined' ? null : window.history;\n if (!h) return () => undefined;\n\n // The stop handle exists because init() runs again on every consent change,\n // StrictMode double-invoke and HMR: without it each init wrapped history\n // again and the old wrapper kept emitting through the DISPOSED client, so one\n // navigation produced one pageview per init that ever happened.\n let stopped = false;\n let last: string | undefined;\n const emit = (): void => {\n if (stopped) return;\n const path = currentPath();\n if (!path || path === last) return;\n last = path;\n // '__page__' is a sentinel componentId: the ingest schema requires one, and\n // every component reader filters on variant_id IS NOT NULL or a specific\n // event_type, so it never surfaces as a component.\n client.track({ projectId, componentId: '__page__', eventType: 'pageview', payload: {} });\n markDelivered(`${projectId}:${path}`);\n };\n\n const installed: Array<['pushState' | 'replaceState', History['pushState'], History['pushState']]> = [];\n for (const name of ['pushState', 'replaceState'] as const) {\n const orig = h[name];\n const wrapper = function (this: History, ...a: unknown[]) {\n const r = (orig as (...x: unknown[]) => unknown).apply(this, a);\n emit();\n return r;\n } as History[typeof name];\n h[name] = wrapper;\n installed.push([name, orig, wrapper]);\n }\n window.addEventListener('popstate', emit);\n\n // The landing page itself — once per (project, path) per page lifetime. When\n // it was already sent, `last` is still primed so the next real navigation\n // emits exactly once.\n const landing = currentPath();\n if (landing && emittedPages.has(`${projectId}:${landing}`)) last = landing;\n else emit();\n\n return () => {\n if (stopped) return;\n stopped = true;\n window.removeEventListener('popstate', emit);\n for (const [name, orig, wrapper] of installed) {\n // Restore only while ours is still on top; if something wrapped over it,\n // unhooking would sever their chain — the stopped flag already makes ours\n // a passthrough.\n if (h[name] === wrapper) h[name] = orig;\n }\n };\n}\n\nconst SSR_CLIENT: SentientClient = {\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n getAssignment: () => null,\n assign: () => Promise.resolve(null),\n decide: () => Promise.resolve(null),\n getSlotResult: () => null,\n getPersona: () => null,\n fetchWeights: () => Promise.resolve([]),\n getGraph: () => ({ pageNodes: [], capturedAt: 0 }),\n dispose: () => undefined,\n destroy: () => undefined,\n};\n\nfunction readTrackedParams(): {\n utmParams: Record<string, string>;\n clickIds: Record<string, string>;\n} {\n try {\n return extractTrackedParams(window.location.search);\n } catch {\n return { utmParams: {}, clickIds: {} };\n }\n}\n\nfunction deriveBaseUrl(ingestUrl: string): string {\n return ingestUrl.replace(/\\/events\\/?$/, '');\n}\n\n/**\n * Detects whether the visitor has signalled a tracking opt-out. Honors Global\n * Privacy Control (`navigator.globalPrivacyControl`) — the legally-enforceable\n * CCPA/CPRA signal — as well as Do Not Track (`navigator.doNotTrack`, the legacy\n * `window.doNotTrack` on older Firefox, and `navigator.msDoNotTrack` on old\n * IE/Edge). GPC is a boolean; DNT is opt-out only when explicitly `'1'`/`'yes'`.\n */\nexport function isDoNotTrackEnabled(): boolean {\n // GPC is a boolean flag, checked separately from the DNT string signals.\n if (\n typeof navigator !== 'undefined' &&\n (navigator as unknown as { globalPrivacyControl?: boolean }).globalPrivacyControl === true\n ) {\n return true;\n }\n const signals = [\n typeof navigator !== 'undefined' ? navigator.doNotTrack : undefined,\n typeof window !== 'undefined'\n ? (window as unknown as { doNotTrack?: string | null }).doNotTrack\n : undefined,\n typeof navigator !== 'undefined'\n ? (navigator as unknown as { msDoNotTrack?: string | null }).msDoNotTrack\n : undefined,\n ];\n return signals.some((v) => v === '1' || v === 'yes');\n}\n\n/**\n * Upgrades a pre-consent client (any client created with `consent: false`, in\n * either `preConsentBehavior` mode) to a fully-tracking client, in place and\n * with no page reload. Call this from your consent management platform callback.\n * For React apps, prefer updating the `consent` prop on `<AdaptiveProvider>`.\n * Pass `apiKey` to target a specific project; omit to upgrade the most-recently-initialized client.\n */\nexport function grantConsent(apiKey?: string): void {\n if (typeof window === 'undefined') return;\n\n const key = apiKey ?? _lastApiKey;\n if (!key) {\n console.warn('[sentient] grantConsent() called before init()');\n return;\n }\n\n const entry = _clients.get(key);\n if (!entry) {\n console.warn('[sentient] grantConsent() called before init()');\n return;\n }\n\n const { config, upgrade, reinit } = entry;\n if (!upgrade) {\n // Keyless/local and invalid-key clients register no upgrade hook — there\n // is no hosted client to swap in. This used to return SILENTLY, so a CMP\n // callback wired to grantConsent() looked like it worked while nothing\n // ever started tracking. DNT-blocked and already-upgraded entries carry no\n // reason and stay quiet: for them the no-op is the documented contract.\n if (entry.upgradeBlockedReason) console.warn(entry.upgradeBlockedReason);\n return;\n }\n\n // Honor an active Do Not Track signal — consent cannot override a global opt-out.\n if (config.respectDoNotTrack !== false && isDoNotTrackEnabled()) return;\n\n // Upgrade through the entry point that ran the gated init when one\n // registered itself (see _registerConsentUpgradeInit) — the lean init knows\n // nothing about that entry's extra resources (the /graph scanner).\n const fullClient = (reinit ?? init)({ ...config, consent: true });\n upgrade(fullClient);\n // init() just registered the full client (with its dispose) under `key`.\n // Preserve that dispose so a later re-init/teardown can still tear it down —\n // we only need to clear the upgrade hook now that consent is granted.\n const disposed = _clients.get(key)?.dispose;\n _clients.set(key, { config: { ...config, consent: true }, upgrade: null, dispose: disposed });\n}\n\nfunction createPreConsentProxy(config: SentientConfig): { proxy: SentientClient; setInner: (c: SentientClient) => void } {\n // 'control' (the default) must reach the network zero times before consent.\n // The proxy still exists so grantConsent() has something to upgrade in place\n // — without it, a site wanting no pre-consent traffic could only start\n // tracking by reloading the page.\n const servesWinner = config.preConsentBehavior === 'statistical_winner';\n const baseUrl = deriveBaseUrl(config.ingestUrl ?? DEFAULT_INGEST_URL);\n const authHeaders = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n } as const;\n\n // Pre-consent winners are read-only and render-driven: every mounted\n // component re-calls assign() on every render, and without a result cache or\n // in-flight coalescing each call refired GET /v1/winner — the full client\n // has inflightAssigns for exactly this. Successful winners are cached for\n // the pre-consent phase (the winner is stable, and a flip mid-visit would be\n // a variant flash anyway); failure fallbacks are NOT cached, so a recovering\n // server gets asked again. Both maps die with the proxy on upgrade.\n const winnerCache = new Map<string, AssignResult>();\n const inflightWinners = new Map<string, Promise<AssignResult | null>>();\n\n let inner: SentientClient = {\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n getAssignment: () => null,\n fetchWeights: () => Promise.resolve([]),\n assign(componentId, variantIds, _agentData?) {\n // Control mode: no request. Callers fall back to variantIds[0] through\n // ssrFallback, exactly as they did against the old no-op client.\n if (!servesWinner) return Promise.resolve(null);\n const cached = winnerCache.get(componentId);\n if (cached) return Promise.resolve(cached);\n return coalesce(inflightWinners, componentId, async (): Promise<AssignResult | null> => {\n try {\n const params = new URLSearchParams({ componentId });\n for (const v of variantIds ?? []) params.append('variantIds[]', v);\n const res = await fetch(`${baseUrl}/winner?${params.toString()}`, {\n headers: authHeaders,\n });\n if (!res.ok) return variantIds?.[0] ? { variantId: variantIds[0], assignmentTtlMs: 0 } : null;\n const body = (await res.json()) as { variantId: string };\n const result: AssignResult = { variantId: body.variantId, assignmentTtlMs: 0 };\n winnerCache.set(componentId, result);\n return result;\n } catch {\n return variantIds?.[0] ? { variantId: variantIds[0], assignmentTtlMs: 0 } : null;\n }\n });\n },\n decide: () => Promise.resolve(null),\n getSlotResult: () => null,\n getPersona: () => null,\n getGraph: () => ({ pageNodes: [], capturedAt: 0 }),\n dispose: () => undefined,\n destroy: () => undefined,\n };\n\n const proxy: SentientClient = {\n track: (e) => inner.track(e),\n // Cast: a single arrow can't structurally satisfy the overloaded member;\n // the passthrough forwards both call shapes untouched.\n goal: ((n: string, m?: Record<string, unknown>, w?: number, s?: number) => inner.goal(n, m, w, s)) as SentientClient['goal'],\n componentGoal: (c, g, o) => inner.componentGoal(c, g, o),\n identify: (u) => inner.identify(u),\n getAssignment: (c, s) => inner.getAssignment(c, s),\n assign: (c, v, a, av) => inner.assign(c, v, a, av),\n decide: (i) => inner.decide(i),\n getSlotResult: (s) => inner.getSlotResult(s),\n getPersona: () => inner.getPersona(),\n fetchWeights: () => inner.fetchWeights(),\n getGraph: () => inner.getGraph(),\n dispose: () => inner.dispose(),\n destroy: () => inner.destroy(),\n };\n\n function setInner(fullClient: SentientClient) {\n inner = fullClient;\n }\n\n return { proxy, setInner };\n}\n\n/**\n * Shares one promise among concurrent calls with the same key — assign() and\n * decide() both use this so N same-tick requests (several mounted slots\n * sharing a component id; per-slot lazy decides) cost one roundtrip. The map\n * entry lives exactly as long as the request is in flight: a settled result\n * must NOT serve later calls (a sequential re-request is a fresh decision —\n * caching lives elsewhere), and a failed one must not wedge the key. No\n * `.finally()` — that's ES2018 and this file ships in the es2017 snippet\n * bundle (the SNIP-5 lesson).\n */\nfunction coalesce<T>(inflight: Map<string, Promise<T>>, key: string, run: () => Promise<T>): Promise<T> {\n const hit = inflight.get(key);\n if (hit) return hit;\n const request = (async (): Promise<T> => {\n try {\n return await run();\n } finally {\n inflight.delete(key);\n }\n })();\n inflight.set(key, request);\n return request;\n}\n\n/**\n * Initializes the Sentient client. Returns a no-op client during SSR.\n */\nexport function init(config: SentientConfig): SentientClient {\n if (typeof window === 'undefined') {\n return SSR_CLIENT;\n }\n\n // `|| 'local'`: keyless clients register under the 'local' fallback key\n // below, and `_lastApiKey = ''` is falsy — so a no-arg grantConsent() after\n // a keyless init() warned \"called before init()\" even though init() DID run,\n // instead of resolving that entry (and its blocked-upgrade explanation).\n _lastApiKey = config.apiKey || 'local';\n\n // A re-init for the same key (HMR, consent toggle, provider remount) supersedes\n // the prior client. Dispose it first so its queue's setInterval and\n // visibilitychange/pagehide listeners don't leak — the map only ever held its\n // config, so without this the old client kept flushing forever.\n const prevEntry = _clients.get(config.apiKey || 'local');\n if (prevEntry?.dispose) {\n try {\n prevEntry.dispose();\n } catch {\n /* teardown must never throw on re-init */\n }\n }\n\n // DNT/GPC (a global opt-out) or an explicit `consent: false` must be evaluated\n // BEFORE the local-mode branch: createLocalModeClient() calls initSession()\n // unconditionally, so a gated visitor would otherwise be issued the 365-day\n // `_snt_uid` identity cookie in keyless/local mode (audit P2). DNT/GPC also\n // gates tracking off even when the site passes `consent: true`, and blocks\n // `grantConsent()` from upgrading.\n const dntBlocked = config.respectDoNotTrack !== false && isDoNotTrackEnabled();\n const gated = config.consent === false || dntBlocked;\n\n // Keyless local mode. `localMode: true` forces the local engine (documented\n // escape hatch); 'auto' (default) engages it only when no valid key is\n // present. In production builds `@sentientui/core/local` resolves to a stub\n // and this degrades to a no-op client + one console.error per page load\n // (createLocalModeClient handles that), so no NODE_ENV check is needed here.\n const keyValid = typeof config.apiKey === 'string' && config.apiKey.startsWith('pk_');\n if (config.localMode === true || (!keyValid && config.localMode !== false)) {\n // One registration for both arms (they used to duplicate this set call).\n // upgrade stays null — there is no hosted client to swap in — but the\n // reason lets grantConsent() explain that instead of no-oping silently.\n _clients.set(config.apiKey || 'local', {\n config,\n upgrade: null,\n upgradeBlockedReason:\n '[sentient] grantConsent(): this client is keyless/local — there is no hosted client to upgrade to. Configure a pk_ API key to enable tracking.',\n });\n // A gated visitor must never get the identity cookie. Local mode has no\n // server to serve a statistical winner from, so return a plain no-op.\n if (gated) return SSR_CLIENT;\n return createLocalModeClient(config);\n }\n\n if (gated) {\n if (!config.apiKey || !config.apiKey.startsWith('pk_')) {\n if (config.preConsentBehavior === 'statistical_winner') {\n console.warn('[sentient] init() called with an invalid apiKey — expected a pk_ public key. SDK disabled.');\n }\n // Registered under the same 'local' fallback key the local branch uses:\n // `config.apiKey` here can be '', and a ''-keyed entry was unreachable\n // by a no-arg grantConsent() (falsy `_lastApiKey`), which then wrongly\n // warned \"called before init()\".\n _clients.set(config.apiKey || 'local', {\n config,\n upgrade: null,\n upgradeBlockedReason:\n '[sentient] grantConsent(): the client was initialized with an invalid apiKey (expected a pk_ public key) — consent cannot enable tracking.',\n });\n return SSR_CLIENT;\n }\n // Every gated client gets an upgradeable proxy, not just the winner-serving\n // one — otherwise grantConsent() is silently dead for the 'control' default\n // and the site has to reload to start tracking. Control mode still makes no\n // request; the proxy only exists so consent can swap the inner client.\n const { proxy, setInner } = createPreConsentProxy(config);\n // Under DNT the read-only winner still serves, but consent can never\n // upgrade it to tracking — so drop the upgrade hook.\n _clients.set(config.apiKey, { config, upgrade: dntBlocked ? null : setInner });\n return proxy;\n }\n\n if (!config.apiKey || !config.apiKey.startsWith('pk_')) {\n console.warn('[sentient] init() called with an invalid apiKey — expected a pk_ public key. SDK disabled.');\n return SSR_CLIENT;\n }\n\n if (config.ingestUrl === '') {\n console.warn('[sentient] init() called with an empty ingestUrl. SDK disabled.');\n return SSR_CLIENT;\n }\n\n const resolvedIngestUrl = config.ingestUrl ?? DEFAULT_INGEST_URL;\n\n const sessionStart = Date.now();\n const session = initSession({ ssrSessionId: config.ssrSessionId, apiKey: config.apiKey });\n const assignmentCache = createAssignmentCache(undefined, config.apiKey);\n const eventQueue = createEventQueue({ ingestUrl: resolvedIngestUrl, apiKey: config.apiKey });\n const baseUrl = deriveBaseUrl(resolvedIngestUrl);\n\n const authHeaders = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n } as const;\n\n // Conversions get the same durable transport the event queue has always had:\n // retry with backoff, a cross-reload bucket, dedupe on the server's goalId.\n // One warning per distinct drop status per page load (see onDrop below).\n const warnedDropStatuses = new Set<number>();\n const goalQueue: GoalQueue = createGoalQueue({\n url: `${baseUrl}/goals`,\n apiKey: config.apiKey,\n headers: authHeaders,\n onDrop: (goal, status) => {\n // NOT debug-gated. CONTRACTS §7 says a dropped goal is reported to the\n // developer and never swallowed, but this returned early unless debug was\n // on — so in production a rate-limited or misconfigured project lost every\n // conversion with no signal anywhere. Warn once per (status, page load):\n // enough to be discoverable in a console or an error reporter, quiet\n // enough that a broken integration cannot flood the page.\n if (!config.debug) {\n if (warnedDropStatuses.has(status)) return;\n warnedDropStatuses.add(status);\n }\n console.warn(\n `[sentient] goal dropped (HTTP ${status}) — this will not be retried. ` +\n (status === 400\n ? 'The session was not found: call init() and let the session upsert complete before firing goals.'\n : status === 401 || status === 403\n ? 'Check the API key and that this origin is on the project allowlist.'\n : 'See the response status for the cause.'),\n goal,\n );\n },\n });\n\n const deviceClass = detectDeviceClass(navigator.userAgent ?? '');\n // No window guard here: init() already returned SSR_CLIENT at the top when\n // window is undefined, so the old `typeof window` ternary was dead code.\n const appOrigin = window.location.origin;\n const trafficSource = detectTrafficSource(document.referrer ?? '', appOrigin);\n const sessionSegment =\n config.sessionSegment ?? `${deviceClass}:${trafficSource}`;\n const inflightAssigns = new Map<string, Promise<AssignResult | null>>();\n // Mirrors inflightAssigns for decide(): per-slot lazy-decide patterns (see\n // local-mode's merge comment — one decide({ slots: [decl] }) per mounted\n // slot, all in the same tick) otherwise issue N roundtrips and N snapshot\n // rewrites for one page. Keyed by the full request payload, never just \"a\n // decide is running\": coalescing {slots:[a]} with {slots:[b]} would hand\n // slot b's caller an outcome that never decided b.\n const inflightDecides = new Map<string, Promise<DecideOutcome | null>>();\n\n // --- Adaptive-slot state (decide) ---\n // Results served for this session, keyed by slot id. Written by decide();\n // read by getSlotResult() (Task 3.3) and componentGoal's slot fallback.\n const slotStore = new Map<string, SlotResult>();\n let personaState: { persona: string; confidence: number } | null = null;\n\n // On decide failure every declared slot must still resolve — to its baseline.\n // Never overwrite a previously served result.\n const seedSlotBaselines = (decls: SlotDeclInput[]): void => {\n for (const d of decls) {\n if (!slotStore.has(d.id)) slotStore.set(d.id, baselineResultFor(d));\n }\n };\n\n // Seed slot/persona state. Priority: explicit SSR seeds → snapshot.\n if (config.initialSlots) {\n for (const [slotId, result] of Object.entries(config.initialSlots)) {\n slotStore.set(slotId, result);\n }\n }\n const seedSnapshot = readSnapshot(config.apiKey);\n if (seedSnapshot) {\n for (const [slotId, result] of Object.entries(seedSnapshot.slots)) {\n if (!slotStore.has(slotId)) slotStore.set(slotId, result);\n }\n }\n\n // Band-only persona sources (html attrs, snapshot) become a band-consistent\n // numeric confidence so confidenceBand(confidence) always equals the band.\n const BAND_CONFIDENCE: Record<string, number> = { low: 0.15, medium: 0.5, high: 0.85 };\n if (config.initialPersona) {\n personaState = { ...config.initialPersona };\n } else {\n // Single-writer rule: the inline pre-paint script owns the <html>\n // attributes. The client ADOPTS them as truth and never rewrites them\n // mid-session (next visit's script picks up the new snapshot instead).\n const ds = document.documentElement.dataset;\n if (ds.sentientPersona) {\n personaState = {\n persona: ds.sentientPersona,\n confidence: BAND_CONFIDENCE[ds.sentientConfidence ?? 'low'] ?? 0.15,\n };\n } else if (seedSnapshot) {\n personaState = {\n persona: seedSnapshot.persona,\n confidence: BAND_CONFIDENCE[seedSnapshot.band] ?? 0.15,\n };\n }\n }\n\n // Seed SSR-preloaded assignments into the local cache so assign() finds a\n // cache hit immediately — no network call, no variant flash on hydration.\n if (config.initialAssignments) {\n for (const [componentId, variantId] of Object.entries(config.initialAssignments)) {\n assignmentCache.set(componentId, sessionSegment, {\n variantId,\n assignedAt: Date.now(),\n segment: sessionSegment,\n confidence: 1,\n });\n }\n }\n\n // Upsert session metadata once on init. assign() awaits this promise so\n // the server isn't asked to assign for a session row that doesn't exist yet.\n let sessionReady: Promise<void> = Promise.resolve();\n\n const sessionId = session.getSessionId();\n if (sessionId) {\n const referrerDomain = referrerDomainFromReferer(document.referrer ?? '');\n const { utmParams, clickIds } = readTrackedParams();\n const sessionBody = {\n sessionId,\n deviceClass,\n trafficSource,\n referrerDomain,\n utmParams,\n // Ad-platform click IDs (gclid & co). Captured separately from utmParams\n // because Google Ads auto-tagging appends ONLY gclid — without this,\n // paid search with no manual UTM template reported as organic.\n clickIds,\n timeOfDay: detectTimeOfDay(new Date()),\n dayOfWeek: ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][new Date().getDay()],\n ephemeral: session.isEphemeral(),\n // Likely-automation hint: navigator.webdriver (set under automation\n // control) or a known agent token in the UA. Probabilistic — used for\n // metrics + bandit exclusion server-side, never to change what's served.\n automation:\n (typeof navigator !== 'undefined' && navigator.webdriver === true) ||\n uaTokenMatch(navigator.userAgent ?? ''),\n ...(config.userId ? { userId: config.userId } : {}),\n ...(config.persona ? { persona: config.persona } : {}),\n ...(config.country ? { country: config.country } : {}),\n // Version-skew reporting. Only a wrapper that knows its own released\n // version sets this — a dev-sentinel version is dropped by the caller,\n // not smuggled through, or the dashboard would read it as \"behind\"\n // forever (the same trap the snippet's decide reporting documents).\n ...(config.sdk ? { sdk: config.sdk.name, sdkVersion: config.sdk.version } : {}),\n };\n // The session row is a PRECONDITION for every conversion: /v1/goals answers\n // 400 session_not_found without it, and the durable queue classifies a 4xx\n // as terminal — so a session upsert that fails silently turns every later\n // conversion into a permanent drop. That is not hypothetical: /v1/sessions\n // carries the same per-IP limiter as /v1/goals, so under shared egress\n // (offices, mobile carriers, corporate NAT) the SESSION call 429s first and\n // the goals that follow are dropped for good. Retry it, so a transient\n // failure costs a moment rather than the visit's whole conversion history.\n const upsertSession = async (): Promise<undefined> => {\n for (let attempt = 0; ; attempt++) {\n try {\n const res = await fetch(`${baseUrl}/sessions`, {\n method: 'POST',\n keepalive: true,\n body: JSON.stringify(sessionBody),\n headers: authHeaders,\n });\n if (res.status === 402) {\n console.warn(\n '[SentientUI] Session limit exceeded for this project. The bandit will stop learning until the limit resets. Upgrade at sentient-ui.com/pricing',\n );\n return undefined; // terminal: retrying a quota will not clear it\n }\n if (res.ok || classifyResponse(res) === 'dropped') return undefined;\n } catch {\n /* network failure — same retry path as a 5xx */\n }\n if (attempt >= SESSION_UPSERT_RETRIES) {\n // Say so once: from here every conversion this visit will 400, and\n // that used to be entirely silent.\n console.warn(\n '[SentientUI] Could not register the session after retries. Conversions in this visit may not be recorded.',\n );\n return undefined;\n }\n await new Promise((r) => setTimeout(r, backoffDelayMs(attempt + 1)));\n }\n };\n try {\n sessionReady = upsertSession();\n } catch {\n /* never throw on init */\n }\n }\n\n if (config.debug) {\n console.log('[sentient] initialized', { context: config.context });\n (\n window as unknown as {\n __sentient?: {\n client: SentientClient;\n queue: EventQueue;\n };\n }\n ).__sentient = {\n client: null as unknown as SentientClient,\n queue: eventQueue,\n };\n }\n\n // One user action must record ONE session-level conversion per goal name.\n // Nested components each fire their declared goal on the same click (every\n // <Adaptive>/hook path calls componentGoal() AND goal()), so a hero nested\n // inside a CTA wrapper wrote two goal_events rows for one click: harmless for\n // a weight-1.0 goal (close-out clamps at 1) but a 0.3-weight step summed to\n // 0.6, and the Goals page counts Hits as COUNT(*) either way.\n //\n // The scope is one event dispatch, not one session and no longer one task:\n // see createActionLatch. A session-wide latch would swallow genuine repeat\n // conversions (two purchases in one visit are two conversions), and a\n // time-boxed one did exactly that whenever the box outlived the action.\n // Calls carrying distinct externalIds are never collapsed — those are, by\n // definition, distinct orders.\n const firedThisAction = createActionLatch();\n\n // Set once tracking starts; dispose() and destroy() call it so a replaced\n // client stops watching history instead of emitting forever. The flag records\n // teardown for the delivery-marking below, which runs on a later microtask.\n let stopPageviews: (() => void) | null = null;\n let pageviewsTornDown = false;\n\n const client: SentientClient = {\n goal(name: string, metadataOrOpts: Record<string, unknown> = {}, weight = 1.0, stepIndex = 0) {\n const sid = session.getSessionId();\n if (!sid) return;\n const opts: GoalOptions = isGoalOptions(metadataOrOpts)\n ? (metadataOrOpts as GoalOptions)\n : { metadata: metadataOrOpts };\n // stepIndex and weight are part of the key: funnel steps share a goal\n // name and differ by stepIndex, so collapsing on name alone dropped a\n // step fired in the same handler. Only IDENTICAL calls are one action.\n //\n // value and currency key the payload SEPARATELY (the latch's valueKey),\n // not as more segments of the flat key, and for a costlier reason: they\n // are the money. Flattened out of the key entirely, a $50 order and a\n // $70 order landing in one window collapsed into a single $50 record —\n // the client half of CONTRACTS §1 \"two orders are worth two orders\",\n // which the server already honours (migration 119 records the repeat if\n // it arrives; this is what stopped it arriving). But flattened INTO the\n // key, a valued inner component nested in a valueless wrapper made two\n // keys out of one click — two rows for one order. So the latch compares\n // values only between valued fires ($50 vs $70 stays two records) and\n // lets a valued record absorb a valueless re-fire of the same action\n // (see createActionLatch for the one asymmetric case).\n //\n // NOT metadata: every nested <Adaptive>/hook path stamps its own\n // componentId and variantId in there, so keying on it would make the\n // duplicate this latch exists to collapse look distinct again.\n const actionKey = [\n name,\n opts.externalId ?? '',\n opts.stepIndex ?? stepIndex,\n opts.weight ?? weight,\n ].join('\\0');\n const valueKey = opts.value !== undefined ? `${opts.value}\\0${opts.currency ?? ''}` : null;\n if (firedThisAction.firedBefore(actionKey, valueKey)) {\n if (config.debug) {\n console.log(`[sentient] goal(\"${name}\") already recorded for this action — not sent twice`);\n }\n return;\n }\n const goalId = generateEventId();\n // undefined values vanish at JSON.stringify time, so optional fields\n // need no conditional assembly.\n const body = {\n sessionId: sid,\n name,\n metadata: opts.metadata ?? {},\n weight: opts.weight ?? weight,\n stepIndex: opts.stepIndex ?? stepIndex,\n goalId,\n value: opts.value,\n currency: opts.currency,\n externalId: opts.externalId,\n };\n if (config.debug) {\n console.log('[sentient] goal', body);\n }\n // Serialize once, here: the queued copy must replay byte-identically\n // (same goalId) so a retry dedupes server-side instead of double-counting.\n const payload = { id: goalId, body: JSON.stringify(body) };\n sessionReady.then(() => goalQueue.send(payload));\n },\n\n componentGoal(componentId, goalType, opts) {\n const sid = session.getSessionId();\n if (!sid) return;\n // Variant experiments resolve from the assignment cache; adaptive slots\n // (useAdaptiveTokens / AdaptiveGroup) resolve from the slot state, using\n // the canonical arm string as the attributed variantId.\n const assignment = assignmentCache.get(componentId, sessionSegment);\n const slotResult = assignment ? null : slotStore.get(componentId) ?? null;\n if (!assignment && slotResult === null) {\n if (config.debug) {\n console.warn(\n `[sentient] componentGoal(\"${componentId}\"): no assignment or slot decision yet — render its <Adaptive>/adaptive hook or call assign()/decide() before recording a goal.`,\n );\n }\n return;\n }\n const attributedVariantId = assignment ? assignment.variantId : armOfResult(slotResult!);\n const fullEvent: SentientEvent = {\n id: generateEventId(),\n sessionId: sid,\n projectId: config.apiKey,\n componentId,\n variantId: attributedVariantId,\n eventType: 'goal_achieved',\n goalType,\n // undefined goalValue/currency vanish at JSON.stringify time.\n payload: {\n reward: opts?.reward ?? 1,\n goalValue: opts?.value,\n currency: opts?.currency,\n ...(opts?.metadata ?? {}),\n },\n timestamp: Date.now(),\n timeInSession: Date.now() - sessionStart,\n path: currentPath(),\n };\n if (config.debug) {\n console.log('[sentient] componentGoal', fullEvent);\n }\n sessionReady.then(() => eventQueue.push(fullEvent));\n },\n\n identify(userId) {\n const sid = session.getSessionId();\n if (!sid) return;\n sessionReady.then(() => {\n fetch(`${baseUrl}/sessions`, {\n method: 'POST',\n keepalive: true,\n body: JSON.stringify({ sessionId: sid, userId, ephemeral: session.isEphemeral() }),\n headers: authHeaders,\n }).catch(() => undefined);\n });\n },\n\n track(event) {\n const sessionId = session.getSessionId();\n if (!sessionId) return;\n\n const fullEvent: SentientEvent = {\n // `path` first so an explicit event.path from the caller wins over the\n // ambient one — a server-side or replayed event knows its page better\n // than location does.\n path: currentPath(),\n ...event,\n id: generateEventId(),\n sessionId,\n timestamp: Date.now(),\n timeInSession: Date.now() - sessionStart,\n };\n\n if (config.debug) {\n console.log('[sentient] track', fullEvent);\n }\n\n sessionReady.then(() => eventQueue.push(fullEvent));\n },\n\n getAssignment(componentId, segment) {\n return assignmentCache.get(componentId, segment);\n },\n\n async assign(componentId, variantIds, agentData?, agentDataByVariant?) {\n const sid = session.getSessionId();\n if (!sid) return null;\n\n const cached = assignmentCache.get(componentId, sessionSegment);\n // When variantIds are provided (A/B code variant), a cache hit is always final.\n // When variantIds are absent (managed text component), only hit the cache if content\n // is present — a seed from initialAssignments has no content and must still fetch.\n if (cached && (variantIds?.length || cached.content !== undefined)) {\n // Surface the entry's remaining TTL (server-provided when set) instead of\n // a hardcoded 0, so callers can reason about when a re-assign is due.\n const remainingTtlMs =\n cached.ttlMs && cached.ttlMs > 0\n ? Math.max(0, cached.assignedAt + cached.ttlMs - Date.now())\n : 0;\n return { variantId: cached.variantId, assignmentTtlMs: remainingTtlMs, content: cached.content };\n }\n\n // Coalesce concurrent assigns for the same component (e.g. several\n // mounted slots sharing one id) into a single network request.\n return coalesce(inflightAssigns, componentId, async () => {\n await sessionReady;\n try {\n const body: Record<string, unknown> = { sessionId: sid, componentId, variantIds };\n if (agentDataByVariant !== undefined) body.agentDataByVariant = agentDataByVariant;\n else if (agentData !== undefined) body.agentData = agentData;\n const res = await fetch(`${baseUrl}/assign`, {\n method: 'POST',\n body: JSON.stringify(body),\n headers: authHeaders,\n });\n if (!res.ok) return null;\n const result = (await res.json()) as AssignResult;\n assignmentCache.set(componentId, sessionSegment, {\n variantId: result.variantId,\n assignedAt: Date.now(),\n segment: sessionSegment,\n confidence: 1,\n content: result.content,\n // Honor the server's TTL as this entry's expiry; omit when absent/0\n // so the cache falls back to its default (DEFAULT_TTL_MS).\n ...(result.assignmentTtlMs && result.assignmentTtlMs > 0\n ? { ttlMs: result.assignmentTtlMs }\n : {}),\n });\n return result;\n } catch {\n return null;\n }\n });\n },\n\n async decide(input) {\n const sid = session.getSessionId();\n if (!sid) return null;\n const declared = input.slots ?? [];\n\n const body: Record<string, unknown> = { sessionId: sid };\n if (input.sections && input.sections.length > 0) {\n body.sections = input.sections.map((id) => ({ id }));\n }\n body.components = input.components ?? [];\n if (declared.length > 0) body.slots = declared.map(toWireSlot);\n if (input.slotsFrom === 'registry') body.slotsFrom = 'registry';\n if (input.v) body.v = input.v;\n // Declared persona rides on decide too: SSR-first flows can race the\n // session upsert, and the decide-body value wins for this decision.\n if (config.persona) body.persona = config.persona;\n\n // Coalesce concurrent IDENTICAL decides into one request + one snapshot\n // write, keyed by the serialized wire payload (also reused as the fetch\n // body). Never key on just \"a decide is running\": coalescing {slots:[a]}\n // with {slots:[b]} would hand slot b's caller an outcome that never\n // decided b. The wire projection is a safe key even though toWireSlot\n // strips SDK-only decl fields — the baselines synthesized for omitted\n // slots below go through baselineResultFor, which projects with the\n // same toWireSlot, so identical payloads imply identical outcomes.\n const decideKey = JSON.stringify(body);\n return coalesce(inflightDecides, decideKey, async () => {\n await sessionReady;\n try {\n const res = await fetch(`${baseUrl}/decide`, {\n method: 'POST',\n body: decideKey,\n headers: authHeaders,\n });\n if (!res.ok) {\n seedSlotBaselines(declared);\n return null;\n }\n const data = (await res.json()) as {\n layoutOrder?: string[] | null;\n assignments?: Record<string, string>;\n slots?: Record<string, SlotResult>;\n slotConfig?: Record<string, SlotConfigEntry>;\n goals?: GoalDefinition[];\n sectionMap?: SectionMapEntry[];\n palette?: import('./blocks.js').SitePalette;\n persona?: string;\n confidence?: number;\n };\n\n const slots: Record<string, SlotResult> = {};\n for (const d of declared) {\n // `data.slots === undefined` means the server predates the slots\n // contract: serve the declared baseline everywhere, do NOT retry.\n // (Distinct from `slots: {}`, which also falls back per-slot.)\n const served = data.slots?.[d.id];\n if (served !== undefined) {\n slots[d.id] = served;\n slotStore.set(d.id, served);\n continue;\n }\n // Slot omitted from the response → synthesize a baseline for the\n // RETURN value, but apply the same never-overwrite rule the failure\n // path (seedSlotBaselines) has always had. This success path used\n // to write unconditionally, so a partial response — or a pre-slots\n // server — clobbered SSR-seeded and previously-served results with\n // synthetic baselines.\n const prior = slotStore.get(d.id);\n if (prior !== undefined) {\n slots[d.id] = prior;\n } else {\n const baseline = baselineResultFor(d);\n slots[d.id] = baseline;\n slotStore.set(d.id, baseline);\n }\n }\n // Registry mode: the server returns slots the request never declared.\n // Take them verbatim (classic mode returns only declared slots, so this\n // union is a no-op there — back-compatible). These are real served\n // results, so they may overwrite the store, unlike the baselines above.\n if (data.slots) {\n for (const [slotId, result] of Object.entries(data.slots)) {\n if (!(slotId in slots)) {\n slots[slotId] = result;\n slotStore.set(slotId, result);\n }\n }\n }\n // Only overwrite persona when the response actually carries one, and\n // never downgrade a known persona to 'unknown' — a decide that omits\n // persona (or returns 'unknown') must not clobber a good SSR/snapshot/\n // initialPersona value, and must not persist that regression below.\n const known = personaState != null && personaState.persona !== 'unknown';\n if (data.persona && !(data.persona === 'unknown' && known)) {\n personaState = { persona: data.persona, confidence: data.confidence ?? 0 };\n } else if (!personaState) {\n personaState = { persona: 'unknown', confidence: 0 };\n }\n\n // Seed component assignments so <Adaptive>/assign() agree with this\n // decide (same shape as the initialAssignments seed above).\n for (const [componentId, variantId] of Object.entries(data.assignments ?? {})) {\n assignmentCache.set(componentId, sessionSegment, {\n variantId,\n assignedAt: Date.now(),\n segment: sessionSegment,\n confidence: 1,\n });\n }\n\n // Persist for the next visit's pre-paint (SPA cache-first pattern).\n writeSnapshot(config.apiKey, {\n v: 1,\n persona: personaState.persona,\n band: confidenceBand(personaState.confidence),\n slots: Object.fromEntries(slotStore),\n layoutOrder: data.layoutOrder ?? null,\n savedAt: Date.now(),\n ...(data.slotConfig ? { slotConfig: data.slotConfig } : {}),\n ...(data.palette ? { palette: data.palette } : {}),\n });\n\n return {\n layoutOrder: data.layoutOrder ?? null,\n assignments: data.assignments ?? {},\n slots,\n persona: personaState.persona,\n confidence: personaState.confidence,\n ...(data.slotConfig ? { slotConfig: data.slotConfig } : {}),\n ...(data.goals ? { goals: data.goals } : {}),\n ...(data.sectionMap ? { sectionMap: data.sectionMap } : {}),\n ...(data.palette ? { palette: data.palette } : {}),\n };\n } catch {\n seedSlotBaselines(declared);\n return null;\n }\n });\n },\n\n getSlotResult(slotId) {\n return slotStore.get(slotId) ?? null;\n },\n\n getPersona() {\n if (!personaState) return null;\n return {\n persona: personaState.persona,\n confidence: personaState.confidence,\n band: confidenceBand(personaState.confidence),\n };\n },\n\n async fetchWeights() {\n try {\n const res = await fetch(`${baseUrl}/weights`, { headers: authHeaders });\n if (!res.ok) return [];\n const data = (await res.json()) as { components: ComponentWeightEntry[] };\n return data.components ?? [];\n } catch {\n return [];\n }\n },\n\n getGraph() {\n return { pageNodes: [], capturedAt: 0 };\n },\n\n dispose() {\n // Stops the flush timer and unload listeners (with a final flush) but\n // leaves identity, snapshot, and retry buckets for the next client.\n stopPageviews?.();\n pageviewsTornDown = true;\n eventQueue.destroy();\n goalQueue.destroy();\n // Drop the registry entry so a later re-init doesn't try to dispose an\n // already-torn-down client (and so the map doesn't pin this closure).\n if (_clients.get(config.apiKey)?.dispose === client.dispose) {\n _clients.delete(config.apiKey);\n }\n if (config.debug) {\n console.log('[sentient] disposed');\n }\n },\n\n destroy() {\n stopPageviews?.();\n pageviewsTornDown = true;\n eventQueue.destroy();\n goalQueue.destroy();\n session.destroy();\n // The assignment cache persists in localStorage (`_snt_asgn_*`) with a\n // 30-minute default TTL — left in place, a revoked visitor returning\n // within that window was handed their previous personalized variants\n // back, so forget-me wasn't total.\n assignmentCache.clear();\n if (_clients.get(config.apiKey)?.dispose === client.dispose) {\n _clients.delete(config.apiKey);\n }\n // Forget-me must be total: a surviving decision snapshot would\n // re-personalize the next visit via the pre-paint script, and a\n // persisted retry bucket would re-send events for the deleted identity.\n try {\n localStorage.removeItem(SNAPSHOT_STORAGE_KEY_PREFIX + config.apiKey);\n localStorage.removeItem(retryStorageKey(config.apiKey));\n localStorage.removeItem(goalRetryStorageKey(config.apiKey));\n } catch {\n /* storage unavailable — nothing persisted to remove */\n }\n if (config.debug) {\n console.log('[sentient] destroyed');\n }\n },\n };\n\n _clients.set(config.apiKey, { config, upgrade: null, dispose: client.dispose });\n\n stopPageviews = startPageviewTracking(client, config.apiKey, (key) => {\n // track() defers its enqueue on sessionReady; chaining after it means this\n // runs once the push has happened. If the client was torn down first, the\n // event went into a destroyed queue and never ships — leave the page\n // unmarked so the replacement client's emit is the one that counts.\n void sessionReady.then(() => {\n if (!pageviewsTornDown) emittedPages.add(key);\n });\n });\n\n if (config.debug) {\n const win = window as unknown as { __sentient?: { client: SentientClient } };\n if (win.__sentient) {\n win.__sentient.client = client;\n }\n }\n\n return client;\n}\n","/**\n * Keyless local mode — client factory.\n *\n * This module ships in the MAIN core bundle and contains no engine code: the\n * engine arrives through a dynamic import of the bare specifier\n * `@sentientui/core/local`, which the consumer's bundler resolves through the\n * development/production export conditions. In production builds that\n * specifier is the stub, and this client degrades to a no-op plus one\n * console.error per page load.\n */\nimport { initSession } from './session.js';\nimport { readSnapshot, writeSnapshot } from './snapshot.js';\nimport { confidenceBand } from '@sentientui/policy';\nimport type {\n DecideOutcome,\n SentientClient,\n SentientConfig,\n SlotDeclInput,\n} from './index.js';\n\ntype LocalEngineModule = {\n LOCAL_ENGINE_AVAILABLE: boolean;\n createLocalEngine(opts: { sessionId: string; forcedPersona?: string }): {\n decide(input: {\n sections?: string[];\n components?: Array<{ id: string; variantIds?: string[] }>;\n slots?: SlotDeclInput[];\n }): DecideOutcome;\n };\n};\n\nexport const PROD_KEYLESS_ERROR =\n '[sentient] No API key configured — nothing is being learned. Set NEXT_PUBLIC_SENTIENT_API_KEY or pass localMode: true for local development.';\n\nexport const LOCAL_MODE_BANNER =\n '[sentient] Local mode — decisions are simulated on-device and nothing is sent over the network. Add an API key to learn from real traffic.';\n\n// Once-per-page-load guards (module state resets on reload).\nlet bannerShown = false;\nlet prodErrorShown = false;\n\n/** @internal test hook */\nexport function __resetLocalModeLogGuards(): void {\n bannerShown = false;\n prodErrorShown = false;\n}\n\n/** Mirrors the ?sentient_variant= override pattern; validated by the engine. */\nfunction readPersonaOverrideFromUrl(): string | undefined {\n try {\n return new URLSearchParams(window.location.search).get('sentient_persona') ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function createLocalModeClient(config: SentientConfig): SentientClient {\n const session = initSession({ ssrSessionId: config.ssrSessionId, apiKey: config.apiKey });\n const sessionId = session.getSessionId() ?? 'local';\n const forcedPersona = readPersonaOverrideFromUrl();\n\n // The specifier MUST stay a bare package subpath (never a relative path) so\n // the consumer's bundler applies the development/production export\n // conditions. tsup keeps it external (see tsup.config.ts).\n const modPromise: Promise<LocalEngineModule | null> = import('@sentientui/core/local')\n .then((mod) => {\n const m = mod as unknown as LocalEngineModule;\n if (!m.LOCAL_ENGINE_AVAILABLE) {\n if (!prodErrorShown) {\n prodErrorShown = true;\n console.error(PROD_KEYLESS_ERROR);\n }\n return null;\n }\n if (!bannerShown) {\n bannerShown = true;\n console.info(LOCAL_MODE_BANNER);\n }\n return m;\n })\n .catch(() => {\n if (!prodErrorShown) {\n prodErrorShown = true;\n console.error(PROD_KEYLESS_ERROR);\n }\n return null;\n });\n\n let lastOutcome: DecideOutcome | null = null;\n\n // Pre-first-decide persona seed. The hosted client serves config.initialPersona\n // (SSR) and then the persisted snapshot before its first decide; local mode\n // returned null until the first decide resolved, so a locally-developed page\n // rendered its default persona on first paint and diverged from what the\n // same code shows against production. Band-only snapshot sources map to the\n // same band-consistent confidences the hosted client uses, so\n // confidenceBand(confidence) always round-trips to the stored band.\n const BAND_CONFIDENCE: Record<string, number> = { low: 0.15, medium: 0.5, high: 0.85 };\n const seedPersona = ((): { persona: string; confidence: number } | null => {\n if (config.initialPersona) return { ...config.initialPersona };\n const snap = readSnapshot(config.apiKey || 'local');\n if (snap) return { persona: snap.persona, confidence: BAND_CONFIDENCE[snap.band] ?? 0.15 };\n return null;\n })();\n\n function applyPersonaAttributes(outcome: DecideOutcome): void {\n // Single-writer rule: adopt attributes already written (e.g. by the\n // AdaptiveRoot inline script); only write when nothing has yet.\n const el = document.documentElement;\n if (el.dataset.sentientPersona === undefined) {\n el.dataset.sentientPersona = outcome.persona;\n el.dataset.sentientConfidence = confidenceBand(outcome.confidence);\n }\n }\n\n return {\n isLocal: true,\n\n async decide(input) {\n const mod = await modPromise;\n if (!mod) return null;\n const outcome = mod.createLocalEngine({ sessionId, forcedPersona }).decide(input);\n // Accumulate across calls: slots decide lazily one at a time (per-slot\n // decide from useSlotResult), so a later decide must not evict results\n // an earlier one served. Same (sessionId, persona) → merging is safe.\n lastOutcome = {\n ...outcome,\n layoutOrder: outcome.layoutOrder ?? lastOutcome?.layoutOrder ?? null,\n slots: { ...(lastOutcome?.slots ?? {}), ...outcome.slots },\n };\n writeSnapshot(config.apiKey || 'local', {\n v: 1,\n persona: lastOutcome.persona,\n band: confidenceBand(lastOutcome.confidence),\n slots: lastOutcome.slots,\n layoutOrder: lastOutcome.layoutOrder,\n savedAt: Date.now(),\n });\n applyPersonaAttributes(outcome);\n return outcome;\n },\n\n getSlotResult(slotId) {\n return lastOutcome?.slots[slotId] ?? config.initialSlots?.[slotId] ?? null;\n },\n\n getPersona() {\n const p = lastOutcome ?? seedPersona;\n if (!p) return null;\n return {\n persona: p.persona,\n confidence: p.confidence,\n band: confidenceBand(p.confidence),\n };\n },\n\n async assign(componentId, variantIds) {\n const mod = await modPromise;\n if (!mod || !variantIds || variantIds.length === 0) {\n return variantIds?.[0] ? { variantId: variantIds[0], assignmentTtlMs: 0 } : null;\n }\n const outcome = mod\n .createLocalEngine({ sessionId, forcedPersona })\n .decide({ components: [{ id: componentId, variantIds }] });\n return { variantId: outcome.assignments[componentId] ?? variantIds[0], assignmentTtlMs: 0 };\n },\n\n // Local mode never talks to the network: the tracking surface no-ops.\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n getAssignment: () => null,\n fetchWeights: () => Promise.resolve([]),\n getGraph: () => ({ pageNodes: [], capturedAt: 0 }),\n dispose: () => undefined, // no timers/network in local mode; keep the session\n destroy: () => session.destroy(),\n };\n}\n","/**\n * Composition Blocks (spec: 2026-08-20-nocode-composition-variants-design.md §4).\n *\n * A bounded, TYPED component tree — never HTML — that a registry arm may carry\n * (`published_config.arms[].blocks`). The vocabulary is the whitelist: every\n * prop value is an enumerated token, validated server-side before publish\n * (apps/api/src/domain/composition-blocks.ts) and rendered client-side through\n * document.createElement + property assignment only. No innerHTML/outerHTML/\n * insertAdjacentHTML exists anywhere on this path — the security property is\n * preserved by never accepting HTML, not by sanitizing it, which is why there\n * is no sanitizer to keep honest.\n *\n * Token lists live here (not in the API domain like SlotOps' mirrors) because\n * three parties must agree byte-for-byte: the server validator, the snippet\n * renderer, and eventually the React renderer (§11) — a drifted copy would let\n * a published arm fail to render, which the fail-safe turns into an invisibly\n * missing section, not an error.\n */\n\nexport const BLOCK_GAPS = ['none', 'sm', 'md', 'lg'] as const;\nexport const BLOCK_ALIGNS = ['start', 'center', 'end', 'stretch'] as const;\nexport const BLOCK_JUSTIFIES = ['start', 'center', 'end', 'between'] as const;\nexport const BLOCK_SIZES = ['sm', 'md', 'lg'] as const;\nexport const BLOCK_WEIGHTS = ['normal', 'medium', 'bold'] as const;\nexport const BLOCK_TONES = ['default', 'muted', 'accent'] as const;\nexport const BLOCK_EMPHASES = ['primary', 'secondary', 'ghost'] as const;\nexport const BLOCK_TEXT_ALIGNS = ['left', 'center', 'right'] as const;\nexport const BLOCK_RATIOS = ['auto', 'square', 'landscape', 'wide'] as const;\nexport const BLOCK_FITS = ['cover', 'contain'] as const;\nexport const BLOCK_GRID_COLUMNS = [2, 3, 4] as const;\nexport const BLOCK_HEADING_LEVELS = [2, 3, 4] as const;\n\nexport type BlockGap = (typeof BLOCK_GAPS)[number];\nexport type BlockAlign = (typeof BLOCK_ALIGNS)[number];\nexport type BlockJustify = (typeof BLOCK_JUSTIFIES)[number];\nexport type BlockSize = (typeof BLOCK_SIZES)[number];\nexport type BlockWeight = (typeof BLOCK_WEIGHTS)[number];\nexport type BlockTone = (typeof BLOCK_TONES)[number];\nexport type BlockEmphasis = (typeof BLOCK_EMPHASES)[number];\nexport type BlockTextAlign = (typeof BLOCK_TEXT_ALIGNS)[number];\nexport type BlockRatio = (typeof BLOCK_RATIOS)[number];\nexport type BlockFit = (typeof BLOCK_FITS)[number];\n\n/** Flex row/column container. */\nexport type StackBlock = {\n type: 'stack';\n direction: 'row' | 'column';\n children: BlockNode[];\n gap?: BlockGap;\n align?: BlockAlign;\n justify?: BlockJustify;\n wrap?: boolean;\n};\n\n/** 2–4 equal-column grid container. */\nexport type GridBlock = {\n type: 'grid';\n columns: (typeof BLOCK_GRID_COLUMNS)[number];\n children: BlockNode[];\n gap?: BlockGap;\n align?: BlockAlign;\n};\n\n/** Paragraph / label. */\nexport type TextBlock = {\n type: 'text';\n value: string;\n size?: BlockSize;\n weight?: BlockWeight;\n tone?: BlockTone;\n align?: BlockTextAlign;\n};\n\n/** h2–h4 — never h1 (the page owns its h1). */\nexport type HeadingBlock = {\n type: 'heading';\n value: string;\n level: (typeof BLOCK_HEADING_LEVELS)[number];\n size?: BlockSize;\n align?: BlockTextAlign;\n};\n\n/** Link styled as a button. `tag` feeds agent legibility (agentDataByVariant). */\nexport type ButtonBlock = {\n type: 'button';\n label: string;\n href: string;\n emphasis?: BlockEmphasis;\n size?: BlockSize;\n tag?: string;\n};\n\n/** Inline text link. */\nexport type LinkBlock = {\n type: 'link';\n label: string;\n href: string;\n tag?: string;\n};\n\n/** Image. `alt` is required; empty only with an explicit `decorative: true`. */\nexport type ImageBlock = {\n type: 'image';\n src: string;\n alt: string;\n decorative?: boolean;\n ratio?: BlockRatio;\n fit?: BlockFit;\n};\n\n/** Eyebrow / pill. */\nexport type BadgeBlock = {\n type: 'badge';\n value: string;\n tone?: BlockTone;\n};\n\n/** Vertical rhythm. */\nexport type SpacerBlock = {\n type: 'spacer';\n size: BlockSize;\n};\n\nexport type BlockNode =\n | StackBlock\n | GridBlock\n | TextBlock\n | HeadingBlock\n | ButtonBlock\n | LinkBlock\n | ImageBlock\n | BadgeBlock\n | SpacerBlock;\n\n/** The derived site palette (spec §4 \"Colour and type: derived, not chosen\").\n * Sampled by the on-site editor from the live page's own buttons — computed\n * styles, so values are plain colors (rgb/hex), never var()/url() — validated\n * server-side, stored per project, and served with the decision so injected\n * blocks render in the merchant's own primary color and corner radius.\n * Absent → the renderer's neutral inherit-first defaults. */\nexport type SitePalette = {\n primaryBg: string;\n primaryText: string;\n radius: string;\n};\n\n// Structural caps. Total-nodes and depth bound the render cost of one arm;\n// the arms cap bounds Option B's DOM weight (every arm pre-renders hidden, so\n// DOM cost is arms × nodes — spec §6 proposed 4 and nothing has argued it up).\nexport const MAX_BLOCK_NODES = 64;\nexport const MAX_BLOCK_DEPTH = 5;\nexport const MAX_BLOCK_CHILDREN = 12;\nexport const MAX_BLOCK_ARMS = 4;\nexport const MAX_BLOCK_TEXT_LEN = 500;\n","export type MicroSignalEmitter = (\n signalType: 'rage_click' | 'text_copy' | 'scroll_hesitation' | 'tab_loss',\n extra?: Record<string, unknown>,\n) => void;\n\nexport type MicroSignalType = Parameters<MicroSignalEmitter>[0];\n\n/**\n * Attaches passive behavioral detectors to `node`. Calls `emit` when a signal\n * fires. Each signal type fires at most once per call to this function.\n * Returns a cleanup function that removes all listeners.\n */\nexport function attachMicroSignalDetectors(\n emit: MicroSignalEmitter,\n node: Element,\n variantAssignedAt?: number,\n options?: { tabLoss?: boolean },\n): () => void {\n const cleanups: Array<() => void> = [];\n\n // --- Rage click: 3+ clicks within 500ms ---\n {\n const WINDOW_MS = 500;\n const THRESHOLD = 3;\n let firedOnce = false;\n const timestamps: number[] = [];\n\n const onClick = (): void => {\n if (firedOnce) return;\n const now = Date.now();\n timestamps.push(now);\n while (timestamps.length > 0 && now - timestamps[0]! > WINDOW_MS) {\n timestamps.shift();\n }\n if (timestamps.length >= THRESHOLD) {\n firedOnce = true;\n emit('rage_click');\n }\n };\n\n node.addEventListener('click', onClick);\n cleanups.push(() => node.removeEventListener('click', onClick));\n }\n\n // --- Text copy: copy event within node ---\n {\n let firedOnce = false;\n\n const onCopy = (e: Event): void => {\n if (firedOnce) return;\n if (!(e.target instanceof Node)) return;\n if (!node.contains(e.target) && node !== e.target) return;\n firedOnce = true;\n const sel = typeof window !== 'undefined' ? window.getSelection() : null;\n const selectionLength = sel ? sel.toString().length : 0;\n emit('text_copy', { selectionLength });\n };\n\n document.addEventListener('copy', onCopy);\n cleanups.push(() => document.removeEventListener('copy', onCopy));\n }\n\n // --- Scroll hesitation: scroll stops 3s while component visible ---\n {\n let firedOnce = false;\n let isVisible = false;\n let hesitationTimer: ReturnType<typeof setTimeout> | null = null;\n\n const clearHesitation = (): void => {\n if (hesitationTimer !== null) {\n clearTimeout(hesitationTimer);\n hesitationTimer = null;\n }\n };\n\n const startHesitation = (): void => {\n if (firedOnce || !isVisible) return;\n clearHesitation();\n hesitationTimer = setTimeout(() => {\n if (!firedOnce && isVisible) {\n firedOnce = true;\n emit('scroll_hesitation');\n }\n }, 3000);\n };\n\n const onScroll = (): void => {\n clearHesitation();\n startHesitation();\n };\n\n const ioCallback = (entries: IntersectionObserverEntry[]): void => {\n for (const entry of entries) {\n isVisible = entry.intersectionRatio > 0.3;\n if (!isVisible) clearHesitation();\n else startHesitation();\n }\n };\n const io = new IntersectionObserver(ioCallback, { threshold: [0.3] });\n io.observe(node);\n window.addEventListener('scroll', onScroll, { passive: true });\n\n cleanups.push(() => {\n io.disconnect();\n window.removeEventListener('scroll', onScroll);\n clearHesitation();\n });\n }\n\n // --- Tab loss: tab hidden within 15s of variant_assigned ---\n // Document-level (not node-scoped), so callers that attach detectors to many\n // nodes at once (e.g. the snippet's per-option slot signals) can opt out on all\n // but one node to avoid emitting a duplicate tab_loss per node (audit M5).\n if (options?.tabLoss !== false) {\n let firedOnce = false;\n const assignedAt = variantAssignedAt ?? Date.now();\n\n const onVisibility = (): void => {\n if (firedOnce) return;\n if (document.visibilityState !== 'hidden') return;\n const elapsed = Date.now() - assignedAt;\n if (elapsed < 15_000) {\n firedOnce = true;\n emit('tab_loss', { timeOnPage: elapsed });\n }\n };\n\n document.addEventListener('visibilitychange', onVisibility);\n cleanups.push(() => document.removeEventListener('visibilitychange', onVisibility));\n }\n\n return () => {\n for (const c of cleanups) c();\n };\n}\n","/** In-memory context graph with persistence and backend sync. */\n\nimport { storageSuffix } from './storage-key.js';\n\nexport type PageNode = {\n id: string;\n componentId: string;\n semanticType: string;\n answers: string[];\n prominenceScore: number;\n depth: number;\n};\n\nexport type GraphSnapshot = {\n pageNodes: PageNode[];\n capturedAt: number;\n};\n\nexport type GraphConfig = {\n syncUrl?: string;\n apiKey?: string;\n projectId?: string;\n sessionId?: string;\n};\n\nexport type StructuralEdge = {\n fromComponentId: string;\n toComponentId: string;\n weight: number;\n};\n\nexport type GraphClient = {\n addPageNode(node: PageNode): void;\n /** Record a DOM-derived parent/child or sibling relationship between two components. */\n addStructuralEdge(edge: StructuralEdge): void;\n /** One-shot batch sync of all current page nodes to the backend. */\n syncOnce(): void;\n snapshot(): GraphSnapshot;\n destroy(): void;\n};\n\n// _snt_graph_edges was written by earlier builds but never synced to the backend.\n// We still read and clear any leftover key on init/destroy so old clients don't\n// accumulate stale data, but we no longer write it.\nconst STALE_EDGES_KEY = '_snt_graph_edges';\n\nconst SEMANTIC_NEIGHBOURS: Record<string, string[]> = {\n pricing: ['features', 'faq'],\n features: ['pricing'],\n faq: ['pricing'],\n social_proof: ['cta'],\n cta: ['social_proof', 'hero', 'trust'],\n hero: ['cta'],\n comparison: ['pricing'],\n trust: ['cta'],\n};\n\nfunction neighboursFor(semanticType: string): string[] {\n return SEMANTIC_NEIGHBOURS[semanticType] ?? [];\n}\n\nfunction readStorage<T>(key: string, fallback: T): T {\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n}\n\nfunction writeStorage(key: string, value: unknown): void {\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n /* ignore */\n }\n}\n\nconst VALID_SEMANTIC_TYPES = new Set([\n 'pricing', 'hero', 'social_proof', 'cta', 'features',\n 'faq', 'comparison', 'trust', 'navigation', 'generic',\n]);\n\nfunction toValidSemanticType(type: string): string {\n return VALID_SEMANTIC_TYPES.has(type) ? type : 'generic';\n}\n\n/**\n * Path-only page URL for graph sync — strips query + fragment so tokens,\n * emails, and other sensitive URL params never leave the browser by default.\n */\nexport function sanitizePageUrl(href: string): string {\n try {\n const u = new URL(href);\n return `${u.origin}${u.pathname}`;\n } catch {\n return '/';\n }\n}\n\nfunction contentHashOf(componentId: string, semanticType: string, answers: string[]): string {\n const input = `${componentId}:${semanticType}:${answers.join(',')}`;\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) & 0xffffffff;\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Creates an in-memory graph client with optional persistence and sync.\n */\nexport function createGraphClient(config?: GraphConfig): GraphClient {\n const pageNodes = new Map<string, PageNode>();\n const structuralEdges = new Map<string, StructuralEdge>();\n\n // Per-project localStorage key so two projects on one origin don't share a\n // graph-node cache (see storage-key.ts). Legacy `_snt_graph_nodes` with no key.\n const nodesKey = `_snt_graph_nodes${storageSuffix(config?.apiKey)}`;\n\n const persist = (): void => {\n if (typeof window === 'undefined') return;\n writeStorage(nodesKey, [...pageNodes.values()]);\n };\n\n // serialize()/restore() were removed here: persisted-node restoration moved\n // into this constructor (below), leaving both dead — yet still shipping in\n // the graph bundle.\n if (typeof window !== 'undefined') {\n const storedNodes = readStorage<PageNode[]>(nodesKey, []);\n for (const node of storedNodes) {\n pageNodes.set(node.componentId, node);\n }\n // Clear any stale edge data written by older SDK versions.\n try { localStorage.removeItem(STALE_EDGES_KEY); } catch { /* ignore */ }\n }\n\n return {\n addPageNode(node: PageNode): void {\n pageNodes.set(node.componentId, node);\n persist();\n },\n\n addStructuralEdge(edge: StructuralEdge): void {\n const key = `${edge.fromComponentId}->${edge.toComponentId}`;\n structuralEdges.set(key, edge);\n },\n\n syncOnce(): void {\n if (!config?.syncUrl || typeof window === 'undefined') return;\n const nodes = [...pageNodes.values()];\n if (nodes.length === 0) return;\n try {\n // Build semantic edges from the SEMANTIC_NEIGHBOURS map. Each node emits\n // an edge to every present sibling that matches one of its neighbour types.\n // Weight matches propagate()'s 0.4 attention factor; confidence is high\n // because the mapping is curated, not inferred.\n const nodesByType = new Map<string, PageNode[]>();\n for (const n of nodes) {\n const list = nodesByType.get(n.semanticType) ?? [];\n list.push(n);\n nodesByType.set(n.semanticType, list);\n }\n const edges: Array<{\n fromComponentId: string;\n toComponentId: string;\n type: 'semantic' | 'structural';\n weight: number;\n confidence: number;\n }> = [];\n const seen = new Set<string>();\n for (const source of nodes) {\n for (const neighbourType of neighboursFor(source.semanticType)) {\n const targets = nodesByType.get(neighbourType) ?? [];\n for (const target of targets) {\n if (target.componentId === source.componentId) continue;\n const key = `semantic:${source.componentId}->${target.componentId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({\n fromComponentId: source.componentId,\n toComponentId: target.componentId,\n type: 'semantic',\n weight: 0.4,\n confidence: 0.9,\n });\n }\n }\n }\n\n // Structural edges from DOM relationships, recorded during scan.\n // Only emit when both endpoints are present in this page's node set —\n // a structural edge to/from a node that no longer exists would dangle.\n const componentIds = new Set(nodes.map((n) => n.componentId));\n for (const edge of structuralEdges.values()) {\n if (!componentIds.has(edge.fromComponentId) || !componentIds.has(edge.toComponentId)) continue;\n const key = `structural:${edge.fromComponentId}->${edge.toComponentId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({\n fromComponentId: edge.fromComponentId,\n toComponentId: edge.toComponentId,\n type: 'structural',\n weight: edge.weight,\n confidence: 1.0,\n });\n }\n\n const payload = {\n pageUrl: sanitizePageUrl(window.location.href),\n // Visitor/project attribution. The /graph/sync handler ignores unknown\n // top-level fields (Fastify additionalProperties + Zod strips extras),\n // so these ride along harmlessly and let beacons carry attribution.\n ...(config.sessionId ? { sessionId: config.sessionId } : {}),\n ...(config.projectId ? { projectId: config.projectId } : {}),\n nodes: nodes.map((n) => {\n const semanticType = toValidSemanticType(n.semanticType);\n return {\n componentId: n.componentId,\n semanticType,\n answers: n.answers,\n contentHash: contentHashOf(n.componentId, semanticType, n.answers),\n prominenceScore: n.prominenceScore,\n depthInPage: n.depth,\n };\n }),\n edges,\n };\n fetch(config.syncUrl, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n },\n body: JSON.stringify(payload),\n }).catch(() => undefined);\n } catch {\n /* ignore */\n }\n },\n\n snapshot(): GraphSnapshot {\n return {\n pageNodes: [...pageNodes.values()],\n capturedAt: Date.now(),\n };\n },\n\n destroy(): void {\n if (typeof window === 'undefined') return;\n try {\n localStorage.removeItem(nodesKey);\n localStorage.removeItem(STALE_EDGES_KEY);\n } catch {\n /* ignore */\n }\n },\n };\n}\n","import type { CompoundLocator } from './snapshot.js';\n\n// Live-DOM twin of apps/api/src/domain/locator-from-html.ts. The two MUST stay\n// in step: section_key is a hash of this object, so any divergence silently\n// splits one physical section into two identities — a crawl-derived one and a\n// client-derived one — and every per-section number downstream halves.\n// Cross-implementation parity is locked by apps/api/src/domain/locator-parity.test.ts.\n// data-sentient-id FIRST (added 2026-09-05, both generators together): it is\n// the one attribute a site authors specifically to name a section for us, and\n// it is exactly what hydration and redesigns do NOT rewrite. Elements with a\n// unique `id` are unaffected (id outranks data attrs); for the rest the change\n// moves section_key once — ingest-time fingerprint reconciliation\n// (section-key-reconcile.ts) writes the alias, and no offline backfill is\n// possible because pre-change locators never captured the attribute.\nconst STABLE_DATA_ATTRS = ['data-sentient-id', 'data-testid', 'data-test', 'data-id', 'data-name', 'data-cy'];\nconst FINGERPRINT_TEXT_MAX = 40;\n\nfunction normalizedText(el: Element): string {\n return (el.textContent ?? '').replace(/\\s+/g, ' ').trim();\n}\n\nfunction fingerprintOf(el: Element): { tag: string; text: string } {\n return {\n tag: el.tagName.toLowerCase(),\n text: normalizedText(el).slice(0, FINGERPRINT_TEXT_MAX),\n };\n}\n\nfunction cssEscape(v: string): string {\n return v.replace(/[\"\\\\\\]]/g, '\\\\$&');\n}\n\nfunction unique(root: ParentNode, selector: string): boolean {\n try {\n return root.querySelectorAll(selector).length === 1;\n } catch {\n return false;\n }\n}\n\n/** Shortest unique selector: tag, tag.class, then parent-qualified, then nth-of-type chain. */\nfunction uniqueSelector(el: Element, root: ParentNode): string | null {\n const tag = el.tagName.toLowerCase();\n if (!tag) return null;\n const classes = (el.getAttribute('class') ?? '').split(/\\s+/).filter((c) => /^[a-zA-Z][\\w-]*$/.test(c));\n const candidates = [tag, ...classes.map((c) => `${tag}.${c}`)];\n for (const c of candidates) if (unique(root, c)) return c;\n const parent = el.parentElement;\n if (parent && (parent as ParentNode) !== root) {\n const parentSel = uniqueSelector(parent, root);\n if (parentSel) {\n for (const c of candidates) {\n const combined = `${parentSel} > ${c}`;\n if (unique(root, combined)) return combined;\n }\n // Element-only children: matches the server's rawTagName filter over\n // childNodes, which excludes text nodes.\n const siblings = Array.from(parent.children).filter((n) => n.tagName.toLowerCase() === tag);\n const idx = siblings.indexOf(el);\n // An nth-of-type selector is only trustworthy when it pairs with a\n // fingerprint that could actually catch drift: if every same-tag sibling\n // has identical text, the node is a true duplicate and the {tag, text}\n // check can never distinguish \"still the right one\" from \"DOM reordered,\n // now pointing at the wrong duplicate\". Refuse it rather than return a\n // false sense of precision.\n const distinguishable = siblings.some((s) => s !== el && normalizedText(s) !== normalizedText(el));\n if (idx >= 0 && distinguishable) {\n const nth = `${parentSel} > ${tag}:nth-of-type(${idx + 1})`;\n if (unique(root, nth)) return nth;\n }\n }\n }\n return null;\n}\n\n/** Build a compound locator for a live DOM element. Returns null when nothing\n * resolves uniquely — the runtime never guesses an identity. */\nexport function locatorFromElement(el: Element, root: ParentNode): CompoundLocator | null {\n const fingerprint = fingerprintOf(el);\n const id = el.getAttribute('id');\n if (id && unique(root, `#${cssEscape(id)}`)) return { v: 1, id, fingerprint };\n for (const name of STABLE_DATA_ATTRS) {\n const value = el.getAttribute(name);\n if (value && unique(root, `[${name}=\"${cssEscape(value)}\"]`)) {\n return { v: 1, dataAttr: { name, value }, fingerprint };\n }\n }\n const selector = uniqueSelector(el, root);\n if (selector) return { v: 1, selector, fingerprint };\n return null;\n}\n","// Semantic section classification for no-code section capture (Phase 3 §2.4).\n// Pure heuristic: element → one of the graph_nodes semantic_type enum. Mirrors\n// the SDK graph scanner's vocabulary so the persona × section matrix consumes\n// snippet-captured sections unchanged.\n//\n// SPLIT (Phase 2b, spec 2026-09-04). This module is the BROWSER-SAFE core:\n// structural rules, the small parent-level keyword table, and the fallbacks.\n// The rich ~40-topic vocabulary lives in ./topics.ts, which only the server\n// imports — shipping it here cost 1.5 KB gzip in every bundle (7% of the\n// always-on snippet) for a layer the browser never reads, since classifySection\n// returns only the parent.\n//\n// Both paths share structuralTopicOf/fallbackTopicOf, so the three fixes below\n// apply identically on client and server; only vocabulary richness differs.\n//\n// Fixes, each reproduced against the shipped classifier before being changed:\n// 1. structural before the converter fallback — `<div class=\"navbar\">` used\n// to become `cta`, because `navigation` was reachable only via tag\n// nav/footer.\n// 2. hero before the converter fallback — the cta rule sat above both hero\n// rules, so a `<header>` with a button and <200 chars could never be hero.\n// 3. tightened pricing/social keywords — `plans?` and `customers?` fired on\n// unrelated copy AND were `strong`, so they auto-applied at confidence 0.9\n// and were never sent to the LLM fallback.\n\nexport type SemanticType =\n | 'pricing' | 'hero' | 'social_proof' | 'cta' | 'features'\n | 'faq' | 'comparison' | 'trust' | 'navigation' | 'generic';\n\nexport type SectionRole = 'converter' | 'persuader' | 'structural';\n\n/** Canonical vocabulary — mirrors the graph_nodes semantic_type CHECK enum. */\nexport const SEMANTIC_TYPES: readonly SemanticType[] = [\n 'pricing', 'hero', 'social_proof', 'cta', 'features',\n 'faq', 'comparison', 'trust', 'navigation', 'generic',\n] as const;\n\nexport type TopicRule = { topic: string; parent: SemanticType; role: SectionRole };\n\n/** Environment-agnostic section features — buildable from a browser Element or\n * a server-parsed node (node-html-parser). */\nexport type SectionFeatures = {\n tag: string; // lowercase tag name\n idClass: string; // `${id} ${className}`\n headingText: string;\n bodyText: string; // normalized text content, first 2000 chars\n actionCount: number;\n textLength: number;\n /** ARIA landmark role, when the element declares one. Optional so every\n * existing caller keeps compiling; a page that uses landmarks gives a\n * high-precision signal for free, which the old classifier ignored — it\n * tested membership of SEMANTIC_TYPES, so only role=\"navigation\" ever hit\n * and role=\"banner\"/\"contentinfo\" were discarded. */\n ariaRole?: string;\n /** schema.org `@type` values found in `<script type=\"application/ld+json\">`\n * INSIDE this section. Highest-precision signal available and free to\n * collect — the crawler already has the HTML. Type-only here; the\n * `@type` → topic table is server-side in ./topics.ts, since the browser\n * never reads the topic layer and the snippet has ~200 bytes of margin. */\n structuredTypes?: string[];\n};\n\n// ARIA landmark → topic. Structural facts, not guesses.\nconst ARIA_TOPIC: Record<string, string> = {\n banner: 'hero',\n navigation: 'navigation',\n contentinfo: 'footer',\n};\n\n/**\n * Structural identification, shared by the browser and server classifiers.\n * Returns a topic name, or null when the section is not structural furniture.\n * Everything matched here is definitive (tag or authored marker), so callers\n * treat it as `strong`.\n */\nexport function structuralTopicOf(f: SectionFeatures): string | null {\n const aria = f.ariaRole ? ARIA_TOPIC[f.ariaRole.toLowerCase()] : undefined;\n if (aria) return aria;\n if (f.tag === 'nav') return 'navigation';\n if (f.tag === 'footer') return 'footer';\n // A page-level <header> IS the banner landmark (HTML-AAM maps it to\n // role=\"banner\", which this function already treats as hero), so it is a\n // structural fact rather than a keyword guess. Without this, a header whose\n // headline happens to contain a content word loses to the keyword table:\n // \"We build brands that move\" scored social_proof and \"Winter collection\"\n // scored features, purely on words inside the hero copy.\n //\n // …EXCEPT when the header IS the site's navigation. The hold-out corpus\n // (2026-09-05, six real unseen sites) showed most real pages wrap their nav\n // in <header> — mega-menus with 10–114 links and almost no prose — and\n // hero-typing those cost the classifier a 0.11 hero precision. A hero header\n // carries a headline and one or two calls to action; a nav header is\n // link-dominated, so the action count is the discriminator. Measured on the\n // hold-out: nav headers had 10+ actions (one at 4), authored hero headers\n // have 0–2.\n if (f.tag === 'header') return f.actionCount >= 5 ? 'navigation' : 'hero';\n const hay = `${f.idClass} ${f.headingText}`.toLowerCase();\n if (/\\b(navbar|nav-bar|navigation|site-nav|main-nav|topbar|footer)\\b/.test(hay)) {\n return /footer/.test(hay) ? 'footer' : 'navigation';\n }\n // Explicit authoring marker beats any inferred keyword, and is matched on\n // idClass ONLY: a `<header class=\"hero\">` headlined \"Expert Car Repair\" is a\n // hero, not a services section — but matching \"hero\" in heading text would\n // also catch \"Hero of the story\".\n if (/\\b(hero|masthead|jumbotron)\\b/i.test(f.idClass)) return 'hero';\n return null;\n}\n\n/**\n * Last-resort classification once no keyword or content evidence matched.\n * Hero is checked BEFORE the converter fallback — fix 2 above.\n */\nexport function fallbackTopicOf(f: SectionFeatures): string {\n // `tag === 'header'` is handled in structuralTopicOf, which runs first.\n if (f.actionCount >= 1 && f.textLength > 0 && f.textLength < 200) return 'cta';\n return 'generic';\n}\n\n// Parent-level keyword table for the BROWSER path. Deliberately close to the\n// original size — the rich topic vocabulary is in ./topics.ts. `plans?` now\n// requires adjacent pricing context, and bare `customers?` is gone (it fired on\n// navigation furniture like a \"Customer Service\" footer block).\nconst KEYWORDS: Array<[SemanticType, RegExp]> = [\n ['pricing', /\\b(pricing|price list|per month|\\/mo|subscriptions?)\\b|\\bplans?\\b(?=[^.]{0,40}(from|start|month|year|[$€£]))/i],\n ['faq', /\\bfaq\\b|frequently asked|common questions?/i],\n ['comparison', /\\b(compare|comparison|versus)\\b|\\bvs\\./i],\n ['social_proof', /\\b(reviews?|ratings?|testimonial|brands?|galler(y|ies)|logos)\\b|trusted by|loved by|case stud|what our customers say/i],\n ['trust', /\\b(insurance|warrant(y|ies)|guarantees?|certifi|accredit|security|privacy|compliance|gdpr|encrypt)\\b|why choose|about us|our team/i],\n ['cta', /\\b(book|booking|reserve|appointments?|newsletter|subscribe)\\b|contact us|get in touch|opening hours/i],\n ['features', /\\b(features?|benefits?|capabilit|services?|repairs?|menus?|products?)\\b|how it works|our process|what we (do|offer)|what you get/i],\n];\n\n// Content-evidence patterns — run against bodyText when the heading gave us\n// nothing. Deliberately conservative: pricing needs per-period/plan context next\n// to money so an article mentioning \"$5 million\" stays generic.\nexport const CONTENT_PATTERNS: Array<[SemanticType, RegExp]> = [\n ['pricing', /(?:[$€£]\\s?\\d[\\d,.]*\\s*(?:\\/|per\\s)\\s*(?:mo|month|yr|year|seat|user))|(?:\\b(?:starter|basic|pro|growth|premium|enterprise)\\b[^.]{0,60}[$€£]\\s?\\d)/i],\n ['social_proof', /(?:★{2,})|(?:\\b\\d(?:\\.\\d)?\\s*(?:out of|\\/)\\s*5\\b)|(?:\\brated\\b)|(?:[\"“][^\"”]{20,160}[\"”]\\s*[—–-]\\s*[A-Z][a-z]+)/],\n ['trust', /\\b(?:money[- ]back guarantee|free returns?|returns? within|refund(?:s)? within|encrypted|soc ?2|iso ?27001|gdpr[- ]compliant|cancel anytime)\\b/i],\n ['comparison', /\\b(?:vs|versus)\\b\\.?[^.!?]{0,80}\\b(?:compare|comparison|plans?|features?|alternative)\\b|\\bhow (?:we|it) compares?\\b/i],\n];\n\n/** Parent for the handful of topics the shared structural/fallback helpers\n * emit. The server maps the full vocabulary via CLASSIFIER_TOPICS instead. */\nconst SHARED_TOPIC_PARENT: Record<string, SemanticType> = {\n navigation: 'navigation',\n footer: 'navigation',\n hero: 'hero',\n cta: 'cta',\n generic: 'generic',\n};\n\nfunction headingText(el: Element): string {\n const h = el.querySelector('h1, h2, h3');\n return (h?.textContent ?? '').slice(0, 160);\n}\n\n/**\n * Pure classification over extracted features. `strong` = keyword, content, or\n * structural evidence (trustable enough to auto-apply); `weak` = a fallback\n * guess (hero/cta/generic — capture-worthy but not persona evidence).\n */\nexport function classifyFeatures(f: SectionFeatures): { type: SemanticType; strength: 'strong' | 'weak' } {\n const structural = structuralTopicOf(f);\n if (structural) return { type: SHARED_TOPIC_PARENT[structural] ?? 'generic', strength: 'strong' };\n\n const hay = `${f.idClass} ${f.headingText}`.toLowerCase();\n for (const [type, re] of KEYWORDS) if (re.test(hay)) return { type, strength: 'strong' };\n for (const [type, re] of CONTENT_PATTERNS) if (re.test(f.bodyText)) return { type, strength: 'strong' };\n\n const fb = fallbackTopicOf(f);\n return { type: SHARED_TOPIC_PARENT[fb] ?? 'generic', strength: 'weak' };\n}\n\n/** Feature extraction from a live DOM element (browser paths). */\nexport function featuresFromElement(el: Element): SectionFeatures {\n const text = (el.textContent ?? '').replace(/\\s+/g, ' ').trim();\n const role = el.getAttribute('role');\n return {\n tag: el.tagName.toLowerCase(),\n idClass: `${el.id} ${String(el.className ?? '')}`,\n headingText: headingText(el),\n bodyText: text.slice(0, 2000),\n actionCount: el.querySelectorAll('a, button, [role=\"button\"]').length,\n textLength: text.length,\n ...(role ? { ariaRole: role } : {}),\n };\n}\n\n/** Classify a page section into a semantic type (never null — falls back to\n * 'generic' so the caller can still capture attention on it). */\nexport function classifySection(el: Element): SemanticType {\n return classifyFeatures(featuresFromElement(el)).type;\n}\n","/** Reads the rendered DOM to build the page-side context graph. */\n\nimport { classifySection, SEMANTIC_TYPES, type SemanticType } from './engagement/classify';\nimport { locatorFromElement } from './locator-from-dom.js';\nimport type { CompoundLocator } from './snapshot.js';\n\nexport type ScannedNode = {\n componentId: string;\n semanticType: string;\n ariaLabel?: string;\n headingText?: string;\n isAboveFold: boolean;\n prominenceScore: number;\n depth: number;\n reactComponentName?: string;\n dataAttributes: Record<string, string>;\n /** Compound locator for this element. The server hashes it into section_key —\n * the identity the crawler and the snippet resolve to for the same physical\n * section. Undefined when nothing resolves uniquely: the runtime never\n * guesses an identity it could not verify later. */\n locator?: CompoundLocator;\n};\n\nexport type StructuralEdge = {\n fromComponentId: string;\n toComponentId: string;\n /** 0.6 for direct parent → child, 0.3 for sibling (both directions emitted). */\n weight: number;\n};\n\nexport type ScanResult = {\n nodes: ScannedNode[];\n edges: StructuralEdge[];\n scannedAt: number;\n};\n\nexport type ContentAddedEvent = {\n nodes: ScannedNode[];\n edges: StructuralEdge[];\n addedAt: number;\n};\n\nexport type DOMScanner = {\n scan(): Promise<ScanResult>;\n observe(onContentAdded: (event: ContentAddedEvent) => void): void;\n getProminenceScore(element: Element): number;\n destroy(): void;\n};\n\n// ASIDE included to match the initial scan (which queries `section, article,\n// main, aside`) — this set omitted it, so a server-rendered aside was captured\n// while an identical dynamically-inserted one was silently ignored.\nconst OBSERVE_TAGS = new Set(['SECTION', 'ARTICLE', 'MAIN', 'DIV', 'ASIDE']);\nconst HEADING_SELECTOR = 'h1, h2, h3';\n// Selector mirror of the initial scan's criteria (collectNodesAndEdges): any\n// element carrying a declared id, plus structural tags with an aria-label.\n// Used to walk INTO inserted subtrees — a SPA mounts ONE root node whose\n// interesting sections are all descendants, and inspecting only the root made\n// every framework-mounted component permanently invisible to graph capture.\nconst SUBTREE_SELECTOR =\n '[data-sentient-id], section[aria-label], article[aria-label], main[aria-label], aside[aria-label]';\n\n// Sibling detection is O(n²) per group and emits two edges per pair, so a page\n// with hundreds of co-located components (e.g. a 200-cell product grid) would\n// otherwise emit tens of thousands of low-signal sibling edges. We cap the\n// sibling fan-out per group and the total structural edges per detection pass.\n// The total stays well under the server's 2000-edge-per-sync limit, leaving room\n// for semantic edges. Parent→child edges (higher signal) are emitted first, so\n// the cap sheds sibling edges before it ever touches them.\nconst MAX_SIBLINGS_PER_GROUP = 30;\nconst MAX_STRUCTURAL_EDGES = 1500;\n\nconst SSR_SCANNER: DOMScanner = {\n scan: async () => ({ nodes: [], edges: [], scannedAt: 0 }),\n observe: () => undefined,\n getProminenceScore: () => 0,\n destroy: () => undefined,\n};\n\nfunction normalize(value: number, min: number, max: number): number {\n if (max <= min) return 0;\n return Math.max(0, Math.min(1, (value - min) / (max - min)));\n}\n\nfunction readReactComponentName(element: Element): string | undefined {\n try {\n const record = element as unknown as Record<string, unknown>;\n for (const key of Object.keys(record)) {\n if (!key.startsWith('__reactFiber') && !key.startsWith('__reactInternalInstance')) {\n continue;\n }\n const fiber = record[key] as { type?: { name?: string; displayName?: string } };\n const name = fiber?.type?.displayName ?? fiber?.type?.name;\n if (name && name.length > 1) {\n return name;\n }\n }\n } catch {\n /* ignore */\n }\n return undefined;\n}\n\nfunction extractDataAttributes(element: Element): Record<string, string> {\n const attrs: Record<string, string> = {};\n for (const attr of Array.from(element.attributes)) {\n if (attr.name.startsWith('data-')) {\n attrs[attr.name] = attr.value;\n }\n }\n return attrs;\n}\n\nconst SEMANTIC_SET: ReadonlySet<string> = new Set(SEMANTIC_TYPES);\n\n// Explicit tag wins → role → heuristic → generic. Every output is normalized to\n// the graph_nodes enum: an out-of-vocabulary data-sentient-type/role falls\n// through to the heuristic instead of being emitted raw (raw values violated\n// the graph_nodes CHECK constraint on insert).\nfunction inferSemanticType(element: Element): SemanticType {\n const explicit = element.getAttribute('data-sentient-type');\n if (explicit && SEMANTIC_SET.has(explicit)) return explicit as SemanticType;\n const role = element.getAttribute('role');\n if (role && SEMANTIC_SET.has(role)) return role as SemanticType;\n return classifySection(element);\n}\n\n// djb2 over a string → 8-hex-char digest. Stable and collision-resistant enough\n// to disambiguate co-located components that declare no id of their own.\nfunction shortHash(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) & 0xffffffff;\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\n// A structural path (tag + sibling index at each level up to the root) uniquely\n// and stably identifies an element's DOM position, so two id-less sections don't\n// hash to the same value the way a bare tagName would.\nfunction domPathSignature(element: Element): string {\n const parts: string[] = [];\n let current: Element | null = element;\n while (current) {\n const parent: Element | null = current.parentElement;\n if (!parent) {\n parts.push(current.tagName.toLowerCase());\n break;\n }\n const index = Array.prototype.indexOf.call(parent.children, current);\n parts.push(`${current.tagName.toLowerCase()}[${index}]`);\n current = parent;\n }\n return parts.reverse().join('/');\n}\n\n// Prefer an author-declared id. When none exists, synthesize a stable, unique id\n// from the element's DOM path — the old `tagName.toLowerCase()` fallback gave\n// every id-less <section> the same \"section\" id, so a second such node silently\n// overwrote the first in the componentId-keyed graph Map (node loss).\nfunction componentIdFor(element: Element): string {\n const declared =\n element.getAttribute('data-sentient-id') ?? element.getAttribute('id');\n if (declared) return declared;\n return `${element.tagName.toLowerCase()}-${shortHash(domPathSignature(element))}`;\n}\n\nfunction depthOf(element: Element): number {\n let depth = 0;\n let current: Element | null = element.parentElement;\n while (current) {\n depth++;\n current = current.parentElement;\n }\n return depth;\n}\n\nfunction scanElement(\n element: Element,\n getProminenceScore: (el: Element) => number,\n): ScannedNode {\n const heading = element.querySelector(HEADING_SELECTOR);\n return {\n componentId: componentIdFor(element),\n semanticType: inferSemanticType(element),\n ariaLabel: element.getAttribute('aria-label') ?? undefined,\n headingText: heading?.textContent?.trim() ?? undefined,\n isAboveFold: element.getBoundingClientRect().top < window.innerHeight,\n prominenceScore: getProminenceScore(element),\n depth: depthOf(element),\n reactComponentName: readReactComponentName(element),\n dataAttributes: extractDataAttributes(element),\n locator: locatorFromElement(element, element.ownerDocument) ?? undefined,\n };\n}\n\n/**\n * Detects structural relationships between scanned component elements:\n * - parent → child (direct ancestor link, weight 0.6)\n * - sibling ↔ sibling (share nearest component ancestor, weight 0.3, bidirectional)\n * A pair appears at most once per direction.\n */\nfunction detectStructuralEdges(elementToId: Map<Element, string>): StructuralEdge[] {\n const edges: StructuralEdge[] = [];\n const seen = new Set<string>();\n const ROOT = '__root__';\n\n // Parent → child: nearest component ancestor only.\n const groups = new Map<string, Element[]>();\n for (const [el, _id] of elementToId) {\n let ancestor: Element | null = el.parentElement;\n let groupKey = ROOT;\n while (ancestor) {\n if (elementToId.has(ancestor)) {\n groupKey = elementToId.get(ancestor)!;\n const childId = elementToId.get(el)!;\n const key = `${groupKey}->${childId}`;\n if (!seen.has(key) && groupKey !== childId) {\n seen.add(key);\n edges.push({ fromComponentId: groupKey, toComponentId: childId, weight: 0.6 });\n }\n break;\n }\n ancestor = ancestor.parentElement;\n }\n const siblings = groups.get(groupKey) ?? [];\n siblings.push(el);\n groups.set(groupKey, siblings);\n }\n\n // Sibling ↔ sibling: same ancestor group.\n for (const sibs of groups.values()) {\n if (sibs.length < 2) continue;\n // Cap the fan-out of any single group before the O(n²) pairing.\n const capped = sibs.length > MAX_SIBLINGS_PER_GROUP ? sibs.slice(0, MAX_SIBLINGS_PER_GROUP) : sibs;\n for (let i = 0; i < capped.length; i++) {\n for (let j = i + 1; j < capped.length; j++) {\n if (edges.length >= MAX_STRUCTURAL_EDGES) return edges;\n const aId = elementToId.get(capped[i]!)!;\n const bId = elementToId.get(capped[j]!)!;\n if (aId === bId) continue;\n const fwd = `${aId}->${bId}::sib`;\n const rev = `${bId}->${aId}::sib`;\n if (!seen.has(fwd)) {\n seen.add(fwd);\n edges.push({ fromComponentId: aId, toComponentId: bId, weight: 0.3 });\n }\n if (!seen.has(rev)) {\n seen.add(rev);\n edges.push({ fromComponentId: bId, toComponentId: aId, weight: 0.3 });\n }\n }\n }\n }\n\n return edges;\n}\n\nfunction collectNodesAndEdges(\n getProminenceScore: (el: Element) => number,\n): { nodes: ScannedNode[]; edges: StructuralEdge[]; elementToId: Map<Element, string> } {\n const nodes: ScannedNode[] = [];\n const seen = new Set<Element>();\n const elementToId = new Map<Element, string>();\n\n const registered = document.querySelectorAll('[data-sentient-id]');\n registered.forEach((el) => {\n if (el instanceof Element && !seen.has(el)) {\n seen.add(el);\n const node = scanElement(el, getProminenceScore);\n nodes.push(node);\n elementToId.set(el, node.componentId);\n }\n });\n\n const structural = document.querySelectorAll('section, article, main, aside');\n structural.forEach((el) => {\n if (!(el instanceof Element) || seen.has(el)) return;\n const hasAria = el.hasAttribute('aria-label');\n const hasSentientId = el.hasAttribute('data-sentient-id');\n if (!hasAria && !hasSentientId) return;\n seen.add(el);\n const node = scanElement(el, getProminenceScore);\n nodes.push(node);\n elementToId.set(el, node.componentId);\n });\n\n return { nodes, edges: detectStructuralEdges(elementToId), elementToId };\n}\n\n/**\n * Creates a DOM scanner that uses idle callbacks and mutation observation.\n */\nexport function createDOMScanner(): DOMScanner {\n if (typeof window === 'undefined') {\n return SSR_SCANNER;\n }\n\n let observer: MutationObserver | null = null;\n let idleCallbackId = 0;\n let contentCallback: ((event: ContentAddedEvent) => void) | null = null;\n // Element→componentId for every node registered so far (initial scan + prior\n // mutations). The MutationObserver detects edges against this whole set — not\n // just the nodes in the current mutation — so a child inserted under an\n // already-scanned parent still gets its parent→child edge.\n const knownElementToId = new Map<Element, string>();\n\n const getProminenceScore = (element: Element): number => {\n try {\n const styles = window.getComputedStyle(element);\n const fontSize = parseFloat(styles.fontSize) || 12;\n const zIndex = parseFloat(styles.zIndex) || 0;\n const rect = element.getBoundingClientRect();\n const distanceFromTop = Math.max(rect.top, 0);\n const viewportHeight = window.innerHeight || 1;\n const inverseDistance = 1 / (distanceFromTop / viewportHeight + 1);\n\n // inverseDistance is already in (0, 1] by construction, so it needs no\n // further normalization — only fontSize and zIndex are rescaled.\n const score =\n normalize(fontSize, 12, 48) * 0.4 +\n inverseDistance * 0.4 +\n normalize(zIndex, 0, 100) * 0.2;\n\n return Math.max(0, Math.min(1, score));\n } catch {\n return 0.5;\n }\n };\n\n const scan = (): Promise<ScanResult> =>\n new Promise((resolve) => {\n const run = (): void => {\n const { nodes, edges, elementToId } = collectNodesAndEdges(getProminenceScore);\n // Seed the known-element registry so the observer can resolve parents that\n // were registered in this scan when later children are inserted.\n knownElementToId.clear();\n for (const [el, id] of elementToId) knownElementToId.set(el, id);\n resolve({ nodes, edges, scannedAt: Date.now() });\n };\n\n try {\n if (typeof requestIdleCallback === 'function') {\n idleCallbackId = requestIdleCallback(run, { timeout: 100 });\n } else {\n run();\n }\n } catch {\n run();\n }\n });\n\n const observe = (onContentAdded: (event: ContentAddedEvent) => void): void => {\n contentCallback = onContentAdded;\n try {\n observer = new MutationObserver((mutations) => {\n const added: ScannedNode[] = [];\n const addedIds = new Set<string>();\n for (const mutation of mutations) {\n if (mutation.type !== 'childList') continue;\n mutation.addedNodes.forEach((node) => {\n if (!(node instanceof Element)) return;\n const candidates: Element[] = [];\n if (\n OBSERVE_TAGS.has(node.tagName) &&\n (node.hasAttribute('data-sentient-id') || node.hasAttribute('aria-label'))\n ) {\n candidates.push(node);\n }\n // Also scan the inserted SUBTREE with the initial scan's criteria\n // (see SUBTREE_SELECTOR): the root of a SPA/framework mount is\n // usually a plain wrapper, and its sections arrive as descendants\n // of ONE childList mutation — inspecting only the root meant they\n // were never captured at all.\n try {\n node.querySelectorAll(SUBTREE_SELECTOR).forEach((el) => candidates.push(el));\n } catch {\n /* exotic host without querySelectorAll — root-only scan stands */\n }\n for (const el of candidates) {\n // Skip elements already registered: a parent and its child can\n // both appear in addedNodes (the child once via the parent's\n // subtree, once directly), which would emit duplicate nodes.\n if (knownElementToId.has(el)) continue;\n const scanned = scanElement(el, getProminenceScore);\n added.push(scanned);\n addedIds.add(scanned.componentId);\n knownElementToId.set(el, scanned.componentId);\n }\n });\n }\n if (added.length === 0 || !contentCallback) return;\n // Drop elements no longer in the document so removed nodes don't dangle\n // and the map stays bounded.\n for (const el of [...knownElementToId.keys()]) {\n if (!el.isConnected) knownElementToId.delete(el);\n }\n // Detect over the FULL known set so a child inserted under an already-\n // scanned parent still gets its parent→child edge, then surface only the\n // edges that touch a newly-added node (existing edges were already emitted).\n const edges = detectStructuralEdges(knownElementToId).filter(\n (e) => addedIds.has(e.fromComponentId) || addedIds.has(e.toComponentId),\n );\n contentCallback({ nodes: added, edges, addedAt: Date.now() });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n } catch {\n /* ignore */\n }\n };\n\n const destroy = (): void => {\n if (observer) {\n observer.disconnect();\n observer = null;\n }\n if (idleCallbackId && typeof cancelIdleCallback === 'function') {\n try {\n cancelIdleCallback(idleCallbackId);\n } catch {\n /* ignore */\n }\n }\n idleCallbackId = 0;\n contentCallback = null;\n knownElementToId.clear();\n };\n\n return {\n scan,\n observe,\n getProminenceScore,\n destroy,\n };\n}\n"],"mappings":"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","names":["index_graph_exports","__export","BLOCK_ALIGNS","BLOCK_EMPHASES","BLOCK_FITS","BLOCK_GAPS","BLOCK_GRID_COLUMNS","BLOCK_HEADING_LEVELS","BLOCK_JUSTIFIES","BLOCK_RATIOS","BLOCK_SIZES","BLOCK_TEXT_ALIGNS","BLOCK_TONES","BLOCK_WEIGHTS","LEGACY_SESSION_COOKIE_NAME","MAX_BLOCK_ARMS","MAX_BLOCK_CHILDREN","MAX_BLOCK_DEPTH","MAX_BLOCK_NODES","MAX_BLOCK_TEXT_LEN","SNAPSHOT_STORAGE_KEY_PREFIX","armOfResult","attachMicroSignalDetectors","baselineResultFor","baselineSlots","deriveSessionSegment","detectDeviceClass","detectTimeOfDay","detectTrafficSource","grantConsent","init","isDoNotTrackEnabled","locatorFromElement","readSnapshot","referrerDomainFromReferer","renderPrePaintScript","sanitizePageUrl","sessionCookieName","toWireSlot","writeSnapshot","__toCommonJS","randomUuidV4","buf","out","i","c","r","storageSuffix","apiKey","LEGACY_SESSION_COOKIE_NAME","sessionCookieName","DEFAULT_COOKIE_NAME","DEFAULT_COOKIE_TTL_DAYS","STORAGE_KEY","generateSessionId","randomUuidV4","readCookie","name","match","e","writeCookie","value","maxAgeSeconds","readLocalStorage","key","writeLocalStorage","readSessionStorage","writeSessionStorage","removeSessionStorage","probeCookieWritable","removeLocalStorage","clearCookie","SSR_MANAGER","initSession","config","_a","_b","_c","_d","_e","_f","_g","suffix","storageSuffix","cookieName","sessionCookieName","storageKey","legacyTombstoneKey","nonEmpty","readLegacy","sessionId","lsOk","cookieOk","ssOk","ephemeral","backoffDelayMs","consecutiveFailures","classifyResponse","res","status","drainBucket","storageKey","max","raw","parsed","e","writeBucket","items","existing","byId","purgeBucket","ids","drop","remaining","MAX_SENT_IDS","KEEPALIVE_BUDGET_BYTES","retryStorageKey","apiKey","SSR_QUEUE","createEventQueue","config","_a","_b","_c","flushIntervalMs","maxBatchSize","maxRetrySize","ingestUrl","RETRY_KEY","queue","sentIds","sentIdOrder","markSent","ids","id","queuedIds","oldest","purgeBucket","capQueue","dropped","enqueue","event","requeueFailed","batch","retryEvents","drainBucket","backoffUntil","consecutiveFailures","transportBatch","salvage","body","e","pending","writeBucket","backoffDelayMs","handleResponse","res","classifyResponse","rest","encoder","byteLength","s","packBatch","pool","out","bytes","size","flush","flushTimerActive","intervalId","onVisibilityChange","onPageHide","MAX_SENT_IDS","goalRetryStorageKey","apiKey","SSR_GOAL_QUEUE","createGoalQueue","config","_a","_b","_c","flushIntervalMs","maxRetrySize","maxPerFlush","RETRY_KEY","pending","pendingIds","sentIds","sentIdOrder","backoffUntil","consecutiveFailures","disposed","markSent","goal","oldest","purgeBucket","markFailed","writeBucket","backoffDelayMs","transport","res","e","handle","r","outcome","classifyResponse","flush","sentThisTick","restored","drainBucket","intervalId","onVisibilityChange","onPageHide","DEFAULT_TTL_MS","cacheKey","componentId","segment","createAssignmentCache","ttlMs","apiKey","memory","keyPrefix","storageSuffix","storageKey","parseStorageKey","key","suffix","sep","e","listStorageKeys","keys","i","isExpired","assignment","raw","parsed","entry","storageK","agentUaList","uaTokenMatch","userAgent","matchedAgentToken","_a","s","token","detectDeviceClass","userAgent","s","detectTrafficSource","referrer","appOrigin","refUrl","e","host","referrerDomainFromReferer","CLICK_ID_KEYS","extractTrackedParams","search","utmParams","clickIds","entries","k","v","first","detectTimeOfDay","d","h","deriveSessionSegment","opts","body","buildSessionUpsertPayload","sessionId","_a","_b","_c","_d","_e","_f","_g","_h","ua","referer","now","uaTokenMatch","import_policy","toWireSlot","d","__spreadValues","dim","values","baselineResultFor","decl","baselineSlots","decls","out","armOfResult","result","SNAPSHOT_STORAGE_KEY_PREFIX","BANDS","readSnapshot","apiKey","raw","p","e","writeSnapshot","snap","renderPrePaintScript","import_policy","import_policy","PROD_KEYLESS_ERROR","LOCAL_MODE_BANNER","bannerShown","prodErrorShown","readPersonaOverrideFromUrl","_a","e","createLocalModeClient","config","session","initSession","sessionId","forcedPersona","modPromise","mod","m","bannerShown","LOCAL_MODE_BANNER","prodErrorShown","PROD_KEYLESS_ERROR","lastOutcome","BAND_CONFIDENCE","seedPersona","__spreadValues","snap","readSnapshot","applyPersonaAttributes","outcome","el","input","_b","_c","__spreadProps","writeSnapshot","slotId","p","componentId","variantIds","BLOCK_GAPS","BLOCK_ALIGNS","BLOCK_JUSTIFIES","BLOCK_SIZES","BLOCK_WEIGHTS","BLOCK_TONES","BLOCK_EMPHASES","BLOCK_TEXT_ALIGNS","BLOCK_RATIOS","BLOCK_FITS","BLOCK_GRID_COLUMNS","BLOCK_HEADING_LEVELS","MAX_BLOCK_NODES","MAX_BLOCK_DEPTH","MAX_BLOCK_CHILDREN","MAX_BLOCK_ARMS","MAX_BLOCK_TEXT_LEN","attachMicroSignalDetectors","emit","node","variantAssignedAt","options","cleanups","firedOnce","timestamps","onClick","now","onCopy","e","sel","selectionLength","isVisible","hesitationTimer","clearHesitation","startHesitation","onScroll","ioCallback","entries","entry","io","assignedAt","onVisibility","elapsed","c","DEFAULT_INGEST_URL","_clients","_lastApiKey","_registerConsentUpgradeInit","apiKey","reinit","entry","isGoalOptions","v","SESSION_UPSERT_RETRIES","generateEventId","randomUuidV4","currentPath","_a","createActionLatch","byEvent","byFlush","flushOpen","alreadyRecorded","map","actionKey","valueKey","values","hasWindowEvent","currentEvent","ev","closeFlushWindow","done","clear","ch","keys","collapsed","emittedPages","startPageviewTracking","client","projectId","markDelivered","h","stopped","last","emit","path","installed","name","orig","wrapper","a","r","landing","SSR_CLIENT","readTrackedParams","extractTrackedParams","deriveBaseUrl","ingestUrl","isDoNotTrackEnabled","grantConsent","key","config","upgrade","fullClient","init","__spreadProps","__spreadValues","disposed","createPreConsentProxy","servesWinner","baseUrl","authHeaders","winnerCache","inflightWinners","inner","componentId","variantIds","_agentData","cached","coalesce","params","res","result","e","proxy","n","m","w","s","c","g","o","u","av","i","setInner","inflight","run","hit","request","_b","_c","_d","_e","_f","_g","_h","_i","prevEntry","dntBlocked","gated","keyValid","createLocalModeClient","resolvedIngestUrl","sessionStart","session","initSession","assignmentCache","createAssignmentCache","eventQueue","createEventQueue","warnedDropStatuses","goalQueue","createGoalQueue","goal","status","deviceClass","detectDeviceClass","appOrigin","trafficSource","detectTrafficSource","sessionSegment","inflightAssigns","inflightDecides","slotStore","personaState","seedSlotBaselines","decls","d","baselineResultFor","slotId","seedSnapshot","readSnapshot","BAND_CONFIDENCE","ds","variantId","sessionReady","sessionId","referrerDomain","referrerDomainFromReferer","utmParams","clickIds","sessionBody","detectTimeOfDay","uaTokenMatch","upsertSession","attempt","classifyResponse","backoffDelayMs","firedThisAction","stopPageviews","pageviewsTornDown","metadataOrOpts","weight","stepIndex","sid","opts","goalId","body","payload","goalType","assignment","slotResult","attributedVariantId","armOfResult","fullEvent","userId","event","segment","agentData","agentDataByVariant","remainingTtlMs","input","declared","id","toWireSlot","decideKey","data","slots","served","prior","baseline","known","writeSnapshot","SNAPSHOT_STORAGE_KEY_PREFIX","retryStorageKey","goalRetryStorageKey","win","STALE_EDGES_KEY","SEMANTIC_NEIGHBOURS","neighboursFor","semanticType","_a","readStorage","key","fallback","raw","e","writeStorage","value","VALID_SEMANTIC_TYPES","toValidSemanticType","type","sanitizePageUrl","href","u","contentHashOf","componentId","answers","input","h","createGraphClient","config","pageNodes","structuralEdges","nodesKey","storageSuffix","persist","storedNodes","node","edge","_b","nodes","nodesByType","n","list","edges","seen","source","neighbourType","targets","target","componentIds","payload","__spreadProps","__spreadValues","STABLE_DATA_ATTRS","normalizedText","el","_a","fingerprintOf","cssEscape","v","unique","root","selector","e","uniqueSelector","tag","classes","c","candidates","parent","parentSel","combined","siblings","n","idx","distinguishable","nth","locatorFromElement","fingerprint","id","name","STABLE_DATA_ATTRS","value","SEMANTIC_TYPES","ARIA_TOPIC","structuralTopicOf","f","aria","hay","fallbackTopicOf","KEYWORDS","CONTENT_PATTERNS","SHARED_TOPIC_PARENT","headingText","el","_a","h","classifyFeatures","_b","structural","type","re","fb","featuresFromElement","text","role","__spreadValues","classifySection","OBSERVE_TAGS","HEADING_SELECTOR","SUBTREE_SELECTOR","MAX_SIBLINGS_PER_GROUP","MAX_STRUCTURAL_EDGES","SSR_SCANNER","normalize","value","min","max","readReactComponentName","element","_a","_b","_c","record","key","fiber","name","e","extractDataAttributes","attrs","attr","SEMANTIC_SET","SEMANTIC_TYPES","inferSemanticType","explicit","role","classifySection","shortHash","input","h","i","domPathSignature","parts","current","parent","index","componentIdFor","declared","depthOf","depth","scanElement","getProminenceScore","_d","heading","locatorFromElement","detectStructuralEdges","elementToId","edges","seen","ROOT","groups","el","_id","ancestor","groupKey","childId","siblings","sibs","capped","j","aId","bId","fwd","rev","collectNodesAndEdges","nodes","node","hasAria","hasSentientId","createDOMScanner","observer","idleCallbackId","contentCallback","knownElementToId","styles","fontSize","zIndex","rect","distanceFromTop","viewportHeight","inverseDistance","score","resolve","run","id","onContentAdded","mutations","added","addedIds","mutation","candidates","scanned","DEFAULT_INGEST_URL","readSntUid","apiKey","_a","read","name","m","e","sessionCookieName","LEGACY_SESSION_COOKIE_NAME","_graphTeardowns","init","config","client","dntBlocked","isDoNotTrackEnabled","gated","keyValid","zeroNetwork","_registerConsentUpgradeInit","c","prevTeardown","domScanner","createDOMScanner","resolvedIngestUrl","graphClient","createGraphClient","result","node","edge","syncDebounceTimer","debouncedSync","event","teardownGraph","__spreadProps","__spreadValues"]}
1
+ {"version":3,"sources":["../src/index-graph.ts","../src/uuid.ts","../src/storage-key.ts","../src/session.ts","../src/durable.ts","../src/queue.ts","../src/goal-queue.ts","../src/cache.ts","../src/session-meta.ts","../src/slots.ts","../src/snapshot.ts","../src/index.ts","../src/local-mode.ts","../src/blocks.ts","../src/micro-signals.ts","../src/graph.ts","../src/locator-from-dom.ts","../src/engagement/classify.ts","../src/scanner.ts"],"sourcesContent":["/**\n * Graph-capable entry point for @sentientui/core.\n *\n * Import from `@sentientui/core/graph` when you need DOM graph scanning and\n * page-structure sync. This entry pulls in `scanner.ts` and `graph.ts` at\n * build time, giving bundlers a real tree-shaking boundary. Standard A/B tests\n * should use `@sentientui/core` (the lean entry) instead.\n */\n\n// Re-export everything from the lean entry except `init`, which we override\n// below — so a graph consumer never needs dual-entry imports (importing the\n// lean entry alongside this one risks initialising two clients).\nexport {\n detectDeviceClass,\n detectTrafficSource,\n detectTimeOfDay,\n deriveSessionSegment,\n referrerDomainFromReferer,\n grantConsent,\n} from './index.js';\n// Sourced from their own modules (identical bindings to the lean barrel's):\n// snapshot/pre-paint helpers, slot helpers, blocks, micro-signals, and the\n// session cookie name — all were missing here, which made the comment above\n// a lie and forced consumers into dual-entry imports.\nexport {\n SNAPSHOT_STORAGE_KEY_PREFIX,\n readSnapshot,\n writeSnapshot,\n renderPrePaintScript,\n} from './snapshot.js';\nexport type { DecisionSnapshot, SlotConfigEntry, SlotOps, CompoundLocator } from './snapshot.js';\nexport { armOfResult, baselineResultFor, baselineSlots, toWireSlot } from './slots.js';\nexport type { SlotDeclInput, SlotResult } from './slots.js';\nexport * from './blocks.js';\nexport { attachMicroSignalDetectors } from './micro-signals.js';\nexport type { MicroSignalEmitter, MicroSignalType } from './micro-signals.js';\nexport { sessionCookieName, LEGACY_SESSION_COOKIE_NAME } from './storage-key.js';\n// SSR preload helpers moved to `@sentientui/core/server` in 0.6.0.\nexport type {\n SentientConfig,\n AssignResult,\n SentientClient,\n} from './index.js';\nexport type {\n SessionConfig,\n SessionManager,\n EventType,\n SentientEvent,\n QueueConfig,\n Assignment,\n} from './index.js';\n// EventQueue/AssignmentCache are no longer on the lean barrel — source them from\n// their own modules so the `/graph` subpath keeps exposing them unchanged.\nexport type { EventQueue } from './queue.js';\nexport type { AssignmentCache } from './cache.js';\n// Scanner + graph types (live in this entry only).\nexport type {\n ScannedNode,\n ScanResult,\n ContentAddedEvent,\n DOMScanner,\n} from './scanner.js';\nexport type {\n PageNode,\n GraphSnapshot,\n GraphConfig,\n GraphClient,\n} from './graph.js';\nexport { sanitizePageUrl } from './graph.js';\nexport { locatorFromElement } from './locator-from-dom.js';\n\nimport {\n init as initLean,\n isDoNotTrackEnabled,\n _registerConsentUpgradeInit,\n type SentientConfig,\n type SentientClient,\n} from './index.js';\nexport { isDoNotTrackEnabled };\nimport { LEGACY_SESSION_COOKIE_NAME, sessionCookieName } from './storage-key.js';\n\nconst DEFAULT_INGEST_URL = 'https://api.sentient-ui.com/v1/events';\nimport { createDOMScanner } from './scanner.js';\nimport { createGraphClient } from './graph.js';\n\nexport type GraphSentientConfig = SentientConfig & {\n /**\n * Enable DOM graph scanning and page-structure sync. Wires a MutationObserver\n * and a localStorage-backed graph. When `false` (default) this entry behaves\n * identically to `@sentientui/core`.\n */\n graph?: boolean;\n /**\n * Include captured heading / DOM text in graph sync payloads. OFF by default —\n * headings can contain account names or user-generated content. Structure\n * (component ids, semantic types, prominence) still syncs when `graph: true`.\n */\n captureDomText?: boolean;\n};\n\n// The client writes the per-project SUFFIXED cookie (sessionCookieName in\n// storage-key.ts). This reader kept the bare `_snt_uid` after namespacing\n// landed, so graph sync sent `sessionId: undefined` for every keyed project.\n// The bare name stays as a fallback for pre-namespacing identities.\nfunction readSntUid(apiKey?: string): string | undefined {\n const read = (name: string): string | undefined => {\n try {\n // Cookie names are `_snt_uid` + `_` + a pk_ key prefix — no regex\n // metacharacters, so interpolation is safe (same pattern as session.ts).\n const m = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));\n return m ? decodeURIComponent(m[1]!) : undefined;\n } catch {\n return undefined;\n }\n };\n return (apiKey ? read(sessionCookieName(apiKey)) : undefined) ?? read(LEGACY_SESSION_COOKIE_NAME);\n}\n\n/**\n * Graph-capable variant of `init()`. Identical to the lean `init` when\n * `config.graph` is `false` (or omitted). When `config.graph: true`, mounts\n * a DOM scanner + graph client and enables `client.getGraph()`.\n *\n * Import from `@sentientui/core/graph` — do not call the lean `init` alongside\n * this function as that would initialise two clients.\n */\n// Teardown for the graph resources (scanner + MutationObserver + debounce\n// timer + graph client) bound to each apiKey. initLean already disposes the\n// lean client's own timers/listeners on re-init, but it knows nothing about\n// these graph resources, so this entry tracks and tears them down itself —\n// otherwise an HMR / consent-toggle / provider-remount re-init would leak a\n// live MutationObserver and a pending sync timer per mount.\nconst _graphTeardowns = new Map<string, () => void>();\n\nexport function init(config: GraphSentientConfig): SentientClient {\n const client = initLean(config);\n\n // Mirror initLean's opt-out gate: a DNT/GPC visitor (or consent:false) must\n // not get the DOM scanner + graph sync, which POST page-structure beacons and\n // read `_snt_uid` — tracking that bypasses the lean client's own gating, even\n // under `preConsentBehavior: 'statistical_winner'` (audit P1).\n const dntBlocked = config.respectDoNotTrack !== false && isDoNotTrackEnabled();\n const gated = config.consent === false || dntBlocked;\n\n // Zero-network contract, mirroring the lean init's own gate: `localMode:\n // true` forces the on-device engine, and a missing OR INVALID (non-`pk_`)\n // key means the lean client is keyless-local or disabled. This used to check\n // only `!apiKey`, so a typo'd key — which the React provider's default\n // `graph: true` reaches — still mounted the scanner and POSTed\n // /v1/graph/sync into a client that discards everything.\n const keyValid = typeof config.apiKey === 'string' && config.apiKey.startsWith('pk_');\n const zeroNetwork = config.localMode === true || !keyValid;\n if (!config.graph || zeroNetwork || typeof window === 'undefined') return client;\n\n if (gated) {\n // Consent may still be granted later: grantConsent() must re-init through\n // THIS entry so the post-consent client mounts the scanner — the lean init\n // it upgraded through before knows nothing about graph resources, so a\n // consent grant used to lose graph capture for the session. DNT-blocked\n // clients register nothing: consent cannot override a global opt-out.\n if (!dntBlocked) {\n _registerConsentUpgradeInit(config.apiKey, (c) => init(c as GraphSentientConfig));\n }\n return client;\n }\n\n // A prior graph mount for this key is now superseded — tear it down first so\n // its observer/timer don't leak alongside the new mount's.\n const prevTeardown = _graphTeardowns.get(config.apiKey);\n if (prevTeardown) {\n try {\n prevTeardown();\n } catch {\n /* teardown must never throw on re-init */\n }\n }\n\n const domScanner = createDOMScanner();\n const resolvedIngestUrl = config.ingestUrl ?? DEFAULT_INGEST_URL;\n const graphClient = createGraphClient({\n syncUrl: resolvedIngestUrl.replace(/\\/events\\/?$/, '/graph/sync'),\n apiKey: config.apiKey,\n projectId: config.apiKey,\n sessionId: readSntUid(config.apiKey),\n });\n\n // Persisted page-node state from a previous page load is restored by the graph\n // client's own constructor (it reads `_snt_graph_nodes` on creation). We no\n // longer re-read the key and call restore() here — that was a redundant second\n // load path that cleared and reloaded identical data.\n\n void domScanner.scan().then((result) => {\n for (const node of result.nodes) {\n graphClient.addPageNode({\n id: node.componentId,\n componentId: node.componentId,\n semanticType: node.semanticType,\n answers: config.captureDomText && node.headingText ? [node.headingText] : [],\n prominenceScore: node.prominenceScore,\n depth: node.depth,\n });\n }\n for (const edge of result.edges) {\n graphClient.addStructuralEdge(edge);\n }\n graphClient.syncOnce();\n });\n\n let syncDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n const debouncedSync = (): void => {\n if (syncDebounceTimer !== null) clearTimeout(syncDebounceTimer);\n syncDebounceTimer = setTimeout(() => {\n syncDebounceTimer = null;\n graphClient.syncOnce();\n }, 500);\n };\n\n domScanner.observe((event) => {\n for (const node of event.nodes) {\n graphClient.addPageNode({\n id: node.componentId,\n componentId: node.componentId,\n semanticType: node.semanticType,\n answers: config.captureDomText && node.headingText ? [node.headingText] : [],\n prominenceScore: node.prominenceScore,\n depth: node.depth,\n });\n }\n for (const edge of event.edges) {\n graphClient.addStructuralEdge(edge);\n }\n debouncedSync();\n });\n\n // Tears down only the graph resources; the caller pairs it with the lean\n // client's own dispose/destroy. Idempotent, and de-registers itself so a\n // later re-init or teardown can't run it twice on already-freed resources.\n const teardownGraph = (): void => {\n if (syncDebounceTimer !== null) {\n clearTimeout(syncDebounceTimer);\n syncDebounceTimer = null;\n }\n domScanner.destroy();\n graphClient.destroy();\n if (_graphTeardowns.get(config.apiKey) === teardownGraph) {\n _graphTeardowns.delete(config.apiKey);\n }\n };\n _graphTeardowns.set(config.apiKey, teardownGraph);\n\n return {\n ...client,\n getGraph: () => graphClient.snapshot(),\n dispose: () => {\n teardownGraph();\n client.dispose();\n },\n destroy: () => {\n teardownGraph();\n client.destroy();\n },\n };\n}\n","/** Shared RFC 4122 v4 UUID generator. */\n\n/**\n * Returns a random RFC 4122 v4 UUID.\n *\n * Resolution order, each falling through only on absence/throw:\n * 1. `crypto.randomUUID()` — requires a **secure context** (HTTPS/localhost),\n * so it is absent on plain `http://` (non-localhost) pages.\n * 2. `crypto.getRandomValues()` — a CSPRNG that, unlike `randomUUID`, *is*\n * available in insecure contexts, so ids stay cryptographically random\n * exactly where step 1 is unavailable.\n * 3. `Math.random()` — last resort only when no Web Crypto exists at all.\n * These ids are anonymous analytics session/event keys (not secrets or\n * authenticators), so a well-formed value the Postgres `uuid` column accepts\n * matters more than PRNG quality here — and the generator must never throw\n * (a missing/malformed id would break session creation and the host page).\n */\nexport function randomUuidV4(): string {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n } catch {\n /* fall through to getRandomValues */\n }\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const buf = new Uint8Array(16);\n crypto.getRandomValues(buf);\n buf[6] = (buf[6]! & 0x0f) | 0x40; // version 4\n buf[8] = (buf[8]! & 0x3f) | 0x80; // variant 10\n let out = '';\n for (let i = 0; i < 16; i++) {\n out += buf[i]!.toString(16).padStart(2, '0');\n if (i === 3 || i === 5 || i === 7 || i === 9) out += '-';\n }\n return out;\n }\n } catch {\n /* fall through to the Math.random() builder */\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n","/**\n * Per-project browser-storage namespace suffix, derived from the public apiKey.\n *\n * Multiple SentientUI projects (different `pk_` keys) can run on the same exact\n * origin. Storage keyed only by name (`_snt_uid`, `_snt_asgn_*`,\n * `_snt_graph_nodes`) would then collide across projects — the visitor id in\n * particular, whose session row is keyed globally server-side, would let one\n * project's traffic land on another's session. Suffixing every browser key with\n * the apiKey prefix isolates projects, matching the queue's existing\n * `_snt_retry_${apiKey.slice(0,12)}` convention.\n *\n * Returns `''` when no apiKey is available (local mode) so keys stay stable\n * there.\n */\nexport function storageSuffix(apiKey?: string): string {\n return apiKey ? `_${apiKey.slice(0, 12)}` : '';\n}\n\n/**\n * The bare, pre-namespacing session cookie name. Still read as a FALLBACK\n * everywhere the suffixed name is read, so visitors who got their identity\n * before per-project namespacing keep it instead of being minted a fresh one.\n */\nexport const LEGACY_SESSION_COOKIE_NAME = '_snt_uid';\n\n/**\n * The one place the session cookie's name is decided. The writer (session.ts)\n * and every reader (core/server.ts SSR helper, graph sync, react devtools) must\n * call THIS — when namespacing landed, the writer moved to the suffixed name\n * while three readers kept the bare `_snt_uid`, so every SSR request for a\n * returning visitor missed the cookie and minted a fresh orphan session (quota\n * inflation, broken sticky assignments and persona continuity), and graph sync\n * sent `sessionId: undefined`. Deriving both sides from one function makes that\n * drift impossible, and index.test.ts pins the reader against the writer.\n */\nexport function sessionCookieName(apiKey?: string): string {\n return `${LEGACY_SESSION_COOKIE_NAME}${storageSuffix(apiKey)}`;\n}\n","/** Manages anonymous session identity with cookie + localStorage layers. */\n\nimport { randomUuidV4 } from './uuid.js';\nimport { sessionCookieName, storageSuffix } from './storage-key.js';\n\nexport type SessionConfig = {\n cookieName?: string;\n cookieTTLDays?: number;\n /**\n * Public apiKey — namespaces the `_snt_uid` cookie + storage per project, so\n * two projects on the same exact origin don't share a visitor id (which would\n * cross-contaminate sessions server-side). Omit in local mode.\n */\n apiKey?: string;\n /**\n * Session ID generated during SSR (e.g. from `loadAdaptiveAssignments`).\n * Used as the fallback when no existing cookie or localStorage entry is found,\n * so the client adopts the same session the server used for variant assignment\n * on first visit rather than generating a new, orphaned ID.\n */\n ssrSessionId?: string;\n};\n\nexport type SessionManager = {\n getSessionId(): string | null;\n /** True when neither cookie nor localStorage could be written — id is in-memory only. */\n isEphemeral(): boolean;\n destroy(): void;\n};\n\nconst DEFAULT_COOKIE_NAME = '_snt_uid';\nconst DEFAULT_COOKIE_TTL_DAYS = 365;\nconst STORAGE_KEY = '_snt_uid';\n\n/**\n * Generates a unique session identifier. Always an RFC 4122 v4 UUID — the id is\n * stored server-side in a Postgres `uuid` column, so the insecure-context\n * fallback must not emit a malformed value (see `randomUuidV4`).\n */\nfunction generateSessionId(): string {\n return randomUuidV4();\n}\n\nfunction readCookie(name: string): string | null {\n try {\n const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));\n return match ? decodeURIComponent(match[1]) : null;\n } catch {\n return null;\n }\n}\n\nfunction writeCookie(name: string, value: string, maxAgeSeconds: number): void {\n try {\n document.cookie = `${name}=${encodeURIComponent(value)}; max-age=${maxAgeSeconds}; SameSite=strict; path=/`;\n } catch {\n /* ignore */\n }\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): boolean {\n try {\n localStorage.setItem(key, value);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction readSessionStorage(key: string): string | null {\n try {\n return sessionStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeSessionStorage(key: string, value: string): boolean {\n try {\n sessionStorage.setItem(key, value);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction removeSessionStorage(key: string): void {\n try {\n sessionStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n}\n\nfunction probeCookieWritable(name: string): boolean {\n try {\n document.cookie = `${name}_probe=1; max-age=1; SameSite=strict; path=/`;\n return document.cookie.indexOf(`${name}_probe=1`) !== -1;\n } catch {\n return false;\n }\n}\n\nfunction removeLocalStorage(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n}\n\nfunction clearCookie(name: string): void {\n try {\n document.cookie = `${name}=; max-age=0; SameSite=strict; path=/`;\n } catch {\n /* ignore */\n }\n}\n\nconst SSR_MANAGER: SessionManager = {\n getSessionId: () => null,\n isEphemeral: () => false,\n destroy: () => undefined,\n};\n\n/**\n * Initializes session management. Returns a no-op manager in SSR environments.\n */\nexport function initSession(config?: SessionConfig): SessionManager {\n if (typeof window === 'undefined') {\n return SSR_MANAGER;\n }\n\n // Namespace the visitor-id keys per project so multiple keys on one origin\n // don't share a `_snt_uid` (see storage-key.ts). An explicit cookieName still\n // wins for callers that manage their own naming. sessionCookieName is the\n // shared writer/reader name — the SSR reader and graph sync derive the same\n // string from it, so they can't drift back to the bare name.\n const suffix = storageSuffix(config?.apiKey);\n const cookieName = config?.cookieName ?? sessionCookieName(config?.apiKey);\n const storageKey = `${STORAGE_KEY}${suffix}`;\n // Forget-me tombstone for the legacy fallback below. destroy() deletes only\n // this project's SUFFIXED keys — deleting the bare pre-namespacing ones\n // would reset every other project on a shared origin — so without a marker\n // the next init()'s readLegacy() re-adopted the exact identity the visitor\n // had just asked to forget. The marker carries the same per-project suffix,\n // so other projects keep adopting the bare id exactly as before.\n const legacyTombstoneKey = `${STORAGE_KEY}_tomb${suffix}`;\n const cookieTTLDays = config?.cookieTTLDays ?? DEFAULT_COOKIE_TTL_DAYS;\n const maxAgeSeconds = cookieTTLDays * 24 * 60 * 60;\n\n // Treat an empty string from any layer as absent. A `_snt_uid=` cookie with no\n // value (or an empty localStorage/sessionStorage entry) decodes to '', which is\n // not nullish — a plain `??` chain would keep it and persist it for a year,\n // leaving getSessionId() falsy so every track/goal/upsert bails out and the\n // visitor is permanently muted with no way to regenerate. Coercing '' to null\n // lets the chain fall through to a real id (or a freshly generated one).\n const nonEmpty = (value: string | null | undefined): string | null =>\n value && value.length > 0 ? value : null;\n\n // Legacy fallback: visitors identified BEFORE per-project namespacing carry\n // their id under the bare `_snt_uid` names. Without this read, the rollout of\n // the suffixed names reset every existing visitor's identity — a fresh session\n // row per visitor, broken sticky assignments, persona continuity lost. The id\n // is adopted (re-written under the suffixed names below) but the legacy keys\n // are left in place: deleting them would reset the OTHER projects on a shared\n // origin that haven't migrated it yet. Skipped when the caller manages its own\n // cookieName — the bare `_snt_uid` was never theirs — and when there is no\n // suffix (local mode still uses the bare names directly).\n // The tombstone (written by destroy()) blocks this fallback for THIS project\n // only — a forgotten visitor must come back a stranger, not resurrected from\n // the bare keys that other projects still legitimately share.\n const readLegacy = (): string | null =>\n suffix && !config?.cookieName && readLocalStorage(legacyTombstoneKey) === null\n ? nonEmpty(readCookie(DEFAULT_COOKIE_NAME)) ??\n nonEmpty(readLocalStorage(STORAGE_KEY)) ??\n nonEmpty(readSessionStorage(STORAGE_KEY))\n : null;\n\n let sessionId: string | null =\n nonEmpty(readCookie(cookieName)) ??\n nonEmpty(readLocalStorage(storageKey)) ??\n nonEmpty(readSessionStorage(storageKey)) ??\n readLegacy() ??\n nonEmpty(config?.ssrSessionId) ??\n generateSessionId();\n\n writeCookie(cookieName, sessionId, maxAgeSeconds);\n const lsOk = writeLocalStorage(storageKey, sessionId);\n const cookieOk = probeCookieWritable(cookieName);\n // Always write sessionStorage when localStorage fails — it covers same-tab\n // navigation even in strict storage environments.\n const ssOk = !lsOk ? writeSessionStorage(storageKey, sessionId) : false;\n const ephemeral = !lsOk && !cookieOk && !ssOk;\n\n return {\n getSessionId: () => sessionId,\n isEphemeral: () => ephemeral,\n destroy: () => {\n sessionId = null;\n clearCookie(cookieName);\n removeLocalStorage(storageKey);\n removeSessionStorage(storageKey);\n // See legacyTombstoneKey: the bare `_snt_uid` keys stay for the other\n // projects on this origin, but this project's next init() must not\n // re-adopt them — that quietly undid the forget-me it just performed.\n if (suffix && !config?.cookieName) writeLocalStorage(legacyTombstoneKey, '1');\n },\n };\n}\n","/**\n * Reliability primitives shared by every outbound SDK transport.\n *\n * Extracted from queue.ts so the goal sender cannot drift from the event\n * queue on the three decisions that decide whether data survives: which\n * responses are worth retrying, how long to wait, and how the cross-reload\n * bucket is read and written. Behaviour is byte-identical to the versions\n * these replace.\n */\n\nconst MAX_BACKOFF_MS = 60_000;\n/** 2^6 s = 64 s, already past MAX_BACKOFF_MS — the cap that stops the shift overflowing. */\nconst BACKOFF_EXPONENT_CAP = 6;\n\n/** Delay before the next attempt after `consecutiveFailures` failed ones (1-based). */\nexport function backoffDelayMs(consecutiveFailures: number): number {\n return Math.min(MAX_BACKOFF_MS, 1000 * 2 ** Math.min(consecutiveFailures, BACKOFF_EXPONENT_CAP));\n}\n\n/** What one delivery attempt achieved. `dropped` is terminal but unsuccessful. */\nexport type DeliveryOutcome = 'delivered' | 'dropped' | 'retry';\n\n/**\n * Classifies a response into the three outcomes that matter to a transport.\n *\n * 2xx delivered. A 4xx other than 429 will never succeed however many times we\n * try it (bad key, rejected body, unknown session), so retrying it loops\n * forever and starves real data out of the bucket — drop it, but distinctly,\n * so callers can tell the developer. 429 and 5xx are transient.\n *\n * `ok` is consulted before `status` deliberately: it is the semantic check, and\n * it keeps stubs that set only `ok` (used throughout the SDK test suite)\n * behaving as they did before this was extracted. An unrecognizable response\n * with neither is treated as retryable, matching the original queue.\n */\nexport function classifyResponse(res: { ok?: boolean; status?: number }): DeliveryOutcome {\n if (res.ok === true) return 'delivered';\n const { status } = res;\n if (typeof status !== 'number') return 'retry';\n if (status >= 200 && status < 300) return 'delivered';\n if (status >= 400 && status < 500 && status !== 429) return 'dropped';\n return 'retry';\n}\n\n/** Anything a persisted bucket can hold: it needs a stable server-side dedupe id. */\nexport type Identified = { id: string };\n\n/**\n * Reads a persisted bucket AND clears it — the caller takes ownership of the\n * items and is responsible for re-persisting any that fail again. Returns the\n * newest `max` entries; a corrupt or absent bucket reads as empty.\n */\nexport function drainBucket<T extends Identified>(storageKey: string, max: number): T[] {\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return [];\n const parsed = JSON.parse(raw) as T[];\n if (!Array.isArray(parsed)) return [];\n localStorage.removeItem(storageKey);\n return parsed.slice(-max);\n } catch {\n return [];\n }\n}\n\n/**\n * Merges `items` into the persisted bucket, de-duped by id (last write wins)\n * before the size cap. The dedupe matters: a batch that 5xx's repeatedly\n * in-session hands the same ids back on every retry, and without it each retry\n * appends another copy and `slice(-max)` evicts other distinct failed items to\n * make room for the duplicates.\n */\nexport function writeBucket<T extends Identified>(items: T[], max: number, storageKey: string): void {\n try {\n const existing = (() => {\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return [] as T[];\n const parsed = JSON.parse(raw) as T[];\n return Array.isArray(parsed) ? parsed : ([] as T[]);\n } catch {\n return [] as T[];\n }\n })();\n const byId = new Map<string, T>();\n for (const e of existing) byId.set(e.id, e);\n for (const e of items) byId.set(e.id, e);\n localStorage.setItem(storageKey, JSON.stringify([...byId.values()].slice(-max)));\n } catch {\n /* storage unavailable — the in-memory retry path still applies */\n }\n}\n\n/**\n * Removes the given ids from the persisted bucket. Called once an item is\n * acknowledged so a transient failure that was written to localStorage isn't\n * replayed on the next page load after the in-session retry succeeded.\n */\nexport function purgeBucket(ids: string[], storageKey: string): void {\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return;\n const parsed = JSON.parse(raw) as Identified[];\n if (!Array.isArray(parsed)) return;\n const drop = new Set(ids);\n const remaining = parsed.filter((e) => !drop.has(e.id));\n if (remaining.length === parsed.length) return; // nothing to remove\n if (remaining.length === 0) localStorage.removeItem(storageKey);\n else localStorage.setItem(storageKey, JSON.stringify(remaining));\n } catch {\n /* ignore */\n }\n}\n","/** Batched event queue with reliable transport (fetch + keepalive, localStorage retry). */\n\nimport { backoffDelayMs, classifyResponse, drainBucket, purgeBucket, writeBucket } from './durable.js';\n\nexport type EventType =\n | 'variant_assigned'\n | 'goal_achieved'\n | 'scroll_depth'\n | 'dwell'\n | 'cursor_signal'\n | 'component_visible'\n | 'component_exited'\n | 'micro_signal'\n // A funnel DECLARATION, not telemetry: the server upserts the funnel entity\n // and never stores it in raw_events. Listed here so callers can express it\n // without casting through the track() signature.\n | 'funnel_declared'\n // One per page load and per SPA route change, so a visit's journey through the\n // site is reconstructable. Carries `path` and nothing else.\n | 'pageview';\n\nexport type SentientEvent = {\n id: string;\n sessionId: string;\n projectId: string;\n componentId: string;\n variantId?: string;\n eventType: EventType;\n goalType?: string;\n /**\n * Page path the event happened on, e.g. `/pricing`. Set automatically from\n * `location.pathname` — deliberately NOT `location.href`, so query strings\n * (which routinely carry emails, reset tokens and order ids) never leave the\n * browser. The server re-strips them anyway; this is the first of two gates.\n * Undefined outside a browser (SSR), where there is no page to name.\n */\n path?: string;\n payload: Record<string, unknown>;\n timestamp: number;\n timeInSession: number;\n};\n\nexport type QueueConfig = {\n ingestUrl: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBatchSize?: number;\n maxRetrySize?: number;\n};\n\nexport type EventQueue = {\n push(event: SentientEvent): void;\n flush(): void;\n destroy(): void;\n};\n\nconst MAX_SENT_IDS = 500;\n// 64 KB is the per-origin keepalive budget on every modern browser. We split unload\n// flushes into chunks below this to avoid the whole batch being dropped.\nconst KEEPALIVE_BUDGET_BYTES = 56 * 1024;\n\n/**\n * Persisted retry-bucket key, namespaced by apiKey prefix so multiple projects\n * on the same origin each get their own bucket. Exported so the client's\n * forget-me teardown can remove it.\n */\nexport function retryStorageKey(apiKey: string): string {\n return `_snt_retry_${apiKey.slice(0, 12)}`;\n}\n\nconst SSR_QUEUE: EventQueue = {\n push: () => undefined,\n flush: () => undefined,\n destroy: () => undefined,\n};\n\n/**\n * Creates a batched event queue with periodic and lifecycle-triggered flushes.\n */\nexport function createEventQueue(config: QueueConfig): EventQueue {\n if (typeof window === 'undefined') {\n return SSR_QUEUE;\n }\n\n const flushIntervalMs = config.flushIntervalMs ?? 5000;\n const maxBatchSize = config.maxBatchSize ?? 20;\n const maxRetrySize = config.maxRetrySize ?? 100;\n const ingestUrl = config.ingestUrl;\n const apiKey = config.apiKey;\n const RETRY_KEY = retryStorageKey(apiKey);\n\n const queue: SentientEvent[] = [];\n const sentIds = new Set<string>();\n const sentIdOrder: string[] = [];\n\n const markSent = (ids: string[]): void => {\n for (const id of ids) {\n queuedIds.delete(id); // free the in-flight slot\n if (sentIds.has(id)) continue;\n sentIds.add(id);\n sentIdOrder.push(id);\n }\n while (sentIdOrder.length > MAX_SENT_IDS) {\n const oldest = sentIdOrder.shift();\n if (oldest) sentIds.delete(oldest);\n }\n // A batch that previously 5xx'd was persisted to the cross-reload retry\n // backstop. Now that it's acknowledged (delivered, or 4xx-dropped as\n // unretryable), drop those ids from localStorage too — otherwise the next\n // page load would replay an already-handled event. `sentIds` is in-memory\n // only, so it can't suppress that cross-reload duplicate on its own.\n purgeBucket(ids, RETRY_KEY);\n };\n\n // Tracks IDs currently in `queue` or in flight (handed to transportBatch\n // but not yet confirmed). Entries are removed on markSent or when an event\n // is dropped, so the set stays bounded by in-flight + queued size.\n const queuedIds = new Set<string>();\n\n // Bound the in-memory queue at the same cap as the persisted retry bucket\n // (maxRetrySize). During a sustained outage flush() keeps re-enqueueing every\n // failed batch while the page keeps producing events, and only the\n // localStorage bucket was capped — so a multi-hour outage on a long-lived\n // SPA tab grew this array without limit. Drop-oldest, matching writeBucket's\n // slice(-max) shed policy: the newest events are the ones a recovering\n // server can still use.\n const capQueue = (): void => {\n while (queue.length > maxRetrySize) {\n const dropped = queue.shift();\n if (dropped) queuedIds.delete(dropped.id);\n }\n };\n\n const enqueue = (event: SentientEvent): void => {\n if (sentIds.has(event.id) || queuedIds.has(event.id)) return;\n queuedIds.add(event.id);\n queue.push(event);\n capQueue();\n };\n\n // A retryable failure hands the batch back to the in-memory queue so the\n // backoff-gated interval flush retries it in-session (localStorage is only the\n // cross-reload backstop). The ids are still tracked in queuedIds — they were\n // pulled from `queue` on flush but never markSent — so we re-add them WITHOUT\n // going through enqueue (which would skip them as already-queued) and without\n // deleting/re-adding queuedIds. Already-sent events are never re-queued, and a\n // single event stays a single copy: `queue` is fully drained each flush.\n const requeueFailed = (batch: SentientEvent[]): void => {\n for (const event of batch) {\n if (sentIds.has(event.id)) continue;\n queuedIds.add(event.id); // idempotent — keeps id-tracking consistent\n queue.push(event);\n }\n // Failed batches count against the same bound as fresh pushes — this path\n // is exactly the one that grew unbounded during an outage (see capQueue).\n capQueue();\n };\n\n const retryEvents = drainBucket<SentientEvent>(RETRY_KEY, maxRetrySize);\n for (const event of retryEvents) {\n enqueue(event);\n }\n\n // Pause flushing until backoff expires.\n let backoffUntil = 0;\n let consecutiveFailures = 0;\n\n // Authenticated transport. keepalive: true gives unload-survival; we still await\n // the response so 5xx/429 actually surface and we can retry.\n const transportBatch = (batch: SentientEvent[], salvage = true): void => {\n if (batch.length === 0) return;\n const body = JSON.stringify(batch);\n const ids = batch.map((e) => e.id);\n\n let pending: Promise<Response> | Response;\n try {\n pending = fetch(ingestUrl, {\n method: 'POST',\n keepalive: true,\n body,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n });\n } catch {\n // Synchronous throw (typically jsdom in tests, or extreme browser failure).\n writeBucket(batch, maxRetrySize, RETRY_KEY);\n requeueFailed(batch);\n consecutiveFailures++;\n backoffUntil = Date.now() + backoffDelayMs(consecutiveFailures);\n return;\n }\n\n // fetch can sometimes return a value directly (tests using stubGlobal). Handle both.\n const handleResponse = (res: Response): void => {\n if (classifyResponse(res) !== 'retry') {\n // An API older than migration 108 400s the whole batch over the unknown\n // 'pageview' type, and a 4xx is terminal — which silently dropped the\n // co-batched exposures and goals too. Re-send once without the\n // pageviews (valid on the old API); the pageviews themselves are\n // unsendable there and are dropped as delivered.\n if (!res.ok && salvage) {\n const rest = batch.filter((e) => e.eventType !== 'pageview');\n if (rest.length > 0 && rest.length < batch.length) {\n markSent(batch.filter((e) => e.eventType === 'pageview').map((e) => e.id));\n transportBatch(rest, false);\n return;\n }\n }\n // 2xx = success. 4xx (except 429) will never succeed — drop them rather than loop.\n markSent(ids);\n consecutiveFailures = 0;\n backoffUntil = 0;\n return;\n }\n // 5xx / 429 → retry with backoff.\n writeBucket(batch, maxRetrySize, RETRY_KEY);\n requeueFailed(batch);\n consecutiveFailures++;\n backoffUntil = Date.now() + backoffDelayMs(consecutiveFailures);\n };\n\n if (pending instanceof Promise) {\n pending.then(handleResponse).catch(() => {\n writeBucket(batch, maxRetrySize, RETRY_KEY);\n requeueFailed(batch);\n consecutiveFailures++;\n backoffUntil = Date.now() + backoffDelayMs(consecutiveFailures);\n });\n } else {\n handleResponse(pending);\n }\n };\n\n // Pack events into a batch that stays under the keepalive byte budget AND the\n // configured event-count cap. Returns the prefix of `pool` that fits.\n // Uses UTF-8 byte length (not char count) — emoji/CJK payloads can be 3–4×\n // larger as bytes and would otherwise blow the keepalive budget.\n const encoder = typeof TextEncoder !== 'undefined' ? new TextEncoder() : null;\n const byteLength = (s: string): number =>\n encoder ? encoder.encode(s).length : s.length;\n const packBatch = (pool: SentientEvent[]): SentientEvent[] => {\n const out: SentientEvent[] = [];\n let bytes = 2; // for the surrounding []\n for (const e of pool) {\n const size = byteLength(JSON.stringify(e)) + 1; // +1 for comma\n if (out.length > 0 && bytes + size > KEEPALIVE_BUDGET_BYTES) break;\n if (out.length >= maxBatchSize) break;\n out.push(e);\n bytes += size;\n }\n return out;\n };\n\n const flush = (): void => {\n try {\n if (Date.now() < backoffUntil) return;\n while (queue.length > 0) {\n // Re-check inside the loop: transportBatch can set backoffUntil\n // synchronously (a sync throw or a same-tick Response) and re-enqueue the\n // failed batch, so a mid-drain backoff must stop further chunks this tick\n // (otherwise the just-re-enqueued batch would immediately retry-loop).\n if (Date.now() < backoffUntil) break;\n const pending = queue.filter((e) => !sentIds.has(e.id));\n queue.length = 0;\n if (pending.length === 0) break;\n const batch = packBatch(pending);\n if (batch.length === 0) break;\n // Put anything we didn't pack back on the queue for the next tick.\n if (batch.length < pending.length) {\n queue.push(...pending.slice(batch.length));\n }\n transportBatch(batch);\n }\n } catch {\n /* never throw */\n }\n };\n\n let flushTimerActive = true;\n let intervalId: ReturnType<typeof setInterval> | null = null;\n\n intervalId = setInterval(() => {\n if (!flushTimerActive) return;\n flush();\n }, flushIntervalMs);\n\n const onVisibilityChange = (): void => {\n if (document.visibilityState === 'hidden') {\n flush();\n }\n };\n\n // `pagehide` (not `beforeunload`): a `beforeunload` listener can disqualify a\n // page from the back/forward cache. `pagehide` fires on real unloads AND on\n // bfcache eviction, and `visibilitychange:hidden` already covers most leaves,\n // so flush-on-leave stays covered without inhibiting bfcache.\n const onPageHide = (): void => {\n flush();\n };\n\n document.addEventListener('visibilitychange', onVisibilityChange);\n window.addEventListener('pagehide', onPageHide);\n\n return {\n push(event: SentientEvent): void {\n enqueue(event);\n if (queue.length >= maxBatchSize) {\n flush();\n }\n },\n flush,\n destroy(): void {\n flushTimerActive = false;\n if (intervalId !== null) {\n clearInterval(intervalId);\n intervalId = null;\n }\n document.removeEventListener('visibilitychange', onVisibilityChange);\n window.removeEventListener('pagehide', onPageHide);\n flush();\n },\n };\n}\n","/**\n * Durable transport for `POST /v1/goals`.\n *\n * A conversion is the single most valuable event the SDK emits, and it used to\n * be the least reliable one: `goal()` fired a bare `fetch(...).catch(() => {})`,\n * which only observes network-layer failures. A 429 (the per-IP limit is 100\n * req/min, and shared egress — corporate NAT, carriers, storefront proxies —\n * hits it routinely), a 5xx, or a 400 all RESOLVE, so the catch never ran and\n * the response was discarded unread. The conversion was gone with no retry and\n * nothing surfaced to the developer.\n *\n * This gives goals the same guarantees the event queue has always had: retry\n * with backoff in-session, a localStorage bucket that survives reload, and\n * dedupe by id. Retry is safe because `goalId` is a client-generated UUID that\n * the server dedupes on (`ON CONFLICT DO NOTHING`) — the safety was already\n * there, it just wasn't used.\n *\n * Unlike events, `/v1/goals` takes ONE goal per request, so this is a serial\n * sender rather than a batcher, and a flush sends at most `maxPerFlush` so a\n * backlog drains at a pace the rate limiter tolerates instead of re-triggering\n * the 429 that created it.\n */\n\nimport { backoffDelayMs, classifyResponse, drainBucket, purgeBucket, writeBucket } from './durable.js';\n\n/** One queued conversion. `id` is the goalId — the server's dedupe key. */\nexport type PendingGoal = {\n id: string;\n /** Pre-serialized request body, so a persisted goal replays byte-identically. */\n body: string;\n};\n\nexport type GoalQueueConfig = {\n /** Absolute URL of the goals endpoint. */\n url: string;\n apiKey: string;\n headers: Record<string, string>;\n flushIntervalMs?: number;\n maxRetrySize?: number;\n maxPerFlush?: number;\n /** Called when a goal is permanently dropped (non-retryable status), so the\n * client can warn in debug mode. Never called for retryable failures. */\n onDrop?: (goal: PendingGoal, status: number) => void;\n};\n\nexport type GoalQueue = {\n /** Sends immediately; on a retryable failure the goal is queued and retried. */\n send(goal: PendingGoal): void;\n flush(): void;\n destroy(): void;\n};\n\nconst MAX_SENT_IDS = 200;\n\n/**\n * Persisted goal-retry bucket key, namespaced by apiKey prefix so multiple\n * projects on one origin keep separate buckets. Exported so the client's\n * forget-me teardown can remove it.\n */\nexport function goalRetryStorageKey(apiKey: string): string {\n return `_snt_goal_retry_${apiKey.slice(0, 12)}`;\n}\n\nconst SSR_GOAL_QUEUE: GoalQueue = {\n send: () => undefined,\n flush: () => undefined,\n destroy: () => undefined,\n};\n\nexport function createGoalQueue(config: GoalQueueConfig): GoalQueue {\n if (typeof window === 'undefined') return SSR_GOAL_QUEUE;\n\n const flushIntervalMs = config.flushIntervalMs ?? 5000;\n const maxRetrySize = config.maxRetrySize ?? 100;\n const maxPerFlush = config.maxPerFlush ?? 5;\n const RETRY_KEY = goalRetryStorageKey(config.apiKey);\n\n const pending: PendingGoal[] = [];\n const pendingIds = new Set<string>();\n const sentIds = new Set<string>();\n const sentIdOrder: string[] = [];\n\n let backoffUntil = 0;\n let consecutiveFailures = 0;\n let disposed = false;\n\n const markSent = (goal: PendingGoal): void => {\n pendingIds.delete(goal.id);\n if (!sentIds.has(goal.id)) {\n sentIds.add(goal.id);\n sentIdOrder.push(goal.id);\n }\n while (sentIdOrder.length > MAX_SENT_IDS) {\n const oldest = sentIdOrder.shift();\n if (oldest) sentIds.delete(oldest);\n }\n // Acknowledged (delivered, or dropped as unretryable) — clear it from the\n // cross-reload bucket so the next page load doesn't replay it.\n purgeBucket([goal.id], RETRY_KEY);\n };\n\n const markFailed = (goal: PendingGoal): void => {\n writeBucket([goal], maxRetrySize, RETRY_KEY);\n if (!sentIds.has(goal.id) && !pendingIds.has(goal.id)) {\n pendingIds.add(goal.id);\n pending.push(goal);\n }\n // One failed ROUND counts once, however many goals were in flight. flush()\n // launches up to maxPerFlush transports synchronously, so counting per goal\n // turned a single bad tick into consecutiveFailures=5 and a 32s backoff\n // where the first failure warrants 2s. queue.ts counts per batch; match it.\n if (Date.now() >= backoffUntil) {\n consecutiveFailures++;\n backoffUntil = Date.now() + backoffDelayMs(consecutiveFailures);\n }\n };\n\n const transport = (goal: PendingGoal): void => {\n let res: Promise<Response> | Response;\n try {\n res = fetch(config.url, {\n method: 'POST',\n keepalive: true,\n body: goal.body,\n headers: config.headers,\n });\n } catch {\n // Synchronous throw (jsdom in tests, or extreme browser failure).\n markFailed(goal);\n return;\n }\n\n const handle = (r: Response): void => {\n const outcome = classifyResponse(r);\n if (outcome === 'retry') {\n markFailed(goal);\n return;\n }\n // Terminal either way, but a drop is a wiring bug the developer can fix —\n // a 400 \"session not found\", a 401 from a misconfigured origin allowlist —\n // and used to be swallowed entirely. Say so instead of failing silently.\n if (outcome === 'dropped') config.onDrop?.(goal, r.status);\n markSent(goal);\n consecutiveFailures = 0;\n backoffUntil = 0;\n };\n\n // fetch can return a plain value under test stubs. Handle both.\n if (res instanceof Promise) res.then(handle).catch(() => markFailed(goal));\n else handle(res);\n };\n\n const flush = (): void => {\n try {\n if (disposed) return;\n if (Date.now() < backoffUntil) return;\n let sentThisTick = 0;\n while (pending.length > 0 && sentThisTick < maxPerFlush) {\n if (Date.now() < backoffUntil) break; // a same-tick failure re-armed backoff\n const goal = pending.shift()!;\n pendingIds.delete(goal.id);\n if (sentIds.has(goal.id)) continue;\n sentThisTick++;\n transport(goal);\n }\n } catch {\n /* never throw from a lifecycle handler */\n }\n };\n\n // Replay anything a previous page load failed to deliver. Left for the first\n // interval tick rather than sent now, so a page that reloads under an ongoing\n // outage doesn't stampede the endpoint during init.\n const restored = drainBucket<PendingGoal>(RETRY_KEY, maxRetrySize);\n for (const goal of restored) {\n if (!pendingIds.has(goal.id)) {\n pendingIds.add(goal.id);\n pending.push(goal);\n }\n }\n // drainBucket CLEARS storage as it reads, but only maxPerFlush goals leave per\n // tick — so a 40-goal backlog moved to memory and the visitor navigating two\n // seconds later lost the 35 that hadn't been sent yet. Put them straight back;\n // markSent purges each id individually once it is actually acknowledged.\n if (restored.length > 0) writeBucket(restored, maxRetrySize, RETRY_KEY);\n\n const intervalId = setInterval(flush, flushIntervalMs);\n const onVisibilityChange = (): void => {\n if (document.visibilityState === 'hidden') flush();\n };\n const onPageHide = (): void => flush();\n document.addEventListener('visibilitychange', onVisibilityChange);\n window.addEventListener('pagehide', onPageHide);\n\n return {\n send(goal: PendingGoal): void {\n if (disposed) return;\n if (sentIds.has(goal.id) || pendingIds.has(goal.id)) return;\n // A conversion goes out now — it is often the last thing that happens\n // before a redirect to a thank-you page. Only a failure makes it queued.\n if (Date.now() < backoffUntil) {\n // Persist BEFORE parking it in memory. A goal only ever reached the\n // cross-reload bucket via markFailed — i.e. only after a failed attempt\n // — so a conversion queued during someone else's backoff lived in memory\n // alone, and flush() early-returns while backoff is armed, so pagehide\n // could not rescue it either. A purchase firing 300ms after a rate-limited\n // add_to_cart was lost on the checkout redirect: exactly the outage this\n // queue exists to survive.\n writeBucket([goal], maxRetrySize, RETRY_KEY);\n pendingIds.add(goal.id);\n pending.push(goal);\n return;\n }\n transport(goal);\n },\n flush,\n destroy(): void {\n clearInterval(intervalId);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n window.removeEventListener('pagehide', onPageHide);\n flush();\n disposed = true;\n },\n };\n}\n","/** Synchronous variant assignment cache (memory + localStorage). */\n\nimport { storageSuffix } from './storage-key.js';\n\nexport type Assignment = {\n variantId: string;\n assignedAt: number;\n segment: string;\n confidence: number;\n content?: string;\n /**\n * Per-entry expiry (ms from `assignedAt`). When present it overrides the\n * cache-wide default TTL — lets the server-provided `assignmentTtlMs` govern\n * how long this specific assignment stays valid. Persists across reloads.\n */\n ttlMs?: number;\n};\n\nexport type AssignmentCache = {\n get(componentId: string, segment: string): Assignment | null;\n set(componentId: string, segment: string, assignment: Assignment): void;\n /** Drops every entry, memory and localStorage — the forget-me path. */\n clear(): void;\n};\n\nconst DEFAULT_TTL_MS = 30 * 60 * 1000;\n\nfunction cacheKey(componentId: string, segment: string): string {\n // URI-encode each part (same scheme as storageKey) and join with a literal\n // ':'. Since encodeURIComponent escapes ':', the separator is unambiguous —\n // otherwise a segment like `device:source` could collide two distinct\n // (componentId, segment) pairs onto the same raw `${id}:${segment}` string.\n return `${encodeURIComponent(componentId)}:${encodeURIComponent(segment)}`;\n}\n\n/**\n * Creates an assignment cache with optional TTL (default 30 minutes). Pass the\n * project's `apiKey` so the localStorage keys are namespaced per project —\n * otherwise two projects on the same origin share cached assignments.\n */\nexport function createAssignmentCache(ttlMs: number = DEFAULT_TTL_MS, apiKey?: string): AssignmentCache {\n const memory = new Map<string, Assignment>();\n\n // Per-project localStorage prefix. No apiKey (local mode) → the legacy\n // `_snt_asgn_` prefix so single-project behavior is unchanged.\n const keyPrefix = `_snt_asgn${storageSuffix(apiKey)}_`;\n\n const storageKey = (componentId: string, segment: string): string =>\n `${keyPrefix}${encodeURIComponent(componentId)}:${encodeURIComponent(segment)}`;\n\n const parseStorageKey = (key: string): { componentId: string; segment: string } | null => {\n const suffix = key.slice(keyPrefix.length);\n const sep = suffix.indexOf(':');\n if (sep < 0) return null;\n try {\n return {\n componentId: decodeURIComponent(suffix.slice(0, sep)),\n segment: decodeURIComponent(suffix.slice(sep + 1)),\n };\n } catch {\n return null;\n }\n };\n\n const listStorageKeys = (): string[] => {\n try {\n const keys: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(keyPrefix)) {\n keys.push(key);\n }\n }\n return keys;\n } catch {\n return [];\n }\n };\n\n // Honor a per-entry TTL (server-provided assignmentTtlMs) when present; fall\n // back to the cache-wide default otherwise.\n const isExpired = (assignment: Assignment): boolean =>\n assignment.assignedAt + (assignment.ttlMs && assignment.ttlMs > 0 ? assignment.ttlMs : ttlMs) < Date.now();\n\n const restoreFromStorage = (): void => {\n for (const key of listStorageKeys()) {\n try {\n const raw = localStorage.getItem(key);\n if (!raw) continue;\n const assignment = JSON.parse(raw) as Assignment;\n if (isExpired(assignment)) {\n localStorage.removeItem(key);\n continue;\n }\n const parsed = parseStorageKey(key);\n if (!parsed) continue;\n memory.set(cacheKey(parsed.componentId, parsed.segment), assignment);\n } catch {\n /* ignore corrupt entries */\n }\n }\n };\n\n if (typeof window !== 'undefined') {\n restoreFromStorage();\n }\n\n return {\n get(componentId: string, segment: string): Assignment | null {\n const entry = memory.get(cacheKey(componentId, segment));\n if (!entry) return null;\n if (isExpired(entry)) {\n memory.delete(cacheKey(componentId, segment));\n return null;\n }\n return entry;\n },\n\n set(componentId: string, segment: string, assignment: Assignment): void {\n const key = cacheKey(componentId, segment);\n memory.set(key, assignment);\n try {\n localStorage.setItem(storageKey(componentId, segment), JSON.stringify(assignment));\n } catch {\n /* ignore */\n }\n },\n\n clear(): void {\n memory.clear();\n for (const storageK of listStorageKeys()) {\n try {\n localStorage.removeItem(storageK);\n } catch {\n /* ignore */\n }\n }\n },\n };\n}\n","/** Session metadata helpers (browser + Node). No DOM APIs. */\n\n/**\n * Known AI-agent / crawler user-agent tokens. Matched case-insensitively as\n * substrings. This is maintained data — bot lists move monthly. Used both to\n * flag automation on sessions (agentic browsers that leak a token) and by the\n * server middleware / agent-feed route to identify crawler HTTP reads.\n */\nexport const agentUaList: readonly string[] = [\n 'GPTBot',\n 'ChatGPT-User',\n 'OAI-SearchBot',\n 'ClaudeBot',\n 'Claude-User',\n 'Claude-SearchBot',\n 'PerplexityBot',\n 'Perplexity-User',\n 'Google-Extended',\n 'Applebot-Extended',\n 'Meta-ExternalAgent',\n 'Bytespider',\n 'CCBot',\n 'Amazonbot',\n 'cohere-ai',\n 'Diffbot',\n];\n\n/** True when the user-agent contains a known AI-agent / crawler token. */\nexport function uaTokenMatch(userAgent: string): boolean {\n return matchedAgentToken(userAgent) !== null;\n}\n\n/** The first known agent token found in the user-agent, or null. */\nexport function matchedAgentToken(userAgent: string): string | null {\n if (!userAgent) return null;\n const s = userAgent.toLowerCase();\n return agentUaList.find((token) => s.includes(token.toLowerCase())) ?? null;\n}\n\n/**\n * Purpose category for an agent fetch, inferred from its published user-agent:\n * - `user` — a person asked an assistant to read the page, live (…-User UAs)\n * - `search` — indexing for an AI answer engine (…-SearchBot / SearchBot)\n * - `training` — model-training crawl (GPTBot, ClaudeBot, CCBot, …)\n * - `other` — not clearly AI, or an unknown/new token\n */\nexport type AgentIntent = 'user' | 'search' | 'training' | 'other';\n\n/** Intent per agent token. Maintained beside `agentUaList` — when you add or\n * rename a token above, set its intent here (a unit test enforces coverage). */\nexport const AGENT_INTENTS: Record<string, AgentIntent> = {\n 'GPTBot': 'training',\n 'ChatGPT-User': 'user',\n 'OAI-SearchBot': 'search',\n 'ClaudeBot': 'training',\n 'Claude-User': 'user',\n 'Claude-SearchBot': 'search',\n 'PerplexityBot': 'search',\n 'Perplexity-User': 'user',\n 'Google-Extended': 'training',\n 'Applebot-Extended': 'training',\n 'Meta-ExternalAgent': 'training',\n 'Bytespider': 'training',\n 'CCBot': 'training',\n 'Amazonbot': 'other',\n 'cohere-ai': 'training',\n 'Diffbot': 'other',\n};\n\n/** Intent for a matched bot token (case-insensitive); `other` for null/unknown. */\nexport function agentIntent(botName: string | null): AgentIntent {\n if (!botName) return 'other';\n const hit = agentUaList.find((t) => t.toLowerCase() === botName.toLowerCase());\n return (hit && AGENT_INTENTS[hit]) || 'other';\n}\n\n/** `agentUaList` grouped by intent — the \"which crawlers do you classify?\" reference. */\nexport function classifiedAgents(): Record<AgentIntent, string[]> {\n const out: Record<AgentIntent, string[]> = { user: [], search: [], training: [], other: [] };\n for (const t of agentUaList) out[AGENT_INTENTS[t] ?? 'other'].push(t);\n return out;\n}\n\nexport function detectDeviceClass(userAgent: string): string {\n const s = userAgent.toLowerCase();\n if (/ipad|tablet|playbook|kindle|silk/.test(s)) return 'tablet';\n if (/mobi|iphone|ipod|android.*mobile|phone/.test(s)) return 'mobile';\n return 'desktop';\n}\n\nexport function detectTrafficSource(referrer: string, appOrigin?: string): string {\n if (!referrer) return 'direct';\n try {\n const refUrl = new URL(referrer);\n if (appOrigin) {\n try {\n if (new URL(appOrigin).host === refUrl.host) return 'direct';\n } catch {\n /* ignore invalid appOrigin */\n }\n }\n const host = refUrl.hostname.toLowerCase();\n if (/(^|\\.)(google|bing|duckduckgo|yahoo)\\./.test(host)) return 'search';\n // Anchor to the registrable domain (exact host or a subdomain of it) so\n // hosts like `x.company.com`, `t.company.io` or `linkedinsights.com` are not\n // misclassified as social by an unbounded substring match.\n if (/(^|\\.)(twitter\\.com|x\\.com|facebook\\.com|linkedin\\.com|reddit\\.com|t\\.co)$/.test(host)) return 'social';\n return 'referral';\n } catch {\n return 'direct';\n }\n}\n\nexport function referrerDomainFromReferer(referrer: string): string | null {\n if (!referrer) return null;\n try {\n return new URL(referrer).hostname;\n } catch {\n return null;\n }\n}\n\n/**\n * Ad-platform click-ID query params worth keeping. An ALLOWLIST, unlike the\n * `utm_` prefix match: unknown query keys here are unbounded-cardinality junk\n * (session tokens, cache busters) that would bloat the sessions table, so only\n * the IDs the major ad platforms actually append survive.\n *\n * gclid / gbraid / wbraid — Google Ads auto-tagging (search, YouTube, Display;\n * gbraid/wbraid are the iOS-14 privacy variants)\n * fbclid — Meta (Facebook + Instagram). Appended to EVERY outbound Meta\n * click, paid and organic alike — it identifies the platform, never spend.\n * ttclid — TikTok Ads\n * msclkid — Microsoft Ads (Bing)\n * twclid — X/Twitter Ads\n * li_fat_id — LinkedIn Ads\n */\nexport const CLICK_ID_KEYS: readonly string[] = [\n 'gclid',\n 'gbraid',\n 'wbraid',\n 'fbclid',\n 'ttclid',\n 'msclkid',\n 'twclid',\n 'li_fat_id',\n];\n\n/**\n * Splits a URL query into the attribution params the session upsert carries:\n * every `utm_`-prefixed key, plus allowlisted ad click IDs (CLICK_ID_KEYS).\n * Accepts a raw search string (\"?a=b\" or \"a=b\"), a URLSearchParams, or a\n * Next.js `searchParams` object (whose values may be string arrays — the\n * first occurrence wins, matching URLSearchParams iteration order).\n * Node-safe: no DOM APIs.\n */\nexport function extractTrackedParams(\n search: string | URLSearchParams | Record<string, string | string[] | undefined>,\n): { utmParams: Record<string, string>; clickIds: Record<string, string> } {\n const utmParams: Record<string, string> = {};\n const clickIds: Record<string, string> = {};\n try {\n const entries: Iterable<[string, string]> =\n typeof search === 'string' || search instanceof URLSearchParams\n ? new URLSearchParams(search)\n : Object.entries(search).flatMap(([k, v]): Array<[string, string]> => {\n const first = Array.isArray(v) ? v[0] : v;\n return first === undefined ? [] : [[k, first]];\n });\n for (const [k, v] of entries) {\n // First occurrence wins for duplicates — a repeated gclid in a mangled\n // URL must not let the later value silently replace the real one.\n if (k.startsWith('utm_')) {\n if (!(k in utmParams)) utmParams[k] = v;\n } else if (CLICK_ID_KEYS.includes(k)) {\n if (!(k in clickIds)) clickIds[k] = v;\n }\n }\n } catch {\n /* malformed input → empty attribution, never a throw at init() */\n }\n return { utmParams, clickIds };\n}\n\nexport function detectTimeOfDay(d: Date): string {\n const h = d.getHours();\n if (h < 6) return 'night';\n if (h < 12) return 'morning';\n if (h < 18) return 'afternoon';\n return 'evening';\n}\n\nexport type SessionUpsertPayload = {\n sessionId: string;\n ephemeral: boolean;\n utmParams: Record<string, string>;\n /** Allowlisted ad-platform click IDs from the landing URL (CLICK_ID_KEYS). */\n clickIds: Record<string, string>;\n deviceClass: string;\n trafficSource: string;\n referrerDomain: string | null;\n timeOfDay: string;\n dayOfWeek: string;\n /**\n * True when this session is likely driven by automation — either\n * `navigator.webdriver` was set, or the user-agent carried a known agent\n * token. Probabilistic: a flag for metrics + bandit exclusion, never a gate.\n */\n automation: boolean;\n};\n\n/** Bandit segment key: `<device_class>:<traffic_source>`. */\nexport function deriveSessionSegment(opts?: {\n userAgent?: string;\n referer?: string;\n appOrigin?: string;\n}): string {\n const body = buildSessionUpsertPayload('__segment__', opts);\n return `${body.deviceClass}:${body.trafficSource}`;\n}\n\n/**\n * Builds a session upsert body aligned with the browser SDK so SSR assign uses\n * the same segment key (`device:source`) as the client after hydration.\n */\nexport function buildSessionUpsertPayload(\n sessionId: string,\n opts?: {\n userAgent?: string;\n referer?: string;\n appOrigin?: string;\n utmParams?: Record<string, string>;\n clickIds?: Record<string, string>;\n now?: Date;\n /** `navigator.webdriver` value from the browser, when available. */\n webdriver?: boolean;\n },\n): SessionUpsertPayload {\n const ua = opts?.userAgent?.trim() ?? '';\n const referer = opts?.referer?.trim() ?? '';\n const now = opts?.now ?? new Date();\n return {\n sessionId,\n ephemeral: false,\n utmParams: opts?.utmParams ?? {},\n clickIds: opts?.clickIds ?? {},\n deviceClass: ua ? detectDeviceClass(ua) : 'desktop',\n trafficSource: referer\n ? detectTrafficSource(referer, opts?.appOrigin)\n : 'direct',\n referrerDomain: referrerDomainFromReferer(referer),\n timeOfDay: detectTimeOfDay(now),\n dayOfWeek: ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][now.getDay()] ?? 'sun',\n automation: opts?.webdriver === true || uaTokenMatch(ua),\n };\n}\n","/**\n * Slot declaration helpers shared by the browser client (decide) and the\n * server preload path. Pure wrappers over @sentientui/policy.\n */\nimport {\n canonicalArm,\n slotBaselineArm,\n slotResultFor,\n type SlotDecl,\n type SlotResult,\n} from '@sentientui/policy';\n\n/** SDK-facing slot declaration. `dims` accepts readonly arrays (`as const`). */\nexport type SlotDeclInput = {\n id: string;\n arms?: string[];\n dims?: Record<string, readonly string[]>;\n baseline?: string | Record<string, string>;\n};\n\nexport type { SlotResult };\n\n/**\n * Whitelists the wire fields of a slot declaration. Anything an SDK layer\n * attached (goal configs, refs, …) is stripped so it never reaches the zod\n * schema on the API. Also normalizes readonly arrays to mutable ones.\n */\nexport function toWireSlot(d: SlotDeclInput): SlotDecl {\n return {\n id: d.id,\n ...(d.arms ? { arms: [...d.arms] } : {}),\n ...(d.dims\n ? {\n dims: Object.fromEntries(\n Object.entries(d.dims).map(([dim, values]) => [dim, [...values]]),\n ),\n }\n : {}),\n ...(d.baseline !== undefined ? { baseline: d.baseline } : {}),\n };\n}\n\n/** The declared (or default first-declared) baseline result for a slot. */\nexport function baselineResultFor(d: SlotDeclInput): SlotResult {\n const decl = toWireSlot(d);\n return slotResultFor(decl, slotBaselineArm(decl));\n}\n\n/** Baseline results for a whole declaration list, keyed by slot id. */\nexport function baselineSlots(decls: SlotDeclInput[]): Record<string, SlotResult> {\n const out: Record<string, SlotResult> = {};\n for (const d of decls) out[d.id] = baselineResultFor(d);\n return out;\n}\n\n/**\n * Canonical arm string of a slot result: dims results encode as sorted\n * `dim=value` pairs joined with '|'; arms results are the arm id verbatim.\n */\nexport function armOfResult(result: SlotResult): string {\n return typeof result === 'string' ? result : canonicalArm(result);\n}\n","/**\n * Decision snapshot: the SPA / return-visit pre-paint source. Written after\n * every successful decide; read by the inline pre-paint script (before any\n * framework code runs) and by init() to seed slot/persona state.\n */\nimport type { SlotResult } from './slots.js';\nimport type { BlockNode, SitePalette } from './blocks.js';\n\nexport const SNAPSHOT_STORAGE_KEY_PREFIX = '_snt_snap:';\n\n/** Versioned compound locator: resolve id → dataAttr → selector, then verify\n * against fingerprint. Lets a slot survive DOM/markup drift. */\nexport type CompoundLocator = {\n v?: number;\n id?: string;\n dataAttr?: { name: string; value: string };\n selector?: string;\n urlMatch?: string;\n fingerprint?: { tag?: string; text?: string };\n semanticId?: string;\n};\n\n/** Bounded, declarative operations a registry arm may apply to its element.\n * The style set is a fixed whitelist (validated server-side); no arbitrary CSS,\n * HTML, or JS ever. `text` is applied via textContent; https-only URLs.\n * moveBefore/moveAfter (exactly one) reposition the element relative to a\n * uniquely-resolving sibling anchor — post-decide only, never pre-paint. */\nexport type SlotOps = {\n text?: string;\n style?: Record<string, string>;\n hidden?: boolean;\n href?: string;\n imageSrc?: string;\n imageAlt?: string;\n moveBefore?: CompoundLocator;\n moveAfter?: CompoundLocator;\n};\n\n/** Registry-mode apply info per slot: where to apply and what to set. Stored so\n * a returning visitor's pre-paint can reapply it. `target` is the Phase-2 bare\n * selector; `locator` (Phase 3) is the compound locator, preferred when present. */\nexport type SlotConfigEntry = {\n kind: 'tokens' | 'arms';\n target?: string;\n locator?: CompoundLocator;\n content?: string;\n ops?: SlotOps;\n /** Composition Blocks per arm — ALL arms, not just the served one, because\n * Option-B rendering pre-paints every arm hidden and reveals the served one\n * (spec §6). Holdout sessions receive the baseline arm's tree only, so the\n * control group's DOM stays meaningful. Absent for non-composition slots. */\n blocks?: Record<string, BlockNode>;\n};\n\nexport type DecisionSnapshot = {\n v: 1;\n persona: string;\n band: 'low' | 'medium' | 'high';\n slots: Record<string, SlotResult>;\n layoutOrder: string[] | null;\n savedAt: number;\n // Additive (kept at v:1 so existing snapshots stay valid — bumping the version\n // would flush every returning visitor's snapshot and flash the baseline once).\n // Present only for registry-mode (no-code) installs.\n slotConfig?: Record<string, SlotConfigEntry>;\n /** Derived site palette for Composition Block rendering — cached so the\n * pre-paint render already looks native (a palette that pops in post-decide\n * would be its own flash). */\n palette?: SitePalette;\n};\n\nconst BANDS = ['low', 'medium', 'high'];\n\n/** Returns null on missing, corrupt, or wrong-version data — never throws. */\nexport function readSnapshot(apiKey: string): DecisionSnapshot | null {\n try {\n const raw = localStorage.getItem(SNAPSHOT_STORAGE_KEY_PREFIX + apiKey);\n if (!raw) return null;\n const p = JSON.parse(raw) as Partial<DecisionSnapshot> | null;\n if (\n !p ||\n typeof p !== 'object' ||\n p.v !== 1 ||\n typeof p.persona !== 'string' ||\n typeof p.band !== 'string' ||\n !BANDS.includes(p.band) ||\n typeof p.slots !== 'object' ||\n p.slots === null ||\n Array.isArray(p.slots) ||\n !(p.layoutOrder === null || Array.isArray(p.layoutOrder)) ||\n typeof p.savedAt !== 'number' ||\n // slotConfig is optional; when present it must be a plain object.\n !(p.slotConfig === undefined || (typeof p.slotConfig === 'object' && p.slotConfig !== null && !Array.isArray(p.slotConfig)))\n ) {\n return null;\n }\n return p as DecisionSnapshot;\n } catch {\n return null;\n }\n}\n\n/** Best-effort persist — storage failures are swallowed. */\nexport function writeSnapshot(apiKey: string, snap: DecisionSnapshot): void {\n try {\n localStorage.setItem(SNAPSHOT_STORAGE_KEY_PREFIX + apiKey, JSON.stringify(snap));\n } catch {\n /* private mode / quota — the snapshot is an optimization, never a requirement */\n }\n}\n\n/**\n * Inline pre-paint script (Rung 1a): reads the snapshot and sets\n * `data-sentient-persona` / `data-sentient-confidence` on <html> before\n * first paint. Single-writer: it never overwrites attributes already set.\n *\n * Safety properties (pinned by tests):\n * - apiKey goes through JSON.stringify, then '<' is escaped to <, so a\n * hostile key can neither break the JS string nor terminate the <script>.\n * - Built by string concatenation and contains no backticks, so the output\n * survives being embedded in template-literal-based renderers.\n */\nexport function renderPrePaintScript(apiKey: string): string {\n const key = JSON.stringify(SNAPSHOT_STORAGE_KEY_PREFIX + apiKey).replace(/</g, '\\\\u003c');\n return (\n '(function(){try{' +\n 'var r=localStorage.getItem(' + key + ');if(!r)return;' +\n 'var s=JSON.parse(r);' +\n 'if(!s||s.v!==1||typeof s.persona!==\"string\"||typeof s.band!==\"string\")return;' +\n 'var d=document.documentElement;' +\n 'if(d.hasAttribute(\"data-sentient-persona\"))return;' +\n 'd.setAttribute(\"data-sentient-persona\",s.persona);' +\n 'd.setAttribute(\"data-sentient-confidence\",s.band);' +\n '}catch(e){}})();'\n );\n}\n","import { initSession, type SessionConfig, type SessionManager } from './session';\nimport {\n createEventQueue,\n retryStorageKey,\n type EventQueue,\n type EventType,\n type QueueConfig,\n type SentientEvent,\n} from './queue';\nimport {\n createGoalQueue,\n goalRetryStorageKey,\n type GoalQueue,\n} from './goal-queue';\nimport {\n createAssignmentCache,\n type Assignment,\n} from './cache';\nimport type {\n GraphConfig,\n GraphSnapshot,\n} from './graph';\nimport {\n detectDeviceClass,\n detectTrafficSource,\n detectTimeOfDay,\n extractTrackedParams,\n referrerDomainFromReferer,\n uaTokenMatch,\n} from './session-meta.js';\nimport {\n toWireSlot,\n baselineResultFor,\n armOfResult,\n type SlotDeclInput,\n type SlotResult,\n} from './slots.js';\nimport { readSnapshot, writeSnapshot, SNAPSHOT_STORAGE_KEY_PREFIX, type SlotConfigEntry, type CompoundLocator } from './snapshot.js';\nimport { confidenceBand } from '@sentientui/policy';\nimport { createLocalModeClient } from './local-mode.js';\nimport { randomUuidV4 } from './uuid.js';\nimport { backoffDelayMs, classifyResponse } from './durable.js';\n\nexport { PROD_KEYLESS_ERROR, LOCAL_MODE_BANNER } from './local-mode.js';\n\n// The one source of truth for the session cookie's name — every out-of-package\n// reader (react devtools, integrator SSR code) must derive the name from this\n// instead of hard-coding `_snt_uid`, which is only the pre-namespacing fallback.\nexport { sessionCookieName, LEGACY_SESSION_COOKIE_NAME } from './storage-key.js';\n\nexport {\n detectDeviceClass,\n detectTrafficSource,\n detectTimeOfDay,\n deriveSessionSegment,\n extractTrackedParams,\n CLICK_ID_KEYS,\n referrerDomainFromReferer,\n uaTokenMatch,\n matchedAgentToken,\n agentUaList,\n agentIntent,\n classifiedAgents,\n AGENT_INTENTS,\n} from './session-meta.js';\nexport type { AgentIntent } from './session-meta.js';\n\nconst DEFAULT_INGEST_URL = 'https://api.sentient-ui.com/v1/events';\n\n// Keyed by apiKey so multiple init() calls (HMR, multi-project) don't collide.\nconst _clients = new Map<string, {\n config: SentientConfig;\n upgrade: ((c: SentientClient) => void) | null;\n // Why grantConsent() cannot upgrade this entry (keyless/local mode, invalid\n // key). grantConsent() warns with this instead of silently no-oping — a CMP\n // callback wired to it otherwise LOOKED like it worked while nothing ever\n // started tracking. Absent for entries where the silent no-op IS the\n // documented contract (DNT-blocked, already upgraded).\n upgradeBlockedReason?: string;\n // Set by an alternate entry point (the /graph entry) whose gated init\n // deferred extra resources: grantConsent() must re-run THAT entry's init —\n // upgrading through the lean init() produced a post-consent client that\n // never mounted the DOM scanner, silently losing graph capture.\n reinit?: (c: SentientConfig) => SentientClient;\n // Teardown for the live client bound to this key (stops its queue interval +\n // unload listeners). Present only for the full tracking client; the no-op /\n // pre-consent / local entries have nothing to tear down. Called before a\n // re-init for the same key replaces it, so timers/listeners can't leak.\n dispose?: () => void;\n}>();\nlet _lastApiKey: string | null = null;\n\n/**\n * @internal Wires an alternate entry point's init as grantConsent()'s upgrade\n * path for `apiKey`. The /graph entry calls this when its init is gated on\n * consent: without it, grantConsent() upgraded through the LEAN init, so a\n * graph-configured page granted consent but never mounted the scanner (the\n * graph resources exist only in the /graph entry). No-op unless the entry is\n * actually upgradeable — DNT-blocked and local entries register no hook.\n */\nexport function _registerConsentUpgradeInit(\n apiKey: string,\n reinit: (config: SentientConfig) => SentientClient,\n): void {\n const entry = _clients.get(apiKey);\n if (entry && entry.upgrade) entry.reinit = reinit;\n}\n\nexport type SentientConfig = {\n apiKey: string;\n context: 'landing' | 'ecommerce' | 'saas' | 'marketplace';\n /** @internal — not exposed to users; defaults to the hosted SentientUI API. */\n ingestUrl?: string;\n debug?: boolean;\n /**\n * Pre-seeded assignments from `preloadAssignments()` (SSR).\n * Seeds the local cache so `assign()` returns without a network call for\n * listed code variants, guaranteeing server and client render the same\n * variant on first paint. Managed-text components (assign with no\n * variantIds) still fetch once when the seed carries no content.\n */\n initialAssignments?: Record<string, string>;\n /**\n * Segment used for SSR preload (`device:source`). When set with `initialAssignments`,\n * seeds the assignment cache under this key so hydration matches the server bandit row.\n */\n sessionSegment?: string;\n /**\n * Consent gate. When `false`, returns a no-op client and performs no tracking.\n * Defaults to `true`. Re-call `init()` (via `AdaptiveProvider` consent prop) when\n * the user grants or revokes consent mid-session.\n */\n consent?: boolean;\n /**\n * Behavior before consent is granted. `'statistical_winner'` fetches the\n * best-performing variant via `GET /v1/winner` — no session or tracking data\n * is stored. `'control'` (default) shows `variantIds[0]` with no API call.\n * Applies when tracking is gated off — either `consent: false` or an active\n * Do Not Track signal.\n */\n preConsentBehavior?: 'statistical_winner' | 'control';\n /**\n * Whether to honor the browser's Do Not Track (DNT) signal. Defaults to `true`.\n * When `true` and the visitor has DNT enabled, the SDK sets no cookies and\n * sends no tracking data — behaving exactly as `consent: false` (still serving\n * the read-only `preConsentBehavior` winner if configured), and `grantConsent()`\n * will not upgrade it. Set `false` to make your own consent gate authoritative.\n */\n respectDoNotTrack?: boolean;\n userId?: string;\n /**\n * Declared persona — the role your app already knows for this visitor\n * (e.g. 'admin', 'evaluator'). Must be a key in the project's persona\n * vocabulary (dashboard → Settings → Personas); unrecognized values are\n * ignored server-side and surfaced in the dashboard so you can add them.\n * Declared personas are served at full confidence, overriding the inferred\n * one. Keep it a low-cardinality role label — never a user id or email.\n */\n persona?: string;\n /**\n * Session ID generated server-side (from `loadAdaptiveAssignments` / `loadAdaptiveDecision`).\n * When provided, the client adopts this ID on first visit instead of generating a new one,\n * ensuring events and goals are attributed to the same session the server used for assignment.\n */\n ssrSessionId?: string;\n /**\n * ISO 3166-1 alpha-2 country code for the visitor. When provided (e.g. from\n * the `CF-IPCountry` header in a Next.js server component), it is included in\n * the session upsert so country-based segmentation works without client-side\n * geo lookup.\n */\n country?: string;\n /**\n * Pre-seeded slot results from `preloadDecisions()` / `loadAdaptiveDecision()`\n * (SSR). Seeds the local slot state so `getSlotResult()` agrees with the\n * server-rendered markup on first paint.\n */\n initialSlots?: Record<string, SlotResult>;\n /**\n * Registry slot config decided outside the browser (SSR). Seeds\n * `getSlotConfig()` so server-rendered block/content arms survive hydration.\n */\n initialSlotConfig?: Record<string, SlotConfigEntry>;\n /** Site palette decided outside the browser (SSR). Seeds `getSitePalette()`. */\n initialPalette?: import('./blocks.js').SitePalette;\n /**\n * Persona decided during SSR. Takes priority over the html-attribute\n * adoption and the local snapshot.\n */\n initialPersona?: { persona: string; confidence: number };\n /**\n * Keyless local mode. 'auto' (default) simulates decisions on-device when no\n * valid API key is configured — but only in development builds (the\n * `development` export condition); production bundles physically exclude the\n * engine. `true` forces the local engine regardless of key (escape hatch);\n * `false` restores the silent keyless no-op.\n */\n localMode?: 'auto' | boolean;\n /**\n * Which integration surface is driving this client, and its released build\n * version — `{ name: 'react', version: '0.27.0' }`. Set by the wrapper\n * package (@sentientui/react, @sentientui/snippet), never by application\n * code: core is a dependency of both, so its own version says nothing about\n * what the customer installed.\n *\n * Rides the session upsert (the one call EVERY integration makes, unlike\n * decide, which only the slot paths use) so the dashboard can tell a project\n * its SDK is behind. Additive and best-effort: older API deployments ignore\n * the fields, and omitting it changes nothing.\n */\n sdk?: { name: string; version: string };\n};\n\nexport type AssignResult = { variantId: string; assignmentTtlMs: number; content?: string };\n\nexport type { SlotDeclInput, SlotResult };\nexport { armOfResult, baselineResultFor, baselineSlots, toWireSlot } from './slots.js';\n\nexport {\n SNAPSHOT_STORAGE_KEY_PREFIX,\n readSnapshot,\n writeSnapshot,\n renderPrePaintScript,\n} from './snapshot.js';\nexport type { DecisionSnapshot, SlotConfigEntry, SlotOps, CompoundLocator } from './snapshot.js';\nexport * from './blocks.js';\n\n/** An editor-defined goal delivered with a registry-mode decision, for the\n * snippet to install delegated listeners from. */\nexport type GoalDefinition = {\n goalId: string;\n event: 'click' | 'form_submit' | 'url_reached' | 'scroll_depth';\n locator?: CompoundLocator;\n urlPattern?: string;\n slotId?: string;\n /** scroll_depth only: fraction of the page (0–1] that counts as read. */\n threshold?: number;\n};\n\n/** One served section-classification row (registry mode): where it is on the\n * page (url match + compound locator) and its semantic type. The snippet\n * resolves the locator to build capture's `typeOf` hook. */\nexport type SectionMapEntry = {\n urlMatch: string;\n locator: CompoundLocator;\n type: string;\n};\n\nexport type DecideOutcome = {\n layoutOrder: string[] | null;\n assignments: Record<string, string>;\n slots: Record<string, SlotResult>;\n persona: string;\n confidence: number;\n // Registry mode only: where/what to apply for server-defined slots.\n slotConfig?: Record<string, SlotConfigEntry>;\n // Registry mode only: editor-defined goals to wire up.\n goals?: GoalDefinition[];\n // Registry mode only: served section-classification map the snippet turns\n // into capture's `typeOf` hook.\n sectionMap?: SectionMapEntry[];\n // Registry mode only: derived site palette for Composition Block rendering.\n palette?: import('./blocks.js').SitePalette;\n};\n\nexport type DecideInput = {\n sections?: string[];\n components?: Array<{ id: string; variantIds?: string[] }>;\n slots?: SlotDeclInput[];\n // 'registry' → serve the project's published slot_definitions in addition to\n // any declared slots (registry wins on id collision). Default 'request'.\n slotsFrom?: 'request' | 'registry';\n /**\n * Caller's build version (e.g. the snippet's `__SNIPPET_VERSION__`), sent\n * as `v` on the wire. Additive/best-effort: the server persists it for\n * version-skew reporting (see apps/api decide route) and ignores it\n * entirely on older deployments. Omit if the caller has no version to report.\n */\n v?: string;\n /**\n * Snippet only: which inline pre-paint contract the install carries\n * (`window.__sntPP.v`), or 0 for the two-tag install with no inline script.\n * Sent as `pp` alongside `v`; the server persists it for the dashboard's\n * install-health nudge and no serving behaviour depends on it. Additive —\n * older deployments ignore it entirely.\n */\n pp?: number;\n};\n\nexport type WeightEntry = { variantId: string; pulls: number; avgReward: number | null };\nexport type ComponentWeightEntry = { componentId: string; updatedAt: number; variants: WeightEntry[] };\n\n/** Options accepted by goal() (and inherited by componentGoal / the React\n * hooks / the snippet) — one shape everywhere (spec §5). */\nexport type GoalOptions = {\n /** Revenue of this conversion, in the project currency. */\n value?: number;\n /** ISO-4217 code, only when it differs from the project currency. */\n currency?: string;\n /** Merchant order/transaction id — dedupes retries, enables refunds later. */\n externalId?: string;\n /** Extra fields merged into the event payload / goal metadata. */\n metadata?: Record<string, unknown>;\n /** Advanced: partial-credit weight in [0,1] (composite steps). */\n weight?: number;\n /** Advanced: funnel step index (0-based). */\n stepIndex?: number;\n};\n\n/** goal()'s second arg is the options object iff it carries a reserved key;\n * anything else keeps the legacy bare-metadata interpretation. Reserved keys\n * inside legacy metadata were inert on the server, so reinterpretation is the\n * upgrade the sender wanted (spec §5). */\nfunction isGoalOptions(v: Record<string, unknown>): boolean {\n return 'value' in v || 'currency' in v || 'externalId' in v || 'metadata' in v || 'weight' in v || 'stepIndex' in v;\n}\n\nexport type ComponentGoalOptions = GoalOptions & {\n /** Reward credited to the served variant (0–1). Defaults to 1. */\n reward?: number;\n};\n\nexport type SentientClient = {\n track(\n event: Omit<SentientEvent, 'id' | 'sessionId' | 'timestamp' | 'timeInSession'>,\n ): void;\n goal(name: string, options?: GoalOptions): void;\n /** @deprecated positional form — prefer goal(name, options). */\n goal(name: string, metadata?: Record<string, unknown>, weight?: number, stepIndex?: number): void;\n /**\n * Records a conversion attributed to the variant currently served for\n * `componentId`, so it feeds the per-variant CVR funnel. Resolves the served\n * variant from the local assignment cache — no need to pass variantId or\n * projectId. No-ops if the component has not been assigned yet (render its\n * `<Adaptive>`/call `assign()` first). Prefer this over bare `goal()` for\n * variant experiments; `goal()` is session-level only (no component attribution).\n */\n componentGoal(componentId: string, goalType: string, opts?: ComponentGoalOptions): void;\n identify(userId: string): void;\n getAssignment(componentId: string, segment: string): Assignment | null;\n /** Server-side variant assignment. Caches the result locally per (component, segment). */\n assign(componentId: string, variantIds?: string[], agentData?: unknown, agentDataByVariant?: Record<string, unknown>): Promise<AssignResult | null>;\n /**\n * Single-roundtrip decision for layout sections, component variants, and\n * adaptive slots. Awaits the session upsert (like `assign`) so the server\n * never decides for a session row that doesn't exist yet. A response\n * without a `slots` field means the server predates slots — every declared\n * slot resolves to its baseline and no retry is made.\n */\n decide(input: DecideInput): Promise<DecideOutcome | null>;\n /** Slot result served this session (decide result, SSR seed, snapshot, or failure baseline). Null when unknown. */\n getSlotResult(slotId: string): SlotResult | null;\n /** Registry slot config served this session (content/ops/blocks for the slot).\n * Null until a registry-mode decide, SSR seed, or snapshot provides it. */\n getSlotConfig(slotId: string): SlotConfigEntry | null;\n /** Report mounted AdaptiveSlot ids the server has no config for, so they\n * auto-register as draft slots. Fire-and-forget, batched, deduped per\n * client — never blocks rendering and never throws. */\n reportSlots(slotIds: string[]): void;\n /** Site palette served with registry block decisions. Null when absent. */\n getSitePalette(): import('./blocks.js').SitePalette | null;\n /** Current persona estimate. Band is always `confidenceBand(confidence)`. Null when nothing is known yet. */\n getPersona(): { persona: string; confidence: number; band: 'low' | 'medium' | 'high' } | null;\n /** Fetches current bandit weights for all components in this project. Used by the provider to keep live-weight polling fresh. */\n fetchWeights(): Promise<ComponentWeightEntry[]>;\n getGraph(): GraphSnapshot;\n /**\n * Routine teardown: stops timers/listeners and flushes pending events, but\n * KEEPS the visitor identity, decision snapshot, and retry bucket. Use for\n * component unmount / re-init (framework providers call this on cleanup).\n */\n dispose(): void;\n /**\n * Consent-revocation / forget-me teardown: everything `dispose()` does,\n * plus deletion of the visitor identity (`_snt_uid`), the decision\n * snapshot, and the persisted retry bucket. The next visit starts as a\n * brand-new visitor.\n */\n destroy(): void;\n /** True when this client is the keyless local-mode client (dev only). */\n readonly isLocal?: boolean;\n};\n\n// SSR preload helpers moved to the `@sentientui/core/server` entry in 0.6.0 so\n// ~200 lines of Node-only fetch logic stop shipping in the browser bundle.\n\nexport { attachMicroSignalDetectors } from './micro-signals.js';\nexport type { MicroSignalEmitter, MicroSignalType } from './micro-signals.js';\n\nexport type {\n SessionConfig,\n SessionManager,\n EventType,\n SentientEvent,\n QueueConfig,\n Assignment,\n GraphSnapshot,\n GraphConfig,\n};\n\n/** Attempts after the first for the session upsert — see upsertSession. */\nconst SESSION_UPSERT_RETRIES = 3;\n\nfunction generateEventId(): string {\n return randomUuidV4();\n}\n\n/**\n * The page an event happened on.\n *\n * `location.pathname` ONLY — never `href` or `search`. Query strings on real\n * sites carry emails, reset tokens, order ids and session ids, and none of that\n * should leave the browser for analytics. The server strips them again\n * (domain/page-path.ts) because a hand-rolled integration can post whatever it\n * likes; this is the first of the two gates, and the one that means the data\n * never travels at all.\n *\n * Undefined outside a browser: during SSR there is no page to name, and an\n * invented one would be wrong for every reader.\n */\nfunction currentPath(): string | undefined {\n if (typeof window === 'undefined') return undefined;\n return window.location?.pathname || undefined;\n}\n\n/**\n * Decides whether a goal() call belongs to a user action already recorded.\n *\n * This used to be a clock: a latch cleared on the next macrotask, so anything\n * inside that window counted as \"the same action\". A clock cannot tell the two\n * cases apart, and it got the expensive one wrong. The window can be JUMPED —\n * a macrotask scheduled to close it runs after any timer already armed, which\n * every real page and every sibling test in a worker has — so a genuinely\n * separate conversion landed in a window that should have shut, and was\n * swallowed rather than queued: gone, with no retry able to rescue it because\n * nothing was ever handed to the queue.\n *\n * So ask the question the clock was approximating. Two nested components\n * reacting to one click are, exactly, two listeners in one event DISPATCH, and\n * the platform already hands us that identity: `window.event` is the same Event\n * object for every listener of one dispatch, a different object for the next\n * click, and undefined outside dispatch entirely. Keying on the event object\n * makes \"one action\" a fact rather than a deadline — no timer to lose a race\n * with, no fake-timer or background-throttle hazard, and no way for a second\n * click to be mistaken for the first however long the page stalls between them.\n *\n * Goals fired outside any dispatch (an effect on mount, a page goal) have no\n * event to key on, and fall back to one SYNCHRONOUS flush — closed on a\n * microtask. That is exact too, and for the same reason the old macrotask\n * window was not: microtasks always drain before the next macrotask, so no\n * pending timer can jump this window. A user cannot perform two actions inside\n * one synchronous block, so anything sharing it is a double-fire, not a repeat.\n *\n * A microtask is deliberately NOT used for the dispatch case: the HTML spec\n * runs a microtask checkpoint between listeners once the JS stack empties, so\n * it would split one click into two and double-count the revenue this collapse\n * exists to protect. That is why the dispatch case keys on the event instead,\n * and why a host without `window.event` keeps the old macrotask window — too\n * loose, but erring toward the old behaviour rather than toward double-counting.\n */\nfunction createActionLatch(): { firedBefore(actionKey: string, valueKey: string | null): boolean } {\n // Per window, per action key: the set of value identities already recorded.\n // An entry with an empty set means a VALUELESS fire was recorded. The split\n // exists because value cannot simply live inside one flat key: an inner\n // component declaring `value: 50` nested in an outer wrapper with the same\n // goal but NO value produced two distinct keys — two rows for one click.\n // Within a window, a valueless fire is absorbed by ANY record of the same\n // action (the valued row already carries the order, and a valueless\n // duplicate would inflate Hits), while a valued fire is collapsed only by an\n // IDENTICAL (value, currency) record — $50 and $70 stay two orders\n // (CONTRACTS §1). A valued fire landing AFTER a valueless one still records:\n // the valueless row has already been handed to the queue (often already on\n // the wire) and cannot be retracted, and losing the money would be the worse\n // error — server-side credit clamps the extra valueless hit at min(1, MAX\n // weight), so the cost is one inflated Hit, not corrupted revenue. Listener\n // order makes the benign ordering the common one: the inner (valued)\n // component's listener runs before the wrapper's in a bubbling dispatch.\n const byEvent = new WeakMap<object, Map<string, Set<string>>>();\n const byFlush = new Map<string, Set<string>>();\n let flushOpen = false;\n\n const alreadyRecorded = (map: Map<string, Set<string>>, actionKey: string, valueKey: string | null): boolean => {\n const values = map.get(actionKey);\n if (valueKey === null) {\n if (values) return true;\n map.set(actionKey, new Set());\n return false;\n }\n if (!values) {\n map.set(actionKey, new Set([valueKey]));\n return false;\n }\n if (values.has(valueKey)) return true;\n values.add(valueKey);\n return false;\n };\n\n // Standardised as Window.event and present in every current browser, but a\n // capability check keeps exotic hosts on the path they have always had.\n const hasWindowEvent = typeof window !== 'undefined' && 'event' in window;\n\n const currentEvent = (): object | undefined => {\n if (!hasWindowEvent) return undefined;\n const ev = (window as unknown as { event?: unknown }).event;\n // Must be a real same-realm Event, not merely an object. Window.event is\n // [Replaceable]: a classic script doing `event = {...}` at top level (an\n // implicit or sloppy global on plenty of host pages) permanently shadows\n // the accessor with a data property. Accepting any object then returned\n // that SAME object forever — its WeakMap entry never died, so every later\n // conversion looked like a re-fire of the first action and ALL repeat\n // conversions were silently dropped for the session. `instanceof Event`\n // cannot be true for such a literal; a cross-realm Event (iframe) fails it\n // too and merely falls back to the flush window, which is safe.\n // (typeof guard: a host with `window` but no Event constructor must fall\n // back, not throw a ReferenceError from inside goal().)\n return typeof Event === 'function' && ev instanceof Event ? ev : undefined;\n };\n\n const closeFlushWindow = (): void => {\n if (hasWindowEvent) {\n // A promise microtask, not queueMicrotask: fake-timer setups can replace\n // queueMicrotask, and this window closing is what keeps repeat\n // conversions from being swallowed.\n void Promise.resolve().then(() => {\n flushOpen = false;\n byFlush.clear();\n });\n return;\n }\n let done = false;\n const clear = (): void => {\n if (done) return;\n done = true;\n flushOpen = false;\n byFlush.clear();\n };\n setTimeout(clear, 0);\n if (typeof MessageChannel === 'function') {\n const ch = new MessageChannel();\n ch.port1.onmessage = () => {\n ch.port1.close();\n ch.port2.close();\n clear();\n };\n ch.port2.postMessage(0);\n }\n };\n\n return {\n firedBefore(actionKey: string, valueKey: string | null): boolean {\n const ev = currentEvent();\n if (ev) {\n let keys = byEvent.get(ev);\n if (!keys) {\n keys = new Map<string, Set<string>>();\n // Keyed weakly: the Map dies with the Event object, so a long session\n // of clicks accumulates nothing.\n byEvent.set(ev, keys);\n }\n return alreadyRecorded(keys, actionKey, valueKey);\n }\n const collapsed = alreadyRecorded(byFlush, actionKey, valueKey);\n if (!collapsed && !flushOpen) {\n flushOpen = true;\n closeFlushWindow();\n }\n return collapsed;\n },\n };\n}\n\n/**\n * Emits one `pageview` per page load and per SPA route change.\n *\n * Patches pushState/replaceState rather than polling: a route change is a\n * discrete event, and polling would either miss fast back-to-back navigations or\n * burn a timer on every page for the entire session. popstate covers\n * back/forward, which history patching does not see.\n *\n * Deduplicates on pathname, because frameworks routinely replaceState several\n * times for one navigation (query/hash updates, scroll restoration) and each\n * would otherwise look like another page in the visitor's journey.\n */\n/** Pages already recorded this page lifetime, keyed project:path. dispose()\n * flushes, so without this a consent-change or StrictMode re-init delivered a\n * second landing pageview for a page the previous client already sent. */\nconst emittedPages = new Set<string>();\n\nfunction startPageviewTracking(\n client: SentientClient,\n projectId: string,\n // Called with the page key after emitting; the caller marks emittedPages only\n // once the event reached a LIVE queue. Marking at emit time instead turned\n // StrictMode's mount→dispose→mount into zero delivered landings: the first\n // mount's event dies in the destroyed queue, and the mark suppressed the\n // second mount's — the one that actually ships.\n markDelivered: (key: string) => void,\n): () => void {\n const h = typeof window === 'undefined' ? null : window.history;\n if (!h) return () => undefined;\n\n // The stop handle exists because init() runs again on every consent change,\n // StrictMode double-invoke and HMR: without it each init wrapped history\n // again and the old wrapper kept emitting through the DISPOSED client, so one\n // navigation produced one pageview per init that ever happened.\n let stopped = false;\n let last: string | undefined;\n const emit = (): void => {\n if (stopped) return;\n const path = currentPath();\n if (!path || path === last) return;\n last = path;\n // '__page__' is a sentinel componentId: the ingest schema requires one, and\n // every component reader filters on variant_id IS NOT NULL or a specific\n // event_type, so it never surfaces as a component.\n client.track({ projectId, componentId: '__page__', eventType: 'pageview', payload: {} });\n markDelivered(`${projectId}:${path}`);\n };\n\n const installed: Array<['pushState' | 'replaceState', History['pushState'], History['pushState']]> = [];\n for (const name of ['pushState', 'replaceState'] as const) {\n const orig = h[name];\n const wrapper = function (this: History, ...a: unknown[]) {\n const r = (orig as (...x: unknown[]) => unknown).apply(this, a);\n emit();\n return r;\n } as History[typeof name];\n h[name] = wrapper;\n installed.push([name, orig, wrapper]);\n }\n window.addEventListener('popstate', emit);\n\n // The landing page itself — once per (project, path) per page lifetime. When\n // it was already sent, `last` is still primed so the next real navigation\n // emits exactly once.\n const landing = currentPath();\n if (landing && emittedPages.has(`${projectId}:${landing}`)) last = landing;\n else emit();\n\n return () => {\n if (stopped) return;\n stopped = true;\n window.removeEventListener('popstate', emit);\n for (const [name, orig, wrapper] of installed) {\n // Restore only while ours is still on top; if something wrapped over it,\n // unhooking would sever their chain — the stopped flag already makes ours\n // a passthrough.\n if (h[name] === wrapper) h[name] = orig;\n }\n };\n}\n\nconst SSR_CLIENT: SentientClient = {\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n getAssignment: () => null,\n assign: () => Promise.resolve(null),\n decide: () => Promise.resolve(null),\n getSlotResult: () => null,\n getSlotConfig: () => null,\n getSitePalette: () => null,\n reportSlots: () => undefined,\n getPersona: () => null,\n fetchWeights: () => Promise.resolve([]),\n getGraph: () => ({ pageNodes: [], capturedAt: 0 }),\n dispose: () => undefined,\n destroy: () => undefined,\n};\n\nfunction readTrackedParams(): {\n utmParams: Record<string, string>;\n clickIds: Record<string, string>;\n} {\n try {\n return extractTrackedParams(window.location.search);\n } catch {\n return { utmParams: {}, clickIds: {} };\n }\n}\n\nfunction deriveBaseUrl(ingestUrl: string): string {\n return ingestUrl.replace(/\\/events\\/?$/, '');\n}\n\n/**\n * Detects whether the visitor has signalled a tracking opt-out. Honors Global\n * Privacy Control (`navigator.globalPrivacyControl`) — the legally-enforceable\n * CCPA/CPRA signal — as well as Do Not Track (`navigator.doNotTrack`, the legacy\n * `window.doNotTrack` on older Firefox, and `navigator.msDoNotTrack` on old\n * IE/Edge). GPC is a boolean; DNT is opt-out only when explicitly `'1'`/`'yes'`.\n */\nexport function isDoNotTrackEnabled(): boolean {\n // GPC is a boolean flag, checked separately from the DNT string signals.\n if (\n typeof navigator !== 'undefined' &&\n (navigator as unknown as { globalPrivacyControl?: boolean }).globalPrivacyControl === true\n ) {\n return true;\n }\n const signals = [\n typeof navigator !== 'undefined' ? navigator.doNotTrack : undefined,\n typeof window !== 'undefined'\n ? (window as unknown as { doNotTrack?: string | null }).doNotTrack\n : undefined,\n typeof navigator !== 'undefined'\n ? (navigator as unknown as { msDoNotTrack?: string | null }).msDoNotTrack\n : undefined,\n ];\n return signals.some((v) => v === '1' || v === 'yes');\n}\n\n/**\n * Upgrades a pre-consent client (any client created with `consent: false`, in\n * either `preConsentBehavior` mode) to a fully-tracking client, in place and\n * with no page reload. Call this from your consent management platform callback.\n * For React apps, prefer updating the `consent` prop on `<AdaptiveProvider>`.\n * Pass `apiKey` to target a specific project; omit to upgrade the most-recently-initialized client.\n */\nexport function grantConsent(apiKey?: string): void {\n if (typeof window === 'undefined') return;\n\n const key = apiKey ?? _lastApiKey;\n if (!key) {\n console.warn('[sentient] grantConsent() called before init()');\n return;\n }\n\n const entry = _clients.get(key);\n if (!entry) {\n console.warn('[sentient] grantConsent() called before init()');\n return;\n }\n\n const { config, upgrade, reinit } = entry;\n if (!upgrade) {\n // Keyless/local and invalid-key clients register no upgrade hook — there\n // is no hosted client to swap in. This used to return SILENTLY, so a CMP\n // callback wired to grantConsent() looked like it worked while nothing\n // ever started tracking. DNT-blocked and already-upgraded entries carry no\n // reason and stay quiet: for them the no-op is the documented contract.\n if (entry.upgradeBlockedReason) console.warn(entry.upgradeBlockedReason);\n return;\n }\n\n // Honor an active Do Not Track signal — consent cannot override a global opt-out.\n if (config.respectDoNotTrack !== false && isDoNotTrackEnabled()) return;\n\n // Upgrade through the entry point that ran the gated init when one\n // registered itself (see _registerConsentUpgradeInit) — the lean init knows\n // nothing about that entry's extra resources (the /graph scanner).\n const fullClient = (reinit ?? init)({ ...config, consent: true });\n upgrade(fullClient);\n // init() just registered the full client (with its dispose) under `key`.\n // Preserve that dispose so a later re-init/teardown can still tear it down —\n // we only need to clear the upgrade hook now that consent is granted.\n const disposed = _clients.get(key)?.dispose;\n _clients.set(key, { config: { ...config, consent: true }, upgrade: null, dispose: disposed });\n}\n\nfunction createPreConsentProxy(config: SentientConfig): { proxy: SentientClient; setInner: (c: SentientClient) => void } {\n // 'control' (the default) must reach the network zero times before consent.\n // The proxy still exists so grantConsent() has something to upgrade in place\n // — without it, a site wanting no pre-consent traffic could only start\n // tracking by reloading the page.\n const servesWinner = config.preConsentBehavior === 'statistical_winner';\n const baseUrl = deriveBaseUrl(config.ingestUrl ?? DEFAULT_INGEST_URL);\n const authHeaders = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n } as const;\n\n // Pre-consent winners are read-only and render-driven: every mounted\n // component re-calls assign() on every render, and without a result cache or\n // in-flight coalescing each call refired GET /v1/winner — the full client\n // has inflightAssigns for exactly this. Successful winners are cached for\n // the pre-consent phase (the winner is stable, and a flip mid-visit would be\n // a variant flash anyway); failure fallbacks are NOT cached, so a recovering\n // server gets asked again. Both maps die with the proxy on upgrade.\n const winnerCache = new Map<string, AssignResult>();\n const inflightWinners = new Map<string, Promise<AssignResult | null>>();\n\n let inner: SentientClient = {\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n getAssignment: () => null,\n fetchWeights: () => Promise.resolve([]),\n assign(componentId, variantIds, _agentData?) {\n // Control mode: no request. Callers fall back to variantIds[0] through\n // ssrFallback, exactly as they did against the old no-op client.\n if (!servesWinner) return Promise.resolve(null);\n const cached = winnerCache.get(componentId);\n if (cached) return Promise.resolve(cached);\n return coalesce(inflightWinners, componentId, async (): Promise<AssignResult | null> => {\n try {\n const params = new URLSearchParams({ componentId });\n for (const v of variantIds ?? []) params.append('variantIds[]', v);\n const res = await fetch(`${baseUrl}/winner?${params.toString()}`, {\n headers: authHeaders,\n });\n if (!res.ok) return variantIds?.[0] ? { variantId: variantIds[0], assignmentTtlMs: 0 } : null;\n const body = (await res.json()) as { variantId: string };\n const result: AssignResult = { variantId: body.variantId, assignmentTtlMs: 0 };\n winnerCache.set(componentId, result);\n return result;\n } catch {\n return variantIds?.[0] ? { variantId: variantIds[0], assignmentTtlMs: 0 } : null;\n }\n });\n },\n decide: () => Promise.resolve(null),\n getSlotResult: () => null,\n getSlotConfig: () => null,\n getSitePalette: () => null,\n reportSlots: () => undefined,\n getPersona: () => null,\n getGraph: () => ({ pageNodes: [], capturedAt: 0 }),\n dispose: () => undefined,\n destroy: () => undefined,\n };\n\n const proxy: SentientClient = {\n track: (e) => inner.track(e),\n // Cast: a single arrow can't structurally satisfy the overloaded member;\n // the passthrough forwards both call shapes untouched.\n goal: ((n: string, m?: Record<string, unknown>, w?: number, s?: number) => inner.goal(n, m, w, s)) as SentientClient['goal'],\n componentGoal: (c, g, o) => inner.componentGoal(c, g, o),\n identify: (u) => inner.identify(u),\n getAssignment: (c, s) => inner.getAssignment(c, s),\n assign: (c, v, a, av) => inner.assign(c, v, a, av),\n decide: (i) => inner.decide(i),\n getSlotResult: (s) => inner.getSlotResult(s),\n getSlotConfig: (s) => inner.getSlotConfig(s),\n getSitePalette: () => inner.getSitePalette(),\n reportSlots: (ids) => inner.reportSlots(ids),\n getPersona: () => inner.getPersona(),\n fetchWeights: () => inner.fetchWeights(),\n getGraph: () => inner.getGraph(),\n dispose: () => inner.dispose(),\n destroy: () => inner.destroy(),\n };\n\n function setInner(fullClient: SentientClient) {\n inner = fullClient;\n }\n\n return { proxy, setInner };\n}\n\n/**\n * Shares one promise among concurrent calls with the same key — assign() and\n * decide() both use this so N same-tick requests (several mounted slots\n * sharing a component id; per-slot lazy decides) cost one roundtrip. The map\n * entry lives exactly as long as the request is in flight: a settled result\n * must NOT serve later calls (a sequential re-request is a fresh decision —\n * caching lives elsewhere), and a failed one must not wedge the key. No\n * `.finally()` — that's ES2018 and this file ships in the es2017 snippet\n * bundle (the SNIP-5 lesson).\n */\nfunction coalesce<T>(inflight: Map<string, Promise<T>>, key: string, run: () => Promise<T>): Promise<T> {\n const hit = inflight.get(key);\n if (hit) return hit;\n const request = (async (): Promise<T> => {\n try {\n return await run();\n } finally {\n inflight.delete(key);\n }\n })();\n inflight.set(key, request);\n return request;\n}\n\n/**\n * Initializes the Sentient client. Returns a no-op client during SSR.\n */\nexport function init(config: SentientConfig): SentientClient {\n if (typeof window === 'undefined') {\n return SSR_CLIENT;\n }\n\n // `|| 'local'`: keyless clients register under the 'local' fallback key\n // below, and `_lastApiKey = ''` is falsy — so a no-arg grantConsent() after\n // a keyless init() warned \"called before init()\" even though init() DID run,\n // instead of resolving that entry (and its blocked-upgrade explanation).\n _lastApiKey = config.apiKey || 'local';\n\n // A re-init for the same key (HMR, consent toggle, provider remount) supersedes\n // the prior client. Dispose it first so its queue's setInterval and\n // visibilitychange/pagehide listeners don't leak — the map only ever held its\n // config, so without this the old client kept flushing forever.\n const prevEntry = _clients.get(config.apiKey || 'local');\n if (prevEntry?.dispose) {\n try {\n prevEntry.dispose();\n } catch {\n /* teardown must never throw on re-init */\n }\n }\n\n // DNT/GPC (a global opt-out) or an explicit `consent: false` must be evaluated\n // BEFORE the local-mode branch: createLocalModeClient() calls initSession()\n // unconditionally, so a gated visitor would otherwise be issued the 365-day\n // `_snt_uid` identity cookie in keyless/local mode (audit P2). DNT/GPC also\n // gates tracking off even when the site passes `consent: true`, and blocks\n // `grantConsent()` from upgrading.\n const dntBlocked = config.respectDoNotTrack !== false && isDoNotTrackEnabled();\n const gated = config.consent === false || dntBlocked;\n\n // Keyless local mode. `localMode: true` forces the local engine (documented\n // escape hatch); 'auto' (default) engages it only when no valid key is\n // present. In production builds `@sentientui/core/local` resolves to a stub\n // and this degrades to a no-op client + one console.error per page load\n // (createLocalModeClient handles that), so no NODE_ENV check is needed here.\n const keyValid = typeof config.apiKey === 'string' && config.apiKey.startsWith('pk_');\n if (config.localMode === true || (!keyValid && config.localMode !== false)) {\n // One registration for both arms (they used to duplicate this set call).\n // upgrade stays null — there is no hosted client to swap in — but the\n // reason lets grantConsent() explain that instead of no-oping silently.\n _clients.set(config.apiKey || 'local', {\n config,\n upgrade: null,\n upgradeBlockedReason:\n '[sentient] grantConsent(): this client is keyless/local — there is no hosted client to upgrade to. Configure a pk_ API key to enable tracking.',\n });\n // A gated visitor must never get the identity cookie. Local mode has no\n // server to serve a statistical winner from, so return a plain no-op.\n if (gated) return SSR_CLIENT;\n return createLocalModeClient(config);\n }\n\n if (gated) {\n if (!config.apiKey || !config.apiKey.startsWith('pk_')) {\n if (config.preConsentBehavior === 'statistical_winner') {\n console.warn('[sentient] init() called with an invalid apiKey — expected a pk_ public key. SDK disabled.');\n }\n // Registered under the same 'local' fallback key the local branch uses:\n // `config.apiKey` here can be '', and a ''-keyed entry was unreachable\n // by a no-arg grantConsent() (falsy `_lastApiKey`), which then wrongly\n // warned \"called before init()\".\n _clients.set(config.apiKey || 'local', {\n config,\n upgrade: null,\n upgradeBlockedReason:\n '[sentient] grantConsent(): the client was initialized with an invalid apiKey (expected a pk_ public key) — consent cannot enable tracking.',\n });\n return SSR_CLIENT;\n }\n // Every gated client gets an upgradeable proxy, not just the winner-serving\n // one — otherwise grantConsent() is silently dead for the 'control' default\n // and the site has to reload to start tracking. Control mode still makes no\n // request; the proxy only exists so consent can swap the inner client.\n const { proxy, setInner } = createPreConsentProxy(config);\n // Under DNT the read-only winner still serves, but consent can never\n // upgrade it to tracking — so drop the upgrade hook.\n _clients.set(config.apiKey, { config, upgrade: dntBlocked ? null : setInner });\n return proxy;\n }\n\n if (!config.apiKey || !config.apiKey.startsWith('pk_')) {\n console.warn('[sentient] init() called with an invalid apiKey — expected a pk_ public key. SDK disabled.');\n return SSR_CLIENT;\n }\n\n if (config.ingestUrl === '') {\n console.warn('[sentient] init() called with an empty ingestUrl. SDK disabled.');\n return SSR_CLIENT;\n }\n\n const resolvedIngestUrl = config.ingestUrl ?? DEFAULT_INGEST_URL;\n\n const sessionStart = Date.now();\n const session = initSession({ ssrSessionId: config.ssrSessionId, apiKey: config.apiKey });\n const assignmentCache = createAssignmentCache(undefined, config.apiKey);\n const eventQueue = createEventQueue({ ingestUrl: resolvedIngestUrl, apiKey: config.apiKey });\n const baseUrl = deriveBaseUrl(resolvedIngestUrl);\n\n const authHeaders = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n } as const;\n\n // Conversions get the same durable transport the event queue has always had:\n // retry with backoff, a cross-reload bucket, dedupe on the server's goalId.\n // One warning per distinct drop status per page load (see onDrop below).\n const warnedDropStatuses = new Set<number>();\n const goalQueue: GoalQueue = createGoalQueue({\n url: `${baseUrl}/goals`,\n apiKey: config.apiKey,\n headers: authHeaders,\n onDrop: (goal, status) => {\n // NOT debug-gated. CONTRACTS §7 says a dropped goal is reported to the\n // developer and never swallowed, but this returned early unless debug was\n // on — so in production a rate-limited or misconfigured project lost every\n // conversion with no signal anywhere. Warn once per (status, page load):\n // enough to be discoverable in a console or an error reporter, quiet\n // enough that a broken integration cannot flood the page.\n if (!config.debug) {\n if (warnedDropStatuses.has(status)) return;\n warnedDropStatuses.add(status);\n }\n console.warn(\n `[sentient] goal dropped (HTTP ${status}) — this will not be retried. ` +\n (status === 400\n ? 'The session was not found: call init() and let the session upsert complete before firing goals.'\n : status === 401 || status === 403\n ? 'Check the API key and that this origin is on the project allowlist.'\n : 'See the response status for the cause.'),\n goal,\n );\n },\n });\n\n const deviceClass = detectDeviceClass(navigator.userAgent ?? '');\n // No window guard here: init() already returned SSR_CLIENT at the top when\n // window is undefined, so the old `typeof window` ternary was dead code.\n const appOrigin = window.location.origin;\n const trafficSource = detectTrafficSource(document.referrer ?? '', appOrigin);\n const sessionSegment =\n config.sessionSegment ?? `${deviceClass}:${trafficSource}`;\n const inflightAssigns = new Map<string, Promise<AssignResult | null>>();\n // Mirrors inflightAssigns for decide(): per-slot lazy-decide patterns (see\n // local-mode's merge comment — one decide({ slots: [decl] }) per mounted\n // slot, all in the same tick) otherwise issue N roundtrips and N snapshot\n // rewrites for one page. Keyed by the full request payload, never just \"a\n // decide is running\": coalescing {slots:[a]} with {slots:[b]} would hand\n // slot b's caller an outcome that never decided b.\n const inflightDecides = new Map<string, Promise<DecideOutcome | null>>();\n\n // --- Adaptive-slot state (decide) ---\n // Results served for this session, keyed by slot id. Written by decide();\n // read by getSlotResult() (Task 3.3) and componentGoal's slot fallback.\n const slotStore = new Map<string, SlotResult>();\n // Registry slot config (content/ops/blocks) served this session. Written by\n // decide() and the SSR/snapshot seeds below; read by getSlotConfig() so SDK\n // surfaces (AdaptiveSlot) can render server-authored arms — previously the\n // snippet was the only consumer and this never left the decide outcome.\n const slotConfigStore = new Map<string, SlotConfigEntry>();\n let sitePalette: import('./blocks.js').SitePalette | null = null;\n let personaState: { persona: string; confidence: number } | null = null;\n // First-seen slot registration (reportSlots): once per id per client.\n const reportedSlotIds = new Set<string>();\n const pendingSlotReports = new Set<string>();\n let slotReportTimer: ReturnType<typeof setTimeout> | null = null;\n\n // On decide failure every declared slot must still resolve — to its baseline.\n // Never overwrite a previously served result.\n const seedSlotBaselines = (decls: SlotDeclInput[]): void => {\n for (const d of decls) {\n if (!slotStore.has(d.id)) slotStore.set(d.id, baselineResultFor(d));\n }\n };\n\n // Seed slot/persona state. Priority: explicit SSR seeds → snapshot.\n if (config.initialSlots) {\n for (const [slotId, result] of Object.entries(config.initialSlots)) {\n slotStore.set(slotId, result);\n }\n }\n if (config.initialSlotConfig) {\n for (const [slotId, entry] of Object.entries(config.initialSlotConfig)) {\n slotConfigStore.set(slotId, entry);\n }\n }\n if (config.initialPalette) sitePalette = config.initialPalette;\n const seedSnapshot = readSnapshot(config.apiKey);\n if (seedSnapshot) {\n for (const [slotId, result] of Object.entries(seedSnapshot.slots)) {\n if (!slotStore.has(slotId)) slotStore.set(slotId, result);\n }\n for (const [slotId, entry] of Object.entries(seedSnapshot.slotConfig ?? {})) {\n if (!slotConfigStore.has(slotId)) slotConfigStore.set(slotId, entry);\n }\n if (!sitePalette && seedSnapshot.palette) sitePalette = seedSnapshot.palette;\n }\n\n // Band-only persona sources (html attrs, snapshot) become a band-consistent\n // numeric confidence so confidenceBand(confidence) always equals the band.\n const BAND_CONFIDENCE: Record<string, number> = { low: 0.15, medium: 0.5, high: 0.85 };\n if (config.initialPersona) {\n personaState = { ...config.initialPersona };\n } else {\n // Single-writer rule: the inline pre-paint script owns the <html>\n // attributes. The client ADOPTS them as truth and never rewrites them\n // mid-session (next visit's script picks up the new snapshot instead).\n const ds = document.documentElement.dataset;\n if (ds.sentientPersona) {\n personaState = {\n persona: ds.sentientPersona,\n confidence: BAND_CONFIDENCE[ds.sentientConfidence ?? 'low'] ?? 0.15,\n };\n } else if (seedSnapshot) {\n personaState = {\n persona: seedSnapshot.persona,\n confidence: BAND_CONFIDENCE[seedSnapshot.band] ?? 0.15,\n };\n }\n }\n\n // Seed SSR-preloaded assignments into the local cache so assign() finds a\n // cache hit immediately — no network call, no variant flash on hydration.\n if (config.initialAssignments) {\n for (const [componentId, variantId] of Object.entries(config.initialAssignments)) {\n assignmentCache.set(componentId, sessionSegment, {\n variantId,\n assignedAt: Date.now(),\n segment: sessionSegment,\n confidence: 1,\n });\n }\n }\n\n // Upsert session metadata once on init. assign() awaits this promise so\n // the server isn't asked to assign for a session row that doesn't exist yet.\n let sessionReady: Promise<void> = Promise.resolve();\n\n const sessionId = session.getSessionId();\n if (sessionId) {\n const referrerDomain = referrerDomainFromReferer(document.referrer ?? '');\n const { utmParams, clickIds } = readTrackedParams();\n const sessionBody = {\n sessionId,\n deviceClass,\n trafficSource,\n referrerDomain,\n utmParams,\n // Ad-platform click IDs (gclid & co). Captured separately from utmParams\n // because Google Ads auto-tagging appends ONLY gclid — without this,\n // paid search with no manual UTM template reported as organic.\n clickIds,\n timeOfDay: detectTimeOfDay(new Date()),\n dayOfWeek: ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][new Date().getDay()],\n ephemeral: session.isEphemeral(),\n // Likely-automation hint: navigator.webdriver (set under automation\n // control) or a known agent token in the UA. Probabilistic — used for\n // metrics + bandit exclusion server-side, never to change what's served.\n automation:\n (typeof navigator !== 'undefined' && navigator.webdriver === true) ||\n uaTokenMatch(navigator.userAgent ?? ''),\n ...(config.userId ? { userId: config.userId } : {}),\n ...(config.persona ? { persona: config.persona } : {}),\n ...(config.country ? { country: config.country } : {}),\n // Version-skew reporting. Only a wrapper that knows its own released\n // version sets this — a dev-sentinel version is dropped by the caller,\n // not smuggled through, or the dashboard would read it as \"behind\"\n // forever (the same trap the snippet's decide reporting documents).\n ...(config.sdk ? { sdk: config.sdk.name, sdkVersion: config.sdk.version } : {}),\n };\n // The session row is a PRECONDITION for every conversion: /v1/goals answers\n // 400 session_not_found without it, and the durable queue classifies a 4xx\n // as terminal — so a session upsert that fails silently turns every later\n // conversion into a permanent drop. That is not hypothetical: /v1/sessions\n // carries the same per-IP limiter as /v1/goals, so under shared egress\n // (offices, mobile carriers, corporate NAT) the SESSION call 429s first and\n // the goals that follow are dropped for good. Retry it, so a transient\n // failure costs a moment rather than the visit's whole conversion history.\n const upsertSession = async (): Promise<undefined> => {\n for (let attempt = 0; ; attempt++) {\n try {\n const res = await fetch(`${baseUrl}/sessions`, {\n method: 'POST',\n keepalive: true,\n body: JSON.stringify(sessionBody),\n headers: authHeaders,\n });\n if (res.status === 402) {\n console.warn(\n '[SentientUI] Session limit exceeded for this project. The bandit will stop learning until the limit resets. Upgrade at sentient-ui.com/pricing',\n );\n return undefined; // terminal: retrying a quota will not clear it\n }\n if (res.ok || classifyResponse(res) === 'dropped') return undefined;\n } catch {\n /* network failure — same retry path as a 5xx */\n }\n if (attempt >= SESSION_UPSERT_RETRIES) {\n // Say so once: from here every conversion this visit will 400, and\n // that used to be entirely silent.\n console.warn(\n '[SentientUI] Could not register the session after retries. Conversions in this visit may not be recorded.',\n );\n return undefined;\n }\n await new Promise((r) => setTimeout(r, backoffDelayMs(attempt + 1)));\n }\n };\n try {\n sessionReady = upsertSession();\n } catch {\n /* never throw on init */\n }\n }\n\n if (config.debug) {\n console.log('[sentient] initialized', { context: config.context });\n (\n window as unknown as {\n __sentient?: {\n client: SentientClient;\n queue: EventQueue;\n };\n }\n ).__sentient = {\n client: null as unknown as SentientClient,\n queue: eventQueue,\n };\n }\n\n // One user action must record ONE session-level conversion per goal name.\n // Nested components each fire their declared goal on the same click (every\n // <Adaptive>/hook path calls componentGoal() AND goal()), so a hero nested\n // inside a CTA wrapper wrote two goal_events rows for one click: harmless for\n // a weight-1.0 goal (close-out clamps at 1) but a 0.3-weight step summed to\n // 0.6, and the Goals page counts Hits as COUNT(*) either way.\n //\n // The scope is one event dispatch, not one session and no longer one task:\n // see createActionLatch. A session-wide latch would swallow genuine repeat\n // conversions (two purchases in one visit are two conversions), and a\n // time-boxed one did exactly that whenever the box outlived the action.\n // Calls carrying distinct externalIds are never collapsed — those are, by\n // definition, distinct orders.\n const firedThisAction = createActionLatch();\n\n // Set once tracking starts; dispose() and destroy() call it so a replaced\n // client stops watching history instead of emitting forever. The flag records\n // teardown for the delivery-marking below, which runs on a later microtask.\n let stopPageviews: (() => void) | null = null;\n let pageviewsTornDown = false;\n\n const client: SentientClient = {\n goal(name: string, metadataOrOpts: Record<string, unknown> = {}, weight = 1.0, stepIndex = 0) {\n const sid = session.getSessionId();\n if (!sid) return;\n const opts: GoalOptions = isGoalOptions(metadataOrOpts)\n ? (metadataOrOpts as GoalOptions)\n : { metadata: metadataOrOpts };\n // stepIndex and weight are part of the key: funnel steps share a goal\n // name and differ by stepIndex, so collapsing on name alone dropped a\n // step fired in the same handler. Only IDENTICAL calls are one action.\n //\n // value and currency key the payload SEPARATELY (the latch's valueKey),\n // not as more segments of the flat key, and for a costlier reason: they\n // are the money. Flattened out of the key entirely, a $50 order and a\n // $70 order landing in one window collapsed into a single $50 record —\n // the client half of CONTRACTS §1 \"two orders are worth two orders\",\n // which the server already honours (migration 119 records the repeat if\n // it arrives; this is what stopped it arriving). But flattened INTO the\n // key, a valued inner component nested in a valueless wrapper made two\n // keys out of one click — two rows for one order. So the latch compares\n // values only between valued fires ($50 vs $70 stays two records) and\n // lets a valued record absorb a valueless re-fire of the same action\n // (see createActionLatch for the one asymmetric case).\n //\n // NOT metadata: every nested <Adaptive>/hook path stamps its own\n // componentId and variantId in there, so keying on it would make the\n // duplicate this latch exists to collapse look distinct again.\n const actionKey = [\n name,\n opts.externalId ?? '',\n opts.stepIndex ?? stepIndex,\n opts.weight ?? weight,\n ].join('\\0');\n const valueKey = opts.value !== undefined ? `${opts.value}\\0${opts.currency ?? ''}` : null;\n if (firedThisAction.firedBefore(actionKey, valueKey)) {\n if (config.debug) {\n console.log(`[sentient] goal(\"${name}\") already recorded for this action — not sent twice`);\n }\n return;\n }\n const goalId = generateEventId();\n // undefined values vanish at JSON.stringify time, so optional fields\n // need no conditional assembly.\n const body = {\n sessionId: sid,\n name,\n metadata: opts.metadata ?? {},\n weight: opts.weight ?? weight,\n stepIndex: opts.stepIndex ?? stepIndex,\n goalId,\n value: opts.value,\n currency: opts.currency,\n externalId: opts.externalId,\n };\n if (config.debug) {\n console.log('[sentient] goal', body);\n }\n // Serialize once, here: the queued copy must replay byte-identically\n // (same goalId) so a retry dedupes server-side instead of double-counting.\n const payload = { id: goalId, body: JSON.stringify(body) };\n sessionReady.then(() => goalQueue.send(payload));\n },\n\n componentGoal(componentId, goalType, opts) {\n const sid = session.getSessionId();\n if (!sid) return;\n // Variant experiments resolve from the assignment cache; adaptive slots\n // (useAdaptiveTokens / AdaptiveGroup) resolve from the slot state, using\n // the canonical arm string as the attributed variantId.\n const assignment = assignmentCache.get(componentId, sessionSegment);\n const slotResult = assignment ? null : slotStore.get(componentId) ?? null;\n if (!assignment && slotResult === null) {\n if (config.debug) {\n console.warn(\n `[sentient] componentGoal(\"${componentId}\"): no assignment or slot decision yet — render its <Adaptive>/adaptive hook or call assign()/decide() before recording a goal.`,\n );\n }\n return;\n }\n const attributedVariantId = assignment ? assignment.variantId : armOfResult(slotResult!);\n const fullEvent: SentientEvent = {\n id: generateEventId(),\n sessionId: sid,\n projectId: config.apiKey,\n componentId,\n variantId: attributedVariantId,\n eventType: 'goal_achieved',\n goalType,\n // undefined goalValue/currency vanish at JSON.stringify time.\n payload: {\n reward: opts?.reward ?? 1,\n goalValue: opts?.value,\n currency: opts?.currency,\n ...(opts?.metadata ?? {}),\n },\n timestamp: Date.now(),\n timeInSession: Date.now() - sessionStart,\n path: currentPath(),\n };\n if (config.debug) {\n console.log('[sentient] componentGoal', fullEvent);\n }\n sessionReady.then(() => eventQueue.push(fullEvent));\n },\n\n identify(userId) {\n const sid = session.getSessionId();\n if (!sid) return;\n sessionReady.then(() => {\n fetch(`${baseUrl}/sessions`, {\n method: 'POST',\n keepalive: true,\n body: JSON.stringify({ sessionId: sid, userId, ephemeral: session.isEphemeral() }),\n headers: authHeaders,\n }).catch(() => undefined);\n });\n },\n\n track(event) {\n const sessionId = session.getSessionId();\n if (!sessionId) return;\n\n const fullEvent: SentientEvent = {\n // `path` first so an explicit event.path from the caller wins over the\n // ambient one — a server-side or replayed event knows its page better\n // than location does.\n path: currentPath(),\n ...event,\n id: generateEventId(),\n sessionId,\n timestamp: Date.now(),\n timeInSession: Date.now() - sessionStart,\n };\n\n if (config.debug) {\n console.log('[sentient] track', fullEvent);\n }\n\n sessionReady.then(() => eventQueue.push(fullEvent));\n },\n\n getAssignment(componentId, segment) {\n return assignmentCache.get(componentId, segment);\n },\n\n async assign(componentId, variantIds, agentData?, agentDataByVariant?) {\n const sid = session.getSessionId();\n if (!sid) return null;\n\n const cached = assignmentCache.get(componentId, sessionSegment);\n // When variantIds are provided (A/B code variant), a cache hit is always final.\n // When variantIds are absent (managed text component), only hit the cache if content\n // is present — a seed from initialAssignments has no content and must still fetch.\n if (cached && (variantIds?.length || cached.content !== undefined)) {\n // Surface the entry's remaining TTL (server-provided when set) instead of\n // a hardcoded 0, so callers can reason about when a re-assign is due.\n const remainingTtlMs =\n cached.ttlMs && cached.ttlMs > 0\n ? Math.max(0, cached.assignedAt + cached.ttlMs - Date.now())\n : 0;\n return { variantId: cached.variantId, assignmentTtlMs: remainingTtlMs, content: cached.content };\n }\n\n // Coalesce concurrent assigns for the same component (e.g. several\n // mounted slots sharing one id) into a single network request.\n return coalesce(inflightAssigns, componentId, async () => {\n await sessionReady;\n try {\n const body: Record<string, unknown> = { sessionId: sid, componentId, variantIds };\n if (agentDataByVariant !== undefined) body.agentDataByVariant = agentDataByVariant;\n else if (agentData !== undefined) body.agentData = agentData;\n const res = await fetch(`${baseUrl}/assign`, {\n method: 'POST',\n body: JSON.stringify(body),\n headers: authHeaders,\n });\n if (!res.ok) return null;\n const result = (await res.json()) as AssignResult;\n assignmentCache.set(componentId, sessionSegment, {\n variantId: result.variantId,\n assignedAt: Date.now(),\n segment: sessionSegment,\n confidence: 1,\n content: result.content,\n // Honor the server's TTL as this entry's expiry; omit when absent/0\n // so the cache falls back to its default (DEFAULT_TTL_MS).\n ...(result.assignmentTtlMs && result.assignmentTtlMs > 0\n ? { ttlMs: result.assignmentTtlMs }\n : {}),\n });\n return result;\n } catch {\n return null;\n }\n });\n },\n\n async decide(input) {\n const sid = session.getSessionId();\n if (!sid) return null;\n const declared = input.slots ?? [];\n\n const body: Record<string, unknown> = { sessionId: sid };\n if (input.sections && input.sections.length > 0) {\n body.sections = input.sections.map((id) => ({ id }));\n }\n body.components = input.components ?? [];\n if (declared.length > 0) body.slots = declared.map(toWireSlot);\n if (input.slotsFrom === 'registry') body.slotsFrom = 'registry';\n if (input.v) body.v = input.v;\n // 0 is meaningful here (a two-tag snippet install with no inline pre-paint\n // script), so this is a presence check, not a truthiness one.\n if (typeof input.pp === 'number') body.pp = input.pp;\n // Declared persona rides on decide too: SSR-first flows can race the\n // session upsert, and the decide-body value wins for this decision.\n if (config.persona) body.persona = config.persona;\n\n // Coalesce concurrent IDENTICAL decides into one request + one snapshot\n // write, keyed by the serialized wire payload (also reused as the fetch\n // body). Never key on just \"a decide is running\": coalescing {slots:[a]}\n // with {slots:[b]} would hand slot b's caller an outcome that never\n // decided b. The wire projection is a safe key even though toWireSlot\n // strips SDK-only decl fields — the baselines synthesized for omitted\n // slots below go through baselineResultFor, which projects with the\n // same toWireSlot, so identical payloads imply identical outcomes.\n const decideKey = JSON.stringify(body);\n return coalesce(inflightDecides, decideKey, async () => {\n await sessionReady;\n try {\n const res = await fetch(`${baseUrl}/decide`, {\n method: 'POST',\n body: decideKey,\n headers: authHeaders,\n });\n if (!res.ok) {\n seedSlotBaselines(declared);\n return null;\n }\n const data = (await res.json()) as {\n layoutOrder?: string[] | null;\n assignments?: Record<string, string>;\n slots?: Record<string, SlotResult>;\n slotConfig?: Record<string, SlotConfigEntry>;\n goals?: GoalDefinition[];\n sectionMap?: SectionMapEntry[];\n palette?: import('./blocks.js').SitePalette;\n persona?: string;\n confidence?: number;\n };\n\n const slots: Record<string, SlotResult> = {};\n for (const d of declared) {\n // `data.slots === undefined` means the server predates the slots\n // contract: serve the declared baseline everywhere, do NOT retry.\n // (Distinct from `slots: {}`, which also falls back per-slot.)\n const served = data.slots?.[d.id];\n if (served !== undefined) {\n slots[d.id] = served;\n slotStore.set(d.id, served);\n continue;\n }\n // Slot omitted from the response → synthesize a baseline for the\n // RETURN value, but apply the same never-overwrite rule the failure\n // path (seedSlotBaselines) has always had. This success path used\n // to write unconditionally, so a partial response — or a pre-slots\n // server — clobbered SSR-seeded and previously-served results with\n // synthetic baselines.\n const prior = slotStore.get(d.id);\n if (prior !== undefined) {\n slots[d.id] = prior;\n } else {\n const baseline = baselineResultFor(d);\n slots[d.id] = baseline;\n slotStore.set(d.id, baseline);\n }\n }\n // Registry mode: the server returns slots the request never declared.\n // Take them verbatim (classic mode returns only declared slots, so this\n // union is a no-op there — back-compatible). These are real served\n // results, so they may overwrite the store, unlike the baselines above.\n if (data.slots) {\n for (const [slotId, result] of Object.entries(data.slots)) {\n if (!(slotId in slots)) {\n slots[slotId] = result;\n slotStore.set(slotId, result);\n }\n }\n }\n // Only overwrite persona when the response actually carries one, and\n // never downgrade a known persona to 'unknown' — a decide that omits\n // persona (or returns 'unknown') must not clobber a good SSR/snapshot/\n // initialPersona value, and must not persist that regression below.\n const known = personaState != null && personaState.persona !== 'unknown';\n if (data.persona && !(data.persona === 'unknown' && known)) {\n personaState = { persona: data.persona, confidence: data.confidence ?? 0 };\n } else if (!personaState) {\n personaState = { persona: 'unknown', confidence: 0 };\n }\n\n // Seed component assignments so <Adaptive>/assign() agree with this\n // decide (same shape as the initialAssignments seed above).\n for (const [componentId, variantId] of Object.entries(data.assignments ?? {})) {\n assignmentCache.set(componentId, sessionSegment, {\n variantId,\n assignedAt: Date.now(),\n segment: sessionSegment,\n confidence: 1,\n });\n }\n\n // Registry slot config accumulates like slotStore does: a later\n // decide that omits slotConfig (classic mode, partial response) must\n // not evict entries an earlier registry decide served.\n if (data.slotConfig) {\n for (const [slotId, entry] of Object.entries(data.slotConfig)) {\n slotConfigStore.set(slotId, entry);\n }\n }\n if (data.palette) sitePalette = data.palette;\n\n // Persist for the next visit's pre-paint (SPA cache-first pattern).\n writeSnapshot(config.apiKey, {\n v: 1,\n persona: personaState.persona,\n band: confidenceBand(personaState.confidence),\n slots: Object.fromEntries(slotStore),\n layoutOrder: data.layoutOrder ?? null,\n savedAt: Date.now(),\n ...(slotConfigStore.size > 0 ? { slotConfig: Object.fromEntries(slotConfigStore) } : {}),\n ...(sitePalette ? { palette: sitePalette } : {}),\n });\n\n return {\n layoutOrder: data.layoutOrder ?? null,\n assignments: data.assignments ?? {},\n slots,\n persona: personaState.persona,\n confidence: personaState.confidence,\n ...(data.slotConfig ? { slotConfig: data.slotConfig } : {}),\n ...(data.goals ? { goals: data.goals } : {}),\n ...(data.sectionMap ? { sectionMap: data.sectionMap } : {}),\n ...(data.palette ? { palette: data.palette } : {}),\n };\n } catch {\n seedSlotBaselines(declared);\n return null;\n }\n });\n },\n\n getSlotResult(slotId) {\n return slotStore.get(slotId) ?? null;\n },\n\n getSlotConfig(slotId) {\n return slotConfigStore.get(slotId) ?? null;\n },\n\n reportSlots(slotIds) {\n // Batch a tick's worth of AdaptiveSlot mounts into one request, once per\n // id per client lifetime — a page of N slots must not fire N registrations\n // on every navigation.\n for (const id of slotIds) {\n if (typeof id === 'string' && id.length > 0 && !reportedSlotIds.has(id)) {\n reportedSlotIds.add(id);\n pendingSlotReports.add(id);\n }\n }\n if (pendingSlotReports.size === 0 || slotReportTimer != null) return;\n slotReportTimer = setTimeout(() => {\n slotReportTimer = null;\n const batch = [...pendingSlotReports].slice(0, 20);\n pendingSlotReports.clear();\n if (batch.length === 0) return;\n void fetch(`${baseUrl}/slots/observed`, {\n method: 'POST',\n headers: authHeaders,\n body: JSON.stringify({ slotIds: batch }),\n }).catch(() => undefined);\n }, 1000);\n },\n\n getSitePalette() {\n return sitePalette;\n },\n\n getPersona() {\n if (!personaState) return null;\n return {\n persona: personaState.persona,\n confidence: personaState.confidence,\n band: confidenceBand(personaState.confidence),\n };\n },\n\n async fetchWeights() {\n try {\n const res = await fetch(`${baseUrl}/weights`, { headers: authHeaders });\n if (!res.ok) return [];\n const data = (await res.json()) as { components: ComponentWeightEntry[] };\n return data.components ?? [];\n } catch {\n return [];\n }\n },\n\n getGraph() {\n return { pageNodes: [], capturedAt: 0 };\n },\n\n dispose() {\n // Stops the flush timer and unload listeners (with a final flush) but\n // leaves identity, snapshot, and retry buckets for the next client.\n stopPageviews?.();\n pageviewsTornDown = true;\n eventQueue.destroy();\n goalQueue.destroy();\n // Drop the registry entry so a later re-init doesn't try to dispose an\n // already-torn-down client (and so the map doesn't pin this closure).\n if (_clients.get(config.apiKey)?.dispose === client.dispose) {\n _clients.delete(config.apiKey);\n }\n if (config.debug) {\n console.log('[sentient] disposed');\n }\n },\n\n destroy() {\n stopPageviews?.();\n pageviewsTornDown = true;\n eventQueue.destroy();\n goalQueue.destroy();\n session.destroy();\n // The assignment cache persists in localStorage (`_snt_asgn_*`) with a\n // 30-minute default TTL — left in place, a revoked visitor returning\n // within that window was handed their previous personalized variants\n // back, so forget-me wasn't total.\n assignmentCache.clear();\n if (_clients.get(config.apiKey)?.dispose === client.dispose) {\n _clients.delete(config.apiKey);\n }\n // Forget-me must be total: a surviving decision snapshot would\n // re-personalize the next visit via the pre-paint script, and a\n // persisted retry bucket would re-send events for the deleted identity.\n try {\n localStorage.removeItem(SNAPSHOT_STORAGE_KEY_PREFIX + config.apiKey);\n localStorage.removeItem(retryStorageKey(config.apiKey));\n localStorage.removeItem(goalRetryStorageKey(config.apiKey));\n } catch {\n /* storage unavailable — nothing persisted to remove */\n }\n if (config.debug) {\n console.log('[sentient] destroyed');\n }\n },\n };\n\n _clients.set(config.apiKey, { config, upgrade: null, dispose: client.dispose });\n\n stopPageviews = startPageviewTracking(client, config.apiKey, (key) => {\n // track() defers its enqueue on sessionReady; chaining after it means this\n // runs once the push has happened. If the client was torn down first, the\n // event went into a destroyed queue and never ships — leave the page\n // unmarked so the replacement client's emit is the one that counts.\n void sessionReady.then(() => {\n if (!pageviewsTornDown) emittedPages.add(key);\n });\n });\n\n if (config.debug) {\n const win = window as unknown as { __sentient?: { client: SentientClient } };\n if (win.__sentient) {\n win.__sentient.client = client;\n }\n }\n\n return client;\n}\n","/**\n * Keyless local mode — client factory.\n *\n * This module ships in the MAIN core bundle and contains no engine code: the\n * engine arrives through a dynamic import of the bare specifier\n * `@sentientui/core/local`, which the consumer's bundler resolves through the\n * development/production export conditions. In production builds that\n * specifier is the stub, and this client degrades to a no-op plus one\n * console.error per page load.\n */\nimport { initSession } from './session.js';\nimport { readSnapshot, writeSnapshot } from './snapshot.js';\nimport { confidenceBand } from '@sentientui/policy';\nimport type {\n DecideOutcome,\n SentientClient,\n SentientConfig,\n SlotDeclInput,\n} from './index.js';\n\ntype LocalEngineModule = {\n LOCAL_ENGINE_AVAILABLE: boolean;\n createLocalEngine(opts: { sessionId: string; forcedPersona?: string }): {\n decide(input: {\n sections?: string[];\n components?: Array<{ id: string; variantIds?: string[] }>;\n slots?: SlotDeclInput[];\n }): DecideOutcome;\n };\n};\n\nexport const PROD_KEYLESS_ERROR =\n '[sentient] No API key configured — nothing is being learned. Set NEXT_PUBLIC_SENTIENT_API_KEY or pass localMode: true for local development.';\n\nexport const LOCAL_MODE_BANNER =\n '[sentient] Local mode — decisions are simulated on-device and nothing is sent over the network. Add an API key to learn from real traffic.';\n\n// Once-per-page-load guards (module state resets on reload).\nlet bannerShown = false;\nlet prodErrorShown = false;\n\n/** @internal test hook */\nexport function __resetLocalModeLogGuards(): void {\n bannerShown = false;\n prodErrorShown = false;\n}\n\n/** Mirrors the ?sentient_variant= override pattern; validated by the engine. */\nfunction readPersonaOverrideFromUrl(): string | undefined {\n try {\n return new URLSearchParams(window.location.search).get('sentient_persona') ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function createLocalModeClient(config: SentientConfig): SentientClient {\n const session = initSession({ ssrSessionId: config.ssrSessionId, apiKey: config.apiKey });\n const sessionId = session.getSessionId() ?? 'local';\n const forcedPersona = readPersonaOverrideFromUrl();\n\n // The specifier MUST stay a bare package subpath (never a relative path) so\n // the consumer's bundler applies the development/production export\n // conditions. tsup keeps it external (see tsup.config.ts).\n const modPromise: Promise<LocalEngineModule | null> = import('@sentientui/core/local')\n .then((mod) => {\n const m = mod as unknown as LocalEngineModule;\n if (!m.LOCAL_ENGINE_AVAILABLE) {\n if (!prodErrorShown) {\n prodErrorShown = true;\n console.error(PROD_KEYLESS_ERROR);\n }\n return null;\n }\n if (!bannerShown) {\n bannerShown = true;\n console.info(LOCAL_MODE_BANNER);\n }\n return m;\n })\n .catch(() => {\n if (!prodErrorShown) {\n prodErrorShown = true;\n console.error(PROD_KEYLESS_ERROR);\n }\n return null;\n });\n\n let lastOutcome: DecideOutcome | null = null;\n\n // Pre-first-decide persona seed. The hosted client serves config.initialPersona\n // (SSR) and then the persisted snapshot before its first decide; local mode\n // returned null until the first decide resolved, so a locally-developed page\n // rendered its default persona on first paint and diverged from what the\n // same code shows against production. Band-only snapshot sources map to the\n // same band-consistent confidences the hosted client uses, so\n // confidenceBand(confidence) always round-trips to the stored band.\n const BAND_CONFIDENCE: Record<string, number> = { low: 0.15, medium: 0.5, high: 0.85 };\n const seedPersona = ((): { persona: string; confidence: number } | null => {\n if (config.initialPersona) return { ...config.initialPersona };\n const snap = readSnapshot(config.apiKey || 'local');\n if (snap) return { persona: snap.persona, confidence: BAND_CONFIDENCE[snap.band] ?? 0.15 };\n return null;\n })();\n\n function applyPersonaAttributes(outcome: DecideOutcome): void {\n // Single-writer rule: adopt attributes already written (e.g. by the\n // AdaptiveRoot inline script); only write when nothing has yet.\n const el = document.documentElement;\n if (el.dataset.sentientPersona === undefined) {\n el.dataset.sentientPersona = outcome.persona;\n el.dataset.sentientConfidence = confidenceBand(outcome.confidence);\n }\n }\n\n return {\n isLocal: true,\n\n async decide(input) {\n const mod = await modPromise;\n if (!mod) return null;\n const outcome = mod.createLocalEngine({ sessionId, forcedPersona }).decide(input);\n // Accumulate across calls: slots decide lazily one at a time (per-slot\n // decide from useSlotResult), so a later decide must not evict results\n // an earlier one served. Same (sessionId, persona) → merging is safe.\n lastOutcome = {\n ...outcome,\n layoutOrder: outcome.layoutOrder ?? lastOutcome?.layoutOrder ?? null,\n slots: { ...(lastOutcome?.slots ?? {}), ...outcome.slots },\n };\n writeSnapshot(config.apiKey || 'local', {\n v: 1,\n persona: lastOutcome.persona,\n band: confidenceBand(lastOutcome.confidence),\n slots: lastOutcome.slots,\n layoutOrder: lastOutcome.layoutOrder,\n savedAt: Date.now(),\n });\n applyPersonaAttributes(outcome);\n return outcome;\n },\n\n getSlotResult(slotId) {\n return lastOutcome?.slots[slotId] ?? config.initialSlots?.[slotId] ?? null;\n },\n\n getSlotConfig(slotId) {\n // Local mode never talks to the registry; only an SSR seed can supply one.\n return config.initialSlotConfig?.[slotId] ?? null;\n },\n\n getSitePalette() {\n return config.initialPalette ?? null;\n },\n\n reportSlots() {\n // Local mode has no server to register with.\n },\n\n getPersona() {\n const p = lastOutcome ?? seedPersona;\n if (!p) return null;\n return {\n persona: p.persona,\n confidence: p.confidence,\n band: confidenceBand(p.confidence),\n };\n },\n\n async assign(componentId, variantIds) {\n const mod = await modPromise;\n if (!mod || !variantIds || variantIds.length === 0) {\n return variantIds?.[0] ? { variantId: variantIds[0], assignmentTtlMs: 0 } : null;\n }\n const outcome = mod\n .createLocalEngine({ sessionId, forcedPersona })\n .decide({ components: [{ id: componentId, variantIds }] });\n return { variantId: outcome.assignments[componentId] ?? variantIds[0], assignmentTtlMs: 0 };\n },\n\n // Local mode never talks to the network: the tracking surface no-ops.\n track: () => undefined,\n goal: () => undefined,\n componentGoal: () => undefined,\n identify: () => undefined,\n getAssignment: () => null,\n fetchWeights: () => Promise.resolve([]),\n getGraph: () => ({ pageNodes: [], capturedAt: 0 }),\n dispose: () => undefined, // no timers/network in local mode; keep the session\n destroy: () => session.destroy(),\n };\n}\n","/**\n * Composition Blocks (spec: 2026-08-20-nocode-composition-variants-design.md §4).\n *\n * A bounded, TYPED component tree — never HTML — that a registry arm may carry\n * (`published_config.arms[].blocks`). The vocabulary is the whitelist: every\n * prop value is an enumerated token, validated server-side before publish\n * (apps/api/src/domain/composition-blocks.ts) and rendered client-side through\n * document.createElement + property assignment only. No innerHTML/outerHTML/\n * insertAdjacentHTML exists anywhere on this path — the security property is\n * preserved by never accepting HTML, not by sanitizing it, which is why there\n * is no sanitizer to keep honest.\n *\n * Token lists live here (not in the API domain like SlotOps' mirrors) because\n * three parties must agree byte-for-byte: the server validator, the snippet\n * renderer, and eventually the React renderer (§11) — a drifted copy would let\n * a published arm fail to render, which the fail-safe turns into an invisibly\n * missing section, not an error.\n */\n\nexport const BLOCK_GAPS = ['none', 'sm', 'md', 'lg'] as const;\nexport const BLOCK_ALIGNS = ['start', 'center', 'end', 'stretch'] as const;\nexport const BLOCK_JUSTIFIES = ['start', 'center', 'end', 'between'] as const;\nexport const BLOCK_SIZES = ['sm', 'md', 'lg'] as const;\nexport const BLOCK_WEIGHTS = ['normal', 'medium', 'bold'] as const;\nexport const BLOCK_TONES = ['default', 'muted', 'accent'] as const;\nexport const BLOCK_EMPHASES = ['primary', 'secondary', 'ghost'] as const;\nexport const BLOCK_TEXT_ALIGNS = ['left', 'center', 'right'] as const;\nexport const BLOCK_RATIOS = ['auto', 'square', 'landscape', 'wide'] as const;\nexport const BLOCK_FITS = ['cover', 'contain'] as const;\nexport const BLOCK_GRID_COLUMNS = [2, 3, 4] as const;\nexport const BLOCK_HEADING_LEVELS = [2, 3, 4] as const;\n\nexport type BlockGap = (typeof BLOCK_GAPS)[number];\nexport type BlockAlign = (typeof BLOCK_ALIGNS)[number];\nexport type BlockJustify = (typeof BLOCK_JUSTIFIES)[number];\nexport type BlockSize = (typeof BLOCK_SIZES)[number];\nexport type BlockWeight = (typeof BLOCK_WEIGHTS)[number];\nexport type BlockTone = (typeof BLOCK_TONES)[number];\nexport type BlockEmphasis = (typeof BLOCK_EMPHASES)[number];\nexport type BlockTextAlign = (typeof BLOCK_TEXT_ALIGNS)[number];\nexport type BlockRatio = (typeof BLOCK_RATIOS)[number];\nexport type BlockFit = (typeof BLOCK_FITS)[number];\n\n/** Flex row/column container. */\nexport type StackBlock = {\n type: 'stack';\n direction: 'row' | 'column';\n children: BlockNode[];\n gap?: BlockGap;\n align?: BlockAlign;\n justify?: BlockJustify;\n wrap?: boolean;\n};\n\n/** 2–4 equal-column grid container. */\nexport type GridBlock = {\n type: 'grid';\n columns: (typeof BLOCK_GRID_COLUMNS)[number];\n children: BlockNode[];\n gap?: BlockGap;\n align?: BlockAlign;\n};\n\n/** Paragraph / label. */\nexport type TextBlock = {\n type: 'text';\n value: string;\n size?: BlockSize;\n weight?: BlockWeight;\n tone?: BlockTone;\n align?: BlockTextAlign;\n};\n\n/** h2–h4 — never h1 (the page owns its h1). */\nexport type HeadingBlock = {\n type: 'heading';\n value: string;\n level: (typeof BLOCK_HEADING_LEVELS)[number];\n size?: BlockSize;\n align?: BlockTextAlign;\n};\n\n/** Link styled as a button. `tag` feeds agent legibility (agentDataByVariant). */\nexport type ButtonBlock = {\n type: 'button';\n label: string;\n href: string;\n emphasis?: BlockEmphasis;\n size?: BlockSize;\n tag?: string;\n};\n\n/** Inline text link. */\nexport type LinkBlock = {\n type: 'link';\n label: string;\n href: string;\n tag?: string;\n};\n\n/** Image. `alt` is required; empty only with an explicit `decorative: true`. */\nexport type ImageBlock = {\n type: 'image';\n src: string;\n alt: string;\n decorative?: boolean;\n ratio?: BlockRatio;\n fit?: BlockFit;\n};\n\n/** Eyebrow / pill. */\nexport type BadgeBlock = {\n type: 'badge';\n value: string;\n tone?: BlockTone;\n};\n\n/** Vertical rhythm. */\nexport type SpacerBlock = {\n type: 'spacer';\n size: BlockSize;\n};\n\nexport const FORM_FIELD_KINDS = ['input', 'textarea', 'select'] as const;\nexport const FORM_INPUT_TYPES = ['text', 'email', 'number', 'tel'] as const;\nexport const MAX_FORM_FIELDS = 5;\nexport const MAX_FORM_SELECT_OPTIONS = 8;\n\n/** One field of a form block. Fields are props, not child blocks, so an input\n * can never appear outside a form — the constraint is structural, no\n * validator has to chase it. */\nexport type FormField = {\n kind: (typeof FORM_FIELD_KINDS)[number];\n /** Slug key for the values object handed to onFormSubmit. */\n name: string;\n /** Visible label — required, a11y is not optional. */\n label: string;\n /** input fields only. Never password/file/hidden — the closed list is the guarantee. */\n inputType?: (typeof FORM_INPUT_TYPES)[number];\n required?: boolean;\n placeholder?: string;\n /** select fields only: 2–MAX_FORM_SELECT_OPTIONS plain-text options. */\n options?: string[];\n};\n\n/** Lead/contact form. Submit fires `submitGoal` (a project goal) and hands the\n * values to the developer's onFormSubmit — field values never reach Sentient.\n * At most one form per tree; a form is a leaf (no children). */\nexport type FormBlock = {\n type: 'form';\n submitGoal: string;\n submitLabel: string;\n fields: FormField[];\n emphasis?: BlockEmphasis;\n};\n\nexport type BlockNode =\n | StackBlock\n | GridBlock\n | TextBlock\n | HeadingBlock\n | ButtonBlock\n | LinkBlock\n | ImageBlock\n | BadgeBlock\n | SpacerBlock\n | FormBlock;\n\n/** The derived site palette (spec §4 \"Colour and type: derived, not chosen\").\n * Sampled by the on-site editor from the live page's own buttons — computed\n * styles, so values are plain colors (rgb/hex), never var()/url() — validated\n * server-side, stored per project, and served with the decision so injected\n * blocks render in the merchant's own primary color and corner radius.\n * Absent → the renderer's neutral inherit-first defaults. */\nexport type SitePalette = {\n primaryBg: string;\n primaryText: string;\n radius: string;\n};\n\n// Structural caps. Total-nodes and depth bound the render cost of one arm;\n// the arms cap bounds Option B's DOM weight (every arm pre-renders hidden, so\n// DOM cost is arms × nodes — spec §6 proposed 4 and nothing has argued it up).\nexport const MAX_BLOCK_NODES = 64;\nexport const MAX_BLOCK_DEPTH = 5;\nexport const MAX_BLOCK_CHILDREN = 12;\nexport const MAX_BLOCK_ARMS = 4;\nexport const MAX_BLOCK_TEXT_LEN = 500;\n\n/** True when the tree contains a form node at any depth. Surfaces that cannot\n * render forms (snippet today, React without an onFormSubmit handler) must\n * refuse the WHOLE tree — skipping just the form node would render a section\n * minus its call-to-action, which looks live while converting nothing. */\nexport function containsFormBlock(node: unknown): boolean {\n if (node == null || typeof node !== 'object' || Array.isArray(node)) return false;\n const n = node as { type?: unknown; children?: unknown };\n if (n.type === 'form') return true;\n if (Array.isArray(n.children)) return n.children.some(containsFormBlock);\n return false;\n}\n","export type MicroSignalEmitter = (\n signalType: 'rage_click' | 'text_copy' | 'scroll_hesitation' | 'tab_loss',\n extra?: Record<string, unknown>,\n) => void;\n\nexport type MicroSignalType = Parameters<MicroSignalEmitter>[0];\n\n/**\n * Attaches passive behavioral detectors to `node`. Calls `emit` when a signal\n * fires. Each signal type fires at most once per call to this function.\n * Returns a cleanup function that removes all listeners.\n */\nexport function attachMicroSignalDetectors(\n emit: MicroSignalEmitter,\n node: Element,\n variantAssignedAt?: number,\n options?: { tabLoss?: boolean },\n): () => void {\n const cleanups: Array<() => void> = [];\n\n // --- Rage click: 3+ clicks within 500ms ---\n {\n const WINDOW_MS = 500;\n const THRESHOLD = 3;\n let firedOnce = false;\n const timestamps: number[] = [];\n\n const onClick = (): void => {\n if (firedOnce) return;\n const now = Date.now();\n timestamps.push(now);\n while (timestamps.length > 0 && now - timestamps[0]! > WINDOW_MS) {\n timestamps.shift();\n }\n if (timestamps.length >= THRESHOLD) {\n firedOnce = true;\n emit('rage_click');\n }\n };\n\n node.addEventListener('click', onClick);\n cleanups.push(() => node.removeEventListener('click', onClick));\n }\n\n // --- Text copy: copy event within node ---\n {\n let firedOnce = false;\n\n const onCopy = (e: Event): void => {\n if (firedOnce) return;\n if (!(e.target instanceof Node)) return;\n if (!node.contains(e.target) && node !== e.target) return;\n firedOnce = true;\n const sel = typeof window !== 'undefined' ? window.getSelection() : null;\n const selectionLength = sel ? sel.toString().length : 0;\n emit('text_copy', { selectionLength });\n };\n\n document.addEventListener('copy', onCopy);\n cleanups.push(() => document.removeEventListener('copy', onCopy));\n }\n\n // --- Scroll hesitation: scroll stops 3s while component visible ---\n {\n let firedOnce = false;\n let isVisible = false;\n let hesitationTimer: ReturnType<typeof setTimeout> | null = null;\n\n const clearHesitation = (): void => {\n if (hesitationTimer !== null) {\n clearTimeout(hesitationTimer);\n hesitationTimer = null;\n }\n };\n\n const startHesitation = (): void => {\n if (firedOnce || !isVisible) return;\n clearHesitation();\n hesitationTimer = setTimeout(() => {\n if (!firedOnce && isVisible) {\n firedOnce = true;\n emit('scroll_hesitation');\n }\n }, 3000);\n };\n\n const onScroll = (): void => {\n clearHesitation();\n startHesitation();\n };\n\n const ioCallback = (entries: IntersectionObserverEntry[]): void => {\n for (const entry of entries) {\n isVisible = entry.intersectionRatio > 0.3;\n if (!isVisible) clearHesitation();\n else startHesitation();\n }\n };\n const io = new IntersectionObserver(ioCallback, { threshold: [0.3] });\n io.observe(node);\n window.addEventListener('scroll', onScroll, { passive: true });\n\n cleanups.push(() => {\n io.disconnect();\n window.removeEventListener('scroll', onScroll);\n clearHesitation();\n });\n }\n\n // --- Tab loss: tab hidden within 15s of variant_assigned ---\n // Document-level (not node-scoped), so callers that attach detectors to many\n // nodes at once (e.g. the snippet's per-option slot signals) can opt out on all\n // but one node to avoid emitting a duplicate tab_loss per node (audit M5).\n if (options?.tabLoss !== false) {\n let firedOnce = false;\n const assignedAt = variantAssignedAt ?? Date.now();\n\n const onVisibility = (): void => {\n if (firedOnce) return;\n if (document.visibilityState !== 'hidden') return;\n const elapsed = Date.now() - assignedAt;\n if (elapsed < 15_000) {\n firedOnce = true;\n emit('tab_loss', { timeOnPage: elapsed });\n }\n };\n\n document.addEventListener('visibilitychange', onVisibility);\n cleanups.push(() => document.removeEventListener('visibilitychange', onVisibility));\n }\n\n return () => {\n for (const c of cleanups) c();\n };\n}\n","/** In-memory context graph with persistence and backend sync. */\n\nimport { storageSuffix } from './storage-key.js';\n\nexport type PageNode = {\n id: string;\n componentId: string;\n semanticType: string;\n answers: string[];\n prominenceScore: number;\n depth: number;\n};\n\nexport type GraphSnapshot = {\n pageNodes: PageNode[];\n capturedAt: number;\n};\n\nexport type GraphConfig = {\n syncUrl?: string;\n apiKey?: string;\n projectId?: string;\n sessionId?: string;\n};\n\nexport type StructuralEdge = {\n fromComponentId: string;\n toComponentId: string;\n weight: number;\n};\n\nexport type GraphClient = {\n addPageNode(node: PageNode): void;\n /** Record a DOM-derived parent/child or sibling relationship between two components. */\n addStructuralEdge(edge: StructuralEdge): void;\n /** One-shot batch sync of all current page nodes to the backend. */\n syncOnce(): void;\n snapshot(): GraphSnapshot;\n destroy(): void;\n};\n\n// _snt_graph_edges was written by earlier builds but never synced to the backend.\n// We still read and clear any leftover key on init/destroy so old clients don't\n// accumulate stale data, but we no longer write it.\nconst STALE_EDGES_KEY = '_snt_graph_edges';\n\nconst SEMANTIC_NEIGHBOURS: Record<string, string[]> = {\n pricing: ['features', 'faq'],\n features: ['pricing'],\n faq: ['pricing'],\n social_proof: ['cta'],\n cta: ['social_proof', 'hero', 'trust'],\n hero: ['cta'],\n comparison: ['pricing'],\n trust: ['cta'],\n};\n\nfunction neighboursFor(semanticType: string): string[] {\n return SEMANTIC_NEIGHBOURS[semanticType] ?? [];\n}\n\nfunction readStorage<T>(key: string, fallback: T): T {\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return fallback;\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n}\n\nfunction writeStorage(key: string, value: unknown): void {\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n /* ignore */\n }\n}\n\nconst VALID_SEMANTIC_TYPES = new Set([\n 'pricing', 'hero', 'social_proof', 'cta', 'features',\n 'faq', 'comparison', 'trust', 'navigation', 'generic',\n]);\n\nfunction toValidSemanticType(type: string): string {\n return VALID_SEMANTIC_TYPES.has(type) ? type : 'generic';\n}\n\n/**\n * Path-only page URL for graph sync — strips query + fragment so tokens,\n * emails, and other sensitive URL params never leave the browser by default.\n */\nexport function sanitizePageUrl(href: string): string {\n try {\n const u = new URL(href);\n return `${u.origin}${u.pathname}`;\n } catch {\n return '/';\n }\n}\n\nfunction contentHashOf(componentId: string, semanticType: string, answers: string[]): string {\n const input = `${componentId}:${semanticType}:${answers.join(',')}`;\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) & 0xffffffff;\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Creates an in-memory graph client with optional persistence and sync.\n */\nexport function createGraphClient(config?: GraphConfig): GraphClient {\n const pageNodes = new Map<string, PageNode>();\n const structuralEdges = new Map<string, StructuralEdge>();\n\n // Per-project localStorage key so two projects on one origin don't share a\n // graph-node cache (see storage-key.ts). Legacy `_snt_graph_nodes` with no key.\n const nodesKey = `_snt_graph_nodes${storageSuffix(config?.apiKey)}`;\n\n const persist = (): void => {\n if (typeof window === 'undefined') return;\n writeStorage(nodesKey, [...pageNodes.values()]);\n };\n\n // serialize()/restore() were removed here: persisted-node restoration moved\n // into this constructor (below), leaving both dead — yet still shipping in\n // the graph bundle.\n if (typeof window !== 'undefined') {\n const storedNodes = readStorage<PageNode[]>(nodesKey, []);\n for (const node of storedNodes) {\n pageNodes.set(node.componentId, node);\n }\n // Clear any stale edge data written by older SDK versions.\n try { localStorage.removeItem(STALE_EDGES_KEY); } catch { /* ignore */ }\n }\n\n return {\n addPageNode(node: PageNode): void {\n pageNodes.set(node.componentId, node);\n persist();\n },\n\n addStructuralEdge(edge: StructuralEdge): void {\n const key = `${edge.fromComponentId}->${edge.toComponentId}`;\n structuralEdges.set(key, edge);\n },\n\n syncOnce(): void {\n if (!config?.syncUrl || typeof window === 'undefined') return;\n const nodes = [...pageNodes.values()];\n if (nodes.length === 0) return;\n try {\n // Build semantic edges from the SEMANTIC_NEIGHBOURS map. Each node emits\n // an edge to every present sibling that matches one of its neighbour types.\n // Weight matches propagate()'s 0.4 attention factor; confidence is high\n // because the mapping is curated, not inferred.\n const nodesByType = new Map<string, PageNode[]>();\n for (const n of nodes) {\n const list = nodesByType.get(n.semanticType) ?? [];\n list.push(n);\n nodesByType.set(n.semanticType, list);\n }\n const edges: Array<{\n fromComponentId: string;\n toComponentId: string;\n type: 'semantic' | 'structural';\n weight: number;\n confidence: number;\n }> = [];\n const seen = new Set<string>();\n for (const source of nodes) {\n for (const neighbourType of neighboursFor(source.semanticType)) {\n const targets = nodesByType.get(neighbourType) ?? [];\n for (const target of targets) {\n if (target.componentId === source.componentId) continue;\n const key = `semantic:${source.componentId}->${target.componentId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({\n fromComponentId: source.componentId,\n toComponentId: target.componentId,\n type: 'semantic',\n weight: 0.4,\n confidence: 0.9,\n });\n }\n }\n }\n\n // Structural edges from DOM relationships, recorded during scan.\n // Only emit when both endpoints are present in this page's node set —\n // a structural edge to/from a node that no longer exists would dangle.\n const componentIds = new Set(nodes.map((n) => n.componentId));\n for (const edge of structuralEdges.values()) {\n if (!componentIds.has(edge.fromComponentId) || !componentIds.has(edge.toComponentId)) continue;\n const key = `structural:${edge.fromComponentId}->${edge.toComponentId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({\n fromComponentId: edge.fromComponentId,\n toComponentId: edge.toComponentId,\n type: 'structural',\n weight: edge.weight,\n confidence: 1.0,\n });\n }\n\n const payload = {\n pageUrl: sanitizePageUrl(window.location.href),\n // Visitor/project attribution. The /graph/sync handler ignores unknown\n // top-level fields (Fastify additionalProperties + Zod strips extras),\n // so these ride along harmlessly and let beacons carry attribution.\n ...(config.sessionId ? { sessionId: config.sessionId } : {}),\n ...(config.projectId ? { projectId: config.projectId } : {}),\n nodes: nodes.map((n) => {\n const semanticType = toValidSemanticType(n.semanticType);\n return {\n componentId: n.componentId,\n semanticType,\n answers: n.answers,\n contentHash: contentHashOf(n.componentId, semanticType, n.answers),\n prominenceScore: n.prominenceScore,\n depthInPage: n.depth,\n };\n }),\n edges,\n };\n fetch(config.syncUrl, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n },\n body: JSON.stringify(payload),\n }).catch(() => undefined);\n } catch {\n /* ignore */\n }\n },\n\n snapshot(): GraphSnapshot {\n return {\n pageNodes: [...pageNodes.values()],\n capturedAt: Date.now(),\n };\n },\n\n destroy(): void {\n if (typeof window === 'undefined') return;\n try {\n localStorage.removeItem(nodesKey);\n localStorage.removeItem(STALE_EDGES_KEY);\n } catch {\n /* ignore */\n }\n },\n };\n}\n","import type { CompoundLocator } from './snapshot.js';\n\n// Live-DOM twin of apps/api/src/domain/locator-from-html.ts. The two MUST stay\n// in step: section_key is a hash of this object, so any divergence silently\n// splits one physical section into two identities — a crawl-derived one and a\n// client-derived one — and every per-section number downstream halves.\n// Cross-implementation parity is locked by apps/api/src/domain/locator-parity.test.ts.\n// data-sentient-id FIRST (added 2026-09-05, both generators together): it is\n// the one attribute a site authors specifically to name a section for us, and\n// it is exactly what hydration and redesigns do NOT rewrite. Elements with a\n// unique `id` are unaffected (id outranks data attrs); for the rest the change\n// moves section_key once — ingest-time fingerprint reconciliation\n// (section-key-reconcile.ts) writes the alias, and no offline backfill is\n// possible because pre-change locators never captured the attribute.\nconst STABLE_DATA_ATTRS = ['data-sentient-id', 'data-testid', 'data-test', 'data-id', 'data-name', 'data-cy'];\nconst FINGERPRINT_TEXT_MAX = 40;\n\nfunction normalizedText(el: Element): string {\n return (el.textContent ?? '').replace(/\\s+/g, ' ').trim();\n}\n\nfunction fingerprintOf(el: Element): { tag: string; text: string } {\n return {\n tag: el.tagName.toLowerCase(),\n text: normalizedText(el).slice(0, FINGERPRINT_TEXT_MAX),\n };\n}\n\nfunction cssEscape(v: string): string {\n return v.replace(/[\"\\\\\\]]/g, '\\\\$&');\n}\n\nfunction unique(root: ParentNode, selector: string): boolean {\n try {\n return root.querySelectorAll(selector).length === 1;\n } catch {\n return false;\n }\n}\n\n/** Shortest unique selector: tag, tag.class, then parent-qualified, then nth-of-type chain. */\nfunction uniqueSelector(el: Element, root: ParentNode): string | null {\n const tag = el.tagName.toLowerCase();\n if (!tag) return null;\n const classes = (el.getAttribute('class') ?? '').split(/\\s+/).filter((c) => /^[a-zA-Z][\\w-]*$/.test(c));\n const candidates = [tag, ...classes.map((c) => `${tag}.${c}`)];\n for (const c of candidates) if (unique(root, c)) return c;\n const parent = el.parentElement;\n if (parent && (parent as ParentNode) !== root) {\n const parentSel = uniqueSelector(parent, root);\n if (parentSel) {\n for (const c of candidates) {\n const combined = `${parentSel} > ${c}`;\n if (unique(root, combined)) return combined;\n }\n // Element-only children: matches the server's rawTagName filter over\n // childNodes, which excludes text nodes.\n const siblings = Array.from(parent.children).filter((n) => n.tagName.toLowerCase() === tag);\n const idx = siblings.indexOf(el);\n // An nth-of-type selector is only trustworthy when it pairs with a\n // fingerprint that could actually catch drift: if every same-tag sibling\n // has identical text, the node is a true duplicate and the {tag, text}\n // check can never distinguish \"still the right one\" from \"DOM reordered,\n // now pointing at the wrong duplicate\". Refuse it rather than return a\n // false sense of precision.\n const distinguishable = siblings.some((s) => s !== el && normalizedText(s) !== normalizedText(el));\n if (idx >= 0 && distinguishable) {\n const nth = `${parentSel} > ${tag}:nth-of-type(${idx + 1})`;\n if (unique(root, nth)) return nth;\n }\n }\n }\n return null;\n}\n\n/** Build a compound locator for a live DOM element. Returns null when nothing\n * resolves uniquely — the runtime never guesses an identity. */\nexport function locatorFromElement(el: Element, root: ParentNode): CompoundLocator | null {\n const fingerprint = fingerprintOf(el);\n const id = el.getAttribute('id');\n if (id && unique(root, `#${cssEscape(id)}`)) return { v: 1, id, fingerprint };\n for (const name of STABLE_DATA_ATTRS) {\n const value = el.getAttribute(name);\n if (value && unique(root, `[${name}=\"${cssEscape(value)}\"]`)) {\n return { v: 1, dataAttr: { name, value }, fingerprint };\n }\n }\n const selector = uniqueSelector(el, root);\n if (selector) return { v: 1, selector, fingerprint };\n return null;\n}\n","// Semantic section classification for no-code section capture (Phase 3 §2.4).\n// Pure heuristic: element → one of the graph_nodes semantic_type enum. Mirrors\n// the SDK graph scanner's vocabulary so the persona × section matrix consumes\n// snippet-captured sections unchanged.\n//\n// SPLIT (Phase 2b, spec 2026-09-04). This module is the BROWSER-SAFE core:\n// structural rules, the small parent-level keyword table, and the fallbacks.\n// The rich ~40-topic vocabulary lives in ./topics.ts, which only the server\n// imports — shipping it here cost 1.5 KB gzip in every bundle (7% of the\n// always-on snippet) for a layer the browser never reads, since classifySection\n// returns only the parent.\n//\n// Both paths share structuralTopicOf/fallbackTopicOf, so the three fixes below\n// apply identically on client and server; only vocabulary richness differs.\n//\n// Fixes, each reproduced against the shipped classifier before being changed:\n// 1. structural before the converter fallback — `<div class=\"navbar\">` used\n// to become `cta`, because `navigation` was reachable only via tag\n// nav/footer.\n// 2. hero before the converter fallback — the cta rule sat above both hero\n// rules, so a `<header>` with a button and <200 chars could never be hero.\n// 3. tightened pricing/social keywords — `plans?` and `customers?` fired on\n// unrelated copy AND were `strong`, so they auto-applied at confidence 0.9\n// and were never sent to the LLM fallback.\n\nexport type SemanticType =\n | 'pricing' | 'hero' | 'social_proof' | 'cta' | 'features'\n | 'faq' | 'comparison' | 'trust' | 'navigation' | 'generic';\n\nexport type SectionRole = 'converter' | 'persuader' | 'structural';\n\n/** Canonical vocabulary — mirrors the graph_nodes semantic_type CHECK enum. */\nexport const SEMANTIC_TYPES: readonly SemanticType[] = [\n 'pricing', 'hero', 'social_proof', 'cta', 'features',\n 'faq', 'comparison', 'trust', 'navigation', 'generic',\n] as const;\n\nexport type TopicRule = { topic: string; parent: SemanticType; role: SectionRole };\n\n/** Environment-agnostic section features — buildable from a browser Element or\n * a server-parsed node (node-html-parser). */\nexport type SectionFeatures = {\n tag: string; // lowercase tag name\n idClass: string; // `${id} ${className}`\n headingText: string;\n bodyText: string; // normalized text content, first 2000 chars\n actionCount: number;\n textLength: number;\n /** ARIA landmark role, when the element declares one. Optional so every\n * existing caller keeps compiling; a page that uses landmarks gives a\n * high-precision signal for free, which the old classifier ignored — it\n * tested membership of SEMANTIC_TYPES, so only role=\"navigation\" ever hit\n * and role=\"banner\"/\"contentinfo\" were discarded. */\n ariaRole?: string;\n /** schema.org `@type` values found in `<script type=\"application/ld+json\">`\n * INSIDE this section. Highest-precision signal available and free to\n * collect — the crawler already has the HTML. Type-only here; the\n * `@type` → topic table is server-side in ./topics.ts, since the browser\n * never reads the topic layer and the snippet has ~200 bytes of margin. */\n structuredTypes?: string[];\n};\n\n// ARIA landmark → topic. Structural facts, not guesses.\nconst ARIA_TOPIC: Record<string, string> = {\n banner: 'hero',\n navigation: 'navigation',\n contentinfo: 'footer',\n};\n\n/**\n * Structural identification, shared by the browser and server classifiers.\n * Returns a topic name, or null when the section is not structural furniture.\n * Everything matched here is definitive (tag or authored marker), so callers\n * treat it as `strong`.\n */\nexport function structuralTopicOf(f: SectionFeatures): string | null {\n const aria = f.ariaRole ? ARIA_TOPIC[f.ariaRole.toLowerCase()] : undefined;\n if (aria) return aria;\n if (f.tag === 'nav') return 'navigation';\n if (f.tag === 'footer') return 'footer';\n // A page-level <header> IS the banner landmark (HTML-AAM maps it to\n // role=\"banner\", which this function already treats as hero), so it is a\n // structural fact rather than a keyword guess. Without this, a header whose\n // headline happens to contain a content word loses to the keyword table:\n // \"We build brands that move\" scored social_proof and \"Winter collection\"\n // scored features, purely on words inside the hero copy.\n //\n // …EXCEPT when the header IS the site's navigation. The hold-out corpus\n // (2026-09-05, six real unseen sites) showed most real pages wrap their nav\n // in <header> — mega-menus with 10–114 links and almost no prose — and\n // hero-typing those cost the classifier a 0.11 hero precision. A hero header\n // carries a headline and one or two calls to action; a nav header is\n // link-dominated, so the action count is the discriminator. Measured on the\n // hold-out: nav headers had 10+ actions (one at 4), authored hero headers\n // have 0–2.\n if (f.tag === 'header') return f.actionCount >= 5 ? 'navigation' : 'hero';\n const hay = `${f.idClass} ${f.headingText}`.toLowerCase();\n if (/\\b(navbar|nav-bar|navigation|site-nav|main-nav|topbar|footer)\\b/.test(hay)) {\n return /footer/.test(hay) ? 'footer' : 'navigation';\n }\n // Explicit authoring marker beats any inferred keyword, and is matched on\n // idClass ONLY: a `<header class=\"hero\">` headlined \"Expert Car Repair\" is a\n // hero, not a services section — but matching \"hero\" in heading text would\n // also catch \"Hero of the story\".\n if (/\\b(hero|masthead|jumbotron)\\b/i.test(f.idClass)) return 'hero';\n return null;\n}\n\n/**\n * Last-resort classification once no keyword or content evidence matched.\n * Hero is checked BEFORE the converter fallback — fix 2 above.\n */\nexport function fallbackTopicOf(f: SectionFeatures): string {\n // `tag === 'header'` is handled in structuralTopicOf, which runs first.\n if (f.actionCount >= 1 && f.textLength > 0 && f.textLength < 200) return 'cta';\n return 'generic';\n}\n\n// Parent-level keyword table for the BROWSER path. Deliberately close to the\n// original size — the rich topic vocabulary is in ./topics.ts. `plans?` now\n// requires adjacent pricing context, and bare `customers?` is gone (it fired on\n// navigation furniture like a \"Customer Service\" footer block).\nconst KEYWORDS: Array<[SemanticType, RegExp]> = [\n ['pricing', /\\b(pricing|price list|per month|\\/mo|subscriptions?)\\b|\\bplans?\\b(?=[^.]{0,40}(from|start|month|year|[$€£]))/i],\n ['faq', /\\bfaq\\b|frequently asked|common questions?/i],\n ['comparison', /\\b(compare|comparison|versus)\\b|\\bvs\\./i],\n ['social_proof', /\\b(reviews?|ratings?|testimonial|brands?|galler(y|ies)|logos)\\b|trusted by|loved by|case stud|what our customers say/i],\n ['trust', /\\b(insurance|warrant(y|ies)|guarantees?|certifi|accredit|security|privacy|compliance|gdpr|encrypt)\\b|why choose|about us|our team/i],\n ['cta', /\\b(book|booking|reserve|appointments?|newsletter|subscribe)\\b|contact us|get in touch|opening hours/i],\n ['features', /\\b(features?|benefits?|capabilit|services?|repairs?|menus?|products?)\\b|how it works|our process|what we (do|offer)|what you get/i],\n];\n\n// Content-evidence patterns — run against bodyText when the heading gave us\n// nothing. Deliberately conservative: pricing needs per-period/plan context next\n// to money so an article mentioning \"$5 million\" stays generic.\nexport const CONTENT_PATTERNS: Array<[SemanticType, RegExp]> = [\n ['pricing', /(?:[$€£]\\s?\\d[\\d,.]*\\s*(?:\\/|per\\s)\\s*(?:mo|month|yr|year|seat|user))|(?:\\b(?:starter|basic|pro|growth|premium|enterprise)\\b[^.]{0,60}[$€£]\\s?\\d)/i],\n ['social_proof', /(?:★{2,})|(?:\\b\\d(?:\\.\\d)?\\s*(?:out of|\\/)\\s*5\\b)|(?:\\brated\\b)|(?:[\"“][^\"”]{20,160}[\"”]\\s*[—–-]\\s*[A-Z][a-z]+)/],\n ['trust', /\\b(?:money[- ]back guarantee|free returns?|returns? within|refund(?:s)? within|encrypted|soc ?2|iso ?27001|gdpr[- ]compliant|cancel anytime)\\b/i],\n ['comparison', /\\b(?:vs|versus)\\b\\.?[^.!?]{0,80}\\b(?:compare|comparison|plans?|features?|alternative)\\b|\\bhow (?:we|it) compares?\\b/i],\n];\n\n/** Parent for the handful of topics the shared structural/fallback helpers\n * emit. The server maps the full vocabulary via CLASSIFIER_TOPICS instead. */\nconst SHARED_TOPIC_PARENT: Record<string, SemanticType> = {\n navigation: 'navigation',\n footer: 'navigation',\n hero: 'hero',\n cta: 'cta',\n generic: 'generic',\n};\n\nfunction headingText(el: Element): string {\n const h = el.querySelector('h1, h2, h3');\n return (h?.textContent ?? '').slice(0, 160);\n}\n\n/**\n * Pure classification over extracted features. `strong` = keyword, content, or\n * structural evidence (trustable enough to auto-apply); `weak` = a fallback\n * guess (hero/cta/generic — capture-worthy but not persona evidence).\n */\nexport function classifyFeatures(f: SectionFeatures): { type: SemanticType; strength: 'strong' | 'weak' } {\n const structural = structuralTopicOf(f);\n if (structural) return { type: SHARED_TOPIC_PARENT[structural] ?? 'generic', strength: 'strong' };\n\n const hay = `${f.idClass} ${f.headingText}`.toLowerCase();\n for (const [type, re] of KEYWORDS) if (re.test(hay)) return { type, strength: 'strong' };\n for (const [type, re] of CONTENT_PATTERNS) if (re.test(f.bodyText)) return { type, strength: 'strong' };\n\n const fb = fallbackTopicOf(f);\n return { type: SHARED_TOPIC_PARENT[fb] ?? 'generic', strength: 'weak' };\n}\n\n/** Feature extraction from a live DOM element (browser paths). */\nexport function featuresFromElement(el: Element): SectionFeatures {\n const text = (el.textContent ?? '').replace(/\\s+/g, ' ').trim();\n const role = el.getAttribute('role');\n return {\n tag: el.tagName.toLowerCase(),\n idClass: `${el.id} ${String(el.className ?? '')}`,\n headingText: headingText(el),\n bodyText: text.slice(0, 2000),\n actionCount: el.querySelectorAll('a, button, [role=\"button\"]').length,\n textLength: text.length,\n ...(role ? { ariaRole: role } : {}),\n };\n}\n\n/** Classify a page section into a semantic type (never null — falls back to\n * 'generic' so the caller can still capture attention on it). */\nexport function classifySection(el: Element): SemanticType {\n return classifyFeatures(featuresFromElement(el)).type;\n}\n","/** Reads the rendered DOM to build the page-side context graph. */\n\nimport { classifySection, SEMANTIC_TYPES, type SemanticType } from './engagement/classify';\nimport { locatorFromElement } from './locator-from-dom.js';\nimport type { CompoundLocator } from './snapshot.js';\n\nexport type ScannedNode = {\n componentId: string;\n semanticType: string;\n ariaLabel?: string;\n headingText?: string;\n isAboveFold: boolean;\n prominenceScore: number;\n depth: number;\n reactComponentName?: string;\n dataAttributes: Record<string, string>;\n /** Compound locator for this element. The server hashes it into section_key —\n * the identity the crawler and the snippet resolve to for the same physical\n * section. Undefined when nothing resolves uniquely: the runtime never\n * guesses an identity it could not verify later. */\n locator?: CompoundLocator;\n};\n\nexport type StructuralEdge = {\n fromComponentId: string;\n toComponentId: string;\n /** 0.6 for direct parent → child, 0.3 for sibling (both directions emitted). */\n weight: number;\n};\n\nexport type ScanResult = {\n nodes: ScannedNode[];\n edges: StructuralEdge[];\n scannedAt: number;\n};\n\nexport type ContentAddedEvent = {\n nodes: ScannedNode[];\n edges: StructuralEdge[];\n addedAt: number;\n};\n\nexport type DOMScanner = {\n scan(): Promise<ScanResult>;\n observe(onContentAdded: (event: ContentAddedEvent) => void): void;\n getProminenceScore(element: Element): number;\n destroy(): void;\n};\n\n// ASIDE included to match the initial scan (which queries `section, article,\n// main, aside`) — this set omitted it, so a server-rendered aside was captured\n// while an identical dynamically-inserted one was silently ignored.\nconst OBSERVE_TAGS = new Set(['SECTION', 'ARTICLE', 'MAIN', 'DIV', 'ASIDE']);\nconst HEADING_SELECTOR = 'h1, h2, h3';\n// Selector mirror of the initial scan's criteria (collectNodesAndEdges): any\n// element carrying a declared id, plus structural tags with an aria-label.\n// Used to walk INTO inserted subtrees — a SPA mounts ONE root node whose\n// interesting sections are all descendants, and inspecting only the root made\n// every framework-mounted component permanently invisible to graph capture.\nconst SUBTREE_SELECTOR =\n '[data-sentient-id], section[aria-label], article[aria-label], main[aria-label], aside[aria-label]';\n\n// Sibling detection is O(n²) per group and emits two edges per pair, so a page\n// with hundreds of co-located components (e.g. a 200-cell product grid) would\n// otherwise emit tens of thousands of low-signal sibling edges. We cap the\n// sibling fan-out per group and the total structural edges per detection pass.\n// The total stays well under the server's 2000-edge-per-sync limit, leaving room\n// for semantic edges. Parent→child edges (higher signal) are emitted first, so\n// the cap sheds sibling edges before it ever touches them.\nconst MAX_SIBLINGS_PER_GROUP = 30;\nconst MAX_STRUCTURAL_EDGES = 1500;\n\nconst SSR_SCANNER: DOMScanner = {\n scan: async () => ({ nodes: [], edges: [], scannedAt: 0 }),\n observe: () => undefined,\n getProminenceScore: () => 0,\n destroy: () => undefined,\n};\n\nfunction normalize(value: number, min: number, max: number): number {\n if (max <= min) return 0;\n return Math.max(0, Math.min(1, (value - min) / (max - min)));\n}\n\nfunction readReactComponentName(element: Element): string | undefined {\n try {\n const record = element as unknown as Record<string, unknown>;\n for (const key of Object.keys(record)) {\n if (!key.startsWith('__reactFiber') && !key.startsWith('__reactInternalInstance')) {\n continue;\n }\n const fiber = record[key] as { type?: { name?: string; displayName?: string } };\n const name = fiber?.type?.displayName ?? fiber?.type?.name;\n if (name && name.length > 1) {\n return name;\n }\n }\n } catch {\n /* ignore */\n }\n return undefined;\n}\n\nfunction extractDataAttributes(element: Element): Record<string, string> {\n const attrs: Record<string, string> = {};\n for (const attr of Array.from(element.attributes)) {\n if (attr.name.startsWith('data-')) {\n attrs[attr.name] = attr.value;\n }\n }\n return attrs;\n}\n\nconst SEMANTIC_SET: ReadonlySet<string> = new Set(SEMANTIC_TYPES);\n\n// Explicit tag wins → role → heuristic → generic. Every output is normalized to\n// the graph_nodes enum: an out-of-vocabulary data-sentient-type/role falls\n// through to the heuristic instead of being emitted raw (raw values violated\n// the graph_nodes CHECK constraint on insert).\nfunction inferSemanticType(element: Element): SemanticType {\n const explicit = element.getAttribute('data-sentient-type');\n if (explicit && SEMANTIC_SET.has(explicit)) return explicit as SemanticType;\n const role = element.getAttribute('role');\n if (role && SEMANTIC_SET.has(role)) return role as SemanticType;\n return classifySection(element);\n}\n\n// djb2 over a string → 8-hex-char digest. Stable and collision-resistant enough\n// to disambiguate co-located components that declare no id of their own.\nfunction shortHash(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) & 0xffffffff;\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\n// A structural path (tag + sibling index at each level up to the root) uniquely\n// and stably identifies an element's DOM position, so two id-less sections don't\n// hash to the same value the way a bare tagName would.\nfunction domPathSignature(element: Element): string {\n const parts: string[] = [];\n let current: Element | null = element;\n while (current) {\n const parent: Element | null = current.parentElement;\n if (!parent) {\n parts.push(current.tagName.toLowerCase());\n break;\n }\n const index = Array.prototype.indexOf.call(parent.children, current);\n parts.push(`${current.tagName.toLowerCase()}[${index}]`);\n current = parent;\n }\n return parts.reverse().join('/');\n}\n\n// Prefer an author-declared id. When none exists, synthesize a stable, unique id\n// from the element's DOM path — the old `tagName.toLowerCase()` fallback gave\n// every id-less <section> the same \"section\" id, so a second such node silently\n// overwrote the first in the componentId-keyed graph Map (node loss).\nfunction componentIdFor(element: Element): string {\n const declared =\n element.getAttribute('data-sentient-id') ?? element.getAttribute('id');\n if (declared) return declared;\n return `${element.tagName.toLowerCase()}-${shortHash(domPathSignature(element))}`;\n}\n\nfunction depthOf(element: Element): number {\n let depth = 0;\n let current: Element | null = element.parentElement;\n while (current) {\n depth++;\n current = current.parentElement;\n }\n return depth;\n}\n\nfunction scanElement(\n element: Element,\n getProminenceScore: (el: Element) => number,\n): ScannedNode {\n const heading = element.querySelector(HEADING_SELECTOR);\n return {\n componentId: componentIdFor(element),\n semanticType: inferSemanticType(element),\n ariaLabel: element.getAttribute('aria-label') ?? undefined,\n headingText: heading?.textContent?.trim() ?? undefined,\n isAboveFold: element.getBoundingClientRect().top < window.innerHeight,\n prominenceScore: getProminenceScore(element),\n depth: depthOf(element),\n reactComponentName: readReactComponentName(element),\n dataAttributes: extractDataAttributes(element),\n locator: locatorFromElement(element, element.ownerDocument) ?? undefined,\n };\n}\n\n/**\n * Detects structural relationships between scanned component elements:\n * - parent → child (direct ancestor link, weight 0.6)\n * - sibling ↔ sibling (share nearest component ancestor, weight 0.3, bidirectional)\n * A pair appears at most once per direction.\n */\nfunction detectStructuralEdges(elementToId: Map<Element, string>): StructuralEdge[] {\n const edges: StructuralEdge[] = [];\n const seen = new Set<string>();\n const ROOT = '__root__';\n\n // Parent → child: nearest component ancestor only.\n const groups = new Map<string, Element[]>();\n for (const [el, _id] of elementToId) {\n let ancestor: Element | null = el.parentElement;\n let groupKey = ROOT;\n while (ancestor) {\n if (elementToId.has(ancestor)) {\n groupKey = elementToId.get(ancestor)!;\n const childId = elementToId.get(el)!;\n const key = `${groupKey}->${childId}`;\n if (!seen.has(key) && groupKey !== childId) {\n seen.add(key);\n edges.push({ fromComponentId: groupKey, toComponentId: childId, weight: 0.6 });\n }\n break;\n }\n ancestor = ancestor.parentElement;\n }\n const siblings = groups.get(groupKey) ?? [];\n siblings.push(el);\n groups.set(groupKey, siblings);\n }\n\n // Sibling ↔ sibling: same ancestor group.\n for (const sibs of groups.values()) {\n if (sibs.length < 2) continue;\n // Cap the fan-out of any single group before the O(n²) pairing.\n const capped = sibs.length > MAX_SIBLINGS_PER_GROUP ? sibs.slice(0, MAX_SIBLINGS_PER_GROUP) : sibs;\n for (let i = 0; i < capped.length; i++) {\n for (let j = i + 1; j < capped.length; j++) {\n if (edges.length >= MAX_STRUCTURAL_EDGES) return edges;\n const aId = elementToId.get(capped[i]!)!;\n const bId = elementToId.get(capped[j]!)!;\n if (aId === bId) continue;\n const fwd = `${aId}->${bId}::sib`;\n const rev = `${bId}->${aId}::sib`;\n if (!seen.has(fwd)) {\n seen.add(fwd);\n edges.push({ fromComponentId: aId, toComponentId: bId, weight: 0.3 });\n }\n if (!seen.has(rev)) {\n seen.add(rev);\n edges.push({ fromComponentId: bId, toComponentId: aId, weight: 0.3 });\n }\n }\n }\n }\n\n return edges;\n}\n\nfunction collectNodesAndEdges(\n getProminenceScore: (el: Element) => number,\n): { nodes: ScannedNode[]; edges: StructuralEdge[]; elementToId: Map<Element, string> } {\n const nodes: ScannedNode[] = [];\n const seen = new Set<Element>();\n const elementToId = new Map<Element, string>();\n\n const registered = document.querySelectorAll('[data-sentient-id]');\n registered.forEach((el) => {\n if (el instanceof Element && !seen.has(el)) {\n seen.add(el);\n const node = scanElement(el, getProminenceScore);\n nodes.push(node);\n elementToId.set(el, node.componentId);\n }\n });\n\n const structural = document.querySelectorAll('section, article, main, aside');\n structural.forEach((el) => {\n if (!(el instanceof Element) || seen.has(el)) return;\n const hasAria = el.hasAttribute('aria-label');\n const hasSentientId = el.hasAttribute('data-sentient-id');\n if (!hasAria && !hasSentientId) return;\n seen.add(el);\n const node = scanElement(el, getProminenceScore);\n nodes.push(node);\n elementToId.set(el, node.componentId);\n });\n\n return { nodes, edges: detectStructuralEdges(elementToId), elementToId };\n}\n\n/**\n * Creates a DOM scanner that uses idle callbacks and mutation observation.\n */\nexport function createDOMScanner(): DOMScanner {\n if (typeof window === 'undefined') {\n return SSR_SCANNER;\n }\n\n let observer: MutationObserver | null = null;\n let idleCallbackId = 0;\n let contentCallback: ((event: ContentAddedEvent) => void) | null = null;\n // Element→componentId for every node registered so far (initial scan + prior\n // mutations). The MutationObserver detects edges against this whole set — not\n // just the nodes in the current mutation — so a child inserted under an\n // already-scanned parent still gets its parent→child edge.\n const knownElementToId = new Map<Element, string>();\n\n const getProminenceScore = (element: Element): number => {\n try {\n const styles = window.getComputedStyle(element);\n const fontSize = parseFloat(styles.fontSize) || 12;\n const zIndex = parseFloat(styles.zIndex) || 0;\n const rect = element.getBoundingClientRect();\n const distanceFromTop = Math.max(rect.top, 0);\n const viewportHeight = window.innerHeight || 1;\n const inverseDistance = 1 / (distanceFromTop / viewportHeight + 1);\n\n // inverseDistance is already in (0, 1] by construction, so it needs no\n // further normalization — only fontSize and zIndex are rescaled.\n const score =\n normalize(fontSize, 12, 48) * 0.4 +\n inverseDistance * 0.4 +\n normalize(zIndex, 0, 100) * 0.2;\n\n return Math.max(0, Math.min(1, score));\n } catch {\n return 0.5;\n }\n };\n\n const scan = (): Promise<ScanResult> =>\n new Promise((resolve) => {\n const run = (): void => {\n const { nodes, edges, elementToId } = collectNodesAndEdges(getProminenceScore);\n // Seed the known-element registry so the observer can resolve parents that\n // were registered in this scan when later children are inserted.\n knownElementToId.clear();\n for (const [el, id] of elementToId) knownElementToId.set(el, id);\n resolve({ nodes, edges, scannedAt: Date.now() });\n };\n\n try {\n if (typeof requestIdleCallback === 'function') {\n idleCallbackId = requestIdleCallback(run, { timeout: 100 });\n } else {\n run();\n }\n } catch {\n run();\n }\n });\n\n const observe = (onContentAdded: (event: ContentAddedEvent) => void): void => {\n contentCallback = onContentAdded;\n try {\n observer = new MutationObserver((mutations) => {\n const added: ScannedNode[] = [];\n const addedIds = new Set<string>();\n for (const mutation of mutations) {\n if (mutation.type !== 'childList') continue;\n mutation.addedNodes.forEach((node) => {\n if (!(node instanceof Element)) return;\n const candidates: Element[] = [];\n if (\n OBSERVE_TAGS.has(node.tagName) &&\n (node.hasAttribute('data-sentient-id') || node.hasAttribute('aria-label'))\n ) {\n candidates.push(node);\n }\n // Also scan the inserted SUBTREE with the initial scan's criteria\n // (see SUBTREE_SELECTOR): the root of a SPA/framework mount is\n // usually a plain wrapper, and its sections arrive as descendants\n // of ONE childList mutation — inspecting only the root meant they\n // were never captured at all.\n try {\n node.querySelectorAll(SUBTREE_SELECTOR).forEach((el) => candidates.push(el));\n } catch {\n /* exotic host without querySelectorAll — root-only scan stands */\n }\n for (const el of candidates) {\n // Skip elements already registered: a parent and its child can\n // both appear in addedNodes (the child once via the parent's\n // subtree, once directly), which would emit duplicate nodes.\n if (knownElementToId.has(el)) continue;\n const scanned = scanElement(el, getProminenceScore);\n added.push(scanned);\n addedIds.add(scanned.componentId);\n knownElementToId.set(el, scanned.componentId);\n }\n });\n }\n if (added.length === 0 || !contentCallback) return;\n // Drop elements no longer in the document so removed nodes don't dangle\n // and the map stays bounded.\n for (const el of [...knownElementToId.keys()]) {\n if (!el.isConnected) knownElementToId.delete(el);\n }\n // Detect over the FULL known set so a child inserted under an already-\n // scanned parent still gets its parent→child edge, then surface only the\n // edges that touch a newly-added node (existing edges were already emitted).\n const edges = detectStructuralEdges(knownElementToId).filter(\n (e) => addedIds.has(e.fromComponentId) || addedIds.has(e.toComponentId),\n );\n contentCallback({ nodes: added, edges, addedAt: Date.now() });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n } catch {\n /* ignore */\n }\n };\n\n const destroy = (): void => {\n if (observer) {\n observer.disconnect();\n observer = null;\n }\n if (idleCallbackId && typeof cancelIdleCallback === 'function') {\n try {\n cancelIdleCallback(idleCallbackId);\n } catch {\n /* ignore */\n }\n }\n idleCallbackId = 0;\n contentCallback = null;\n knownElementToId.clear();\n };\n\n return {\n scan,\n observe,\n getProminenceScore,\n destroy,\n };\n}\n"],"mappings":"o7BAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,kBAAAE,GAAA,mBAAAC,GAAA,eAAAC,GAAA,eAAAC,GAAA,uBAAAC,GAAA,yBAAAC,GAAA,oBAAAC,GAAA,iBAAAC,GAAA,gBAAAC,GAAA,sBAAAC,GAAA,gBAAAC,GAAA,kBAAAC,GAAA,qBAAAC,GAAA,qBAAAC,GAAA,+BAAAC,GAAA,mBAAAC,GAAA,uBAAAC,GAAA,oBAAAC,GAAA,oBAAAC,GAAA,uBAAAC,GAAA,oBAAAC,GAAA,4BAAAC,GAAA,gCAAAC,GAAA,gBAAAC,GAAA,+BAAAC,GAAA,sBAAAC,GAAA,kBAAAC,GAAA,sBAAAC,GAAA,yBAAAC,GAAA,sBAAAC,GAAA,oBAAAC,GAAA,wBAAAC,GAAA,iBAAAC,GAAA,SAAAC,GAAA,wBAAAC,GAAA,uBAAAC,GAAA,iBAAAC,GAAA,8BAAAC,GAAA,yBAAAC,GAAA,oBAAAC,GAAA,sBAAAC,GAAA,eAAAC,GAAA,kBAAAC,KAAA,eAAAC,GAAA7C,ICiBO,SAAS8C,IAAuB,CACrC,GAAI,CACF,GAAI,OAAO,QAAW,aAAe,OAAO,OAAO,YAAe,WAChE,OAAO,OAAO,WAAW,CAE7B,OAAQ,GAER,CACA,GAAI,CACF,GAAI,OAAO,QAAW,aAAe,OAAO,OAAO,iBAAoB,WAAY,CACjF,IAAMC,EAAM,IAAI,WAAW,EAAE,EAC7B,OAAO,gBAAgBA,CAAG,EAC1BA,EAAI,CAAC,EAAKA,EAAI,CAAC,EAAK,GAAQ,GAC5BA,EAAI,CAAC,EAAKA,EAAI,CAAC,EAAK,GAAQ,IAC5B,IAAIC,EAAM,GACV,QAASC,EAAI,EAAGA,EAAI,GAAIA,IACtBD,GAAOD,EAAIE,CAAC,EAAG,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,GACvCA,IAAM,GAAKA,IAAM,GAAKA,IAAM,GAAKA,IAAM,KAAGD,GAAO,KAEvD,OAAOA,CACT,CACF,OAAQ,GAER,CACA,MAAO,uCAAuC,QAAQ,QAAUE,GAAM,CACpE,IAAMC,EAAK,KAAK,OAAO,EAAI,GAAM,EACjC,OAAQD,IAAM,IAAMC,EAAKA,EAAI,EAAO,GAAK,SAAS,EAAE,CACtD,CAAC,CACH,CC/BO,SAASC,GAAcC,EAAyB,CACrD,OAAOA,EAAS,IAAIA,EAAO,MAAM,EAAG,EAAE,CAAC,GAAK,EAC9C,CAOO,IAAMC,GAA6B,WAYnC,SAASC,GAAkBF,EAAyB,CACzD,MAAO,GAAGC,EAA0B,GAAGF,GAAcC,CAAM,CAAC,EAC9D,CCPA,IAAMG,GAAsB,WACtBC,GAA0B,IAC1BC,GAAc,WAOpB,SAASC,IAA4B,CACnC,OAAOC,GAAa,CACtB,CAEA,SAASC,GAAWC,EAA6B,CAC/C,GAAI,CACF,IAAMC,EAAQ,SAAS,OAAO,MAAM,IAAI,OAAO,WAAWD,CAAI,UAAU,CAAC,EACzE,OAAOC,EAAQ,mBAAmBA,EAAM,CAAC,CAAC,EAAI,IAChD,OAAQC,EAAA,CACN,OAAO,IACT,CACF,CAEA,SAASC,GAAYH,EAAcI,EAAeC,EAA6B,CAC7E,GAAI,CACF,SAAS,OAAS,GAAGL,CAAI,IAAI,mBAAmBI,CAAK,CAAC,aAAaC,CAAa,2BAClF,OAAQH,EAAA,CAER,CACF,CAEA,SAASI,GAAiBC,EAA4B,CACpD,GAAI,CACF,OAAO,aAAa,QAAQA,CAAG,CACjC,OAAQL,EAAA,CACN,OAAO,IACT,CACF,CAEA,SAASM,GAAkBD,EAAaH,EAAwB,CAC9D,GAAI,CACF,oBAAa,QAAQG,EAAKH,CAAK,EACxB,EACT,OAAQF,EAAA,CACN,MAAO,EACT,CACF,CAEA,SAASO,GAAmBF,EAA4B,CACtD,GAAI,CACF,OAAO,eAAe,QAAQA,CAAG,CACnC,OAAQL,EAAA,CACN,OAAO,IACT,CACF,CAEA,SAASQ,GAAoBH,EAAaH,EAAwB,CAChE,GAAI,CACF,sBAAe,QAAQG,EAAKH,CAAK,EAC1B,EACT,OAAQF,EAAA,CACN,MAAO,EACT,CACF,CAEA,SAASS,GAAqBJ,EAAmB,CAC/C,GAAI,CACF,eAAe,WAAWA,CAAG,CAC/B,OAAQL,EAAA,CAER,CACF,CAEA,SAASU,GAAoBZ,EAAuB,CAClD,GAAI,CACF,gBAAS,OAAS,GAAGA,CAAI,+CAClB,SAAS,OAAO,QAAQ,GAAGA,CAAI,UAAU,IAAM,EACxD,OAAQE,EAAA,CACN,MAAO,EACT,CACF,CAEA,SAASW,GAAmBN,EAAmB,CAC7C,GAAI,CACF,aAAa,WAAWA,CAAG,CAC7B,OAAQL,EAAA,CAER,CACF,CAEA,SAASY,GAAYd,EAAoB,CACvC,GAAI,CACF,SAAS,OAAS,GAAGA,CAAI,uCAC3B,OAAQE,EAAA,CAER,CACF,CAEA,IAAMa,GAA8B,CAClC,aAAc,IAAM,KACpB,YAAa,IAAM,GACnB,QAAS,IAAG,EACd,EAKO,SAASC,GAAYC,EAAwC,CAxIpE,IAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAyIE,GAAI,OAAO,QAAW,YACpB,OAAOT,GAQT,IAAMU,EAASC,GAAcT,GAAA,YAAAA,EAAQ,MAAM,EACrCU,GAAaT,EAAAD,GAAA,YAAAA,EAAQ,aAAR,KAAAC,EAAsBU,GAAkBX,GAAA,YAAAA,EAAQ,MAAM,EACnEY,EAAa,GAAGjC,EAAW,GAAG6B,CAAM,GAOpCK,EAAqB,GAAGlC,EAAW,QAAQ6B,CAAM,GAEjDpB,IADgBc,EAAAF,GAAA,YAAAA,EAAQ,gBAAR,KAAAE,EAAyBxB,IACT,GAAK,GAAK,GAQ1CoC,EAAY3B,GAChBA,GAASA,EAAM,OAAS,EAAIA,EAAQ,KAchC4B,EAAa,IAAkB,CApLvC,IAAAd,EAAAC,EAqLI,OAAAM,GAAU,EAACR,GAAA,MAAAA,EAAQ,aAAcX,GAAiBwB,CAAkB,IAAM,MACtEX,GAAAD,EAAAa,EAAShC,GAAWL,EAAmB,CAAC,IAAxC,KAAAwB,EACAa,EAASzB,GAAiBV,EAAW,CAAC,IADtC,KAAAuB,EAEAY,EAAStB,GAAmBb,EAAW,CAAC,EACxC,MAEFqC,GACFT,GAAAD,GAAAD,GAAAD,GAAAD,EAAAW,EAAShC,GAAW4B,CAAU,CAAC,IAA/B,KAAAP,EACAW,EAASzB,GAAiBuB,CAAU,CAAC,IADrC,KAAAR,EAEAU,EAAStB,GAAmBoB,CAAU,CAAC,IAFvC,KAAAP,EAGAU,EAAW,IAHX,KAAAT,EAIAQ,EAASd,GAAA,YAAAA,EAAQ,YAAY,IAJ7B,KAAAO,EAKA3B,GAAkB,EAEpBM,GAAYwB,EAAYM,EAAW5B,CAAa,EAChD,IAAM6B,EAAO1B,GAAkBqB,EAAYI,CAAS,EAC9CE,EAAWvB,GAAoBe,CAAU,EAGzCS,EAAQF,EAAoD,GAA7CxB,GAAoBmB,EAAYI,CAAS,EACxDI,EAAY,CAACH,GAAQ,CAACC,GAAY,CAACC,EAEzC,MAAO,CACL,aAAc,IAAMH,EACpB,YAAa,IAAMI,EACnB,QAAS,IAAM,CACbJ,EAAY,KACZnB,GAAYa,CAAU,EACtBd,GAAmBgB,CAAU,EAC7BlB,GAAqBkB,CAAU,EAI3BJ,GAAU,EAACR,GAAA,MAAAA,EAAQ,aAAYT,GAAkBsB,EAAoB,GAAG,CAC9E,CACF,CACF,CC1MO,SAASQ,GAAeC,EAAqC,CAClE,OAAO,KAAK,IAAI,IAAgB,IAAO,GAAK,KAAK,IAAIA,EAAqB,CAAoB,CAAC,CACjG,CAkBO,SAASC,GAAiBC,EAAyD,CACxF,GAAIA,EAAI,KAAO,GAAM,MAAO,YAC5B,GAAM,CAAE,OAAAC,CAAO,EAAID,EACnB,OAAI,OAAOC,GAAW,SAAiB,QACnCA,GAAU,KAAOA,EAAS,IAAY,YACtCA,GAAU,KAAOA,EAAS,KAAOA,IAAW,IAAY,UACrD,OACT,CAUO,SAASC,GAAkCC,EAAoBC,EAAkB,CACtF,GAAI,CACF,IAAMC,EAAM,aAAa,QAAQF,CAAU,EAC3C,GAAI,CAACE,EAAK,MAAO,CAAC,EAClB,IAAMC,EAAS,KAAK,MAAMD,CAAG,EAC7B,OAAK,MAAM,QAAQC,CAAM,GACzB,aAAa,WAAWH,CAAU,EAC3BG,EAAO,MAAM,CAACF,CAAG,GAFW,CAAC,CAGtC,OAAQG,EAAA,CACN,MAAO,CAAC,CACV,CACF,CASO,SAASC,GAAkCC,EAAYL,EAAaD,EAA0B,CACnG,GAAI,CACF,IAAMO,GAAY,IAAM,CACtB,GAAI,CACF,IAAML,EAAM,aAAa,QAAQF,CAAU,EAC3C,GAAI,CAACE,EAAK,MAAO,CAAC,EAClB,IAAMC,EAAS,KAAK,MAAMD,CAAG,EAC7B,OAAO,MAAM,QAAQC,CAAM,EAAIA,EAAU,CAAC,CAC5C,OAAQC,EAAA,CACN,MAAO,CAAC,CACV,CACF,GAAG,EACGI,EAAO,IAAI,IACjB,QAAWJ,KAAKG,EAAUC,EAAK,IAAIJ,EAAE,GAAIA,CAAC,EAC1C,QAAWA,KAAKE,EAAOE,EAAK,IAAIJ,EAAE,GAAIA,CAAC,EACvC,aAAa,QAAQJ,EAAY,KAAK,UAAU,CAAC,GAAGQ,EAAK,OAAO,CAAC,EAAE,MAAM,CAACP,CAAG,CAAC,CAAC,CACjF,OAAQG,EAAA,CAER,CACF,CAOO,SAASK,GAAYC,EAAeV,EAA0B,CACnE,GAAI,CACF,IAAME,EAAM,aAAa,QAAQF,CAAU,EAC3C,GAAI,CAACE,EAAK,OACV,IAAMC,EAAS,KAAK,MAAMD,CAAG,EAC7B,GAAI,CAAC,MAAM,QAAQC,CAAM,EAAG,OAC5B,IAAMQ,EAAO,IAAI,IAAID,CAAG,EAClBE,EAAYT,EAAO,OAAQC,GAAM,CAACO,EAAK,IAAIP,EAAE,EAAE,CAAC,EACtD,GAAIQ,EAAU,SAAWT,EAAO,OAAQ,OACpCS,EAAU,SAAW,EAAG,aAAa,WAAWZ,CAAU,EACzD,aAAa,QAAQA,EAAY,KAAK,UAAUY,CAAS,CAAC,CACjE,OAAQR,EAAA,CAER,CACF,CCxDA,IAAMS,GAAe,IAGfC,GAAyB,GAAK,KAO7B,SAASC,GAAgBC,EAAwB,CACtD,MAAO,cAAcA,EAAO,MAAM,EAAG,EAAE,CAAC,EAC1C,CAEA,IAAMC,GAAwB,CAC5B,KAAM,IAAG,GACT,MAAO,IAAG,GACV,QAAS,IAAG,EACd,EAKO,SAASC,GAAiBC,EAAiC,CA/ElE,IAAAC,EAAAC,EAAAC,GAgFE,GAAI,OAAO,QAAW,YACpB,OAAOL,GAGT,IAAMM,GAAkBH,EAAAD,EAAO,kBAAP,KAAAC,EAA0B,IAC5CI,GAAeH,EAAAF,EAAO,eAAP,KAAAE,EAAuB,GACtCI,GAAeH,GAAAH,EAAO,eAAP,KAAAG,GAAuB,IACtCI,EAAYP,EAAO,UACnBH,EAASG,EAAO,OAChBQ,EAAYZ,GAAgBC,CAAM,EAElCY,EAAyB,CAAC,EAC1BC,EAAU,IAAI,IACdC,EAAwB,CAAC,EAEzBC,EAAYC,GAAwB,CACxC,QAAWC,KAAMD,EACfE,EAAU,OAAOD,CAAE,EACf,CAAAJ,EAAQ,IAAII,CAAE,IAClBJ,EAAQ,IAAII,CAAE,EACdH,EAAY,KAAKG,CAAE,GAErB,KAAOH,EAAY,OAASjB,IAAc,CACxC,IAAMsB,EAASL,EAAY,MAAM,EAC7BK,GAAQN,EAAQ,OAAOM,CAAM,CACnC,CAMAC,GAAYJ,EAAKL,CAAS,CAC5B,EAKMO,EAAY,IAAI,IAShBG,EAAW,IAAY,CAC3B,KAAOT,EAAM,OAASH,GAAc,CAClC,IAAMa,EAAUV,EAAM,MAAM,EACxBU,GAASJ,EAAU,OAAOI,EAAQ,EAAE,CAC1C,CACF,EAEMC,EAAWC,GAA+B,CAC1CX,EAAQ,IAAIW,EAAM,EAAE,GAAKN,EAAU,IAAIM,EAAM,EAAE,IACnDN,EAAU,IAAIM,EAAM,EAAE,EACtBZ,EAAM,KAAKY,CAAK,EAChBH,EAAS,EACX,EASMI,EAAiBC,GAAiC,CACtD,QAAWF,KAASE,EACdb,EAAQ,IAAIW,EAAM,EAAE,IACxBN,EAAU,IAAIM,EAAM,EAAE,EACtBZ,EAAM,KAAKY,CAAK,GAIlBH,EAAS,CACX,EAEMM,EAAcC,GAA2BjB,EAAWF,CAAY,EACtE,QAAWe,KAASG,EAClBJ,EAAQC,CAAK,EAIf,IAAIK,EAAe,EACfC,EAAsB,EAIpBC,EAAiB,CAACL,EAAwBM,EAAU,KAAe,CACvE,GAAIN,EAAM,SAAW,EAAG,OACxB,IAAMO,EAAO,KAAK,UAAUP,CAAK,EAC3BV,EAAMU,EAAM,IAAKQ,GAAMA,EAAE,EAAE,EAE7BC,EACJ,GAAI,CACFA,EAAU,MAAMzB,EAAW,CACzB,OAAQ,OACR,UAAW,GACX,KAAAuB,EACA,QAAS,CACP,eAAgB,mBAChB,cAAe,UAAUjC,CAAM,EACjC,CACF,CAAC,CACH,OAAQkC,EAAA,CAENE,GAAYV,EAAOjB,EAAcE,CAAS,EAC1Cc,EAAcC,CAAK,EACnBI,IACAD,EAAe,KAAK,IAAI,EAAIQ,GAAeP,CAAmB,EAC9D,MACF,CAGA,IAAMQ,EAAkBC,GAAwB,CAC9C,GAAIC,GAAiBD,CAAG,IAAM,QAAS,CAMrC,GAAI,CAACA,EAAI,IAAMP,EAAS,CACtB,IAAMS,GAAOf,EAAM,OAAQQ,GAAMA,EAAE,YAAc,UAAU,EAC3D,GAAIO,GAAK,OAAS,GAAKA,GAAK,OAASf,EAAM,OAAQ,CACjDX,EAASW,EAAM,OAAQQ,GAAMA,EAAE,YAAc,UAAU,EAAE,IAAKA,GAAMA,EAAE,EAAE,CAAC,EACzEH,EAAeU,GAAM,EAAK,EAC1B,MACF,CACF,CAEA1B,EAASC,CAAG,EACZc,EAAsB,EACtBD,EAAe,EACf,MACF,CAEAO,GAAYV,EAAOjB,EAAcE,CAAS,EAC1Cc,EAAcC,CAAK,EACnBI,IACAD,EAAe,KAAK,IAAI,EAAIQ,GAAeP,CAAmB,CAChE,EAEIK,aAAmB,QACrBA,EAAQ,KAAKG,CAAc,EAAE,MAAM,IAAM,CACvCF,GAAYV,EAAOjB,EAAcE,CAAS,EAC1Cc,EAAcC,CAAK,EACnBI,IACAD,EAAe,KAAK,IAAI,EAAIQ,GAAeP,CAAmB,CAChE,CAAC,EAEDQ,EAAeH,CAAO,CAE1B,EAMMO,EAAU,OAAO,aAAgB,YAAc,IAAI,YAAgB,KACnEC,EAAcC,GAClBF,EAAUA,EAAQ,OAAOE,CAAC,EAAE,OAASA,EAAE,OACnCC,EAAaC,GAA2C,CAC5D,IAAMC,EAAuB,CAAC,EAC1BC,EAAQ,EACZ,QAAWd,KAAKY,EAAM,CACpB,IAAMG,EAAON,EAAW,KAAK,UAAUT,CAAC,CAAC,EAAI,EAE7C,GADIa,EAAI,OAAS,GAAKC,EAAQC,EAAOnD,IACjCiD,EAAI,QAAUvC,EAAc,MAChCuC,EAAI,KAAKb,CAAC,EACVc,GAASC,CACX,CACA,OAAOF,CACT,EAEMG,EAAQ,IAAY,CACxB,GAAI,CACF,GAAI,KAAK,IAAI,EAAIrB,EAAc,OAC/B,KAAOjB,EAAM,OAAS,GAKhB,OAAK,IAAI,EAAIiB,IALM,CAMvB,IAAMM,EAAUvB,EAAM,OAAQsB,GAAM,CAACrB,EAAQ,IAAIqB,EAAE,EAAE,CAAC,EAEtD,GADAtB,EAAM,OAAS,EACXuB,EAAQ,SAAW,EAAG,MAC1B,IAAMT,EAAQmB,EAAUV,CAAO,EAC/B,GAAIT,EAAM,SAAW,EAAG,MAEpBA,EAAM,OAASS,EAAQ,QACzBvB,EAAM,KAAK,GAAGuB,EAAQ,MAAMT,EAAM,MAAM,CAAC,EAE3CK,EAAeL,CAAK,CACtB,CACF,OAAQQ,EAAA,CAER,CACF,EAEIiB,EAAmB,GACnBC,EAAoD,KAExDA,EAAa,YAAY,IAAM,CACxBD,GACLD,EAAM,CACR,EAAG3C,CAAe,EAElB,IAAM8C,EAAqB,IAAY,CACjC,SAAS,kBAAoB,UAC/BH,EAAM,CAEV,EAMMI,EAAa,IAAY,CAC7BJ,EAAM,CACR,EAEA,gBAAS,iBAAiB,mBAAoBG,CAAkB,EAChE,OAAO,iBAAiB,WAAYC,CAAU,EAEvC,CACL,KAAK9B,EAA4B,CAC/BD,EAAQC,CAAK,EACTZ,EAAM,QAAUJ,GAClB0C,EAAM,CAEV,EACA,MAAAA,EACA,SAAgB,CACdC,EAAmB,GACfC,IAAe,OACjB,cAAcA,CAAU,EACxBA,EAAa,MAEf,SAAS,oBAAoB,mBAAoBC,CAAkB,EACnE,OAAO,oBAAoB,WAAYC,CAAU,EACjDJ,EAAM,CACR,CACF,CACF,CChRA,IAAMK,GAAe,IAOd,SAASC,GAAoBC,EAAwB,CAC1D,MAAO,mBAAmBA,EAAO,MAAM,EAAG,EAAE,CAAC,EAC/C,CAEA,IAAMC,GAA4B,CAChC,KAAM,IAAG,GACT,MAAO,IAAG,GACV,QAAS,IAAG,EACd,EAEO,SAASC,GAAgBC,EAAoC,CArEpE,IAAAC,EAAAC,EAAAC,EAsEE,GAAI,OAAO,QAAW,YAAa,OAAOL,GAE1C,IAAMM,GAAkBH,EAAAD,EAAO,kBAAP,KAAAC,EAA0B,IAC5CI,GAAeH,EAAAF,EAAO,eAAP,KAAAE,EAAuB,IACtCI,GAAcH,EAAAH,EAAO,cAAP,KAAAG,EAAsB,EACpCI,EAAYX,GAAoBI,EAAO,MAAM,EAE7CQ,EAAyB,CAAC,EAC1BC,EAAa,IAAI,IACjBC,EAAU,IAAI,IACdC,EAAwB,CAAC,EAE3BC,EAAe,EACfC,EAAsB,EACtBC,EAAW,GAETC,EAAYC,GAA4B,CAM5C,IALAP,EAAW,OAAOO,EAAK,EAAE,EACpBN,EAAQ,IAAIM,EAAK,EAAE,IACtBN,EAAQ,IAAIM,EAAK,EAAE,EACnBL,EAAY,KAAKK,EAAK,EAAE,GAEnBL,EAAY,OAAShB,IAAc,CACxC,IAAMsB,EAASN,EAAY,MAAM,EAC7BM,GAAQP,EAAQ,OAAOO,CAAM,CACnC,CAGAC,GAAY,CAACF,EAAK,EAAE,EAAGT,CAAS,CAClC,EAEMY,EAAcH,GAA4B,CAC9CI,GAAY,CAACJ,CAAI,EAAGX,EAAcE,CAAS,EACvC,CAACG,EAAQ,IAAIM,EAAK,EAAE,GAAK,CAACP,EAAW,IAAIO,EAAK,EAAE,IAClDP,EAAW,IAAIO,EAAK,EAAE,EACtBR,EAAQ,KAAKQ,CAAI,GAMf,KAAK,IAAI,GAAKJ,IAChBC,IACAD,EAAe,KAAK,IAAI,EAAIS,GAAeR,CAAmB,EAElE,EAEMS,EAAaN,GAA4B,CAC7C,IAAIO,EACJ,GAAI,CACFA,EAAM,MAAMvB,EAAO,IAAK,CACtB,OAAQ,OACR,UAAW,GACX,KAAMgB,EAAK,KACX,QAAShB,EAAO,OAClB,CAAC,CACH,OAAQwB,EAAA,CAENL,EAAWH,CAAI,EACf,MACF,CAEA,IAAMS,EAAUC,GAAsB,CApI1C,IAAAzB,EAqIM,IAAM0B,EAAUC,GAAiBF,CAAC,EAClC,GAAIC,IAAY,QAAS,CACvBR,EAAWH,CAAI,EACf,MACF,CAIIW,IAAY,aAAW1B,EAAAD,EAAO,SAAP,MAAAC,EAAA,KAAAD,EAAgBgB,EAAMU,EAAE,SACnDX,EAASC,CAAI,EACbH,EAAsB,EACtBD,EAAe,CACjB,EAGIW,aAAe,QAASA,EAAI,KAAKE,CAAM,EAAE,MAAM,IAAMN,EAAWH,CAAI,CAAC,EACpES,EAAOF,CAAG,CACjB,EAEMM,EAAQ,IAAY,CACxB,GAAI,CAEF,GADIf,GACA,KAAK,IAAI,EAAIF,EAAc,OAC/B,IAAIkB,EAAe,EACnB,KAAOtB,EAAQ,OAAS,GAAKsB,EAAexB,GACtC,OAAK,IAAI,EAAIM,IADsC,CAEvD,IAAMI,EAAOR,EAAQ,MAAM,EAC3BC,EAAW,OAAOO,EAAK,EAAE,EACrB,CAAAN,EAAQ,IAAIM,EAAK,EAAE,IACvBc,IACAR,EAAUN,CAAI,EAChB,CACF,OAAQQ,EAAA,CAER,CACF,EAKMO,EAAWC,GAAyBzB,EAAWF,CAAY,EACjE,QAAWW,KAAQe,EACZtB,EAAW,IAAIO,EAAK,EAAE,IACzBP,EAAW,IAAIO,EAAK,EAAE,EACtBR,EAAQ,KAAKQ,CAAI,GAOjBe,EAAS,OAAS,GAAGX,GAAYW,EAAU1B,EAAcE,CAAS,EAEtE,IAAM0B,EAAa,YAAYJ,EAAOzB,CAAe,EAC/C8B,EAAqB,IAAY,CACjC,SAAS,kBAAoB,UAAUL,EAAM,CACnD,EACMM,EAAa,IAAYN,EAAM,EACrC,gBAAS,iBAAiB,mBAAoBK,CAAkB,EAChE,OAAO,iBAAiB,WAAYC,CAAU,EAEvC,CACL,KAAKnB,EAAyB,CAC5B,GAAI,CAAAF,GACA,EAAAJ,EAAQ,IAAIM,EAAK,EAAE,GAAKP,EAAW,IAAIO,EAAK,EAAE,GAGlD,IAAI,KAAK,IAAI,EAAIJ,EAAc,CAQ7BQ,GAAY,CAACJ,CAAI,EAAGX,EAAcE,CAAS,EAC3CE,EAAW,IAAIO,EAAK,EAAE,EACtBR,EAAQ,KAAKQ,CAAI,EACjB,MACF,CACAM,EAAUN,CAAI,EAChB,EACA,MAAAa,EACA,SAAgB,CACd,cAAcI,CAAU,EACxB,SAAS,oBAAoB,mBAAoBC,CAAkB,EACnE,OAAO,oBAAoB,WAAYC,CAAU,EACjDN,EAAM,EACNf,EAAW,EACb,CACF,CACF,CCvMA,IAAMsB,GAAiB,KAAU,IAEjC,SAASC,GAASC,EAAqBC,EAAyB,CAK9D,MAAO,GAAG,mBAAmBD,CAAW,CAAC,IAAI,mBAAmBC,CAAO,CAAC,EAC1E,CAOO,SAASC,GAAsBC,EAAgBL,GAAgBM,EAAkC,CACtG,IAAMC,EAAS,IAAI,IAIbC,EAAY,YAAYC,GAAcH,CAAM,CAAC,IAE7CI,EAAa,CAACR,EAAqBC,IACvC,GAAGK,CAAS,GAAG,mBAAmBN,CAAW,CAAC,IAAI,mBAAmBC,CAAO,CAAC,GAEzEQ,EAAmBC,GAAiE,CACxF,IAAMC,EAASD,EAAI,MAAMJ,EAAU,MAAM,EACnCM,EAAMD,EAAO,QAAQ,GAAG,EAC9B,GAAIC,EAAM,EAAG,OAAO,KACpB,GAAI,CACF,MAAO,CACL,YAAa,mBAAmBD,EAAO,MAAM,EAAGC,CAAG,CAAC,EACpD,QAAS,mBAAmBD,EAAO,MAAMC,EAAM,CAAC,CAAC,CACnD,CACF,OAAQC,EAAA,CACN,OAAO,IACT,CACF,EAEMC,EAAkB,IAAgB,CACtC,GAAI,CACF,IAAMC,EAAiB,CAAC,EACxB,QAASC,EAAI,EAAGA,EAAI,aAAa,OAAQA,IAAK,CAC5C,IAAMN,EAAM,aAAa,IAAIM,CAAC,EAC1BN,GAAA,MAAAA,EAAK,WAAWJ,IAClBS,EAAK,KAAKL,CAAG,CAEjB,CACA,OAAOK,CACT,OAAQF,EAAA,CACN,MAAO,CAAC,CACV,CACF,EAIMI,EAAaC,GACjBA,EAAW,YAAcA,EAAW,OAASA,EAAW,MAAQ,EAAIA,EAAW,MAAQf,GAAS,KAAK,IAAI,EAqB3G,OAAI,OAAO,QAAW,cAnBK,IAAY,CACrC,QAAWO,KAAOI,EAAgB,EAChC,GAAI,CACF,IAAMK,EAAM,aAAa,QAAQT,CAAG,EACpC,GAAI,CAACS,EAAK,SACV,IAAMD,EAAa,KAAK,MAAMC,CAAG,EACjC,GAAIF,EAAUC,CAAU,EAAG,CACzB,aAAa,WAAWR,CAAG,EAC3B,QACF,CACA,IAAMU,EAASX,EAAgBC,CAAG,EAClC,GAAI,CAACU,EAAQ,SACbf,EAAO,IAAIN,GAASqB,EAAO,YAAaA,EAAO,OAAO,EAAGF,CAAU,CACrE,OAAQL,EAAA,CAER,CAEJ,GAGqB,EAGd,CACL,IAAIb,EAAqBC,EAAoC,CAC3D,IAAMoB,EAAQhB,EAAO,IAAIN,GAASC,EAAaC,CAAO,CAAC,EACvD,OAAKoB,EACDJ,EAAUI,CAAK,GACjBhB,EAAO,OAAON,GAASC,EAAaC,CAAO,CAAC,EACrC,MAEFoB,EALY,IAMrB,EAEA,IAAIrB,EAAqBC,EAAiBiB,EAA8B,CACtE,IAAMR,EAAMX,GAASC,EAAaC,CAAO,EACzCI,EAAO,IAAIK,EAAKQ,CAAU,EAC1B,GAAI,CACF,aAAa,QAAQV,EAAWR,EAAaC,CAAO,EAAG,KAAK,UAAUiB,CAAU,CAAC,CACnF,OAAQL,EAAA,CAER,CACF,EAEA,OAAc,CACZR,EAAO,MAAM,EACb,QAAWiB,KAAYR,EAAgB,EACrC,GAAI,CACF,aAAa,WAAWQ,CAAQ,CAClC,OAAQT,EAAA,CAER,CAEJ,CACF,CACF,CCnIO,IAAMU,GAAiC,CAC5C,SACA,eACA,gBACA,YACA,cACA,mBACA,gBACA,kBACA,kBACA,oBACA,qBACA,aACA,QACA,YACA,YACA,SACF,EAGO,SAASC,GAAaC,EAA4B,CACvD,OAAOC,GAAkBD,CAAS,IAAM,IAC1C,CAGO,SAASC,GAAkBD,EAAkC,CAjCpE,IAAAE,EAkCE,GAAI,CAACF,EAAW,OAAO,KACvB,IAAMG,EAAIH,EAAU,YAAY,EAChC,OAAOE,EAAAJ,GAAY,KAAMM,GAAUD,EAAE,SAASC,EAAM,YAAY,CAAC,CAAC,IAA3D,KAAAF,EAAgE,IACzE,CA8CO,SAASG,GAAkBC,EAA2B,CAC3D,IAAMC,EAAID,EAAU,YAAY,EAChC,MAAI,mCAAmC,KAAKC,CAAC,EAAU,SACnD,yCAAyC,KAAKA,CAAC,EAAU,SACtD,SACT,CAEO,SAASC,GAAoBC,EAAkBC,EAA4B,CAChF,GAAI,CAACD,EAAU,MAAO,SACtB,GAAI,CACF,IAAME,EAAS,IAAI,IAAIF,CAAQ,EAC/B,GAAIC,EACF,GAAI,CACF,GAAI,IAAI,IAAIA,CAAS,EAAE,OAASC,EAAO,KAAM,MAAO,QACtD,OAAQC,EAAA,CAER,CAEF,IAAMC,EAAOF,EAAO,SAAS,YAAY,EACzC,MAAI,yCAAyC,KAAKE,CAAI,EAAU,SAI5D,6EAA6E,KAAKA,CAAI,EAAU,SAC7F,UACT,OAAQD,EAAA,CACN,MAAO,QACT,CACF,CAEO,SAASE,GAA0BL,EAAiC,CACzE,GAAI,CAACA,EAAU,OAAO,KACtB,GAAI,CACF,OAAO,IAAI,IAAIA,CAAQ,EAAE,QAC3B,OAAQG,EAAA,CACN,OAAO,IACT,CACF,CAiBO,IAAMG,GAAmC,CAC9C,QACA,SACA,SACA,SACA,SACA,UACA,SACA,WACF,EAUO,SAASC,GACdC,EACyE,CACzE,IAAMC,EAAoC,CAAC,EACrCC,EAAmC,CAAC,EAC1C,GAAI,CACF,IAAMC,EACJ,OAAOH,GAAW,UAAYA,aAAkB,gBAC5C,IAAI,gBAAgBA,CAAM,EAC1B,OAAO,QAAQA,CAAM,EAAE,QAAQ,CAAC,CAACI,EAAGC,CAAC,IAA+B,CAClE,IAAMC,EAAQ,MAAM,QAAQD,CAAC,EAAIA,EAAE,CAAC,EAAIA,EACxC,OAAOC,IAAU,OAAY,CAAC,EAAI,CAAC,CAACF,EAAGE,CAAK,CAAC,CAC/C,CAAC,EACP,OAAW,CAACF,EAAGC,CAAC,IAAKF,EAGfC,EAAE,WAAW,MAAM,EACfA,KAAKH,IAAYA,EAAUG,CAAC,EAAIC,GAC7BP,GAAc,SAASM,CAAC,IAC3BA,KAAKF,IAAWA,EAASE,CAAC,EAAIC,GAG1C,OAAQV,EAAA,CAER,CACA,MAAO,CAAE,UAAAM,EAAW,SAAAC,CAAS,CAC/B,CAEO,SAASK,GAAgBC,EAAiB,CAC/C,IAAMC,EAAID,EAAE,SAAS,EACrB,OAAIC,EAAI,EAAU,QACdA,EAAI,GAAW,UACfA,EAAI,GAAW,YACZ,SACT,CAsBO,SAASC,GAAqBC,EAI1B,CACT,IAAMC,EAAOC,GAA0B,cAAeF,CAAI,EAC1D,MAAO,GAAGC,EAAK,WAAW,IAAIA,EAAK,aAAa,EAClD,CAMO,SAASC,GACdC,EACAH,EAUsB,CA7OxB,IAAAI,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EA8OE,IAAMC,GAAKP,GAAAD,EAAAJ,GAAA,YAAAA,EAAM,YAAN,YAAAI,EAAiB,SAAjB,KAAAC,EAA2B,GAChCQ,GAAUN,GAAAD,EAAAN,GAAA,YAAAA,EAAM,UAAN,YAAAM,EAAe,SAAf,KAAAC,EAAyB,GACnCO,GAAMN,EAAAR,GAAA,YAAAA,EAAM,MAAN,KAAAQ,EAAa,IAAI,KAC7B,MAAO,CACL,UAAAL,EACA,UAAW,GACX,WAAWM,EAAAT,GAAA,YAAAA,EAAM,YAAN,KAAAS,EAAmB,CAAC,EAC/B,UAAUC,EAAAV,GAAA,YAAAA,EAAM,WAAN,KAAAU,EAAkB,CAAC,EAC7B,YAAaE,EAAKnC,GAAkBmC,CAAE,EAAI,UAC1C,cAAeC,EACXjC,GAAoBiC,EAASb,GAAA,YAAAA,EAAM,SAAS,EAC5C,SACJ,eAAgBd,GAA0B2B,CAAO,EACjD,UAAWjB,GAAgBkB,CAAG,EAC9B,WAAWH,EAAA,CAAC,MAAO,MAAO,MAAO,MAAO,MAAO,MAAO,KAAK,EAAEG,EAAI,OAAO,CAAC,IAA9D,KAAAH,EAAmE,MAC9E,YAAYX,GAAA,YAAAA,EAAM,aAAc,IAAQe,GAAaH,CAAE,CACzD,CACF,CC3PA,IAAAI,GAMO,8BAiBA,SAASC,GAAWC,EAA4B,CACrD,OAAOC,MAAA,CACL,GAAID,EAAE,IACFA,EAAE,KAAO,CAAE,KAAM,CAAC,GAAGA,EAAE,IAAI,CAAE,EAAI,CAAC,GAClCA,EAAE,KACF,CACE,KAAM,OAAO,YACX,OAAO,QAAQA,EAAE,IAAI,EAAE,IAAI,CAAC,CAACE,EAAKC,CAAM,IAAM,CAACD,EAAK,CAAC,GAAGC,CAAM,CAAC,CAAC,CAClE,CACF,EACA,CAAC,GACDH,EAAE,WAAa,OAAY,CAAE,SAAUA,EAAE,QAAS,EAAI,CAAC,EAE/D,CAGO,SAASI,GAAkBJ,EAA8B,CAC9D,IAAMK,EAAON,GAAWC,CAAC,EACzB,SAAO,kBAAcK,KAAM,oBAAgBA,CAAI,CAAC,CAClD,CAGO,SAASC,GAAcC,EAAoD,CAChF,IAAMC,EAAkC,CAAC,EACzC,QAAWR,KAAKO,EAAOC,EAAIR,EAAE,EAAE,EAAII,GAAkBJ,CAAC,EACtD,OAAOQ,CACT,CAMO,SAASC,GAAYC,EAA4B,CACtD,OAAO,OAAOA,GAAW,SAAWA,KAAS,iBAAaA,CAAM,CAClE,CCrDO,IAAMC,GAA8B,aA+DrCC,GAAQ,CAAC,MAAO,SAAU,MAAM,EAG/B,SAASC,GAAaC,EAAyC,CACpE,GAAI,CACF,IAAMC,EAAM,aAAa,QAAQJ,GAA8BG,CAAM,EACrE,GAAI,CAACC,EAAK,OAAO,KACjB,IAAMC,EAAI,KAAK,MAAMD,CAAG,EACxB,MACE,CAACC,GACD,OAAOA,GAAM,UACbA,EAAE,IAAM,GACR,OAAOA,EAAE,SAAY,UACrB,OAAOA,EAAE,MAAS,UAClB,CAACJ,GAAM,SAASI,EAAE,IAAI,GACtB,OAAOA,EAAE,OAAU,UACnBA,EAAE,QAAU,MACZ,MAAM,QAAQA,EAAE,KAAK,GACrB,EAAEA,EAAE,cAAgB,MAAQ,MAAM,QAAQA,EAAE,WAAW,IACvD,OAAOA,EAAE,SAAY,UAErB,EAAEA,EAAE,aAAe,QAAc,OAAOA,EAAE,YAAe,UAAYA,EAAE,aAAe,MAAQ,CAAC,MAAM,QAAQA,EAAE,UAAU,GAElH,KAEFA,CACT,OAAQC,EAAA,CACN,OAAO,IACT,CACF,CAGO,SAASC,GAAcJ,EAAgBK,EAA8B,CAC1E,GAAI,CACF,aAAa,QAAQR,GAA8BG,EAAQ,KAAK,UAAUK,CAAI,CAAC,CACjF,OAAQF,EAAA,CAER,CACF,CAaO,SAASG,GAAqBN,EAAwB,CAE3D,MACE,8CAFU,KAAK,UAAUH,GAA8BG,CAAM,EAAE,QAAQ,KAAM,SAAS,EAGhD,uTAS1C,CCjGA,IAAAO,GAA+B,8BC1B/B,IAAAC,GAA+B,8BAmBxB,IAAMC,GACX,oJAEWC,GACX,kJAGEC,GAAc,GACdC,GAAiB,GASrB,SAASC,IAAiD,CAhD1D,IAAAC,EAiDE,GAAI,CACF,OAAOA,EAAA,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAAE,IAAI,kBAAkB,IAAlE,KAAAA,EAAuE,MAChF,OAAQC,EAAA,CACN,MACF,CACF,CAEO,SAASC,GAAsBC,EAAwC,CAxD9E,IAAAH,EAyDE,IAAMI,EAAUC,GAAY,CAAE,aAAcF,EAAO,aAAc,OAAQA,EAAO,MAAO,CAAC,EAClFG,GAAYN,EAAAI,EAAQ,aAAa,IAArB,KAAAJ,EAA0B,QACtCO,EAAgBR,GAA2B,EAK3CS,EAAgD,OAAO,wBAAwB,EAClF,KAAMC,GAAQ,CACb,IAAMC,EAAID,EACV,OAAKC,EAAE,wBAOFC,KACHA,GAAc,GACd,QAAQ,KAAKC,EAAiB,GAEzBF,IAVAG,KACHA,GAAiB,GACjB,QAAQ,MAAMC,EAAkB,GAE3B,KAOX,CAAC,EACA,MAAM,KACAD,KACHA,GAAiB,GACjB,QAAQ,MAAMC,EAAkB,GAE3B,KACR,EAECC,EAAoC,KASlCC,EAA0C,CAAE,IAAK,IAAM,OAAQ,GAAK,KAAM,GAAK,EAC/EC,GAAe,IAAsD,CAlG7E,IAAAjB,EAmGI,GAAIG,EAAO,eAAgB,OAAOe,EAAA,GAAKf,EAAO,gBAC9C,IAAMgB,EAAOC,GAAajB,EAAO,QAAU,OAAO,EAClD,OAAIgB,EAAa,CAAE,QAASA,EAAK,QAAS,YAAYnB,EAAAgB,EAAgBG,EAAK,IAAI,IAAzB,KAAAnB,EAA8B,GAAK,EAClF,IACT,GAAG,EAEH,SAASqB,EAAuBC,EAA8B,CAG5D,IAAMC,EAAK,SAAS,gBAChBA,EAAG,QAAQ,kBAAoB,SACjCA,EAAG,QAAQ,gBAAkBD,EAAQ,QACrCC,EAAG,QAAQ,sBAAqB,mBAAeD,EAAQ,UAAU,EAErE,CAEA,MAAO,CACL,QAAS,GAET,MAAM,OAAOE,EAAO,CAtHxB,IAAAxB,EAAAyB,EAAAC,EAuHM,IAAMjB,EAAM,MAAMD,EAClB,GAAI,CAACC,EAAK,OAAO,KACjB,IAAMa,EAAUb,EAAI,kBAAkB,CAAE,UAAAH,EAAW,cAAAC,CAAc,CAAC,EAAE,OAAOiB,CAAK,EAIhF,OAAAT,EAAcY,EAAAT,EAAA,GACTI,GADS,CAEZ,aAAaG,GAAAzB,EAAAsB,EAAQ,cAAR,KAAAtB,EAAuBe,GAAA,YAAAA,EAAa,cAApC,KAAAU,EAAmD,KAChE,MAAOP,IAAA,IAAMQ,EAAAX,GAAA,YAAAA,EAAa,QAAb,KAAAW,EAAsB,CAAC,GAAOJ,EAAQ,MACrD,GACAM,GAAczB,EAAO,QAAU,QAAS,CACtC,EAAG,EACH,QAASY,EAAY,QACrB,QAAM,mBAAeA,EAAY,UAAU,EAC3C,MAAOA,EAAY,MACnB,YAAaA,EAAY,YACzB,QAAS,KAAK,IAAI,CACpB,CAAC,EACDM,EAAuBC,CAAO,EACvBA,CACT,EAEA,cAAcO,EAAQ,CA9I1B,IAAA7B,EAAAyB,EAAAC,EA+IM,OAAOA,GAAAD,EAAAV,GAAA,YAAAA,EAAa,MAAMc,KAAnB,KAAAJ,GAA8BzB,EAAAG,EAAO,eAAP,YAAAH,EAAsB6B,KAApD,KAAAH,EAA+D,IACxE,EAEA,cAAcG,EAAQ,CAlJ1B,IAAA7B,EAAAyB,EAoJM,OAAOA,GAAAzB,EAAAG,EAAO,oBAAP,YAAAH,EAA2B6B,KAA3B,KAAAJ,EAAsC,IAC/C,EAEA,gBAAiB,CAvJrB,IAAAzB,EAwJM,OAAOA,EAAAG,EAAO,iBAAP,KAAAH,EAAyB,IAClC,EAEA,aAAc,CAEd,EAEA,YAAa,CACX,IAAM8B,EAAIf,GAAA,KAAAA,EAAeE,EACzB,OAAKa,EACE,CACL,QAASA,EAAE,QACX,WAAYA,EAAE,WACd,QAAM,mBAAeA,EAAE,UAAU,CACnC,EALe,IAMjB,EAEA,MAAM,OAAOC,EAAaC,EAAY,CAzK1C,IAAAhC,EA0KM,IAAMS,EAAM,MAAMD,EAClB,MAAI,CAACC,GAAO,CAACuB,GAAcA,EAAW,SAAW,EACxCA,GAAA,MAAAA,EAAa,GAAK,CAAE,UAAWA,EAAW,CAAC,EAAG,gBAAiB,CAAE,EAAI,KAKvE,CAAE,WAAWhC,EAHJS,EACb,kBAAkB,CAAE,UAAAH,EAAW,cAAAC,CAAc,CAAC,EAC9C,OAAO,CAAE,WAAY,CAAC,CAAE,GAAIwB,EAAa,WAAAC,CAAW,CAAC,CAAE,CAAC,EAC/B,YAAYD,CAAW,IAA/B,KAAA/B,EAAoCgC,EAAW,CAAC,EAAG,gBAAiB,CAAE,CAC5F,EAGA,MAAO,IAAG,GACV,KAAM,IAAG,GACT,cAAe,IAAG,GAClB,SAAU,IAAG,GACb,cAAe,IAAM,KACrB,aAAc,IAAM,QAAQ,QAAQ,CAAC,CAAC,EACtC,SAAU,KAAO,CAAE,UAAW,CAAC,EAAG,WAAY,CAAE,GAChD,QAAS,IAAG,GACZ,QAAS,IAAM5B,EAAQ,QAAQ,CACjC,CACF,CC5KO,IAAM6B,GAAa,CAAC,OAAQ,KAAM,KAAM,IAAI,EACtCC,GAAe,CAAC,QAAS,SAAU,MAAO,SAAS,EACnDC,GAAkB,CAAC,QAAS,SAAU,MAAO,SAAS,EACtDC,GAAc,CAAC,KAAM,KAAM,IAAI,EAC/BC,GAAgB,CAAC,SAAU,SAAU,MAAM,EAC3CC,GAAc,CAAC,UAAW,QAAS,QAAQ,EAC3CC,GAAiB,CAAC,UAAW,YAAa,OAAO,EACjDC,GAAoB,CAAC,OAAQ,SAAU,OAAO,EAC9CC,GAAe,CAAC,OAAQ,SAAU,YAAa,MAAM,EACrDC,GAAa,CAAC,QAAS,SAAS,EAChCC,GAAqB,CAAC,EAAG,EAAG,CAAC,EAC7BC,GAAuB,CAAC,EAAG,EAAG,CAAC,EA6F/BC,GAAmB,CAAC,QAAS,WAAY,QAAQ,EACjDC,GAAmB,CAAC,OAAQ,QAAS,SAAU,KAAK,EACpDC,GAAkB,EAClBC,GAA0B,EAyD1BC,GAAkB,GAClBC,GAAkB,EAClBC,GAAqB,GACrBC,GAAiB,EACjBC,GAAqB,IAM3B,SAASC,GAAkBC,EAAwB,CACxD,GAAIA,GAAQ,MAAQ,OAAOA,GAAS,UAAY,MAAM,QAAQA,CAAI,EAAG,MAAO,GAC5E,IAAMC,EAAID,EACV,OAAIC,EAAE,OAAS,OAAe,GAC1B,MAAM,QAAQA,EAAE,QAAQ,EAAUA,EAAE,SAAS,KAAKF,EAAiB,EAChE,EACT,CC3LO,SAASG,GACdC,EACAC,EACAC,EACAC,EACY,CACZ,IAAMC,EAA8B,CAAC,EAGrC,CAGE,IAAIC,EAAY,GACVC,EAAuB,CAAC,EAExBC,EAAU,IAAY,CAC1B,GAAIF,EAAW,OACf,IAAMG,EAAM,KAAK,IAAI,EAErB,IADAF,EAAW,KAAKE,CAAG,EACZF,EAAW,OAAS,GAAKE,EAAMF,EAAW,CAAC,EAAK,KACrDA,EAAW,MAAM,EAEfA,EAAW,QAAU,IACvBD,EAAY,GACZL,EAAK,YAAY,EAErB,EAEAC,EAAK,iBAAiB,QAASM,CAAO,EACtCH,EAAS,KAAK,IAAMH,EAAK,oBAAoB,QAASM,CAAO,CAAC,CAChE,CAGA,CACE,IAAIF,EAAY,GAEVI,EAAUC,GAAmB,CAGjC,GAFIL,GACA,EAAEK,EAAE,kBAAkB,OACtB,CAACT,EAAK,SAASS,EAAE,MAAM,GAAKT,IAASS,EAAE,OAAQ,OACnDL,EAAY,GACZ,IAAMM,EAAM,OAAO,QAAW,YAAc,OAAO,aAAa,EAAI,KAC9DC,EAAkBD,EAAMA,EAAI,SAAS,EAAE,OAAS,EACtDX,EAAK,YAAa,CAAE,gBAAAY,CAAgB,CAAC,CACvC,EAEA,SAAS,iBAAiB,OAAQH,CAAM,EACxCL,EAAS,KAAK,IAAM,SAAS,oBAAoB,OAAQK,CAAM,CAAC,CAClE,CAGA,CACE,IAAIJ,EAAY,GACZQ,EAAY,GACZC,EAAwD,KAEtDC,EAAkB,IAAY,CAC9BD,IAAoB,OACtB,aAAaA,CAAe,EAC5BA,EAAkB,KAEtB,EAEME,EAAkB,IAAY,CAC9BX,GAAa,CAACQ,IAClBE,EAAgB,EAChBD,EAAkB,WAAW,IAAM,CAC7B,CAACT,GAAaQ,IAChBR,EAAY,GACZL,EAAK,mBAAmB,EAE5B,EAAG,GAAI,EACT,EAEMiB,EAAW,IAAY,CAC3BF,EAAgB,EAChBC,EAAgB,CAClB,EAEME,EAAcC,GAA+C,CACjE,QAAWC,KAASD,EAClBN,EAAYO,EAAM,kBAAoB,GACjCP,EACAG,EAAgB,EADLD,EAAgB,CAGpC,EACMM,EAAK,IAAI,qBAAqBH,EAAY,CAAE,UAAW,CAAC,EAAG,CAAE,CAAC,EACpEG,EAAG,QAAQpB,CAAI,EACf,OAAO,iBAAiB,SAAUgB,EAAU,CAAE,QAAS,EAAK,CAAC,EAE7Db,EAAS,KAAK,IAAM,CAClBiB,EAAG,WAAW,EACd,OAAO,oBAAoB,SAAUJ,CAAQ,EAC7CF,EAAgB,CAClB,CAAC,CACH,CAMA,IAAIZ,GAAA,YAAAA,EAAS,WAAY,GAAO,CAC9B,IAAIE,EAAY,GACViB,EAAapB,GAAA,KAAAA,EAAqB,KAAK,IAAI,EAE3CqB,EAAe,IAAY,CAE/B,GADIlB,GACA,SAAS,kBAAoB,SAAU,OAC3C,IAAMmB,EAAU,KAAK,IAAI,EAAIF,EACzBE,EAAU,OACZnB,EAAY,GACZL,EAAK,WAAY,CAAE,WAAYwB,CAAQ,CAAC,EAE5C,EAEA,SAAS,iBAAiB,mBAAoBD,CAAY,EAC1DnB,EAAS,KAAK,IAAM,SAAS,oBAAoB,mBAAoBmB,CAAY,CAAC,CACpF,CAEA,MAAO,IAAM,CACX,QAAWE,KAAKrB,EAAUqB,EAAE,CAC9B,CACF,CHnEA,IAAMC,GAAqB,wCAGrBC,EAAW,IAAI,IAoBjBC,GAA6B,KAU1B,SAASC,GACdC,EACAC,EACM,CACN,IAAMC,EAAQL,EAAS,IAAIG,CAAM,EAC7BE,GAASA,EAAM,UAASA,EAAM,OAASD,EAC7C,CA+MA,SAASE,GAAcC,EAAqC,CAC1D,MAAO,UAAWA,GAAK,aAAcA,GAAK,eAAgBA,GAAK,aAAcA,GAAK,WAAYA,GAAK,cAAeA,CACpH,CAsFA,IAAMC,GAAyB,EAE/B,SAASC,IAA0B,CACjC,OAAOC,GAAa,CACtB,CAeA,SAASC,IAAkC,CApa3C,IAAAC,EAqaE,GAAI,OAAO,QAAW,YACtB,QAAOA,EAAA,OAAO,WAAP,YAAAA,EAAiB,WAAY,MACtC,CAqCA,SAASC,IAA0F,CAiBjG,IAAMC,EAAU,IAAI,QACdC,EAAU,IAAI,IAChBC,EAAY,GAEVC,EAAkB,CAACC,EAA+BC,EAAmBC,IAAqC,CAC9G,IAAMC,EAASH,EAAI,IAAIC,CAAS,EAChC,OAAIC,IAAa,KACXC,EAAe,IACnBH,EAAI,IAAIC,EAAW,IAAI,GAAK,EACrB,IAEJE,EAIDA,EAAO,IAAID,CAAQ,EAAU,IACjCC,EAAO,IAAID,CAAQ,EACZ,KALLF,EAAI,IAAIC,EAAW,IAAI,IAAI,CAACC,CAAQ,CAAC,CAAC,EAC/B,GAKX,EAIME,EAAiB,OAAO,QAAW,aAAe,UAAW,OAE7DC,EAAe,IAA0B,CAC7C,GAAI,CAACD,EAAgB,OACrB,IAAME,EAAM,OAA0C,MAYtD,OAAO,OAAO,OAAU,YAAcA,aAAc,MAAQA,EAAK,MACnE,EAEMC,EAAmB,IAAY,CACnC,GAAIH,EAAgB,CAIb,QAAQ,QAAQ,EAAE,KAAK,IAAM,CAChCN,EAAY,GACZD,EAAQ,MAAM,CAChB,CAAC,EACD,MACF,CACA,IAAIW,EAAO,GACLC,EAAQ,IAAY,CACpBD,IACJA,EAAO,GACPV,EAAY,GACZD,EAAQ,MAAM,EAChB,EAEA,GADA,WAAWY,EAAO,CAAC,EACf,OAAO,gBAAmB,WAAY,CACxC,IAAMC,EAAK,IAAI,eACfA,EAAG,MAAM,UAAY,IAAM,CACzBA,EAAG,MAAM,MAAM,EACfA,EAAG,MAAM,MAAM,EACfD,EAAM,CACR,EACAC,EAAG,MAAM,YAAY,CAAC,CACxB,CACF,EAEA,MAAO,CACL,YAAYT,EAAmBC,EAAkC,CAC/D,IAAMI,EAAKD,EAAa,EACxB,GAAIC,EAAI,CACN,IAAIK,EAAOf,EAAQ,IAAIU,CAAE,EACzB,OAAKK,IACHA,EAAO,IAAI,IAGXf,EAAQ,IAAIU,EAAIK,CAAI,GAEfZ,EAAgBY,EAAMV,EAAWC,CAAQ,CAClD,CACA,IAAMU,EAAYb,EAAgBF,EAASI,EAAWC,CAAQ,EAC9D,MAAI,CAACU,GAAa,CAACd,IACjBA,EAAY,GACZS,EAAiB,GAEZK,CACT,CACF,CACF,CAiBA,IAAMC,GAAe,IAAI,IAEzB,SAASC,GACPC,EACAC,EAMAC,EACY,CACZ,IAAMC,EAAI,OAAO,QAAW,YAAc,KAAO,OAAO,QACxD,GAAI,CAACA,EAAG,MAAO,IAAG,GAMlB,IAAIC,EAAU,GACVC,EACEC,EAAO,IAAY,CACvB,GAAIF,EAAS,OACb,IAAMG,EAAO7B,GAAY,EACrB,CAAC6B,GAAQA,IAASF,IACtBA,EAAOE,EAIPP,EAAO,MAAM,CAAE,UAAAC,EAAW,YAAa,WAAY,UAAW,WAAY,QAAS,CAAC,CAAE,CAAC,EACvFC,EAAc,GAAGD,CAAS,IAAIM,CAAI,EAAE,EACtC,EAEMC,EAA+F,CAAC,EACtG,QAAWC,IAAQ,CAAC,YAAa,cAAc,EAAY,CACzD,IAAMC,EAAOP,EAAEM,CAAI,EACbE,EAAU,YAA4BC,EAAc,CACxD,IAAMC,EAAKH,EAAsC,MAAM,KAAME,CAAC,EAC9D,OAAAN,EAAK,EACEO,CACT,EACAV,EAAEM,CAAI,EAAIE,EACVH,EAAU,KAAK,CAACC,EAAMC,EAAMC,CAAO,CAAC,CACtC,CACA,OAAO,iBAAiB,WAAYL,CAAI,EAKxC,IAAMQ,EAAUpC,GAAY,EAC5B,OAAIoC,GAAWhB,GAAa,IAAI,GAAGG,CAAS,IAAIa,CAAO,EAAE,EAAGT,EAAOS,EAC9DR,EAAK,EAEH,IAAM,CACX,GAAI,CAAAF,EACJ,CAAAA,EAAU,GACV,OAAO,oBAAoB,WAAYE,CAAI,EAC3C,OAAW,CAACG,EAAMC,EAAMC,CAAO,IAAKH,EAI9BL,EAAEM,CAAI,IAAME,IAASR,EAAEM,CAAI,EAAIC,GAEvC,CACF,CAEA,IAAMK,GAA6B,CACjC,MAAO,IAAG,GACV,KAAM,IAAG,GACT,cAAe,IAAG,GAClB,SAAU,IAAG,GACb,cAAe,IAAM,KACrB,OAAQ,IAAM,QAAQ,QAAQ,IAAI,EAClC,OAAQ,IAAM,QAAQ,QAAQ,IAAI,EAClC,cAAe,IAAM,KACrB,cAAe,IAAM,KACrB,eAAgB,IAAM,KACtB,YAAa,IAAG,GAChB,WAAY,IAAM,KAClB,aAAc,IAAM,QAAQ,QAAQ,CAAC,CAAC,EACtC,SAAU,KAAO,CAAE,UAAW,CAAC,EAAG,WAAY,CAAE,GAChD,QAAS,IAAG,GACZ,QAAS,IAAG,EACd,EAEA,SAASC,IAGP,CACA,GAAI,CACF,OAAOC,GAAqB,OAAO,SAAS,MAAM,CACpD,OAAQ,GACN,MAAO,CAAE,UAAW,CAAC,EAAG,SAAU,CAAC,CAAE,CACvC,CACF,CAEA,SAASC,GAAcC,EAA2B,CAChD,OAAOA,EAAU,QAAQ,eAAgB,EAAE,CAC7C,CASO,SAASC,IAA+B,CAE7C,OACE,OAAO,WAAc,aACpB,UAA4D,uBAAyB,GAE/E,GAEO,CACd,OAAO,WAAc,YAAc,UAAU,WAAa,OAC1D,OAAO,QAAW,YACb,OAAqD,WACtD,OACJ,OAAO,WAAc,YAChB,UAA0D,aAC3D,MACN,EACe,KAAM9C,GAAMA,IAAM,KAAOA,IAAM,KAAK,CACrD,CASO,SAAS+C,GAAanD,EAAuB,CAhtBpD,IAAAS,EAitBE,GAAI,OAAO,QAAW,YAAa,OAEnC,IAAM2C,EAAMpD,GAAA,KAAAA,EAAUF,GACtB,GAAI,CAACsD,EAAK,CACR,QAAQ,KAAK,gDAAgD,EAC7D,MACF,CAEA,IAAMlD,EAAQL,EAAS,IAAIuD,CAAG,EAC9B,GAAI,CAAClD,EAAO,CACV,QAAQ,KAAK,gDAAgD,EAC7D,MACF,CAEA,GAAM,CAAE,OAAAmD,EAAQ,QAAAC,EAAS,OAAArD,CAAO,EAAIC,EACpC,GAAI,CAACoD,EAAS,CAMRpD,EAAM,sBAAsB,QAAQ,KAAKA,EAAM,oBAAoB,EACvE,MACF,CAGA,GAAImD,EAAO,oBAAsB,IAASH,GAAoB,EAAG,OAKjE,IAAMK,GAActD,GAAA,KAAAA,EAAUuD,IAAMC,EAAAC,EAAA,GAAKL,GAAL,CAAa,QAAS,EAAK,EAAC,EAChEC,EAAQC,CAAU,EAIlB,IAAMI,GAAWlD,EAAAZ,EAAS,IAAIuD,CAAG,IAAhB,YAAA3C,EAAmB,QACpCZ,EAAS,IAAIuD,EAAK,CAAE,OAAQK,EAAAC,EAAA,GAAKL,GAAL,CAAa,QAAS,EAAK,GAAG,QAAS,KAAM,QAASM,CAAS,CAAC,CAC9F,CAEA,SAASC,GAAsBP,EAA0F,CAzvBzH,IAAA5C,EA8vBE,IAAMoD,EAAeR,EAAO,qBAAuB,qBAC7CS,EAAUd,IAAcvC,EAAA4C,EAAO,YAAP,KAAA5C,EAAoBb,EAAkB,EAC9DmE,EAAc,CAClB,eAAgB,mBAChB,cAAe,UAAUV,EAAO,MAAM,EACxC,EASMW,EAAc,IAAI,IAClBC,EAAkB,IAAI,IAExBC,EAAwB,CAC1B,MAAO,IAAG,GACV,KAAM,IAAG,GACT,cAAe,IAAG,GAClB,SAAU,IAAG,GACb,cAAe,IAAM,KACrB,aAAc,IAAM,QAAQ,QAAQ,CAAC,CAAC,EACtC,OAAOC,EAAaC,EAAYC,EAAa,CAG3C,GAAI,CAACR,EAAc,OAAO,QAAQ,QAAQ,IAAI,EAC9C,IAAMS,EAASN,EAAY,IAAIG,CAAW,EAC1C,OAAIG,EAAe,QAAQ,QAAQA,CAAM,EAClCC,GAASN,EAAiBE,EAAa,SAA0C,CACtF,GAAI,CACF,IAAMK,EAAS,IAAI,gBAAgB,CAAE,YAAAL,CAAY,CAAC,EAClD,QAAW/D,KAAKgE,GAAA,KAAAA,EAAc,CAAC,EAAGI,EAAO,OAAO,eAAgBpE,CAAC,EACjE,IAAMqE,EAAM,MAAM,MAAM,GAAGX,CAAO,WAAWU,EAAO,SAAS,CAAC,GAAI,CAChE,QAAST,CACX,CAAC,EACD,GAAI,CAACU,EAAI,GAAI,OAAOL,GAAA,MAAAA,EAAa,GAAK,CAAE,UAAWA,EAAW,CAAC,EAAG,gBAAiB,CAAE,EAAI,KAEzF,IAAMM,EAAuB,CAAE,WADjB,MAAMD,EAAI,KAAK,GACkB,UAAW,gBAAiB,CAAE,EAC7E,OAAAT,EAAY,IAAIG,EAAaO,CAAM,EAC5BA,CACT,OAAQC,EAAA,CACN,OAAOP,GAAA,MAAAA,EAAa,GAAK,CAAE,UAAWA,EAAW,CAAC,EAAG,gBAAiB,CAAE,EAAI,IAC9E,CACF,CAAC,CACH,EACA,OAAQ,IAAM,QAAQ,QAAQ,IAAI,EAClC,cAAe,IAAM,KACrB,cAAe,IAAM,KACrB,eAAgB,IAAM,KACtB,YAAa,IAAG,GAChB,WAAY,IAAM,KAClB,SAAU,KAAO,CAAE,UAAW,CAAC,EAAG,WAAY,CAAE,GAChD,QAAS,IAAG,GACZ,QAAS,IAAG,EACd,EAEMQ,EAAwB,CAC5B,MAAQD,GAAMT,EAAM,MAAMS,CAAC,EAG3B,MAAO,CAACE,EAAWC,EAA6BC,EAAYC,IAAed,EAAM,KAAKW,EAAGC,EAAGC,EAAGC,CAAC,GAChG,cAAe,CAACC,EAAGC,EAAGC,IAAMjB,EAAM,cAAce,EAAGC,EAAGC,CAAC,EACvD,SAAWC,GAAMlB,EAAM,SAASkB,CAAC,EACjC,cAAe,CAACH,EAAGD,IAAMd,EAAM,cAAce,EAAGD,CAAC,EACjD,OAAQ,CAACC,EAAG7E,EAAGsC,EAAG2C,IAAOnB,EAAM,OAAOe,EAAG7E,EAAGsC,EAAG2C,CAAE,EACjD,OAASC,GAAMpB,EAAM,OAAOoB,CAAC,EAC7B,cAAgBN,GAAMd,EAAM,cAAcc,CAAC,EAC3C,cAAgBA,GAAMd,EAAM,cAAcc,CAAC,EAC3C,eAAgB,IAAMd,EAAM,eAAe,EAC3C,YAAcqB,GAAQrB,EAAM,YAAYqB,CAAG,EAC3C,WAAY,IAAMrB,EAAM,WAAW,EACnC,aAAc,IAAMA,EAAM,aAAa,EACvC,SAAU,IAAMA,EAAM,SAAS,EAC/B,QAAS,IAAMA,EAAM,QAAQ,EAC7B,QAAS,IAAMA,EAAM,QAAQ,CAC/B,EAEA,SAASsB,EAASjC,EAA4B,CAC5CW,EAAQX,CACV,CAEA,MAAO,CAAE,MAAAqB,EAAO,SAAAY,CAAS,CAC3B,CAYA,SAASjB,GAAYkB,EAAmCrC,EAAasC,EAAmC,CACtG,IAAMC,EAAMF,EAAS,IAAIrC,CAAG,EAC5B,GAAIuC,EAAK,OAAOA,EAChB,IAAMC,GAAW,SAAwB,CACvC,GAAI,CACF,OAAO,MAAMF,EAAI,CACnB,QAAE,CACAD,EAAS,OAAOrC,CAAG,CACrB,CACF,GAAG,EACH,OAAAqC,EAAS,IAAIrC,EAAKwC,CAAO,EAClBA,CACT,CAKO,SAASpC,GAAKH,EAAwC,CA/2B7D,IAAA5C,EAAAoF,GAAAC,EAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAg3BE,GAAI,OAAO,QAAW,YACpB,OAAOxD,GAOT/C,GAAcuD,EAAO,QAAU,QAM/B,IAAMiD,EAAYzG,EAAS,IAAIwD,EAAO,QAAU,OAAO,EACvD,GAAIiD,GAAA,MAAAA,EAAW,QACb,GAAI,CACFA,EAAU,QAAQ,CACpB,OAAQ3B,EAAA,CAER,CASF,IAAM4B,EAAalD,EAAO,oBAAsB,IAASH,GAAoB,EACvEsD,EAAQnD,EAAO,UAAY,IAASkD,EAOpCE,EAAW,OAAOpD,EAAO,QAAW,UAAYA,EAAO,OAAO,WAAW,KAAK,EACpF,GAAIA,EAAO,YAAc,IAAS,CAACoD,GAAYpD,EAAO,YAAc,GAYlE,OARAxD,EAAS,IAAIwD,EAAO,QAAU,QAAS,CACrC,OAAAA,EACA,QAAS,KACT,qBACE,qJACJ,CAAC,EAGGmD,EAAc3D,GACX6D,GAAsBrD,CAAM,EAGrC,GAAImD,EAAO,CACT,GAAI,CAACnD,EAAO,QAAU,CAACA,EAAO,OAAO,WAAW,KAAK,EACnD,OAAIA,EAAO,qBAAuB,sBAChC,QAAQ,KAAK,iGAA4F,EAM3GxD,EAAS,IAAIwD,EAAO,QAAU,QAAS,CACrC,OAAAA,EACA,QAAS,KACT,qBACE,iJACJ,CAAC,EACMR,GAMT,GAAM,CAAE,MAAA+B,EAAO,SAAAY,CAAS,EAAI5B,GAAsBP,CAAM,EAGxD,OAAAxD,EAAS,IAAIwD,EAAO,OAAQ,CAAE,OAAAA,EAAQ,QAASkD,EAAa,KAAOf,CAAS,CAAC,EACtEZ,CACT,CAEA,GAAI,CAACvB,EAAO,QAAU,CAACA,EAAO,OAAO,WAAW,KAAK,EACnD,eAAQ,KAAK,iGAA4F,EAClGR,GAGT,GAAIQ,EAAO,YAAc,GACvB,eAAQ,KAAK,iEAAiE,EACvER,GAGT,IAAM8D,GAAoBlG,EAAA4C,EAAO,YAAP,KAAA5C,EAAoBb,GAExCgH,EAAe,KAAK,IAAI,EACxBC,EAAUC,GAAY,CAAE,aAAczD,EAAO,aAAc,OAAQA,EAAO,MAAO,CAAC,EAClF0D,EAAkBC,GAAsB,OAAW3D,EAAO,MAAM,EAChE4D,EAAaC,GAAiB,CAAE,UAAWP,EAAmB,OAAQtD,EAAO,MAAO,CAAC,EACrFS,EAAUd,GAAc2D,CAAiB,EAEzC5C,EAAc,CAClB,eAAgB,mBAChB,cAAe,UAAUV,EAAO,MAAM,EACxC,EAKM8D,EAAqB,IAAI,IACzBC,EAAuBC,GAAgB,CAC3C,IAAK,GAAGvD,CAAO,SACf,OAAQT,EAAO,OACf,QAASU,EACT,OAAQ,CAACuD,EAAMC,IAAW,CAOxB,GAAI,CAAClE,EAAO,MAAO,CACjB,GAAI8D,EAAmB,IAAII,CAAM,EAAG,OACpCJ,EAAmB,IAAII,CAAM,CAC/B,CACA,QAAQ,KACN,iCAAiCA,CAAM,uCACpCA,IAAW,IACR,kGACAA,IAAW,KAAOA,IAAW,IAC3B,sEACA,0CACRD,CACF,CACF,CACF,CAAC,EAEKE,EAAcC,IAAkB5B,GAAA,UAAU,YAAV,KAAAA,GAAuB,EAAE,EAGzD6B,EAAY,OAAO,SAAS,OAC5BC,EAAgBC,IAAoB9B,EAAA,SAAS,WAAT,KAAAA,EAAqB,GAAI4B,CAAS,EACtEG,GACJ9B,GAAA1C,EAAO,iBAAP,KAAA0C,GAAyB,GAAGyB,CAAW,IAAIG,CAAa,GACpDG,EAAkB,IAAI,IAOtBC,EAAkB,IAAI,IAKtBC,EAAY,IAAI,IAKhBC,EAAkB,IAAI,IACxBC,EAAwD,KACxDC,EAA+D,KAE7DC,EAAkB,IAAI,IACtBC,EAAqB,IAAI,IAC3BC,EAAwD,KAItDC,EAAqBC,GAAiC,CAC1D,QAAWC,KAAKD,EACTR,EAAU,IAAIS,EAAE,EAAE,GAAGT,EAAU,IAAIS,EAAE,GAAIC,GAAkBD,CAAC,CAAC,CAEtE,EAGA,GAAIpF,EAAO,aACT,OAAW,CAACsF,EAAQjE,CAAM,IAAK,OAAO,QAAQrB,EAAO,YAAY,EAC/D2E,EAAU,IAAIW,EAAQjE,CAAM,EAGhC,GAAIrB,EAAO,kBACT,OAAW,CAACsF,EAAQzI,CAAK,IAAK,OAAO,QAAQmD,EAAO,iBAAiB,EACnE4E,EAAgB,IAAIU,EAAQzI,CAAK,EAGjCmD,EAAO,iBAAgB6E,EAAc7E,EAAO,gBAChD,IAAMuF,EAAeC,GAAaxF,EAAO,MAAM,EAC/C,GAAIuF,EAAc,CAChB,OAAW,CAACD,EAAQjE,CAAM,IAAK,OAAO,QAAQkE,EAAa,KAAK,EACzDZ,EAAU,IAAIW,CAAM,GAAGX,EAAU,IAAIW,EAAQjE,CAAM,EAE1D,OAAW,CAACiE,EAAQzI,CAAK,IAAK,OAAO,SAAQ8F,GAAA4C,EAAa,aAAb,KAAA5C,GAA2B,CAAC,CAAC,EACnEiC,EAAgB,IAAIU,CAAM,GAAGV,EAAgB,IAAIU,EAAQzI,CAAK,EAEjE,CAACgI,GAAeU,EAAa,UAASV,EAAcU,EAAa,QACvE,CAIA,IAAME,GAA0C,CAAE,IAAK,IAAM,OAAQ,GAAK,KAAM,GAAK,EACrF,GAAIzF,EAAO,eACT8E,EAAezE,EAAA,GAAKL,EAAO,oBACtB,CAIL,IAAM0F,EAAK,SAAS,gBAAgB,QAChCA,EAAG,gBACLZ,EAAe,CACb,QAASY,EAAG,gBACZ,YAAY7C,GAAA4C,IAAgB7C,GAAA8C,EAAG,qBAAH,KAAA9C,GAAyB,KAAK,IAA9C,KAAAC,GAAmD,GACjE,EACS0C,IACTT,EAAe,CACb,QAASS,EAAa,QACtB,YAAYzC,GAAA2C,GAAgBF,EAAa,IAAI,IAAjC,KAAAzC,GAAsC,GACpD,EAEJ,CAIA,GAAI9C,EAAO,mBACT,OAAW,CAACc,EAAa6E,CAAS,IAAK,OAAO,QAAQ3F,EAAO,kBAAkB,EAC7E0D,EAAgB,IAAI5C,EAAa0D,EAAgB,CAC/C,UAAAmB,EACA,WAAY,KAAK,IAAI,EACrB,QAASnB,EACT,WAAY,CACd,CAAC,EAML,IAAIoB,EAA8B,QAAQ,QAAQ,EAE5CC,EAAYrC,EAAQ,aAAa,EACvC,GAAIqC,EAAW,CACb,IAAMC,EAAiBC,IAA0BhD,GAAA,SAAS,WAAT,KAAAA,GAAqB,EAAE,EAClE,CAAE,UAAAiD,EAAW,SAAAC,CAAS,EAAIxG,GAAkB,EAC5CyG,EAAc7F,QAAA,CAClB,UAAAwF,EACA,YAAA1B,EACA,cAAAG,EACA,eAAAwB,EACA,UAAAE,EAIA,SAAAC,EACA,UAAWE,GAAgB,IAAI,IAAM,EACrC,UAAW,CAAC,MAAO,MAAO,MAAO,MAAO,MAAO,MAAO,KAAK,EAAE,IAAI,KAAK,EAAE,OAAO,CAAC,EAChF,UAAW3C,EAAQ,YAAY,EAI/B,WACG,OAAO,WAAc,aAAe,UAAU,YAAc,IAC7D4C,IAAapD,GAAA,UAAU,YAAV,KAAAA,GAAuB,EAAE,GACpChD,EAAO,OAAS,CAAE,OAAQA,EAAO,MAAO,EAAI,CAAC,GAC7CA,EAAO,QAAU,CAAE,QAASA,EAAO,OAAQ,EAAI,CAAC,GAChDA,EAAO,QAAU,CAAE,QAASA,EAAO,OAAQ,EAAI,CAAC,GAKhDA,EAAO,IAAM,CAAE,IAAKA,EAAO,IAAI,KAAM,WAAYA,EAAO,IAAI,OAAQ,EAAI,CAAC,GAUzEqG,EAAgB,SAAgC,CACpD,QAASC,EAAU,GAAKA,IAAW,CACjC,GAAI,CACF,IAAMlF,EAAM,MAAM,MAAM,GAAGX,CAAO,YAAa,CAC7C,OAAQ,OACR,UAAW,GACX,KAAM,KAAK,UAAUyF,CAAW,EAChC,QAASxF,CACX,CAAC,EACD,GAAIU,EAAI,SAAW,IAAK,CACtB,QAAQ,KACN,gJACF,EACA,MACF,CACA,GAAIA,EAAI,IAAMmF,GAAiBnF,CAAG,IAAM,UAAW,MACrD,OAAQE,EAAA,CAER,CACA,GAAIgF,GAAWtJ,GAAwB,CAGrC,QAAQ,KACN,2GACF,EACA,MACF,CACA,MAAM,IAAI,QAASsC,GAAM,WAAWA,EAAGkH,GAAeF,EAAU,CAAC,CAAC,CAAC,CACrE,CACF,EACA,GAAI,CACFV,EAAeS,EAAc,CAC/B,OAAQ/E,EAAA,CAER,CACF,CAEItB,EAAO,QACT,QAAQ,IAAI,yBAA0B,CAAE,QAASA,EAAO,OAAQ,CAAC,EAE/D,OAMA,WAAa,CACb,OAAQ,KACR,MAAO4D,CACT,GAgBF,IAAM6C,EAAkBpJ,GAAkB,EAKtCqJ,EAAqC,KACrCC,EAAoB,GAElBlI,EAAyB,CAC7B,KAAKS,EAAc0H,EAA0C,CAAC,EAAGC,EAAS,EAAKC,EAAY,EAAG,CAjtClG,IAAA1J,GAAAoF,GAAAC,GAAAC,EAAAC,EAAAC,GAAAC,EAktCM,IAAMkE,EAAMvD,EAAQ,aAAa,EACjC,GAAI,CAACuD,EAAK,OACV,IAAMC,EAAoBlK,GAAc8J,CAAc,EACjDA,EACD,CAAE,SAAUA,CAAe,EAqBzBjJ,EAAY,CAChBuB,GACA9B,GAAA4J,EAAK,aAAL,KAAA5J,GAAmB,IACnBoF,GAAAwE,EAAK,YAAL,KAAAxE,GAAkBsE,GAClBrE,GAAAuE,EAAK,SAAL,KAAAvE,GAAeoE,CACjB,EAAE,KAAK,IAAI,EACLjJ,EAAWoJ,EAAK,QAAU,OAAY,GAAGA,EAAK,KAAK,MAAKtE,EAAAsE,EAAK,WAAL,KAAAtE,EAAiB,EAAE,GAAK,KACtF,GAAI+D,EAAgB,YAAY9I,EAAWC,CAAQ,EAAG,CAChDoC,EAAO,OACT,QAAQ,IAAI,oBAAoBd,CAAI,2DAAsD,EAE5F,MACF,CACA,IAAM+H,EAAShK,GAAgB,EAGzBiK,EAAO,CACX,UAAWH,EACX,KAAA7H,EACA,UAAUyD,EAAAqE,EAAK,WAAL,KAAArE,EAAiB,CAAC,EAC5B,QAAQC,GAAAoE,EAAK,SAAL,KAAApE,GAAeiE,EACvB,WAAWhE,EAAAmE,EAAK,YAAL,KAAAnE,EAAkBiE,EAC7B,OAAAG,EACA,MAAOD,EAAK,MACZ,SAAUA,EAAK,SACf,WAAYA,EAAK,UACnB,EACIhH,EAAO,OACT,QAAQ,IAAI,kBAAmBkH,CAAI,EAIrC,IAAMC,GAAU,CAAE,GAAIF,EAAQ,KAAM,KAAK,UAAUC,CAAI,CAAE,EACzDtB,EAAa,KAAK,IAAM7B,EAAU,KAAKoD,EAAO,CAAC,CACjD,EAEA,cAAcrG,EAAasG,EAAUJ,EAAM,CA/wC/C,IAAA5J,EAAAoF,EAAAC,GAgxCM,IAAMsE,EAAMvD,EAAQ,aAAa,EACjC,GAAI,CAACuD,EAAK,OAIV,IAAMM,EAAa3D,EAAgB,IAAI5C,EAAa0D,CAAc,EAC5D8C,EAAaD,EAAa,MAAOjK,EAAAuH,EAAU,IAAI7D,CAAW,IAAzB,KAAA1D,EAA8B,KACrE,GAAI,CAACiK,GAAcC,IAAe,KAAM,CAClCtH,EAAO,OACT,QAAQ,KACN,6BAA6Bc,CAAW,sIAC1C,EAEF,MACF,CACA,IAAMyG,EAAsBF,EAAaA,EAAW,UAAYG,GAAYF,CAAW,EACjFG,EAA2B,CAC/B,GAAIxK,GAAgB,EACpB,UAAW8J,EACX,UAAW/G,EAAO,OAClB,YAAAc,EACA,UAAWyG,EACX,UAAW,gBACX,SAAAH,EAEA,QAAS/G,EAAA,CACP,QAAQmC,EAAAwE,GAAA,YAAAA,EAAM,SAAN,KAAAxE,EAAgB,EACxB,UAAWwE,GAAA,YAAAA,EAAM,MACjB,SAAUA,GAAA,YAAAA,EAAM,WACZvE,GAAAuE,GAAA,YAAAA,EAAM,WAAN,KAAAvE,GAAkB,CAAC,GAEzB,UAAW,KAAK,IAAI,EACpB,cAAe,KAAK,IAAI,EAAIc,EAC5B,KAAMpG,GAAY,CACpB,EACI6C,EAAO,OACT,QAAQ,IAAI,2BAA4ByH,CAAS,EAEnD7B,EAAa,KAAK,IAAMhC,EAAW,KAAK6D,CAAS,CAAC,CACpD,EAEA,SAASC,EAAQ,CACf,IAAMX,EAAMvD,EAAQ,aAAa,EAC5BuD,GACLnB,EAAa,KAAK,IAAM,CACtB,MAAM,GAAGnF,CAAO,YAAa,CAC3B,OAAQ,OACR,UAAW,GACX,KAAM,KAAK,UAAU,CAAE,UAAWsG,EAAK,OAAAW,EAAQ,UAAWlE,EAAQ,YAAY,CAAE,CAAC,EACjF,QAAS9C,CACX,CAAC,EAAE,MAAM,IAAG,EAAY,CAC1B,CAAC,CACH,EAEA,MAAMiH,EAAO,CACX,IAAM9B,EAAYrC,EAAQ,aAAa,EACvC,GAAI,CAACqC,EAAW,OAEhB,IAAM4B,EAA2BrH,EAAAC,EAAA,CAI/B,KAAMlD,GAAY,GACfwK,GAL4B,CAM/B,GAAI1K,GAAgB,EACpB,UAAA4I,EACA,UAAW,KAAK,IAAI,EACpB,cAAe,KAAK,IAAI,EAAItC,CAC9B,GAEIvD,EAAO,OACT,QAAQ,IAAI,mBAAoByH,CAAS,EAG3C7B,EAAa,KAAK,IAAMhC,EAAW,KAAK6D,CAAS,CAAC,CACpD,EAEA,cAAc3G,EAAa8G,EAAS,CAClC,OAAOlE,EAAgB,IAAI5C,EAAa8G,CAAO,CACjD,EAEA,MAAM,OAAO9G,EAAaC,EAAY8G,EAAYC,EAAqB,CACrE,IAAMf,EAAMvD,EAAQ,aAAa,EACjC,GAAI,CAACuD,EAAK,OAAO,KAEjB,IAAM9F,EAASyC,EAAgB,IAAI5C,EAAa0D,CAAc,EAI9D,GAAIvD,IAAWF,GAAA,MAAAA,EAAY,QAAUE,EAAO,UAAY,QAAY,CAGlE,IAAM8G,EACJ9G,EAAO,OAASA,EAAO,MAAQ,EAC3B,KAAK,IAAI,EAAGA,EAAO,WAAaA,EAAO,MAAQ,KAAK,IAAI,CAAC,EACzD,EACN,MAAO,CAAE,UAAWA,EAAO,UAAW,gBAAiB8G,EAAgB,QAAS9G,EAAO,OAAQ,CACjG,CAIA,OAAOC,GAASuD,EAAiB3D,EAAa,SAAY,CACxD,MAAM8E,EACN,GAAI,CACF,IAAMsB,EAAgC,CAAE,UAAWH,EAAK,YAAAjG,EAAa,WAAAC,CAAW,EAC5E+G,IAAuB,OAAWZ,EAAK,mBAAqBY,EACvDD,IAAc,SAAWX,EAAK,UAAYW,GACnD,IAAMzG,EAAM,MAAM,MAAM,GAAGX,CAAO,UAAW,CAC3C,OAAQ,OACR,KAAM,KAAK,UAAUyG,CAAI,EACzB,QAASxG,CACX,CAAC,EACD,GAAI,CAACU,EAAI,GAAI,OAAO,KACpB,IAAMC,EAAU,MAAMD,EAAI,KAAK,EAC/B,OAAAsC,EAAgB,IAAI5C,EAAa0D,EAAgBnE,EAAA,CAC/C,UAAWgB,EAAO,UAClB,WAAY,KAAK,IAAI,EACrB,QAASmD,EACT,WAAY,EACZ,QAASnD,EAAO,SAGZA,EAAO,iBAAmBA,EAAO,gBAAkB,EACnD,CAAE,MAAOA,EAAO,eAAgB,EAChC,CAAC,EACN,EACMA,CACT,OAAQC,EAAA,CACN,OAAO,IACT,CACF,CAAC,CACH,EAEA,MAAM,OAAO0G,EAAO,CAr5CxB,IAAA5K,EAAAoF,EAs5CM,IAAMuE,EAAMvD,EAAQ,aAAa,EACjC,GAAI,CAACuD,EAAK,OAAO,KACjB,IAAMkB,GAAW7K,EAAA4K,EAAM,QAAN,KAAA5K,EAAe,CAAC,EAE3B8J,EAAgC,CAAE,UAAWH,CAAI,EACnDiB,EAAM,UAAYA,EAAM,SAAS,OAAS,IAC5Cd,EAAK,SAAWc,EAAM,SAAS,IAAKE,IAAQ,CAAE,GAAAA,CAAG,EAAE,GAErDhB,EAAK,YAAa1E,EAAAwF,EAAM,aAAN,KAAAxF,EAAoB,CAAC,EACnCyF,EAAS,OAAS,IAAGf,EAAK,MAAQe,EAAS,IAAIE,EAAU,GACzDH,EAAM,YAAc,aAAYd,EAAK,UAAY,YACjDc,EAAM,IAAGd,EAAK,EAAIc,EAAM,GAGxB,OAAOA,EAAM,IAAO,WAAUd,EAAK,GAAKc,EAAM,IAG9ChI,EAAO,UAASkH,EAAK,QAAUlH,EAAO,SAU1C,IAAMoI,EAAY,KAAK,UAAUlB,CAAI,EACrC,OAAOhG,GAASwD,EAAiB0D,EAAW,SAAY,CAl7C9D,IAAAhL,EAAAoF,EAAAC,EAAAC,GAAAC,GAAAC,GAm7CQ,MAAMgD,EACN,GAAI,CACF,IAAMxE,GAAM,MAAM,MAAM,GAAGX,CAAO,UAAW,CAC3C,OAAQ,OACR,KAAM2H,EACN,QAAS1H,CACX,CAAC,EACD,GAAI,CAACU,GAAI,GACP,OAAA8D,EAAkB+C,CAAQ,EACnB,KAET,IAAMI,EAAQ,MAAMjH,GAAI,KAAK,EAYvBkH,EAAoC,CAAC,EAC3C,QAAWlD,KAAK6C,EAAU,CAIxB,IAAMM,GAASnL,EAAAiL,EAAK,QAAL,YAAAjL,EAAagI,EAAE,IAC9B,GAAImD,IAAW,OAAW,CACxBD,EAAMlD,EAAE,EAAE,EAAImD,EACd5D,EAAU,IAAIS,EAAE,GAAImD,CAAM,EAC1B,QACF,CAOA,IAAMC,GAAQ7D,EAAU,IAAIS,EAAE,EAAE,EAChC,GAAIoD,KAAU,OACZF,EAAMlD,EAAE,EAAE,EAAIoD,OACT,CACL,IAAMC,GAAWpD,GAAkBD,CAAC,EACpCkD,EAAMlD,EAAE,EAAE,EAAIqD,GACd9D,EAAU,IAAIS,EAAE,GAAIqD,EAAQ,CAC9B,CACF,CAKA,GAAIJ,EAAK,MACP,OAAW,CAAC/C,EAAQjE,CAAM,IAAK,OAAO,QAAQgH,EAAK,KAAK,EAChD/C,KAAUgD,IACdA,EAAMhD,CAAM,EAAIjE,EAChBsD,EAAU,IAAIW,EAAQjE,CAAM,GAQlC,IAAMqH,GAAQ5D,GAAgB,MAAQA,EAAa,UAAY,UAC3DuD,EAAK,SAAW,EAAEA,EAAK,UAAY,WAAaK,IAClD5D,EAAe,CAAE,QAASuD,EAAK,QAAS,YAAY7F,EAAA6F,EAAK,aAAL,KAAA7F,EAAmB,CAAE,EAC/DsC,IACVA,EAAe,CAAE,QAAS,UAAW,WAAY,CAAE,GAKrD,OAAW,CAAChE,EAAa6E,CAAS,IAAK,OAAO,SAAQlD,EAAA4F,EAAK,cAAL,KAAA5F,EAAoB,CAAC,CAAC,EAC1EiB,EAAgB,IAAI5C,EAAa0D,EAAgB,CAC/C,UAAAmB,EACA,WAAY,KAAK,IAAI,EACrB,QAASnB,EACT,WAAY,CACd,CAAC,EAMH,GAAI6D,EAAK,WACP,OAAW,CAAC/C,EAAQzI,CAAK,IAAK,OAAO,QAAQwL,EAAK,UAAU,EAC1DzD,EAAgB,IAAIU,EAAQzI,CAAK,EAGrC,OAAIwL,EAAK,UAASxD,EAAcwD,EAAK,SAGrCM,GAAc3I,EAAO,OAAQK,IAAA,CAC3B,EAAG,EACH,QAASyE,EAAa,QACtB,QAAM,mBAAeA,EAAa,UAAU,EAC5C,MAAO,OAAO,YAAYH,CAAS,EACnC,aAAajC,GAAA2F,EAAK,cAAL,KAAA3F,GAAoB,KACjC,QAAS,KAAK,IAAI,GACdkC,EAAgB,KAAO,EAAI,CAAE,WAAY,OAAO,YAAYA,CAAe,CAAE,EAAI,CAAC,GAClFC,EAAc,CAAE,QAASA,CAAY,EAAI,CAAC,EAC/C,EAEMxE,QAAA,CACL,aAAasC,GAAA0F,EAAK,cAAL,KAAA1F,GAAoB,KACjC,aAAaC,GAAAyF,EAAK,cAAL,KAAAzF,GAAoB,CAAC,EAClC,MAAA0F,EACA,QAASxD,EAAa,QACtB,WAAYA,EAAa,YACrBuD,EAAK,WAAa,CAAE,WAAYA,EAAK,UAAW,EAAI,CAAC,GACrDA,EAAK,MAAQ,CAAE,MAAOA,EAAK,KAAM,EAAI,CAAC,GACtCA,EAAK,WAAa,CAAE,WAAYA,EAAK,UAAW,EAAI,CAAC,GACrDA,EAAK,QAAU,CAAE,QAASA,EAAK,OAAQ,EAAI,CAAC,EAEpD,OAAQ/G,GAAA,CACN,OAAA4D,EAAkB+C,CAAQ,EACnB,IACT,CACF,CAAC,CACH,EAEA,cAAc3C,EAAQ,CA9iD1B,IAAAlI,EA+iDM,OAAOA,EAAAuH,EAAU,IAAIW,CAAM,IAApB,KAAAlI,EAAyB,IAClC,EAEA,cAAckI,EAAQ,CAljD1B,IAAAlI,EAmjDM,OAAOA,EAAAwH,EAAgB,IAAIU,CAAM,IAA1B,KAAAlI,EAA+B,IACxC,EAEA,YAAYwL,EAAS,CAInB,QAAWV,KAAMU,EACX,OAAOV,GAAO,UAAYA,EAAG,OAAS,GAAK,CAACnD,EAAgB,IAAImD,CAAE,IACpEnD,EAAgB,IAAImD,CAAE,EACtBlD,EAAmB,IAAIkD,CAAE,GAGzBlD,EAAmB,OAAS,GAAKC,GAAmB,OACxDA,EAAkB,WAAW,IAAM,CACjCA,EAAkB,KAClB,IAAM4D,EAAQ,CAAC,GAAG7D,CAAkB,EAAE,MAAM,EAAG,EAAE,EACjDA,EAAmB,MAAM,EACrB6D,EAAM,SAAW,GAChB,MAAM,GAAGpI,CAAO,kBAAmB,CACtC,OAAQ,OACR,QAASC,EACT,KAAM,KAAK,UAAU,CAAE,QAASmI,CAAM,CAAC,CACzC,CAAC,EAAE,MAAM,IAAG,EAAY,CAC1B,EAAG,GAAI,EACT,EAEA,gBAAiB,CACf,OAAOhE,CACT,EAEA,YAAa,CACX,OAAKC,EACE,CACL,QAASA,EAAa,QACtB,WAAYA,EAAa,WACzB,QAAM,mBAAeA,EAAa,UAAU,CAC9C,EAL0B,IAM5B,EAEA,MAAM,cAAe,CA3lDzB,IAAA1H,EA4lDM,GAAI,CACF,IAAMgE,EAAM,MAAM,MAAM,GAAGX,CAAO,WAAY,CAAE,QAASC,CAAY,CAAC,EACtE,OAAKU,EAAI,IAEFhE,GADO,MAAMgE,EAAI,KAAK,GACjB,aAAL,KAAAhE,EAAmB,CAAC,EAFP,CAAC,CAGvB,OAAQkE,EAAA,CACN,MAAO,CAAC,CACV,CACF,EAEA,UAAW,CACT,MAAO,CAAE,UAAW,CAAC,EAAG,WAAY,CAAE,CACxC,EAEA,SAAU,CA1mDd,IAAAlE,EA6mDMsJ,GAAA,MAAAA,IACAC,EAAoB,GACpB/C,EAAW,QAAQ,EACnBG,EAAU,QAAQ,IAGd3G,EAAAZ,EAAS,IAAIwD,EAAO,MAAM,IAA1B,YAAA5C,EAA6B,WAAYqB,EAAO,SAClDjC,EAAS,OAAOwD,EAAO,MAAM,EAE3BA,EAAO,OACT,QAAQ,IAAI,qBAAqB,CAErC,EAEA,SAAU,CA3nDd,IAAA5C,EA4nDMsJ,GAAA,MAAAA,IACAC,EAAoB,GACpB/C,EAAW,QAAQ,EACnBG,EAAU,QAAQ,EAClBP,EAAQ,QAAQ,EAKhBE,EAAgB,MAAM,IAClBtG,EAAAZ,EAAS,IAAIwD,EAAO,MAAM,IAA1B,YAAA5C,EAA6B,WAAYqB,EAAO,SAClDjC,EAAS,OAAOwD,EAAO,MAAM,EAK/B,GAAI,CACF,aAAa,WAAW8I,GAA8B9I,EAAO,MAAM,EACnE,aAAa,WAAW+I,GAAgB/I,EAAO,MAAM,CAAC,EACtD,aAAa,WAAWgJ,GAAoBhJ,EAAO,MAAM,CAAC,CAC5D,OAAQsB,EAAA,CAER,CACItB,EAAO,OACT,QAAQ,IAAI,sBAAsB,CAEtC,CACF,EAcA,GAZAxD,EAAS,IAAIwD,EAAO,OAAQ,CAAE,OAAAA,EAAQ,QAAS,KAAM,QAASvB,EAAO,OAAQ,CAAC,EAE9EiI,EAAgBlI,GAAsBC,EAAQuB,EAAO,OAASD,GAAQ,CAK/D6F,EAAa,KAAK,IAAM,CACtBe,GAAmBpI,GAAa,IAAIwB,CAAG,CAC9C,CAAC,CACH,CAAC,EAEGC,EAAO,MAAO,CAChB,IAAMiJ,EAAM,OACRA,EAAI,aACNA,EAAI,WAAW,OAASxK,EAE5B,CAEA,OAAOA,CACT,CIjoDA,IAAMyK,GAAkB,mBAElBC,GAAgD,CACpD,QAAS,CAAC,WAAY,KAAK,EAC3B,SAAU,CAAC,SAAS,EACpB,IAAK,CAAC,SAAS,EACf,aAAc,CAAC,KAAK,EACpB,IAAK,CAAC,eAAgB,OAAQ,OAAO,EACrC,KAAM,CAAC,KAAK,EACZ,WAAY,CAAC,SAAS,EACtB,MAAO,CAAC,KAAK,CACf,EAEA,SAASC,GAAcC,EAAgC,CAzDvD,IAAAC,EA0DE,OAAOA,EAAAH,GAAoBE,CAAY,IAAhC,KAAAC,EAAqC,CAAC,CAC/C,CAEA,SAASC,GAAeC,EAAaC,EAAgB,CACnD,GAAI,CACF,IAAMC,EAAM,aAAa,QAAQF,CAAG,EACpC,OAAKE,EACE,KAAK,MAAMA,CAAG,EADJD,CAEnB,OAAQE,EAAA,CACN,OAAOF,CACT,CACF,CAEA,SAASG,GAAaJ,EAAaK,EAAsB,CACvD,GAAI,CACF,aAAa,QAAQL,EAAK,KAAK,UAAUK,CAAK,CAAC,CACjD,OAAQF,EAAA,CAER,CACF,CAEA,IAAMG,GAAuB,IAAI,IAAI,CACnC,UAAW,OAAQ,eAAgB,MAAO,WAC1C,MAAO,aAAc,QAAS,aAAc,SAC9C,CAAC,EAED,SAASC,GAAoBC,EAAsB,CACjD,OAAOF,GAAqB,IAAIE,CAAI,EAAIA,EAAO,SACjD,CAMO,SAASC,GAAgBC,EAAsB,CACpD,GAAI,CACF,IAAMC,EAAI,IAAI,IAAID,CAAI,EACtB,MAAO,GAAGC,EAAE,MAAM,GAAGA,EAAE,QAAQ,EACjC,OAAQR,EAAA,CACN,MAAO,GACT,CACF,CAEA,SAASS,GAAcC,EAAqBhB,EAAsBiB,EAA2B,CAC3F,IAAMC,EAAQ,GAAGF,CAAW,IAAIhB,CAAY,IAAIiB,EAAQ,KAAK,GAAG,CAAC,GAC7DE,EAAI,KACR,QAASC,EAAI,EAAGA,EAAIF,EAAM,OAAQE,IAChCD,GAAMA,GAAK,GAAKA,EAAID,EAAM,WAAWE,CAAC,EAAK,WAE7C,OAAQD,IAAM,GAAG,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAC/C,CAKO,SAASE,GAAkBC,EAAmC,CACnE,IAAMC,EAAY,IAAI,IAChBC,EAAkB,IAAI,IAItBC,EAAW,mBAAmBC,GAAcJ,GAAA,YAAAA,EAAQ,MAAM,CAAC,GAE3DK,EAAU,IAAY,CACtB,OAAO,QAAW,aACtBpB,GAAakB,EAAU,CAAC,GAAGF,EAAU,OAAO,CAAC,CAAC,CAChD,EAKA,GAAI,OAAO,QAAW,YAAa,CACjC,IAAMK,EAAc1B,GAAwBuB,EAAU,CAAC,CAAC,EACxD,QAAWI,KAAQD,EACjBL,EAAU,IAAIM,EAAK,YAAaA,CAAI,EAGtC,GAAI,CAAE,aAAa,WAAWhC,EAAe,CAAG,OAAQS,EAAA,CAAe,CACzE,CAEA,MAAO,CACL,YAAYuB,EAAsB,CAChCN,EAAU,IAAIM,EAAK,YAAaA,CAAI,EACpCF,EAAQ,CACV,EAEA,kBAAkBG,EAA4B,CAC5C,IAAM3B,EAAM,GAAG2B,EAAK,eAAe,KAAKA,EAAK,aAAa,GAC1DN,EAAgB,IAAIrB,EAAK2B,CAAI,CAC/B,EAEA,UAAiB,CArJrB,IAAA7B,EAAA8B,EAsJM,GAAI,EAACT,GAAA,MAAAA,EAAQ,UAAW,OAAO,QAAW,YAAa,OACvD,IAAMU,EAAQ,CAAC,GAAGT,EAAU,OAAO,CAAC,EACpC,GAAIS,EAAM,SAAW,EACrB,GAAI,CAKF,IAAMC,EAAc,IAAI,IACxB,QAAWC,KAAKF,EAAO,CACrB,IAAMG,GAAOlC,EAAAgC,EAAY,IAAIC,EAAE,YAAY,IAA9B,KAAAjC,EAAmC,CAAC,EACjDkC,EAAK,KAAKD,CAAC,EACXD,EAAY,IAAIC,EAAE,aAAcC,CAAI,CACtC,CACA,IAAMC,EAMD,CAAC,EACAC,EAAO,IAAI,IACjB,QAAWC,KAAUN,EACnB,QAAWO,KAAiBxC,GAAcuC,EAAO,YAAY,EAAG,CAC9D,IAAME,GAAUT,EAAAE,EAAY,IAAIM,CAAa,IAA7B,KAAAR,EAAkC,CAAC,EACnD,QAAWU,KAAUD,EAAS,CAC5B,GAAIC,EAAO,cAAgBH,EAAO,YAAa,SAC/C,IAAMnC,EAAM,YAAYmC,EAAO,WAAW,KAAKG,EAAO,WAAW,GAC7DJ,EAAK,IAAIlC,CAAG,IAChBkC,EAAK,IAAIlC,CAAG,EACZiC,EAAM,KAAK,CACT,gBAAiBE,EAAO,YACxB,cAAeG,EAAO,YACtB,KAAM,WACN,OAAQ,GACR,WAAY,EACd,CAAC,EACH,CACF,CAMF,IAAMC,EAAe,IAAI,IAAIV,EAAM,IAAKE,GAAMA,EAAE,WAAW,CAAC,EAC5D,QAAWJ,KAAQN,EAAgB,OAAO,EAAG,CAC3C,GAAI,CAACkB,EAAa,IAAIZ,EAAK,eAAe,GAAK,CAACY,EAAa,IAAIZ,EAAK,aAAa,EAAG,SACtF,IAAM3B,EAAM,cAAc2B,EAAK,eAAe,KAAKA,EAAK,aAAa,GACjEO,EAAK,IAAIlC,CAAG,IAChBkC,EAAK,IAAIlC,CAAG,EACZiC,EAAM,KAAK,CACT,gBAAiBN,EAAK,gBACtB,cAAeA,EAAK,cACpB,KAAM,aACN,OAAQA,EAAK,OACb,WAAY,CACd,CAAC,EACH,CAEA,IAAMa,EAAUC,EAAAC,IAAA,CACd,QAASjC,GAAgB,OAAO,SAAS,IAAI,GAIzCU,EAAO,UAAY,CAAE,UAAWA,EAAO,SAAU,EAAI,CAAC,GACtDA,EAAO,UAAY,CAAE,UAAWA,EAAO,SAAU,EAAI,CAAC,GAN5C,CAOd,MAAOU,EAAM,IAAKE,GAAM,CACtB,IAAMlC,EAAeU,GAAoBwB,EAAE,YAAY,EACvD,MAAO,CACL,YAAaA,EAAE,YACf,aAAAlC,EACA,QAASkC,EAAE,QACX,YAAanB,GAAcmB,EAAE,YAAalC,EAAckC,EAAE,OAAO,EACjE,gBAAiBA,EAAE,gBACnB,YAAaA,EAAE,KACjB,CACF,CAAC,EACD,MAAAE,CACF,GACA,MAAMd,EAAO,QAAS,CACpB,OAAQ,OACR,UAAW,GACX,QAASuB,EAAA,CACP,eAAgB,oBACZvB,EAAO,OAAS,CAAE,cAAe,UAAUA,EAAO,MAAM,EAAG,EAAI,CAAC,GAEtE,KAAM,KAAK,UAAUqB,CAAO,CAC9B,CAAC,EAAE,MAAM,IAAG,EAAY,CAC1B,OAAQrC,EAAA,CAER,CACF,EAEA,UAA0B,CACxB,MAAO,CACL,UAAW,CAAC,GAAGiB,EAAU,OAAO,CAAC,EACjC,WAAY,KAAK,IAAI,CACvB,CACF,EAEA,SAAgB,CACd,GAAI,OAAO,QAAW,YACtB,GAAI,CACF,aAAa,WAAWE,CAAQ,EAChC,aAAa,WAAW5B,EAAe,CACzC,OAAQS,EAAA,CAER,CACF,CACF,CACF,CCtPA,IAAMwC,GAAoB,CAAC,mBAAoB,cAAe,YAAa,UAAW,YAAa,SAAS,EAG5G,SAASC,GAAeC,EAAqB,CAjB7C,IAAAC,EAkBE,QAAQA,EAAAD,EAAG,cAAH,KAAAC,EAAkB,IAAI,QAAQ,OAAQ,GAAG,EAAE,KAAK,CAC1D,CAEA,SAASC,GAAcF,EAA4C,CACjE,MAAO,CACL,IAAKA,EAAG,QAAQ,YAAY,EAC5B,KAAMD,GAAeC,CAAE,EAAE,MAAM,EAAG,EAAoB,CACxD,CACF,CAEA,SAASG,GAAUC,EAAmB,CACpC,OAAOA,EAAE,QAAQ,WAAY,MAAM,CACrC,CAEA,SAASC,GAAOC,EAAkBC,EAA2B,CAC3D,GAAI,CACF,OAAOD,EAAK,iBAAiBC,CAAQ,EAAE,SAAW,CACpD,OAAQC,EAAA,CACN,MAAO,EACT,CACF,CAGA,SAASC,GAAeT,EAAaM,EAAiC,CAzCtE,IAAAL,EA0CE,IAAMS,EAAMV,EAAG,QAAQ,YAAY,EACnC,GAAI,CAACU,EAAK,OAAO,KACjB,IAAMC,IAAWV,EAAAD,EAAG,aAAa,OAAO,IAAvB,KAAAC,EAA4B,IAAI,MAAM,KAAK,EAAE,OAAQW,GAAM,mBAAmB,KAAKA,CAAC,CAAC,EAChGC,EAAa,CAACH,EAAK,GAAGC,EAAQ,IAAKC,GAAM,GAAGF,CAAG,IAAIE,CAAC,EAAE,CAAC,EAC7D,QAAWA,KAAKC,EAAY,GAAIR,GAAOC,EAAMM,CAAC,EAAG,OAAOA,EACxD,IAAME,EAASd,EAAG,cAClB,GAAIc,GAAWA,IAA0BR,EAAM,CAC7C,IAAMS,EAAYN,GAAeK,EAAQR,CAAI,EAC7C,GAAIS,EAAW,CACb,QAAWH,KAAKC,EAAY,CAC1B,IAAMG,EAAW,GAAGD,CAAS,MAAMH,CAAC,GACpC,GAAIP,GAAOC,EAAMU,CAAQ,EAAG,OAAOA,CACrC,CAGA,IAAMC,EAAW,MAAM,KAAKH,EAAO,QAAQ,EAAE,OAAQI,GAAMA,EAAE,QAAQ,YAAY,IAAMR,CAAG,EACpFS,EAAMF,EAAS,QAAQjB,CAAE,EAOzBoB,EAAkBH,EAAS,KAAMI,GAAMA,IAAMrB,GAAMD,GAAesB,CAAC,IAAMtB,GAAeC,CAAE,CAAC,EACjG,GAAImB,GAAO,GAAKC,EAAiB,CAC/B,IAAME,EAAM,GAAGP,CAAS,MAAML,CAAG,gBAAgBS,EAAM,CAAC,IACxD,GAAId,GAAOC,EAAMgB,CAAG,EAAG,OAAOA,CAChC,CACF,CACF,CACA,OAAO,IACT,CAIO,SAASC,GAAmBvB,EAAaM,EAA0C,CACxF,IAAMkB,EAActB,GAAcF,CAAE,EAC9ByB,EAAKzB,EAAG,aAAa,IAAI,EAC/B,GAAIyB,GAAMpB,GAAOC,EAAM,IAAIH,GAAUsB,CAAE,CAAC,EAAE,EAAG,MAAO,CAAE,EAAG,EAAG,GAAAA,EAAI,YAAAD,CAAY,EAC5E,QAAWE,KAAQC,GAAmB,CACpC,IAAMC,EAAQ5B,EAAG,aAAa0B,CAAI,EAClC,GAAIE,GAASvB,GAAOC,EAAM,IAAIoB,CAAI,KAAKvB,GAAUyB,CAAK,CAAC,IAAI,EACzD,MAAO,CAAE,EAAG,EAAG,SAAU,CAAE,KAAAF,EAAM,MAAAE,CAAM,EAAG,YAAAJ,CAAY,CAE1D,CACA,IAAMjB,EAAWE,GAAeT,EAAIM,CAAI,EACxC,OAAIC,EAAiB,CAAE,EAAG,EAAG,SAAAA,EAAU,YAAAiB,CAAY,EAC5C,IACT,CC1DO,IAAMK,GAA0C,CACrD,UAAW,OAAQ,eAAgB,MAAO,WAC1C,MAAO,aAAc,QAAS,aAAc,SAC9C,EA4BMC,GAAqC,CACzC,OAAQ,OACR,WAAY,aACZ,YAAa,QACf,EAQO,SAASC,GAAkBC,EAAmC,CACnE,IAAMC,EAAOD,EAAE,SAAWF,GAAWE,EAAE,SAAS,YAAY,CAAC,EAAI,OACjE,GAAIC,EAAM,OAAOA,EACjB,GAAID,EAAE,MAAQ,MAAO,MAAO,aAC5B,GAAIA,EAAE,MAAQ,SAAU,MAAO,SAgB/B,GAAIA,EAAE,MAAQ,SAAU,OAAOA,EAAE,aAAe,EAAI,aAAe,OACnE,IAAME,EAAM,GAAGF,EAAE,OAAO,IAAIA,EAAE,WAAW,GAAG,YAAY,EACxD,MAAI,kEAAkE,KAAKE,CAAG,EACrE,SAAS,KAAKA,CAAG,EAAI,SAAW,aAMrC,iCAAiC,KAAKF,EAAE,OAAO,EAAU,OACtD,IACT,CAMO,SAASG,GAAgBH,EAA4B,CAE1D,OAAIA,EAAE,aAAe,GAAKA,EAAE,WAAa,GAAKA,EAAE,WAAa,IAAY,MAClE,SACT,CAMA,IAAMI,GAA0C,CAC9C,CAAC,UAAW,+GAA+G,EAC3H,CAAC,MAAO,6CAA6C,EACrD,CAAC,aAAc,yCAAyC,EACxD,CAAC,eAAgB,uHAAuH,EACxI,CAAC,QAAS,oIAAoI,EAC9I,CAAC,MAAO,sGAAsG,EAC9G,CAAC,WAAY,mIAAmI,CAClJ,EAKaC,GAAkD,CAC7D,CAAC,UAAW,oJAAoJ,EAChK,CAAC,eAAgB,iHAAiH,EAClI,CAAC,QAAS,iJAAiJ,EAC3J,CAAC,aAAc,sHAAsH,CACvI,EAIMC,GAAoD,CACxD,WAAY,aACZ,OAAQ,aACR,KAAM,OACN,IAAK,MACL,QAAS,SACX,EAEA,SAASC,GAAYC,EAAqB,CAxJ1C,IAAAC,EAyJE,IAAMC,EAAIF,EAAG,cAAc,YAAY,EACvC,QAAQC,EAAAC,GAAA,YAAAA,EAAG,cAAH,KAAAD,EAAkB,IAAI,MAAM,EAAG,GAAG,CAC5C,CAOO,SAASE,GAAiBX,EAAyE,CAlK1G,IAAAS,EAAAG,EAmKE,IAAMC,EAAad,GAAkBC,CAAC,EACtC,GAAIa,EAAY,MAAO,CAAE,MAAMJ,EAAAH,GAAoBO,CAAU,IAA9B,KAAAJ,EAAmC,UAAW,SAAU,QAAS,EAEhG,IAAMP,EAAM,GAAGF,EAAE,OAAO,IAAIA,EAAE,WAAW,GAAG,YAAY,EACxD,OAAW,CAACc,EAAMC,CAAE,IAAKX,GAAU,GAAIW,EAAG,KAAKb,CAAG,EAAG,MAAO,CAAE,KAAAY,EAAM,SAAU,QAAS,EACvF,OAAW,CAACA,EAAMC,CAAE,IAAKV,GAAkB,GAAIU,EAAG,KAAKf,EAAE,QAAQ,EAAG,MAAO,CAAE,KAAAc,EAAM,SAAU,QAAS,EAEtG,IAAME,EAAKb,GAAgBH,CAAC,EAC5B,MAAO,CAAE,MAAMY,EAAAN,GAAoBU,CAAE,IAAtB,KAAAJ,EAA2B,UAAW,SAAU,MAAO,CACxE,CAGO,SAASK,GAAoBT,EAA8B,CA/KlE,IAAAC,EAAAG,EAgLE,IAAMM,IAAQT,EAAAD,EAAG,cAAH,KAAAC,EAAkB,IAAI,QAAQ,OAAQ,GAAG,EAAE,KAAK,EACxDU,EAAOX,EAAG,aAAa,MAAM,EACnC,OAAOY,EAAA,CACL,IAAKZ,EAAG,QAAQ,YAAY,EAC5B,QAAS,GAAGA,EAAG,EAAE,IAAI,QAAOI,EAAAJ,EAAG,YAAH,KAAAI,EAAgB,EAAE,CAAC,GAC/C,YAAaL,GAAYC,CAAE,EAC3B,SAAUU,EAAK,MAAM,EAAG,GAAI,EAC5B,YAAaV,EAAG,iBAAiB,4BAA4B,EAAE,OAC/D,WAAYU,EAAK,QACbC,EAAO,CAAE,SAAUA,CAAK,EAAI,CAAC,EAErC,CAIO,SAASE,GAAgBb,EAA2B,CACzD,OAAOG,GAAiBM,GAAoBT,CAAE,CAAC,EAAE,IACnD,CC7IA,IAAMc,GAAe,IAAI,IAAI,CAAC,UAAW,UAAW,OAAQ,MAAO,OAAO,CAAC,EACrEC,GAAmB,aAMnBC,GACJ,oGASIC,GAAyB,GACzBC,GAAuB,KAEvBC,GAA0B,CAC9B,KAAM,UAAa,CAAE,MAAO,CAAC,EAAG,MAAO,CAAC,EAAG,UAAW,CAAE,GACxD,QAAS,IAAG,GACZ,mBAAoB,IAAM,EAC1B,QAAS,IAAG,EACd,EAEA,SAASC,GAAUC,EAAeC,EAAaC,EAAqB,CAClE,OAAIA,GAAOD,EAAY,EAChB,KAAK,IAAI,EAAG,KAAK,IAAI,GAAID,EAAQC,IAAQC,EAAMD,EAAI,CAAC,CAC7D,CAEA,SAASE,GAAuBC,EAAsC,CApFtE,IAAAC,EAAAC,EAAAC,EAqFE,GAAI,CACF,IAAMC,EAASJ,EACf,QAAWK,KAAO,OAAO,KAAKD,CAAM,EAAG,CACrC,GAAI,CAACC,EAAI,WAAW,cAAc,GAAK,CAACA,EAAI,WAAW,yBAAyB,EAC9E,SAEF,IAAMC,EAAQF,EAAOC,CAAG,EAClBE,GAAOJ,GAAAF,EAAAK,GAAA,YAAAA,EAAO,OAAP,YAAAL,EAAa,cAAb,KAAAE,GAA4BD,EAAAI,GAAA,YAAAA,EAAO,OAAP,YAAAJ,EAAa,KACtD,GAAIK,GAAQA,EAAK,OAAS,EACxB,OAAOA,CAEX,CACF,OAAQC,EAAA,CAER,CAEF,CAEA,SAASC,GAAsBT,EAA0C,CACvE,IAAMU,EAAgC,CAAC,EACvC,QAAWC,KAAQ,MAAM,KAAKX,EAAQ,UAAU,EAC1CW,EAAK,KAAK,WAAW,OAAO,IAC9BD,EAAMC,EAAK,IAAI,EAAIA,EAAK,OAG5B,OAAOD,CACT,CAEA,IAAME,GAAoC,IAAI,IAAIC,EAAc,EAMhE,SAASC,GAAkBd,EAAgC,CACzD,IAAMe,EAAWf,EAAQ,aAAa,oBAAoB,EAC1D,GAAIe,GAAYH,GAAa,IAAIG,CAAQ,EAAG,OAAOA,EACnD,IAAMC,EAAOhB,EAAQ,aAAa,MAAM,EACxC,OAAIgB,GAAQJ,GAAa,IAAII,CAAI,EAAUA,EACpCC,GAAgBjB,CAAO,CAChC,CAIA,SAASkB,GAAUC,EAAuB,CACxC,IAAIC,EAAI,KACR,QAASC,EAAI,EAAGA,EAAIF,EAAM,OAAQE,IAChCD,GAAMA,GAAK,GAAKA,EAAID,EAAM,WAAWE,CAAC,EAAK,WAE7C,OAAQD,IAAM,GAAG,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAC/C,CAKA,SAASE,GAAiBtB,EAA0B,CAClD,IAAMuB,EAAkB,CAAC,EACrBC,EAA0BxB,EAC9B,KAAOwB,GAAS,CACd,IAAMC,EAAyBD,EAAQ,cACvC,GAAI,CAACC,EAAQ,CACXF,EAAM,KAAKC,EAAQ,QAAQ,YAAY,CAAC,EACxC,KACF,CACA,IAAME,EAAQ,MAAM,UAAU,QAAQ,KAAKD,EAAO,SAAUD,CAAO,EACnED,EAAM,KAAK,GAAGC,EAAQ,QAAQ,YAAY,CAAC,IAAIE,CAAK,GAAG,EACvDF,EAAUC,CACZ,CACA,OAAOF,EAAM,QAAQ,EAAE,KAAK,GAAG,CACjC,CAMA,SAASI,GAAe3B,EAA0B,CAhKlD,IAAAC,EAiKE,IAAM2B,GACJ3B,EAAAD,EAAQ,aAAa,kBAAkB,IAAvC,KAAAC,EAA4CD,EAAQ,aAAa,IAAI,EACvE,OAAI4B,GACG,GAAG5B,EAAQ,QAAQ,YAAY,CAAC,IAAIkB,GAAUI,GAAiBtB,CAAO,CAAC,CAAC,EACjF,CAEA,SAAS6B,GAAQ7B,EAA0B,CACzC,IAAI8B,EAAQ,EACRN,EAA0BxB,EAAQ,cACtC,KAAOwB,GACLM,IACAN,EAAUA,EAAQ,cAEpB,OAAOM,CACT,CAEA,SAASC,GACP/B,EACAgC,EACa,CApLf,IAAA/B,EAAAC,EAAAC,EAAA8B,EAqLE,IAAMC,EAAUlC,EAAQ,cAAcV,EAAgB,EACtD,MAAO,CACL,YAAaqC,GAAe3B,CAAO,EACnC,aAAcc,GAAkBd,CAAO,EACvC,WAAWC,EAAAD,EAAQ,aAAa,YAAY,IAAjC,KAAAC,EAAsC,OACjD,aAAaE,GAAAD,EAAAgC,GAAA,YAAAA,EAAS,cAAT,YAAAhC,EAAsB,SAAtB,KAAAC,EAAgC,OAC7C,YAAaH,EAAQ,sBAAsB,EAAE,IAAM,OAAO,YAC1D,gBAAiBgC,EAAmBhC,CAAO,EAC3C,MAAO6B,GAAQ7B,CAAO,EACtB,mBAAoBD,GAAuBC,CAAO,EAClD,eAAgBS,GAAsBT,CAAO,EAC7C,SAASiC,EAAAE,GAAmBnC,EAASA,EAAQ,aAAa,IAAjD,KAAAiC,EAAsD,MACjE,CACF,CAQA,SAASG,GAAsBC,EAAqD,CA1MpF,IAAApC,EA2ME,IAAMqC,EAA0B,CAAC,EAC3BC,EAAO,IAAI,IACXC,EAAO,WAGPC,EAAS,IAAI,IACnB,OAAW,CAACC,EAAIC,CAAG,IAAKN,EAAa,CACnC,IAAIO,EAA2BF,EAAG,cAC9BG,EAAWL,EACf,KAAOI,GAAU,CACf,GAAIP,EAAY,IAAIO,CAAQ,EAAG,CAC7BC,EAAWR,EAAY,IAAIO,CAAQ,EACnC,IAAME,EAAUT,EAAY,IAAIK,CAAE,EAC5BrC,EAAM,GAAGwC,CAAQ,KAAKC,CAAO,GAC/B,CAACP,EAAK,IAAIlC,CAAG,GAAKwC,IAAaC,IACjCP,EAAK,IAAIlC,CAAG,EACZiC,EAAM,KAAK,CAAE,gBAAiBO,EAAU,cAAeC,EAAS,OAAQ,EAAI,CAAC,GAE/E,KACF,CACAF,EAAWA,EAAS,aACtB,CACA,IAAMG,GAAW9C,EAAAwC,EAAO,IAAII,CAAQ,IAAnB,KAAA5C,EAAwB,CAAC,EAC1C8C,EAAS,KAAKL,CAAE,EAChBD,EAAO,IAAII,EAAUE,CAAQ,CAC/B,CAGA,QAAWC,KAAQP,EAAO,OAAO,EAAG,CAClC,GAAIO,EAAK,OAAS,EAAG,SAErB,IAAMC,EAASD,EAAK,OAASxD,GAAyBwD,EAAK,MAAM,EAAGxD,EAAsB,EAAIwD,EAC9F,QAAS3B,EAAI,EAAGA,EAAI4B,EAAO,OAAQ5B,IACjC,QAAS6B,EAAI7B,EAAI,EAAG6B,EAAID,EAAO,OAAQC,IAAK,CAC1C,GAAIZ,EAAM,QAAU7C,GAAsB,OAAO6C,EACjD,IAAMa,EAAMd,EAAY,IAAIY,EAAO5B,CAAC,CAAE,EAChC+B,EAAMf,EAAY,IAAIY,EAAOC,CAAC,CAAE,EACtC,GAAIC,IAAQC,EAAK,SACjB,IAAMC,EAAM,GAAGF,CAAG,KAAKC,CAAG,QACpBE,EAAM,GAAGF,CAAG,KAAKD,CAAG,QACrBZ,EAAK,IAAIc,CAAG,IACfd,EAAK,IAAIc,CAAG,EACZf,EAAM,KAAK,CAAE,gBAAiBa,EAAK,cAAeC,EAAK,OAAQ,EAAI,CAAC,GAEjEb,EAAK,IAAIe,CAAG,IACff,EAAK,IAAIe,CAAG,EACZhB,EAAM,KAAK,CAAE,gBAAiBc,EAAK,cAAeD,EAAK,OAAQ,EAAI,CAAC,EAExE,CAEJ,CAEA,OAAOb,CACT,CAEA,SAASiB,GACPvB,EACsF,CACtF,IAAMwB,EAAuB,CAAC,EACxBjB,EAAO,IAAI,IACXF,EAAc,IAAI,IAGxB,OADmB,SAAS,iBAAiB,oBAAoB,EACtD,QAASK,GAAO,CACzB,GAAIA,aAAc,SAAW,CAACH,EAAK,IAAIG,CAAE,EAAG,CAC1CH,EAAK,IAAIG,CAAE,EACX,IAAMe,EAAO1B,GAAYW,EAAIV,CAAkB,EAC/CwB,EAAM,KAAKC,CAAI,EACfpB,EAAY,IAAIK,EAAIe,EAAK,WAAW,CACtC,CACF,CAAC,EAEkB,SAAS,iBAAiB,+BAA+B,EACjE,QAASf,GAAO,CACzB,GAAI,EAAEA,aAAc,UAAYH,EAAK,IAAIG,CAAE,EAAG,OAC9C,IAAMgB,EAAUhB,EAAG,aAAa,YAAY,EACtCiB,EAAgBjB,EAAG,aAAa,kBAAkB,EACxD,GAAI,CAACgB,GAAW,CAACC,EAAe,OAChCpB,EAAK,IAAIG,CAAE,EACX,IAAMe,EAAO1B,GAAYW,EAAIV,CAAkB,EAC/CwB,EAAM,KAAKC,CAAI,EACfpB,EAAY,IAAIK,EAAIe,EAAK,WAAW,CACtC,CAAC,EAEM,CAAE,MAAAD,EAAO,MAAOpB,GAAsBC,CAAW,EAAG,YAAAA,CAAY,CACzE,CAKO,SAASuB,IAA+B,CAC7C,GAAI,OAAO,QAAW,YACpB,OAAOlE,GAGT,IAAImE,EAAoC,KACpCC,EAAiB,EACjBC,EAA+D,KAK7DC,EAAmB,IAAI,IAEvBhC,EAAsBhC,GAA6B,CACvD,GAAI,CACF,IAAMiE,EAAS,OAAO,iBAAiBjE,CAAO,EACxCkE,EAAW,WAAWD,EAAO,QAAQ,GAAK,GAC1CE,EAAS,WAAWF,EAAO,MAAM,GAAK,EACtCG,EAAOpE,EAAQ,sBAAsB,EACrCqE,EAAkB,KAAK,IAAID,EAAK,IAAK,CAAC,EACtCE,EAAiB,OAAO,aAAe,EACvCC,EAAkB,GAAKF,EAAkBC,EAAiB,GAI1DE,EACJ7E,GAAUuE,EAAU,GAAI,EAAE,EAAI,GAC9BK,EAAkB,GAClB5E,GAAUwE,EAAQ,EAAG,GAAG,EAAI,GAE9B,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,EAAGK,CAAK,CAAC,CACvC,OAAQhE,EAAA,CACN,MAAO,GACT,CACF,EAoGA,MAAO,CACL,KAnGW,IACX,IAAI,QAASiE,GAAY,CACvB,IAAMC,EAAM,IAAY,CACtB,GAAM,CAAE,MAAAlB,EAAO,MAAAlB,EAAO,YAAAD,CAAY,EAAIkB,GAAqBvB,CAAkB,EAG7EgC,EAAiB,MAAM,EACvB,OAAW,CAACtB,EAAIiC,CAAE,IAAKtC,EAAa2B,EAAiB,IAAItB,EAAIiC,CAAE,EAC/DF,EAAQ,CAAE,MAAAjB,EAAO,MAAAlB,EAAO,UAAW,KAAK,IAAI,CAAE,CAAC,CACjD,EAEA,GAAI,CACE,OAAO,qBAAwB,WACjCwB,EAAiB,oBAAoBY,EAAK,CAAE,QAAS,GAAI,CAAC,EAE1DA,EAAI,CAER,OAAQlE,EAAA,CACNkE,EAAI,CACN,CACF,CAAC,EAgFD,QA9EeE,GAA6D,CAC5Eb,EAAkBa,EAClB,GAAI,CACFf,EAAW,IAAI,iBAAkBgB,GAAc,CAC7C,IAAMC,EAAuB,CAAC,EACxBC,EAAW,IAAI,IACrB,QAAWC,KAAYH,EACjBG,EAAS,OAAS,aACtBA,EAAS,WAAW,QAASvB,GAAS,CACpC,GAAI,EAAEA,aAAgB,SAAU,OAChC,IAAMwB,EAAwB,CAAC,EAE7B5F,GAAa,IAAIoE,EAAK,OAAO,IAC5BA,EAAK,aAAa,kBAAkB,GAAKA,EAAK,aAAa,YAAY,IAExEwB,EAAW,KAAKxB,CAAI,EAOtB,GAAI,CACFA,EAAK,iBAAiBlE,EAAgB,EAAE,QAASmD,GAAOuC,EAAW,KAAKvC,CAAE,CAAC,CAC7E,OAAQlC,EAAA,CAER,CACA,QAAWkC,KAAMuC,EAAY,CAI3B,GAAIjB,EAAiB,IAAItB,CAAE,EAAG,SAC9B,IAAMwC,EAAUnD,GAAYW,EAAIV,CAAkB,EAClD8C,EAAM,KAAKI,CAAO,EAClBH,EAAS,IAAIG,EAAQ,WAAW,EAChClB,EAAiB,IAAItB,EAAIwC,EAAQ,WAAW,CAC9C,CACF,CAAC,EAEH,GAAIJ,EAAM,SAAW,GAAK,CAACf,EAAiB,OAG5C,QAAWrB,IAAM,CAAC,GAAGsB,EAAiB,KAAK,CAAC,EACrCtB,EAAG,aAAasB,EAAiB,OAAOtB,CAAE,EAKjD,IAAMJ,EAAQF,GAAsB4B,CAAgB,EAAE,OACnDxD,GAAMuE,EAAS,IAAIvE,EAAE,eAAe,GAAKuE,EAAS,IAAIvE,EAAE,aAAa,CACxE,EACAuD,EAAgB,CAAE,MAAOe,EAAO,MAAAxC,EAAO,QAAS,KAAK,IAAI,CAAE,CAAC,CAC9D,CAAC,EACDuB,EAAS,QAAQ,SAAS,KAAM,CAAE,UAAW,GAAM,QAAS,EAAK,CAAC,CACpE,OAAQrD,EAAA,CAER,CACF,EAsBE,mBAAAwB,EACA,QArBc,IAAY,CAK1B,GAJI6B,IACFA,EAAS,WAAW,EACpBA,EAAW,MAETC,GAAkB,OAAO,oBAAuB,WAClD,GAAI,CACF,mBAAmBA,CAAc,CACnC,OAAQtD,EAAA,CAER,CAEFsD,EAAiB,EACjBC,EAAkB,KAClBC,EAAiB,MAAM,CACzB,CAOA,CACF,ClBjWA,IAAMmB,GAAqB,wCAuB3B,SAASC,GAAWC,EAAqC,CAxGzD,IAAAC,EAyGE,IAAMC,EAAQC,GAAqC,CACjD,GAAI,CAGF,IAAMC,EAAI,SAAS,OAAO,MAAM,IAAI,OAAO,WAAWD,CAAI,UAAU,CAAC,EACrE,OAAOC,EAAI,mBAAmBA,EAAE,CAAC,CAAE,EAAI,MACzC,OAAQC,EAAA,CACN,MACF,CACF,EACA,OAAQJ,EAAAD,EAASE,EAAKI,GAAkBN,CAAM,CAAC,EAAI,SAA3C,KAAAC,EAAyDC,EAAKK,EAA0B,CAClG,CAgBA,IAAMC,GAAkB,IAAI,IAErB,SAASC,GAAKC,EAA6C,CAtIlE,IAAAT,EAuIE,IAAMU,EAASF,GAASC,CAAM,EAMxBE,EAAaF,EAAO,oBAAsB,IAASG,GAAoB,EACvEC,EAAQJ,EAAO,UAAY,IAASE,EAQpCG,EAAW,OAAOL,EAAO,QAAW,UAAYA,EAAO,OAAO,WAAW,KAAK,EAC9EM,EAAcN,EAAO,YAAc,IAAQ,CAACK,EAClD,GAAI,CAACL,EAAO,OAASM,GAAe,OAAO,QAAW,YAAa,OAAOL,EAE1E,GAAIG,EAMF,OAAKF,GACHK,GAA4BP,EAAO,OAASQ,GAAMT,GAAKS,CAAwB,CAAC,EAE3EP,EAKT,IAAMQ,EAAeX,GAAgB,IAAIE,EAAO,MAAM,EACtD,GAAIS,EACF,GAAI,CACFA,EAAa,CACf,OAAQd,EAAA,CAER,CAGF,IAAMe,EAAaC,GAAiB,EAC9BC,GAAoBrB,EAAAS,EAAO,YAAP,KAAAT,EAAoBH,GACxCyB,EAAcC,GAAkB,CACpC,QAASF,EAAkB,QAAQ,eAAgB,aAAa,EAChE,OAAQZ,EAAO,OACf,UAAWA,EAAO,OAClB,UAAWX,GAAWW,EAAO,MAAM,CACrC,CAAC,EAOIU,EAAW,KAAK,EAAE,KAAMK,GAAW,CACtC,QAAWC,KAAQD,EAAO,MACxBF,EAAY,YAAY,CACtB,GAAIG,EAAK,YACT,YAAaA,EAAK,YAClB,aAAcA,EAAK,aACnB,QAAShB,EAAO,gBAAkBgB,EAAK,YAAc,CAACA,EAAK,WAAW,EAAI,CAAC,EAC3E,gBAAiBA,EAAK,gBACtB,MAAOA,EAAK,KACd,CAAC,EAEH,QAAWC,KAAQF,EAAO,MACxBF,EAAY,kBAAkBI,CAAI,EAEpCJ,EAAY,SAAS,CACvB,CAAC,EAED,IAAIK,EAA0D,KACxDC,EAAgB,IAAY,CAC5BD,IAAsB,MAAM,aAAaA,CAAiB,EAC9DA,EAAoB,WAAW,IAAM,CACnCA,EAAoB,KACpBL,EAAY,SAAS,CACvB,EAAG,GAAG,CACR,EAEAH,EAAW,QAASU,GAAU,CAC5B,QAAWJ,KAAQI,EAAM,MACvBP,EAAY,YAAY,CACtB,GAAIG,EAAK,YACT,YAAaA,EAAK,YAClB,aAAcA,EAAK,aACnB,QAAShB,EAAO,gBAAkBgB,EAAK,YAAc,CAACA,EAAK,WAAW,EAAI,CAAC,EAC3E,gBAAiBA,EAAK,gBACtB,MAAOA,EAAK,KACd,CAAC,EAEH,QAAWC,KAAQG,EAAM,MACvBP,EAAY,kBAAkBI,CAAI,EAEpCE,EAAc,CAChB,CAAC,EAKD,IAAME,EAAgB,IAAY,CAC5BH,IAAsB,OACxB,aAAaA,CAAiB,EAC9BA,EAAoB,MAEtBR,EAAW,QAAQ,EACnBG,EAAY,QAAQ,EAChBf,GAAgB,IAAIE,EAAO,MAAM,IAAMqB,GACzCvB,GAAgB,OAAOE,EAAO,MAAM,CAExC,EACA,OAAAF,GAAgB,IAAIE,EAAO,OAAQqB,CAAa,EAEzCC,EAAAC,EAAA,GACFtB,GADE,CAEL,SAAU,IAAMY,EAAY,SAAS,EACrC,QAAS,IAAM,CACbQ,EAAc,EACdpB,EAAO,QAAQ,CACjB,EACA,QAAS,IAAM,CACboB,EAAc,EACdpB,EAAO,QAAQ,CACjB,CACF,EACF","names":["index_graph_exports","__export","BLOCK_ALIGNS","BLOCK_EMPHASES","BLOCK_FITS","BLOCK_GAPS","BLOCK_GRID_COLUMNS","BLOCK_HEADING_LEVELS","BLOCK_JUSTIFIES","BLOCK_RATIOS","BLOCK_SIZES","BLOCK_TEXT_ALIGNS","BLOCK_TONES","BLOCK_WEIGHTS","FORM_FIELD_KINDS","FORM_INPUT_TYPES","LEGACY_SESSION_COOKIE_NAME","MAX_BLOCK_ARMS","MAX_BLOCK_CHILDREN","MAX_BLOCK_DEPTH","MAX_BLOCK_NODES","MAX_BLOCK_TEXT_LEN","MAX_FORM_FIELDS","MAX_FORM_SELECT_OPTIONS","SNAPSHOT_STORAGE_KEY_PREFIX","armOfResult","attachMicroSignalDetectors","baselineResultFor","baselineSlots","containsFormBlock","deriveSessionSegment","detectDeviceClass","detectTimeOfDay","detectTrafficSource","grantConsent","init","isDoNotTrackEnabled","locatorFromElement","readSnapshot","referrerDomainFromReferer","renderPrePaintScript","sanitizePageUrl","sessionCookieName","toWireSlot","writeSnapshot","__toCommonJS","randomUuidV4","buf","out","i","c","r","storageSuffix","apiKey","LEGACY_SESSION_COOKIE_NAME","sessionCookieName","DEFAULT_COOKIE_NAME","DEFAULT_COOKIE_TTL_DAYS","STORAGE_KEY","generateSessionId","randomUuidV4","readCookie","name","match","e","writeCookie","value","maxAgeSeconds","readLocalStorage","key","writeLocalStorage","readSessionStorage","writeSessionStorage","removeSessionStorage","probeCookieWritable","removeLocalStorage","clearCookie","SSR_MANAGER","initSession","config","_a","_b","_c","_d","_e","_f","_g","suffix","storageSuffix","cookieName","sessionCookieName","storageKey","legacyTombstoneKey","nonEmpty","readLegacy","sessionId","lsOk","cookieOk","ssOk","ephemeral","backoffDelayMs","consecutiveFailures","classifyResponse","res","status","drainBucket","storageKey","max","raw","parsed","e","writeBucket","items","existing","byId","purgeBucket","ids","drop","remaining","MAX_SENT_IDS","KEEPALIVE_BUDGET_BYTES","retryStorageKey","apiKey","SSR_QUEUE","createEventQueue","config","_a","_b","_c","flushIntervalMs","maxBatchSize","maxRetrySize","ingestUrl","RETRY_KEY","queue","sentIds","sentIdOrder","markSent","ids","id","queuedIds","oldest","purgeBucket","capQueue","dropped","enqueue","event","requeueFailed","batch","retryEvents","drainBucket","backoffUntil","consecutiveFailures","transportBatch","salvage","body","e","pending","writeBucket","backoffDelayMs","handleResponse","res","classifyResponse","rest","encoder","byteLength","s","packBatch","pool","out","bytes","size","flush","flushTimerActive","intervalId","onVisibilityChange","onPageHide","MAX_SENT_IDS","goalRetryStorageKey","apiKey","SSR_GOAL_QUEUE","createGoalQueue","config","_a","_b","_c","flushIntervalMs","maxRetrySize","maxPerFlush","RETRY_KEY","pending","pendingIds","sentIds","sentIdOrder","backoffUntil","consecutiveFailures","disposed","markSent","goal","oldest","purgeBucket","markFailed","writeBucket","backoffDelayMs","transport","res","e","handle","r","outcome","classifyResponse","flush","sentThisTick","restored","drainBucket","intervalId","onVisibilityChange","onPageHide","DEFAULT_TTL_MS","cacheKey","componentId","segment","createAssignmentCache","ttlMs","apiKey","memory","keyPrefix","storageSuffix","storageKey","parseStorageKey","key","suffix","sep","e","listStorageKeys","keys","i","isExpired","assignment","raw","parsed","entry","storageK","agentUaList","uaTokenMatch","userAgent","matchedAgentToken","_a","s","token","detectDeviceClass","userAgent","s","detectTrafficSource","referrer","appOrigin","refUrl","e","host","referrerDomainFromReferer","CLICK_ID_KEYS","extractTrackedParams","search","utmParams","clickIds","entries","k","v","first","detectTimeOfDay","d","h","deriveSessionSegment","opts","body","buildSessionUpsertPayload","sessionId","_a","_b","_c","_d","_e","_f","_g","_h","ua","referer","now","uaTokenMatch","import_policy","toWireSlot","d","__spreadValues","dim","values","baselineResultFor","decl","baselineSlots","decls","out","armOfResult","result","SNAPSHOT_STORAGE_KEY_PREFIX","BANDS","readSnapshot","apiKey","raw","p","e","writeSnapshot","snap","renderPrePaintScript","import_policy","import_policy","PROD_KEYLESS_ERROR","LOCAL_MODE_BANNER","bannerShown","prodErrorShown","readPersonaOverrideFromUrl","_a","e","createLocalModeClient","config","session","initSession","sessionId","forcedPersona","modPromise","mod","m","bannerShown","LOCAL_MODE_BANNER","prodErrorShown","PROD_KEYLESS_ERROR","lastOutcome","BAND_CONFIDENCE","seedPersona","__spreadValues","snap","readSnapshot","applyPersonaAttributes","outcome","el","input","_b","_c","__spreadProps","writeSnapshot","slotId","p","componentId","variantIds","BLOCK_GAPS","BLOCK_ALIGNS","BLOCK_JUSTIFIES","BLOCK_SIZES","BLOCK_WEIGHTS","BLOCK_TONES","BLOCK_EMPHASES","BLOCK_TEXT_ALIGNS","BLOCK_RATIOS","BLOCK_FITS","BLOCK_GRID_COLUMNS","BLOCK_HEADING_LEVELS","FORM_FIELD_KINDS","FORM_INPUT_TYPES","MAX_FORM_FIELDS","MAX_FORM_SELECT_OPTIONS","MAX_BLOCK_NODES","MAX_BLOCK_DEPTH","MAX_BLOCK_CHILDREN","MAX_BLOCK_ARMS","MAX_BLOCK_TEXT_LEN","containsFormBlock","node","n","attachMicroSignalDetectors","emit","node","variantAssignedAt","options","cleanups","firedOnce","timestamps","onClick","now","onCopy","e","sel","selectionLength","isVisible","hesitationTimer","clearHesitation","startHesitation","onScroll","ioCallback","entries","entry","io","assignedAt","onVisibility","elapsed","c","DEFAULT_INGEST_URL","_clients","_lastApiKey","_registerConsentUpgradeInit","apiKey","reinit","entry","isGoalOptions","v","SESSION_UPSERT_RETRIES","generateEventId","randomUuidV4","currentPath","_a","createActionLatch","byEvent","byFlush","flushOpen","alreadyRecorded","map","actionKey","valueKey","values","hasWindowEvent","currentEvent","ev","closeFlushWindow","done","clear","ch","keys","collapsed","emittedPages","startPageviewTracking","client","projectId","markDelivered","h","stopped","last","emit","path","installed","name","orig","wrapper","a","r","landing","SSR_CLIENT","readTrackedParams","extractTrackedParams","deriveBaseUrl","ingestUrl","isDoNotTrackEnabled","grantConsent","key","config","upgrade","fullClient","init","__spreadProps","__spreadValues","disposed","createPreConsentProxy","servesWinner","baseUrl","authHeaders","winnerCache","inflightWinners","inner","componentId","variantIds","_agentData","cached","coalesce","params","res","result","e","proxy","n","m","w","s","c","g","o","u","av","i","ids","setInner","inflight","run","hit","request","_b","_c","_d","_e","_f","_g","_h","_i","_j","prevEntry","dntBlocked","gated","keyValid","createLocalModeClient","resolvedIngestUrl","sessionStart","session","initSession","assignmentCache","createAssignmentCache","eventQueue","createEventQueue","warnedDropStatuses","goalQueue","createGoalQueue","goal","status","deviceClass","detectDeviceClass","appOrigin","trafficSource","detectTrafficSource","sessionSegment","inflightAssigns","inflightDecides","slotStore","slotConfigStore","sitePalette","personaState","reportedSlotIds","pendingSlotReports","slotReportTimer","seedSlotBaselines","decls","d","baselineResultFor","slotId","seedSnapshot","readSnapshot","BAND_CONFIDENCE","ds","variantId","sessionReady","sessionId","referrerDomain","referrerDomainFromReferer","utmParams","clickIds","sessionBody","detectTimeOfDay","uaTokenMatch","upsertSession","attempt","classifyResponse","backoffDelayMs","firedThisAction","stopPageviews","pageviewsTornDown","metadataOrOpts","weight","stepIndex","sid","opts","goalId","body","payload","goalType","assignment","slotResult","attributedVariantId","armOfResult","fullEvent","userId","event","segment","agentData","agentDataByVariant","remainingTtlMs","input","declared","id","toWireSlot","decideKey","data","slots","served","prior","baseline","known","writeSnapshot","slotIds","batch","SNAPSHOT_STORAGE_KEY_PREFIX","retryStorageKey","goalRetryStorageKey","win","STALE_EDGES_KEY","SEMANTIC_NEIGHBOURS","neighboursFor","semanticType","_a","readStorage","key","fallback","raw","e","writeStorage","value","VALID_SEMANTIC_TYPES","toValidSemanticType","type","sanitizePageUrl","href","u","contentHashOf","componentId","answers","input","h","i","createGraphClient","config","pageNodes","structuralEdges","nodesKey","storageSuffix","persist","storedNodes","node","edge","_b","nodes","nodesByType","n","list","edges","seen","source","neighbourType","targets","target","componentIds","payload","__spreadProps","__spreadValues","STABLE_DATA_ATTRS","normalizedText","el","_a","fingerprintOf","cssEscape","v","unique","root","selector","e","uniqueSelector","tag","classes","c","candidates","parent","parentSel","combined","siblings","n","idx","distinguishable","s","nth","locatorFromElement","fingerprint","id","name","STABLE_DATA_ATTRS","value","SEMANTIC_TYPES","ARIA_TOPIC","structuralTopicOf","f","aria","hay","fallbackTopicOf","KEYWORDS","CONTENT_PATTERNS","SHARED_TOPIC_PARENT","headingText","el","_a","h","classifyFeatures","_b","structural","type","re","fb","featuresFromElement","text","role","__spreadValues","classifySection","OBSERVE_TAGS","HEADING_SELECTOR","SUBTREE_SELECTOR","MAX_SIBLINGS_PER_GROUP","MAX_STRUCTURAL_EDGES","SSR_SCANNER","normalize","value","min","max","readReactComponentName","element","_a","_b","_c","record","key","fiber","name","e","extractDataAttributes","attrs","attr","SEMANTIC_SET","SEMANTIC_TYPES","inferSemanticType","explicit","role","classifySection","shortHash","input","h","i","domPathSignature","parts","current","parent","index","componentIdFor","declared","depthOf","depth","scanElement","getProminenceScore","_d","heading","locatorFromElement","detectStructuralEdges","elementToId","edges","seen","ROOT","groups","el","_id","ancestor","groupKey","childId","siblings","sibs","capped","j","aId","bId","fwd","rev","collectNodesAndEdges","nodes","node","hasAria","hasSentientId","createDOMScanner","observer","idleCallbackId","contentCallback","knownElementToId","styles","fontSize","zIndex","rect","distanceFromTop","viewportHeight","inverseDistance","score","resolve","run","id","onContentAdded","mutations","added","addedIds","mutation","candidates","scanned","DEFAULT_INGEST_URL","readSntUid","apiKey","_a","read","name","m","e","sessionCookieName","LEGACY_SESSION_COOKIE_NAME","_graphTeardowns","init","config","client","dntBlocked","isDoNotTrackEnabled","gated","keyValid","zeroNetwork","_registerConsentUpgradeInit","c","prevTeardown","domScanner","createDOMScanner","resolvedIngestUrl","graphClient","createGraphClient","result","node","edge","syncDebounceTimer","debouncedSync","event","teardownGraph","__spreadProps","__spreadValues"]}