@matterfact/embed 0.11.2 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/embed.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,11 +58,14 @@ about the data behind it — no wiring, and the token never reaches the model.
|
|
|
58
58
|
|
|
59
59
|
## Documentation
|
|
60
60
|
|
|
61
|
-
The full integration
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
The full embed integration reference — endpoints, page context, host tools, telemetry, and
|
|
62
|
+
the Microsoft Entra / authorization setup — is available to partners after signing in, in the
|
|
63
|
+
**Embed** section of the matterfact API docs:
|
|
64
64
|
|
|
65
|
-
**[app.matterfact.com/docs](https://app.matterfact.com/docs)** *(partner sign-in required)*
|
|
65
|
+
**[app.matterfact.com/docs/api](https://app.matterfact.com/docs/api)** *(partner sign-in required)*
|
|
66
|
+
|
|
67
|
+
The Microsoft Entra one-time setup and the token/scope walkthrough are under
|
|
68
|
+
[**#tag/authorization**](https://app.matterfact.com/docs/api#tag/authorization).
|
|
66
69
|
|
|
67
70
|
Don't have access yet? Ask your matterfact contact.
|
|
68
71
|
|
package/dist/embed.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/protocol.ts", "../src/geometry.ts", "../src/loader.ts", "../src/embed.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * The matterfact embed bridge protocol.\n *\n * This is the contract between the HOST PAGE (a customer's site, running our\n * loader) and the WIDGET (our chat, running in a cross-origin iframe on\n * app.matterfact.com).\n *\n * Two things to understand before changing anything here:\n *\n * 1. THE IFRAME CANNOT SEE THE PAGE. It is cross-origin \u2014 that is the entire\n * point of it. So every fact about the host page (its URL, its DOM, what the\n * user just did) reaches the agent only because the LOADER, which runs as\n * ordinary page JS and *can* see it, chose to send it. The loader is the eye;\n * the widget is the mouth.\n *\n * 2. THIS CONTRACT OUTLIVES THE IFRAME. The next tier is an in-page React SDK\n * with no iframe at all \u2014 and it will speak exactly this. If the message\n * shapes are right, \"deeper integration\" is a transport swap (postMessage ->\n * direct call), not a rewrite. So model the DOMAIN here, not the plumbing.\n */\n\n// v3 (2026-07): adds `host.sitemap` (a structured, content-annotated site map),\n// `widget.navigate` (deeplink the host to one of its own routes), and `widget.chat`\n// (content-free chat-turn telemetry forwarded to the host's `onEvent`). All additive \u2014\n// a peer that doesn't know a message simply never sends/handles it \u2014 so this is a\n// capability signal, not a breaking change. No code branches on the version number.\nexport const PROTOCOL_VERSION = 3;\n\n/** Every message is namespaced so we never collide with the host page's own postMessage traffic. */\nexport const CHANNEL = 'mf-embed';\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 refs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/**\n * A handle on a real element in the host page.\n *\n * Format and semantics are lifted from Playwright's ARIA snapshot (`ref=e12`),\n * for a specific reason: it is the element-handle syntax the frontier models have\n * seen most, via playwright-mcp. We are not being original here on purpose.\n *\n * The ref is an in-page expando plus a snapshot-scoped Map \u2014 never an attribute,\n * so it is invisible to the host page's CSS, its serialization, and its own\n * mutation observers. It is resolved back to an element by the LOADER; the agent\n * never touches the DOM, and never learns a selector.\n */\nexport type ElementRef = string; // 'e12'\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 host \u2192 widget \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/** Ambient facts about the page. Cheap, sent on every navigation. */\nexport interface PageContext {\n url?: string;\n /** A route PATTERN ('/orders/:id') beats a raw path \u2014 it tells the agent what KIND of page this is. */\n route?: string;\n path?: string;\n title?: string;\n locale?: string;\n /** 1\u20132 sentences, written the way you'd brief a colleague. The highest-value field by far. */\n description?: string;\n /** Things the page is about. These also become @-mentionable in the composer, for free. */\n entities?: PageEntity[];\n /** Allowlisted only. Never a dump of app state. */\n data?: Record<string, unknown>;\n}\n\nexport interface PageEntity {\n /** Host kinds are namespaced (`x:ticker`) so they can never collide with matterfact's own. */\n kind: string;\n id: string;\n label: string;\n /** Pre-rendered, agent-readable. Keep it short. */\n text?: string;\n data?: Record<string, unknown>;\n}\n\n/**\n * How a route's content is classified, for the annotated site map.\n *\n * The host DECORATES the site map we auto-derive (see `SiteMapEntry`) so the agent\n * knows which routes carry MATTERFACT content and of what kind \u2014 app-wide awareness\n * it can navigate toward. This is CLASSIFICATION only: no per-instance ids, and never\n * a capability token. The concrete artifact/document on the CURRENT page still arrives\n * via `host.context` entities / `host.artifactGrants`, resolved per page.\n *\n * - `mf-artifact` \u2014 a co-embedded matterfact artifact (its slug); the token stays on\n * the live iframe / grant, never here.\n * - `mf-document` \u2014 a matterfact document (dossier/report), resolved to an MF_DOC_ID\n * host-side per page.\n * - `host-data` \u2014 the host's own data; not a matterfact entity.\n */\nexport type ContentClass =\n | { kind: 'mf-artifact'; slug: string }\n | { kind: 'mf-document'; doctype: string }\n | { kind: 'host-data' };\n\n/**\n * One route in the annotated site map: the path pattern + label we derived, plus the\n * host's content classification. `current` marks the route the user is on.\n */\nexport interface SiteMapEntry {\n /** Route pattern or path, e.g. '/app/company/:ticker/dossier'. */\n path: string;\n /** Human label for the route ('Company Dossier'). */\n label?: string;\n /** The host's classification of this route's content, when it annotated it. */\n content?: ContentClass;\n current?: boolean;\n}\n\n/** A co-embedded artifact's read-only capability, for the backend to materialize its\n * data. Backend-only \u2014 never rendered. `token` is the artifact's own embed token. */\nexport interface ArtifactGrant {\n id: string;\n owner: string;\n token: string;\n}\n\n/**\n * A DECLARED artifact grant \u2014 the `<MatterfactAgent artifacts>` prop (or\n * `window.matterfact.artifacts`). Same wire shape as a grant discovered by scanning\n * co-embedded `<MatterfactArtifact>` iframes, plus an optional `label`; it feeds the\n * SAME `host.artifactGrants` channel. This is how a host makes a board reachable from\n * a route where its iframe ISN'T mounted, without hidden zero-size iframes smuggling\n * the token into the DOM. The token stays in the grant channel, never in message text.\n */\nexport interface DeclaredArtifact {\n slug: string;\n owner: string;\n token: string;\n label?: string;\n}\n\n/**\n * One host-tool lifecycle event, surfaced to the host via `onToolEvent` (and the dev\n * panel) \u2014 the host's own telemetry hook. Fired loader-side; a throwing or slow\n * callback here NEVER blocks or breaks the call path.\n *\n * - `advertise` \u2014 the merged tool list changed (fired with the new set's names).\n * - `call` \u2014 a tool call is about to run (`args` present).\n * - `result` \u2014 the call settled (`ok`/`error`/`ms`).\n */\nexport interface ToolEvent {\n phase: 'advertise' | 'call' | 'result';\n name: string;\n toolClass: 'hoist' | 'mf' | 'app';\n args?: unknown;\n ok?: boolean;\n error?: string;\n ms?: number;\n}\n\n/**\n * One embed lifecycle event, surfaced to the host via `onEvent` \u2014 the host's single\n * telemetry hook. Fired loader-side (lifecycle) and widget-side (chat); a throwing or\n * slow callback NEVER blocks or breaks the mount/call/auth/run path.\n *\n * - `ready` \u2014 the widget connected (protocol handshake done).\n * - `open`/`close` \u2014 the corner widget was opened/closed. (Inline has no open event \u2014\n * the host's own panel is the open/close control.)\n * - `navigate` \u2014 the agent deeplinked the host to one of its own routes.\n * - `auth` \u2014 host-auth passthrough: `required` (widget asked), `granted` (a\n * token was handed over), `failed` (no provider result / the provider threw).\n * - `error` \u2014 the widget failed to mount / load.\n * - `chat` \u2014 a chat-turn moment. Content-free: phase + opaque chatId only.\n * - `tool` \u2014 a host-tool lifecycle event (the `ToolEvent` shape, tagged). Also\n * still delivered untagged to the legacy `onToolEvent` hook for back-compat.\n */\nexport type MatterfactEvent =\n | { type: 'ready' }\n | { type: 'open' }\n | { type: 'close' }\n | { type: 'navigate'; href: string }\n | { type: 'auth'; phase: 'required' | 'granted' | 'failed' }\n | { type: 'error'; message: string }\n | { type: 'chat'; phase: 'message' | 'response-start' | 'response-end'; chatId?: string }\n | ({ type: 'tool' } & ToolEvent);\n\n/**\n * A snapshot of the page as the agent sees it.\n *\n * `yaml` is Playwright's ARIA-snapshot rendering \u2014 role, accessible name, state,\n * and a ref on anything interactable:\n *\n * - heading \"Orders\" [level=1]\n * - textbox \"Search orders\" [ref=e5]\n * - button \"Export CSV\" [ref=e9]\n *\n * NOT raw HTML. A real page is tens of thousands of tokens of div soup, it gives\n * the model no handle to act with, and it is a prompt-injection firehose. The\n * a11y tree is a tenth the size and is the only signal that survives in-page \u2014\n * `getEventListeners()` is DevTools-only, so listener-sniffing (the browser-use\n * approach) cannot see a single React handler on a real customer's app.\n */\nexport interface DomSnapshot {\n yaml: string;\n /** Monotonic. A ref is only valid within the snapshot that minted it. */\n seq: number;\n /** True when the walk hit its node budget and stopped. The agent must be told it is not seeing everything. */\n truncated: boolean;\n}\n\n/**\n * Where the user's attention is RIGHT NOW.\n *\n * This is the difference between an assistant that can read your page and one that\n * knows what you're doing. A full DOM snapshot answers \"what is on this screen\"; this\n * answers \"what are you looking at and working in\" \u2014 which is what a colleague\n * leaning over reads first. It is small, so it rides on every turn, where the full\n * snapshot does not.\n *\n * Every field here is a LABEL or a REF, never a value. `selection` is the sharpest\n * intent signal there is (\"this is the thing I'm asking about\"), and for exactly that\n * reason it is also where a user might have highlighted their own email \u2014 so it is\n * redacted and honours data-mf-private like everything else.\n */\nexport interface FocusContext {\n /** Text the user has selected. The single strongest \"I'm asking about this\" signal. Redacted. */\n selection?: string;\n /** The control the user is working IN \u2014 by label and ref, never its contents. */\n focused?: { ref?: ElementRef; label: string; role: string };\n /** The refs currently in the viewport: the handful of things actually on screen, out of the whole tree. */\n visibleRefs?: ElementRef[];\n /** How far down the page they are, 0\u20131. A cheap proxy for \"which part am I reading\". */\n scroll?: number;\n}\n\n/** Something the user did. Shaped after PostHog's autocapture \u2014 one readable line, not a replay frame. */\nexport interface ActivityEvent {\n seq: number;\n ts: number;\n type: 'nav' | 'click' | 'input' | 'submit' | 'selection' | 'custom';\n /** Human-readable: 'clicked button \"Export CSV\"'. Semantic, not coordinates. */\n summary: string;\n ref?: ElementRef;\n data?: Record<string, unknown>;\n}\n\n/**\n * A tool the HOST page exposes to the agent.\n *\n * Deliberately shaped as a superset of WebMCP (`navigator.modelContext`, W3C\n * WebML CG draft / Chrome origin trial). A customer who adopts WebMCP should be\n * able to hand us their existing tool declarations unchanged.\n */\nexport interface HostTool {\n name: string;\n description: string;\n /** JSON Schema. */\n inputSchema: Record<string, unknown>;\n /**\n * A read-only tool may run without asking. Anything that MUTATES the page\n * requires the user to confirm, every time \u2014 see the note on ToolCall.\n */\n readOnly?: boolean;\n /**\n * The tiered confirm policy, set by the host (e.g. `window.matterfact.hoist.actions`):\n * `'required'` \u21D2 the widget must show a confirm card and get the user's approval\n * before every call; `'auto'` \u21D2 it may call without asking. Absent for a tool that\n * predates this policy (treat as `'required'` \u2014 the safer default).\n */\n confirm?: 'auto' | 'required';\n}\n\nexport type HostToWidget =\n | { type: 'host.ready'; protocol: number; origin: string }\n | { type: 'host.context'; context: PageContext }\n | { type: 'host.focus'; focus: FocusContext }\n | { type: 'host.snapshot'; snapshot: DomSnapshot }\n | { type: 'host.region'; ref: ElementRef; yaml: string }\n | { type: 'host.activity'; events: ActivityEvent[] }\n | { type: 'host.artifactGrants'; grants: ArtifactGrant[] }\n /**\n * The annotated site map \u2014 the app's routes with the host's content\n * classification. Sent on mount and re-sent on navigation (a virtualized SPA can\n * change what's routable). Distinct from the per-page `host.context`: this is the\n * whole app's structure, so the agent can reason about and navigate toward MF\n * content the user isn't currently looking at.\n */\n | { type: 'host.sitemap'; sitemap: SiteMapEntry[] }\n | { type: 'host.tools'; tools: HostTool[] }\n | {\n type: 'host.toolResult';\n callId: string;\n ok: boolean;\n result?: unknown;\n error?: string;\n }\n | { type: 'host.auth'; token: string; expiresAt: number }\n | { type: 'host.theme'; mode: 'light' | 'dark' }\n /**\n * Where the widget currently IS. Sent on mount and after anything that moves it\n * (drag, snap, mode change, viewport resize).\n *\n * The widget cannot work this out for itself \u2014 it can't see the viewport, and the\n * loader owns the box. Without it the widget renders its launcher against a stale\n * guess: a launcher dragged to the left edge kept drawing its circle bottom-RIGHT\n * inside a left-anchored box, so the circle jumped as soon as the box grew for the\n * hover pill. `growth` is the direction the pill/menu/panel must open (toward the\n * viewport centre); `mode` decides whether the launcher is a circle or an edge tab.\n */\n | {\n type: 'host.geometry';\n mode: 'float' | 'dock';\n dockSide: 'left' | 'right';\n growth: 'tl' | 'tr' | 'bl' | 'br';\n };\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 widget \u2192 host \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/**\n * The agent asking the host to do something.\n *\n * THE SECURITY MODEL, stated once: the page's content is UNTRUSTED. Text on the\n * page can try to instruct the agent, and the moment the agent can act, that stops\n * being a bad answer and becomes a bad ACTION. So:\n *\n * - v1 is READ-ONLY. `snapshot` and `activity` flow; no tool mutates the page.\n * - A tool that is not `readOnly` must be confirmed by the USER, every call. Not\n * \"unless the host opts out\" \u2014 every call. The trigger may have been written by\n * the page.\n * - The host is the enforcement point, not the widget. The widget can be lied to;\n * the loader runs in the page and decides what actually happens.\n */\nexport interface ToolCall {\n callId: string;\n name: string;\n args: Record<string, unknown>;\n}\n\nexport type WidgetToHost =\n | { type: 'widget.ready'; protocol: number }\n /**\n * Ask for the FULL page snapshot. This is the agent PULLING detail, not us pushing\n * it: the focus context rides on every turn, but the whole tree is fetched only\n * when the agent decides it needs to look \u2014 cheaper, and it reads what's relevant\n * to the question instead of drowning in the footer nav every turn.\n */\n | { type: 'widget.requestSnapshot' }\n /**\n * Pull the DECLARED page context fresh, right now. The host answers with `host.context`.\n *\n * Declared context (`window.matterfact.context`, or a `getPageContext()` callback) is\n * otherwise only published on load and on URL navigation, so in a virtualized SPA it goes\n * stale on any non-navigation state change (a team switch, a filter edit, a selection).\n * The widget pulls it at turn-assembly time \u2014 symmetric with `widget.requestSnapshot` \u2014\n * so every turn carries the current context, not the context as of the last route change.\n */\n | { type: 'widget.requestContext' }\n /** Zoom in: the a11y sub-tree under one ref (a table, a form, a card the agent cares about). */\n | { type: 'widget.readRegion'; ref: ElementRef }\n | { type: 'widget.callTool'; call: ToolCall }\n /** Chat opened/closed \u2014 the loader resizes the iframe and starts/stops observing. */\n | { type: 'widget.setOpen'; open: boolean }\n | { type: 'widget.resize'; height: number }\n /**\n * Dock / undock the open panel.\n *\n * Docked is a FULL-HEIGHT SIDE PANEL on the launcher's own side \u2014 left corners dock\n * to the left edge, right corners to the right, unless `setDockSide` overrides it.\n * This replaces the old `setExpanded`, which was hardcoded to the right edge and so\n * threw the panel across the screen whenever the launcher sat on the left.\n *\n * The loader owns the box, so only it can do this \u2014 `widget.resize` (height-only)\n * can neither widen the panel nor move it to an edge.\n */\n | { type: 'widget.setMode'; mode: 'float' | 'dock' }\n /** Which edge the tab and its panel live on. */\n | { type: 'widget.setDockSide'; side: 'left' | 'right' }\n /** Place the FLOATING launcher at a specific corner (the menu's snap picker). */\n | { type: 'widget.snapCorner'; corner: 'tl' | 'tr' | 'bl' | 'br' }\n /**\n * Resize the COLLAPSED launcher's host box so the widget can paint outside the 56px\n * circle \u2014 the hover pill (`{w:180,h:56}`) or the context menu (its own box). The\n * iframe clips to the host box, so anything beside the circle is invisible until the\n * host makes room. `{w:56,h:56}` returns to the bare circle. Ignored while open.\n */\n | { type: 'widget.setLauncherRegion'; w: number; h: number }\n /**\n * User resize of the floating panel, via the grip on its inner corner.\n *\n * Same contract as the launcher drag below, and for the same reason: the press is\n * captured by the iframe, so the host cannot see the moves. The widget tracks the\n * gesture and sends SCREEN-coordinate deltas; the loader owns the sizing, the\n * per-corner sign of the delta, and the clamp.\n */\n | { type: 'widget.resizeStart' }\n | { type: 'widget.resizeMove'; dx: number; dy: number }\n | { type: 'widget.resizeEnd' }\n /**\n * Launcher drag. The widget tracks the gesture; the host only moves the element.\n *\n * It has to be this way round, and the reason is easy to get wrong: a press that starts\n * inside an iframe is implicitly captured by that iframe, so every pointermove and the\n * pointerup belong to the widget's document until the button comes up. The host cannot\n * see them at all \u2014 not even through an overlay of its own, because capture beats hit\n * testing. So the host cannot take over a drag mid-gesture; it can only be told.\n *\n * `dx`/`dy` are deltas in SCREEN coordinates, which is the other half of the trick:\n * client coordinates are measured against the iframe's own viewport, and the iframe is\n * being moved under the cursor as the drag proceeds \u2014 so the pointer would appear to\n * stop moving and the launcher would stall after one step. Screen coordinates are\n * absolute and immune to that feedback loop.\n */\n | { type: 'widget.dragStart' }\n | { type: 'widget.dragMove'; dx: number; dy: number }\n | { type: 'widget.dragEnd' }\n /** Put the launcher back in the default corner and forget the saved size/dock. */\n | { type: 'widget.resetPos' }\n /** Get out of the way until the page reloads (a menu action, not a setting). */\n | { type: 'widget.hide' }\n /** The widget has no session; the host must run the hosted-login popup. */\n | { type: 'widget.needsAuth' }\n /**\n * Deeplink the host to one of its OWN routes \u2014 the \"open on page\" control on a\n * co-embedded document's side-panel viewer. The widget is a cross-origin iframe and\n * can't navigate the top window itself, so it asks the loader, which resolves the\n * href against the host location and refuses anything off the host's own origin.\n */\n | { type: 'widget.navigate'; href: string }\n /**\n * A chat-turn lifecycle moment, forwarded by the loader to the host's `onEvent`.\n * Content-free: `phase` + an opaque `chatId`, never message text. Additive.\n */\n | {\n type: 'widget.chat';\n phase: 'message' | 'response-start' | 'response-end';\n chatId?: string;\n };\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 envelope \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/**\n * Everything on the wire is wrapped.\n *\n * `channel` keeps us out of the host page's own postMessage traffic (and out of\n * every other widget's). The receiver ALSO checks `event.origin` against an\n * allowlist and `event.source` against the expected window, on every single\n * message \u2014 a namespace is not a security boundary, it is a politeness.\n */\nexport interface Envelope<T> {\n channel: typeof CHANNEL;\n protocol: number;\n /** Correlates a response to its request; absent for notifications. */\n id?: string;\n payload: T;\n}\n\nexport function envelope<T>(payload: T, id?: string): Envelope<T> {\n return {\n channel: CHANNEL,\n protocol: PROTOCOL_VERSION,\n ...(id ? { id } : {}),\n payload,\n };\n}\n\nexport function isEnvelope(data: unknown): data is Envelope<unknown> {\n return (\n typeof data === 'object' &&\n data !== null &&\n (data as { channel?: unknown }).channel === CHANNEL\n );\n}\n", "/**\n * Pure geometry for the embed launcher/panel.\n *\n * No DOM, no React \u2014 every function here is a total map from numbers/strings to\n * numbers/strings. That is deliberate: the loader attaches a CLOSED shadow root, so\n * the iframe inside it is unreachable from a test. Keeping the whole positioning\n * model in pure functions is what makes it verifiable at all; `loader.ts` is then a\n * thin applier that sets the returned box on the light-DOM host element.\n *\n * There are two MODES, and the mode decides both how the widget rests and how it opens:\n *\n * dock (default) \u2014 the launcher is a TAB flush against a side edge, and opening\n * gives a full-height side panel on that edge. This is the shape\n * most hosts want: it reads as part of the app, not as a bubble\n * floating over it.\n * float \u2014 the launcher is a CIRCLE the user can put anywhere, and opening\n * gives a panel anchored near it.\n *\n * In float the position is FREE. A corner is only recorded when the user drops the\n * launcher near one (see `snapOrFree`), because a snap that always fires isn't a\n * convenience, it's a constraint \u2014 you could never place the thing mid-edge. Recording\n * the corner when it IS snapped is what lets it stay in that corner across a viewport\n * resize, where a raw x/y would drift.\n */\n\nexport type Corner = 'tl' | 'tr' | 'bl' | 'br';\nexport type DockSide = 'left' | 'right';\nexport type Mode = 'float' | 'dock';\n\nexport interface EmbedGeometry {\n mode: Mode;\n /** float: the corner it snapped to, or null when freely placed. */\n corner: Corner | null;\n /** float: free launcher top-left, used only when `corner` is null. */\n x: number;\n y: number;\n floatW: number;\n floatH: number;\n dockSide: DockSide;\n /** dock: the tab's top offset along its edge. */\n tabY: number;\n dockW: number;\n}\n\n/**\n * A set of fixed-position box properties to apply to the host `<div>`.\n * Exactly two of top/right/bottom/left carry a px value; the other two are 'auto',\n * so the box stays pinned to the edges it grows away from.\n */\nexport interface BoxStyle {\n top: string;\n right: string;\n bottom: string;\n left: string;\n width: string;\n height: string;\n}\n\nexport const MARGIN = 20;\nexport const LAUNCHER = 56;\nexport const PILL_W = 180;\n/** How close to a corner a drop must land before it snaps. Generous enough to feel\n * magnetic, small enough that the middle of an edge is still reachable. */\nexport const SNAP_RADIUS = 96;\n/**\n * The docked launcher tab.\n *\n * Wide enough to carry the logo mark, and clearly TALLER than it is wide so the shape\n * still reads as a tab on the page edge. The ratio is the thing that matters: at a\n * near-square size any inner radius rounds it into a lozenge, which is what \"squished\n * circle\" looks like.\n */\nexport const TAB_W = 44;\nexport const TAB_H = 132;\n/**\n * How close to a SIDE edge a drop must land to become a docked tab. Dragging the tab\n * away from the edge is therefore how you float it, and dragging the circle back to an\n * edge is how you dock it \u2014 the placement follows where you put the thing, so there is\n * no mode switch to go hunting for.\n */\nexport const EDGE_DOCK = 64;\nexport const DEFAULT_FLOAT_W = 420;\nexport const DEFAULT_FLOAT_H = 640;\nexport const MIN_FLOAT_W = 320;\nexport const MIN_FLOAT_H = 420;\nexport const DEFAULT_DOCK_W = 600;\nexport const MIN_DOCK_W = 320;\n\nconst CORNERS: readonly Corner[] = ['tl', 'tr', 'bl', 'br'];\n\nexport function defaultGeometry(): EmbedGeometry {\n return {\n // Dock is the default: a side panel reads as part of the host app.\n mode: 'dock',\n corner: 'br',\n x: 0,\n y: 0,\n floatW: DEFAULT_FLOAT_W,\n floatH: DEFAULT_FLOAT_H,\n dockSide: 'right',\n tabY: 0, // 0 => centre it (see tabTop)\n dockW: DEFAULT_DOCK_W,\n };\n}\n\nfunction clamp(n: number, lo: number, hi: number): number {\n return Math.min(Math.max(n, lo), hi);\n}\n\n/** Where a snapped corner puts the launcher's top-left. */\nfunction cornerOrigin(\n corner: Corner,\n w: number,\n h: number,\n vw: number,\n vh: number,\n): { x: number; y: number } {\n return {\n x: corner[1] === 'l' ? MARGIN : vw - MARGIN - w,\n y: corner[0] === 't' ? MARGIN : vh - MARGIN - h,\n };\n}\n\n/** The launcher's top-left in float mode: its snapped corner, else its free spot. */\nexport function launcherOrigin(\n g: EmbedGeometry,\n vw: number,\n vh: number,\n): { x: number; y: number } {\n if (g.corner) return cornerOrigin(g.corner, LAUNCHER, LAUNCHER, vw, vh);\n return {\n x: clamp(g.x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER)),\n y: clamp(g.y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER)),\n };\n}\n\n/**\n * Which way things grow from the launcher: toward the viewport centre.\n *\n * Distinct from `geometry.corner` on purpose \u2014 a FREELY placed launcher has no corner,\n * but its pill, menu and panel still have to open inward or they'd run off the page.\n */\nexport function growthCorner(g: EmbedGeometry, vw: number, vh: number): Corner {\n if (g.corner) return g.corner;\n const o = launcherOrigin(g, vw, vh);\n return nearestCorner(o.x + LAUNCHER / 2, o.y + LAUNCHER / 2, vw, vh);\n}\n\n/** Which quadrant a point is in. */\nexport function nearestCorner(\n cx: number,\n cy: number,\n vw: number,\n vh: number,\n): Corner {\n return `${cy < vh / 2 ? 't' : 'b'}${cx < vw / 2 ? 'l' : 'r'}` as Corner;\n}\n\n/**\n * Resolve a drop: snap to a corner only if it landed NEAR one, else keep it where the\n * user put it (clamped on-screen). Returns the fields to merge into the geometry.\n */\nexport function snapOrFree(\n x: number,\n y: number,\n vw: number,\n vh: number,\n): { corner: Corner | null; x: number; y: number } {\n const cx = clamp(x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER));\n const cy = clamp(y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER));\n for (const corner of CORNERS) {\n const o = cornerOrigin(corner, LAUNCHER, LAUNCHER, vw, vh);\n if (Math.hypot(cx - o.x, cy - o.y) <= SNAP_RADIUS) {\n return { corner, x: cx, y: cy };\n }\n }\n return { corner: null, x: cx, y: cy };\n}\n\n/** Is this box close enough to a side edge to be docked? The live half of the drag. */\nexport function edgeDock(x: number, w: number, vw: number): DockSide | null {\n if (x <= EDGE_DOCK) return 'left';\n if (x + w >= vw - EDGE_DOCK) return 'right';\n return null;\n}\n\n/**\n * The point a drag MOVES \u2014 the widget's logical anchor, not its painted box.\n *\n * They differ once the panel is open: the box is the whole panel, but what the geometry\n * stores (and what a corner drop resolves) is the launcher's own origin. Dragging by the\n * box's top-left would shift the widget by the panel's size the moment you grabbed it.\n */\nexport function dragAnchor(\n g: EmbedGeometry,\n vw: number,\n vh: number,\n): { x: number; y: number } {\n if (g.mode === 'dock') {\n return { x: g.dockSide === 'left' ? 0 : vw - TAB_W, y: tabTop(g, vh) };\n }\n return launcherOrigin(g, vw, vh);\n}\n\n/**\n * The widget's painted horizontal extent for the state it is in.\n *\n * Derived, not measured: `getBoundingClientRect` is unavailable before layout (and is\n * all-zeros under jsdom), and the loader already knows every input. This is what the\n * edge test reads \u2014 \"dragged to the side\" is about the box you can see touching the\n * edge, which for an open panel is nowhere near its anchor.\n */\nexport function visibleBox(\n g: EmbedGeometry,\n open: boolean,\n vw: number,\n vh: number,\n): { x: number; w: number } {\n if (g.mode === 'dock') {\n const w = open\n ? Math.min(Math.max(g.dockW, MIN_DOCK_W), Math.max(0, vw - 2 * MARGIN))\n : TAB_W;\n return { x: g.dockSide === 'left' ? 0 : vw - w, w };\n }\n const o = launcherOrigin(g, vw, vh);\n if (!open) return { x: o.x, w: LAUNCHER };\n const { w } = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);\n // A right-growing panel hangs LEFT off the launcher, so its left edge is the\n // launcher's right edge minus the panel width.\n return {\n x: growthCorner(g, vw, vh)[1] === 'r' ? o.x + LAUNCHER - w : o.x,\n w,\n };\n}\n\n/**\n * Resolve a drop into a PLACEMENT \u2014 which mode the widget lands in, and where.\n *\n * Two different measurements, deliberately:\n * - the EDGE test uses the widget's visible box, because \"dragged to the side\" is\n * about what you can see touching the edge. Testing the anchor instead means a\n * 420px-wide panel whose right edge is already against the screen refuses to dock,\n * since its anchor is still 400px away.\n * - the CORNER test uses the anchor, because that is what a corner placement stores.\n *\n * Priority matters, and corner beats edge on purpose: every corner is also within reach\n * of a side edge, so checking the edge first would make corner-snapping unreachable and\n * quietly turn the floating circle into a tab whenever you aimed for a corner.\n */\nexport function placeDrop(\n anchorX: number,\n anchorY: number,\n boxLeft: number,\n boxW: number,\n vw: number,\n vh: number,\n):\n | { mode: 'float'; corner: Corner | null; x: number; y: number }\n | { mode: 'dock'; dockSide: DockSide; tabY: number } {\n const snapped = snapOrFree(anchorX, anchorY, vw, vh);\n if (snapped.corner) return { mode: 'float', ...snapped };\n const side = edgeDock(boxLeft, boxW, vw);\n if (side) {\n return {\n mode: 'dock',\n dockSide: side,\n tabY: clamp(anchorY, 0, Math.max(0, vh - TAB_H)),\n };\n }\n return { mode: 'float', ...snapped };\n}\n\nexport function dockSideForCorner(corner: Corner): DockSide {\n return corner[1] === 'l' ? 'left' : 'right';\n}\n\nexport function effectiveDockSide(g: EmbedGeometry): DockSide {\n return g.dockSide;\n}\n\n/**\n * Fit a size inside the viewport.\n *\n * Order matters: the minimum is applied FIRST and the viewport cap LAST, so on a\n * viewport smaller than the minimum the VIEWPORT wins. That is the whole point \u2014 a\n * panel that honoured its minimum past the viewport edge would hang off the page.\n */\nexport function clampSize(\n w: number,\n h: number,\n vw: number,\n vh: number,\n minW: number,\n minH: number,\n): { w: number; h: number } {\n return {\n w: Math.min(Math.max(w, minW), Math.max(0, vw - 2 * MARGIN)),\n h: Math.min(Math.max(h, minH), Math.max(0, vh - 2 * MARGIN)),\n };\n}\n\n/**\n * Apply a resize drag to the float size. The grip sits on the panel's INNER corner\n * (the one facing viewport centre), so dragging it away from the anchored corner\n * grows the panel \u2014 which flips the sign of the delta per corner. Unclamped.\n */\nexport function applyResizeDelta(\n corner: Corner,\n startW: number,\n startH: number,\n dx: number,\n dy: number,\n): { w: number; h: number } {\n const wSign = corner[1] === 'r' ? -1 : 1; // right-anchored grows on -dx\n const hSign = corner[0] === 'b' ? -1 : 1; // bottom-anchored grows on -dy\n return { w: startW + wSign * dx, h: startH + hSign * dy };\n}\n\n/** Widen/narrow a docked panel by dragging its inner edge. */\nexport function applyDockResizeDelta(\n side: DockSide,\n startW: number,\n dx: number,\n): number {\n return side === 'right' ? startW - dx : startW + dx;\n}\n\nexport function parseGeometry(raw: string | null): EmbedGeometry | null {\n if (!raw) return null;\n try {\n const p = JSON.parse(raw) as Partial<EmbedGeometry>;\n // Pre-mode values (and the even older {right,bottom}) are dropped rather than\n // guessed at \u2014 there is no honest mapping from them onto this model.\n if (!p || (p.mode !== 'float' && p.mode !== 'dock')) return null;\n const d = defaultGeometry();\n const num = (v: unknown, dv: number) => (typeof v === 'number' ? v : dv);\n return {\n mode: p.mode,\n corner: CORNERS.includes(p.corner as Corner)\n ? (p.corner as Corner)\n : null,\n x: num(p.x, d.x),\n y: num(p.y, d.y),\n floatW: num(p.floatW, d.floatW),\n floatH: num(p.floatH, d.floatH),\n dockSide: p.dockSide === 'left' ? 'left' : 'right',\n tabY: num(p.tabY, d.tabY),\n dockW: num(p.dockW, d.dockW),\n };\n } catch {\n return null;\n }\n}\n\nexport function serializeGeometry(g: EmbedGeometry): string {\n return JSON.stringify(g);\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 box builders \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nconst px = (n: number) => `${n}px`;\n\n/**\n * Pin the box by the two edges nearest the growth corner, so it grows inward.\n * Works for a snapped launcher and a freely-placed one alike: the anchor is always\n * the launcher's own top-left plus its size, expressed from whichever edges it grows\n * away from.\n */\nfunction boxAt(\n ox: number,\n oy: number,\n w: number,\n h: number,\n corner: Corner,\n vw: number,\n vh: number,\n): BoxStyle {\n const right = corner[1] === 'r';\n const bottom = corner[0] === 'b';\n // The anchored edges are measured from the LAUNCHER's own far side, so the launcher\n // stays put while the box grows past it...\n const rawX = right ? vw - (ox + LAUNCHER) : ox;\n const rawY = bottom ? vh - (oy + LAUNCHER) : oy;\n // ...but only as far as the viewport allows. Clamping the SIZE is not enough: a\n // launcher parked mid-page opens a 640px panel that grows straight off the top, and\n // the panel's header \u2014 the only way to close, dock or drag it \u2014 goes with it. So the\n // pinned offset is clamped too, which shifts the panel back into view while keeping\n // it as close to the launcher as it can be.\n const x = clamp(rawX, MARGIN, Math.max(MARGIN, vw - MARGIN - w));\n const y = clamp(rawY, MARGIN, Math.max(MARGIN, vh - MARGIN - h));\n return {\n top: bottom ? 'auto' : px(y),\n bottom: bottom ? px(y) : 'auto',\n left: right ? 'auto' : px(x),\n right: right ? px(x) : 'auto',\n width: px(w),\n height: px(h),\n };\n}\n\n/** The tab's top offset; a stored 0 means \"centre it\". */\nfunction tabTop(g: EmbedGeometry, vh: number): number {\n const max = Math.max(0, vh - TAB_H);\n return clamp(g.tabY || Math.round((vh - TAB_H) / 2), 0, max);\n}\n\n/** The docked launcher: a slim tab flush against its edge (no margin \u2014 it hugs). */\nexport function tabBox(g: EmbedGeometry, vw: number, vh: number): BoxStyle {\n const left = g.dockSide === 'left';\n return {\n top: px(tabTop(g, vh)),\n bottom: 'auto',\n left: left ? '0px' : 'auto',\n right: left ? 'auto' : '0px',\n width: px(TAB_W),\n height: px(TAB_H),\n };\n}\n\n/**\n * ANY float-mode box \u2014 the circle, the hover pill, the menu region, the open panel.\n *\n * They are all the same shape: a box of size w\u00D7h anchored at the launcher and growing\n * inward. One function rather than four wrappers, because this module is bundled into\n * the per-page loader stub and four near-identical bodies is pure weight.\n */\nexport function floatChrome(\n g: EmbedGeometry,\n w: number,\n h: number,\n vw: number,\n vh: number,\n /**\n * Override the growth direction. Used to FREEZE it for the length of a drag: growth\n * is derived from which half of the viewport the launcher is in, so dragging an open\n * panel across the middle would otherwise flip the side it grows from and jump the\n * panel by its own width. Held still, it tracks the cursor.\n */\n growth?: Corner,\n): BoxStyle {\n const o = launcherOrigin(g, vw, vh);\n return boxAt(o.x, o.y, w, h, growth ?? growthCorner(g, vw, vh), vw, vh);\n}\n\n/** The region the docked tab grows into for its context menu \u2014 hugging its edge. */\nexport function dockChrome(\n g: EmbedGeometry,\n w: number,\n h: number,\n vw: number,\n vh: number,\n): BoxStyle {\n const left = g.dockSide === 'left';\n return {\n top: px(tabTop(g, vh)),\n bottom: 'auto',\n left: left ? '0px' : 'auto',\n right: left ? 'auto' : '0px',\n width: px(w),\n height: px(h),\n };\n}\n\n/** The collapsed launcher for the current mode: dock \u2192 tab, float \u2192 circle. */\nexport function launcherBox(\n g: EmbedGeometry,\n vw: number,\n vh: number,\n): BoxStyle {\n return g.mode === 'dock'\n ? tabBox(g, vw, vh)\n : floatChrome(g, LAUNCHER, LAUNCHER, vw, vh);\n}\n\n/** The floating open panel, anchored at the launcher and clamped on-screen. */\nexport function floatBox(\n g: EmbedGeometry,\n vw: number,\n vh: number,\n growth?: Corner,\n): BoxStyle {\n const f = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);\n return floatChrome(g, f.w, f.h, vw, vh, growth);\n}\n\n/** Full-height side panel flush against its edge. */\nexport function dockBox(g: EmbedGeometry, vw: number, _vh: number): BoxStyle {\n const w = Math.min(\n Math.max(g.dockW, MIN_DOCK_W),\n Math.max(0, vw - 2 * MARGIN),\n );\n const left = g.dockSide === 'left';\n return {\n top: '0px',\n bottom: '0px',\n height: 'auto',\n left: left ? '0px' : 'auto',\n right: left ? 'auto' : '0px',\n width: px(w),\n };\n}\n", "import {\n CHANNEL,\n PROTOCOL_VERSION,\n envelope,\n isEnvelope,\n type HostToWidget,\n type MatterfactEvent,\n type WidgetToHost,\n} from './protocol';\n// Type-only: erased at build, so it does NOT pull the lazy ./context chunk eagerly.\nimport type { PageContextProvider } from './context';\nimport {\n applyDockResizeDelta,\n applyResizeDelta,\n clampSize,\n defaultGeometry,\n dockBox,\n floatBox,\n growthCorner,\n tabBox,\n dockChrome,\n floatChrome,\n parseGeometry,\n serializeGeometry,\n dragAnchor,\n visibleBox,\n edgeDock,\n placeDrop,\n LAUNCHER,\n MARGIN,\n MIN_DOCK_W,\n MIN_FLOAT_H,\n MIN_FLOAT_W,\n DEFAULT_DOCK_W,\n type BoxStyle,\n type Corner,\n type EmbedGeometry,\n} from './geometry';\n\n/**\n * The matterfact embed loader.\n *\n * <script src=\"https://cdn.matterfact.com/embed.js\"\n * data-key=\"pk_live_acme_...\"\n * defer></script>\n *\n * This file runs on EVERY page load of a customer's site, so it is deliberately\n * tiny and deliberately dumb:\n *\n * - zero dependencies\n * - it does NOT read the DOM. The snapshot/activity code is a separate chunk,\n * fetched only when the user actually opens the chat. A customer should pay\n * nothing for a widget nobody clicked.\n * - it does NOT bundle React, or a CSS framework, or an RPC library.\n *\n * `iframe-resizer` is the thing you will be tempted to reach for here. It is\n * GPL-3.0 and cannot ship in this file. The ResizeObserver bridge below is the\n * fifteen lines it would have cost you anyway.\n */\n\n/** Returns a token this matterfact deployment's auth provider trusts (Firebase idToken,\n * Entra token, \u2026). May be async; return null when there's no signed-in user. */\nexport type AuthTokenProvider = () => string | null | Promise<string | null>;\n\nexport interface LoaderConfig {\n publishableKey: string;\n /** Origin of the widget. Overridable for dev/self-hosted; defaults to production. */\n origin: string;\n theme: 'light' | 'dark' | 'auto';\n /** Optional label identifying THIS embedding, when one app embeds the agent in more\n * than one place (e.g. \"orders\" vs \"support\"). Stamped on runs/threads so usage and\n * history can be told apart per surface. Free-form; the app_id (from the session)\n * is the trusted half. */\n surface: string;\n /**\n * OPTIONAL host-auth passthrough, for TRUSTED first-party embeds (the admin portal,\n * EMC). When set, the host page already holds a token this deployment's auth provider\n * trusts (its Firebase idToken, an Entra token, \u2026); the loader hands it to the widget\n * on `widget.needsAuth` instead of opening a sign-in popup. The backend still verifies\n * the token, so identity can't be forged \u2014 this only skips a redundant second sign-in.\n * Called on demand (so tokens can refresh). Absent \u21D2 the popup/inline sign-in flow.\n * The `<script>` loader has no config here; it reads window.matterfact.getEmbedAuthToken\n * instead (see provideAuth).\n */\n authTokenProvider?: AuthTokenProvider;\n /**\n * OPTIONAL pull callback for DECLARED page context, for a host whose context changes\n * without a URL navigation (a virtualized SPA: team switch, filter edit, selection). The\n * widget calls it at turn-assembly time (via `widget.requestContext`), so every turn\n * carries current context instead of context as of the last route change. Symmetric with\n * `authTokenProvider`. The `<script>` loader has no config here; it reads\n * `window.matterfact.getPageContext` instead (see resolveDeclaredContext in context.ts).\n * Absent \u21D2 falls back to the static `window.matterfact.context`.\n */\n pageContextProvider?: PageContextProvider;\n /**\n * Render INTO this element instead of floating in the corner \u2014 for a host that has its\n * own side panel, tool drawer or tab and wants the agent to live there.\n *\n * This inverts who owns the chrome. In corner mode the widget owns everything: it draws\n * its own launcher and it tells us how big to be (setOpen/setDocked/resize). Inside\n * someone else's panel, both are theirs \u2014 a widget that resized its host's drawer, or\n * drew a second floating bubble inside it, is just broken. So here we fill the\n * container, ignore the size messages, and the widget skips the launcher and renders\n * the chat directly.\n *\n * The host also owns visibility: showing and hiding their own panel IS the open/close\n * control, so there is nothing for us to add.\n */\n container?: HTMLElement | null;\n /**\n * Turn off ALL host-page observation \u2014 no `host.context`, no `host.focus`, no\n * `host.snapshot`, no `host.activity`, no `host.artifactGrants`. Not a filter on what\n * gets sent: when this is false the loader never installs the DOM/nav/focus/activity\n * observers in the first place, and an inbound `widget.requestSnapshot` is ignored\n * rather than answered. Default `true` \u2014 seeing the page is the widget's whole value\n * proposition; this is the escape hatch for a host that wants agent chat with nothing\n * about the page ever reaching it.\n */\n pageContext?: boolean;\n /**\n * Force dev mode (see `devRequested()` below) without needing `?mfdev=1` on the host\n * URL. This is the programmatic equivalent of that trigger, not a replacement for it \u2014\n * the URL param still works untouched; `mount()` ORs the two together.\n */\n dev?: boolean;\n}\n\nconst DEFAULT_ORIGIN = 'https://app.matterfact.com';\n\n/**\n * Has someone asked to see what the widget is doing? `?mfdev=1` on the HOST page.\n *\n * Driven by the host's own URL rather than a `data-` attribute on purpose: an engineer\n * debugging a page they didn't write can add a query param, but they cannot redeploy the\n * customer's site to change a script tag. It's also ephemeral \u2014 it lasts exactly as long\n * as that URL, so nobody can leave it on by accident.\n *\n * This only surfaces what the widget already collects FROM THE PAGE IT'S ON, to the\n * person whose page it is. It grants no access: the session, the origin allowlist and\n * the route scope are all enforced server-side and none of them consult this.\n */\nfunction devRequested(): boolean {\n try {\n return new URLSearchParams(location.search).get('mfdev') === '1';\n } catch {\n return false;\n }\n}\n\nfunction readConfig(): LoaderConfig | null {\n // `document.currentScript` is null inside a module or after async execution, so\n // fall back to finding our own tag by src.\n const el =\n (document.currentScript as HTMLScriptElement | null) ??\n document.querySelector<HTMLScriptElement>('script[data-key][src*=\"embed\"]');\n\n const publishableKey = el?.dataset.key;\n if (!publishableKey) {\n console.error('[matterfact] missing data-key on the embed script tag');\n return null;\n }\n // `data-container=\"#panel\"` renders into the host's own element instead of the\n // corner. A selector rather than an element, because a script tag can't hand us a\n // reference \u2014 and if it doesn't resolve we fall back to the corner rather than\n // vanish: a widget nobody can find looks identical to a broken install.\n const sel = el?.dataset.container;\n const container = sel ? document.querySelector<HTMLElement>(sel) : null;\n if (sel && !container) {\n console.error(\n `[matterfact] data-container=\"${sel}\" matched nothing; falling back to the corner`,\n );\n }\n\n // `data-page-context=\"off\"` (also \"false\" / \"0\") disables all page observation.\n // Anything else \u2014 including the attribute being absent \u2014 leaves it on. Lowercased\n // once before comparing: a privacy toggle that silently stays ON for \"Off\" or\n // \"FALSE\" (a customer's templating/CMS can easily produce either, unintentionally)\n // is exactly the wrong direction for this to fail in.\n const pageContextAttr = el?.dataset.pageContext?.toLowerCase();\n\n return {\n publishableKey,\n origin: el?.dataset.origin || DEFAULT_ORIGIN,\n theme: (el?.dataset.theme as LoaderConfig['theme']) || 'auto',\n surface: el?.dataset.surface || '',\n container,\n pageContext:\n pageContextAttr !== 'off' &&\n pageContextAttr !== 'false' &&\n pageContextAttr !== '0',\n // No `data-dev` \u2014 see the `dev` field's doc comment: the URL trigger is the\n // point for the script-tag path, so there is deliberately no script-tag knob here.\n // No `data-actions` either: the action policy lives entirely in the lazy chunk,\n // read straight off `window.matterfact.hoist.actions` by hoist-runtime.ts's\n // readActionsConfig() \u2014 see LoaderConfig's class doc and MatterfactAgentProps.actions\n // (react.tsx) for the one remaining way to SET that global programmatically.\n };\n}\n\nconst POS_KEY = 'mf.embed.pos';\n\nclass EmbedHost {\n private iframe: HTMLIFrameElement | null = null;\n private shadow: ShadowRoot | null = null;\n /** Buffered until the widget says it's listening \u2014 postMessage before load is dropped silently. */\n private queue: HostToWidget[] = [];\n private ready = false;\n private open = false;\n /** The persisted box: launcher corner, float size, dock state/side/width. */\n private geo: EmbedGeometry = defaultGeometry();\n /** Float size at resizeStart; resizeMove deltas apply against it (they're cumulative). */\n private resizeBase: { w: number; h: number } | null = null;\n /** Docked width at resizeStart \u2014 the docked panel resizes in one axis only. */\n private dockBase = DEFAULT_DOCK_W;\n /** The widget's logical anchor when the drag began, and where it is now. Deltas arrive\n * cumulative from the press, so the current spot is simply origin + delta \u2014 no need to\n * read the box back, which stops being the thing we're moving the moment it docks. */\n private dragFrom: { x: number; y: number } | null = null;\n private dragAt = { x: 0, y: 0 };\n /** The painted box when the drag began \u2014 the edge test measures what you can SEE\n * touching the screen edge, which is not the anchor once the panel is open. */\n private dragBox = { x: 0, w: 0 };\n /** Growth direction frozen for the drag \u2014 see floatChrome's `growth` param. */\n private dragGrowth: Corner | null = null;\n /**\n * Has this drag been clear of the dock band yet?\n *\n * The resting position is INSIDE the band \u2014 a corner-anchored widget sits MARGIN\n * (20px) from the edge, well within EDGE_DOCK (64px) \u2014 so without this, picking one\n * up docked it instantly, before it had moved anywhere. Docking now requires\n * ENTERING the band rather than merely starting in it.\n */\n private dragLeftBand = false;\n /** Loaded on first open. Holds everything that touches the customer's DOM. */\n private context: Promise<typeof import('./context')> | null = null;\n /** The host element; kept so `destroy()` can remove it (React lifecycle). */\n private hostEl: HTMLDivElement | null = null;\n /** Rendering into the host's own element: they own the box, the chrome and visibility. */\n private readonly inline: boolean;\n /** Host-auth storm guard (see provideAuth). Counts getAuthToken calls; past a cap we stop\n * calling the provider, rather than joining the storm. This host instance OUTLIVES iframe\n * reloads, so \u2014 unlike anything in the widget \u2014 the counter survives the very reload loop\n * that drives the storm. (Kept tiny on purpose: this file has a hard per-page size budget;\n * the widget side carries the windowed/retry-after-settle half.) Terse name = fewer bytes\n * in the stub; it is the host-auth request count. */\n private ac = 0;\n\n constructor(private config: LoaderConfig) {\n this.inline = !!config.container;\n }\n\n mount() {\n // A CLOSED shadow root, on a container that resets every inherited property.\n //\n // We are a guest on someone else's page. Their CSS must not reach into us, our\n // CSS must not leak out, and their JS should not be able to walk into our tree\n // by accident (closed mode: `host.shadowRoot` is null). `contain` stops our\n // layout/paint from invalidating theirs.\n const host = document.createElement('div');\n this.hostEl = host;\n host.id = 'matterfact-embed';\n\n if (this.inline) {\n // The container's box IS the widget's box. No fixed positioning, no z-index\n // scramble, no drag: where this sits is the host's layout problem, which is the\n // entire point of asking for it.\n host.style.cssText = [\n 'all: initial',\n 'position: relative',\n 'display: block',\n 'width: 100%',\n 'height: 100%',\n 'contain: layout style',\n ].join(';');\n this.config.container!.appendChild(host);\n } else {\n this.geo = this.readGeometry();\n host.style.cssText = [\n 'all: initial',\n 'position: fixed',\n // Below the max so a host that genuinely needs to cover us (a modal, a cookie\n // banner they are legally obliged to show) still can.\n 'z-index: 2147483000',\n 'contain: layout style',\n // The host element owns the BOX now (position AND size); the iframe fills it.\n // Transitioning the box is what makes the corner snap and the open/dock read as\n // motion rather than a jump. Suppressed during drag/resize (see those handlers).\n 'transition: width .18s ease, height .18s ease, top .18s ease,' +\n ' right .18s ease, bottom .18s ease, left .18s ease',\n ].join(';');\n document.body.appendChild(host);\n }\n\n this.shadow = host.attachShadow({ mode: 'closed' });\n\n const style = document.createElement('style');\n // Shared prefix, then only what differs. This ships on every page load and the stub\n // has ~no headroom, so the two modes must not each carry a whole stylesheet.\n //\n // Inline fills the host's box and adds nothing: no radius, no shadow. Their panel\n // already has whatever chrome they chose; ours would sit inside it looking like a\n // mistake.\n // The iframe simply FILLS the host element, in both modes. The host owns every\n // dimension, so all the geometry lives in one place (and stays observable in the\n // light DOM \u2014 this shadow root is closed, so nothing in here can be inspected).\n // Only the corner treatment differs, switched off the host's data-mode: a circle\n // while it's the launcher, a card once it's a pill, menu or panel.\n //\n // Hairline ring + soft ambient: the widget must read as a crisp shape on ANY host\n // background, including pure white where a shadow alone bleeds.\n // Only the OPEN panel is a surface of ours. While collapsed the widget draws its\n // own shapes \u2014 the tab, the circle, the menu \u2014 onto the host's page, so the iframe\n // must not paint a card behind them: the host box grows to make room for the menu,\n // and a background there shows up as a slab hanging off the launcher.\n style.textContent =\n ':host{all:initial}' +\n 'iframe{border:0;display:block;width:100%;height:100%;background:transparent}' +\n (this.inline\n ? ''\n : // color-scheme only on the OPEN panel \u2014 on a collapsed launcher it makes the\n // UA paint an opaque canvas base behind our shapes.\n ':host([data-mode=\"float\"]) iframe,:host([data-mode=\"dock\"]) iframe' +\n '{color-scheme:light dark}' +\n ':host([data-mode=\"float\"]) iframe{border-radius:12px;' +\n 'box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029}' +\n ':host([data-mode=\"dock\"]) iframe{box-shadow:0 0 0 1px #00000014,' +\n '0 8px 40px #00000029}' +\n ':host([data-mode=\"dock\"][data-flush=\"right\"]) iframe' +\n '{border-radius:12px 0 0 12px}' +\n ':host([data-mode=\"dock\"][data-flush=\"left\"]) iframe' +\n '{border-radius:0 12px 12px 0}');\n this.shadow.appendChild(style);\n\n const iframe = document.createElement('iframe');\n iframe.title = 'matterfact assistant';\n //\n // `allow-same-origin` is REQUIRED, and leaving it out is the trap.\n //\n // Without it the framed document gets an OPAQUE origin: its postMessage arrives\n // as `event.origin === \"null\"`, so our own origin check rejects the handshake and\n // the bridge silently never connects. It also denies the widget any storage at\n // all \u2014 no session, no auth, ever.\n //\n // The familiar warning \u2014 \"allow-scripts + allow-same-origin lets the frame escape\n // its sandbox\" \u2014 is about a frame that is SAME-ORIGIN WITH THE FRAMER, which can\n // then reach up and delete its own sandbox attribute. Ours is cross-origin to the\n // host by construction (that is the entire security model), so it cannot touch\n // this element. What the sandbox still buys us is real: no top-level navigation,\n // no downloads, no pointer lock.\n // `allow-downloads` lets the chat save a file in place. Every download path\n // is an in-place `<a download>`.click(); WITHOUT this flag a sandboxed frame\n // blocks that and the browser falls back to opening the file in a new tab.\n // It is a sandbox flag, not a permissions-policy `allow` token.\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads',\n );\n // Powerful features need an explicit permissions-policy grant: a cross-origin\n // frame is granted NONE by default, so without `allow` they are hard-blocked\n // no matter what the user approves.\n // - microphone: dictation's getUserMedia.\n // - clipboard-write: the \"Copy\" button on a chat message calls\n // navigator.clipboard.writeText, which throws NotAllowedError without it.\n // The bare tokens scope each grant to the iframe's own origin (the app), not\n // the host page. A host page can still deny either via its own\n // Permissions-Policy response header \u2014 outside our control, which is why the\n // in-app copy path also degrades gracefully rather than throwing.\n iframe.setAttribute('allow', 'microphone; clipboard-write');\n iframe.src =\n `${this.config.origin}/embed/chat?k=${encodeURIComponent(\n this.config.publishableKey,\n )}&o=${encodeURIComponent(location.origin)}` +\n (this.config.surface\n ? `&s=${encodeURIComponent(this.config.surface)}`\n : '') +\n // Either trigger works: the URL param (no redeploy needed) OR the config's\n // programmatic `dev` (the React prop). See both doc comments above.\n (devRequested() || this.config.dev ? '&dev=1' : '') +\n (this.inline ? '&inline=1' : '');\n this.iframe = iframe;\n this.shadow.appendChild(iframe);\n\n window.addEventListener('message', this.onMessage);\n if (!this.inline) {\n // A viewport change can strand a saved box off-page (a laptop undocked from an\n // external monitor is the common one), so re-clamp and re-anchor on resize.\n window.addEventListener('resize', this.onViewportResize);\n this.place();\n }\n }\n\n /**\n * Every message is checked twice, on every single message \u2014 not once at setup.\n *\n * `channel` is a namespace, not a boundary. The origin and source checks are the\n * boundary: any frame on the page can postMessage us, and a page with an ad iframe\n * on it has plenty of frames.\n */\n private onMessage = (event: MessageEvent) => {\n if (event.origin !== this.config.origin) return;\n if (event.source !== this.iframe?.contentWindow) return;\n if (!isEnvelope(event.data)) return;\n\n this.handle(event.data.payload as WidgetToHost);\n };\n\n /**\n * Test seam. The shadow root is CLOSED, so a test cannot reach the iframe to forge a\n * source-valid MessageEvent \u2014 this routes a message through the identical logic\n * without weakening the origin/source checks above, which stay the only real door.\n */\n __testHandle(msg: WidgetToHost) {\n this.handle(msg);\n }\n\n /**\n * The eager stub's own emit-and-swallow for the host's `onEvent` telemetry hook.\n * Deliberately does NOT import the registry's `emitEvent`: pulling the lazy ./context\n * chunk (where the registry lives) into the size-budgeted stub would blow it, so this\n * ~5-line duplication across the bundle boundary is intentional. Host telemetry is\n * host code \u2014 a throw here must never break the widget.\n */\n private emit(e: MatterfactEvent) {\n const cb = (\n globalThis as { matterfact?: { onEvent?: (e: MatterfactEvent) => void } }\n ).matterfact?.onEvent;\n if (typeof cb !== 'function') return;\n try {\n cb(e);\n } catch {\n /* host telemetry must never break the widget */\n }\n }\n\n private handle(msg: WidgetToHost) {\n switch (msg.type) {\n case 'widget.ready':\n this.ready = true;\n this.send({\n type: 'host.ready',\n protocol: PROTOCOL_VERSION,\n origin: location.origin,\n });\n // Theme NOW, not on first open. It used to ride in with the lazy context\n // chunk, which is only fetched when the chat is opened \u2014 so a collapsed\n // launcher sat in the wrong colours until you interacted with it.\n this.send({ type: 'host.theme', mode: this.themeMode() });\n this.flush();\n // Inline has no \"open\" event to hang this off \u2014 the chat is visible the moment\n // the host renders their panel, so the eye has to load now or the agent is\n // blind to the page for the whole session. The corner keeps its lazy load: a\n // page whose visitors never click still pays nothing.\n if (this.inline) void this.loadContext();\n this.emit({ type: 'ready' });\n break;\n\n case 'widget.setOpen':\n // Host telemetry sees the open/close moment regardless of placement (the inline\n // guard below only skips the popup SIZING, which is meaningless inline).\n this.emit({ type: msg.open ? 'open' : 'close' });\n // Inline: the host's panel IS the open/close control. There's no popup to size.\n if (this.inline) break;\n this.open = msg.open;\n this.place();\n // The DOM code is fetched HERE \u2014 the first time someone actually opens the\n // chat \u2014 and never on a page where they don't.\n if (msg.open) void this.loadContext();\n break;\n\n case 'widget.setMode':\n // Inline: the host owns the box; float/dock is not ours to choose.\n if (this.inline) break;\n this.geo.mode = msg.mode;\n this.writeGeometry(this.geo);\n this.place();\n break;\n\n case 'widget.setDockSide':\n if (this.inline) break;\n this.geo.dockSide = msg.side;\n this.writeGeometry(this.geo);\n this.place();\n break;\n\n case 'widget.snapCorner':\n if (this.inline) break;\n // Snapping is a FLOAT affordance; the picker only shows in float mode.\n this.geo.corner = msg.corner;\n this.writeGeometry(this.geo);\n this.place();\n break;\n\n case 'widget.setLauncherRegion': {\n // Only meaningful while collapsed \u2014 open, the panel is already bigger than any\n // pill or menu, and growing the box would fight the panel's own size.\n if (this.inline || this.open) break;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n if (msg.w <= LAUNCHER && msg.h <= LAUNCHER) {\n // Back to the bare launcher \u2014 place() already knows how to draw it.\n this.place();\n } else {\n const region =\n this.geo.mode === 'dock'\n ? dockChrome(this.geo, msg.w, msg.h, vw, vh)\n : floatChrome(this.geo, msg.w, msg.h, vw, vh);\n this.applyBox(region, msg.h > LAUNCHER ? 'menu' : 'pill');\n }\n break;\n }\n\n case 'widget.resize':\n // Inline: the container's height is the host's business, and an auto-grow here\n // would fight their layout every time the transcript got longer.\n if (this.inline) break;\n // Content height auto-grow, for the FLOATING panel only. Docked, the edges own\n // the height; and a manual resize has already written floatH, which place()\n // would restore on the next call anyway.\n if (this.hostEl && this.open && this.geo.mode === 'float') {\n this.hostEl.style.height = `${Math.min(\n msg.height,\n Math.max(0, window.innerHeight - 2 * MARGIN),\n )}px`;\n }\n break;\n\n case 'widget.resizeStart':\n if (this.inline) break;\n this.resizeBase = { w: this.geo.floatW, h: this.geo.floatH };\n this.dockBase = this.geo.dockW;\n // The box must track the pointer exactly; an eased transition lags behind it.\n if (this.hostEl) this.hostEl.style.transition = 'none';\n break;\n\n case 'widget.resizeMove': {\n if (this.inline || !this.resizeBase) break;\n if (this.geo.mode === 'dock') {\n // Docked, only the WIDTH is the user's \u2014 the edges own the height.\n const w = applyDockResizeDelta(\n this.geo.dockSide,\n this.dockBase,\n msg.dx,\n );\n this.geo.dockW = Math.min(\n Math.max(w, MIN_DOCK_W),\n Math.max(0, window.innerWidth - 2 * MARGIN),\n );\n } else {\n const next = applyResizeDelta(\n growthCorner(this.geo, window.innerWidth, window.innerHeight),\n this.resizeBase.w,\n this.resizeBase.h,\n msg.dx,\n msg.dy,\n );\n const fit = clampSize(\n next.w,\n next.h,\n window.innerWidth,\n window.innerHeight,\n MIN_FLOAT_W,\n MIN_FLOAT_H,\n );\n this.geo.floatW = fit.w;\n this.geo.floatH = fit.h;\n }\n this.place();\n break;\n }\n\n case 'widget.resizeEnd':\n if (this.inline) break;\n this.resizeBase = null;\n if (this.hostEl) this.hostEl.style.transition = '';\n this.writeGeometry(this.geo);\n break;\n\n case 'widget.requestContext':\n // The pull path for declared context \u2014 re-read it now (via getPageContext if the\n // host supplied one) and re-publish `host.context`. No-op if pageContext is off.\n void this.loadContext().then((m) => m.provideContext());\n break;\n\n case 'widget.requestSnapshot':\n // pageContext: false \u21D2 `sendSnapshot` itself no-ops (see context.ts) rather\n // than gating here \u2014 this file is the size-budgeted stub, and the check\n // costs nothing extra in the lazy chunk that already owns the DOM read.\n void this.loadContext().then((m) => m.sendSnapshot(this.send));\n break;\n\n case 'widget.readRegion':\n void this.loadContext().then((m) => m.sendRegion(msg.ref, this.send));\n break;\n\n case 'widget.callTool':\n void this.loadContext().then((m) => m.callTool(msg.call, this.send));\n break;\n\n // Everything below is LAUNCHER chrome: there is no launcher inline (the widget\n // doesn't draw one), and moving or hiding the host's own panel from inside it\n // would be us redecorating their app. An older cached loader could still be told\n // any of these by a newer widget, so they're guarded rather than assumed absent.\n case 'widget.dragStart':\n if (this.inline) break;\n this.dragFrom = dragAnchor(\n this.geo,\n window.innerWidth,\n window.innerHeight,\n );\n this.dragAt = { ...this.dragFrom };\n this.dragLeftBand = false;\n this.dragGrowth = growthCorner(\n this.geo,\n window.innerWidth,\n window.innerHeight,\n );\n this.dragBox = visibleBox(\n this.geo,\n this.open,\n window.innerWidth,\n window.innerHeight,\n );\n // Follow the pointer 1:1 while dragging; the ease is for the snap on release.\n if (this.hostEl) this.hostEl.style.transition = 'none';\n break;\n\n case 'widget.dragMove': {\n if (this.inline || !this.dragFrom) break;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n this.dragAt = {\n x: this.dragFrom.x + msg.dx,\n y: this.dragFrom.y + msg.dy,\n };\n // Switch placement DURING the drag, not on release: drag toward a side and it\n // becomes the tab under your cursor, drag away and it pops back to the circle.\n // Deliberately only the EDGE test here \u2014 running the full drop resolver would\n // also snap corners mid-gesture, yanking the widget away while you're still\n // holding it. Corners are resolved on release, where a jump reads as a snap.\n const side = edgeDock(this.dragBox.x + msg.dx, this.dragBox.w, vw);\n if (!side) this.dragLeftBand = true;\n // A tab is already docked, so it may stay docked without having left first;\n // anything else must have been clear of the band at some point in this drag.\n if (side && (this.dragLeftBand || this.geo.mode === 'dock')) {\n this.geo.mode = 'dock';\n this.geo.dockSide = side;\n this.geo.tabY = Math.max(0, this.dragAt.y);\n } else {\n this.geo.mode = 'float';\n this.geo.corner = null;\n this.geo.x = this.dragAt.x;\n this.geo.y = this.dragAt.y;\n }\n this.place();\n break;\n }\n\n case 'widget.dragEnd': {\n if (this.inline || !this.dragFrom) break;\n // The drop resolves the FULL placement \u2014 including the corner snap the live\n // pass deliberately skipped. Restoring the transition first is what makes that\n // final snap animate rather than teleport.\n const at = placeDrop(\n this.dragAt.x,\n this.dragAt.y,\n this.dragBox.x + (this.dragAt.x - this.dragFrom.x),\n this.dragBox.w,\n window.innerWidth,\n window.innerHeight,\n );\n this.geo.mode = at.mode;\n if (at.mode === 'dock') {\n this.geo.dockSide = at.dockSide;\n this.geo.tabY = at.tabY;\n } else {\n this.geo.corner = at.corner;\n this.geo.x = at.x;\n this.geo.y = at.y;\n }\n this.dragFrom = null;\n this.dragGrowth = null;\n if (this.hostEl) this.hostEl.style.transition = '';\n this.place();\n this.writeGeometry(this.geo);\n break;\n }\n\n case 'widget.resetPos':\n if (this.inline) break;\n this.geo = defaultGeometry();\n this.writeGeometry(this.geo);\n this.place();\n break;\n\n case 'widget.hide':\n // Inline: hiding is the host's panel to hide. Blanking their drawer from in\n // here would leave them with an empty box and no way to get it back.\n if (this.inline) break;\n if (this.hostEl) this.hostEl.style.display = 'none';\n break;\n\n case 'widget.needsAuth':\n this.emit({ type: 'auth', phase: 'required' });\n void this.provideAuth();\n break;\n\n case 'widget.navigate':\n this.emit({ type: 'navigate', href: msg.href });\n // Delegated to the lazy context chunk (kept out of the every-page stub \u2014\n // the eager budget is tight). By the time a document deeplink is clicked the\n // chat is open, so context is already loaded.\n void this.loadContext().then((m) => m.navigateHost(msg.href));\n break;\n\n case 'widget.chat':\n // Content-free chat-turn telemetry (phase + opaque chatId), relayed straight\n // to the host's onEvent. Never carries message text.\n this.emit({ type: 'chat', phase: msg.phase, chatId: msg.chatId });\n break;\n }\n }\n\n /**\n * Own a drag for its lifetime.\n *\n * The widget reports the press and then goes quiet: a cross-origin iframe only gets\n * pointer events while the pointer is over it, and a drag leaves that box immediately.\n * So we lay a transparent layer over the whole viewport and track the gesture in the\n * host document, where it can't be lost. The layer also stops the pointer landing on\n * the customer's own UI mid-drag (text selection, hover states, stray clicks).\n *\n * We are the dumb half on purpose \u2014 see `widget.dragMove` in the protocol for why the\n * widget has to own the gesture. All we do is take a delta and place the element.\n */\n /** Write a box onto the host element and stamp the mode the shadow CSS keys off. */\n private applyBox(\n box: BoxStyle,\n mode: 'launcher' | 'tab' | 'pill' | 'menu' | 'float' | 'dock',\n ) {\n if (!this.hostEl || this.inline) return;\n const s = this.hostEl.style;\n s.top = box.top;\n s.right = box.right;\n s.bottom = box.bottom;\n s.left = box.left;\n s.width = box.width;\n s.height = box.height;\n this.hostEl.dataset.mode = mode;\n // The flush shapes (tab, docked panel) round only on the side facing the page.\n if (mode === 'tab' || mode === 'dock')\n this.hostEl.dataset.flush = this.geo.dockSide;\n else delete this.hostEl.dataset.flush;\n }\n\n /** Re-apply the box for whatever state we're in: collapsed launcher, float, or dock. */\n /**\n * The growth direction to use RIGHT NOW \u2014 frozen while a drag is in flight.\n *\n * Every consumer has to agree on this, which is the bug that made the widget\n * flip-flop under small movements near the viewport centre: the box was frozen but\n * `host.geometry` still published a freshly-derived growth, so the WIDGET kept\n * swapping which corner it drew against (and it animates that), twitching back and\n * forth while the loader held perfectly still.\n */\n private growthNow(): Corner {\n return (\n this.dragGrowth ??\n growthCorner(this.geo, window.innerWidth, window.innerHeight)\n );\n }\n\n private place() {\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n if (!this.open) {\n const dock = this.geo.mode === 'dock';\n this.applyBox(\n dock\n ? tabBox(this.geo, vw, vh)\n : floatChrome(this.geo, LAUNCHER, LAUNCHER, vw, vh, this.growthNow()),\n dock ? 'tab' : 'launcher',\n );\n } else if (this.geo.mode === 'dock') {\n this.applyBox(dockBox(this.geo, vw, vh), 'dock');\n } else {\n this.applyBox(floatBox(this.geo, vw, vh, this.growthNow()), 'float');\n }\n this.publishGeometry();\n }\n\n /**\n * Tell the widget where it is. It can't see the viewport and doesn't own the box, so\n * without this it draws its launcher against a stale guess \u2014 which is what made the\n * circle jump when the launcher sat on the left (see `host.geometry` in the protocol).\n */\n private publishGeometry() {\n this.send({\n type: 'host.geometry',\n mode: this.geo.mode,\n dockSide: this.geo.dockSide,\n growth: this.growthNow(),\n });\n }\n\n /** The theme to publish: an explicit data-theme wins, else the OS preference. */\n private themeMode(): 'light' | 'dark' {\n return this.config.theme === 'auto'\n ? matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n : this.config.theme;\n }\n\n private onViewportResize = () => {\n // Re-clamp and re-anchor: a saved box must never be left hanging off a smaller page.\n this.place();\n };\n\n private readGeometry(): EmbedGeometry {\n // First-party storage on the CUSTOMER's origin, so each site remembers its own spot.\n // Never throws: storage is unavailable in some embedding contexts, and a widget that\n // can't remember where it was put is fine \u2014 one that fails to mount is not.\n try {\n return parseGeometry(localStorage.getItem(POS_KEY)) ?? defaultGeometry();\n } catch {\n return defaultGeometry();\n }\n }\n\n private writeGeometry(g: EmbedGeometry) {\n try {\n localStorage.setItem(POS_KEY, serializeGeometry(g));\n } catch {\n /* storage denied \u2014 the box just won't survive the reload */\n }\n }\n\n /**\n * Answer `widget.needsAuth`. If a trusted first-party host has an auth-token provider,\n * call it and hand the token straight to the widget via `host.auth` \u2014 no popup. The\n * provider comes from EITHER the programmatic config (the React `<MatterfactAgent\n * getAuthToken>` prop) OR a global the host page sets for the `<script>` loader:\n *\n * window.matterfact = { getEmbedAuthToken: () => getIdToken(user) };\n *\n * Read fresh at call time (not at readConfig), so a global set after the loader booted\n * \u2014 e.g. once the host's auth is ready \u2014 is still picked up. `expiresAt: 0`: the widget\n * doesn't cache it, it exchanges the token for a rotating embed session anyway.\n *\n * No provider \u21D2 this does nothing. The loader never opens a sign-in popup \u2014 the widget\n * owns interactive sign-in (inline Firebase/email, and Microsoft's own MSAL login popup\n * for Entra, then the PKCE token exchange), so the loader must not also fire its stale\n * `/embed/authorize?k=&o=` popup, which the deployed authorize page no longer accepts.\n */\n private async provideAuth() {\n const provider =\n this.config.authTokenProvider ??\n (globalThis as { matterfact?: { getEmbedAuthToken?: AuthTokenProvider } })\n .matterfact?.getEmbedAuthToken;\n if (!provider) return; // the widget owns interactive sign-in; the loader never opens it\n\n // Storm guard. A healthy host-auth flow asks ONCE \u2014 the widget exchanges the token for a\n // rotating session and never asks again. A flood of `widget.needsAuth` means the token\n // keeps being rejected (wrong audience/scope) and something is remounting the widget in a\n // loop; calling getAuthToken on every one hammers the host's identity provider (MSAL \u2192\n // login.microsoftonline.com), which can get the app RATE-LIMITED. Past the cap we simply\n // stop calling the provider. Silent + minimal by necessity (hard per-page size budget);\n // the widget-side guard is what surfaces the reason to the user and logs the rejection.\n if (++this.ac > 5) return;\n try {\n const token = await provider();\n if (token) this.send({ type: 'host.auth', token, expiresAt: 0 });\n // One emit either way \u2014 a null token is a failure to provide, same as a throw.\n this.emit({ type: 'auth', phase: token ? 'granted' : 'failed' });\n } catch {\n this.emit({ type: 'auth', phase: 'failed' });\n /* no token this time; the widget falls back to its own inline / popup sign-in */\n }\n }\n\n private loadContext() {\n // One chunk, one fetch, cached. Everything that can see the customer's page\n // lives behind this boundary.\n this.context ??= import('./context').then((m) => {\n // The single place the eye actually starts: when pageContext is off, `start()`\n // skips installing every DOM/nav/focus/activity observer \u2014 not a filter over\n // what gets sent afterward, nothing is collected in the first place. Passed\n // through raw: `start`'s default parameter is what turns `undefined` (the\n // option omitted) into \"on\".\n m.start(\n this.send,\n this.config.origin,\n this.config.pageContext,\n this.config.pageContextProvider,\n );\n // start() emits the OS-preference theme. An explicit data-theme on the\n // script tag is the host's choice \u2014 resend it so it wins over auto-detect\n // (a light-branded site must be able to force a light widget on a dark-OS\n // visitor, and vice versa).\n if (this.config.theme !== 'auto') {\n this.send({ type: 'host.theme', mode: this.config.theme });\n }\n return m;\n });\n return this.context;\n }\n\n private send = (msg: HostToWidget) => {\n if (!this.ready) {\n this.queue.push(msg);\n return;\n }\n // targetOrigin is the widget's exact origin \u2014 never '*'. A '*' here would\n // broadcast the customer's page content to whatever happens to be listening.\n this.iframe?.contentWindow?.postMessage(envelope(msg), this.config.origin);\n };\n\n private flush() {\n const pending = this.queue;\n this.queue = [];\n for (const m of pending) this.send(m);\n }\n\n /** Tear down: stop listening and remove the host element. For the React wrapper's\n * unmount \u2014 the vanilla `<script>` loader lives for the page's lifetime and never\n * calls this. */\n destroy() {\n window.removeEventListener('message', this.onMessage);\n window.removeEventListener('resize', this.onViewportResize);\n this.hostEl?.remove();\n this.hostEl = null;\n this.iframe = null;\n this.shadow = null;\n this.ready = false;\n // context.ts's listeners/history-patch/ref-registry are module-global and\n // outlive this instance \u2014 stop() undoes exactly what start() installed. A no-op\n // if the eye was never loaded (chat never opened, so nothing was ever started).\n // This is the unmount-with-no-remount case; start()'s own stop()-first call\n // (see context.ts) is what protects a REMOUNT, which is why both exist.\n void this.context?.then((m) => m.stop());\n }\n}\n\n/**\n * Programmatic entry \u2014 mount the widget from a config object instead of the DOM\n * `<script data-*>`. This is what the React wrapper (`@matterfact/embed/react`) drives;\n * the vanilla loader below reads the script tag and calls this same class.\n */\nexport function mount(config: LoaderConfig): EmbedHost {\n const host = new EmbedHost(config);\n host.mount();\n return host;\n}\n\nexport { EmbedHost, CHANNEL, readConfig };\n", "/**\n * The vanilla `<script>` loader entry \u2014 the browser stub hosted on a CDN and dropped\n * onto any page:\n *\n * <script src=\"https://cdn.matterfact.com/embed.js\" data-key=\"pk_live_\u2026\" defer></script>\n *\n * This is the ONLY module with the DOM auto-run side-effect. The reusable core\n * (`./loader`), the npm `.` entry (`./index`), and the React wrapper (`./react`) never\n * import it, so they stay SSR-safe \u2014 importing them touches no `document`.\n */\nimport { mount, readConfig } from './loader';\n\nconst config = readConfig();\nif (config) {\n const boot = () => mount(config);\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', boot, { once: true });\n } else {\n boot();\n }\n}\n"],
|
|
4
|
+
"sourcesContent": ["/**\n * The matterfact embed bridge protocol.\n *\n * This is the contract between the HOST PAGE (a customer's site, running our\n * loader) and the WIDGET (our chat, running in a cross-origin iframe on\n * app.matterfact.com).\n *\n * Two things to understand before changing anything here:\n *\n * 1. THE IFRAME CANNOT SEE THE PAGE. It is cross-origin \u2014 that is the entire\n * point of it. So every fact about the host page (its URL, its DOM, what the\n * user just did) reaches the agent only because the LOADER, which runs as\n * ordinary page JS and *can* see it, chose to send it. The loader is the eye;\n * the widget is the mouth.\n *\n * 2. THIS CONTRACT OUTLIVES THE IFRAME. The next tier is an in-page React SDK\n * with no iframe at all \u2014 and it will speak exactly this. If the message\n * shapes are right, \"deeper integration\" is a transport swap (postMessage ->\n * direct call), not a rewrite. So model the DOMAIN here, not the plumbing.\n */\n\n// v3 (2026-07): adds `host.sitemap` (a structured, content-annotated site map),\n// `widget.navigate` (deeplink the host to one of its own routes), and `widget.chat`\n// (content-free chat-turn telemetry forwarded to the host's `onEvent`). All additive \u2014\n// a peer that doesn't know a message simply never sends/handles it \u2014 so this is a\n// capability signal, not a breaking change. No code branches on the version number.\nexport const PROTOCOL_VERSION = 3;\n\n/** Every message is namespaced so we never collide with the host page's own postMessage traffic. */\nexport const CHANNEL = 'mf-embed';\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 refs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/**\n * A handle on a real element in the host page.\n *\n * Format and semantics are lifted from Playwright's ARIA snapshot (`ref=e12`),\n * for a specific reason: it is the element-handle syntax the frontier models have\n * seen most, via playwright-mcp. We are not being original here on purpose.\n *\n * The ref is an in-page expando plus a snapshot-scoped Map \u2014 never an attribute,\n * so it is invisible to the host page's CSS, its serialization, and its own\n * mutation observers. It is resolved back to an element by the LOADER; the agent\n * never touches the DOM, and never learns a selector.\n */\nexport type ElementRef = string; // 'e12'\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 host \u2192 widget \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/** Ambient facts about the page. Cheap, sent on every navigation. */\nexport interface PageContext {\n url?: string;\n /** A route PATTERN ('/orders/:id') beats a raw path \u2014 it tells the agent what KIND of page this is. */\n route?: string;\n path?: string;\n title?: string;\n locale?: string;\n /** 1\u20132 sentences, written the way you'd brief a colleague. The highest-value field by far. */\n description?: string;\n /** Things the page is about. These also become @-mentionable in the composer, for free. */\n entities?: PageEntity[];\n /** Allowlisted only. Never a dump of app state. */\n data?: Record<string, unknown>;\n}\n\nexport interface PageEntity {\n /** Host kinds are namespaced (`x:ticker`) so they can never collide with matterfact's own. */\n kind: string;\n id: string;\n label: string;\n /** Pre-rendered, agent-readable. Keep it short. */\n text?: string;\n data?: Record<string, unknown>;\n}\n\n/**\n * How a route's content is classified, for the annotated site map.\n *\n * The host DECORATES the site map we auto-derive (see `SiteMapEntry`) so the agent\n * knows which routes carry MATTERFACT content and of what kind \u2014 app-wide awareness\n * it can navigate toward. This is CLASSIFICATION only: no per-instance ids, and never\n * a capability token. The concrete artifact/document on the CURRENT page still arrives\n * via `host.context` entities / `host.artifactGrants`, resolved per page.\n *\n * - `mf-artifact` \u2014 a co-embedded matterfact artifact (its slug); the token stays on\n * the live iframe / grant, never here.\n * - `mf-document` \u2014 a matterfact document (dossier/report), resolved to an MF_DOC_ID\n * host-side per page.\n * - `host-data` \u2014 the host's own data; not a matterfact entity.\n */\nexport type ContentClass =\n | { kind: 'mf-artifact'; slug: string }\n | { kind: 'mf-document'; doctype: string }\n | { kind: 'host-data' };\n\n/**\n * One route in the annotated site map: the path pattern + label we derived, plus the\n * host's content classification. `current` marks the route the user is on.\n */\nexport interface SiteMapEntry {\n /** Route pattern or path, e.g. '/app/company/:ticker/dossier'. */\n path: string;\n /** Human label for the route ('Company Dossier'). */\n label?: string;\n /** The host's classification of this route's content, when it annotated it. */\n content?: ContentClass;\n current?: boolean;\n}\n\n/** A co-embedded artifact's read-only capability, for the backend to materialize its\n * data. Backend-only \u2014 never rendered. `token` is the artifact's own embed token. */\nexport interface ArtifactGrant {\n id: string;\n owner: string;\n token: string;\n}\n\n/**\n * A DECLARED artifact grant \u2014 the `<MatterfactAgent artifacts>` prop (or\n * `window.matterfact.artifacts`). Same wire shape as a grant discovered by scanning\n * co-embedded `<MatterfactArtifact>` iframes, plus an optional `label`; it feeds the\n * SAME `host.artifactGrants` channel. This is how a host makes a board reachable from\n * a route where its iframe ISN'T mounted, without hidden zero-size iframes smuggling\n * the token into the DOM. The token stays in the grant channel, never in message text.\n */\nexport interface DeclaredArtifact {\n slug: string;\n owner: string;\n token: string;\n label?: string;\n}\n\n/**\n * One host-tool lifecycle event, surfaced to the host via `onToolEvent` (and the dev\n * panel) \u2014 the host's own telemetry hook. Fired loader-side; a throwing or slow\n * callback here NEVER blocks or breaks the call path.\n *\n * - `advertise` \u2014 the merged tool list changed (fired with the new set's names).\n * - `call` \u2014 a tool call is about to run (`args` present).\n * - `result` \u2014 the call settled (`ok`/`error`/`ms`).\n */\nexport interface ToolEvent {\n phase: 'advertise' | 'call' | 'result';\n name: string;\n toolClass: 'hoist' | 'mf' | 'app';\n args?: unknown;\n ok?: boolean;\n error?: string;\n ms?: number;\n}\n\n/**\n * One embed lifecycle event, surfaced to the host via `onEvent` \u2014 the host's single\n * telemetry hook. Fired loader-side (lifecycle) and widget-side (chat); a throwing or\n * slow callback NEVER blocks or breaks the mount/call/auth/run path.\n *\n * - `ready` \u2014 the widget connected (protocol handshake done).\n * - `open`/`close` \u2014 the corner widget was opened/closed. (Inline has no open event \u2014\n * the host's own panel is the open/close control.)\n * - `navigate` \u2014 the agent deeplinked the host to one of its own routes.\n * - `auth` \u2014 host-auth passthrough: `required` (widget asked), `granted` (a\n * token was handed over), `failed` (no provider result / the provider threw).\n * - `error` \u2014 the widget failed to mount / load.\n * - `chat` \u2014 a chat-turn moment. Content-free: phase + opaque chatId only.\n * - `tool` \u2014 a host-tool lifecycle event (the `ToolEvent` shape, tagged). Also\n * still delivered untagged to the legacy `onToolEvent` hook for back-compat.\n */\nexport type MatterfactEvent =\n | { type: 'ready' }\n | { type: 'open' }\n | { type: 'close' }\n | { type: 'navigate'; href: string }\n | { type: 'auth'; phase: 'required' | 'granted' | 'failed' }\n | { type: 'error'; message: string }\n | { type: 'chat'; phase: 'message' | 'response-start' | 'response-end'; chatId?: string }\n | ({ type: 'tool' } & ToolEvent);\n\n/**\n * A snapshot of the page as the agent sees it.\n *\n * `yaml` is Playwright's ARIA-snapshot rendering \u2014 role, accessible name, state,\n * and a ref on anything interactable:\n *\n * - heading \"Orders\" [level=1]\n * - textbox \"Search orders\" [ref=e5]\n * - button \"Export CSV\" [ref=e9]\n *\n * NOT raw HTML. A real page is tens of thousands of tokens of div soup, it gives\n * the model no handle to act with, and it is a prompt-injection firehose. The\n * a11y tree is a tenth the size and is the only signal that survives in-page \u2014\n * `getEventListeners()` is DevTools-only, so listener-sniffing (the browser-use\n * approach) cannot see a single React handler on a real customer's app.\n */\nexport interface DomSnapshot {\n yaml: string;\n /** Monotonic. A ref is only valid within the snapshot that minted it. */\n seq: number;\n /** True when the walk hit its node budget and stopped. The agent must be told it is not seeing everything. */\n truncated: boolean;\n}\n\n/**\n * Where the user's attention is RIGHT NOW.\n *\n * This is the difference between an assistant that can read your page and one that\n * knows what you're doing. A full DOM snapshot answers \"what is on this screen\"; this\n * answers \"what are you looking at and working in\" \u2014 which is what a colleague\n * leaning over reads first. It is small, so it rides on every turn, where the full\n * snapshot does not.\n *\n * Every field here is a LABEL or a REF, never a value. `selection` is the sharpest\n * intent signal there is (\"this is the thing I'm asking about\"), and for exactly that\n * reason it is also where a user might have highlighted their own email \u2014 so it is\n * redacted and honours data-mf-private like everything else.\n */\nexport interface FocusContext {\n /** Text the user has selected. The single strongest \"I'm asking about this\" signal. Redacted. */\n selection?: string;\n /** The control the user is working IN \u2014 by label and ref, never its contents. */\n focused?: { ref?: ElementRef; label: string; role: string };\n /** The refs currently in the viewport: the handful of things actually on screen, out of the whole tree. */\n visibleRefs?: ElementRef[];\n /** How far down the page they are, 0\u20131. A cheap proxy for \"which part am I reading\". */\n scroll?: number;\n}\n\n/** Something the user did. Shaped after PostHog's autocapture \u2014 one readable line, not a replay frame. */\nexport interface ActivityEvent {\n seq: number;\n ts: number;\n type: 'nav' | 'click' | 'input' | 'submit' | 'selection' | 'custom';\n /** Human-readable: 'clicked button \"Export CSV\"'. Semantic, not coordinates. */\n summary: string;\n ref?: ElementRef;\n data?: Record<string, unknown>;\n}\n\n/**\n * A tool the HOST page exposes to the agent.\n *\n * Deliberately shaped as a superset of WebMCP (`navigator.modelContext`, W3C\n * WebML CG draft / Chrome origin trial). A customer who adopts WebMCP should be\n * able to hand us their existing tool declarations unchanged.\n */\nexport interface HostTool {\n name: string;\n description: string;\n /** JSON Schema. */\n inputSchema: Record<string, unknown>;\n /**\n * A read-only tool may run without asking. Anything that MUTATES the page\n * requires the user to confirm, every time \u2014 see the note on ToolCall.\n */\n readOnly?: boolean;\n /**\n * The tiered confirm policy, set by the host (e.g. `window.matterfact.hoist.actions`):\n * `'required'` \u21D2 the widget must show a confirm card and get the user's approval\n * before every call; `'auto'` \u21D2 it may call without asking. Absent for a tool that\n * predates this policy (treat as `'required'` \u2014 the safer default).\n */\n confirm?: 'auto' | 'required';\n}\n\nexport type HostToWidget =\n | { type: 'host.ready'; protocol: number; origin: string }\n | { type: 'host.context'; context: PageContext }\n | { type: 'host.focus'; focus: FocusContext }\n | { type: 'host.snapshot'; snapshot: DomSnapshot }\n | { type: 'host.region'; ref: ElementRef; yaml: string }\n | { type: 'host.activity'; events: ActivityEvent[] }\n | { type: 'host.artifactGrants'; grants: ArtifactGrant[] }\n /**\n * The annotated site map \u2014 the app's routes with the host's content\n * classification. Sent on mount and re-sent on navigation (a virtualized SPA can\n * change what's routable). Distinct from the per-page `host.context`: this is the\n * whole app's structure, so the agent can reason about and navigate toward MF\n * content the user isn't currently looking at.\n */\n | { type: 'host.sitemap'; sitemap: SiteMapEntry[] }\n | { type: 'host.tools'; tools: HostTool[] }\n | {\n type: 'host.toolResult';\n callId: string;\n ok: boolean;\n result?: unknown;\n error?: string;\n }\n | { type: 'host.auth'; token: string; expiresAt: number }\n | { type: 'host.theme'; mode: 'light' | 'dark' }\n /**\n * Where the widget currently IS. Sent on mount and after anything that moves it\n * (drag, snap, mode change, viewport resize).\n *\n * The widget cannot work this out for itself \u2014 it can't see the viewport, and the\n * loader owns the box. Without it the widget renders its launcher against a stale\n * guess: a launcher dragged to the left edge kept drawing its circle bottom-RIGHT\n * inside a left-anchored box, so the circle jumped as soon as the box grew for the\n * hover pill. `growth` is the direction the pill/menu/panel must open (toward the\n * viewport centre); `mode` decides whether the launcher is a circle or an edge tab.\n */\n | {\n type: 'host.geometry';\n mode: 'float' | 'dock';\n dockSide: 'left' | 'right';\n growth: 'tl' | 'tr' | 'bl' | 'br';\n };\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 widget \u2192 host \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/**\n * The agent asking the host to do something.\n *\n * THE SECURITY MODEL, stated once: the page's content is UNTRUSTED. Text on the\n * page can try to instruct the agent, and the moment the agent can act, that stops\n * being a bad answer and becomes a bad ACTION. So:\n *\n * - v1 is READ-ONLY. `snapshot` and `activity` flow; no tool mutates the page.\n * - A tool that is not `readOnly` must be confirmed by the USER, every call. Not\n * \"unless the host opts out\" \u2014 every call. The trigger may have been written by\n * the page.\n * - The host is the enforcement point, not the widget. The widget can be lied to;\n * the loader runs in the page and decides what actually happens.\n */\nexport interface ToolCall {\n callId: string;\n name: string;\n args: Record<string, unknown>;\n}\n\nexport type WidgetToHost =\n | { type: 'widget.ready'; protocol: number }\n /**\n * Ask for the FULL page snapshot. This is the agent PULLING detail, not us pushing\n * it: the focus context rides on every turn, but the whole tree is fetched only\n * when the agent decides it needs to look \u2014 cheaper, and it reads what's relevant\n * to the question instead of drowning in the footer nav every turn.\n */\n | { type: 'widget.requestSnapshot' }\n /**\n * Pull the DECLARED page context fresh, right now. The host answers with `host.context`.\n *\n * Declared context (`window.matterfact.context`, or a `getPageContext()` callback) is\n * otherwise only published on load and on URL navigation, so in a virtualized SPA it goes\n * stale on any non-navigation state change (a team switch, a filter edit, a selection).\n * The widget pulls it at turn-assembly time \u2014 symmetric with `widget.requestSnapshot` \u2014\n * so every turn carries the current context, not the context as of the last route change.\n */\n | { type: 'widget.requestContext' }\n /** Zoom in: the a11y sub-tree under one ref (a table, a form, a card the agent cares about). */\n | { type: 'widget.readRegion'; ref: ElementRef }\n | { type: 'widget.callTool'; call: ToolCall }\n /** Chat opened/closed \u2014 the loader resizes the iframe and starts/stops observing. */\n | { type: 'widget.setOpen'; open: boolean }\n | { type: 'widget.resize'; height: number }\n /**\n * Dock / undock the open panel.\n *\n * Docked is a FULL-HEIGHT SIDE PANEL on the launcher's own side \u2014 left corners dock\n * to the left edge, right corners to the right, unless `setDockSide` overrides it.\n * This replaces the old `setExpanded`, which was hardcoded to the right edge and so\n * threw the panel across the screen whenever the launcher sat on the left.\n *\n * The loader owns the box, so only it can do this \u2014 `widget.resize` (height-only)\n * can neither widen the panel nor move it to an edge.\n */\n | { type: 'widget.setMode'; mode: 'float' | 'dock' }\n /** Which edge the tab and its panel live on. */\n | { type: 'widget.setDockSide'; side: 'left' | 'right' }\n /** Place the FLOATING launcher at a specific corner (the menu's snap picker). */\n | { type: 'widget.snapCorner'; corner: 'tl' | 'tr' | 'bl' | 'br' }\n /**\n * Resize the COLLAPSED launcher's host box so the widget can paint outside the 56px\n * circle \u2014 the hover pill (`{w:180,h:56}`) or the context menu (its own box). The\n * iframe clips to the host box, so anything beside the circle is invisible until the\n * host makes room. `{w:56,h:56}` returns to the bare circle. Ignored while open.\n */\n | { type: 'widget.setLauncherRegion'; w: number; h: number }\n /**\n * User resize of the floating panel, via the grip on its inner corner.\n *\n * Same contract as the launcher drag below, and for the same reason: the press is\n * captured by the iframe, so the host cannot see the moves. The widget tracks the\n * gesture and sends SCREEN-coordinate deltas; the loader owns the sizing, the\n * per-corner sign of the delta, and the clamp.\n */\n | { type: 'widget.resizeStart' }\n | { type: 'widget.resizeMove'; dx: number; dy: number }\n | { type: 'widget.resizeEnd' }\n /**\n * Launcher drag. The widget tracks the gesture; the host only moves the element.\n *\n * It has to be this way round, and the reason is easy to get wrong: a press that starts\n * inside an iframe is implicitly captured by that iframe, so every pointermove and the\n * pointerup belong to the widget's document until the button comes up. The host cannot\n * see them at all \u2014 not even through an overlay of its own, because capture beats hit\n * testing. So the host cannot take over a drag mid-gesture; it can only be told.\n *\n * `dx`/`dy` are deltas in SCREEN coordinates, which is the other half of the trick:\n * client coordinates are measured against the iframe's own viewport, and the iframe is\n * being moved under the cursor as the drag proceeds \u2014 so the pointer would appear to\n * stop moving and the launcher would stall after one step. Screen coordinates are\n * absolute and immune to that feedback loop.\n */\n | { type: 'widget.dragStart' }\n | { type: 'widget.dragMove'; dx: number; dy: number }\n | { type: 'widget.dragEnd' }\n /** Put the launcher back in the default corner and forget the saved size/dock. */\n | { type: 'widget.resetPos' }\n /** Get out of the way until the page reloads (a menu action, not a setting). */\n | { type: 'widget.hide' }\n /** The widget has no session; the host must run the hosted-login popup. */\n | { type: 'widget.needsAuth' }\n /**\n * Deeplink the host to one of its OWN routes \u2014 the \"open on page\" control on a\n * co-embedded document's side-panel viewer. The widget is a cross-origin iframe and\n * can't navigate the top window itself, so it asks the loader, which resolves the\n * href against the host location and refuses anything off the host's own origin.\n */\n | { type: 'widget.navigate'; href: string }\n /**\n * A chat-turn lifecycle moment, forwarded by the loader to the host's `onEvent`.\n * Content-free: `phase` + an opaque `chatId`, never message text. Additive.\n */\n | {\n type: 'widget.chat';\n phase: 'message' | 'response-start' | 'response-end';\n chatId?: string;\n };\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 envelope \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/**\n * Everything on the wire is wrapped.\n *\n * `channel` keeps us out of the host page's own postMessage traffic (and out of\n * every other widget's). The receiver ALSO checks `event.origin` against an\n * allowlist and `event.source` against the expected window, on every single\n * message \u2014 a namespace is not a security boundary, it is a politeness.\n */\nexport interface Envelope<T> {\n channel: typeof CHANNEL;\n protocol: number;\n /** Correlates a response to its request; absent for notifications. */\n id?: string;\n payload: T;\n}\n\nexport function envelope<T>(payload: T, id?: string): Envelope<T> {\n return {\n channel: CHANNEL,\n protocol: PROTOCOL_VERSION,\n ...(id ? { id } : {}),\n payload,\n };\n}\n\nexport function isEnvelope(data: unknown): data is Envelope<unknown> {\n return (\n typeof data === 'object' &&\n data !== null &&\n (data as { channel?: unknown }).channel === CHANNEL\n );\n}\n", "/**\n * Pure geometry for the embed launcher/panel.\n *\n * No DOM, no React \u2014 every function here is a total map from numbers/strings to\n * numbers/strings. That is deliberate: the loader attaches a CLOSED shadow root, so\n * the iframe inside it is unreachable from a test. Keeping the whole positioning\n * model in pure functions is what makes it verifiable at all; `loader.ts` is then a\n * thin applier that sets the returned box on the light-DOM host element.\n *\n * There are two MODES, and the mode decides both how the widget rests and how it opens:\n *\n * dock (default) \u2014 the launcher is a TAB flush against a side edge, and opening\n * gives a full-height side panel on that edge. This is the shape\n * most hosts want: it reads as part of the app, not as a bubble\n * floating over it.\n * float \u2014 the launcher is a CIRCLE the user can put anywhere, and opening\n * gives a panel anchored near it.\n *\n * In float the position is FREE. A corner is only recorded when the user drops the\n * launcher near one (see `snapOrFree`), because a snap that always fires isn't a\n * convenience, it's a constraint \u2014 you could never place the thing mid-edge. Recording\n * the corner when it IS snapped is what lets it stay in that corner across a viewport\n * resize, where a raw x/y would drift.\n */\n\nexport type Corner = 'tl' | 'tr' | 'bl' | 'br';\nexport type DockSide = 'left' | 'right';\nexport type Mode = 'float' | 'dock';\n\nexport interface EmbedGeometry {\n mode: Mode;\n /** float: the corner it snapped to, or null when freely placed. */\n corner: Corner | null;\n /** float: free launcher top-left, used only when `corner` is null. */\n x: number;\n y: number;\n floatW: number;\n floatH: number;\n dockSide: DockSide;\n /** dock: the tab's top offset along its edge. */\n tabY: number;\n dockW: number;\n}\n\n/**\n * A set of fixed-position box properties to apply to the host `<div>`.\n * Exactly two of top/right/bottom/left carry a px value; the other two are 'auto',\n * so the box stays pinned to the edges it grows away from.\n */\nexport interface BoxStyle {\n top: string;\n right: string;\n bottom: string;\n left: string;\n width: string;\n height: string;\n}\n\nexport const MARGIN = 20;\nexport const LAUNCHER = 56;\nexport const PILL_W = 180;\n/** How close to a corner a drop must land before it snaps. Generous enough to feel\n * magnetic, small enough that the middle of an edge is still reachable. */\nexport const SNAP_RADIUS = 96;\n/**\n * The docked launcher tab.\n *\n * Wide enough to carry the logo mark, and clearly TALLER than it is wide so the shape\n * still reads as a tab on the page edge. The ratio is the thing that matters: at a\n * near-square size any inner radius rounds it into a lozenge, which is what \"squished\n * circle\" looks like.\n */\nexport const TAB_W = 44;\nexport const TAB_H = 132;\n/**\n * How close to a SIDE edge a drop must land to become a docked tab. Dragging the tab\n * away from the edge is therefore how you float it, and dragging the circle back to an\n * edge is how you dock it \u2014 the placement follows where you put the thing, so there is\n * no mode switch to go hunting for.\n */\nexport const EDGE_DOCK = 64;\nexport const DEFAULT_FLOAT_W = 420;\nexport const DEFAULT_FLOAT_H = 640;\nexport const MIN_FLOAT_W = 320;\nexport const MIN_FLOAT_H = 420;\nexport const DEFAULT_DOCK_W = 600;\nexport const MIN_DOCK_W = 320;\n\nconst CORNERS: readonly Corner[] = ['tl', 'tr', 'bl', 'br'];\n\nexport function defaultGeometry(): EmbedGeometry {\n return {\n // Dock is the default: a side panel reads as part of the host app.\n mode: 'dock',\n corner: 'br',\n x: 0,\n y: 0,\n floatW: DEFAULT_FLOAT_W,\n floatH: DEFAULT_FLOAT_H,\n dockSide: 'right',\n tabY: 0, // 0 => centre it (see tabTop)\n dockW: DEFAULT_DOCK_W,\n };\n}\n\nfunction clamp(n: number, lo: number, hi: number): number {\n return Math.min(Math.max(n, lo), hi);\n}\n\n/** Where a snapped corner puts the launcher's top-left. */\nfunction cornerOrigin(\n corner: Corner,\n w: number,\n h: number,\n vw: number,\n vh: number,\n): { x: number; y: number } {\n return {\n x: corner[1] === 'l' ? MARGIN : vw - MARGIN - w,\n y: corner[0] === 't' ? MARGIN : vh - MARGIN - h,\n };\n}\n\n/** The launcher's top-left in float mode: its snapped corner, else its free spot. */\nexport function launcherOrigin(\n g: EmbedGeometry,\n vw: number,\n vh: number,\n): { x: number; y: number } {\n if (g.corner) return cornerOrigin(g.corner, LAUNCHER, LAUNCHER, vw, vh);\n return {\n x: clamp(g.x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER)),\n y: clamp(g.y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER)),\n };\n}\n\n/**\n * Which way things grow from the launcher: toward the viewport centre.\n *\n * Distinct from `geometry.corner` on purpose \u2014 a FREELY placed launcher has no corner,\n * but its pill, menu and panel still have to open inward or they'd run off the page.\n */\nexport function growthCorner(g: EmbedGeometry, vw: number, vh: number): Corner {\n if (g.corner) return g.corner;\n const o = launcherOrigin(g, vw, vh);\n return nearestCorner(o.x + LAUNCHER / 2, o.y + LAUNCHER / 2, vw, vh);\n}\n\n/** Which quadrant a point is in. */\nexport function nearestCorner(\n cx: number,\n cy: number,\n vw: number,\n vh: number,\n): Corner {\n return `${cy < vh / 2 ? 't' : 'b'}${cx < vw / 2 ? 'l' : 'r'}` as Corner;\n}\n\n/**\n * Resolve a drop: snap to a corner only if it landed NEAR one, else keep it where the\n * user put it (clamped on-screen). Returns the fields to merge into the geometry.\n */\nexport function snapOrFree(\n x: number,\n y: number,\n vw: number,\n vh: number,\n): { corner: Corner | null; x: number; y: number } {\n const cx = clamp(x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER));\n const cy = clamp(y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER));\n for (const corner of CORNERS) {\n const o = cornerOrigin(corner, LAUNCHER, LAUNCHER, vw, vh);\n if (Math.hypot(cx - o.x, cy - o.y) <= SNAP_RADIUS) {\n return { corner, x: cx, y: cy };\n }\n }\n return { corner: null, x: cx, y: cy };\n}\n\n/** Is this box close enough to a side edge to be docked? The live half of the drag. */\nexport function edgeDock(x: number, w: number, vw: number): DockSide | null {\n if (x <= EDGE_DOCK) return 'left';\n if (x + w >= vw - EDGE_DOCK) return 'right';\n return null;\n}\n\n/**\n * The point a drag MOVES \u2014 the widget's logical anchor, not its painted box.\n *\n * They differ once the panel is open: the box is the whole panel, but what the geometry\n * stores (and what a corner drop resolves) is the launcher's own origin. Dragging by the\n * box's top-left would shift the widget by the panel's size the moment you grabbed it.\n */\nexport function dragAnchor(\n g: EmbedGeometry,\n vw: number,\n vh: number,\n): { x: number; y: number } {\n if (g.mode === 'dock') {\n return { x: g.dockSide === 'left' ? 0 : vw - TAB_W, y: tabTop(g, vh) };\n }\n return launcherOrigin(g, vw, vh);\n}\n\n/**\n * The widget's painted horizontal extent for the state it is in.\n *\n * Derived, not measured: `getBoundingClientRect` is unavailable before layout (and is\n * all-zeros under jsdom), and the loader already knows every input. This is what the\n * edge test reads \u2014 \"dragged to the side\" is about the box you can see touching the\n * edge, which for an open panel is nowhere near its anchor.\n */\nexport function visibleBox(\n g: EmbedGeometry,\n open: boolean,\n vw: number,\n vh: number,\n): { x: number; w: number } {\n if (g.mode === 'dock') {\n const w = open\n ? Math.min(Math.max(g.dockW, MIN_DOCK_W), Math.max(0, vw - 2 * MARGIN))\n : TAB_W;\n return { x: g.dockSide === 'left' ? 0 : vw - w, w };\n }\n const o = launcherOrigin(g, vw, vh);\n if (!open) return { x: o.x, w: LAUNCHER };\n const { w } = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);\n // A right-growing panel hangs LEFT off the launcher, so its left edge is the\n // launcher's right edge minus the panel width.\n return {\n x: growthCorner(g, vw, vh)[1] === 'r' ? o.x + LAUNCHER - w : o.x,\n w,\n };\n}\n\n/**\n * Resolve a drop into a PLACEMENT \u2014 which mode the widget lands in, and where.\n *\n * Two different measurements, deliberately:\n * - the EDGE test uses the widget's visible box, because \"dragged to the side\" is\n * about what you can see touching the edge. Testing the anchor instead means a\n * 420px-wide panel whose right edge is already against the screen refuses to dock,\n * since its anchor is still 400px away.\n * - the CORNER test uses the anchor, because that is what a corner placement stores.\n *\n * Priority matters, and corner beats edge on purpose: every corner is also within reach\n * of a side edge, so checking the edge first would make corner-snapping unreachable and\n * quietly turn the floating circle into a tab whenever you aimed for a corner.\n */\nexport function placeDrop(\n anchorX: number,\n anchorY: number,\n boxLeft: number,\n boxW: number,\n vw: number,\n vh: number,\n):\n | { mode: 'float'; corner: Corner | null; x: number; y: number }\n | { mode: 'dock'; dockSide: DockSide; tabY: number } {\n const snapped = snapOrFree(anchorX, anchorY, vw, vh);\n if (snapped.corner) return { mode: 'float', ...snapped };\n const side = edgeDock(boxLeft, boxW, vw);\n if (side) {\n return {\n mode: 'dock',\n dockSide: side,\n tabY: clamp(anchorY, 0, Math.max(0, vh - TAB_H)),\n };\n }\n return { mode: 'float', ...snapped };\n}\n\nexport function dockSideForCorner(corner: Corner): DockSide {\n return corner[1] === 'l' ? 'left' : 'right';\n}\n\nexport function effectiveDockSide(g: EmbedGeometry): DockSide {\n return g.dockSide;\n}\n\n/**\n * Fit a size inside the viewport.\n *\n * Order matters: the minimum is applied FIRST and the viewport cap LAST, so on a\n * viewport smaller than the minimum the VIEWPORT wins. That is the whole point \u2014 a\n * panel that honoured its minimum past the viewport edge would hang off the page.\n */\nexport function clampSize(\n w: number,\n h: number,\n vw: number,\n vh: number,\n minW: number,\n minH: number,\n): { w: number; h: number } {\n return {\n w: Math.min(Math.max(w, minW), Math.max(0, vw - 2 * MARGIN)),\n h: Math.min(Math.max(h, minH), Math.max(0, vh - 2 * MARGIN)),\n };\n}\n\n/**\n * Apply a resize drag to the float size. The grip sits on the panel's INNER corner\n * (the one facing viewport centre), so dragging it away from the anchored corner\n * grows the panel \u2014 which flips the sign of the delta per corner. Unclamped.\n */\nexport function applyResizeDelta(\n corner: Corner,\n startW: number,\n startH: number,\n dx: number,\n dy: number,\n): { w: number; h: number } {\n const wSign = corner[1] === 'r' ? -1 : 1; // right-anchored grows on -dx\n const hSign = corner[0] === 'b' ? -1 : 1; // bottom-anchored grows on -dy\n return { w: startW + wSign * dx, h: startH + hSign * dy };\n}\n\n/** Widen/narrow a docked panel by dragging its inner edge. */\nexport function applyDockResizeDelta(\n side: DockSide,\n startW: number,\n dx: number,\n): number {\n return side === 'right' ? startW - dx : startW + dx;\n}\n\nexport function parseGeometry(raw: string | null): EmbedGeometry | null {\n if (!raw) return null;\n try {\n const p = JSON.parse(raw) as Partial<EmbedGeometry>;\n // Pre-mode values (and the even older {right,bottom}) are dropped rather than\n // guessed at \u2014 there is no honest mapping from them onto this model.\n if (!p || (p.mode !== 'float' && p.mode !== 'dock')) return null;\n const d = defaultGeometry();\n const num = (v: unknown, dv: number) => (typeof v === 'number' ? v : dv);\n return {\n mode: p.mode,\n corner: CORNERS.includes(p.corner as Corner)\n ? (p.corner as Corner)\n : null,\n x: num(p.x, d.x),\n y: num(p.y, d.y),\n floatW: num(p.floatW, d.floatW),\n floatH: num(p.floatH, d.floatH),\n dockSide: p.dockSide === 'left' ? 'left' : 'right',\n tabY: num(p.tabY, d.tabY),\n dockW: num(p.dockW, d.dockW),\n };\n } catch {\n return null;\n }\n}\n\nexport function serializeGeometry(g: EmbedGeometry): string {\n return JSON.stringify(g);\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 box builders \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nconst px = (n: number) => `${n}px`;\n\n/**\n * Pin the box by the two edges nearest the growth corner, so it grows inward.\n * Works for a snapped launcher and a freely-placed one alike: the anchor is always\n * the launcher's own top-left plus its size, expressed from whichever edges it grows\n * away from.\n */\nfunction boxAt(\n ox: number,\n oy: number,\n w: number,\n h: number,\n corner: Corner,\n vw: number,\n vh: number,\n): BoxStyle {\n const right = corner[1] === 'r';\n const bottom = corner[0] === 'b';\n // The anchored edges are measured from the LAUNCHER's own far side, so the launcher\n // stays put while the box grows past it...\n const rawX = right ? vw - (ox + LAUNCHER) : ox;\n const rawY = bottom ? vh - (oy + LAUNCHER) : oy;\n // ...but only as far as the viewport allows. Clamping the SIZE is not enough: a\n // launcher parked mid-page opens a 640px panel that grows straight off the top, and\n // the panel's header \u2014 the only way to close, dock or drag it \u2014 goes with it. So the\n // pinned offset is clamped too, which shifts the panel back into view while keeping\n // it as close to the launcher as it can be.\n const x = clamp(rawX, MARGIN, Math.max(MARGIN, vw - MARGIN - w));\n const y = clamp(rawY, MARGIN, Math.max(MARGIN, vh - MARGIN - h));\n return {\n top: bottom ? 'auto' : px(y),\n bottom: bottom ? px(y) : 'auto',\n left: right ? 'auto' : px(x),\n right: right ? px(x) : 'auto',\n width: px(w),\n height: px(h),\n };\n}\n\n/** The tab's top offset; a stored 0 means \"centre it\". */\nfunction tabTop(g: EmbedGeometry, vh: number): number {\n const max = Math.max(0, vh - TAB_H);\n return clamp(g.tabY || Math.round((vh - TAB_H) / 2), 0, max);\n}\n\n/** The docked launcher: a slim tab flush against its edge (no margin \u2014 it hugs). */\nexport function tabBox(g: EmbedGeometry, vw: number, vh: number): BoxStyle {\n const left = g.dockSide === 'left';\n return {\n top: px(tabTop(g, vh)),\n bottom: 'auto',\n left: left ? '0px' : 'auto',\n right: left ? 'auto' : '0px',\n width: px(TAB_W),\n height: px(TAB_H),\n };\n}\n\n/**\n * ANY float-mode box \u2014 the circle, the hover pill, the menu region, the open panel.\n *\n * They are all the same shape: a box of size w\u00D7h anchored at the launcher and growing\n * inward. One function rather than four wrappers, because this module is bundled into\n * the per-page loader stub and four near-identical bodies is pure weight.\n */\nexport function floatChrome(\n g: EmbedGeometry,\n w: number,\n h: number,\n vw: number,\n vh: number,\n /**\n * Override the growth direction. Used to FREEZE it for the length of a drag: growth\n * is derived from which half of the viewport the launcher is in, so dragging an open\n * panel across the middle would otherwise flip the side it grows from and jump the\n * panel by its own width. Held still, it tracks the cursor.\n */\n growth?: Corner,\n): BoxStyle {\n const o = launcherOrigin(g, vw, vh);\n return boxAt(o.x, o.y, w, h, growth ?? growthCorner(g, vw, vh), vw, vh);\n}\n\n/** The region the docked tab grows into for its context menu \u2014 hugging its edge. */\nexport function dockChrome(\n g: EmbedGeometry,\n w: number,\n h: number,\n vw: number,\n vh: number,\n): BoxStyle {\n const left = g.dockSide === 'left';\n return {\n top: px(tabTop(g, vh)),\n bottom: 'auto',\n left: left ? '0px' : 'auto',\n right: left ? 'auto' : '0px',\n width: px(w),\n height: px(h),\n };\n}\n\n/** The collapsed launcher for the current mode: dock \u2192 tab, float \u2192 circle. */\nexport function launcherBox(\n g: EmbedGeometry,\n vw: number,\n vh: number,\n): BoxStyle {\n return g.mode === 'dock'\n ? tabBox(g, vw, vh)\n : floatChrome(g, LAUNCHER, LAUNCHER, vw, vh);\n}\n\n/** The floating open panel, anchored at the launcher and clamped on-screen. */\nexport function floatBox(\n g: EmbedGeometry,\n vw: number,\n vh: number,\n growth?: Corner,\n): BoxStyle {\n const f = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);\n return floatChrome(g, f.w, f.h, vw, vh, growth);\n}\n\n/** Full-height side panel flush against its edge. */\nexport function dockBox(g: EmbedGeometry, vw: number, _vh: number): BoxStyle {\n const w = Math.min(\n Math.max(g.dockW, MIN_DOCK_W),\n Math.max(0, vw - 2 * MARGIN),\n );\n const left = g.dockSide === 'left';\n return {\n top: '0px',\n bottom: '0px',\n height: 'auto',\n left: left ? '0px' : 'auto',\n right: left ? 'auto' : '0px',\n width: px(w),\n };\n}\n", "import {\n CHANNEL,\n PROTOCOL_VERSION,\n envelope,\n isEnvelope,\n type HostToWidget,\n type MatterfactEvent,\n type WidgetToHost,\n} from './protocol';\n// Type-only: erased at build, so it does NOT pull the lazy ./context chunk eagerly.\nimport type { PageContextProvider } from './context';\nimport {\n applyDockResizeDelta,\n applyResizeDelta,\n clampSize,\n defaultGeometry,\n dockBox,\n floatBox,\n growthCorner,\n tabBox,\n dockChrome,\n floatChrome,\n parseGeometry,\n serializeGeometry,\n dragAnchor,\n visibleBox,\n edgeDock,\n placeDrop,\n LAUNCHER,\n MARGIN,\n MIN_DOCK_W,\n MIN_FLOAT_H,\n MIN_FLOAT_W,\n DEFAULT_DOCK_W,\n type BoxStyle,\n type Corner,\n type EmbedGeometry,\n} from './geometry';\n\n/**\n * The matterfact embed loader.\n *\n * <script type=\"module\" src=\"https://app.matterfact.com/embed/embed.js\"\n * data-key=\"pk_live_acme_...\"\n * data-origin=\"https://app.matterfact.com\"></script>\n *\n * This file runs on EVERY page load of a customer's site, so it is deliberately\n * tiny and deliberately dumb:\n *\n * - zero dependencies\n * - it does NOT read the DOM. The snapshot/activity code is a separate chunk,\n * fetched only when the user actually opens the chat. A customer should pay\n * nothing for a widget nobody clicked.\n * - it does NOT bundle React, or a CSS framework, or an RPC library.\n *\n * `iframe-resizer` is the thing you will be tempted to reach for here. It is\n * GPL-3.0 and cannot ship in this file. The ResizeObserver bridge below is the\n * fifteen lines it would have cost you anyway.\n */\n\n/** Returns a token this matterfact deployment's auth provider trusts (Firebase idToken,\n * Entra token, \u2026). May be async; return null when there's no signed-in user. */\nexport type AuthTokenProvider = () => string | null | Promise<string | null>;\n\nexport interface LoaderConfig {\n publishableKey: string;\n /** Origin of the widget. Overridable for dev/self-hosted; defaults to production. */\n origin: string;\n theme: 'light' | 'dark' | 'auto';\n /** Optional label identifying THIS embedding, when one app embeds the agent in more\n * than one place (e.g. \"orders\" vs \"support\"). Stamped on runs/threads so usage and\n * history can be told apart per surface. Free-form; the app_id (from the session)\n * is the trusted half. */\n surface: string;\n /**\n * OPTIONAL host-auth passthrough, for TRUSTED first-party embeds (the admin portal,\n * EMC). When set, the host page already holds a token this deployment's auth provider\n * trusts (its Firebase idToken, an Entra token, \u2026); the loader hands it to the widget\n * on `widget.needsAuth` instead of opening a sign-in popup. The backend still verifies\n * the token, so identity can't be forged \u2014 this only skips a redundant second sign-in.\n * Called on demand (so tokens can refresh). Absent \u21D2 the popup/inline sign-in flow.\n * The `<script>` loader has no config here; it reads window.matterfact.getEmbedAuthToken\n * instead (see provideAuth).\n */\n authTokenProvider?: AuthTokenProvider;\n /**\n * OPTIONAL pull callback for DECLARED page context, for a host whose context changes\n * without a URL navigation (a virtualized SPA: team switch, filter edit, selection). The\n * widget calls it at turn-assembly time (via `widget.requestContext`), so every turn\n * carries current context instead of context as of the last route change. Symmetric with\n * `authTokenProvider`. The `<script>` loader has no config here; it reads\n * `window.matterfact.getPageContext` instead (see resolveDeclaredContext in context.ts).\n * Absent \u21D2 falls back to the static `window.matterfact.context`.\n */\n pageContextProvider?: PageContextProvider;\n /**\n * Render INTO this element instead of floating in the corner \u2014 for a host that has its\n * own side panel, tool drawer or tab and wants the agent to live there.\n *\n * This inverts who owns the chrome. In corner mode the widget owns everything: it draws\n * its own launcher and it tells us how big to be (setOpen/setDocked/resize). Inside\n * someone else's panel, both are theirs \u2014 a widget that resized its host's drawer, or\n * drew a second floating bubble inside it, is just broken. So here we fill the\n * container, ignore the size messages, and the widget skips the launcher and renders\n * the chat directly.\n *\n * The host also owns visibility: showing and hiding their own panel IS the open/close\n * control, so there is nothing for us to add.\n */\n container?: HTMLElement | null;\n /**\n * Turn off ALL host-page observation \u2014 no `host.context`, no `host.focus`, no\n * `host.snapshot`, no `host.activity`, no `host.artifactGrants`. Not a filter on what\n * gets sent: when this is false the loader never installs the DOM/nav/focus/activity\n * observers in the first place, and an inbound `widget.requestSnapshot` is ignored\n * rather than answered. Default `true` \u2014 seeing the page is the widget's whole value\n * proposition; this is the escape hatch for a host that wants agent chat with nothing\n * about the page ever reaching it.\n */\n pageContext?: boolean;\n /**\n * Force dev mode (see `devRequested()` below) without needing `?mfdev=1` on the host\n * URL. This is the programmatic equivalent of that trigger, not a replacement for it \u2014\n * the URL param still works untouched; `mount()` ORs the two together.\n */\n dev?: boolean;\n}\n\nconst DEFAULT_ORIGIN = 'https://app.matterfact.com';\n\n/**\n * Has someone asked to see what the widget is doing? `?mfdev=1` on the HOST page.\n *\n * Driven by the host's own URL rather than a `data-` attribute on purpose: an engineer\n * debugging a page they didn't write can add a query param, but they cannot redeploy the\n * customer's site to change a script tag. It's also ephemeral \u2014 it lasts exactly as long\n * as that URL, so nobody can leave it on by accident.\n *\n * This only surfaces what the widget already collects FROM THE PAGE IT'S ON, to the\n * person whose page it is. It grants no access: the session, the origin allowlist and\n * the route scope are all enforced server-side and none of them consult this.\n */\nfunction devRequested(): boolean {\n try {\n return new URLSearchParams(location.search).get('mfdev') === '1';\n } catch {\n return false;\n }\n}\n\nfunction readConfig(): LoaderConfig | null {\n // `document.currentScript` is null inside a module or after async execution, so\n // fall back to finding our own tag by src.\n const el =\n (document.currentScript as HTMLScriptElement | null) ??\n document.querySelector<HTMLScriptElement>('script[data-key][src*=\"embed\"]');\n\n const publishableKey = el?.dataset.key;\n if (!publishableKey) {\n console.error('[matterfact] missing data-key on the embed script tag');\n return null;\n }\n // `data-container=\"#panel\"` renders into the host's own element instead of the\n // corner. A selector rather than an element, because a script tag can't hand us a\n // reference \u2014 and if it doesn't resolve we fall back to the corner rather than\n // vanish: a widget nobody can find looks identical to a broken install.\n const sel = el?.dataset.container;\n const container = sel ? document.querySelector<HTMLElement>(sel) : null;\n if (sel && !container) {\n console.error(\n `[matterfact] data-container=\"${sel}\" matched nothing; falling back to the corner`,\n );\n }\n\n // `data-page-context=\"off\"` (also \"false\" / \"0\") disables all page observation.\n // Anything else \u2014 including the attribute being absent \u2014 leaves it on. Lowercased\n // once before comparing: a privacy toggle that silently stays ON for \"Off\" or\n // \"FALSE\" (a customer's templating/CMS can easily produce either, unintentionally)\n // is exactly the wrong direction for this to fail in.\n const pageContextAttr = el?.dataset.pageContext?.toLowerCase();\n\n return {\n publishableKey,\n origin: el?.dataset.origin || DEFAULT_ORIGIN,\n theme: (el?.dataset.theme as LoaderConfig['theme']) || 'auto',\n surface: el?.dataset.surface || '',\n container,\n pageContext:\n pageContextAttr !== 'off' &&\n pageContextAttr !== 'false' &&\n pageContextAttr !== '0',\n // No `data-dev` \u2014 see the `dev` field's doc comment: the URL trigger is the\n // point for the script-tag path, so there is deliberately no script-tag knob here.\n // No `data-actions` either: the action policy lives entirely in the lazy chunk,\n // read straight off `window.matterfact.hoist.actions` by hoist-runtime.ts's\n // readActionsConfig() \u2014 see LoaderConfig's class doc and MatterfactAgentProps.actions\n // (react.tsx) for the one remaining way to SET that global programmatically.\n };\n}\n\nconst POS_KEY = 'mf.embed.pos';\n\nclass EmbedHost {\n private iframe: HTMLIFrameElement | null = null;\n private shadow: ShadowRoot | null = null;\n /** Buffered until the widget says it's listening \u2014 postMessage before load is dropped silently. */\n private queue: HostToWidget[] = [];\n private ready = false;\n private open = false;\n /** The persisted box: launcher corner, float size, dock state/side/width. */\n private geo: EmbedGeometry = defaultGeometry();\n /** Float size at resizeStart; resizeMove deltas apply against it (they're cumulative). */\n private resizeBase: { w: number; h: number } | null = null;\n /** Docked width at resizeStart \u2014 the docked panel resizes in one axis only. */\n private dockBase = DEFAULT_DOCK_W;\n /** The widget's logical anchor when the drag began, and where it is now. Deltas arrive\n * cumulative from the press, so the current spot is simply origin + delta \u2014 no need to\n * read the box back, which stops being the thing we're moving the moment it docks. */\n private dragFrom: { x: number; y: number } | null = null;\n private dragAt = { x: 0, y: 0 };\n /** The painted box when the drag began \u2014 the edge test measures what you can SEE\n * touching the screen edge, which is not the anchor once the panel is open. */\n private dragBox = { x: 0, w: 0 };\n /** Growth direction frozen for the drag \u2014 see floatChrome's `growth` param. */\n private dragGrowth: Corner | null = null;\n /**\n * Has this drag been clear of the dock band yet?\n *\n * The resting position is INSIDE the band \u2014 a corner-anchored widget sits MARGIN\n * (20px) from the edge, well within EDGE_DOCK (64px) \u2014 so without this, picking one\n * up docked it instantly, before it had moved anywhere. Docking now requires\n * ENTERING the band rather than merely starting in it.\n */\n private dragLeftBand = false;\n /** Loaded on first open. Holds everything that touches the customer's DOM. */\n private context: Promise<typeof import('./context')> | null = null;\n /** The host element; kept so `destroy()` can remove it (React lifecycle). */\n private hostEl: HTMLDivElement | null = null;\n /** Rendering into the host's own element: they own the box, the chrome and visibility. */\n private readonly inline: boolean;\n /** Host-auth storm guard (see provideAuth). Counts getAuthToken calls; past a cap we stop\n * calling the provider, rather than joining the storm. This host instance OUTLIVES iframe\n * reloads, so \u2014 unlike anything in the widget \u2014 the counter survives the very reload loop\n * that drives the storm. (Kept tiny on purpose: this file has a hard per-page size budget;\n * the widget side carries the windowed/retry-after-settle half.) Terse name = fewer bytes\n * in the stub; it is the host-auth request count. */\n private ac = 0;\n\n constructor(private config: LoaderConfig) {\n this.inline = !!config.container;\n }\n\n mount() {\n // A CLOSED shadow root, on a container that resets every inherited property.\n //\n // We are a guest on someone else's page. Their CSS must not reach into us, our\n // CSS must not leak out, and their JS should not be able to walk into our tree\n // by accident (closed mode: `host.shadowRoot` is null). `contain` stops our\n // layout/paint from invalidating theirs.\n const host = document.createElement('div');\n this.hostEl = host;\n host.id = 'matterfact-embed';\n\n if (this.inline) {\n // The container's box IS the widget's box. No fixed positioning, no z-index\n // scramble, no drag: where this sits is the host's layout problem, which is the\n // entire point of asking for it.\n host.style.cssText = [\n 'all: initial',\n 'position: relative',\n 'display: block',\n 'width: 100%',\n 'height: 100%',\n 'contain: layout style',\n ].join(';');\n this.config.container!.appendChild(host);\n } else {\n this.geo = this.readGeometry();\n host.style.cssText = [\n 'all: initial',\n 'position: fixed',\n // Below the max so a host that genuinely needs to cover us (a modal, a cookie\n // banner they are legally obliged to show) still can.\n 'z-index: 2147483000',\n 'contain: layout style',\n // The host element owns the BOX now (position AND size); the iframe fills it.\n // Transitioning the box is what makes the corner snap and the open/dock read as\n // motion rather than a jump. Suppressed during drag/resize (see those handlers).\n 'transition: width .18s ease, height .18s ease, top .18s ease,' +\n ' right .18s ease, bottom .18s ease, left .18s ease',\n ].join(';');\n document.body.appendChild(host);\n }\n\n this.shadow = host.attachShadow({ mode: 'closed' });\n\n const style = document.createElement('style');\n // Shared prefix, then only what differs. This ships on every page load and the stub\n // has ~no headroom, so the two modes must not each carry a whole stylesheet.\n //\n // Inline fills the host's box and adds nothing: no radius, no shadow. Their panel\n // already has whatever chrome they chose; ours would sit inside it looking like a\n // mistake.\n // The iframe simply FILLS the host element, in both modes. The host owns every\n // dimension, so all the geometry lives in one place (and stays observable in the\n // light DOM \u2014 this shadow root is closed, so nothing in here can be inspected).\n // Only the corner treatment differs, switched off the host's data-mode: a circle\n // while it's the launcher, a card once it's a pill, menu or panel.\n //\n // Hairline ring + soft ambient: the widget must read as a crisp shape on ANY host\n // background, including pure white where a shadow alone bleeds.\n // Only the OPEN panel is a surface of ours. While collapsed the widget draws its\n // own shapes \u2014 the tab, the circle, the menu \u2014 onto the host's page, so the iframe\n // must not paint a card behind them: the host box grows to make room for the menu,\n // and a background there shows up as a slab hanging off the launcher.\n style.textContent =\n ':host{all:initial}' +\n 'iframe{border:0;display:block;width:100%;height:100%;background:transparent}' +\n (this.inline\n ? ''\n : // color-scheme only on the OPEN panel \u2014 on a collapsed launcher it makes the\n // UA paint an opaque canvas base behind our shapes.\n ':host([data-mode=\"float\"]) iframe,:host([data-mode=\"dock\"]) iframe' +\n '{color-scheme:light dark}' +\n ':host([data-mode=\"float\"]) iframe{border-radius:12px;' +\n 'box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029}' +\n ':host([data-mode=\"dock\"]) iframe{box-shadow:0 0 0 1px #00000014,' +\n '0 8px 40px #00000029}' +\n ':host([data-mode=\"dock\"][data-flush=\"right\"]) iframe' +\n '{border-radius:12px 0 0 12px}' +\n ':host([data-mode=\"dock\"][data-flush=\"left\"]) iframe' +\n '{border-radius:0 12px 12px 0}');\n this.shadow.appendChild(style);\n\n const iframe = document.createElement('iframe');\n iframe.title = 'matterfact assistant';\n //\n // `allow-same-origin` is REQUIRED, and leaving it out is the trap.\n //\n // Without it the framed document gets an OPAQUE origin: its postMessage arrives\n // as `event.origin === \"null\"`, so our own origin check rejects the handshake and\n // the bridge silently never connects. It also denies the widget any storage at\n // all \u2014 no session, no auth, ever.\n //\n // The familiar warning \u2014 \"allow-scripts + allow-same-origin lets the frame escape\n // its sandbox\" \u2014 is about a frame that is SAME-ORIGIN WITH THE FRAMER, which can\n // then reach up and delete its own sandbox attribute. Ours is cross-origin to the\n // host by construction (that is the entire security model), so it cannot touch\n // this element. What the sandbox still buys us is real: no top-level navigation,\n // no downloads, no pointer lock.\n // `allow-downloads` lets the chat save a file in place. Every download path\n // is an in-place `<a download>`.click(); WITHOUT this flag a sandboxed frame\n // blocks that and the browser falls back to opening the file in a new tab.\n // It is a sandbox flag, not a permissions-policy `allow` token.\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads',\n );\n // Powerful features need an explicit permissions-policy grant: a cross-origin\n // frame is granted NONE by default, so without `allow` they are hard-blocked\n // no matter what the user approves.\n // - microphone: dictation's getUserMedia.\n // - clipboard-write: the \"Copy\" button on a chat message calls\n // navigator.clipboard.writeText, which throws NotAllowedError without it.\n // The bare tokens scope each grant to the iframe's own origin (the app), not\n // the host page. A host page can still deny either via its own\n // Permissions-Policy response header \u2014 outside our control, which is why the\n // in-app copy path also degrades gracefully rather than throwing.\n iframe.setAttribute('allow', 'microphone; clipboard-write');\n iframe.src =\n `${this.config.origin}/embed/chat?k=${encodeURIComponent(\n this.config.publishableKey,\n )}&o=${encodeURIComponent(location.origin)}` +\n (this.config.surface\n ? `&s=${encodeURIComponent(this.config.surface)}`\n : '') +\n // Either trigger works: the URL param (no redeploy needed) OR the config's\n // programmatic `dev` (the React prop). See both doc comments above.\n (devRequested() || this.config.dev ? '&dev=1' : '') +\n (this.inline ? '&inline=1' : '');\n this.iframe = iframe;\n this.shadow.appendChild(iframe);\n\n window.addEventListener('message', this.onMessage);\n if (!this.inline) {\n // A viewport change can strand a saved box off-page (a laptop undocked from an\n // external monitor is the common one), so re-clamp and re-anchor on resize.\n window.addEventListener('resize', this.onViewportResize);\n this.place();\n }\n }\n\n /**\n * Every message is checked twice, on every single message \u2014 not once at setup.\n *\n * `channel` is a namespace, not a boundary. The origin and source checks are the\n * boundary: any frame on the page can postMessage us, and a page with an ad iframe\n * on it has plenty of frames.\n */\n private onMessage = (event: MessageEvent) => {\n if (event.origin !== this.config.origin) return;\n if (event.source !== this.iframe?.contentWindow) return;\n if (!isEnvelope(event.data)) return;\n\n this.handle(event.data.payload as WidgetToHost);\n };\n\n /**\n * Test seam. The shadow root is CLOSED, so a test cannot reach the iframe to forge a\n * source-valid MessageEvent \u2014 this routes a message through the identical logic\n * without weakening the origin/source checks above, which stay the only real door.\n */\n __testHandle(msg: WidgetToHost) {\n this.handle(msg);\n }\n\n /**\n * The eager stub's own emit-and-swallow for the host's `onEvent` telemetry hook.\n * Deliberately does NOT import the registry's `emitEvent`: pulling the lazy ./context\n * chunk (where the registry lives) into the size-budgeted stub would blow it, so this\n * ~5-line duplication across the bundle boundary is intentional. Host telemetry is\n * host code \u2014 a throw here must never break the widget.\n */\n private emit(e: MatterfactEvent) {\n const cb = (\n globalThis as { matterfact?: { onEvent?: (e: MatterfactEvent) => void } }\n ).matterfact?.onEvent;\n if (typeof cb !== 'function') return;\n try {\n cb(e);\n } catch {\n /* host telemetry must never break the widget */\n }\n }\n\n private handle(msg: WidgetToHost) {\n switch (msg.type) {\n case 'widget.ready':\n this.ready = true;\n this.send({\n type: 'host.ready',\n protocol: PROTOCOL_VERSION,\n origin: location.origin,\n });\n // Theme NOW, not on first open. It used to ride in with the lazy context\n // chunk, which is only fetched when the chat is opened \u2014 so a collapsed\n // launcher sat in the wrong colours until you interacted with it.\n this.send({ type: 'host.theme', mode: this.themeMode() });\n this.flush();\n // Inline has no \"open\" event to hang this off \u2014 the chat is visible the moment\n // the host renders their panel, so the eye has to load now or the agent is\n // blind to the page for the whole session. The corner keeps its lazy load: a\n // page whose visitors never click still pays nothing.\n if (this.inline) void this.loadContext();\n this.emit({ type: 'ready' });\n break;\n\n case 'widget.setOpen':\n // Host telemetry sees the open/close moment regardless of placement (the inline\n // guard below only skips the popup SIZING, which is meaningless inline).\n this.emit({ type: msg.open ? 'open' : 'close' });\n // Inline: the host's panel IS the open/close control. There's no popup to size.\n if (this.inline) break;\n this.open = msg.open;\n this.place();\n // The DOM code is fetched HERE \u2014 the first time someone actually opens the\n // chat \u2014 and never on a page where they don't.\n if (msg.open) void this.loadContext();\n break;\n\n case 'widget.setMode':\n // Inline: the host owns the box; float/dock is not ours to choose.\n if (this.inline) break;\n this.geo.mode = msg.mode;\n this.writeGeometry(this.geo);\n this.place();\n break;\n\n case 'widget.setDockSide':\n if (this.inline) break;\n this.geo.dockSide = msg.side;\n this.writeGeometry(this.geo);\n this.place();\n break;\n\n case 'widget.snapCorner':\n if (this.inline) break;\n // Snapping is a FLOAT affordance; the picker only shows in float mode.\n this.geo.corner = msg.corner;\n this.writeGeometry(this.geo);\n this.place();\n break;\n\n case 'widget.setLauncherRegion': {\n // Only meaningful while collapsed \u2014 open, the panel is already bigger than any\n // pill or menu, and growing the box would fight the panel's own size.\n if (this.inline || this.open) break;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n if (msg.w <= LAUNCHER && msg.h <= LAUNCHER) {\n // Back to the bare launcher \u2014 place() already knows how to draw it.\n this.place();\n } else {\n const region =\n this.geo.mode === 'dock'\n ? dockChrome(this.geo, msg.w, msg.h, vw, vh)\n : floatChrome(this.geo, msg.w, msg.h, vw, vh);\n this.applyBox(region, msg.h > LAUNCHER ? 'menu' : 'pill');\n }\n break;\n }\n\n case 'widget.resize':\n // Inline: the container's height is the host's business, and an auto-grow here\n // would fight their layout every time the transcript got longer.\n if (this.inline) break;\n // Content height auto-grow, for the FLOATING panel only. Docked, the edges own\n // the height; and a manual resize has already written floatH, which place()\n // would restore on the next call anyway.\n if (this.hostEl && this.open && this.geo.mode === 'float') {\n this.hostEl.style.height = `${Math.min(\n msg.height,\n Math.max(0, window.innerHeight - 2 * MARGIN),\n )}px`;\n }\n break;\n\n case 'widget.resizeStart':\n if (this.inline) break;\n this.resizeBase = { w: this.geo.floatW, h: this.geo.floatH };\n this.dockBase = this.geo.dockW;\n // The box must track the pointer exactly; an eased transition lags behind it.\n if (this.hostEl) this.hostEl.style.transition = 'none';\n break;\n\n case 'widget.resizeMove': {\n if (this.inline || !this.resizeBase) break;\n if (this.geo.mode === 'dock') {\n // Docked, only the WIDTH is the user's \u2014 the edges own the height.\n const w = applyDockResizeDelta(\n this.geo.dockSide,\n this.dockBase,\n msg.dx,\n );\n this.geo.dockW = Math.min(\n Math.max(w, MIN_DOCK_W),\n Math.max(0, window.innerWidth - 2 * MARGIN),\n );\n } else {\n const next = applyResizeDelta(\n growthCorner(this.geo, window.innerWidth, window.innerHeight),\n this.resizeBase.w,\n this.resizeBase.h,\n msg.dx,\n msg.dy,\n );\n const fit = clampSize(\n next.w,\n next.h,\n window.innerWidth,\n window.innerHeight,\n MIN_FLOAT_W,\n MIN_FLOAT_H,\n );\n this.geo.floatW = fit.w;\n this.geo.floatH = fit.h;\n }\n this.place();\n break;\n }\n\n case 'widget.resizeEnd':\n if (this.inline) break;\n this.resizeBase = null;\n if (this.hostEl) this.hostEl.style.transition = '';\n this.writeGeometry(this.geo);\n break;\n\n case 'widget.requestContext':\n // The pull path for declared context \u2014 re-read it now (via getPageContext if the\n // host supplied one) and re-publish `host.context`. No-op if pageContext is off.\n void this.loadContext().then((m) => m.provideContext());\n break;\n\n case 'widget.requestSnapshot':\n // pageContext: false \u21D2 `sendSnapshot` itself no-ops (see context.ts) rather\n // than gating here \u2014 this file is the size-budgeted stub, and the check\n // costs nothing extra in the lazy chunk that already owns the DOM read.\n void this.loadContext().then((m) => m.sendSnapshot(this.send));\n break;\n\n case 'widget.readRegion':\n void this.loadContext().then((m) => m.sendRegion(msg.ref, this.send));\n break;\n\n case 'widget.callTool':\n void this.loadContext().then((m) => m.callTool(msg.call, this.send));\n break;\n\n // Everything below is LAUNCHER chrome: there is no launcher inline (the widget\n // doesn't draw one), and moving or hiding the host's own panel from inside it\n // would be us redecorating their app. An older cached loader could still be told\n // any of these by a newer widget, so they're guarded rather than assumed absent.\n case 'widget.dragStart':\n if (this.inline) break;\n this.dragFrom = dragAnchor(\n this.geo,\n window.innerWidth,\n window.innerHeight,\n );\n this.dragAt = { ...this.dragFrom };\n this.dragLeftBand = false;\n this.dragGrowth = growthCorner(\n this.geo,\n window.innerWidth,\n window.innerHeight,\n );\n this.dragBox = visibleBox(\n this.geo,\n this.open,\n window.innerWidth,\n window.innerHeight,\n );\n // Follow the pointer 1:1 while dragging; the ease is for the snap on release.\n if (this.hostEl) this.hostEl.style.transition = 'none';\n break;\n\n case 'widget.dragMove': {\n if (this.inline || !this.dragFrom) break;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n this.dragAt = {\n x: this.dragFrom.x + msg.dx,\n y: this.dragFrom.y + msg.dy,\n };\n // Switch placement DURING the drag, not on release: drag toward a side and it\n // becomes the tab under your cursor, drag away and it pops back to the circle.\n // Deliberately only the EDGE test here \u2014 running the full drop resolver would\n // also snap corners mid-gesture, yanking the widget away while you're still\n // holding it. Corners are resolved on release, where a jump reads as a snap.\n const side = edgeDock(this.dragBox.x + msg.dx, this.dragBox.w, vw);\n if (!side) this.dragLeftBand = true;\n // A tab is already docked, so it may stay docked without having left first;\n // anything else must have been clear of the band at some point in this drag.\n if (side && (this.dragLeftBand || this.geo.mode === 'dock')) {\n this.geo.mode = 'dock';\n this.geo.dockSide = side;\n this.geo.tabY = Math.max(0, this.dragAt.y);\n } else {\n this.geo.mode = 'float';\n this.geo.corner = null;\n this.geo.x = this.dragAt.x;\n this.geo.y = this.dragAt.y;\n }\n this.place();\n break;\n }\n\n case 'widget.dragEnd': {\n if (this.inline || !this.dragFrom) break;\n // The drop resolves the FULL placement \u2014 including the corner snap the live\n // pass deliberately skipped. Restoring the transition first is what makes that\n // final snap animate rather than teleport.\n const at = placeDrop(\n this.dragAt.x,\n this.dragAt.y,\n this.dragBox.x + (this.dragAt.x - this.dragFrom.x),\n this.dragBox.w,\n window.innerWidth,\n window.innerHeight,\n );\n this.geo.mode = at.mode;\n if (at.mode === 'dock') {\n this.geo.dockSide = at.dockSide;\n this.geo.tabY = at.tabY;\n } else {\n this.geo.corner = at.corner;\n this.geo.x = at.x;\n this.geo.y = at.y;\n }\n this.dragFrom = null;\n this.dragGrowth = null;\n if (this.hostEl) this.hostEl.style.transition = '';\n this.place();\n this.writeGeometry(this.geo);\n break;\n }\n\n case 'widget.resetPos':\n if (this.inline) break;\n this.geo = defaultGeometry();\n this.writeGeometry(this.geo);\n this.place();\n break;\n\n case 'widget.hide':\n // Inline: hiding is the host's panel to hide. Blanking their drawer from in\n // here would leave them with an empty box and no way to get it back.\n if (this.inline) break;\n if (this.hostEl) this.hostEl.style.display = 'none';\n break;\n\n case 'widget.needsAuth':\n this.emit({ type: 'auth', phase: 'required' });\n void this.provideAuth();\n break;\n\n case 'widget.navigate':\n this.emit({ type: 'navigate', href: msg.href });\n // Delegated to the lazy context chunk (kept out of the every-page stub \u2014\n // the eager budget is tight). By the time a document deeplink is clicked the\n // chat is open, so context is already loaded.\n void this.loadContext().then((m) => m.navigateHost(msg.href));\n break;\n\n case 'widget.chat':\n // Content-free chat-turn telemetry (phase + opaque chatId), relayed straight\n // to the host's onEvent. Never carries message text.\n this.emit({ type: 'chat', phase: msg.phase, chatId: msg.chatId });\n break;\n }\n }\n\n /**\n * Own a drag for its lifetime.\n *\n * The widget reports the press and then goes quiet: a cross-origin iframe only gets\n * pointer events while the pointer is over it, and a drag leaves that box immediately.\n * So we lay a transparent layer over the whole viewport and track the gesture in the\n * host document, where it can't be lost. The layer also stops the pointer landing on\n * the customer's own UI mid-drag (text selection, hover states, stray clicks).\n *\n * We are the dumb half on purpose \u2014 see `widget.dragMove` in the protocol for why the\n * widget has to own the gesture. All we do is take a delta and place the element.\n */\n /** Write a box onto the host element and stamp the mode the shadow CSS keys off. */\n private applyBox(\n box: BoxStyle,\n mode: 'launcher' | 'tab' | 'pill' | 'menu' | 'float' | 'dock',\n ) {\n if (!this.hostEl || this.inline) return;\n const s = this.hostEl.style;\n s.top = box.top;\n s.right = box.right;\n s.bottom = box.bottom;\n s.left = box.left;\n s.width = box.width;\n s.height = box.height;\n this.hostEl.dataset.mode = mode;\n // The flush shapes (tab, docked panel) round only on the side facing the page.\n if (mode === 'tab' || mode === 'dock')\n this.hostEl.dataset.flush = this.geo.dockSide;\n else delete this.hostEl.dataset.flush;\n }\n\n /** Re-apply the box for whatever state we're in: collapsed launcher, float, or dock. */\n /**\n * The growth direction to use RIGHT NOW \u2014 frozen while a drag is in flight.\n *\n * Every consumer has to agree on this, which is the bug that made the widget\n * flip-flop under small movements near the viewport centre: the box was frozen but\n * `host.geometry` still published a freshly-derived growth, so the WIDGET kept\n * swapping which corner it drew against (and it animates that), twitching back and\n * forth while the loader held perfectly still.\n */\n private growthNow(): Corner {\n return (\n this.dragGrowth ??\n growthCorner(this.geo, window.innerWidth, window.innerHeight)\n );\n }\n\n private place() {\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n if (!this.open) {\n const dock = this.geo.mode === 'dock';\n this.applyBox(\n dock\n ? tabBox(this.geo, vw, vh)\n : floatChrome(this.geo, LAUNCHER, LAUNCHER, vw, vh, this.growthNow()),\n dock ? 'tab' : 'launcher',\n );\n } else if (this.geo.mode === 'dock') {\n this.applyBox(dockBox(this.geo, vw, vh), 'dock');\n } else {\n this.applyBox(floatBox(this.geo, vw, vh, this.growthNow()), 'float');\n }\n this.publishGeometry();\n }\n\n /**\n * Tell the widget where it is. It can't see the viewport and doesn't own the box, so\n * without this it draws its launcher against a stale guess \u2014 which is what made the\n * circle jump when the launcher sat on the left (see `host.geometry` in the protocol).\n */\n private publishGeometry() {\n this.send({\n type: 'host.geometry',\n mode: this.geo.mode,\n dockSide: this.geo.dockSide,\n growth: this.growthNow(),\n });\n }\n\n /** The theme to publish: an explicit data-theme wins, else the OS preference. */\n private themeMode(): 'light' | 'dark' {\n return this.config.theme === 'auto'\n ? matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n : this.config.theme;\n }\n\n private onViewportResize = () => {\n // Re-clamp and re-anchor: a saved box must never be left hanging off a smaller page.\n this.place();\n };\n\n private readGeometry(): EmbedGeometry {\n // First-party storage on the CUSTOMER's origin, so each site remembers its own spot.\n // Never throws: storage is unavailable in some embedding contexts, and a widget that\n // can't remember where it was put is fine \u2014 one that fails to mount is not.\n try {\n return parseGeometry(localStorage.getItem(POS_KEY)) ?? defaultGeometry();\n } catch {\n return defaultGeometry();\n }\n }\n\n private writeGeometry(g: EmbedGeometry) {\n try {\n localStorage.setItem(POS_KEY, serializeGeometry(g));\n } catch {\n /* storage denied \u2014 the box just won't survive the reload */\n }\n }\n\n /**\n * Answer `widget.needsAuth`. If a trusted first-party host has an auth-token provider,\n * call it and hand the token straight to the widget via `host.auth` \u2014 no popup. The\n * provider comes from EITHER the programmatic config (the React `<MatterfactAgent\n * getAuthToken>` prop) OR a global the host page sets for the `<script>` loader:\n *\n * window.matterfact = { getEmbedAuthToken: () => getIdToken(user) };\n *\n * Read fresh at call time (not at readConfig), so a global set after the loader booted\n * \u2014 e.g. once the host's auth is ready \u2014 is still picked up. `expiresAt: 0`: the widget\n * doesn't cache it, it exchanges the token for a rotating embed session anyway.\n *\n * No provider \u21D2 this does nothing. The loader never opens a sign-in popup \u2014 the widget\n * owns interactive sign-in (inline Firebase/email, and Microsoft's own MSAL login popup\n * for Entra, then the PKCE token exchange), so the loader must not also fire its stale\n * `/embed/authorize?k=&o=` popup, which the deployed authorize page no longer accepts.\n */\n private async provideAuth() {\n const provider =\n this.config.authTokenProvider ??\n (globalThis as { matterfact?: { getEmbedAuthToken?: AuthTokenProvider } })\n .matterfact?.getEmbedAuthToken;\n if (!provider) return; // the widget owns interactive sign-in; the loader never opens it\n\n // Storm guard. A healthy host-auth flow asks ONCE \u2014 the widget exchanges the token for a\n // rotating session and never asks again. A flood of `widget.needsAuth` means the token\n // keeps being rejected (wrong audience/scope) and something is remounting the widget in a\n // loop; calling getAuthToken on every one hammers the host's identity provider (MSAL \u2192\n // login.microsoftonline.com), which can get the app RATE-LIMITED. Past the cap we simply\n // stop calling the provider. Silent + minimal by necessity (hard per-page size budget);\n // the widget-side guard is what surfaces the reason to the user and logs the rejection.\n if (++this.ac > 5) return;\n try {\n const token = await provider();\n if (token) this.send({ type: 'host.auth', token, expiresAt: 0 });\n // One emit either way \u2014 a null token is a failure to provide, same as a throw.\n this.emit({ type: 'auth', phase: token ? 'granted' : 'failed' });\n } catch {\n this.emit({ type: 'auth', phase: 'failed' });\n /* no token this time; the widget falls back to its own inline / popup sign-in */\n }\n }\n\n private loadContext() {\n // One chunk, one fetch, cached. Everything that can see the customer's page\n // lives behind this boundary.\n this.context ??= import('./context').then((m) => {\n // The single place the eye actually starts: when pageContext is off, `start()`\n // skips installing every DOM/nav/focus/activity observer \u2014 not a filter over\n // what gets sent afterward, nothing is collected in the first place. Passed\n // through raw: `start`'s default parameter is what turns `undefined` (the\n // option omitted) into \"on\".\n m.start(\n this.send,\n this.config.origin,\n this.config.pageContext,\n this.config.pageContextProvider,\n );\n // start() emits the OS-preference theme. An explicit data-theme on the\n // script tag is the host's choice \u2014 resend it so it wins over auto-detect\n // (a light-branded site must be able to force a light widget on a dark-OS\n // visitor, and vice versa).\n if (this.config.theme !== 'auto') {\n this.send({ type: 'host.theme', mode: this.config.theme });\n }\n return m;\n });\n return this.context;\n }\n\n private send = (msg: HostToWidget) => {\n if (!this.ready) {\n this.queue.push(msg);\n return;\n }\n // targetOrigin is the widget's exact origin \u2014 never '*'. A '*' here would\n // broadcast the customer's page content to whatever happens to be listening.\n this.iframe?.contentWindow?.postMessage(envelope(msg), this.config.origin);\n };\n\n private flush() {\n const pending = this.queue;\n this.queue = [];\n for (const m of pending) this.send(m);\n }\n\n /** Tear down: stop listening and remove the host element. For the React wrapper's\n * unmount \u2014 the vanilla `<script>` loader lives for the page's lifetime and never\n * calls this. */\n destroy() {\n window.removeEventListener('message', this.onMessage);\n window.removeEventListener('resize', this.onViewportResize);\n this.hostEl?.remove();\n this.hostEl = null;\n this.iframe = null;\n this.shadow = null;\n this.ready = false;\n // context.ts's listeners/history-patch/ref-registry are module-global and\n // outlive this instance \u2014 stop() undoes exactly what start() installed. A no-op\n // if the eye was never loaded (chat never opened, so nothing was ever started).\n // This is the unmount-with-no-remount case; start()'s own stop()-first call\n // (see context.ts) is what protects a REMOUNT, which is why both exist.\n void this.context?.then((m) => m.stop());\n }\n}\n\n/**\n * Programmatic entry \u2014 mount the widget from a config object instead of the DOM\n * `<script data-*>`. This is what the React wrapper (`@matterfact/embed/react`) drives;\n * the vanilla loader below reads the script tag and calls this same class.\n */\nexport function mount(config: LoaderConfig): EmbedHost {\n const host = new EmbedHost(config);\n host.mount();\n return host;\n}\n\nexport { EmbedHost, CHANNEL, readConfig };\n", "/**\n * The vanilla `<script>` loader entry \u2014 the browser stub served by the app and dropped\n * onto any page:\n *\n * <script type=\"module\" src=\"https://app.matterfact.com/embed/embed.js\" data-key=\"pk_live_\u2026\"></script>\n *\n * This is the ONLY module with the DOM auto-run side-effect. The reusable core\n * (`./loader`), the npm `.` entry (`./index`), and the React wrapper (`./react`) never\n * import it, so they stay SSR-safe \u2014 importing them touches no `document`.\n */\nimport { mount, readConfig } from './loader';\n\nconst config = readConfig();\nif (config) {\n const boot = () => mount(config);\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', boot, { once: true });\n } else {\n boot();\n }\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["CHANNEL", "envelope", "payload", "id", "isEnvelope", "data", "CORNERS", "defaultGeometry", "clamp", "n", "lo", "hi", "cornerOrigin", "corner", "w", "h", "vw", "vh", "launcherOrigin", "g", "growthCorner", "o", "nearestCorner", "cx", "cy", "snapOrFree", "x", "y", "edgeDock", "dragAnchor", "tabTop", "visibleBox", "open", "clampSize", "placeDrop", "anchorX", "anchorY", "boxLeft", "boxW", "snapped", "side", "clampSize", "w", "h", "vw", "vh", "minW", "minH", "applyResizeDelta", "corner", "startW", "startH", "dx", "dy", "wSign", "hSign", "applyDockResizeDelta", "side", "parseGeometry", "raw", "p", "d", "defaultGeometry", "num", "v", "dv", "CORNERS", "serializeGeometry", "g", "px", "n", "boxAt", "ox", "oy", "right", "bottom", "rawX", "rawY", "x", "clamp", "y", "tabTop", "max", "tabBox", "left", "floatChrome", "growth", "o", "launcherOrigin", "growthCorner", "dockChrome", "floatBox", "g", "vw", "vh", "growth", "f", "clampSize", "floatChrome", "dockBox", "_vh", "w", "left", "px", "DEFAULT_ORIGIN", "devRequested", "readConfig", "el", "publishableKey", "sel", "container", "pageContextAttr", "POS_KEY", "EmbedHost", "config", "defaultGeometry", "event", "isEnvelope", "msg", "envelope", "host", "style", "iframe", "cb", "vw", "vh", "region", "dockChrome", "floatChrome", "w", "applyDockResizeDelta", "next", "applyResizeDelta", "growthCorner", "fit", "clampSize", "m", "dragAnchor", "visibleBox", "side", "edgeDock", "at", "placeDrop", "box", "mode", "s", "dockBox", "floatBox", "dock", "tabBox", "parseGeometry", "g", "serializeGeometry", "provider", "token", "pending", "mount", "config", "readConfig", "boot", "mount"]
|
|
7
7
|
}
|