@helpai/elements 0.7.1 → 0.7.2
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/elements-web-component.esm.js +1 -1
- package/elements-web-component.esm.js.map +1 -1
- package/elements.cjs.js +1 -1
- package/elements.cjs.js.map +1 -1
- package/elements.esm.js +1 -1
- package/elements.esm.js.map +1 -1
- package/elements.js +1 -1
- package/elements.js.map +1 -1
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/style.css +1 -1
- package/web-component.mjs +1 -1
package/elements.esm.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../node_modules/preact/src/constants.js", "../../node_modules/preact/src/util.js", "../../node_modules/preact/src/options.js", "../../node_modules/preact/src/create-element.js", "../../node_modules/preact/src/component.js", "../../node_modules/preact/src/diff/props.js", "../../node_modules/preact/src/create-context.js", "../../node_modules/preact/src/diff/children.js", "../../node_modules/preact/src/diff/index.js", "../../node_modules/preact/src/render.js", "../../node_modules/preact/src/clone-element.js", "../../node_modules/preact/src/diff/catch-error.js", "../../node_modules/preact/hooks/src/index.js", "../../node_modules/@preact/signals-core/src/index.ts", "../../node_modules/@preact/signals/src/index.ts", "../../src/core/brand.ts", "../../src/i18n/strings.ts", "../../src/core/storage.ts", "../../src/core/logger.ts", "../../src/core/config/user-context.ts", "../../src/core/config/defaults.ts", "../../src/core/config/resolve.ts", "../../src/core/config/overrides.ts", "../../src/core/config/merge.ts", "../../src/core/config/attrs.ts", "../../src/core/start-session-shape.ts", "../../src/core/host-commands.ts", "../../src/styles/tokens.css", "../../src/styles/reset.css", "../../src/styles/panel.css", "../../src/styles/standalone.css", "../../src/styles/page.css", "../../src/styles/index.ts", "../../src/shadow/mount.ts", "../../src/ui/host-mode.ts", "../../src/core/auth.ts", "../../src/core/ids.ts", "../../src/stream/types.ts", "../../src/stream/parser.ts", "../../src/stream/context-codec.ts", "../../src/stream/protocol.ts", "../../src/stream/transport.ts", "../../src/stream/messages.ts", "../../src/stream/reducer.ts", "../../src/core/persistence.ts", "../../src/core/feedback/audio.ts", "../../src/core/feedback/defaults.ts", "../../src/core/feedback/haptics.ts", "../../src/core/feedback/index.ts", "../../src/ui/error-message.ts", "../../node_modules/preact/jsx-runtime/src/utils.js", "../../node_modules/preact/src/constants.js", "../../node_modules/preact/jsx-runtime/src/index.js", "../../src/ui/icons.tsx", "../../src/ui/test-ids.ts", "../../src/ui/launcher.tsx", "../../src/ui/resize-grip.tsx", "../../src/ui/use-drag-move.ts", "../../src/ui/action-visibility.ts", "../../src/shadow/a11y.ts", "../../src/ui/initials.ts", "../../src/ui/agent-badge.tsx", "../../src/core/attachments.ts", "../../src/ui/attachment-chip.tsx", "../../src/ui/voice.ts", "../../src/ui/composer.tsx", "../../src/ui/drop-zone.tsx", "../../src/ui/use-file-drop.ts", "../../node_modules/streaming-markdown/smd.js", "../../src/ui/markdown.tsx", "../../src/ui/tool-chip.tsx", "../../src/ui/message-bubble.tsx", "../../src/ui/message-list.tsx", "../../src/ui/use-popover-menu.ts", "../../src/ui/overflow-menu.tsx", "../../src/ui/history-groups.ts", "../../src/ui/conversation-list.tsx", "../../src/ui/chat-history-pane.tsx", "../../src/ui/language-switcher.tsx", "../../src/ui/theme-switcher.tsx", "../../src/ui/suggestions.tsx", "../../src/ui/panel.tsx", "../../src/ui/sidebar.tsx", "../../src/ui/page-shell.tsx", "../../src/ui/home-nav.ts", "../../src/ui/modules/messages.tsx", "../../src/ui/back-header.tsx", "../../src/ui/home-search.tsx", "../../src/ui/list-row.tsx", "../../src/ui/module-state.tsx", "../../src/ui/modules/help.tsx", "../../src/ui/home-card.tsx", "../../src/ui/modules/home.tsx", "../../src/ui/modules/news.tsx", "../../src/ui/modules/registry.ts", "../../src/ui/home-tab-bar.tsx", "../../src/ui/iframe-view.tsx", "../../src/ui/content-view.tsx", "../../src/ui/messenger-home.tsx", "../../src/ui/modules-empty.tsx", "../../src/ui/welcome-animator.ts", "../../src/ui/use-launcher-callout.ts", "../../src/ui/app.tsx", "../../src/ui/error-boundary.tsx", "../../src/shadow/iframe-mount.ts", "../../src/core/events.ts", "../../src/core/hooks.ts", "../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["/** Normal hydration that attaches to a DOM tree but does not diff it. */\nexport const MODE_HYDRATE = 1 << 5;\n/** Signifies this VNode suspended on the previous render */\nexport const MODE_SUSPENDED = 1 << 7;\n/** Indicates that this node needs to be inserted while patching children */\nexport const INSERT_VNODE = 1 << 2;\n/** Indicates a VNode has been matched with another VNode in the diff */\nexport const MATCHED = 1 << 1;\n\n/** Reset all mode flags */\nexport const RESET_MODE = ~(MODE_HYDRATE | MODE_SUSPENDED);\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\nexport const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\nexport const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n\nexport const NULL = null;\nexport const UNDEFINED = undefined;\nexport const EMPTY_OBJ = /** @type {any} */ ({});\nexport const EMPTY_ARR = [];\nexport const IS_NON_DIMENSIONAL =\n\t/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;\n", "import { EMPTY_ARR } from './constants';\n\nexport const isArray = Array.isArray;\n\n/**\n * Assign properties from `props` to `obj`\n * @template O, P The obj and props types\n * @param {O} obj The object to copy properties to\n * @param {P} props The object to copy properties from\n * @returns {O & P}\n */\nexport function assign(obj, props) {\n\t// @ts-expect-error We change the type of `obj` to be `O & P`\n\tfor (let i in props) obj[i] = props[i];\n\treturn /** @type {O & P} */ (obj);\n}\n\n/**\n * Remove a child node from its parent if attached. This is a workaround for\n * IE11 which doesn't support `Element.prototype.remove()`. Using this function\n * is smaller than including a dedicated polyfill.\n * @param {import('./index').ContainerNode} node The node to remove\n */\nexport function removeNode(node) {\n\tif (node && node.parentNode) node.parentNode.removeChild(node);\n}\n\nexport const slice = EMPTY_ARR.slice;\n", "import { _catchError } from './diff/catch-error';\n\n/**\n * The `option` object can potentially contain callback functions\n * that are called during various stages of our renderer. This is the\n * foundation on which all our addons like `preact/debug`, `preact/compat`,\n * and `preact/hooks` are based on. See the `Options` type in `internal.d.ts`\n * for a full list of available option hooks (most editors/IDEs allow you to\n * ctrl+click or cmd+click on mac the type definition below).\n * @type {import('./internal').Options}\n */\nconst options = {\n\t_catchError\n};\n\nexport default options;\n", "import { slice } from './util';\nimport options from './options';\nimport { NULL, UNDEFINED } from './constants';\n\nlet vnodeId = 0;\n\n/**\n * Create an virtual node (used for JSX)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component constructor for this\n * virtual node\n * @param {object | null | undefined} [props] The properties of the virtual node\n * @param {Array<import('.').ComponentChildren>} [children] The children of the\n * virtual node\n * @returns {import('./internal').VNode}\n */\nexport function createElement(type, props, children) {\n\tlet normalizedProps = {},\n\t\tkey,\n\t\tref,\n\t\ti;\n\tfor (i in props) {\n\t\tif (i == 'key') key = props[i];\n\t\telse if (i == 'ref') ref = props[i];\n\t\telse normalizedProps[i] = props[i];\n\t}\n\n\tif (arguments.length > 2) {\n\t\tnormalizedProps.children =\n\t\t\targuments.length > 3 ? slice.call(arguments, 2) : children;\n\t}\n\n\t// If a Component VNode, check for and apply defaultProps\n\t// Note: type may be undefined in development, must never error here.\n\tif (typeof type == 'function' && type.defaultProps != NULL) {\n\t\tfor (i in type.defaultProps) {\n\t\t\tif (normalizedProps[i] === UNDEFINED) {\n\t\t\t\tnormalizedProps[i] = type.defaultProps[i];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn createVNode(type, normalizedProps, key, ref, NULL);\n}\n\n/**\n * Create a VNode (used internally by Preact)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component\n * Constructor for this virtual node\n * @param {object | string | number | null} props The properties of this virtual node.\n * If this virtual node represents a text node, this is the text of the node (string or number).\n * @param {string | number | null} key The key for this virtual node, used when\n * diffing it against its children\n * @param {import('./internal').VNode[\"ref\"]} ref The ref property that will\n * receive a reference to its created child\n * @returns {import('./internal').VNode}\n */\nexport function createVNode(type, props, key, ref, original) {\n\t// V8 seems to be better at detecting type shapes if the object is allocated from the same call site\n\t// Do not inline into createElement and coerceToVNode!\n\t/** @type {import('./internal').VNode} */\n\tconst vnode = {\n\t\ttype,\n\t\tprops,\n\t\tkey,\n\t\tref,\n\t\t_children: NULL,\n\t\t_parent: NULL,\n\t\t_depth: 0,\n\t\t_dom: NULL,\n\t\t_component: NULL,\n\t\tconstructor: UNDEFINED,\n\t\t_original: original == NULL ? ++vnodeId : original,\n\t\t_index: -1,\n\t\t_flags: 0\n\t};\n\n\t// Only invoke the vnode hook if this was *not* a direct copy:\n\tif (original == NULL && options.vnode != NULL) options.vnode(vnode);\n\n\treturn vnode;\n}\n\nexport function createRef() {\n\treturn { current: NULL };\n}\n\nexport function Fragment(props) {\n\treturn props.children;\n}\n\n/**\n * Check if a the argument is a valid Preact VNode.\n * @param {*} vnode\n * @returns {vnode is VNode}\n */\nexport const isValidElement = vnode =>\n\tvnode != NULL && vnode.constructor === UNDEFINED;\n", "import { assign } from './util';\nimport { diff, commitRoot } from './diff/index';\nimport options from './options';\nimport { Fragment } from './create-element';\nimport { MODE_HYDRATE, NULL } from './constants';\n\n/**\n * Base Component class. Provides `setState()` and `forceUpdate()`, which\n * trigger rendering\n * @param {object} props The initial component props\n * @param {object} context The initial context from parent components'\n * getChildContext\n */\nexport function BaseComponent(props, context) {\n\tthis.props = props;\n\tthis.context = context;\n}\n\n/**\n * Update component state and schedule a re-render.\n * @this {import('./internal').Component}\n * @param {object | ((s: object, p: object) => object)} update A hash of state\n * properties to update with new values or a function that given the current\n * state and props returns a new partial state\n * @param {() => void} [callback] A function to be called once component state is\n * updated\n */\nBaseComponent.prototype.setState = function (update, callback) {\n\t// only clone state when copying to nextState the first time.\n\tlet s;\n\tif (this._nextState != NULL && this._nextState != this.state) {\n\t\ts = this._nextState;\n\t} else {\n\t\ts = this._nextState = assign({}, this.state);\n\t}\n\n\tif (typeof update == 'function') {\n\t\t// Some libraries like `immer` mark the current state as readonly,\n\t\t// preventing us from mutating it, so we need to clone it. See #2716\n\t\tupdate = update(assign({}, s), this.props);\n\t}\n\n\tif (update) {\n\t\tassign(s, update);\n\t}\n\n\t// Skip update if updater function returned null\n\tif (update == NULL) return;\n\n\tif (this._vnode) {\n\t\tif (callback) {\n\t\t\tthis._stateCallbacks.push(callback);\n\t\t}\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Immediately perform a synchronous re-render of the component\n * @this {import('./internal').Component}\n * @param {() => void} [callback] A function to be called after component is\n * re-rendered\n */\nBaseComponent.prototype.forceUpdate = function (callback) {\n\tif (this._vnode) {\n\t\t// Set render mode so that we can differentiate where the render request\n\t\t// is coming from. We need this because forceUpdate should never call\n\t\t// shouldComponentUpdate\n\t\tthis._force = true;\n\t\tif (callback) this._renderCallbacks.push(callback);\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Accepts `props` and `state`, and returns a new Virtual DOM tree to build.\n * Virtual DOM is generally constructed via [JSX](https://jasonformat.com/wtf-is-jsx).\n * @param {object} props Props (eg: JSX attributes) received from parent\n * element/component\n * @param {object} state The component's current state\n * @param {object} context Context object, as returned by the nearest\n * ancestor's `getChildContext()`\n * @returns {ComponentChildren | void}\n */\nBaseComponent.prototype.render = Fragment;\n\n/**\n * @param {import('./internal').VNode} vnode\n * @param {number | null} [childIndex]\n */\nexport function getDomSibling(vnode, childIndex) {\n\tif (childIndex == NULL) {\n\t\t// Use childIndex==null as a signal to resume the search from the vnode's sibling\n\t\treturn vnode._parent\n\t\t\t? getDomSibling(vnode._parent, vnode._index + 1)\n\t\t\t: NULL;\n\t}\n\n\tlet sibling;\n\tfor (; childIndex < vnode._children.length; childIndex++) {\n\t\tsibling = vnode._children[childIndex];\n\n\t\tif (sibling != NULL && sibling._dom != NULL) {\n\t\t\t// Since updateParentDomPointers keeps _dom pointer correct,\n\t\t\t// we can rely on _dom to tell us if this subtree contains a\n\t\t\t// rendered DOM node, and what the first rendered DOM node is\n\t\t\treturn sibling._dom;\n\t\t}\n\t}\n\n\t// If we get here, we have not found a DOM node in this vnode's children.\n\t// We must resume from this vnode's sibling (in it's parent _children array)\n\t// Only climb up and search the parent if we aren't searching through a DOM\n\t// VNode (meaning we reached the DOM parent of the original vnode that began\n\t// the search)\n\treturn typeof vnode.type == 'function' ? getDomSibling(vnode) : NULL;\n}\n\n/**\n * Trigger in-place re-rendering of a component.\n * @param {import('./internal').Component} component The component to rerender\n */\nfunction renderComponent(component) {\n\tif (component._parentDom && component._dirty) {\n\t\tlet oldVNode = component._vnode,\n\t\t\toldDom = oldVNode._dom,\n\t\t\tcommitQueue = [],\n\t\t\trefQueue = [],\n\t\t\tnewVNode = assign({}, oldVNode);\n\t\tnewVNode._original = oldVNode._original + 1;\n\t\tif (options.vnode) options.vnode(newVNode);\n\n\t\tdiff(\n\t\t\tcomponent._parentDom,\n\t\t\tnewVNode,\n\t\t\toldVNode,\n\t\t\tcomponent._globalContext,\n\t\t\tcomponent._parentDom.namespaceURI,\n\t\t\toldVNode._flags & MODE_HYDRATE ? [oldDom] : NULL,\n\t\t\tcommitQueue,\n\t\t\toldDom == NULL ? getDomSibling(oldVNode) : oldDom,\n\t\t\t!!(oldVNode._flags & MODE_HYDRATE),\n\t\t\trefQueue\n\t\t);\n\n\t\tnewVNode._original = oldVNode._original;\n\t\tnewVNode._parent._children[newVNode._index] = newVNode;\n\t\tcommitRoot(commitQueue, newVNode, refQueue);\n\t\toldVNode._dom = oldVNode._parent = null;\n\n\t\tif (newVNode._dom != oldDom) {\n\t\t\tupdateParentDomPointers(newVNode);\n\t\t}\n\t}\n}\n\n/**\n * @param {import('./internal').VNode} vnode\n */\nfunction updateParentDomPointers(vnode) {\n\tif ((vnode = vnode._parent) != NULL && vnode._component != NULL) {\n\t\tvnode._dom = vnode._component.base = NULL;\n\t\tvnode._children.some(child => {\n\t\t\tif (child != NULL && child._dom != NULL) {\n\t\t\t\treturn (vnode._dom = vnode._component.base = child._dom);\n\t\t\t}\n\t\t});\n\n\t\treturn updateParentDomPointers(vnode);\n\t}\n}\n\n/**\n * The render queue\n * @type {Array<import('./internal').Component>}\n */\nlet rerenderQueue = [];\n\n/*\n * The value of `Component.debounce` must asynchronously invoke the passed in callback. It is\n * important that contributors to Preact can consistently reason about what calls to `setState`, etc.\n * do, and when their effects will be applied. See the links below for some further reading on designing\n * asynchronous APIs.\n * * [Designing APIs for Asynchrony](https://blog.izs.me/2013/08/designing-apis-for-asynchrony)\n * * [Callbacks synchronous and asynchronous](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)\n */\n\nlet prevDebounce;\n\nconst defer =\n\ttypeof Promise == 'function'\n\t\t? Promise.prototype.then.bind(Promise.resolve())\n\t\t: setTimeout;\n\n/**\n * Enqueue a rerender of a component\n * @param {import('./internal').Component} c The component to rerender\n */\nexport function enqueueRender(c) {\n\tif (\n\t\t(!c._dirty &&\n\t\t\t(c._dirty = true) &&\n\t\t\trerenderQueue.push(c) &&\n\t\t\t!process._rerenderCount++) ||\n\t\tprevDebounce != options.debounceRendering\n\t) {\n\t\tprevDebounce = options.debounceRendering;\n\t\t(prevDebounce || defer)(process);\n\t}\n}\n\n/**\n * @param {import('./internal').Component} a\n * @param {import('./internal').Component} b\n */\nconst depthSort = (a, b) => a._vnode._depth - b._vnode._depth;\n\n/** Flush the render queue by rerendering all queued components */\nfunction process() {\n\ttry {\n\t\tlet c,\n\t\t\tl = 1;\n\n\t\t// Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary\n\t\t// process() calls from getting scheduled while `queue` is still being consumed.\n\t\twhile (rerenderQueue.length) {\n\t\t\t// Keep the rerender queue sorted by (depth, insertion order). The queue\n\t\t\t// will initially be sorted on the first iteration only if it has more than 1 item.\n\t\t\t//\n\t\t\t// New items can be added to the queue e.g. when rerendering a provider, so we want to\n\t\t\t// keep the order from top to bottom with those new items so we can handle them in a\n\t\t\t// single pass\n\t\t\tif (rerenderQueue.length > l) {\n\t\t\t\trerenderQueue.sort(depthSort);\n\t\t\t}\n\n\t\t\tc = rerenderQueue.shift();\n\t\t\tl = rerenderQueue.length;\n\n\t\t\trenderComponent(c);\n\t\t}\n\t} finally {\n\t\trerenderQueue.length = process._rerenderCount = 0;\n\t}\n}\n\nprocess._rerenderCount = 0;\n", "import { IS_NON_DIMENSIONAL, NULL, SVG_NAMESPACE } from '../constants';\nimport options from '../options';\n\n// Per-instance unique key for event clock stamps. Each Preact copy on the page\n// gets its own random suffix so that `_dispatched` / `_attached` properties on\n// shared event objects and handler functions cannot collide across instances.\n// ~1 in 60M collision odds - if you have that many praect versions on the page,\n// you deserve some weird bugs.\n// In 11 we can replace this with a\n// Symbol\nlet _id = Math.random().toString(8),\n\tEVENT_DISPATCHED = '__d' + _id,\n\tEVENT_ATTACHED = '__a' + _id;\n\nfunction setStyle(style, key, value) {\n\tif (key[0] == '-') {\n\t\tstyle.setProperty(key, value == NULL ? '' : value);\n\t} else if (value == NULL) {\n\t\tstyle[key] = '';\n\t} else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {\n\t\tstyle[key] = value;\n\t} else {\n\t\tstyle[key] = value + 'px';\n\t}\n}\n\nconst CAPTURE_REGEX = /(PointerCapture)$|Capture$/i;\n\n// A logical clock to solve issues like https://github.com/preactjs/preact/issues/3927.\n// When the DOM performs an event it leaves micro-ticks in between bubbling up which means that\n// an event can trigger on a newly reated DOM-node while the event bubbles up.\n//\n// Originally inspired by Vue\n// (https://github.com/vuejs/core/blob/caeb8a68811a1b0f79/packages/runtime-dom/src/modules/events.ts#L90-L101),\n// but modified to use a logical clock instead of Date.now() in case event handlers get attached\n// and events get dispatched during the same millisecond.\n//\n// The clock is incremented after each new event dispatch. This allows 1 000 000 new events\n// per second for over 280 years before the value reaches Number.MAX_SAFE_INTEGER (2**53 - 1).\nlet eventClock = 0;\n\n/**\n * Set a property value on a DOM node\n * @param {import('../internal').PreactElement} dom The DOM node to modify\n * @param {string} name The name of the property to set\n * @param {*} value The value to set the property to\n * @param {*} oldValue The old value the property had\n * @param {string} namespace Whether or not this DOM node is an SVG node or not\n */\nexport function setProperty(dom, name, value, oldValue, namespace) {\n\tlet useCapture;\n\n\to: if (name == 'style') {\n\t\tif (typeof value == 'string') {\n\t\t\tdom.style.cssText = value;\n\t\t} else {\n\t\t\tif (typeof oldValue == 'string') {\n\t\t\t\tdom.style.cssText = oldValue = '';\n\t\t\t}\n\n\t\t\tif (oldValue) {\n\t\t\t\tfor (name in oldValue) {\n\t\t\t\t\tif (!(value && name in value)) {\n\t\t\t\t\t\tsetStyle(dom.style, name, '');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (value) {\n\t\t\t\tfor (name in value) {\n\t\t\t\t\tif (!oldValue || value[name] != oldValue[name]) {\n\t\t\t\t\t\tsetStyle(dom.style, name, value[name]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6\n\telse if (name[0] == 'o' && name[1] == 'n') {\n\t\tuseCapture = name != (name = name.replace(CAPTURE_REGEX, '$1'));\n\t\tconst lowerCaseName = name.toLowerCase();\n\n\t\t// Infer correct casing for DOM built-in events:\n\t\tif (lowerCaseName in dom || name == 'onFocusOut' || name == 'onFocusIn')\n\t\t\tname = lowerCaseName.slice(2);\n\t\telse name = name.slice(2);\n\n\t\tif (!dom._listeners) dom._listeners = {};\n\t\tdom._listeners[name + useCapture] = value;\n\n\t\tif (value) {\n\t\t\tif (!oldValue) {\n\t\t\t\tvalue[EVENT_ATTACHED] = eventClock;\n\t\t\t\tdom.addEventListener(\n\t\t\t\t\tname,\n\t\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\t\tuseCapture\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tvalue[EVENT_ATTACHED] = oldValue[EVENT_ATTACHED];\n\t\t\t}\n\t\t} else {\n\t\t\tdom.removeEventListener(\n\t\t\t\tname,\n\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\tuseCapture\n\t\t\t);\n\t\t}\n\t} else {\n\t\tif (namespace == SVG_NAMESPACE) {\n\t\t\t// Normalize incorrect prop usage for SVG:\n\t\t\t// - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)\n\t\t\t// - className --> class\n\t\t\tname = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's');\n\t\t} else if (\n\t\t\tname != 'width' &&\n\t\t\tname != 'height' &&\n\t\t\tname != 'href' &&\n\t\t\tname != 'list' &&\n\t\t\tname != 'form' &&\n\t\t\t// Default value in browsers is `-1` and an empty string is\n\t\t\t// cast to `0` instead\n\t\t\tname != 'tabIndex' &&\n\t\t\tname != 'download' &&\n\t\t\tname != 'rowSpan' &&\n\t\t\tname != 'colSpan' &&\n\t\t\tname != 'role' &&\n\t\t\tname != 'popover' &&\n\t\t\tname in dom\n\t\t) {\n\t\t\ttry {\n\t\t\t\tdom[name] = value == NULL ? '' : value;\n\t\t\t\t// labelled break is 1b smaller here than a return statement (sorry)\n\t\t\t\tbreak o;\n\t\t\t} catch (e) {}\n\t\t}\n\n\t\t// aria- and data- attributes have no boolean representation.\n\t\t// A `false` value is different from the attribute not being\n\t\t// present, so we can't remove it. For non-boolean aria\n\t\t// attributes we could treat false as a removal, but the\n\t\t// amount of exceptions would cost too many bytes. On top of\n\t\t// that other frameworks generally stringify `false`.\n\n\t\tif (typeof value == 'function') {\n\t\t\t// never serialize functions as attribute values\n\t\t} else if (value != NULL && (value !== false || name[4] == '-')) {\n\t\t\tdom.setAttribute(name, name == 'popover' && value == true ? '' : value);\n\t\t} else {\n\t\t\tdom.removeAttribute(name);\n\t\t}\n\t}\n}\n\n/**\n * Create an event proxy function.\n * @param {boolean} useCapture Is the event handler for the capture phase.\n * @private\n */\nfunction createEventProxy(useCapture) {\n\t/**\n\t * Proxy an event to hooked event handlers\n\t * @param {import('../internal').PreactEvent} e The event object from the browser\n\t * @private\n\t */\n\treturn function (e) {\n\t\tif (this._listeners) {\n\t\t\tconst eventHandler = this._listeners[e.type + useCapture];\n\t\t\tif (e[EVENT_DISPATCHED] == NULL) {\n\t\t\t\te[EVENT_DISPATCHED] = eventClock++;\n\n\t\t\t\t// When `e[EVENT_DISPATCHED]` is smaller than the time when the targeted event\n\t\t\t\t// handler was attached we know we have bubbled up to an element that was added\n\t\t\t\t// during patching the DOM.\n\t\t\t} else if (e[EVENT_DISPATCHED] < eventHandler[EVENT_ATTACHED]) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn eventHandler(options.event ? options.event(e) : e);\n\t\t}\n\t};\n}\n\nconst eventProxy = createEventProxy(false);\nconst eventProxyCapture = createEventProxy(true);\n", "import { enqueueRender } from './component';\nimport { NULL } from './constants';\n\nexport let i = 0;\n\nexport function createContext(defaultValue) {\n\tfunction Context(props) {\n\t\tif (!this.getChildContext) {\n\t\t\t/** @type {Set<import('./internal').Component> | null} */\n\t\t\tlet subs = new Set();\n\t\t\tlet ctx = {};\n\t\t\tctx[Context._id] = this;\n\n\t\t\tthis.getChildContext = () => ctx;\n\n\t\t\tthis.componentWillUnmount = () => {\n\t\t\t\tsubs = NULL;\n\t\t\t};\n\n\t\t\tthis.shouldComponentUpdate = function (_props) {\n\t\t\t\t// @ts-expect-error even\n\t\t\t\tif (this.props.value != _props.value) {\n\t\t\t\t\tsubs.forEach(c => {\n\t\t\t\t\t\tc._force = true;\n\t\t\t\t\t\tenqueueRender(c);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.sub = c => {\n\t\t\t\tsubs.add(c);\n\t\t\t\tlet old = c.componentWillUnmount;\n\t\t\t\tc.componentWillUnmount = () => {\n\t\t\t\t\tif (subs) {\n\t\t\t\t\t\tsubs.delete(c);\n\t\t\t\t\t}\n\t\t\t\t\tif (old) old.call(c);\n\t\t\t\t};\n\t\t\t};\n\t\t}\n\n\t\treturn props.children;\n\t}\n\n\tContext._id = '__cC' + i++;\n\tContext._defaultValue = defaultValue;\n\n\t/** @type {import('./internal').FunctionComponent} */\n\tContext.Consumer = (props, contextValue) => {\n\t\treturn props.children(contextValue);\n\t};\n\n\t// we could also get rid of _contextRef entirely\n\tContext.Provider =\n\t\tContext._contextRef =\n\t\tContext.Consumer.contextType =\n\t\t\tContext;\n\n\treturn Context;\n}\n", "import { diff, unmount, applyRef } from './index';\nimport { createVNode, Fragment } from '../create-element';\nimport {\n\tEMPTY_OBJ,\n\tEMPTY_ARR,\n\tINSERT_VNODE,\n\tMATCHED,\n\tUNDEFINED,\n\tNULL\n} from '../constants';\nimport { isArray } from '../util';\nimport { getDomSibling } from '../component';\n\n/**\n * @typedef {import('../internal').ComponentChildren} ComponentChildren\n * @typedef {import('../internal').Component} Component\n * @typedef {import('../internal').PreactElement} PreactElement\n * @typedef {import('../internal').VNode} VNode\n */\n\n/**\n * Diff the children of a virtual node\n * @param {PreactElement} parentDom The DOM element whose children are being\n * diffed\n * @param {ComponentChildren[]} renderResult\n * @param {VNode} newParentVNode The new virtual node whose children should be\n * diff'ed against oldParentVNode\n * @param {VNode} oldParentVNode The old virtual node whose children should be\n * diff'ed against newParentVNode\n * @param {object} globalContext The current context object - modified by\n * getChildContext\n * @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)\n * @param {Array<PreactElement>} excessDomChildren\n * @param {Array<Component>} commitQueue List of components which have callbacks\n * to invoke in commitRoot\n * @param {PreactElement} oldDom The current attached DOM element any new dom\n * elements should be placed around. Likely `null` on first render (except when\n * hydrating). Can be a sibling DOM element when diffing Fragments that have\n * siblings. In most cases, it starts out as `oldChildren[0]._dom`.\n * @param {boolean} isHydrating Whether or not we are in hydration\n * @param {any[]} refQueue an array of elements needed to invoke refs\n */\nexport function diffChildren(\n\tparentDom,\n\trenderResult,\n\tnewParentVNode,\n\toldParentVNode,\n\tglobalContext,\n\tnamespace,\n\texcessDomChildren,\n\tcommitQueue,\n\toldDom,\n\tisHydrating,\n\trefQueue\n) {\n\tlet i,\n\t\t/** @type {VNode} */\n\t\toldVNode,\n\t\t/** @type {VNode} */\n\t\tchildVNode,\n\t\t/** @type {PreactElement} */\n\t\tnewDom,\n\t\t/** @type {PreactElement} */\n\t\tfirstChildDom;\n\n\t// This is a compression of oldParentVNode!=null && oldParentVNode != EMPTY_OBJ && oldParentVNode._children || EMPTY_ARR\n\t// as EMPTY_OBJ._children should be `undefined`.\n\t/** @type {VNode[]} */\n\tlet oldChildren = (oldParentVNode && oldParentVNode._children) || EMPTY_ARR;\n\n\tlet newChildrenLength = renderResult.length;\n\n\toldDom = constructNewChildrenArray(\n\t\tnewParentVNode,\n\t\trenderResult,\n\t\toldChildren,\n\t\toldDom,\n\t\tnewChildrenLength\n\t);\n\n\tfor (i = 0; i < newChildrenLength; i++) {\n\t\tchildVNode = newParentVNode._children[i];\n\t\tif (childVNode == NULL) continue;\n\n\t\t// At this point, constructNewChildrenArray has assigned _index to be the\n\t\t// matchingIndex for this VNode's oldVNode (or -1 if there is no oldVNode).\n\t\toldVNode =\n\t\t\t(childVNode._index != -1 && oldChildren[childVNode._index]) || EMPTY_OBJ;\n\n\t\t// Update childVNode._index to its final index\n\t\tchildVNode._index = i;\n\n\t\t// Morph the old element into the new one, but don't append it to the dom yet\n\t\tlet result = diff(\n\t\t\tparentDom,\n\t\t\tchildVNode,\n\t\t\toldVNode,\n\t\t\tglobalContext,\n\t\t\tnamespace,\n\t\t\texcessDomChildren,\n\t\t\tcommitQueue,\n\t\t\toldDom,\n\t\t\tisHydrating,\n\t\t\trefQueue\n\t\t);\n\n\t\t// Adjust DOM nodes\n\t\tnewDom = childVNode._dom;\n\t\tif (childVNode.ref && oldVNode.ref != childVNode.ref) {\n\t\t\tif (oldVNode.ref) {\n\t\t\t\tapplyRef(oldVNode.ref, NULL, childVNode);\n\t\t\t}\n\t\t\trefQueue.push(\n\t\t\t\tchildVNode.ref,\n\t\t\t\tchildVNode._component || newDom,\n\t\t\t\tchildVNode\n\t\t\t);\n\t\t}\n\n\t\tif (firstChildDom == NULL && newDom != NULL) {\n\t\t\tfirstChildDom = newDom;\n\t\t}\n\n\t\tlet shouldPlace = !!(childVNode._flags & INSERT_VNODE);\n\t\tif (shouldPlace || oldVNode._children === childVNode._children) {\n\t\t\toldDom = insert(childVNode, oldDom, parentDom, shouldPlace);\n\n\t\t\t// When a matched VNode is physically moved via INSERT_VNODE, its old\n\t\t\t// _dom pointer becomes a stale positional reference. Clear it so that\n\t\t\t// getDomSibling (called from nested diffs) won't return this stale\n\t\t\t// reference and mis-place subsequent DOM nodes. See #5065.\n\t\t\tif (shouldPlace && oldVNode._dom) {\n\t\t\t\toldVNode._dom = NULL;\n\t\t\t}\n\t\t} else if (typeof childVNode.type == 'function' && result !== UNDEFINED) {\n\t\t\toldDom = result;\n\t\t} else if (newDom) {\n\t\t\toldDom = newDom.nextSibling;\n\t\t}\n\n\t\t// Unset diffing flags\n\t\tchildVNode._flags &= ~(INSERT_VNODE | MATCHED);\n\t}\n\n\tnewParentVNode._dom = firstChildDom;\n\n\treturn oldDom;\n}\n\n/**\n * @param {VNode} newParentVNode\n * @param {ComponentChildren[]} renderResult\n * @param {VNode[]} oldChildren\n */\nfunction constructNewChildrenArray(\n\tnewParentVNode,\n\trenderResult,\n\toldChildren,\n\toldDom,\n\tnewChildrenLength\n) {\n\t/** @type {number} */\n\tlet i;\n\t/** @type {VNode} */\n\tlet childVNode;\n\t/** @type {VNode} */\n\tlet oldVNode;\n\n\tlet oldChildrenLength = oldChildren.length,\n\t\tremainingOldChildren = oldChildrenLength;\n\n\tlet skew = 0;\n\n\tnewParentVNode._children = new Array(newChildrenLength);\n\tfor (i = 0; i < newChildrenLength; i++) {\n\t\t// @ts-expect-error We are reusing the childVNode variable to hold both the\n\t\t// pre and post normalized childVNode\n\t\tchildVNode = renderResult[i];\n\n\t\tif (\n\t\t\tchildVNode == NULL ||\n\t\t\ttypeof childVNode == 'boolean' ||\n\t\t\ttypeof childVNode == 'function'\n\t\t) {\n\t\t\tnewParentVNode._children[i] = NULL;\n\t\t\tcontinue;\n\t\t}\n\t\t// If this newVNode is being reused (e.g. <div>{reuse}{reuse}</div>) in the same diff,\n\t\t// or we are rendering a component (e.g. setState) copy the oldVNodes so it can have\n\t\t// it's own DOM & etc. pointers\n\t\telse if (\n\t\t\ttypeof childVNode == 'string' ||\n\t\t\ttypeof childVNode == 'number' ||\n\t\t\t// eslint-disable-next-line valid-typeof\n\t\t\ttypeof childVNode == 'bigint' ||\n\t\t\tchildVNode.constructor == String\n\t\t) {\n\t\t\tchildVNode = newParentVNode._children[i] = createVNode(\n\t\t\t\tNULL,\n\t\t\t\tchildVNode,\n\t\t\t\tNULL,\n\t\t\t\tNULL,\n\t\t\t\tNULL\n\t\t\t);\n\t\t} else if (isArray(childVNode)) {\n\t\t\tchildVNode = newParentVNode._children[i] = createVNode(\n\t\t\t\tFragment,\n\t\t\t\t{ children: childVNode },\n\t\t\t\tNULL,\n\t\t\t\tNULL,\n\t\t\t\tNULL\n\t\t\t);\n\t\t} else if (childVNode.constructor === UNDEFINED && childVNode._depth > 0) {\n\t\t\t// VNode is already in use, clone it. This can happen in the following\n\t\t\t// scenario:\n\t\t\t// const reuse = <div />\n\t\t\t// <div>{reuse}<span />{reuse}</div>\n\t\t\tchildVNode = newParentVNode._children[i] = createVNode(\n\t\t\t\tchildVNode.type,\n\t\t\t\tchildVNode.props,\n\t\t\t\tchildVNode.key,\n\t\t\t\tchildVNode.ref ? childVNode.ref : NULL,\n\t\t\t\tchildVNode._original\n\t\t\t);\n\t\t} else {\n\t\t\tnewParentVNode._children[i] = childVNode;\n\t\t}\n\n\t\tconst skewedIndex = i + skew;\n\t\tchildVNode._parent = newParentVNode;\n\t\tchildVNode._depth = newParentVNode._depth + 1;\n\n\t\t// Temporarily store the matchingIndex on the _index property so we can pull\n\t\t// out the oldVNode in diffChildren. We'll override this to the VNode's\n\t\t// final index after using this property to get the oldVNode\n\t\tconst matchingIndex = (childVNode._index = findMatchingIndex(\n\t\t\tchildVNode,\n\t\t\toldChildren,\n\t\t\tskewedIndex,\n\t\t\tremainingOldChildren\n\t\t));\n\n\t\toldVNode = NULL;\n\t\tif (matchingIndex != -1) {\n\t\t\toldVNode = oldChildren[matchingIndex];\n\t\t\tremainingOldChildren--;\n\t\t\tif (oldVNode) {\n\t\t\t\toldVNode._flags |= MATCHED;\n\t\t\t}\n\t\t}\n\n\t\t// Here, we define isMounting for the purposes of the skew diffing\n\t\t// algorithm. Nodes that are unsuspending are considered mounting and we detect\n\t\t// this by checking if oldVNode._original == null\n\t\tconst isMounting = oldVNode == NULL || oldVNode._original == NULL;\n\n\t\tif (isMounting) {\n\t\t\tif (matchingIndex == -1) {\n\t\t\t\t// When the array of children is growing we need to decrease the skew\n\t\t\t\t// as we are adding a new element to the array.\n\t\t\t\t// Example:\n\t\t\t\t// [1, 2, 3] --> [0, 1, 2, 3]\n\t\t\t\t// oldChildren newChildren\n\t\t\t\t//\n\t\t\t\t// The new element is at index 0, so our skew is 0,\n\t\t\t\t// we need to decrease the skew as we are adding a new element.\n\t\t\t\t// The decrease will cause us to compare the element at position 1\n\t\t\t\t// with value 1 with the element at position 0 with value 0.\n\t\t\t\t//\n\t\t\t\t// A linear concept is applied when the array is shrinking,\n\t\t\t\t// if the length is unchanged we can assume that no skew\n\t\t\t\t// changes are needed.\n\t\t\t\tif (newChildrenLength > oldChildrenLength) {\n\t\t\t\t\tskew--;\n\t\t\t\t} else if (newChildrenLength < oldChildrenLength) {\n\t\t\t\t\tskew++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we are mounting a DOM VNode, mark it for insertion\n\t\t\tif (typeof childVNode.type != 'function') {\n\t\t\t\tchildVNode._flags |= INSERT_VNODE;\n\t\t\t}\n\t\t} else if (matchingIndex != skewedIndex) {\n\t\t\t// When we move elements around i.e. [0, 1, 2] --> [1, 0, 2]\n\t\t\t// --> we diff 1, we find it at position 1 while our skewed index is 0 and our skew is 0\n\t\t\t// we set the skew to 1 as we found an offset.\n\t\t\t// --> we diff 0, we find it at position 0 while our skewed index is at 2 and our skew is 1\n\t\t\t// this makes us increase the skew again.\n\t\t\t// --> we diff 2, we find it at position 2 while our skewed index is at 4 and our skew is 2\n\t\t\t//\n\t\t\t// this becomes an optimization question where currently we see a 1 element offset as an insertion\n\t\t\t// or deletion i.e. we optimize for [0, 1, 2] --> [9, 0, 1, 2]\n\t\t\t// while a more than 1 offset we see as a swap.\n\t\t\t// We could probably build heuristics for having an optimized course of action here as well, but\n\t\t\t// might go at the cost of some bytes.\n\t\t\t//\n\t\t\t// If we wanted to optimize for i.e. only swaps we'd just do the last two code-branches and have\n\t\t\t// only the first item be a re-scouting and all the others fall in their skewed counter-part.\n\t\t\t// We could also further optimize for swaps\n\t\t\tif (matchingIndex == skewedIndex - 1) {\n\t\t\t\tskew--;\n\t\t\t} else if (matchingIndex == skewedIndex + 1) {\n\t\t\t\tskew++;\n\t\t\t} else {\n\t\t\t\tif (matchingIndex > skewedIndex) {\n\t\t\t\t\tskew--;\n\t\t\t\t} else {\n\t\t\t\t\tskew++;\n\t\t\t\t}\n\n\t\t\t\t// Move this VNode's DOM if the original index (matchingIndex) doesn't\n\t\t\t\t// match the new skew index (i + new skew)\n\t\t\t\t// In the former two branches we know that it matches after skewing\n\t\t\t\tchildVNode._flags |= INSERT_VNODE;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Remove remaining oldChildren if there are any. Loop forwards so that as we\n\t// unmount DOM from the beginning of the oldChildren, we can adjust oldDom to\n\t// point to the next child, which needs to be the first DOM node that won't be\n\t// unmounted.\n\tif (remainingOldChildren) {\n\t\tfor (i = 0; i < oldChildrenLength; i++) {\n\t\t\toldVNode = oldChildren[i];\n\t\t\tif (oldVNode != NULL && (oldVNode._flags & MATCHED) == 0) {\n\t\t\t\tif (oldVNode._dom == oldDom) {\n\t\t\t\t\toldDom = getDomSibling(oldVNode);\n\t\t\t\t}\n\n\t\t\t\tunmount(oldVNode, oldVNode);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn oldDom;\n}\n\n/**\n * @param {VNode} parentVNode\n * @param {PreactElement} oldDom\n * @param {PreactElement} parentDom\n * @param {boolean} shouldPlace\n * @returns {PreactElement}\n */\nfunction insert(parentVNode, oldDom, parentDom, shouldPlace) {\n\t// Note: VNodes in nested suspended trees may be missing _children.\n\n\tif (typeof parentVNode.type == 'function') {\n\t\tlet children = parentVNode._children;\n\t\tfor (let i = 0; children && i < children.length; i++) {\n\t\t\tif (children[i]) {\n\t\t\t\t// If we enter this code path on sCU bailout, where we copy\n\t\t\t\t// oldVNode._children to newVNode._children, we need to update the old\n\t\t\t\t// children's _parent pointer to point to the newVNode (parentVNode\n\t\t\t\t// here).\n\t\t\t\tchildren[i]._parent = parentVNode;\n\t\t\t\toldDom = insert(children[i], oldDom, parentDom, shouldPlace);\n\t\t\t}\n\t\t}\n\n\t\treturn oldDom;\n\t} else if (parentVNode._dom != oldDom) {\n\t\tif (shouldPlace) {\n\t\t\tif (oldDom && parentVNode.type && !oldDom.parentNode) {\n\t\t\t\toldDom = getDomSibling(parentVNode);\n\t\t\t}\n\t\t\tparentDom.insertBefore(parentVNode._dom, oldDom || NULL);\n\t\t}\n\t\toldDom = parentVNode._dom;\n\t}\n\n\tdo {\n\t\toldDom = oldDom && oldDom.nextSibling;\n\t} while (oldDom != NULL && oldDom.nodeType == 8);\n\n\treturn oldDom;\n}\n\n/**\n * Flatten and loop through the children of a virtual node\n * @param {ComponentChildren} children The unflattened children of a virtual\n * node\n * @returns {VNode[]}\n */\nexport function toChildArray(children, out) {\n\tout = out || [];\n\tif (children == NULL || typeof children == 'boolean') {\n\t} else if (isArray(children)) {\n\t\tchildren.some(child => {\n\t\t\ttoChildArray(child, out);\n\t\t});\n\t} else {\n\t\tout.push(children);\n\t}\n\treturn out;\n}\n\n/**\n * @param {VNode} childVNode\n * @param {VNode[]} oldChildren\n * @param {number} skewedIndex\n * @param {number} remainingOldChildren\n * @returns {number}\n */\nfunction findMatchingIndex(\n\tchildVNode,\n\toldChildren,\n\tskewedIndex,\n\tremainingOldChildren\n) {\n\tconst key = childVNode.key;\n\tconst type = childVNode.type;\n\tlet oldVNode = oldChildren[skewedIndex];\n\tconst matched = oldVNode != NULL && (oldVNode._flags & MATCHED) == 0;\n\n\t// We only need to perform a search if there are more children\n\t// (remainingOldChildren) to search. However, if the oldVNode we just looked\n\t// at skewedIndex was not already used in this diff, then there must be at\n\t// least 1 other (so greater than 1) remainingOldChildren to attempt to match\n\t// against. So the following condition checks that ensuring\n\t// remainingOldChildren > 1 if the oldVNode is not already used/matched. Else\n\t// if the oldVNode was null or matched, then there could needs to be at least\n\t// 1 (aka `remainingOldChildren > 0`) children to find and compare against.\n\t//\n\t// If there is an unkeyed functional VNode, that isn't a built-in like our Fragment,\n\t// we should not search as we risk re-using state of an unrelated VNode. (reverted for now)\n\tlet shouldSearch =\n\t\t// (typeof type != 'function' || type === Fragment || key) &&\n\t\tremainingOldChildren > (matched ? 1 : 0);\n\n\tif (\n\t\t(oldVNode === NULL && key == null) ||\n\t\t(matched && key == oldVNode.key && type == oldVNode.type)\n\t) {\n\t\treturn skewedIndex;\n\t} else if (shouldSearch) {\n\t\tlet x = skewedIndex - 1;\n\t\tlet y = skewedIndex + 1;\n\t\twhile (x >= 0 || y < oldChildren.length) {\n\t\t\tconst childIndex = x >= 0 ? x-- : y++;\n\t\t\toldVNode = oldChildren[childIndex];\n\t\t\tif (\n\t\t\t\toldVNode != NULL &&\n\t\t\t\t(oldVNode._flags & MATCHED) == 0 &&\n\t\t\t\tkey == oldVNode.key &&\n\t\t\t\ttype == oldVNode.type\n\t\t\t) {\n\t\t\t\treturn childIndex;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn -1;\n}\n", "import {\n\tEMPTY_ARR,\n\tEMPTY_OBJ,\n\tMATH_NAMESPACE,\n\tMODE_HYDRATE,\n\tMODE_SUSPENDED,\n\tNULL,\n\tRESET_MODE,\n\tSVG_NAMESPACE,\n\tUNDEFINED,\n\tXHTML_NAMESPACE\n} from '../constants';\nimport { BaseComponent, getDomSibling } from '../component';\nimport { Fragment } from '../create-element';\nimport { diffChildren } from './children';\nimport { setProperty } from './props';\nimport { assign, isArray, removeNode, slice } from '../util';\nimport options from '../options';\n\n/**\n * @typedef {import('../internal').ComponentChildren} ComponentChildren\n * @typedef {import('../internal').Component} Component\n * @typedef {import('../internal').PreactElement} PreactElement\n * @typedef {import('../internal').VNode} VNode\n */\n\n/**\n * @template {any} T\n * @typedef {import('../internal').Ref<T>} Ref<T>\n */\n\n/**\n * Diff two virtual nodes and apply proper changes to the DOM\n * @param {PreactElement} parentDom The parent of the DOM element\n * @param {VNode} newVNode The new virtual node\n * @param {VNode} oldVNode The old virtual node\n * @param {object} globalContext The current context object. Modified by\n * getChildContext\n * @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)\n * @param {Array<PreactElement>} excessDomChildren\n * @param {Array<Component>} commitQueue List of components which have callbacks\n * to invoke in commitRoot\n * @param {PreactElement} oldDom The current attached DOM element any new dom\n * elements should be placed around. Likely `null` on first render (except when\n * hydrating). Can be a sibling DOM element when diffing Fragments that have\n * siblings. In most cases, it starts out as `oldChildren[0]._dom`.\n * @param {boolean} isHydrating Whether or not we are in hydration\n * @param {any[]} refQueue an array of elements needed to invoke refs\n */\nexport function diff(\n\tparentDom,\n\tnewVNode,\n\toldVNode,\n\tglobalContext,\n\tnamespace,\n\texcessDomChildren,\n\tcommitQueue,\n\toldDom,\n\tisHydrating,\n\trefQueue\n) {\n\t/** @type {any} */\n\tlet tmp,\n\t\tnewType = newVNode.type;\n\n\t// When passing through createElement it assigns the object\n\t// constructor as undefined. This to prevent JSON-injection.\n\tif (newVNode.constructor !== UNDEFINED) return NULL;\n\n\t// If the previous diff bailed out, resume creating/hydrating.\n\tif (oldVNode._flags & MODE_SUSPENDED) {\n\t\tisHydrating = !!(oldVNode._flags & MODE_HYDRATE);\n\t\toldDom = newVNode._dom = oldVNode._dom;\n\t\texcessDomChildren = [oldDom];\n\t}\n\n\tif ((tmp = options._diff)) tmp(newVNode);\n\n\touter: if (typeof newType == 'function') {\n\t\ttry {\n\t\t\tlet c, isNew, oldProps, oldState, snapshot, clearProcessingException;\n\t\t\tlet newProps = newVNode.props;\n\t\t\tconst isClassComponent = newType.prototype && newType.prototype.render;\n\n\t\t\t// Necessary for createContext api. Setting this property will pass\n\t\t\t// the context value as `this.context` just for this component.\n\t\t\ttmp = newType.contextType;\n\t\t\tlet provider = tmp && globalContext[tmp._id];\n\t\t\tlet componentContext = tmp\n\t\t\t\t? provider\n\t\t\t\t\t? provider.props.value\n\t\t\t\t\t: tmp._defaultValue\n\t\t\t\t: globalContext;\n\n\t\t\t// Get component and set it to `c`\n\t\t\tif (oldVNode._component) {\n\t\t\t\tc = newVNode._component = oldVNode._component;\n\t\t\t\tclearProcessingException = c._processingException = c._pendingError;\n\t\t\t} else {\n\t\t\t\t// Instantiate the new component\n\t\t\t\tif (isClassComponent) {\n\t\t\t\t\t// @ts-expect-error The check above verifies that newType is suppose to be constructed\n\t\t\t\t\tnewVNode._component = c = new newType(newProps, componentContext); // eslint-disable-line new-cap\n\t\t\t\t} else {\n\t\t\t\t\t// @ts-expect-error Trust me, Component implements the interface we want\n\t\t\t\t\tnewVNode._component = c = new BaseComponent(\n\t\t\t\t\t\tnewProps,\n\t\t\t\t\t\tcomponentContext\n\t\t\t\t\t);\n\t\t\t\t\tc.constructor = newType;\n\t\t\t\t\tc.render = doRender;\n\t\t\t\t}\n\t\t\t\tif (provider) provider.sub(c);\n\n\t\t\t\tif (!c.state) c.state = {};\n\t\t\t\tc._globalContext = globalContext;\n\t\t\t\tisNew = c._dirty = true;\n\t\t\t\tc._renderCallbacks = [];\n\t\t\t\tc._stateCallbacks = [];\n\t\t\t}\n\n\t\t\t// Invoke getDerivedStateFromProps\n\t\t\tif (isClassComponent && c._nextState == NULL) {\n\t\t\t\tc._nextState = c.state;\n\t\t\t}\n\n\t\t\tif (isClassComponent && newType.getDerivedStateFromProps != NULL) {\n\t\t\t\tif (c._nextState == c.state) {\n\t\t\t\t\tc._nextState = assign({}, c._nextState);\n\t\t\t\t}\n\n\t\t\t\tassign(\n\t\t\t\t\tc._nextState,\n\t\t\t\t\tnewType.getDerivedStateFromProps(newProps, c._nextState)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\toldProps = c.props;\n\t\t\toldState = c.state;\n\t\t\tc._vnode = newVNode;\n\n\t\t\t// Invoke pre-render lifecycle methods\n\t\t\tif (isNew) {\n\t\t\t\tif (\n\t\t\t\t\tisClassComponent &&\n\t\t\t\t\tnewType.getDerivedStateFromProps == NULL &&\n\t\t\t\t\tc.componentWillMount != NULL\n\t\t\t\t) {\n\t\t\t\t\tc.componentWillMount();\n\t\t\t\t}\n\n\t\t\t\tif (isClassComponent && c.componentDidMount != NULL) {\n\t\t\t\t\tc._renderCallbacks.push(c.componentDidMount);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tisClassComponent &&\n\t\t\t\t\tnewType.getDerivedStateFromProps == NULL &&\n\t\t\t\t\tnewProps !== oldProps &&\n\t\t\t\t\tc.componentWillReceiveProps != NULL\n\t\t\t\t) {\n\t\t\t\t\tc.componentWillReceiveProps(newProps, componentContext);\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\tnewVNode._original == oldVNode._original ||\n\t\t\t\t\t(!c._force &&\n\t\t\t\t\t\tc.shouldComponentUpdate != NULL &&\n\t\t\t\t\t\tc.shouldComponentUpdate(\n\t\t\t\t\t\t\tnewProps,\n\t\t\t\t\t\t\tc._nextState,\n\t\t\t\t\t\t\tcomponentContext\n\t\t\t\t\t\t) === false)\n\t\t\t\t) {\n\t\t\t\t\t// More info about this here: https://gist.github.com/JoviDeCroock/bec5f2ce93544d2e6070ef8e0036e4e8\n\t\t\t\t\tif (newVNode._original != oldVNode._original) {\n\t\t\t\t\t\t// When we are dealing with a bail because of sCU we have to update\n\t\t\t\t\t\t// the props, state and dirty-state.\n\t\t\t\t\t\t// when we are dealing with strict-equality we don't as the child could still\n\t\t\t\t\t\t// be dirtied see #3883\n\t\t\t\t\t\tc.props = newProps;\n\t\t\t\t\t\tc.state = c._nextState;\n\t\t\t\t\t\tc._dirty = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tnewVNode._dom = oldVNode._dom;\n\t\t\t\t\tnewVNode._children = oldVNode._children;\n\t\t\t\t\tnewVNode._children.some(vnode => {\n\t\t\t\t\t\tif (vnode) vnode._parent = newVNode;\n\t\t\t\t\t});\n\n\t\t\t\t\tEMPTY_ARR.push.apply(c._renderCallbacks, c._stateCallbacks);\n\t\t\t\t\tc._stateCallbacks = [];\n\n\t\t\t\t\tif (c._renderCallbacks.length) {\n\t\t\t\t\t\tcommitQueue.push(c);\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak outer;\n\t\t\t\t}\n\n\t\t\t\tif (c.componentWillUpdate != NULL) {\n\t\t\t\t\tc.componentWillUpdate(newProps, c._nextState, componentContext);\n\t\t\t\t}\n\n\t\t\t\tif (isClassComponent && c.componentDidUpdate != NULL) {\n\t\t\t\t\tc._renderCallbacks.push(() => {\n\t\t\t\t\t\tc.componentDidUpdate(oldProps, oldState, snapshot);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tc.context = componentContext;\n\t\t\tc.props = newProps;\n\t\t\tc._parentDom = parentDom;\n\t\t\tc._force = false;\n\n\t\t\tlet renderHook = options._render,\n\t\t\t\tcount = 0;\n\t\t\tif (isClassComponent) {\n\t\t\t\tc.state = c._nextState;\n\t\t\t\tc._dirty = false;\n\n\t\t\t\tif (renderHook) renderHook(newVNode);\n\n\t\t\t\ttmp = c.render(c.props, c.state, c.context);\n\n\t\t\t\tEMPTY_ARR.push.apply(c._renderCallbacks, c._stateCallbacks);\n\t\t\t\tc._stateCallbacks = [];\n\t\t\t} else {\n\t\t\t\tdo {\n\t\t\t\t\tc._dirty = false;\n\t\t\t\t\tif (renderHook) renderHook(newVNode);\n\n\t\t\t\t\ttmp = c.render(c.props, c.state, c.context);\n\n\t\t\t\t\t// Handle setState called in render, see #2553\n\t\t\t\t\tc.state = c._nextState;\n\t\t\t\t} while (c._dirty && ++count < 25);\n\t\t\t}\n\n\t\t\t// Handle setState called in render, see #2553\n\t\t\tc.state = c._nextState;\n\n\t\t\tif (c.getChildContext != NULL) {\n\t\t\t\tglobalContext = assign(assign({}, globalContext), c.getChildContext());\n\t\t\t}\n\n\t\t\tif (isClassComponent && !isNew && c.getSnapshotBeforeUpdate != NULL) {\n\t\t\t\tsnapshot = c.getSnapshotBeforeUpdate(oldProps, oldState);\n\t\t\t}\n\n\t\t\tlet renderResult =\n\t\t\t\ttmp != NULL && tmp.type === Fragment && tmp.key == NULL\n\t\t\t\t\t? cloneNode(tmp.props.children)\n\t\t\t\t\t: tmp;\n\n\t\t\toldDom = diffChildren(\n\t\t\t\tparentDom,\n\t\t\t\tisArray(renderResult) ? renderResult : [renderResult],\n\t\t\t\tnewVNode,\n\t\t\t\toldVNode,\n\t\t\t\tglobalContext,\n\t\t\t\tnamespace,\n\t\t\t\texcessDomChildren,\n\t\t\t\tcommitQueue,\n\t\t\t\toldDom,\n\t\t\t\tisHydrating,\n\t\t\t\trefQueue\n\t\t\t);\n\n\t\t\tc.base = newVNode._dom;\n\n\t\t\t// We successfully rendered this VNode, unset any stored hydration/bailout state:\n\t\t\tnewVNode._flags &= RESET_MODE;\n\n\t\t\tif (c._renderCallbacks.length) {\n\t\t\t\tcommitQueue.push(c);\n\t\t\t}\n\n\t\t\tif (clearProcessingException) {\n\t\t\t\tc._pendingError = c._processingException = NULL;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tnewVNode._original = NULL;\n\t\t\t// if hydrating or creating initial tree, bailout preserves DOM:\n\t\t\tif (isHydrating || excessDomChildren != NULL) {\n\t\t\t\tif (e.then) {\n\t\t\t\t\tnewVNode._flags |= isHydrating\n\t\t\t\t\t\t? MODE_HYDRATE | MODE_SUSPENDED\n\t\t\t\t\t\t: MODE_SUSPENDED;\n\n\t\t\t\t\twhile (oldDom && oldDom.nodeType == 8 && oldDom.nextSibling) {\n\t\t\t\t\t\toldDom = oldDom.nextSibling;\n\t\t\t\t\t}\n\n\t\t\t\t\texcessDomChildren[excessDomChildren.indexOf(oldDom)] = NULL;\n\t\t\t\t\tnewVNode._dom = oldDom;\n\t\t\t\t} else {\n\t\t\t\t\tfor (let i = excessDomChildren.length; i--; ) {\n\t\t\t\t\t\tremoveNode(excessDomChildren[i]);\n\t\t\t\t\t}\n\t\t\t\t\tmarkAsForce(newVNode);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnewVNode._dom = oldVNode._dom;\n\t\t\t\tnewVNode._children = oldVNode._children;\n\t\t\t\tif (!e.then) markAsForce(newVNode);\n\t\t\t}\n\t\t\toptions._catchError(e, newVNode, oldVNode);\n\t\t}\n\t} else if (\n\t\texcessDomChildren == NULL &&\n\t\tnewVNode._original == oldVNode._original\n\t) {\n\t\tnewVNode._children = oldVNode._children;\n\t\tnewVNode._dom = oldVNode._dom;\n\t} else {\n\t\toldDom = newVNode._dom = diffElementNodes(\n\t\t\toldVNode._dom,\n\t\t\tnewVNode,\n\t\t\toldVNode,\n\t\t\tglobalContext,\n\t\t\tnamespace,\n\t\t\texcessDomChildren,\n\t\t\tcommitQueue,\n\t\t\tisHydrating,\n\t\t\trefQueue\n\t\t);\n\t}\n\n\tif ((tmp = options.diffed)) tmp(newVNode);\n\n\treturn newVNode._flags & MODE_SUSPENDED ? undefined : oldDom;\n}\n\nfunction markAsForce(vnode) {\n\tif (vnode) {\n\t\tif (vnode._component) vnode._component._force = true;\n\t\tif (vnode._children) vnode._children.some(markAsForce);\n\t}\n}\n\n/**\n * @param {Array<Component>} commitQueue List of components\n * which have callbacks to invoke in commitRoot\n * @param {VNode} root\n */\nexport function commitRoot(commitQueue, root, refQueue) {\n\tfor (let i = 0; i < refQueue.length; i++) {\n\t\tapplyRef(refQueue[i], refQueue[++i], refQueue[++i]);\n\t}\n\n\tif (options._commit) options._commit(root, commitQueue);\n\n\tcommitQueue.some(c => {\n\t\ttry {\n\t\t\t// @ts-expect-error Reuse the commitQueue variable here so the type changes\n\t\t\tcommitQueue = c._renderCallbacks;\n\t\t\tc._renderCallbacks = [];\n\t\t\tcommitQueue.some(cb => {\n\t\t\t\t// @ts-expect-error See above comment on commitQueue\n\t\t\t\tcb.call(c);\n\t\t\t});\n\t\t} catch (e) {\n\t\t\toptions._catchError(e, c._vnode);\n\t\t}\n\t});\n}\n\nfunction cloneNode(node) {\n\tif (typeof node != 'object' || node == NULL || node._depth > 0) {\n\t\treturn node;\n\t}\n\n\tif (isArray(node)) {\n\t\treturn node.map(cloneNode);\n\t}\n\n\tif (node.constructor !== UNDEFINED) return null;\n\n\treturn assign({}, node);\n}\n\n/**\n * Diff two virtual nodes representing DOM element\n * @param {PreactElement} dom The DOM element representing the virtual nodes\n * being diffed\n * @param {VNode} newVNode The new virtual node\n * @param {VNode} oldVNode The old virtual node\n * @param {object} globalContext The current context object\n * @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)\n * @param {Array<PreactElement>} excessDomChildren\n * @param {Array<Component>} commitQueue List of components which have callbacks\n * to invoke in commitRoot\n * @param {boolean} isHydrating Whether or not we are in hydration\n * @param {any[]} refQueue an array of elements needed to invoke refs\n * @returns {PreactElement}\n */\nfunction diffElementNodes(\n\tdom,\n\tnewVNode,\n\toldVNode,\n\tglobalContext,\n\tnamespace,\n\texcessDomChildren,\n\tcommitQueue,\n\tisHydrating,\n\trefQueue\n) {\n\tlet oldProps = oldVNode.props || EMPTY_OBJ;\n\tlet newProps = newVNode.props;\n\tlet nodeType = /** @type {string} */ (newVNode.type);\n\t/** @type {any} */\n\tlet i;\n\t/** @type {{ __html?: string }} */\n\tlet newHtml;\n\t/** @type {{ __html?: string }} */\n\tlet oldHtml;\n\t/** @type {ComponentChildren} */\n\tlet newChildren;\n\tlet value;\n\tlet inputValue;\n\tlet checked;\n\n\t// Tracks entering and exiting namespaces when descending through the tree.\n\tif (nodeType == 'svg') namespace = SVG_NAMESPACE;\n\telse if (nodeType == 'math') namespace = MATH_NAMESPACE;\n\telse if (!namespace) namespace = XHTML_NAMESPACE;\n\n\tif (excessDomChildren != NULL) {\n\t\tfor (i = 0; i < excessDomChildren.length; i++) {\n\t\t\tvalue = excessDomChildren[i];\n\n\t\t\t// if newVNode matches an element in excessDomChildren or the `dom`\n\t\t\t// argument matches an element in excessDomChildren, remove it from\n\t\t\t// excessDomChildren so it isn't later removed in diffChildren\n\t\t\tif (\n\t\t\t\tvalue &&\n\t\t\t\t'setAttribute' in value == !!nodeType &&\n\t\t\t\t(nodeType ? value.localName == nodeType : value.nodeType == 3)\n\t\t\t) {\n\t\t\t\tdom = value;\n\t\t\t\texcessDomChildren[i] = NULL;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (dom == NULL) {\n\t\tif (nodeType == NULL) {\n\t\t\treturn document.createTextNode(newProps);\n\t\t}\n\n\t\tdom = document.createElementNS(\n\t\t\tnamespace,\n\t\t\tnodeType,\n\t\t\tnewProps.is && newProps\n\t\t);\n\n\t\t// we are creating a new node, so we can assume this is a new subtree (in\n\t\t// case we are hydrating), this deopts the hydrate\n\t\tif (isHydrating) {\n\t\t\tif (options._hydrationMismatch)\n\t\t\t\toptions._hydrationMismatch(newVNode, excessDomChildren);\n\t\t\tisHydrating = false;\n\t\t}\n\t\t// we created a new parent, so none of the previously attached children can be reused:\n\t\texcessDomChildren = NULL;\n\t}\n\n\tif (nodeType == NULL) {\n\t\t// During hydration, we still have to split merged text from SSR'd HTML.\n\t\tif (oldProps !== newProps && (!isHydrating || dom.data != newProps)) {\n\t\t\tdom.data = newProps;\n\t\t}\n\t} else {\n\t\t// If excessDomChildren was not null, repopulate it with the current element's children:\n\t\texcessDomChildren =\n\t\t\tnodeType == 'textarea' && newProps.defaultValue != NULL\n\t\t\t\t? NULL\n\t\t\t\t: excessDomChildren && slice.call(dom.childNodes);\n\n\t\t// If we are in a situation where we are not hydrating but are using\n\t\t// existing DOM (e.g. replaceNode) we should read the existing DOM\n\t\t// attributes to diff them\n\t\tif (!isHydrating && excessDomChildren != NULL) {\n\t\t\toldProps = {};\n\t\t\tfor (i = 0; i < dom.attributes.length; i++) {\n\t\t\t\tvalue = dom.attributes[i];\n\t\t\t\toldProps[value.name] = value.value;\n\t\t\t}\n\t\t}\n\n\t\tfor (i in oldProps) {\n\t\t\tvalue = oldProps[i];\n\t\t\tif (i == 'dangerouslySetInnerHTML') {\n\t\t\t\toldHtml = value;\n\t\t\t} else if (\n\t\t\t\ti != 'children' &&\n\t\t\t\t!(i in newProps) &&\n\t\t\t\t!(i == 'value' && 'defaultValue' in newProps) &&\n\t\t\t\t!(i == 'checked' && 'defaultChecked' in newProps)\n\t\t\t) {\n\t\t\t\tsetProperty(dom, i, NULL, value, namespace);\n\t\t\t}\n\t\t}\n\n\t\t// During hydration, props are not diffed at all (including dangerouslySetInnerHTML)\n\t\t// @TODO we should warn in debug mode when props don't match here.\n\t\tfor (i in newProps) {\n\t\t\tvalue = newProps[i];\n\t\t\tif (i == 'children') {\n\t\t\t\tnewChildren = value;\n\t\t\t} else if (i == 'dangerouslySetInnerHTML') {\n\t\t\t\tnewHtml = value;\n\t\t\t} else if (i == 'value') {\n\t\t\t\tinputValue = value;\n\t\t\t} else if (i == 'checked') {\n\t\t\t\tchecked = value;\n\t\t\t} else if (\n\t\t\t\t(!isHydrating || typeof value == 'function') &&\n\t\t\t\toldProps[i] !== value\n\t\t\t) {\n\t\t\t\tsetProperty(dom, i, value, oldProps[i], namespace);\n\t\t\t}\n\t\t}\n\n\t\t// If the new vnode didn't have dangerouslySetInnerHTML, diff its children\n\t\tif (newHtml) {\n\t\t\t// Avoid re-applying the same '__html' if it did not changed between re-render\n\t\t\tif (\n\t\t\t\t!isHydrating &&\n\t\t\t\t(!oldHtml ||\n\t\t\t\t\t(newHtml.__html != oldHtml.__html && newHtml.__html != dom.innerHTML))\n\t\t\t) {\n\t\t\t\tdom.innerHTML = newHtml.__html;\n\t\t\t}\n\n\t\t\tnewVNode._children = [];\n\t\t} else {\n\t\t\tif (oldHtml) dom.innerHTML = '';\n\n\t\t\tdiffChildren(\n\t\t\t\t// @ts-expect-error\n\t\t\t\tnewVNode.type == 'template' ? dom.content : dom,\n\t\t\t\tisArray(newChildren) ? newChildren : [newChildren],\n\t\t\t\tnewVNode,\n\t\t\t\toldVNode,\n\t\t\t\tglobalContext,\n\t\t\t\tnodeType == 'foreignObject' ? XHTML_NAMESPACE : namespace,\n\t\t\t\texcessDomChildren,\n\t\t\t\tcommitQueue,\n\t\t\t\texcessDomChildren\n\t\t\t\t\t? excessDomChildren[0]\n\t\t\t\t\t: oldVNode._children && getDomSibling(oldVNode, 0),\n\t\t\t\tisHydrating,\n\t\t\t\trefQueue\n\t\t\t);\n\n\t\t\t// Remove children that are not part of any vnode.\n\t\t\tif (excessDomChildren != NULL) {\n\t\t\t\tfor (i = excessDomChildren.length; i--; ) {\n\t\t\t\t\tremoveNode(excessDomChildren[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// As above, don't diff props during hydration\n\t\tif (!isHydrating || nodeType == 'textarea') {\n\t\t\ti = 'value';\n\t\t\tif (nodeType == 'progress' && inputValue == NULL) {\n\t\t\t\tdom.removeAttribute('value');\n\t\t\t} else if (\n\t\t\t\tinputValue != UNDEFINED &&\n\t\t\t\t// #2756 For the <progress>-element the initial value is 0,\n\t\t\t\t// despite the attribute not being present. When the attribute\n\t\t\t\t// is missing the progress bar is treated as indeterminate.\n\t\t\t\t// To fix that we'll always update it when it is 0 for progress elements\n\t\t\t\t(inputValue !== dom[i] ||\n\t\t\t\t\t(nodeType == 'progress' && !inputValue) ||\n\t\t\t\t\t// This is only for IE 11 to fix <select> value not being updated.\n\t\t\t\t\t// To avoid a stale select value we need to set the option.value\n\t\t\t\t\t// again, which triggers IE11 to re-evaluate the select value\n\t\t\t\t\t(nodeType == 'option' && inputValue != oldProps[i]))\n\t\t\t) {\n\t\t\t\tsetProperty(dom, i, inputValue, oldProps[i], namespace);\n\t\t\t}\n\n\t\t\ti = 'checked';\n\t\t\tif (checked != UNDEFINED && checked != dom[i]) {\n\t\t\t\tsetProperty(dom, i, checked, oldProps[i], namespace);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dom;\n}\n\n/**\n * Invoke or update a ref, depending on whether it is a function or object ref.\n * @param {Ref<any> & { _unmount?: unknown }} ref\n * @param {any} value\n * @param {VNode} vnode\n */\nexport function applyRef(ref, value, vnode) {\n\ttry {\n\t\tif (typeof ref == 'function') {\n\t\t\tlet hasRefUnmount = typeof ref._unmount == 'function';\n\t\t\tif (hasRefUnmount) {\n\t\t\t\t// @ts-ignore TS doesn't like moving narrowing checks into variables\n\t\t\t\tref._unmount();\n\t\t\t}\n\n\t\t\tif (!hasRefUnmount || value != NULL) {\n\t\t\t\t// Store the cleanup function on the function\n\t\t\t\t// instance object itself to avoid shape\n\t\t\t\t// transitioning vnode\n\t\t\t\tref._unmount = ref(value);\n\t\t\t}\n\t\t} else ref.current = value;\n\t} catch (e) {\n\t\toptions._catchError(e, vnode);\n\t}\n}\n\n/**\n * Unmount a virtual node from the tree and apply DOM changes\n * @param {VNode} vnode The virtual node to unmount\n * @param {VNode} parentVNode The parent of the VNode that initiated the unmount\n * @param {boolean} [skipRemove] Flag that indicates that a parent node of the\n * current element is already detached from the DOM.\n */\nexport function unmount(vnode, parentVNode, skipRemove) {\n\tlet r;\n\tif (options.unmount) options.unmount(vnode);\n\n\tif ((r = vnode.ref)) {\n\t\tif (!r.current || r.current == vnode._dom) {\n\t\t\tapplyRef(r, NULL, parentVNode);\n\t\t}\n\t}\n\n\tif ((r = vnode._component) != NULL) {\n\t\tif (r.componentWillUnmount) {\n\t\t\ttry {\n\t\t\t\tr.componentWillUnmount();\n\t\t\t} catch (e) {\n\t\t\t\toptions._catchError(e, parentVNode);\n\t\t\t}\n\t\t}\n\n\t\tr.base = r._parentDom = NULL;\n\t}\n\n\tif ((r = vnode._children)) {\n\t\tfor (let i = 0; i < r.length; i++) {\n\t\t\tif (r[i]) {\n\t\t\t\tunmount(\n\t\t\t\t\tr[i],\n\t\t\t\t\tparentVNode,\n\t\t\t\t\tskipRemove || typeof vnode.type != 'function'\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!skipRemove) {\n\t\tremoveNode(vnode._dom);\n\t}\n\n\tvnode._component = vnode._parent = vnode._dom = UNDEFINED;\n}\n\n/** The `.render()` method for a PFC backing instance. */\nfunction doRender(props, state, context) {\n\treturn this.constructor(props, context);\n}\n", "import { EMPTY_OBJ, NULL } from './constants';\nimport { commitRoot, diff } from './diff/index';\nimport { createElement, Fragment } from './create-element';\nimport options from './options';\nimport { slice } from './util';\n\n/**\n * Render a Preact virtual node into a DOM element\n * @param {import('./internal').ComponentChild} vnode The virtual node to render\n * @param {import('./internal').PreactElement} parentDom The DOM element to render into\n * @param {import('./internal').PreactElement | object} [replaceNode] Optional: Attempt to re-use an\n * existing DOM tree rooted at `replaceNode`\n */\nexport function render(vnode, parentDom, replaceNode) {\n\t// https://github.com/preactjs/preact/issues/3794\n\tif (parentDom == document) {\n\t\tparentDom = document.documentElement;\n\t}\n\n\tif (options._root) options._root(vnode, parentDom);\n\n\t// We abuse the `replaceNode` parameter in `hydrate()` to signal if we are in\n\t// hydration mode or not by passing the `hydrate` function instead of a DOM\n\t// element..\n\tlet isHydrating = typeof replaceNode == 'function';\n\n\t// To be able to support calling `render()` multiple times on the same\n\t// DOM node, we need to obtain a reference to the previous tree. We do\n\t// this by assigning a new `_children` property to DOM nodes which points\n\t// to the last rendered tree. By default this property is not present, which\n\t// means that we are mounting a new tree for the first time.\n\tlet oldVNode = isHydrating\n\t\t? NULL\n\t\t: (replaceNode && replaceNode._children) || parentDom._children;\n\n\tvnode = ((!isHydrating && replaceNode) || parentDom)._children =\n\t\tcreateElement(Fragment, NULL, [vnode]);\n\n\t// List of effects that need to be called after diffing.\n\tlet commitQueue = [],\n\t\trefQueue = [];\n\tdiff(\n\t\tparentDom,\n\t\t// Determine the new vnode tree and store it on the DOM element on\n\t\t// our custom `_children` property.\n\t\tvnode,\n\t\toldVNode || EMPTY_OBJ,\n\t\tEMPTY_OBJ,\n\t\tparentDom.namespaceURI,\n\t\t!isHydrating && replaceNode\n\t\t\t? [replaceNode]\n\t\t\t: oldVNode\n\t\t\t\t? NULL\n\t\t\t\t: parentDom.firstChild\n\t\t\t\t\t? slice.call(parentDom.childNodes)\n\t\t\t\t\t: NULL,\n\t\tcommitQueue,\n\t\t!isHydrating && replaceNode\n\t\t\t? replaceNode\n\t\t\t: oldVNode\n\t\t\t\t? oldVNode._dom\n\t\t\t\t: parentDom.firstChild,\n\t\tisHydrating,\n\t\trefQueue\n\t);\n\n\t// Flush all queued effects\n\tcommitRoot(commitQueue, vnode, refQueue);\n}\n\n/**\n * Update an existing DOM element with data from a Preact virtual node\n * @param {import('./internal').ComponentChild} vnode The virtual node to render\n * @param {import('./internal').PreactElement} parentDom The DOM element to update\n */\nexport function hydrate(vnode, parentDom) {\n\trender(vnode, parentDom, hydrate);\n}\n", "import { assign, slice } from './util';\nimport { createVNode } from './create-element';\nimport { NULL, UNDEFINED } from './constants';\n\n/**\n * Clones the given VNode, optionally adding attributes/props and replacing its\n * children.\n * @param {import('./internal').VNode} vnode The virtual DOM element to clone\n * @param {object} props Attributes/props to add when cloning\n * @param {Array<import('./internal').ComponentChildren>} rest Any additional arguments will be used\n * as replacement children.\n * @returns {import('./internal').VNode}\n */\nexport function cloneElement(vnode, props, children) {\n\tlet normalizedProps = assign({}, vnode.props),\n\t\tkey,\n\t\tref,\n\t\ti;\n\n\tlet defaultProps;\n\n\tif (vnode.type && vnode.type.defaultProps) {\n\t\tdefaultProps = vnode.type.defaultProps;\n\t}\n\n\tfor (i in props) {\n\t\tif (i == 'key') key = props[i];\n\t\telse if (i == 'ref') ref = props[i];\n\t\telse if (props[i] === UNDEFINED && defaultProps != UNDEFINED) {\n\t\t\tnormalizedProps[i] = defaultProps[i];\n\t\t} else {\n\t\t\tnormalizedProps[i] = props[i];\n\t\t}\n\t}\n\n\tif (arguments.length > 2) {\n\t\tnormalizedProps.children =\n\t\t\targuments.length > 3 ? slice.call(arguments, 2) : children;\n\t}\n\n\treturn createVNode(\n\t\tvnode.type,\n\t\tnormalizedProps,\n\t\tkey || vnode.key,\n\t\tref || vnode.ref,\n\t\tNULL\n\t);\n}\n", "import { NULL } from '../constants';\n\n/**\n * Find the closest error boundary to a thrown error and call it\n * @param {object} error The thrown value\n * @param {import('../internal').VNode} vnode The vnode that threw the error that was caught (except\n * for unmounting when this parameter is the highest parent that was being\n * unmounted)\n * @param {import('../internal').VNode} [oldVNode]\n * @param {import('../internal').ErrorInfo} [errorInfo]\n */\nexport function _catchError(error, vnode, oldVNode, errorInfo) {\n\t/** @type {import('../internal').Component} */\n\tlet component,\n\t\t/** @type {import('../internal').ComponentType} */\n\t\tctor,\n\t\t/** @type {boolean} */\n\t\thandled;\n\n\tfor (; (vnode = vnode._parent); ) {\n\t\tif ((component = vnode._component) && !component._processingException) {\n\t\t\ttry {\n\t\t\t\tctor = component.constructor;\n\n\t\t\t\tif (ctor && ctor.getDerivedStateFromError != NULL) {\n\t\t\t\t\tcomponent.setState(ctor.getDerivedStateFromError(error));\n\t\t\t\t\thandled = component._dirty;\n\t\t\t\t}\n\n\t\t\t\tif (component.componentDidCatch != NULL) {\n\t\t\t\t\tcomponent.componentDidCatch(error, errorInfo || {});\n\t\t\t\t\thandled = component._dirty;\n\t\t\t\t}\n\n\t\t\t\t// This is an error boundary. Mark it as having bailed out, and whether it was mid-hydration.\n\t\t\t\tif (handled) {\n\t\t\t\t\treturn (component._pendingError = component);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\terror = e;\n\t\t\t}\n\t\t}\n\t}\n\n\tthrow error;\n}\n", "import { options as _options } from 'preact';\n\n/** @type {number} */\nlet currentIndex;\n\n/** @type {import('./internal').Component} */\nlet currentComponent;\n\n/** @type {import('./internal').Component} */\nlet previousComponent;\n\n/** @type {number} */\nlet currentHook = 0;\n\n/** @type {Array<import('./internal').Component>} */\nlet afterPaintEffects = [];\n\n// Cast to use internal Options type\nconst options = /** @type {import('./internal').Options} */ (_options);\n\nlet oldBeforeDiff = options._diff;\nlet oldBeforeRender = options._render;\nlet oldAfterDiff = options.diffed;\nlet oldCommit = options._commit;\nlet oldBeforeUnmount = options.unmount;\nlet oldRoot = options._root;\n\n// We take the minimum timeout for requestAnimationFrame to ensure that\n// the callback is invoked after the next frame. 35ms is based on a 30hz\n// refresh rate, which is the minimum rate for a smooth user experience.\nconst RAF_TIMEOUT = 35;\nlet prevRaf;\n\n/** @type {(vnode: import('./internal').VNode) => void} */\noptions._diff = vnode => {\n\tcurrentComponent = null;\n\tif (oldBeforeDiff) oldBeforeDiff(vnode);\n};\n\noptions._root = (vnode, parentDom) => {\n\tif (vnode && parentDom._children && parentDom._children._mask) {\n\t\tvnode._mask = parentDom._children._mask;\n\t}\n\n\tif (oldRoot) oldRoot(vnode, parentDom);\n};\n\n/** @type {(vnode: import('./internal').VNode) => void} */\noptions._render = vnode => {\n\tif (oldBeforeRender) oldBeforeRender(vnode);\n\n\tcurrentComponent = vnode._component;\n\tcurrentIndex = 0;\n\n\tconst hooks = currentComponent.__hooks;\n\tif (hooks) {\n\t\tif (previousComponent === currentComponent) {\n\t\t\thooks._pendingEffects = [];\n\t\t\tcurrentComponent._renderCallbacks = [];\n\t\t\thooks._list.some(hookItem => {\n\t\t\t\tif (hookItem._nextValue) {\n\t\t\t\t\thookItem._value = hookItem._nextValue;\n\t\t\t\t}\n\t\t\t\thookItem._pendingArgs = hookItem._nextValue = undefined;\n\t\t\t});\n\t\t} else {\n\t\t\thooks._pendingEffects.some(invokeCleanup);\n\t\t\thooks._pendingEffects.some(invokeEffect);\n\t\t\thooks._pendingEffects = [];\n\t\t\tcurrentIndex = 0;\n\t\t}\n\t}\n\tpreviousComponent = currentComponent;\n};\n\n/** @type {(vnode: import('./internal').VNode) => void} */\noptions.diffed = vnode => {\n\tif (oldAfterDiff) oldAfterDiff(vnode);\n\n\tconst c = vnode._component;\n\tif (c && c.__hooks) {\n\t\tif (c.__hooks._pendingEffects.length) afterPaint(afterPaintEffects.push(c));\n\t\tc.__hooks._list.some(hookItem => {\n\t\t\tif (hookItem._pendingArgs) {\n\t\t\t\thookItem._args = hookItem._pendingArgs;\n\t\t\t}\n\t\t\thookItem._pendingArgs = undefined;\n\t\t});\n\t}\n\tpreviousComponent = currentComponent = null;\n};\n\n// TODO: Improve typing of commitQueue parameter\n/** @type {(vnode: import('./internal').VNode, commitQueue: any) => void} */\noptions._commit = (vnode, commitQueue) => {\n\tcommitQueue.some(component => {\n\t\ttry {\n\t\t\tcomponent._renderCallbacks.some(invokeCleanup);\n\t\t\tcomponent._renderCallbacks = component._renderCallbacks.filter(cb =>\n\t\t\t\tcb._value ? invokeEffect(cb) : true\n\t\t\t);\n\t\t} catch (e) {\n\t\t\tcommitQueue.some(c => {\n\t\t\t\tif (c._renderCallbacks) c._renderCallbacks = [];\n\t\t\t});\n\t\t\tcommitQueue = [];\n\t\t\toptions._catchError(e, component._vnode);\n\t\t}\n\t});\n\n\tif (oldCommit) oldCommit(vnode, commitQueue);\n};\n\n/** @type {(vnode: import('./internal').VNode) => void} */\noptions.unmount = vnode => {\n\tif (oldBeforeUnmount) oldBeforeUnmount(vnode);\n\n\tconst c = vnode._component;\n\tif (c && c.__hooks) {\n\t\tlet hasErrored;\n\t\tc.__hooks._list.some(s => {\n\t\t\ttry {\n\t\t\t\tinvokeCleanup(s);\n\t\t\t} catch (e) {\n\t\t\t\thasErrored = e;\n\t\t\t}\n\t\t});\n\t\tc.__hooks = undefined;\n\t\tif (hasErrored) options._catchError(hasErrored, c._vnode);\n\t}\n};\n\n/**\n * Get a hook's state from the currentComponent\n * @param {number} index The index of the hook to get\n * @param {number} type The index of the hook to get\n * @returns {any}\n */\nfunction getHookState(index, type) {\n\tif (options._hook) {\n\t\toptions._hook(currentComponent, index, currentHook || type);\n\t}\n\tcurrentHook = 0;\n\n\t// Largely inspired by:\n\t// * https://github.com/michael-klein/funcy.js/blob/f6be73468e6ec46b0ff5aa3cc4c9baf72a29025a/src/hooks/core_hooks.mjs\n\t// * https://github.com/michael-klein/funcy.js/blob/650beaa58c43c33a74820a3c98b3c7079cf2e333/src/renderer.mjs\n\t// Other implementations to look at:\n\t// * https://codesandbox.io/s/mnox05qp8\n\tconst hooks =\n\t\tcurrentComponent.__hooks ||\n\t\t(currentComponent.__hooks = {\n\t\t\t_list: [],\n\t\t\t_pendingEffects: []\n\t\t});\n\n\tif (index >= hooks._list.length) {\n\t\thooks._list.push({});\n\t}\n\n\treturn hooks._list[index];\n}\n\n/**\n * @template {unknown} S\n * @param {import('./index').Dispatch<import('./index').StateUpdater<S>>} [initialState]\n * @returns {[S, (state: S) => void]}\n */\nexport function useState(initialState) {\n\tcurrentHook = 1;\n\treturn useReducer(invokeOrReturn, initialState);\n}\n\n/**\n * @template {unknown} S\n * @template {unknown} A\n * @param {import('./index').Reducer<S, A>} reducer\n * @param {import('./index').Dispatch<import('./index').StateUpdater<S>>} initialState\n * @param {(initialState: any) => void} [init]\n * @returns {[ S, (state: S) => void ]}\n */\nexport function useReducer(reducer, initialState, init) {\n\t/** @type {import('./internal').ReducerHookState} */\n\tconst hookState = getHookState(currentIndex++, 2);\n\thookState._reducer = reducer;\n\tif (!hookState._component) {\n\t\thookState._value = [\n\t\t\t!init ? invokeOrReturn(undefined, initialState) : init(initialState),\n\n\t\t\taction => {\n\t\t\t\tconst currentValue = hookState._nextValue\n\t\t\t\t\t? hookState._nextValue[0]\n\t\t\t\t\t: hookState._value[0];\n\t\t\t\tconst nextValue = hookState._reducer(currentValue, action);\n\n\t\t\t\tif (currentValue !== nextValue) {\n\t\t\t\t\thookState._nextValue = [nextValue, hookState._value[1]];\n\t\t\t\t\thookState._component.setState({});\n\t\t\t\t}\n\t\t\t}\n\t\t];\n\n\t\thookState._component = currentComponent;\n\n\t\tif (!currentComponent._hasScuFromHooks) {\n\t\t\tcurrentComponent._hasScuFromHooks = true;\n\t\t\tlet prevScu = currentComponent.shouldComponentUpdate;\n\t\t\tconst prevCWU = currentComponent.componentWillUpdate;\n\n\t\t\t// If we're dealing with a forced update `shouldComponentUpdate` will\n\t\t\t// not be called. But we use that to update the hook values, so we\n\t\t\t// need to call it.\n\t\t\tcurrentComponent.componentWillUpdate = function (p, s, c) {\n\t\t\t\tif (this._force) {\n\t\t\t\t\tlet tmp = prevScu;\n\t\t\t\t\t// Clear to avoid other sCU hooks from being called\n\t\t\t\t\tprevScu = undefined;\n\t\t\t\t\tupdateHookState(p, s, c);\n\t\t\t\t\tprevScu = tmp;\n\t\t\t\t}\n\n\t\t\t\tif (prevCWU) prevCWU.call(this, p, s, c);\n\t\t\t};\n\n\t\t\t// This SCU has the purpose of bailing out after repeated updates\n\t\t\t// to stateful hooks.\n\t\t\t// we store the next value in _nextValue[0] and keep doing that for all\n\t\t\t// state setters, if we have next states and\n\t\t\t// all next states within a component end up being equal to their original state\n\t\t\t// we are safe to bail out for this specific component.\n\t\t\t/**\n\t\t\t *\n\t\t\t * @type {import('./internal').Component[\"shouldComponentUpdate\"]}\n\t\t\t */\n\t\t\t// @ts-ignore - We don't use TS to downtranspile\n\t\t\t// eslint-disable-next-line no-inner-declarations\n\t\t\tfunction updateHookState(p, s, c) {\n\t\t\t\tif (!hookState._component.__hooks) return true;\n\n\t\t\t\tconst stateHooks = hookState._component.__hooks._list.filter(\n\t\t\t\t\tx => x._component\n\t\t\t\t);\n\n\t\t\t\tconst allHooksEmpty = stateHooks.every(x => !x._nextValue);\n\t\t\t\t// When we have no updated hooks in the component we invoke the previous SCU or\n\t\t\t\t// traverse the VDOM tree further.\n\t\t\t\tif (allHooksEmpty) {\n\t\t\t\t\treturn prevScu ? prevScu.call(this, p, s, c) : true;\n\t\t\t\t}\n\n\t\t\t\t// We check whether we have components with a nextValue set that\n\t\t\t\t// have values that aren't equal to one another this pushes\n\t\t\t\t// us to update further down the tree\n\t\t\t\tlet shouldUpdate = hookState._component.props !== p;\n\t\t\t\tstateHooks.some(hookItem => {\n\t\t\t\t\tif (hookItem._nextValue) {\n\t\t\t\t\t\tconst currentValue = hookItem._value[0];\n\t\t\t\t\t\thookItem._value = hookItem._nextValue;\n\t\t\t\t\t\thookItem._nextValue = undefined;\n\t\t\t\t\t\tif (currentValue !== hookItem._value[0]) shouldUpdate = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn prevScu\n\t\t\t\t\t? prevScu.call(this, p, s, c) || shouldUpdate\n\t\t\t\t\t: shouldUpdate;\n\t\t\t}\n\n\t\t\tcurrentComponent.shouldComponentUpdate = updateHookState;\n\t\t}\n\t}\n\n\treturn hookState._nextValue || hookState._value;\n}\n\n/**\n * @param {import('./internal').Effect} callback\n * @param {unknown[]} args\n * @returns {void}\n */\nexport function useEffect(callback, args) {\n\t/** @type {import('./internal').EffectHookState} */\n\tconst state = getHookState(currentIndex++, 3);\n\tif (!options._skipEffects && argsChanged(state._args, args)) {\n\t\tstate._value = callback;\n\t\tstate._pendingArgs = args;\n\n\t\tcurrentComponent.__hooks._pendingEffects.push(state);\n\t}\n}\n\n/**\n * @param {import('./internal').Effect} callback\n * @param {unknown[]} args\n * @returns {void}\n */\nexport function useLayoutEffect(callback, args) {\n\t/** @type {import('./internal').EffectHookState} */\n\tconst state = getHookState(currentIndex++, 4);\n\tif (!options._skipEffects && argsChanged(state._args, args)) {\n\t\tstate._value = callback;\n\t\tstate._pendingArgs = args;\n\n\t\tcurrentComponent._renderCallbacks.push(state);\n\t}\n}\n\n/** @type {(initialValue: unknown) => unknown} */\nexport function useRef(initialValue) {\n\tcurrentHook = 5;\n\treturn useMemo(() => ({ current: initialValue }), []);\n}\n\n/**\n * @param {object} ref\n * @param {() => object} createHandle\n * @param {unknown[]} args\n * @returns {void}\n */\nexport function useImperativeHandle(ref, createHandle, args) {\n\tcurrentHook = 6;\n\tuseLayoutEffect(\n\t\t() => {\n\t\t\tif (typeof ref == 'function') {\n\t\t\t\tconst result = ref(createHandle());\n\t\t\t\treturn () => {\n\t\t\t\t\tref(null);\n\t\t\t\t\tif (result && typeof result == 'function') result();\n\t\t\t\t};\n\t\t\t} else if (ref) {\n\t\t\t\tref.current = createHandle();\n\t\t\t\treturn () => (ref.current = null);\n\t\t\t}\n\t\t},\n\t\targs == null ? args : args.concat(ref)\n\t);\n}\n\n/**\n * @template {unknown} T\n * @param {() => T} factory\n * @param {unknown[]} args\n * @returns {T}\n */\nexport function useMemo(factory, args) {\n\t/** @type {import('./internal').MemoHookState<T>} */\n\tconst state = getHookState(currentIndex++, 7);\n\tif (argsChanged(state._args, args)) {\n\t\tstate._value = factory();\n\t\tstate._args = args;\n\t\tstate._factory = factory;\n\t}\n\n\treturn state._value;\n}\n\n/**\n * @param {() => void} callback\n * @param {unknown[]} args\n * @returns {() => void}\n */\nexport function useCallback(callback, args) {\n\tcurrentHook = 8;\n\treturn useMemo(() => callback, args);\n}\n\n/**\n * @param {import('./internal').PreactContext} context\n */\nexport function useContext(context) {\n\tconst provider = currentComponent.context[context._id];\n\t// We could skip this call here, but than we'd not call\n\t// `options._hook`. We need to do that in order to make\n\t// the devtools aware of this hook.\n\t/** @type {import('./internal').ContextHookState} */\n\tconst state = getHookState(currentIndex++, 9);\n\t// The devtools needs access to the context object to\n\t// be able to pull of the default value when no provider\n\t// is present in the tree.\n\tstate._context = context;\n\tif (!provider) return context._defaultValue;\n\t// This is probably not safe to convert to \"!\"\n\tif (state._value == null) {\n\t\tstate._value = true;\n\t\tprovider.sub(currentComponent);\n\t}\n\treturn provider.props.value;\n}\n\n/**\n * Display a custom label for a custom hook for the devtools panel\n * @type {<T>(value: T, cb?: (value: T) => string | number) => void}\n */\nexport function useDebugValue(value, formatter) {\n\tif (options.useDebugValue) {\n\t\toptions.useDebugValue(\n\t\t\tformatter ? formatter(value) : /** @type {any}*/ (value)\n\t\t);\n\t}\n}\n\n/**\n * @param {(error: unknown, errorInfo: import('preact').ErrorInfo) => void} cb\n * @returns {[unknown, () => void]}\n */\nexport function useErrorBoundary(cb) {\n\t/** @type {import('./internal').ErrorBoundaryHookState} */\n\tconst state = getHookState(currentIndex++, 10);\n\tconst errState = useState();\n\tstate._value = cb;\n\tif (!currentComponent.componentDidCatch) {\n\t\tcurrentComponent.componentDidCatch = (err, errorInfo) => {\n\t\t\tif (state._value) state._value(err, errorInfo);\n\t\t\terrState[1](err);\n\t\t};\n\t}\n\treturn [\n\t\terrState[0],\n\t\t() => {\n\t\t\terrState[1](undefined);\n\t\t}\n\t];\n}\n\n/** @type {() => string} */\nexport function useId() {\n\t/** @type {import('./internal').IdHookState} */\n\tconst state = getHookState(currentIndex++, 11);\n\tif (!state._value) {\n\t\t// Grab either the root node or the nearest async boundary node.\n\t\t/** @type {import('./internal').VNode} */\n\t\tlet root = currentComponent._vnode;\n\t\twhile (root !== null && !root._mask && root._parent !== null) {\n\t\t\troot = root._parent;\n\t\t}\n\n\t\tlet mask = root._mask || (root._mask = [0, 0]);\n\t\tstate._value = 'P' + mask[0] + '-' + mask[1]++;\n\t}\n\n\treturn state._value;\n}\n\n/**\n * After paint effects consumer.\n */\nfunction flushAfterPaintEffects() {\n\tlet component;\n\twhile ((component = afterPaintEffects.shift())) {\n\t\tconst hooks = component.__hooks;\n\t\tif (!component._parentDom || !hooks) continue;\n\t\ttry {\n\t\t\thooks._pendingEffects.some(invokeCleanup);\n\t\t\thooks._pendingEffects.some(invokeEffect);\n\t\t\thooks._pendingEffects = [];\n\t\t} catch (e) {\n\t\t\thooks._pendingEffects = [];\n\t\t\toptions._catchError(e, component._vnode);\n\t\t}\n\t}\n}\n\nlet HAS_RAF = typeof requestAnimationFrame == 'function';\n\n/**\n * Schedule a callback to be invoked after the browser has a chance to paint a new frame.\n * Do this by combining requestAnimationFrame (rAF) + setTimeout to invoke a callback after\n * the next browser frame.\n *\n * Also, schedule a timeout in parallel to the the rAF to ensure the callback is invoked\n * even if RAF doesn't fire (for example if the browser tab is not visible)\n *\n * @param {() => void} callback\n */\nfunction afterNextFrame(callback) {\n\tconst done = () => {\n\t\tclearTimeout(timeout);\n\t\tif (HAS_RAF) cancelAnimationFrame(raf);\n\t\tsetTimeout(callback);\n\t};\n\tconst timeout = setTimeout(done, RAF_TIMEOUT);\n\n\tlet raf;\n\tif (HAS_RAF) {\n\t\traf = requestAnimationFrame(done);\n\t}\n}\n\n// Note: if someone used options.debounceRendering = requestAnimationFrame,\n// then effects will ALWAYS run on the NEXT frame instead of the current one, incurring a ~16ms delay.\n// Perhaps this is not such a big deal.\n/**\n * Schedule afterPaintEffects flush after the browser paints\n * @param {number} newQueueLength\n * @returns {void}\n */\nfunction afterPaint(newQueueLength) {\n\tif (newQueueLength === 1 || prevRaf !== options.requestAnimationFrame) {\n\t\tprevRaf = options.requestAnimationFrame;\n\t\t(prevRaf || afterNextFrame)(flushAfterPaintEffects);\n\t}\n}\n\n/**\n * @param {import('./internal').HookState} hook\n * @returns {void}\n */\nfunction invokeCleanup(hook) {\n\t// A hook cleanup can introduce a call to render which creates a new root, this will call options.vnode\n\t// and move the currentComponent away.\n\tconst comp = currentComponent;\n\tlet cleanup = hook._cleanup;\n\tif (typeof cleanup == 'function') {\n\t\thook._cleanup = undefined;\n\t\tcleanup();\n\t}\n\n\tcurrentComponent = comp;\n}\n\n/**\n * Invoke a Hook's effect\n * @param {import('./internal').EffectHookState} hook\n * @returns {void}\n */\nfunction invokeEffect(hook) {\n\t// A hook call can introduce a call to render which creates a new root, this will call options.vnode\n\t// and move the currentComponent away.\n\tconst comp = currentComponent;\n\thook._cleanup = hook._value();\n\tcurrentComponent = comp;\n}\n\n/**\n * @param {unknown[]} oldArgs\n * @param {unknown[]} newArgs\n * @returns {boolean}\n */\nfunction argsChanged(oldArgs, newArgs) {\n\treturn (\n\t\t!oldArgs ||\n\t\toldArgs.length !== newArgs.length ||\n\t\tnewArgs.some((arg, index) => arg !== oldArgs[index])\n\t);\n}\n\n/**\n * @template Arg\n * @param {Arg} arg\n * @param {(arg: Arg) => any} f\n * @returns {any}\n */\nfunction invokeOrReturn(arg, f) {\n\treturn typeof f == 'function' ? f(arg) : f;\n}\n", "// An named symbol/brand for detecting Signal instances even when they weren't\n// created using the same signals library version.\nconst BRAND_SYMBOL = Symbol.for(\"preact-signals\");\n\n// Flags for Computed and Effect.\nconst RUNNING = 1 << 0;\nconst NOTIFIED = 1 << 1;\nconst OUTDATED = 1 << 2;\nconst DISPOSED = 1 << 3;\nconst HAS_ERROR = 1 << 4;\nconst TRACKING = 1 << 5;\n\n// A linked list node used to track dependencies (sources) and dependents (targets).\n// Also used to remember the source's last version number that the target saw.\ntype Node = {\n\t// A source whose value the target depends on.\n\t_source: Signal;\n\t_prevSource?: Node;\n\t_nextSource?: Node;\n\n\t// A target that depends on the source and should be notified when the source changes.\n\t_target: Computed | Effect;\n\t_prevTarget?: Node;\n\t_nextTarget?: Node;\n\n\t// The version number of the source that target has last seen. We use version numbers\n\t// instead of storing the source value, because source values can take arbitrary amount\n\t// of memory, and computeds could hang on to them forever because they're lazily evaluated.\n\t// Use the special value -1 to mark potentially unused but recyclable nodes.\n\t_version: number;\n\n\t// Used to remember & roll back the source's previous `._node` value when entering &\n\t// exiting a new evaluation context.\n\t_rollbackNode?: Node;\n};\n\nfunction startBatch() {\n\tbatchDepth++;\n}\n\nfunction endBatch() {\n\tif (batchDepth > 1) {\n\t\tbatchDepth--;\n\t\treturn;\n\t}\n\n\tlet error: unknown;\n\tlet hasError = false;\n\treconcileBatchSnapshots();\n\n\twhile (batchedEffect !== undefined) {\n\t\tlet effect: Effect | undefined = batchedEffect;\n\t\tbatchedEffect = undefined;\n\n\t\tbatchIteration++;\n\n\t\twhile (effect !== undefined) {\n\t\t\tconst next: Effect | undefined = effect._nextBatchedEffect;\n\t\t\teffect._nextBatchedEffect = undefined;\n\t\t\teffect._flags &= ~NOTIFIED;\n\n\t\t\tif (!(effect._flags & DISPOSED) && needsToRecompute(effect)) {\n\t\t\t\ttry {\n\t\t\t\t\teffect._callback();\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (!hasError) {\n\t\t\t\t\t\terror = err;\n\t\t\t\t\t\thasError = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\teffect = next;\n\t\t}\n\t}\n\tbatchIteration = 0;\n\tbatchDepth--;\n\n\tif (hasError) {\n\t\tthrow error;\n\t}\n}\n\n/**\n * Combine multiple value updates into one \"commit\" at the end of the provided callback.\n *\n * Batches can be nested and changes are only flushed once the outermost batch callback\n * completes.\n *\n * Accessing a signal that has been modified within a batch will reflect its updated\n * value.\n *\n * @param fn The callback function.\n * @returns The value returned by the callback.\n */\nfunction batch<T>(fn: () => T): T {\n\tif (batchDepth > 0) {\n\t\treturn fn();\n\t}\n\tcurrentBatchSnapshotVersion = ++batchSnapshotVersion;\n\t/*@__INLINE__**/ startBatch();\n\ttry {\n\t\treturn fn();\n\t} finally {\n\t\tendBatch();\n\t}\n}\n\n// Currently evaluated computed or effect.\nlet evalContext: Computed | Effect | undefined = undefined;\n\n/**\n * Run a callback function that can access signal values without\n * subscribing to the signal updates.\n *\n * @param fn The callback function.\n * @returns The value returned by the callback.\n */\nfunction untracked<T>(fn: () => T): T {\n\tconst prevContext = evalContext;\n\tevalContext = undefined;\n\ttry {\n\t\treturn fn();\n\t} finally {\n\t\tevalContext = prevContext;\n\t}\n}\n\n// Effects collected into a batch.\nlet batchedEffect: Effect | undefined = undefined;\nlet batchDepth = 0;\nlet batchIteration = 0;\n\ntype BatchSnapshot = {\n\t_source: Signal;\n\t_value: unknown;\n\t_version: number;\n\t_next?: BatchSnapshot;\n};\n\nlet batchSnapshotVersion = 0;\nlet currentBatchSnapshotVersion = 0;\nlet batchSnapshots: BatchSnapshot | undefined = undefined;\n\n// A global version number for signals, used for fast-pathing repeated\n// computed.peek()/computed.value calls when nothing has changed globally.\nlet globalVersion = 0;\n\nfunction recordBatchSnapshot(source: Signal) {\n\t// Only capture writes during the user-visible batch callback, not during effect flush.\n\tif (batchDepth === 0 || batchIteration !== 0) {\n\t\treturn;\n\t}\n\n\tif (source._batchSnapshotVersion !== currentBatchSnapshotVersion) {\n\t\tsource._batchSnapshotVersion = currentBatchSnapshotVersion;\n\t\tbatchSnapshots = {\n\t\t\t_source: source,\n\t\t\t_value: source._value,\n\t\t\t_version: source._version,\n\t\t\t_next: batchSnapshots,\n\t\t};\n\t}\n}\n\nfunction reconcileBatchSnapshots() {\n\tlet snapshots = batchSnapshots;\n\tbatchSnapshots = undefined;\n\n\twhile (snapshots !== undefined) {\n\t\tif (snapshots._source._value === snapshots._value) {\n\t\t\tsnapshots._source._version = snapshots._version;\n\t\t}\n\t\tsnapshots = snapshots._next;\n\t}\n}\n\nfunction addDependency(signal: Signal): Node | undefined {\n\tif (evalContext === undefined) {\n\t\treturn undefined;\n\t}\n\n\tlet node = signal._node;\n\tif (node === undefined || node._target !== evalContext) {\n\t\t/**\n\t\t * `signal` is a new dependency. Create a new dependency node, and set it\n\t\t * as the tail of the current context's dependency list. e.g:\n\t\t *\n\t\t * { A <-> B }\n\t\t * ↑ ↑\n\t\t * tail node (new)\n\t\t * ↓\n\t\t * { A <-> B <-> C }\n\t\t * ↑\n\t\t * tail (evalContext._sources)\n\t\t */\n\t\tnode = {\n\t\t\t_version: 0,\n\t\t\t_source: signal,\n\t\t\t_prevSource: evalContext._sources,\n\t\t\t_nextSource: undefined,\n\t\t\t_target: evalContext,\n\t\t\t_prevTarget: undefined,\n\t\t\t_nextTarget: undefined,\n\t\t\t_rollbackNode: node,\n\t\t};\n\n\t\tif (evalContext._sources !== undefined) {\n\t\t\tevalContext._sources._nextSource = node;\n\t\t}\n\t\tevalContext._sources = node;\n\t\tsignal._node = node;\n\n\t\t// Subscribe to change notifications from this dependency if we're in an effect\n\t\t// OR evaluating a computed signal that in turn has subscribers.\n\t\tif (evalContext._flags & TRACKING) {\n\t\t\tsignal._subscribe(node);\n\t\t}\n\t\treturn node;\n\t} else if (node._version === -1) {\n\t\t// `signal` is an existing dependency from a previous evaluation. Reuse it.\n\t\tnode._version = 0;\n\n\t\t/**\n\t\t * If `node` is not already the current tail of the dependency list (i.e.\n\t\t * there is a next node in the list), then make the `node` the new tail. e.g:\n\t\t *\n\t\t * { A <-> B <-> C <-> D }\n\t\t * ↑ ↑\n\t\t * node ┌─── tail (evalContext._sources)\n\t\t * └─────│─────┐\n\t\t * ↓ ↓\n\t\t * { A <-> C <-> D <-> B }\n\t\t * ↑\n\t\t * tail (evalContext._sources)\n\t\t */\n\t\tif (node._nextSource !== undefined) {\n\t\t\tnode._nextSource._prevSource = node._prevSource;\n\n\t\t\tif (node._prevSource !== undefined) {\n\t\t\t\tnode._prevSource._nextSource = node._nextSource;\n\t\t\t}\n\n\t\t\tnode._prevSource = evalContext._sources;\n\t\t\tnode._nextSource = undefined;\n\n\t\t\tevalContext._sources!._nextSource = node;\n\t\t\tevalContext._sources = node;\n\t\t}\n\n\t\t// We can assume that the currently evaluated effect / computed signal is already\n\t\t// subscribed to change notifications from `signal` if needed.\n\t\treturn node;\n\t}\n\treturn undefined;\n}\n\n//#region Signal\n\n/**\n * The base class for plain and computed signals.\n */\n//\n// A function with the same name is defined later, so we need to ignore TypeScript's\n// warning about a redeclared variable.\n//\n// The class is declared here, but later implemented with ES5-style prototypes.\n// This enables better control of the transpiled output size.\n// @ts-ignore: \"Cannot redeclare exported variable 'Signal'.\"\ndeclare class Signal<T = any> {\n\t/** @internal */\n\t_value: unknown;\n\n\t/**\n\t * @internal\n\t * Version numbers should always be >= 0, because the special value -1 is used\n\t * by Nodes to signify potentially unused but recyclable nodes.\n\t */\n\t_version: number;\n\n\t/** @internal */\n\t_node?: Node;\n\n\t/** @internal */\n\t_targets?: Node;\n\n\t/** @internal */\n\t_batchSnapshotVersion: number;\n\n\tconstructor(value?: T, options?: SignalOptions<T>);\n\n\t/** @internal */\n\t_refresh(): boolean;\n\n\t/** @internal */\n\t_subscribe(node: Node): void;\n\n\t/** @internal */\n\t_unsubscribe(node: Node): void;\n\n\t/** @internal */\n\t_watched?(this: Signal<T>): void;\n\n\t/** @internal */\n\t_unwatched?(this: Signal<T>): void;\n\n\tsubscribe(fn: (value: T) => void): () => void;\n\n\tname?: string;\n\n\tvalueOf(): T;\n\n\ttoString(): string;\n\n\ttoJSON(): T;\n\n\tpeek(): T;\n\n\tbrand: typeof BRAND_SYMBOL;\n\n\tget value(): T;\n\tset value(value: T);\n}\n\nexport interface SignalOptions<T = any> {\n\twatched?: (this: Signal<T>) => void;\n\tunwatched?: (this: Signal<T>) => void;\n\tname?: string;\n}\n\n/** @internal */\n// A class with the same name has already been declared, so we need to ignore\n// TypeScript's warning about a redeclared variable.\n//\n// The previously declared class is implemented here with ES5-style prototypes.\n// This enables better control of the transpiled output size.\n// @ts-ignore: \"Cannot redeclare exported variable 'Signal'.\"\nfunction Signal(this: Signal, value?: unknown, options?: SignalOptions) {\n\tthis._value = value;\n\tthis._version = 0;\n\tthis._node = undefined;\n\tthis._targets = undefined;\n\tthis._batchSnapshotVersion = 0;\n\tthis._watched = options?.watched;\n\tthis._unwatched = options?.unwatched;\n\tthis.name = options?.name;\n}\n\nSignal.prototype.brand = BRAND_SYMBOL;\n\nSignal.prototype._refresh = function () {\n\treturn true;\n};\n\nSignal.prototype._subscribe = function (node) {\n\tconst targets = this._targets;\n\tif (targets !== node && node._prevTarget === undefined) {\n\t\tnode._nextTarget = targets;\n\t\tthis._targets = node;\n\n\t\tif (targets !== undefined) {\n\t\t\ttargets._prevTarget = node;\n\t\t} else {\n\t\t\tuntracked(() => {\n\t\t\t\tthis._watched?.call(this);\n\t\t\t});\n\t\t}\n\t}\n};\n\nSignal.prototype._unsubscribe = function (node) {\n\t// Only run the unsubscribe step if the signal has any subscribers to begin with.\n\tif (this._targets !== undefined) {\n\t\tconst prev = node._prevTarget;\n\t\tconst next = node._nextTarget;\n\t\tif (prev !== undefined) {\n\t\t\tprev._nextTarget = next;\n\t\t\tnode._prevTarget = undefined;\n\t\t}\n\n\t\tif (next !== undefined) {\n\t\t\tnext._prevTarget = prev;\n\t\t\tnode._nextTarget = undefined;\n\t\t}\n\n\t\tif (node === this._targets) {\n\t\t\tthis._targets = next;\n\t\t\tif (next === undefined) {\n\t\t\t\tuntracked(() => {\n\t\t\t\t\tthis._unwatched?.call(this);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n};\n\nSignal.prototype.subscribe = function (fn) {\n\treturn effect(\n\t\t() => {\n\t\t\tconst value = this.value;\n\t\t\tconst prevContext = evalContext;\n\t\t\tevalContext = undefined;\n\t\t\ttry {\n\t\t\t\tfn(value);\n\t\t\t} finally {\n\t\t\t\tevalContext = prevContext;\n\t\t\t}\n\t\t},\n\t\t{ name: \"sub\" }\n\t);\n};\n\nSignal.prototype.valueOf = function () {\n\treturn this.value;\n};\n\nSignal.prototype.toString = function () {\n\treturn this.value + \"\";\n};\n\nSignal.prototype.toJSON = function () {\n\treturn this.value;\n};\n\nSignal.prototype.peek = function () {\n\treturn untracked(() => this.value);\n};\n\nObject.defineProperty(Signal.prototype, \"value\", {\n\tget(this: Signal) {\n\t\tconst node = addDependency(this);\n\t\tif (node !== undefined) {\n\t\t\tnode._version = this._version;\n\t\t}\n\t\treturn this._value;\n\t},\n\tset(this: Signal, value) {\n\t\tif (value !== this._value) {\n\t\t\tif (batchIteration > 100) {\n\t\t\t\tthrow new Error(\"Cycle detected\");\n\t\t\t}\n\n\t\t\trecordBatchSnapshot(this);\n\t\t\tthis._value = value;\n\t\t\tthis._version++;\n\t\t\tglobalVersion++;\n\n\t\t\t/**@__INLINE__*/ startBatch();\n\t\t\ttry {\n\t\t\t\tfor (\n\t\t\t\t\tlet node = this._targets;\n\t\t\t\t\tnode !== undefined;\n\t\t\t\t\tnode = node._nextTarget\n\t\t\t\t) {\n\t\t\t\t\tnode._target._notify();\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tendBatch();\n\t\t\t}\n\t\t}\n\t},\n});\n\n/**\n * Create a new plain signal.\n *\n * @param value The initial value for the signal.\n * @returns A new signal.\n */\nexport function signal<T>(value: T, options?: SignalOptions<T>): Signal<T>;\nexport function signal<T = undefined>(): Signal<T | undefined>;\nexport function signal<T>(value?: T, options?: SignalOptions<T>): Signal<T> {\n\treturn new Signal(value, options);\n}\n\n//#endregion Signal\n\n//#region Computed\n\nfunction needsToRecompute(target: Computed | Effect): boolean {\n\t// Check the dependencies for changed values. The dependency list is already\n\t// in order of use. Therefore if multiple dependencies have changed values, only\n\t// the first used dependency is re-evaluated at this point.\n\tfor (\n\t\tlet node = target._sources;\n\t\tnode !== undefined;\n\t\tnode = node._nextSource\n\t) {\n\t\tif (\n\t\t\t// If the dependency has definitely been updated since its version number\n\t\t\t// was observed, then we need to recompute. This first check is not strictly\n\t\t\t// necessary for correctness, but allows us to skip the refresh call if the\n\t\t\t// dependency has already been updated.\n\t\t\tnode._source._version !== node._version ||\n\t\t\t// Refresh the dependency. If there's something blocking the refresh (e.g. a\n\t\t\t// dependency cycle), then we need to recompute.\n\t\t\t!node._source._refresh() ||\n\t\t\t// If the dependency got a new version after the refresh, then we need to recompute.\n\t\t\tnode._source._version !== node._version\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t}\n\t// If none of the dependencies have changed values since last recompute then\n\t// there's no need to recompute.\n\treturn false;\n}\n\nfunction prepareSources(target: Computed | Effect) {\n\t/**\n\t * 1. Mark all current sources as re-usable nodes (version: -1)\n\t * 2. Set a rollback node if the current node is being used in a different context\n\t * 3. Point 'target._sources' to the tail of the doubly-linked list, e.g:\n\t *\n\t * { undefined <- A <-> B <-> C -> undefined }\n\t * ↑ ↑\n\t * │ └──────┐\n\t * target._sources = A; (node is head) │\n\t * ↓ │\n\t * target._sources = C; (node is tail) ─┘\n\t */\n\tfor (\n\t\tlet node = target._sources;\n\t\tnode !== undefined;\n\t\tnode = node._nextSource\n\t) {\n\t\tconst rollbackNode = node._source._node;\n\t\tif (rollbackNode !== undefined) {\n\t\t\tnode._rollbackNode = rollbackNode;\n\t\t}\n\t\tnode._source._node = node;\n\t\tnode._version = -1;\n\n\t\tif (node._nextSource === undefined) {\n\t\t\ttarget._sources = node;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nfunction cleanupSources(target: Computed | Effect) {\n\tlet node = target._sources;\n\tlet head: Node | undefined = undefined;\n\n\t/**\n\t * At this point 'target._sources' points to the tail of the doubly-linked list.\n\t * It contains all existing sources + new sources in order of use.\n\t * Iterate backwards until we find the head node while dropping old dependencies.\n\t */\n\twhile (node !== undefined) {\n\t\tconst prev = node._prevSource;\n\n\t\t/**\n\t\t * The node was not re-used, unsubscribe from its change notifications and remove itself\n\t\t * from the doubly-linked list. e.g:\n\t\t *\n\t\t * { A <-> B <-> C }\n\t\t * ↓\n\t\t * { A <-> C }\n\t\t */\n\t\tif (node._version === -1) {\n\t\t\tnode._source._unsubscribe(node);\n\n\t\t\tif (prev !== undefined) {\n\t\t\t\tprev._nextSource = node._nextSource;\n\t\t\t}\n\t\t\tif (node._nextSource !== undefined) {\n\t\t\t\tnode._nextSource._prevSource = prev;\n\t\t\t}\n\t\t} else {\n\t\t\t/**\n\t\t\t * The new head is the last node seen which wasn't removed/unsubscribed\n\t\t\t * from the doubly-linked list. e.g:\n\t\t\t *\n\t\t\t * { A <-> B <-> C }\n\t\t\t * ↑ ↑ ↑\n\t\t\t * │ │ └ head = node\n\t\t\t * │ └ head = node\n\t\t\t * └ head = node\n\t\t\t */\n\t\t\thead = node;\n\t\t}\n\n\t\tnode._source._node = node._rollbackNode;\n\t\tif (node._rollbackNode !== undefined) {\n\t\t\tnode._rollbackNode = undefined;\n\t\t}\n\n\t\tnode = prev;\n\t}\n\n\ttarget._sources = head;\n}\n\n/**\n * The base class for computed signals.\n */\ndeclare class Computed<T = any> extends Signal<T> {\n\t_fn: () => T;\n\t_sources?: Node;\n\t_globalVersion: number;\n\t_flags: number;\n\n\tconstructor(fn: () => T, options?: SignalOptions<T>);\n\n\t_notify(): void;\n\tget value(): T;\n}\n\n/** @internal */\nfunction Computed(this: Computed, fn: () => unknown, options?: SignalOptions) {\n\tSignal.call(this, undefined);\n\n\tthis._fn = fn;\n\tthis._sources = undefined;\n\tthis._globalVersion = globalVersion - 1;\n\tthis._flags = OUTDATED;\n\tthis._watched = options?.watched;\n\tthis._unwatched = options?.unwatched;\n\tthis.name = options?.name;\n}\n\nComputed.prototype = new Signal() as Computed;\n\nComputed.prototype._refresh = function () {\n\tthis._flags &= ~NOTIFIED;\n\n\tif (this._flags & RUNNING) {\n\t\treturn false;\n\t}\n\n\t// If this computed signal has subscribed to updates from its dependencies\n\t// (TRACKING flag set) and none of them have notified about changes (OUTDATED\n\t// flag not set), then the computed value can't have changed.\n\tif ((this._flags & (OUTDATED | TRACKING)) === TRACKING) {\n\t\treturn true;\n\t}\n\tthis._flags &= ~OUTDATED;\n\n\tif (this._globalVersion === globalVersion) {\n\t\treturn true;\n\t}\n\tthis._globalVersion = globalVersion;\n\n\t// Mark this computed signal running before checking the dependencies for value\n\t// changes, so that the RUNNING flag can be used to notice cyclical dependencies.\n\tthis._flags |= RUNNING;\n\tif (this._version > 0 && !needsToRecompute(this)) {\n\t\tthis._flags &= ~RUNNING;\n\t\treturn true;\n\t}\n\n\tconst prevContext = evalContext;\n\ttry {\n\t\tprepareSources(this);\n\t\tevalContext = this;\n\t\tconst value = this._fn();\n\t\tif (\n\t\t\tthis._flags & HAS_ERROR ||\n\t\t\tthis._value !== value ||\n\t\t\tthis._version === 0\n\t\t) {\n\t\t\tthis._value = value;\n\t\t\tthis._flags &= ~HAS_ERROR;\n\t\t\tthis._version++;\n\t\t}\n\t} catch (err) {\n\t\tthis._value = err;\n\t\tthis._flags |= HAS_ERROR;\n\t\tthis._version++;\n\t}\n\tevalContext = prevContext;\n\tcleanupSources(this);\n\tthis._flags &= ~RUNNING;\n\treturn true;\n};\n\nComputed.prototype._subscribe = function (node) {\n\tif (this._targets === undefined) {\n\t\tthis._flags |= OUTDATED | TRACKING;\n\n\t\t// A computed signal subscribes lazily to its dependencies when it\n\t\t// gets its first subscriber.\n\t\tfor (\n\t\t\tlet node = this._sources;\n\t\t\tnode !== undefined;\n\t\t\tnode = node._nextSource\n\t\t) {\n\t\t\tnode._source._subscribe(node);\n\t\t}\n\t}\n\tSignal.prototype._subscribe.call(this, node);\n};\n\nComputed.prototype._unsubscribe = function (node) {\n\t// Only run the unsubscribe step if the computed signal has any subscribers.\n\tif (this._targets !== undefined) {\n\t\tSignal.prototype._unsubscribe.call(this, node);\n\n\t\t// Computed signal unsubscribes from its dependencies when it loses its last subscriber.\n\t\t// This makes it possible for unreferences subgraphs of computed signals to get garbage collected.\n\t\tif (this._targets === undefined) {\n\t\t\tthis._flags &= ~TRACKING;\n\n\t\t\tfor (\n\t\t\t\tlet node = this._sources;\n\t\t\t\tnode !== undefined;\n\t\t\t\tnode = node._nextSource\n\t\t\t) {\n\t\t\t\tnode._source._unsubscribe(node);\n\t\t\t}\n\t\t}\n\t}\n};\n\nComputed.prototype._notify = function () {\n\tif (!(this._flags & NOTIFIED)) {\n\t\tthis._flags |= OUTDATED | NOTIFIED;\n\n\t\tfor (\n\t\t\tlet node = this._targets;\n\t\t\tnode !== undefined;\n\t\t\tnode = node._nextTarget\n\t\t) {\n\t\t\tnode._target._notify();\n\t\t}\n\t}\n};\n\nObject.defineProperty(Computed.prototype, \"value\", {\n\tget(this: Computed) {\n\t\tif (this._flags & RUNNING) {\n\t\t\tthrow new Error(\"Cycle detected\");\n\t\t}\n\t\tconst node = addDependency(this);\n\t\tthis._refresh();\n\t\tif (node !== undefined) {\n\t\t\tnode._version = this._version;\n\t\t}\n\t\tif (this._flags & HAS_ERROR) {\n\t\t\tthrow this._value;\n\t\t}\n\t\treturn this._value;\n\t},\n});\n\n/**\n * An interface for read-only signals.\n */\ninterface ReadonlySignal<T = any> {\n\treadonly value: T;\n\tpeek(): T;\n\n\tsubscribe(fn: (value: T) => void): () => void;\n\tvalueOf(): T;\n\ttoString(): string;\n\ttoJSON(): T;\n\tbrand: typeof BRAND_SYMBOL;\n}\n\n/**\n * Create a new signal that is computed based on the values of other signals.\n *\n * The returned computed signal is read-only, and its value is automatically\n * updated when any signals accessed from within the callback function change.\n *\n * @param fn The effect callback.\n * @returns A new read-only signal.\n */\nfunction computed<T>(\n\tfn: () => T,\n\toptions?: SignalOptions<T>\n): ReadonlySignal<T> {\n\treturn new Computed(fn, options);\n}\n\n//#endregion Computed\n\n//#region Effect\n\nfunction cleanupEffect(effect: Effect) {\n\tconst cleanup = effect._cleanup;\n\teffect._cleanup = undefined;\n\n\tif (typeof cleanup === \"function\") {\n\t\t/*@__INLINE__**/ startBatch();\n\n\t\t// Run cleanup functions always outside of any context.\n\t\tconst prevContext = evalContext;\n\t\tevalContext = undefined;\n\t\ttry {\n\t\t\tcleanup();\n\t\t} catch (err) {\n\t\t\teffect._flags &= ~RUNNING;\n\t\t\teffect._flags |= DISPOSED;\n\t\t\tdisposeEffect(effect);\n\t\t\tthrow err;\n\t\t} finally {\n\t\t\tevalContext = prevContext;\n\t\t\tendBatch();\n\t\t}\n\t}\n}\n\nfunction disposeEffect(effect: Effect) {\n\tfor (\n\t\tlet node = effect._sources;\n\t\tnode !== undefined;\n\t\tnode = node._nextSource\n\t) {\n\t\tnode._source._unsubscribe(node);\n\t}\n\teffect._fn = undefined;\n\teffect._sources = undefined;\n\n\tcleanupEffect(effect);\n}\n\nfunction endEffect(this: Effect, prevContext?: Computed | Effect) {\n\tif (evalContext !== this) {\n\t\tthrow new Error(\"Out-of-order effect\");\n\t}\n\tcleanupSources(this);\n\tevalContext = prevContext;\n\n\tthis._flags &= ~RUNNING;\n\tif (this._flags & DISPOSED) {\n\t\tdisposeEffect(this);\n\t}\n\tendBatch();\n}\n\ntype EffectFn =\n\t| ((this: { dispose: () => void }) => void | (() => void))\n\t| (() => void | (() => void));\n\n// Avoid hard-requiring the ESNext.Disposable lib in consuming tsconfigs.\n// When `Symbol.dispose` is available, this becomes a symbol-keyed disposer type.\ntype DisposeSymbol = typeof Symbol extends { readonly dispose: infer TDispose }\n\t? TDispose\n\t: never;\ntype DisposableLike = {\n\t[K in DisposeSymbol & PropertyKey]: () => void;\n};\ntype DisposeFn = (() => void) & DisposableLike;\n\n/**\n * The base class for reactive effects.\n */\ndeclare class Effect {\n\t_fn?: EffectFn;\n\t_cleanup?: () => void;\n\t_sources?: Node;\n\t_nextBatchedEffect?: Effect;\n\t_flags: number;\n\t_debugCallback?: () => void;\n\tname?: string;\n\n\tconstructor(fn: EffectFn, options?: EffectOptions);\n\n\t_callback(): void;\n\t_start(): () => void;\n\t_notify(): void;\n\t_dispose(): void;\n\tdispose(): void;\n}\n\nexport interface EffectOptions {\n\tname?: string;\n}\n\nlet capturedEffects: Effect[] | undefined;\n\n/** @internal */\nfunction Effect(this: Effect, fn: EffectFn, options?: EffectOptions) {\n\tthis._fn = fn;\n\tthis._cleanup = undefined;\n\tthis._sources = undefined;\n\tthis._nextBatchedEffect = undefined;\n\tthis._flags = TRACKING;\n\tthis.name = options?.name;\n\n\tif (capturedEffects) {\n\t\tcapturedEffects.push(this);\n\t}\n}\n\nEffect.prototype._callback = function () {\n\tconst finish = this._start();\n\ttry {\n\t\tif (this._flags & DISPOSED) return;\n\t\tif (this._fn === undefined) return;\n\n\t\tconst cleanup = this._fn();\n\t\tif (typeof cleanup === \"function\") {\n\t\t\tthis._cleanup = cleanup;\n\t\t}\n\t} finally {\n\t\tfinish();\n\t}\n};\n\nEffect.prototype._start = function () {\n\tif (this._flags & RUNNING) {\n\t\tthrow new Error(\"Cycle detected\");\n\t}\n\tthis._flags |= RUNNING;\n\tthis._flags &= ~DISPOSED;\n\tcleanupEffect(this);\n\tprepareSources(this);\n\n\t/*@__INLINE__**/ startBatch();\n\tconst prevContext = evalContext;\n\tevalContext = this;\n\treturn endEffect.bind(this, prevContext);\n};\n\nEffect.prototype._notify = function () {\n\tif (!(this._flags & NOTIFIED)) {\n\t\tthis._flags |= NOTIFIED;\n\t\tthis._nextBatchedEffect = batchedEffect;\n\t\tbatchedEffect = this;\n\t}\n};\n\nEffect.prototype._dispose = function () {\n\tthis._flags |= DISPOSED;\n\n\tif (!(this._flags & RUNNING)) {\n\t\tdisposeEffect(this);\n\t}\n};\n\nEffect.prototype.dispose = function () {\n\tthis._dispose();\n};\n/**\n * Create an effect to run arbitrary code in response to signal changes.\n *\n * An effect tracks which signals are accessed within the given callback\n * function `fn`, and re-runs the callback when those signals change.\n *\n * The callback may return a cleanup function. The cleanup function gets\n * run once, either when the callback is next called or when the effect\n * gets disposed, whichever happens first.\n *\n * @param fn The effect callback.\n * @returns A function for disposing the effect.\n */\nfunction effect(fn: EffectFn, options?: EffectOptions): DisposeFn {\n\tconst effect = new Effect(fn, options);\n\ttry {\n\t\teffect._callback();\n\t} catch (err) {\n\t\teffect._dispose();\n\t\tthrow err;\n\t}\n\t// Return a bound function instead of a wrapper like `() => effect._dispose()`,\n\t// because bound functions seem to be just as fast and take up a lot less memory.\n\tconst dispose = effect._dispose.bind(effect);\n\t(dispose as any)[Symbol.dispose] = dispose;\n\treturn dispose as DisposeFn;\n}\n\n//#endregion Effect\n\n//#region Action\n\nfunction action<TArgs extends unknown[], TReturn>(\n\tfn: (...args: TArgs) => TReturn\n): (...args: TArgs) => TReturn {\n\treturn function actionWrapper(this: unknown, ...args: TArgs) {\n\t\treturn batch(() => untracked(() => fn.apply(this, args)));\n\t};\n}\n\n//#endregion Action\n\n//#region createModel\n\n/** Models should only contain signals, actions, and nested objects containing only signals and actions. */\ntype ValidateModel<TModel> = {\n\t[Key in keyof TModel]: TModel[Key] extends ReadonlySignal<unknown>\n\t\t? TModel[Key]\n\t\t: TModel[Key] extends (...args: any[]) => any\n\t\t\t? TModel[Key]\n\t\t\t: TModel[Key] extends object\n\t\t\t\t? ValidateModel<TModel[Key]>\n\t\t\t\t: `Property ${Key extends string ? `'${Key}' ` : \"\"}is not a Signal, Action, or an object that contains only Signals and Actions.`;\n};\n\nexport type Model<TModel> = ValidateModel<TModel> & DisposableLike;\n\nexport type ModelFactory<TModel, TFactoryArgs extends any[] = []> = (\n\t...args: TFactoryArgs\n) => ValidateModel<TModel>;\nexport type ModelConstructor<TModel, TFactoryArgs extends any[] = []> = new (\n\t...args: TFactoryArgs\n) => Model<TModel>;\n\n/**\n * The public types for ModelConstructor require using `new` to help\n * disambiguate the function passed into `createModel` and the returned\n * constructor function. It is easier to say that `createModel` accepts\n * a factory and returns a class, then to say it accepts a factory and\n * returns a factory. In other words, this example:\n *\n * ```ts\n * const PersonModel = createModel((name: string) => ({ ... }));\n * const person = new PersonModel(\"John\");\n * ```\n *\n * is easier to understand than this example:\n *\n * ```ts\n * const createPerson = createModel((name: string) => ({ ... }));\n * const person = createPerson(\"John\");\n * ```\n *\n * However, internally we implement `createModel` to return a function\n * that can be called without `new` for simplicity. To bridge the gap\n * between the public types and the internal implementation, we define\n * this internal interface that extends the public interface but also\n * allows calling without `new`.\n *\n * This pattern is used by the Preact & React adapters to make instantiating\n * a model or a function that returns a model easier.\n *\n * @internal\n */\ninterface InternalModelConstructor<\n\tTModel,\n\tTFactoryArgs extends any[],\n> extends ModelConstructor<TModel, TFactoryArgs> {\n\t(...args: TFactoryArgs): Model<TModel>;\n}\n\nfunction startCapturingEffects(): () => Effect[] | undefined {\n\tlet prevCapturedEffects = capturedEffects;\n\tcapturedEffects = [];\n\n\treturn function stopCapturingEffects() {\n\t\tlet modelEffects = capturedEffects;\n\t\tif (capturedEffects && prevCapturedEffects) {\n\t\t\tprevCapturedEffects = prevCapturedEffects.concat(capturedEffects);\n\t\t}\n\n\t\tcapturedEffects = prevCapturedEffects;\n\n\t\treturn modelEffects;\n\t};\n}\n\nconst wrapInAction = (value: Record<string, unknown>) => {\n\tfor (const key in value) {\n\t\tconst val = value[key];\n\t\tif (typeof val === \"function\") {\n\t\t\tvalue[key] = action(val as (...args: unknown[]) => unknown);\n\t\t} else if (typeof val === \"object\" && val !== null && !(\"brand\" in val)) {\n\t\t\t// Recursively wrap nested object properties in actions. This allows users to write\n\t\t\t// nested models without worrying about wrapping their functions in `action`.\n\t\t\twrapInAction(val as Record<string, unknown>);\n\t\t}\n\t}\n};\n\nfunction createModel<TModel, TFactoryArgs extends any[] = []>(\n\tmodelFactory: ModelFactory<TModel, TFactoryArgs>\n): ModelConstructor<TModel, TFactoryArgs> {\n\treturn function SignalModel(...args: TFactoryArgs): Model<TModel> {\n\t\tlet modelEffects: Effect[] | undefined;\n\t\tlet model: Model<TModel>;\n\n\t\tconst stopCapturingEffects = startCapturingEffects();\n\t\ttry {\n\t\t\tmodel = modelFactory(...args) as Model<TModel>;\n\t\t} catch (err) {\n\t\t\t// Drop any captured effects on error. Errors from nested models will bubble\n\t\t\t// up here and recursively reset `capturedEffects` to `undefined` preventing\n\t\t\t// any captured effects from leaking\n\t\t\tcapturedEffects = undefined;\n\t\t\tthrow err;\n\t\t} finally {\n\t\t\tmodelEffects = stopCapturingEffects();\n\t\t}\n\n\t\twrapInAction(model);\n\n\t\tmodel[Symbol.dispose] = action(function disposeModel() {\n\t\t\tif (modelEffects) {\n\t\t\t\tfor (let i = 0; i < modelEffects.length; i++) {\n\t\t\t\t\tmodelEffects[i].dispose();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmodelEffects = undefined;\n\t\t});\n\n\t\treturn model;\n\t} as InternalModelConstructor<TModel, TFactoryArgs>;\n}\n\n//#endregion createModel\n\nexport {\n\tcomputed,\n\teffect,\n\tbatch,\n\tuntracked,\n\taction,\n\tcreateModel,\n\tSignal,\n\tReadonlySignal,\n\tEffect,\n\tComputed,\n};\n", "import { options, Component, isValidElement, Fragment } from \"preact\";\nimport { useRef, useMemo, useEffect } from \"preact/hooks\";\nimport {\n\tsignal,\n\tcomputed,\n\tbatch,\n\teffect,\n\taction,\n\tcreateModel,\n\ttype Model,\n\ttype ModelConstructor,\n\tSignal,\n\ttype ReadonlySignal,\n\tuntracked,\n\tSignalOptions,\n\tEffectOptions,\n} from \"@preact/signals-core\";\nimport {\n\tVNode,\n\tOptionsTypes,\n\tHookFn,\n\tEffect,\n\tPropertyUpdater,\n\tAugmentedComponent,\n\tAugmentedElement as Element,\n} from \"./internal\";\n\nexport {\n\tsignal,\n\tcomputed,\n\tbatch,\n\teffect,\n\taction,\n\ttype Model,\n\ttype ModelConstructor,\n\tcreateModel,\n\tSignal,\n\ttype ReadonlySignal,\n\tuntracked,\n};\n\nconst DEVTOOLS_ENABLED =\n\ttypeof window !== \"undefined\" && !!window.__PREACT_SIGNALS_DEVTOOLS__;\n\nconst HAS_PENDING_UPDATE = 1 << 0;\nconst HAS_HOOK_STATE = 1 << 1;\nconst HAS_COMPUTEDS = 1 << 2;\n\nlet oldNotify: (this: Effect) => void,\n\teffectsQueue: Array<Effect> = [],\n\tdomQueue: Array<Effect> = [];\n\n// Capture the original `Effect.prototype._notify` method so that we can install\n// custom `._notify`s for each different use-case but still call the original\n// implementation in the end. Dispose the temporary effect immediately afterwards.\neffect(function (this: Effect) {\n\toldNotify = this._notify;\n})();\n\n// Install a Preact options hook\nfunction hook<T extends OptionsTypes>(hookName: T, hookFn: HookFn<T>) {\n\t// @ts-ignore-next-line private options hooks usage\n\toptions[hookName] = hookFn.bind(null, options[hookName] || (() => {}));\n}\n\nlet currentComponent: AugmentedComponent | undefined;\nlet finishUpdate: (() => void) | undefined;\n\nfunction setCurrentUpdater(updater?: Effect) {\n\t// end tracking for the current update:\n\tif (finishUpdate) {\n\t\tconst finish = finishUpdate;\n\t\tfinishUpdate = undefined;\n\t\tfinish();\n\t}\n\t// start tracking the new update:\n\tfinishUpdate = updater && updater._start();\n}\n\nfunction createUpdater(update: () => void, name: string) {\n\tlet updater!: Effect;\n\teffect(\n\t\tfunction (this: Effect) {\n\t\t\tupdater = this;\n\t\t},\n\t\t{ name }\n\t);\n\tupdater._callback = update;\n\treturn updater;\n}\n\n/** @todo This may be needed for complex prop value detection. */\n// function isSignalValue(value: any): value is Signal {\n// \tif (typeof value !== \"object\" || value == null) return false;\n// \tif (value instanceof Signal) return true;\n// \t// @TODO: uncomment this when we land Reactive (ideally behind a brand check)\n// \t// for (let i in value) if (value[i] instanceof Signal) return true;\n// \treturn false;\n// }\n\n/**\n * A wrapper component that renders a Signal directly as a Text node.\n * @todo: in Preact 11, just decorate Signal with `type:null`\n */\nfunction SignalValue(this: AugmentedComponent, { data }: { data: Signal }) {\n\t// hasComputeds.add(this);\n\n\t// Store the props.data signal in another signal so that\n\t// passing a new signal reference re-runs the text computed:\n\tconst currentSignal = useSignal(data);\n\tcurrentSignal.value = data;\n\n\tconst [isText, s] = useMemo(() => {\n\t\tlet self = this;\n\t\t// mark the parent component as having computeds so it gets optimized\n\t\tlet v = this.__v;\n\t\twhile ((v = v.__!)) {\n\t\t\tif (v.__c) {\n\t\t\t\tv.__c._updateFlags |= HAS_COMPUTEDS;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tconst wrappedSignal = computed(() => {\n\t\t\tlet s = currentSignal.value.value;\n\t\t\treturn s === 0 ? 0 : s === true ? \"\" : s || \"\";\n\t\t});\n\n\t\tconst isText = computed(\n\t\t\t() =>\n\t\t\t\t!Array.isArray(wrappedSignal.value) &&\n\t\t\t\t!isValidElement(wrappedSignal.value)\n\t\t);\n\t\t// Update text nodes directly without rerendering when the new value\n\t\t// is also text.\n\t\tconst dispose = effect(function (this: Effect) {\n\t\t\tthis._notify = notifyDomUpdates;\n\n\t\t\t// Subscribe to wrappedSignal updates only when its values are text...\n\t\t\tif (isText.value) {\n\t\t\t\t// ...but regardless of `self.base`'s current value, as it can be\n\t\t\t\t// undefined before mounting or a non-text node. In both of those cases\n\t\t\t\t// the update gets handled by a full rerender.\n\t\t\t\tconst value = wrappedSignal.value;\n\t\t\t\tif (self.__v && self.__v.__e && self.__v.__e.nodeType === 3) {\n\t\t\t\t\t(self.__v.__e as Text).data = value;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Piggyback this._updater's disposal to ensure that the text updater effect\n\t\t// above also gets disposed on unmount.\n\t\tconst oldDispose = this._updater!._dispose;\n\t\tthis._updater!._dispose = function () {\n\t\t\tdispose();\n\t\t\toldDispose.call(this);\n\t\t};\n\n\t\treturn [isText, wrappedSignal];\n\t}, []);\n\n\t// Rerender the component whenever `data.value` changes from a VNode\n\t// to another VNode, from text to a VNode, or from a VNode to text.\n\t// That is, everything else except text-to-text updates.\n\t//\n\t// This also ensures that the backing DOM node types gets updated to\n\t// text nodes and back when needed.\n\t//\n\t// For text-to-text updates, `.peek()` is used to skip full rerenders,\n\t// leaving them to the optimized path above.\n\treturn isText.value ? s.peek() : s.value;\n}\n\nSignalValue.displayName = \"ReactiveTextNode\";\n\nObject.defineProperties(Signal.prototype, {\n\tconstructor: { configurable: true, value: undefined },\n\ttype: { configurable: true, value: SignalValue },\n\tprops: {\n\t\tconfigurable: true,\n\t\tget() {\n\t\t\tconst s: Signal = this;\n\t\t\treturn {\n\t\t\t\tdata: {\n\t\t\t\t\tget value() {\n\t\t\t\t\t\treturn s.value;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t},\n\t// Setting a VNode's _depth to 1 forces Preact to clone it before modifying:\n\t// https://github.com/preactjs/preact/blob/d7a433ee8463a7dc23a05111bb47de9ec729ad4d/src/diff/children.js#L77\n\t// @todo remove this for Preact 11\n\t__b: { configurable: true, value: 1 },\n});\n\n/** Inject low-level property/attribute bindings for Signals into Preact's diff */\nhook(OptionsTypes.DIFF, (old, vnode) => {\n\tif (typeof vnode.type === \"string\") {\n\t\tlet signalProps: Record<string, any> | undefined;\n\n\t\tlet props = vnode.props;\n\t\tfor (let i in props) {\n\t\t\tif (i === \"children\") continue;\n\n\t\t\tlet value = props[i];\n\t\t\tif (value instanceof Signal) {\n\t\t\t\tif (!signalProps) vnode.__np = signalProps = {};\n\t\t\t\tsignalProps[i] = value;\n\t\t\t\tprops[i] = value.peek();\n\t\t\t}\n\t\t}\n\t}\n\n\told(vnode);\n});\n\n/** Set up Updater before rendering a component */\nhook(OptionsTypes.RENDER, (old, vnode) => {\n\told(vnode);\n\t// Ignore the Fragment inserted by preact.createElement().\n\tif (vnode.type !== Fragment) {\n\t\tsetCurrentUpdater();\n\n\t\tlet updater: Effect | undefined;\n\n\t\tlet component = vnode.__c;\n\t\tif (component) {\n\t\t\tcomponent._updateFlags &= ~HAS_PENDING_UPDATE;\n\n\t\t\tupdater = component._updater;\n\t\t\tif (updater === undefined) {\n\t\t\t\tcomponent._updater = updater = createUpdater(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tif (DEVTOOLS_ENABLED) updater!._debugCallback?.call(updater);\n\t\t\t\t\t\tcomponent._updateFlags |= HAS_PENDING_UPDATE;\n\t\t\t\t\t\tcomponent.setState({});\n\t\t\t\t\t},\n\t\t\t\t\ttypeof vnode.type === \"function\"\n\t\t\t\t\t\t? vnode.type.displayName || vnode.type.name\n\t\t\t\t\t\t: \"\"\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tcurrentComponent = component;\n\t\tsetCurrentUpdater(updater);\n\t}\n});\n\n/** Finish current updater if a component errors */\nhook(OptionsTypes.CATCH_ERROR, (old, error, vnode, oldVNode) => {\n\tsetCurrentUpdater();\n\tcurrentComponent = undefined;\n\told(error, vnode, oldVNode);\n});\n\n/** Finish current updater after rendering any VNode */\nhook(OptionsTypes.DIFFED, (old, vnode) => {\n\tsetCurrentUpdater();\n\tcurrentComponent = undefined;\n\n\tlet dom: Element;\n\n\t// vnode._dom is undefined during string rendering,\n\t// so we use this to skip prop subscriptions during SSR.\n\tif (typeof vnode.type === \"string\" && (dom = vnode.__e as Element)) {\n\t\tlet props = vnode.__np;\n\t\tlet renderedProps = vnode.props;\n\t\tif (props) {\n\t\t\tlet updaters = dom._updaters;\n\t\t\tif (updaters) {\n\t\t\t\tfor (let prop in updaters) {\n\t\t\t\t\tlet updater = updaters[prop];\n\t\t\t\t\tif (updater !== undefined && !(prop in props)) {\n\t\t\t\t\t\tupdater._dispose();\n\t\t\t\t\t\t// @todo we could just always invoke _dispose() here\n\t\t\t\t\t\tupdaters[prop] = undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tupdaters = {};\n\t\t\t\tdom._updaters = updaters;\n\t\t\t}\n\n\t\t\tfor (let prop in props) {\n\t\t\t\tlet updater = updaters[prop];\n\t\t\t\tlet signal = props[prop];\n\t\t\t\tif (updater === undefined) {\n\t\t\t\t\tupdater = createPropUpdater(dom, prop, signal, renderedProps);\n\t\t\t\t\tupdaters[prop] = updater;\n\t\t\t\t} else {\n\t\t\t\t\tupdater._update(signal, renderedProps);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\told(vnode);\n});\n\nfunction createPropUpdater(\n\tdom: Element,\n\tprop: string,\n\tpropSignal: Signal,\n\tprops: Record<string, any>\n): PropertyUpdater {\n\tconst setAsProperty =\n\t\tprop in dom &&\n\t\t// SVG elements need to go through `setAttribute` because they\n\t\t// expect things like SVGAnimatedTransformList instead of strings.\n\t\t// @ts-ignore\n\t\tdom.ownerSVGElement === undefined;\n\n\tconst changeSignal = signal(propSignal);\n\t// Track the last value we know was applied to the DOM, so we can skip\n\t// redundant updates without writing back to vnode.props (which would\n\t// clobber the Signal reference and break unmount/remount cycles).\n\tlet lastRenderedValue: any = propSignal.peek();\n\treturn {\n\t\t_update: (newSignal: Signal, newProps: typeof props) => {\n\t\t\tchangeSignal.value = newSignal;\n\t\t\tprops = newProps;\n\t\t\tlastRenderedValue = newSignal.peek();\n\t\t},\n\t\t_dispose: effect(function (this: Effect) {\n\t\t\tthis._notify = notifyDomUpdates;\n\t\t\tconst value = changeSignal.value.value;\n\t\t\t// If Preact just rendered this value, don't render it again:\n\t\t\tif (lastRenderedValue === value) {\n\t\t\t\tlastRenderedValue = undefined;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlastRenderedValue = undefined;\n\t\t\tif (setAsProperty) {\n\t\t\t\t// @ts-ignore-next-line silly\n\t\t\t\tdom[prop] = value;\n\t\t\t\t// Match Preact's attribute handling: data-* and aria-* attributes\n\t\t\t\t// https://github.com/preactjs/preact/blob/main/src/diff/props.js#L132\n\t\t\t} else if (value != null && (value !== false || prop[4] === \"-\")) {\n\t\t\t\tdom.setAttribute(prop, value);\n\t\t\t} else {\n\t\t\t\tdom.removeAttribute(prop);\n\t\t\t}\n\t\t}),\n\t};\n}\n\n/** Unsubscribe from Signals when unmounting components/vnodes */\nhook(OptionsTypes.UNMOUNT, (old, vnode: VNode) => {\n\tif (typeof vnode.type === \"string\") {\n\t\tlet dom = vnode.__e as Element | undefined;\n\t\t// vnode._dom is undefined during string rendering\n\t\tif (dom) {\n\t\t\tconst updaters = dom._updaters;\n\t\t\tif (updaters) {\n\t\t\t\tdom._updaters = undefined;\n\t\t\t\tfor (let prop in updaters) {\n\t\t\t\t\tlet updater = updaters[prop];\n\t\t\t\t\tif (updater) updater._dispose();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Restore Signal references into vnode.props so that, if this vnode\n\t\t// instance is reused for a remount, the DIFF hook can re-detect the\n\t\t// signal-bound props (they were replaced with peeked values during diff).\n\t\tlet signalProps = vnode.__np;\n\t\tif (signalProps) {\n\t\t\tlet props = vnode.props;\n\t\t\tfor (let prop in signalProps) {\n\t\t\t\tprops[prop] = signalProps[prop];\n\t\t\t}\n\t\t}\n\t\tvnode.__np = undefined;\n\t} else {\n\t\tlet component = vnode.__c;\n\t\tif (component) {\n\t\t\tconst updater = component._updater;\n\t\t\tif (updater) {\n\t\t\t\tcomponent._updater = undefined;\n\t\t\t\tupdater._dispose();\n\t\t\t}\n\t\t}\n\t}\n\told(vnode);\n});\n\n/** Mark components that use hook state so we can skip sCU optimization. */\nhook(OptionsTypes.HOOK, (old, component, index, type) => {\n\tif (type < 3 || type === 9)\n\t\t(component as AugmentedComponent)._updateFlags |= HAS_HOOK_STATE;\n\told(component, index, type);\n});\n\n/**\n * Auto-memoize components that use Signals/Computeds.\n * Note: Does _not_ optimize components that use hook/class state.\n */\nComponent.prototype.shouldComponentUpdate = function (\n\tthis: AugmentedComponent,\n\tprops,\n\tstate\n) {\n\t// Suspended vnodes should always update:\n\tif (this.__R) return true;\n\n\t// @todo: Once preactjs/preact#3671 lands, this could just use `currentUpdater`:\n\tconst updater = this._updater;\n\tconst hasSignals = updater && updater._sources !== undefined;\n\n\t// If this is a component using state, rerender\n\t// @ts-ignore\n\tfor (let i in state) return true;\n\n\tif (this.__f || (typeof this.u == \"boolean\" && this.u === true)) {\n\t\tconst hasHooksState = this._updateFlags & HAS_HOOK_STATE;\n\t\t// if this component used no signals or computeds and no hooks state, update:\n\t\tif (!hasSignals && !hasHooksState && !(this._updateFlags & HAS_COMPUTEDS))\n\t\t\treturn true;\n\n\t\t// if there is a pending re-render triggered from Signals,\n\t\t// or if there is hooks state, update:\n\t\tif (this._updateFlags & HAS_PENDING_UPDATE) return true;\n\t} else {\n\t\t// if this component used no signals or computeds, update:\n\t\tif (!hasSignals && !(this._updateFlags & HAS_COMPUTEDS)) return true;\n\n\t\t// if there is a pending re-render triggered from Signals,\n\t\t// or if there is hooks state, update:\n\t\tif (this._updateFlags & (HAS_PENDING_UPDATE | HAS_HOOK_STATE)) return true;\n\t}\n\n\t// if any non-Signal props changed, update:\n\tfor (let i in props) {\n\t\tif (i !== \"__source\" && props[i] !== this.props[i]) return true;\n\t}\n\tfor (let i in this.props) if (!(i in props)) return true;\n\n\t// this is a purely Signal-driven component, don't update:\n\treturn false;\n};\n\nexport function useSignal<T>(value: T, options?: SignalOptions<T>): Signal<T>;\nexport function useSignal<T = undefined>(): Signal<T | undefined>;\nexport function useSignal<T>(value?: T, options?: SignalOptions<T>) {\n\treturn useMemo(\n\t\t() => signal<T | undefined>(value, options as SignalOptions),\n\t\t[]\n\t);\n}\n\nexport function useComputed<T>(compute: () => T, options?: SignalOptions<T>) {\n\tconst $compute = useRef(compute);\n\t$compute.current = compute;\n\t(currentComponent as AugmentedComponent)._updateFlags |= HAS_COMPUTEDS;\n\treturn useMemo(() => computed<T>(() => $compute.current(), options), []);\n}\n\nfunction safeRaf(callback: () => void) {\n\tconst done = () => {\n\t\tclearTimeout(timeout);\n\t\tcancelAnimationFrame(raf);\n\t\tcallback();\n\t};\n\n\tconst timeout = setTimeout(done, 35);\n\tconst raf = requestAnimationFrame(done);\n}\n\nconst deferEffects =\n\ttypeof requestAnimationFrame === \"undefined\" ? setTimeout : safeRaf;\n\nconst deferDomUpdates = (cb: any) => {\n\tqueueMicrotask(() => {\n\t\tqueueMicrotask(cb);\n\t});\n};\n\nfunction flushEffects() {\n\tbatch(() => {\n\t\tlet inst: Effect | undefined;\n\t\twhile ((inst = effectsQueue.shift())) {\n\t\t\toldNotify.call(inst);\n\t\t}\n\t});\n}\n\nfunction notifyEffects(this: Effect) {\n\tif (effectsQueue.push(this) === 1) {\n\t\t(options.requestAnimationFrame || deferEffects)(flushEffects);\n\t}\n}\n\nfunction flushDomUpdates() {\n\tbatch(() => {\n\t\tlet inst: Effect | undefined;\n\t\twhile ((inst = domQueue.shift())) {\n\t\t\toldNotify.call(inst);\n\t\t}\n\t});\n}\n\nfunction notifyDomUpdates(this: Effect) {\n\tif (domQueue.push(this) === 1) {\n\t\t(options.requestAnimationFrame || deferDomUpdates)(flushDomUpdates);\n\t}\n}\n\nexport function useSignalEffect(\n\tcb: () => void | (() => void),\n\toptions?: EffectOptions\n) {\n\tconst callback = useRef(cb);\n\tcallback.current = cb;\n\n\tuseEffect(() => {\n\t\treturn effect(function (this: Effect) {\n\t\t\tthis._notify = notifyEffects;\n\t\t\treturn callback.current();\n\t\t}, options);\n\t}, []);\n}\n\n/** See comment in packages/core/src/index.ts on the same interface for an explanation */\ninterface InternalModelConstructor<\n\tTModel,\n\tTArgs extends any[],\n> extends ModelConstructor<TModel, TArgs> {\n\t(...args: TArgs): Model<TModel>;\n}\n\nexport function useModel<TModel>(\n\tfactory: ModelConstructor<TModel, []> | (() => Model<TModel>)\n): Model<TModel> {\n\ttype InternalFactory =\n\t\t| InternalModelConstructor<TModel, []>\n\t\t| (() => Model<TModel>);\n\n\tconst inst = useMemo(() => (factory as InternalFactory)(), []);\n\tuseEffect(() => inst[Symbol.dispose], [inst]);\n\treturn inst;\n}\n\n/**\n * @todo Determine which Reactive implementation we'll be using.\n * @internal\n */\n// export function useReactive<T extends object>(value: T): Reactive<T> {\n// \treturn useMemo(() => reactive<T>(value), []);\n// }\n\n/**\n * @internal\n * Update a Reactive's using the properties of an object or other Reactive.\n * Also works for Signals.\n * @example\n * // Update a Reactive with Object.assign()-like syntax:\n * const r = reactive({ name: \"Alice\" });\n * update(r, { name: \"Bob\" });\n * update(r, { age: 42 }); // property 'age' does not exist in type '{ name?: string }'\n * update(r, 2); // '2' has no properties in common with '{ name?: string }'\n * console.log(r.name.value); // \"Bob\"\n *\n * @example\n * // Update a Reactive with the properties of another Reactive:\n * const A = reactive({ name: \"Alice\" });\n * const B = reactive({ name: \"Bob\", age: 42 });\n * update(A, B);\n * console.log(`${A.name} is ${A.age}`); // \"Bob is 42\"\n *\n * @example\n * // Update a signal with assign()-like syntax:\n * const s = signal(42);\n * update(s, \"hi\"); // Argument type 'string' not assignable to type 'number'\n * update(s, {}); // Argument type '{}' not assignable to type 'number'\n * update(s, 43);\n * console.log(s.value); // 43\n *\n * @param obj The Reactive or Signal to be updated\n * @param update The value, Signal, object or Reactive to update `obj` to match\n * @param overwrite If `true`, any properties `obj` missing from `update` are set to `undefined`\n */\n/*\nexport function update<T extends SignalOrReactive>(\n\tobj: T,\n\tupdate: Partial<Unwrap<T>>,\n\toverwrite = false\n) {\n\tif (obj instanceof Signal) {\n\t\tobj.value = peekValue(update);\n\t} else {\n\t\tfor (let i in update) {\n\t\t\tif (i in obj) {\n\t\t\t\tobj[i].value = peekValue(update[i]);\n\t\t\t} else {\n\t\t\t\tlet sig = signal(peekValue(update[i]));\n\t\t\t\tsig[KEY] = i;\n\t\t\t\tobj[i] = sig;\n\t\t\t}\n\t\t}\n\t\tif (overwrite) {\n\t\t\tfor (let i in obj) {\n\t\t\t\tif (!(i in update)) {\n\t\t\t\t\tobj[i].value = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n*/\n", "/**\n * Brand constants, baked in at build time by `scripts/build-brand.mjs`.\n *\n * esbuild `define` replaces every `__BRAND_*__` / `__CSS_PREFIX__` reference\n * with the JSON-encoded value from the brand JSON. We use `typeof \u2026 !==\n * \"undefined\"` guards so the module still imports cleanly in tooling\n * contexts (Vitest, the Vite dev server) where defines come from separate\n * config files.\n */\n\ndeclare const __BRAND_ID__: string;\ndeclare const __BRAND_NAME__: string;\ndeclare const __BRAND_GLOBAL__: string;\ndeclare const __BRAND_TAG_NAME__: string;\ndeclare const __BRAND_DEFAULT_BASE_URL__: string;\ndeclare const __BRAND_STANDALONE_URL__: string;\ndeclare const __BRAND_DISPLAY_NAME__: string;\ndeclare const __BRAND_DOMAIN__: string;\ndeclare const __BRAND_SITE_URL__: string;\ndeclare const __CSS_PREFIX__: string;\n\nexport const BRAND = {\n id: typeof __BRAND_ID__ !== \"undefined\" ? __BRAND_ID__ : \"help-ai\",\n name: typeof __BRAND_NAME__ !== \"undefined\" ? __BRAND_NAME__ : \"Help AI\",\n global: typeof __BRAND_GLOBAL__ !== \"undefined\" ? __BRAND_GLOBAL__ : \"HelpAI\",\n // Custom-element tag \u2014 `web-ai-chat` for every brand. Brand-neutral\n // by design so docs and copy snippets work the same across @helpai\n // and @goai builds.\n tagName: typeof __BRAND_TAG_NAME__ !== \"undefined\" ? __BRAND_TAG_NAME__ : \"web-ai-chat\",\n cssPrefix: typeof __CSS_PREFIX__ !== \"undefined\" ? __CSS_PREFIX__ : \"helpai\",\n defaultBaseUrl: typeof __BRAND_DEFAULT_BASE_URL__ !== \"undefined\" ? __BRAND_DEFAULT_BASE_URL__ : \"\",\n standaloneUrl: typeof __BRAND_STANDALONE_URL__ !== \"undefined\" ? __BRAND_STANDALONE_URL__ : \"\",\n /** Public-facing brand label, e.g. \"Help AI\" / \"Go AI\". */\n displayName: typeof __BRAND_DISPLAY_NAME__ !== \"undefined\" ? __BRAND_DISPLAY_NAME__ : \"Help AI\",\n /** Bare host without protocol, e.g. \"help.ai\" / \"go.ai\". */\n domain: typeof __BRAND_DOMAIN__ !== \"undefined\" ? __BRAND_DOMAIN__ : \"help.ai\",\n /** Full marketing site URL, e.g. \"https://help.ai\". */\n siteUrl: typeof __BRAND_SITE_URL__ !== \"undefined\" ? __BRAND_SITE_URL__ : \"https://help.ai\",\n} as const;\n", "/**\n * UI strings \u2014 built-in defaults, type contracts, and runtime resolution.\n *\n * The widget ships English as a built-in (always complete). Every additional\n * locale is supplied either by the page owner (`init({ strings })`) or by\n * the SaaS backend in the `connect` response \u2014 both pushed *partially*: only\n * the keys they want to override.\n *\n * To keep render hot-paths cheap, the merged shape is **pre-computed once**\n * (at config-resolve time and after each server-settings merge) into a fully\n * populated {@link Strings} map. UI components then read `strings.send` as a\n * normal property access \u2014 no per-render lookup, no per-render allocation.\n */\n\n// ---- Keys ---------------------------------------------------------------\n\nexport type StringKey =\n | \"launcherOpen\"\n | \"panelTitle\"\n | \"composerPlaceholder\"\n | \"send\"\n | \"stop\"\n | \"attach\"\n | \"micStart\"\n | \"micStop\"\n | \"micUnsupported\"\n | \"expand\"\n | \"collapse\"\n | \"fullscreen\"\n | \"exitFullscreen\"\n | \"resizeHandle\"\n | \"popOut\"\n | \"close\"\n | \"moreActions\"\n | \"soundOn\"\n | \"soundOff\"\n | \"language\"\n | \"theme\"\n | \"themeAuto\"\n | \"themeLight\"\n | \"themeDark\"\n | \"history\"\n | \"historyTitle\"\n | \"historyEmpty\"\n | \"historyLoading\"\n | \"historyNewChat\"\n | \"historyBack\"\n | \"messagesLoading\"\n | \"chatClosed\"\n | \"startNewConversation\"\n | \"dateToday\"\n | \"dateYesterday\"\n | \"dateLastWeek\"\n | \"dateOlder\"\n | \"newConversation\"\n | \"dropZone\"\n | \"attachmentTooLarge\"\n | \"attachmentTooMany\"\n | \"attachmentMimeRejected\"\n | \"errorRetry\"\n | \"errorGeneric\"\n | \"errorRateLimited\"\n | \"loading\"\n | \"thinking\"\n | \"thoughts\"\n | \"usedTool\"\n | \"collapseSidebar\"\n | \"expandSidebar\"\n // \u2500\u2500 Messenger modules \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\u2500\u2500\u2500\u2500\u2500\u2500\n | \"tabHome\"\n | \"tabMessages\"\n | \"tabHelp\"\n | \"tabNews\"\n | \"modulesEmpty\"\n | \"moduleBack\"\n | \"contentLoading\"\n | \"homeGreeting\"\n | \"homeGreetingNamed\"\n | \"homeGreetingLead\"\n | \"homeSearchPlaceholder\"\n | \"homeContentTitle\"\n | \"helpTitle\"\n | \"helpSearchPlaceholder\"\n | \"helpEmpty\"\n | \"helpLoading\"\n | \"helpSearchEmpty\"\n | \"newsTitle\"\n | \"newsEmpty\"\n | \"newsLoading\"\n | \"newsBack\"\n | \"newsPublishedAt\";\n\n// ---- Public types -------------------------------------------------------\n\n/** A partial map for one locale \u2014 what overrides look like on the wire. */\nexport type LocaleStrings = Partial<Record<StringKey, string>>;\n\n/**\n * The post-resolution map \u2014 every key guaranteed present. UI components\n * receive this shape and read it via direct property access (no `t()`).\n */\nexport type Strings = Record<StringKey, string>;\n\n/**\n * Override shape accepted by both `init({ strings })` and the server's\n * `settings.strings`. Two forms supported:\n *\n * 1. **Flat** \u2014 `{ send: \"Click\", stop: \"Halt\" }`. Applies to the active\n * locale. Convenient for single-language deployments.\n * 2. **Nested** \u2014 `{ en: { send: \"Click\" }, fr: { send: \"Cliquer\" } }`.\n * Lets a multilingual backend push every translation in one payload \u2014\n * the widget picks the entry matching the user's locale and falls back\n * to the `en` entry (then to built-in English) for any missing keys.\n *\n * Both forms may be **partial** at every level \u2014 anything you don't supply\n * inherits the built-in default. Detection is by value type: if any\n * top-level value is an object, the whole override is treated as nested.\n */\nexport type StringsOverride = LocaleStrings | Partial<Record<string, LocaleStrings>>;\n\n// ---- Built-in defaults --------------------------------------------------\n\n/**\n * English \u2014 the only locale that MUST be complete. Every other locale (and\n * every override) layers on top of this map, so unknown keys never surface\n * as raw `StringKey` strings in the UI.\n */\nexport const STRINGS_EN: Strings = {\n attach: \"Attach files\",\n attachmentMimeRejected: \"File type not supported\",\n attachmentTooLarge: \"File is too large\",\n attachmentTooMany: \"Attachment limit reached\",\n chatClosed: \"This conversation is closed\",\n close: \"Close\",\n collapse: \"Collapse\",\n collapseSidebar: \"Collapse sidebar\",\n composerPlaceholder: \"Type a message\u2026\",\n dateLastWeek: \"Last 7 days\",\n dateOlder: \"Older\",\n dateToday: \"Today\",\n dateYesterday: \"Yesterday\",\n dropZone: \"Drop files to attach\",\n errorGeneric: \"Something went wrong\",\n errorRateLimited: \"Too many requests \u2014 please try again in a moment.\",\n errorRetry: \"Retry\",\n expand: \"Expand\",\n expandSidebar: \"Expand sidebar\",\n exitFullscreen: \"Exit fullscreen\",\n fullscreen: \"Fullscreen\",\n history: \"History\",\n historyBack: \"Back to chat\",\n historyEmpty: \"No previous conversations yet\",\n historyLoading: \"Loading your chats\u2026\",\n historyNewChat: \"New chat\",\n historyTitle: \"Chat history\",\n language: \"Language\",\n launcherOpen: \"Open chat\",\n messagesLoading: \"Loading conversation\u2026\",\n micStart: \"Start voice input\",\n micStop: \"Stop voice input\",\n micUnsupported: \"Voice input is not supported in this browser\",\n moreActions: \"More actions\",\n newConversation: \"New conversation\",\n panelTitle: \"Chat\",\n popOut: \"Open in a new tab\",\n resizeHandle: \"Drag to resize\",\n send: \"Send\",\n soundOff: \"Sound off\",\n soundOn: \"Sound on\",\n startNewConversation: \"Start a new conversation\",\n stop: \"Stop\",\n theme: \"Theme\",\n themeAuto: \"Auto (system)\",\n themeDark: \"Dark\",\n themeLight: \"Light\",\n loading: \"Loading\u2026\",\n thinking: \"Thinking\u2026\",\n thoughts: \"Thoughts\",\n usedTool: \"Used tool\",\n // \u2500\u2500 Messenger modules \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\u2500\u2500\u2500\u2500\u2500\u2500\n tabHome: \"Home\",\n tabMessages: \"Messages\",\n tabHelp: \"Help\",\n tabNews: \"News\",\n modulesEmpty: \"Nothing here yet\",\n moduleBack: \"Back\",\n contentLoading: \"Loading\u2026\",\n homeGreeting: \"How can we help?\",\n homeGreetingNamed: \"Hi {name} \uD83D\uDC4B\",\n homeGreetingLead: \"How can we help?\",\n homeSearchPlaceholder: \"Search for help\",\n homeContentTitle: \"Popular articles\",\n helpTitle: \"Help\",\n helpSearchPlaceholder: \"Search for help\",\n helpEmpty: \"No help articles yet\",\n helpLoading: \"Loading help\u2026\",\n helpSearchEmpty: \"No results found\",\n newsTitle: \"News\",\n newsEmpty: \"No news yet\",\n newsLoading: \"Loading news\u2026\",\n newsBack: \"Back to news\",\n newsPublishedAt: \"Published {date}\",\n};\n\n// ---- Resolution ---------------------------------------------------------\n\n/**\n * Merge built-in defaults + override into a fully-populated {@link Strings}\n * map for one locale. Call once per options-resolve / server-merge \u2014 the UI\n * then reads from the resulting map without further function calls.\n *\n * Resolution order (later wins):\n * 1. Built-in English (always complete) \u2014 guarantees no undefined keys.\n * 2. Override's `en` entry (nested form only) \u2014 backend's English defaults.\n * 3. Override's locale-specific entry \u2014 backend's per-language strings.\n *\n * Locale negotiation: the BCP-47 tag is folded to its primary subtag and\n * lowercased (`en-US` \u2192 `en`, `fr-CA` \u2192 `fr`). Non-English UI strings ship\n * via the backend's `i18n.strings` payload \u2014 no built-in non-English maps.\n */\nexport function resolveStrings(locale: string, override?: StringsOverride): Strings {\n const tag = primaryTag(locale);\n const isNested = isNestedShape(override);\n\n // Override's per-locale slice (or the whole flat object treated as the active locale).\n const localeOverride: LocaleStrings = isNested\n ? ((override as Record<string, LocaleStrings>)[tag] ?? {})\n : ((override as LocaleStrings | undefined) ?? {});\n\n // Override's English slice \u2014 used as a secondary fallback so nested-form\n // backends can ship \"English defaults + per-locale spot overrides\".\n const enOverride: LocaleStrings = isNested ? ((override as Record<string, LocaleStrings>).en ?? {}) : {};\n\n return {\n ...STRINGS_EN,\n ...enOverride,\n ...localeOverride,\n };\n}\n\n/** Primary BCP-47 subtag, lowercased. `en-US` \u2192 `en`. */\nfunction primaryTag(locale: string): string {\n return (locale.split(\"-\")[0] ?? \"en\").toLowerCase();\n}\n\n/**\n * Resolve a module/tab `label` to display text. The label is an i18n **key**\n * (built-in like `tabHome`, or a custom key the backend ships via\n * `i18n.strings`); if no translation exists it renders literally. This is how\n * dynamic tab labels stay translatable through the same `i18n.strings` path as\n * everything else.\n */\nexport function moduleLabel(strings: Strings, label: string): string {\n return (strings as Record<string, string>)[label] ?? label;\n}\n\n/**\n * Detect whether `override` is in nested (per-locale) form. The cheapest\n * reliable signal: if any top-level value is an object, it's nested. A flat\n * override only ever has string values.\n */\nexport function isNestedShape(override: StringsOverride | undefined): boolean {\n if (!override) return false;\n for (const v of Object.values(override)) {\n if (v && typeof v === \"object\") return true;\n }\n return false;\n}\n\n/**\n * Friendly name for a locale tag, expressed *in the UI's current locale*.\n * Uses {@link Intl.DisplayNames} (browser-native, zero bundle cost).\n *\n * `localeDisplayName(\"fr\", \"en\")` \u2192 `\"French\"`.\n * `localeDisplayName(\"fr\", \"fr\")` \u2192 `\"fran\u00E7ais\"`.\n *\n * Falls back to the upper-cased tag if the browser doesn't recognise it\n * (or pre-Intl runtimes \u2014 happy-dom does, but defensive code is cheap).\n */\nexport function localeDisplayName(target: string, displayInLocale: string): string {\n try {\n const dn = new Intl.DisplayNames([displayInLocale], { type: \"language\" });\n return dn.of(target) ?? target.toUpperCase();\n } catch {\n return target.toUpperCase();\n }\n}\n", "/**\n * Pluggable client-side key-value storage.\n *\n * Drop in a custom adapter to:\n * - Add logging / analytics around reads + writes\n * - Encrypt or sign values\n * - Use a different backing store (sessionStorage, IndexedDB shim, in-memory)\n * - Add quota / migration handling\n *\n * The default implementation is {@link LocalStorageAdapter} \u2014 a thin wrapper\n * over `localStorage` with try/catch around every call so storage being\n * disabled (private mode pre-iOS 11, floating webviews, quota exceeded)\n * silently degrades to no-op rather than throwing.\n *\n * Values are stored as opaque strings \u2014 the persistence layer above\n * serialises typed values to JSON before calling `set`.\n */\n\nexport interface ClientStorage {\n /** Returns the stored string for `key`, or `null` when absent / unreadable. */\n get(key: string): string | null;\n /** Stores `value` under `key`. Best-effort \u2014 silent on quota / disabled storage. */\n set(key: string, value: string): void;\n /** Removes `key`. No-op if absent. */\n remove(key: string): void;\n}\n\n/**\n * Default adapter \u2014 wraps `globalThis.localStorage`. Every call is wrapped\n * in try/catch so callers can treat storage as best-effort: a private-mode\n * Safari that throws on `setItem` simply degrades to no persistence rather\n * than crashing the widget.\n */\nexport class LocalStorageAdapter implements ClientStorage {\n get(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // Best-effort \u2014 quota / disabled storage / private mode all fall through.\n }\n }\n\n remove(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n // Best-effort.\n }\n }\n}\n\n/**\n * Process-wide singleton used when the developer doesn't pass a custom\n * `storage` option. Reusing the same instance across widget mounts is\n * harmless \u2014 `LocalStorageAdapter` is stateless.\n */\nexport const defaultStorage: ClientStorage = new LocalStorageAdapter();\n", "/**\n * Comprehensive logger for the widget runtime.\n *\n * Designed for \"I have a customer reporting a weird bug on their\n * site, I want every relevant detail without rebuilding\" debugging:\n *\n * \u2022 Five levels (off / error / warn / info / debug / trace) that\n * gate output. Default `warn` \u2014 errors + warnings always go\n * through, info / debug / trace stay silent until opted in.\n * \u2022 Scoped child loggers \u2014 `logger.scope(\"transport\")` returns a\n * logger that tags every line `[helpai:transport] \u2026`. Every\n * subsystem has its own scope so the console transcript reads\n * like a flight recorder.\n * \u2022 Structured payloads \u2014 every call accepts an object as the\n * second arg; the browser console renders it inline, expandable.\n * \u2022 Remote toggle \u2014 `chat.setDebug(\"trace\")`, `HelpAI.setDebug(\u2026)`,\n * `?helpai-debug=trace`, or the `options.debug` mount param all\n * converge on the same internal level. Persisted to\n * `localStorage[\"helpai.debug\"]` so a refresh keeps visibility.\n * \u2022 Co-hosted brand safety \u2014 every signal (query, global key, log\n * tag) is brand-prefixed so `help-ai` and `go-ai` widgets on the\n * same page don't fight over flags or pollute each other's\n * transcripts.\n */\n\nimport { BRAND } from \"./brand\";\n\nexport type LogLevel = \"off\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\";\n\nconst LEVELS: LogLevel[] = [\"off\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"];\n\nexport interface Logger {\n error: (message: string, payload?: unknown) => void;\n warn: (message: string, payload?: unknown) => void;\n info: (message: string, payload?: unknown) => void;\n debug: (message: string, payload?: unknown) => void;\n trace: (message: string, payload?: unknown) => void;\n /** Child logger with appended scope: `[brand:parent:child] \u2026`. */\n scope: (childScope: string) => Logger;\n}\n\nconst p = BRAND.cssPrefix;\nconst STORAGE_KEY = `${p}.debug`;\nconst GLOBAL_FLAG_KEY = `__${BRAND.global}_DEBUG__`;\nconst URL_QUERY_KEY = `${p}-debug`;\n\nlet levelIndex = 2; // \"warn\"\nlet sink: ((entry: LogEntry) => void) | null = null;\n\nfunction resolveInitial(): number {\n if (typeof window === \"undefined\") return 2;\n try {\n const qs = new URLSearchParams(window.location.search).get(URL_QUERY_KEY);\n if (qs !== null) {\n const i = parseToIndex(qs);\n if (i >= 0) return i;\n }\n } catch {\n /* unsupported URL \u2014 ignore */\n }\n try {\n const persisted = window.localStorage?.getItem(STORAGE_KEY);\n if (persisted) {\n const i = parseToIndex(persisted);\n if (i >= 0) return i;\n }\n } catch {\n /* private-mode Safari etc. */\n }\n if ((window as unknown as Record<string, unknown>)[GLOBAL_FLAG_KEY]) return 4;\n return 2;\n}\n\nfunction parseToIndex(input: unknown): number {\n if (input === true || input === \"1\" || input === \"true\") return 4;\n if (input === false || input === \"0\" || input === \"false\" || input === \"off\") return 0;\n if (typeof input === \"number\") return Math.max(0, Math.min(5, Math.floor(input)));\n if (typeof input === \"string\") {\n const i = LEVELS.indexOf(input as LogLevel);\n if (i >= 0) return i;\n }\n return -1;\n}\n\nexport function parseLevel(input: unknown): LogLevel | null {\n const i = parseToIndex(input);\n return i >= 0 ? (LEVELS[i] as LogLevel) : null;\n}\n\n/**\n * Set the active level. Persisted so the next page load keeps the\n * same visibility \u2014 critical for the \"ask a customer to reproduce\n * and send the console\" workflow.\n *\n * Available on the public surface via:\n * \u2022 `chat.setDebug(level)` on any `ChatWidgetInstance`.\n * \u2022 `HelpAI.setDebug(level)` on the global (CDN auto-boot).\n * \u2022 `HelpAI(\"setDebug\", level)` via the queueable command form.\n */\nexport function setLogLevel(input: LogLevel | boolean | number | string): void {\n const i = parseToIndex(input);\n if (i < 0) return;\n levelIndex = i;\n try {\n window.localStorage?.setItem(STORAGE_KEY, LEVELS[i] as string);\n } catch {\n /* private mode \u2014 keep in-memory level */\n }\n}\n\nexport function getLogLevel(): LogLevel {\n return LEVELS[levelIndex] as LogLevel;\n}\n\nlevelIndex = resolveInitial();\n\ntype ConsoleMethod = \"error\" | \"warn\" | \"info\" | \"debug\";\n\nfunction makeLogger(scope: string): Logger {\n const tag = `[${p}:${scope}]`;\n // Look up console.X at CALL time so test spies (and host consoles\n // that monkey-patch) actually see the writes.\n const emit = (threshold: number, key: ConsoleMethod, level: LogLevel) => (message: string, payload?: unknown) => {\n if (levelIndex < threshold) return;\n if (payload === undefined) console[key](tag, message);\n else console[key](tag, message, payload);\n if (sink) {\n try {\n sink({ level, scope, message, payload });\n } catch {\n // A sink throw must never break the widget.\n }\n }\n };\n return {\n // 0 off / 1 error / 2 warn / 3 info / 4 debug / 5 trace\n error: emit(1, \"error\", \"error\"),\n warn: emit(2, \"warn\", \"warn\"),\n info: emit(3, \"info\", \"info\"),\n debug: emit(4, \"debug\", \"debug\"),\n trace: emit(5, \"debug\", \"trace\"),\n scope: (childScope) => makeLogger(`${scope}:${childScope}`),\n };\n}\n\nexport const logger: Logger = makeLogger(\"core\");\n\n/** One structured line, as forwarded to an external {@link setLogSink} sink. */\nexport interface LogEntry {\n level: LogLevel;\n /** Subsystem tag, e.g. `\"transport\"` or `\"core:hooks\"`. */\n scope: string;\n message: string;\n payload?: unknown;\n}\n\n/**\n * Forward every emitted log line (that passes the active level gate) to an\n * external sink \u2014 pipe errors/warnings/debug into Sentry breadcrumbs, Datadog,\n * your own logger, etc. `null` clears it. One global sink per bundle; filter by\n * `scope` if you host multiple widgets. Wired publicly via `HelpAI.onLog(fn)` /\n * `chat.onLog(fn)`. Raise visibility with `setDebug(\"trace\")` to capture more.\n */\nexport function setLogSink(fn: ((entry: LogEntry) => void) | null): void {\n sink = fn;\n}\n", "/**\n * Sanitise host-asserted context objects ({@link UserContext},\n * {@link PageContext}) before they leave the browser.\n *\n * These hold data the embedding site asserts about its visitor (the widget\n * runs on third-party origins like `example.com`). It's **informational\n * context for the agent \u2014 never an auth credential** \u2014 so we clean rather\n * than reject: drop anything malformed, coerce/limit values, strip keys that\n * would collide with the widget's own identifiers, and emit `warn`s. Never\n * throws.\n */\n\nimport { logger } from \"../logger\";\nimport type { PageContext, UserContext } from \"./types\";\n\nconst log = logger.scope(\"context\");\n\n/** Keys reserved for the widget's own minted identity / auth \u2014 never host-settable. */\nconst RESERVED_KEYS = new Set([\"visitorId\", \"sessionId\", \"sessionId\", \"jwt\", \"token\", \"authorization\", \"password\"]);\nconst MAX_KEYS = 50;\nconst MAX_STRING = 1024;\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Sanitise {@link UserContext} (who the visitor is). Validates `email`. */\nexport function sanitizeUserContext(raw: unknown): UserContext | undefined {\n return sanitizeContext(raw, \"userContext\", true) as UserContext | undefined;\n}\n\n/** Sanitise {@link PageContext} (where the visit is). */\nexport function sanitizePageContext(raw: unknown): PageContext | undefined {\n return sanitizeContext(raw, \"pageContext\", false) as PageContext | undefined;\n}\n\nfunction sanitizeContext(\n raw: unknown,\n label: string,\n validateEmail: boolean,\n): Record<string, string | number | boolean> | undefined {\n if (raw == null) return undefined;\n if (typeof raw !== \"object\" || Array.isArray(raw)) {\n log.warn(`ignored ${label} \u2014 expected an object`, { type: Array.isArray(raw) ? \"array\" : typeof raw });\n return undefined;\n }\n\n const out: Record<string, string | number | boolean> = {};\n let kept = 0;\n\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n if (RESERVED_KEYS.has(key)) {\n log.warn(`dropped reserved ${label} key \"${key}\" \u2014 managed by the widget, not host-settable`);\n continue;\n }\n if (value == null) continue; // skip null/undefined silently\n if (kept >= MAX_KEYS) {\n log.warn(`${label} truncated at ${MAX_KEYS} keys \u2014 dropped \"${key}\"`);\n continue;\n }\n const clean = sanitizeValue(label, key, value);\n if (clean === undefined) continue;\n out[key] = clean;\n kept++;\n }\n\n if (validateEmail && typeof out.email === \"string\" && !EMAIL_RE.test(out.email)) {\n log.warn(`dropped malformed ${label}.email \"${out.email}\"`);\n delete out.email;\n }\n\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nfunction sanitizeValue(label: string, key: string, value: unknown): string | number | boolean | undefined {\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n log.warn(`dropped non-finite ${label}.${key}`);\n return undefined;\n }\n return value;\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") return undefined;\n if (trimmed.length > MAX_STRING) {\n log.warn(`truncated ${label}.${key} to ${MAX_STRING} chars`);\n return trimmed.slice(0, MAX_STRING);\n }\n return trimmed;\n }\n log.warn(`dropped ${label}.${key} \u2014 only string/number/boolean values are allowed`, { type: typeof value });\n return undefined;\n}\n", "/**\n * Resolver fallbacks. Every value here mirrors a `.default()` on the\n * Zod schema \u2014 single source of truth lives on the schema, this file\n * exists so the resolver has fast lookups without re-running the\n * schema definitions. **If you change one, change the other.** The\n * configurator / dashboard surfaces use Zod's `.default()` values\n * directly; the IIFE runtime path doesn't run Zod (size budget \u2014 see\n * the `bun run size` checks), so these literals are what\n * `resolveOptions` falls back to when input fields are missing.\n *\n * Precedence at runtime: customer `mount({...})` > server-pushed\n * settings > the literals in this file.\n */\n\nimport type {\n Endpoints,\n HapticsOptions,\n HeaderActionsOption,\n LauncherIconPreset,\n LauncherSize,\n LauncherVariant,\n Mode,\n ModulesOptions,\n SoundOptions,\n VoiceMode,\n WelcomeOptions,\n} from \"./types\";\n\n/**\n * Mirror of `attachmentLimitsSchema` defaults in `src/schema/widget.ts`.\n */\nexport const DEFAULT_ATTACHMENTS = {\n maxBytes: 10 * 1024 * 1024,\n maxCount: 10,\n accept: \"*/*\",\n};\n\nexport const DEFAULT_WELCOME: Required<WelcomeOptions> = { animation: \"typewriter\", speed: 60 };\n\n/** Capability defaults. Voice mode defaults to local Web Speech when available. */\nexport const DEFAULT_FEATURES = {\n files: true,\n voice: \"local\" as VoiceMode,\n};\n\n/** HTTP endpoint defaults. Paths, appended to `baseUrl`. */\nexport const DEFAULT_ENDPOINTS: Required<Endpoints> = {\n upload: \"/ai/agent/upload-file\",\n transcribe: \"/ai/agent/transcribe-audio\",\n};\n\nexport const DEFAULT_LAUNCHER = {\n variant: \"circle\" as LauncherVariant,\n size: \"md\" as LauncherSize,\n label: undefined as string | undefined,\n icon: \"chat\" as LauncherIconPreset | string,\n hidden: false,\n};\n\n/**\n * Default header-action layout per mode. Items listed in `main` appear as\n * icon buttons in the header bar; items in `overflow` appear as rows under\n * the 3-dot menu. Order is preserved; unlisted actions are hidden.\n *\n * - `floating`/`iframe` \u2014 close visible, everything else under the menu.\n * - `inline` \u2014 close listed but visibility-gated to fullscreen\n * (X exits fullscreen back to in-flow layout);\n * `isActionVisible` filters it out in the normal\n * in-flow state so the deployment doesn't have to.\n * - `standalone` \u2014 minimal chrome (already in a dedicated tab).\n * - `page` \u2014 full-viewport with sidebar; same minimal chrome as standalone.\n */\nexport const DEFAULT_ACTIONS_BY_MODE: Record<Mode, Required<HeaderActionsOption>> = {\n floating: {\n main: [\"theme\", \"history\", \"close\"],\n overflow: [\"clear\", \"expand\", \"fullscreen\", \"popOut\", \"sound\", \"language\"],\n },\n inline: {\n main: [\"theme\", \"history\", \"close\"],\n overflow: [\"clear\", \"fullscreen\", \"popOut\", \"sound\", \"language\"],\n },\n standalone: {\n main: [\"theme\", \"history\"],\n overflow: [\"clear\", \"sound\", \"language\"],\n },\n page: {\n main: [\"theme\", \"history\"],\n overflow: [\"clear\", \"sound\", \"language\"],\n },\n modal: {\n main: [\"theme\", \"history\", \"close\"],\n overflow: [\"clear\", \"fullscreen\", \"popOut\", \"sound\", \"language\"],\n },\n drawer: {\n main: [\"theme\", \"history\", \"close\"],\n overflow: [\"clear\", \"fullscreen\", \"popOut\", \"sound\", \"language\"],\n },\n};\n\n/**\n * Feedback defaults. Sound + haptics both default to enabled \u2014 sound\n * waits for a user gesture before it can play (browser policy), so\n * starting \"on\" is safe and the page owner doesn't have to opt in to\n * get the feedback their users expect. Per-event presets live in\n * `core/feedback/defaults.ts` \u2014 keeping them there avoids pulling the\n * audio module into every bundle that only reads config types.\n */\nexport const DEFAULT_SOUND: Required<Omit<SoundOptions, \"events\">> & { events: SoundOptions[\"events\"] } = {\n enabled: true,\n volume: 0.3,\n events: undefined,\n};\n\nexport const DEFAULT_HAPTICS: Required<Omit<HapticsOptions, \"events\">> & { events: HapticsOptions[\"events\"] } = {\n enabled: true,\n events: undefined,\n};\n\n/**\n * Messenger modules \u2014 mirrors `src/schema/widget/modules.ts`. Defaults to a\n * single `chat` tab, so a deployment that configures nothing is just the\n * conversation with no tab bar. A deployment opts into the tabbed surface by\n * declaring more tabs.\n */\nexport const DEFAULT_MODULES: ModulesOptions = [{ label: \"tabMessages\", layout: \"chat\" }];\n\n/** `home` layout defaults (mirrors `moduleSchema`). */\nexport const DEFAULT_HOME_MODULE = { showSearchBar: true };\n", "/**\n * Normalise raw {@link ChatWidgetOptions} into {@link ResolvedOptions}.\n *\n * No network, no DOM. Called on every `mount()`, `init()`, `update()`, and\n * after a server settings merge. The one side effect is sanitising the\n * untrusted host-provided `userContext` / `pageContext`, which may log\n * `warn`s about dropped/malformed fields (see `sanitize*Context`).\n */\n\nimport { resolveStrings } from \"@/i18n/strings\";\nimport { BRAND } from \"../brand\";\nimport { defaultStorage } from \"../storage\";\nimport { sanitizePageContext, sanitizeUserContext } from \"./user-context\";\nimport {\n DEFAULT_ACTIONS_BY_MODE,\n DEFAULT_ATTACHMENTS,\n DEFAULT_ENDPOINTS,\n DEFAULT_FEATURES,\n DEFAULT_HAPTICS,\n DEFAULT_HOME_MODULE,\n DEFAULT_LAUNCHER,\n DEFAULT_MODULES,\n DEFAULT_SOUND,\n DEFAULT_WELCOME,\n} from \"./defaults\";\nimport type {\n ChatWidgetOptions,\n ComposerOptions,\n HapticsOptions,\n HeaderActionsOption,\n LauncherOptions,\n Mode,\n ModulesOptions,\n PoweredByOption,\n ResolvedModule,\n ResolvedModules,\n ResolvedOptions,\n SoundOptions,\n ThemeOverrides,\n ThemePreference,\n} from \"./types\";\n\nexport function resolveOptions(rawOpts: ChatWidgetOptions): ResolvedOptions {\n // Guard against a non-object root (string / number / null) by\n // substituting `{}` so every `opts.foo ?? default` below resolves\n // safely. Bad VALUES on specific fields are handled by the\n // `?? default` fallbacks themselves \u2014 no validation pass needed.\n // Zod-driven validation lives in the configurator / dashboard path\n // via `@/schema`; the IIFE auto-boot bundle stays tight.\n const opts = rawOpts !== null && typeof rawOpts === \"object\" ? rawOpts : ({} as ChatWidgetOptions);\n const presentation = opts.presentation ?? {};\n const behavior = opts.behavior ?? {};\n const i18n = opts.i18n ?? {};\n const feedback = opts.feedback ?? {};\n const footer = opts.footer ?? {};\n const header = opts.header ?? {};\n\n const { themeMode, themeOverrides } = parseTheme(opts.theme);\n const mode: Mode = presentation.mode ?? \"floating\";\n // Locale resolution. `i18n.locale` (page-owner explicit) wins;\n // otherwise we resolve `i18n.defaultLocale` (a concrete tag, or\n // `\"auto\"` \u2192 `navigator.language`). The end-user's persisted picker\n // choice is layered on top in App \u2014 resolveOptions stays pure /\n // DOM-free. `availableLocales` defaults to a single-entry list of\n // the active locale, which hides the language switcher (it requires\n // \u22652 to render).\n const locale = i18n.locale ?? resolveDefaultLocale(i18n.defaultLocale);\n const availableLocales = i18n.availableLocales ?? [locale];\n\n return {\n widgetId: opts.widgetId ?? \"default\",\n publicKey: opts.publicKey,\n aiAgentDeploymentId: opts.aiAgentDeploymentId,\n baseUrl: (opts.baseUrl ?? BRAND.defaultBaseUrl).replace(/\\/$/, \"\"),\n userContext: sanitizeUserContext(opts.userContext),\n pageContext: sanitizePageContext(opts.pageContext),\n position: presentation.position ?? \"bottom-right\",\n themeMode,\n themeOverrides,\n mode,\n iframe: presentation.iframe ?? false,\n responseMode: behavior.responseMode ?? \"streaming\",\n showReasoning: behavior.showReasoning ?? false,\n showToolCalls: behavior.showToolCalls ?? false,\n popOutUrl: behavior.popOutUrl ?? BRAND.standaloneUrl,\n welcome: { ...DEFAULT_WELCOME, ...opts.welcome },\n features: {\n files: opts.features?.files ?? DEFAULT_FEATURES.files,\n voice: opts.features?.voice ?? DEFAULT_FEATURES.voice,\n tools: opts.features?.tools,\n },\n endpoints: {\n upload: opts.endpoints?.upload ?? DEFAULT_ENDPOINTS.upload,\n transcribe: opts.endpoints?.transcribe ?? DEFAULT_ENDPOINTS.transcribe,\n },\n attachments: {\n accept: opts.composer?.attachments?.accept ?? DEFAULT_ATTACHMENTS.accept,\n maxBytes: opts.composer?.attachments?.maxBytes ?? DEFAULT_ATTACHMENTS.maxBytes,\n maxCount: opts.composer?.attachments?.maxCount ?? DEFAULT_ATTACHMENTS.maxCount,\n },\n locale,\n availableLocales,\n startMinimized: behavior.startMinimized ?? true,\n actions: resolveActions(mode, header.actions),\n size: presentation.size ?? {},\n launcher: resolveLauncher(opts.launcher),\n sound: resolveSound(feedback.sound),\n haptics: resolveHaptics(feedback.haptics),\n strings: resolveStrings(locale, i18n.strings),\n stringsOverride: i18n.strings,\n composerDisclaimer: footer.composerDisclaimer ?? \"\",\n poweredBy: resolvePoweredBy(footer.poweredBy),\n composer: resolveComposer(opts.composer),\n modules: resolveModules(opts.modules),\n storage: opts.storage ?? defaultStorage,\n onMessage: opts.onMessage,\n onOpen: opts.onOpen,\n onClose: opts.onClose,\n onError: opts.onError,\n };\n}\n\n/**\n * Split the union-shaped `theme` input into the two slots\n * `ResolvedOptions` exposes \u2014 a preference enum plus an overrides\n * object.\n */\nfunction parseTheme(input: ChatWidgetOptions[\"theme\"]): { themeMode: ThemePreference; themeOverrides: ThemeOverrides } {\n if (typeof input === \"string\") return { themeMode: input, themeOverrides: {} };\n if (input && typeof input === \"object\") return { themeMode: \"auto\", themeOverrides: input };\n return { themeMode: \"auto\", themeOverrides: {} };\n}\n\n/**\n * Resolve the `defaultLocale` config field down to a concrete BCP-47 tag.\n *\n * - Unset or `\"auto\"` \u2192 sniff `navigator.language` (or `\"en\"` outside browsers).\n * - Any other value \u2192 returned as-is (page-owner / server explicit override).\n */\nfunction resolveDefaultLocale(input: string | undefined): string {\n if (input && input !== \"auto\") return input;\n return typeof navigator !== \"undefined\" && navigator.language ? navigator.language : \"en\";\n}\n\nfunction resolveActions(mode: Mode, overrides: HeaderActionsOption | undefined): Required<HeaderActionsOption> {\n const defaults = DEFAULT_ACTIONS_BY_MODE[mode];\n return {\n main: overrides?.main ?? defaults.main,\n overflow: overrides?.overflow ?? defaults.overflow,\n };\n}\n\nfunction resolveSound(overrides: SoundOptions | undefined): ResolvedOptions[\"sound\"] {\n return {\n enabled: overrides?.enabled ?? DEFAULT_SOUND.enabled,\n volume: overrides?.volume ?? DEFAULT_SOUND.volume,\n events: overrides?.events,\n };\n}\n\nfunction resolveHaptics(overrides: HapticsOptions | undefined): ResolvedOptions[\"haptics\"] {\n return {\n enabled: overrides?.enabled ?? DEFAULT_HAPTICS.enabled,\n events: overrides?.events,\n };\n}\n\n/**\n * Normalise the attribution object. The brand provides a sensible\n * default (`Powered by <DisplayName>` linking to the brand site) so\n * page-owners don't need to wire anything; explicit overrides win\n * per-field. Pass `poweredBy: { text: \"\" }` to opt out.\n */\nfunction resolvePoweredBy(overrides: PoweredByOption | undefined): PoweredByOption | null {\n const brandDefault: PoweredByOption = {\n text: `Powered by ${BRAND.displayName}`,\n href: BRAND.siteUrl,\n };\n const merged = { ...brandDefault, ...overrides };\n // Empty `text` + no `logoUrl` \u2192 caller is opting out.\n const hasContent = Boolean(merged.text || merged.logoUrl);\n return hasContent ? merged : null;\n}\n\nfunction resolveComposer(overrides: ComposerOptions | undefined): ResolvedOptions[\"composer\"] {\n const sb = overrides?.sendButton;\n return {\n sendButton: {\n shape: sb?.shape ?? \"circle\",\n icon: sb?.icon ?? \"arrowUp\",\n variant: sb?.variant ?? \"filled\",\n },\n };\n}\n\n/**\n * Resolve the messenger `modules` list: keep declared order, cap at 4, fill a\n * stable `id` (`id` ?? first `contentTags` ?? `layout`, deduped), and default\n * `home.searchEnabled`. An ABSENT list (undefined) falls back to\n * {@link DEFAULT_MODULES} (a single `chat` tab); an explicit EMPTY list stays\n * empty (\u2192 the empty state). Entries missing `label`/`layout` are dropped.\n */\nfunction resolveModules(overrides: ModulesOptions | undefined): ResolvedModules {\n const raw = overrides === undefined ? DEFAULT_MODULES : overrides;\n const seen = new Set<string>();\n const list: ResolvedModule[] = [];\n for (const m of raw.slice(0, 4)) {\n if (!m?.label || !m?.layout) continue;\n const tags = m.contentTags?.filter(Boolean);\n let id = m.id ?? tags?.[0] ?? m.layout;\n while (seen.has(id)) id = `${id}-${list.length}`;\n seen.add(id);\n const resolved: ResolvedModule = { id, label: m.label, layout: m.layout };\n if (tags && tags.length > 0) resolved.contentTags = tags;\n if (m.layout === \"home\") {\n resolved.brandName = m.brandName;\n resolved.greetingText = m.greetingText;\n resolved.showGreeting = m.showGreeting ?? true;\n resolved.showSearchBar = m.showSearchBar ?? DEFAULT_HOME_MODULE.showSearchBar;\n resolved.showRecentMessages = m.showRecentMessages ?? true;\n if (m.userAvatars && m.userAvatars.length > 0) resolved.userAvatars = m.userAvatars;\n if (m.status) resolved.status = m.status;\n resolved.contentBlockTitle = m.contentBlockTitle;\n }\n list.push(resolved);\n }\n const byId = Object.fromEntries(list.map((m) => [m.id, m]));\n return { list, byId };\n}\n\nfunction resolveLauncher(overrides: LauncherOptions | undefined): ResolvedOptions[\"launcher\"] {\n // Callout defaults: shape \"pill\", auto-position (component picks\n // left/right based on launcher corner), 1.2s reveal delay, no\n // auto-dismiss, animated, dismiss-once. Empty / missing `text`\n // collapses the whole callout to `null` so the UI never renders\n // an empty bubble.\n const callout = overrides?.callout?.text\n ? {\n shape: \"pill\" as const,\n position: \"auto\" as const,\n delayMs: 1200,\n autoDismissMs: 0,\n animated: true,\n persistent: true,\n ...overrides.callout,\n }\n : null;\n return {\n variant: overrides?.variant ?? DEFAULT_LAUNCHER.variant,\n size: overrides?.size ?? DEFAULT_LAUNCHER.size,\n label: overrides?.label ?? DEFAULT_LAUNCHER.label,\n icon: overrides?.icon ?? DEFAULT_LAUNCHER.icon,\n hidden: overrides?.hidden ?? DEFAULT_LAUNCHER.hidden,\n callout,\n };\n}\n", "/**\n * Apply user-driven runtime overrides (active locale, active theme\n * mode) on top of an already-resolved `ResolvedOptions` snapshot.\n *\n * `resolveOptions(rawOptions)` happens once per mount / option update\n * and produces the immutable baseline. The user can then change\n * locale (language switcher) or theme (theme switcher) at runtime;\n * `applyOptionOverrides` produces a fresh `ResolvedOptions` with\n * those two fields swapped. Identity-preserving when neither override\n * differs from the resolved baseline, so downstream `useMemo` deps on\n * the returned object stay stable in the common no-change case.\n *\n * Used by `<App>` via `useOptionsWithOverrides()` \u2014 the hook just\n * wraps this function in a `useMemo` for ergonomics.\n */\n\nimport { resolveStrings } from \"@/i18n/strings\";\nimport type { ResolvedOptions, ThemePreference } from \"./types\";\n\nexport function applyOptionOverrides(\n options: ResolvedOptions,\n activeLocale: string,\n activeThemeMode: ThemePreference,\n): ResolvedOptions {\n // Identity-preserving fast path \u2014 every common render hits this.\n if (activeLocale === options.locale && activeThemeMode === options.themeMode) return options;\n\n const next: ResolvedOptions = { ...options };\n if (activeLocale !== options.locale) {\n next.locale = activeLocale;\n next.strings = resolveStrings(activeLocale, options.stringsOverride);\n }\n if (activeThemeMode !== options.themeMode) next.themeMode = activeThemeMode;\n return next;\n}\n", "/**\n * Merge server-pushed settings into raw user options.\n *\n * Policy: **user wins per-field.** If the developer explicitly passed a\n * value to `init()`, it stays. Only fields the developer left\n * `undefined` take their value from the server. Section-shaped fields\n * merge at the leaf level so both sides can contribute partial configs.\n *\n * Both sides use the nested shape \u2014 `mode`/`position` live on\n * `presentation`, `startMinimized`/`responseMode` on `behavior`, etc.\n * Theme is a union (preference enum or overrides object); it's\n * merged scalar-style since a preference value can't sit alongside\n * overrides.\n */\n\nimport { isNestedShape, type LocaleStrings } from \"@/i18n/strings\";\nimport type { ChatWidgetOptions, ServerConfig, StringsOverride } from \"./types\";\n\n/** Top-level keys that the server can push and we merge object-style. */\nconst SECTION_KEYS = [\n \"presentation\",\n \"behavior\",\n \"theme\",\n \"launcher\",\n \"header\",\n \"composer\",\n \"welcome\",\n \"feedback\",\n \"i18n\",\n \"footer\",\n \"features\",\n \"endpoints\",\n] as const satisfies readonly (keyof ServerConfig & keyof ChatWidgetOptions)[];\n\nexport function mergeServerConfig(user: ChatWidgetOptions, server: ServerConfig | undefined): ChatWidgetOptions {\n if (!server) return user;\n const out = { ...user } as Record<string, unknown>;\n\n for (const key of SECTION_KEYS) {\n const sv = server[key];\n if (sv === undefined) continue;\n const uv = (user as Record<string, unknown>)[key];\n\n // `theme` is a union (preference string OR object). Treat\n // scalar-style: user wins, otherwise server fills in.\n if (key === \"theme\") {\n if (uv === undefined) out[key] = sv;\n continue;\n }\n\n // `i18n` is handled below \u2014 `strings` needs the locale-aware merge,\n // and folding it into the generic leaf merge here would replace\n // the result a second time. Other `i18n.*` keys (defaultLocale,\n // locale, availableLocales) merge alongside in that single pass.\n if (key === \"i18n\") continue;\n\n // Section / object \u2014 leaf-merge with user winning per-key.\n out[key] = mergeLeaves(sv as Record<string, unknown>, uv as Record<string, unknown> | undefined);\n }\n\n // `modules` is an ordered ARRAY (the messenger tabs) \u2014 there's no sane\n // per-key merge, so it's replace-whole: an explicit `init({ modules })` wins,\n // otherwise the server's list is adopted.\n if (server.modules !== undefined && user.modules === undefined) out.modules = server.modules;\n\n // Single-pass `i18n` merge: leaf-merge the scalar fields\n // (defaultLocale / locale / availableLocales) AND apply the\n // locale-aware override merge for `strings`.\n const userI18n = user.i18n;\n const serverI18n = server.i18n;\n if (userI18n || serverI18n) {\n const merged = mergeLeaves(\n (serverI18n ?? {}) as Record<string, unknown>,\n userI18n as Record<string, unknown> | undefined,\n );\n const userStrings = userI18n?.strings;\n const serverStrings = serverI18n?.strings;\n if (userStrings || serverStrings) {\n merged.strings = mergeStringsOverride(serverStrings, userStrings);\n }\n out.i18n = merged;\n }\n\n return out as ChatWidgetOptions;\n}\n\n/**\n * Shallow leaf merge: every key from `server` is kept, then any key\n * the user explicitly set (non-undefined) overrides it. Used for\n * every section above except `theme` (union) and `i18n.strings`\n * (locale-aware).\n */\nfunction mergeLeaves(\n server: Record<string, unknown>,\n user: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n if (!user) return server;\n const out: Record<string, unknown> = { ...server };\n for (const [k, v] of Object.entries(user)) {\n if (v !== undefined) out[k] = v;\n }\n return out;\n}\n\n/**\n * Merge `strings` overrides from server + user.\n *\n * Both sides may use either the **flat** form (`{ send: \"Click\" }` \u2014 applies\n * to the active locale) or the **nested** form\n * (`{ en: { send: \"Click\" }, fr: { send: \"Cliquer\" } }` \u2014 per-locale).\n *\n * If either side is nested, the result is normalised to nested so no\n * per-locale strings are silently dropped. User wins per-key (consistent\n * with the rest of the merge \u2014 page owner overrides server defaults).\n */\nfunction mergeStringsOverride(server: StringsOverride | undefined, user: StringsOverride | undefined): StringsOverride {\n if (!server) return user as StringsOverride;\n if (!user) return server;\n const serverNested = isNestedShape(server);\n const userNested = isNestedShape(user);\n if (!serverNested && !userNested) {\n // Both flat \u2014 shallow merge with user winning.\n return { ...(server as LocaleStrings), ...(user as LocaleStrings) };\n }\n // At least one is nested \u2014 promote the flat side under \"en\" so no\n // per-key intent is lost, then deep-merge per locale.\n const out: Record<string, LocaleStrings> = {};\n const serverNestedMap = serverNested ? (server as Record<string, LocaleStrings>) : { en: server as LocaleStrings };\n const userNestedMap = userNested ? (user as Record<string, LocaleStrings>) : { en: user as LocaleStrings };\n for (const [loc, map] of Object.entries(serverNestedMap)) out[loc] = { ...map };\n for (const [loc, map] of Object.entries(userNestedMap)) out[loc] = { ...out[loc], ...map };\n return out;\n}\n", "/**\n * Parse kebab-case attributes into {@link ChatWidgetOptions}.\n *\n * Shared by the `<script data-*>` auto-boot path and the Web Component.\n * HTML attributes are flat (`data-mode=\"floating\"`); the nested\n * `ChatWidgetOptions` shape is the canonical input form. This parser\n * is the boundary that turns one into the other so customer-facing\n * embeds don't have to type `data-presentation-mode`.\n */\n\nimport type {\n ActionName,\n BehaviorOptions,\n ChatWidgetOptions,\n Endpoints,\n FeatureFlags,\n HeaderActionsOption,\n LauncherOptions,\n LauncherSize,\n LauncherVariant,\n Mode,\n Position,\n PresentationOptions,\n ResponseMode,\n PageContext,\n SizeOptions,\n ThemePreference,\n UserContext,\n VoiceMode,\n} from \"./types\";\n\n/**\n * Action names recognised by the data-`{main,overflow}-actions`\n * attributes. Mirrors `actionNameSchema` in\n * `src/schema/widget/header.ts` \u2014 the schema is the source of truth\n * for the dashboard / validation surface, but we duplicate the literal\n * here so the IIFE auto-boot path doesn't import `@/schema`'s Zod\n * barrel (~70 KB gz). A unit test in `tests/unit/action-names-sync.test.ts`\n * pins the two arrays in lock-step so this hand mirror can't drift.\n */\nexport const ACTION_NAME_LITERALS = [\n \"clear\",\n \"expand\",\n \"fullscreen\",\n \"popOut\",\n \"sound\",\n \"history\",\n \"language\",\n \"theme\",\n \"close\",\n] as const satisfies readonly ActionName[];\n\n/**\n * Read an attribute value. The kebab-case `name` is passed in \u2014 the\n * implementation maps it to whatever storage backs this source.\n */\nexport type AttrGetter = (name: string) => string | null;\n\ntype Str = (name: string) => string | undefined;\n\n/** `true` for any string other than the literal `\"false\"`. Useful for boolean attrs. */\nconst boolAttr = (v: string) => v !== \"false\";\n\n/** `,`-separated \u2192 `string[]` (trimmed, empty segments dropped). */\nconst csv = (v: string) =>\n v\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n\n// ---- embed-time (L2) attrs \u2014 top-level on ChatWidgetOptions ------------\n\ntype EmbedScalarSpec = [attr: string, key: keyof ChatWidgetOptions, cast?: (raw: string) => unknown];\n\nconst EMBED_SCALAR_ATTRS: EmbedScalarSpec[] = [\n [\"public-key\", \"publicKey\"],\n [\"ai-agent-deployment-id\", \"aiAgentDeploymentId\"],\n [\"base-url\", \"baseUrl\"],\n [\"theme\", \"theme\", (v) => v as ThemePreference],\n];\n\n// ---- presentation section attrs ---------------------------------------\n\ntype PresentationScalarSpec = [attr: string, key: keyof PresentationOptions, cast?: (v: string) => unknown];\n\nconst PRESENTATION_ATTRS: PresentationScalarSpec[] = [\n [\"mode\", \"mode\", (v) => v as Mode],\n [\"position\", \"position\", (v) => v as Position],\n [\"iframe\", \"iframe\", boolAttr],\n];\n\nconst PRESENTATION_SIZE_ATTRS: Array<[attr: string, key: keyof SizeOptions]> = [\n [\"width\", \"width\"],\n [\"height\", \"height\"],\n];\n\n// ---- behavior section attrs -------------------------------------------\n\ntype BehaviorScalarSpec = [attr: string, key: keyof BehaviorOptions, cast?: (v: string) => unknown];\n\nconst BEHAVIOR_ATTRS: BehaviorScalarSpec[] = [\n [\"response-mode\", \"responseMode\", (v) => v as ResponseMode],\n [\"start-minimized\", \"startMinimized\", boolAttr],\n [\"show-reasoning\", \"showReasoning\", boolAttr],\n [\"show-tool-calls\", \"showToolCalls\", boolAttr],\n];\n\n// ---- i18n section attrs -----------------------------------------------\n\nconst I18N_ATTRS: Array<[attr: string, key: keyof NonNullable<ChatWidgetOptions[\"i18n\"]>]> = [[\"locale\", \"locale\"]];\n\n// ---- header / launcher / composer / endpoints / features ---------------\n\n/**\n * Recognised action names \u2014 anything else is dropped from the parsed\n * list. Mirrors `actionNameSchema` in the schema package; the unit\n * test linked in `ACTION_NAME_LITERALS` above pins them in lock-step.\n */\nconst ACTION_NAMES = new Set<ActionName>(ACTION_NAME_LITERALS);\n\nconst onlyValidActions = (v: string): ActionName[] =>\n csv(v).filter((name): name is ActionName => ACTION_NAMES.has(name as ActionName));\n\nconst LAUNCHER_ATTRS: Array<[attr: string, key: keyof LauncherOptions, cast?: (v: string) => unknown]> = [\n [\"launcher-variant\", \"variant\", (v) => v as LauncherVariant],\n [\"launcher-size\", \"size\", (v) => v as LauncherSize],\n [\"launcher-icon\", \"icon\"],\n [\"launcher-hidden\", \"hidden\", boolAttr],\n];\n\nconst FEATURE_ATTRS: Array<[attr: string, key: keyof FeatureFlags, cast?: (v: string) => unknown]> = [\n [\"features-files\", \"files\", boolAttr],\n [\"features-voice\", \"voice\", (v) => v as VoiceMode],\n [\"features-tools\", \"tools\", csv],\n];\n\nconst ENDPOINT_ATTRS: Array<[attr: string, key: keyof Endpoints]> = [\n [\"endpoint-upload\", \"upload\"],\n [\"endpoint-transcribe\", \"transcribe\"],\n];\n\n// ---- host-asserted context attrs --------------------------------------\n// Informational context the embedding site asserts about its visitor \u2014\n// distinct from the widget's own visitor/session, and never auth. Custom\n// fields go through `data-user-context` / `data-page-context` as JSON.\n\nconst USER_CONTEXT_ATTRS: Array<[attr: string, key: \"id\" | \"email\" | \"name\"]> = [\n [\"user-id\", \"id\"],\n [\"user-email\", \"email\"],\n [\"user-name\", \"name\"],\n];\n\nconst PAGE_CONTEXT_ATTRS: Array<[attr: string, key: \"area\" | \"url\" | \"path\" | \"title\"]> = [\n [\"page-area\", \"area\"],\n [\"page-url\", \"url\"],\n [\"page-path\", \"path\"],\n [\"page-title\", \"title\"],\n];\n\n/**\n * Attributes that don't fit a generic table because their parser\n * needs custom logic (multi-attr aggregation, empty-string accepted,\n * etc.). Listed here so they're folded into `REACTIVE_ATTRS` without\n * a free-floating string literal further down.\n */\nconst SPECIAL_REACTIVE_ATTRS = [\n \"launcher-label\",\n \"callout-text\",\n \"callout-delay\",\n \"actions-main\",\n \"actions-overflow\",\n \"user-context\",\n \"page-context\",\n] as const;\n\n/**\n * Every flat-attribute table in this file. Single source of truth \u2014\n * both the runtime parser and `REACTIVE_ATTRS` derive from this list.\n */\nconst ATTR_TABLES = [\n EMBED_SCALAR_ATTRS,\n PRESENTATION_ATTRS,\n PRESENTATION_SIZE_ATTRS,\n BEHAVIOR_ATTRS,\n I18N_ATTRS,\n LAUNCHER_ATTRS,\n FEATURE_ATTRS,\n ENDPOINT_ATTRS,\n USER_CONTEXT_ATTRS,\n PAGE_CONTEXT_ATTRS,\n] as const;\n\n/**\n * Attributes the web component observes for runtime mutation.\n *\n * `iframe` and `start-minimized` are filtered out because both\n * require a full re-mount path (not just `attributeChangedCallback`).\n */\nconst NON_REACTIVE_SCALARS = new Set<string>([\"iframe\", \"start-minimized\"]);\nexport const REACTIVE_ATTRS: readonly string[] = [\n ...ATTR_TABLES.flatMap((table) =>\n (table as readonly (readonly [string, ...unknown[]])[])\n .map(([attr]) => attr)\n .filter((attr) => !NON_REACTIVE_SCALARS.has(attr)),\n ),\n ...SPECIAL_REACTIVE_ATTRS,\n];\n\n/**\n * Parse the flat HTML data-* attribute surface into nested\n * `ChatWidgetOptions`. Sections (`presentation`, `behavior`, `i18n`,\n * `header`, `launcher`, `composer`, `features`) get instantiated only\n * when at least one of their fields is present, so the resulting\n * object is sparse.\n */\nexport function parseAttributes(get: AttrGetter): ChatWidgetOptions {\n const opts: ChatWidgetOptions = {};\n const str: Str = (name) => {\n const v = get(name);\n return v === null || v === \"\" ? undefined : v;\n };\n\n // Embed-time (L2) scalars sit at the top level.\n for (const [attr, key, cast] of EMBED_SCALAR_ATTRS) {\n const raw = str(attr);\n if (raw === undefined) continue;\n (opts as Record<string, unknown>)[key] = cast ? cast(raw) : raw;\n }\n\n // Presentation section.\n const presentationFlat = collectSubset<PresentationOptions>(str, PRESENTATION_ATTRS);\n const size = collectSubset<SizeOptions>(str, PRESENTATION_SIZE_ATTRS);\n if (presentationFlat || size) {\n opts.presentation = { ...presentationFlat, ...(size ? { size } : {}) };\n }\n\n // Behavior section.\n const behavior = collectSubset<BehaviorOptions>(str, BEHAVIOR_ATTRS);\n if (behavior) opts.behavior = behavior;\n\n // i18n section.\n const i18n = collectSubset<NonNullable<ChatWidgetOptions[\"i18n\"]>>(str, I18N_ATTRS);\n if (i18n) opts.i18n = i18n;\n\n // Launcher (incl. callout sub-block + `launcher-label` empty-OK).\n const launcher = parseLauncher(get, str);\n if (launcher) opts.launcher = launcher;\n\n // Features.\n const features = collectSubset<FeatureFlags>(str, FEATURE_ATTRS);\n if (features) opts.features = features;\n\n // Endpoints.\n const endpoints = collectSubset<Endpoints>(str, ENDPOINT_ATTRS);\n if (endpoints) opts.endpoints = endpoints;\n\n // Host-asserted context (informational, untrusted, sanitised later in\n // `resolveOptions`). Recognised scalar fields + an optional JSON object of\n // extra custom fields. Explicit scalar attrs win over JSON keys.\n const userContext = collectContext(str, USER_CONTEXT_ATTRS, \"user-context\");\n if (userContext) opts.userContext = userContext as UserContext;\n const pageContext = collectContext(str, PAGE_CONTEXT_ATTRS, \"page-context\");\n if (pageContext) opts.pageContext = pageContext as PageContext;\n\n // Header actions.\n const actions = parseActions(str);\n if (actions) opts.header = { actions };\n\n return opts;\n}\n\n/**\n * Parse a `<script>` tag's `dataset` into {@link ChatWidgetOptions}.\n * Converts camelCase `dataset` keys back to kebab-case for {@link parseAttributes}.\n */\nexport function optionsFromScriptDataset(ds: DOMStringMap | undefined): ChatWidgetOptions {\n if (!ds) return {};\n return parseAttributes((attr) => ds[kebabToCamel(attr)] ?? null);\n}\n\n/**\n * Look for the two `actions-*` lists and build {@link HeaderActionsOption}.\n * Returns `null` when neither is present so the resolver falls back to\n * per-mode defaults.\n */\nfunction parseActions(str: Str): HeaderActionsOption | null {\n const mainRaw = str(\"actions-main\");\n const overflowRaw = str(\"actions-overflow\");\n if (mainRaw === undefined && overflowRaw === undefined) return null;\n const out: HeaderActionsOption = {};\n if (mainRaw !== undefined) out.main = onlyValidActions(mainRaw);\n if (overflowRaw !== undefined) out.overflow = onlyValidActions(overflowRaw);\n return out;\n}\n\n// ---- helpers -----------------------------------------------------------\n\nfunction kebabToCamel(kebab: string): string {\n return kebab.replaceAll(/-([a-z])/g, (_, c) => c.toUpperCase());\n}\n\n/**\n * Apply a table of `[attr, key, cast]` tuples to a nested options block.\n * Returns `null` when no attributes matched \u2014 so callers can omit the\n * empty object from the parent.\n */\nfunction collectSubset<T extends object>(\n str: Str,\n table: ReadonlyArray<readonly [string, keyof T, ((v: string) => unknown)?]>,\n): T | null {\n const out: Record<string, unknown> = {};\n for (const [attr, key, cast] of table) {\n const raw = str(attr);\n if (raw === undefined) continue;\n out[key as string] = cast ? cast(raw) : raw;\n }\n return Object.keys(out).length > 0 ? (out as T) : null;\n}\n\n/**\n * Parse a host-asserted context block (`userContext` / `pageContext`):\n * recognised scalar attrs from `table`, merged onto an optional JSON object in\n * `jsonAttr` (extra custom fields). Explicit scalar attrs win over JSON keys.\n * Malformed JSON is ignored \u2014 the scalar fields still apply. Values are\n * sanitised later in `resolveOptions`. Returns `null` when nothing matched.\n */\nfunction collectContext(\n str: Str,\n table: ReadonlyArray<readonly [string, string]>,\n jsonAttr: string,\n): Record<string, string> | null {\n const scalars: Record<string, string> = {};\n for (const [attr, key] of table) {\n const raw = str(attr);\n if (raw !== undefined) scalars[key] = raw;\n }\n\n let fromJson: Record<string, string> = {};\n const jsonRaw = str(jsonAttr);\n if (jsonRaw) {\n try {\n const parsed = JSON.parse(jsonRaw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) fromJson = parsed as Record<string, string>;\n } catch {\n // Ignore malformed JSON \u2014 recognised scalar fields still apply.\n }\n }\n\n const out = { ...fromJson, ...scalars };\n return Object.keys(out).length > 0 ? out : null;\n}\n\n/** Launcher has a wrinkle (`callout-text` + `callout-delay` pair), so it gets its own parser. */\nfunction parseLauncher(get: AttrGetter, str: Str): LauncherOptions | null {\n const base = collectSubset<LauncherOptions>(str, LAUNCHER_ATTRS) ?? {};\n\n // `launcher-label` accepts the empty string (renders a wordless pill),\n // so use the raw getter rather than `str`, which strips empties.\n const label = get(\"launcher-label\");\n if (label !== null) base.label = label;\n\n const text = str(\"callout-text\");\n if (text) {\n const delay = Number.parseInt(str(\"callout-delay\") ?? \"\", 10);\n base.callout = { text, ...(Number.isFinite(delay) ? { delayMs: delay } : {}) };\n }\n\n return Object.keys(base).length > 0 ? base : null;\n}\n", "/**\n * Lightweight runtime type-guards for the page-mode payloads on the\n * start-session response (`site` + `blocks`). The canonical Zod schemas\n * in `src/schema/deployment.ts` (`siteConfigSchema` /\n * `blocksConfigSchema`) are the source of truth for the wire\n * contract, but the IIFE auto-boot bundle can't import Zod (size\n * budget \u2014 see `bun run size`).\n *\n * Both `siteConfigSchema` and `blocksConfigSchema` are fully optional\n * `.loose()` shapes \u2014 there is no required discriminator field on\n * either. These narrowers accept any plain object and reject the\n * shapes that would crash the renderer (non-array `navigation` /\n * `linkCards`). Dashboards / dev consumers use `safeParse` via\n * `@helpai/elements/schema` for full validation.\n *\n * Renderers consume:\n * - `site.{title, logo, logoDark}` \u2014 sidebar brand identity\n * - `blocks.{navigation, linkCards}` \u2014 each Link has\n * `id?, label, href?, icon?, description?`.\n * Page URL slug lives on `deployment.slug`, not `site.slug`.\n */\n\nimport type { BlocksConfig, SiteConfig } from \"@/schema\";\n\nfunction isPlainObject(raw: unknown): raw is Record<string, unknown> {\n return !!raw && typeof raw === \"object\" && !Array.isArray(raw);\n}\n\nexport function isSiteConfigShape(raw: unknown): raw is SiteConfig {\n return isPlainObject(raw);\n}\n\nexport function isBlocksConfigShape(raw: unknown): raw is BlocksConfig {\n if (!isPlainObject(raw)) return false;\n // All fields are optional; reject only when one of the consumed\n // arrays is the wrong type. Renderer falls back to `[]` when missing.\n if (raw.navigation !== undefined && !Array.isArray(raw.navigation)) return false;\n if (raw.linkCards !== undefined && !Array.isArray(raw.linkCards)) return false;\n return true;\n}\n", "/**\n * Host-element command bus.\n *\n * The programmatic API (`chat.open()`, `.close()`, `.expand()`, `.popOut()`)\n * dispatches `CustomEvent`s on the shadow host element. The App listens\n * for those same events and routes them to its state handlers. That\n * keeps the two modules decoupled \u2014 `index.ts` doesn't hold a ref to App\n * state, App doesn't know about the public instance handle.\n *\n * This module is the single source of truth for the event names + the\n * tiny helpers on both sides of the bridge.\n */\n\nimport { BRAND } from \"./brand\";\n\n/** Names of commands exchanged between `index.ts` and `App`. */\nexport type HostCommand = \"open\" | \"close\" | \"expand\" | \"fullscreen\" | \"popout\";\n\n/** `${BRAND.cssPrefix}:${command}` \u2014 the full event name on the host. */\nfunction commandEventName(cmd: HostCommand): string {\n return `${BRAND.cssPrefix}:${cmd}`;\n}\n\n/** Dispatch a command to the App via the shadow host element. */\nexport function dispatchHostCommand(host: HTMLElement, cmd: HostCommand): void {\n host.dispatchEvent(new CustomEvent(commandEventName(cmd)));\n}\n\n/**\n * Bind every command name to its handler on the host element.\n * Returns a cleanup function that removes all listeners.\n */\nexport function bindHostCommands(host: HTMLElement, handlers: Record<HostCommand, () => void>): () => void {\n const entries = Object.entries(handlers) as [HostCommand, () => void][];\n const wrapped = entries.map(([cmd, fn]) => {\n const event = commandEventName(cmd);\n const listener = () => fn();\n host.addEventListener(event, listener);\n return [event, listener] as const;\n });\n return () => {\n for (const [event, listener] of wrapped) host.removeEventListener(event, listener);\n };\n}\n", ":host{--__P__-accent: __BRAND_ACCENT__;--__P__-accent-text: __BRAND_ACCENT_TEXT__;--__P__-radius: __BRAND_RADIUS__;--__P__-radius-sm: 8px;--__P__-radius-md: 10px;--__P__-radius-lg: 16px;--__P__-font: __BRAND_FONT__;--__P__-font-display: var(--__P__-font);--__P__-font-mono: ui-monospace, \"SF Mono\", Menlo, Consolas, monospace;--__P__-bg: oklch(1 0 0);--__P__-bg-elevated: oklch(.9789 .0029 278.3);--__P__-surface: var(--__P__-bg-elevated);--__P__-muted: var(--__P__-fg-muted);--__P__-hover: oklch(.1686 .011 278.3 / .06);--__P__-fg: oklch(.1686 .011 278.3);--__P__-fg-muted: oklch(.4932 .0301 278.3);--__P__-border: oklch(.9312 .0084 278.3);--__P__-border-strong: oklch(.8757 .0131 278.3);--__P__-bubble-user: var(--__P__-accent);--__P__-bubble-user-text: var(--__P__-accent-text);--__P__-bubble-assistant: oklch(.9638 .0058 278.3);--__P__-bubble-assistant-text: var(--__P__-fg);--__P__-on-accent: var(--__P__-accent-text);--__P__-success: oklch(.6456 .1654 151.53);--__P__-warning: oklch(.7135 .1494 74.05);--__P__-danger: oklch(.5966 .1818 24.51);--__P__-danger-text: oklch(.5054 .1905 27.52);--__P__-danger-bg: oklch(.5966 .1818 24.51 / .1);--__P__-neutral: oklch(.7137 .0192 278.3);--__P__-focus: var(--__P__-accent);--__P__-shadow-panel: 0 8px 24px -8px rgba(13, 15, 20, .18), 0 24px 64px -24px rgba(13, 15, 20, .24);--__P__-shadow-card: 0 1px 2px rgba(13, 15, 20, .06), 0 4px 12px -6px rgba(13, 15, 20, .12);--__P__-backdrop: oklch(.1686 .011 278.3 / .55);--__P__-shadow-fab: 0 6px 16px -4px color-mix(in oklch, var(--__P__-accent) 45%, transparent), 0 12px 32px -12px rgba(13, 15, 20, .25);--__P__-z-fab: 2147483600;--__P__-z-panel: 2147483601;--__P__-panel-w: 380px;--__P__-panel-h: 600px;--__P__-fab-size: 56px;--__P__-space-1: 4px;--__P__-space-2: 8px;--__P__-space-3: 12px;--__P__-space-4: 16px;--__P__-space-5: 20px;--__P__-space-6: 24px;--__P__-space-8: 32px;--__P__-text-xs: 12px;--__P__-text-sm: 13px;--__P__-text-base: 15px;--__P__-text-md: 16px;--__P__-text-lg: 18px;--__P__-text-xl: 22px;--__P__-text-2xl: 30px;--__P__-ease: cubic-bezier(.2, .7, .2, 1);--__P__-ease-out: cubic-bezier(.2, .7, .2, 1);--__P__-ease-in: cubic-bezier(.4, 0, 1, 1);--__P__-ease-in-out: cubic-bezier(.65, 0, .35, 1);--__P__-dur-quick: .12s;--__P__-dur-base: .22s;--__P__-dur-slow: .32s;font-family:var(--__P__-font);color:var(--__P__-fg);color-scheme:light}:host([data-theme=\"dark\"]){--__P__-bg: oklch(.1783 .0128 278.3);--__P__-bg-elevated: oklch(.2232 .0182 278.3);--__P__-fg: oklch(.9368 .01 278.3);--__P__-fg-muted: oklch(.7048 .0296 278.3);--__P__-border: oklch(.2752 .0266 278.3);--__P__-border-strong: oklch(.335 .0291 278.3);--__P__-hover: oklch(.9368 .01 278.3 / .08);--__P__-bubble-assistant: oklch(.2474 .0311 278.3);--__P__-bubble-assistant-text: var(--__P__-fg);--__P__-danger-text: oklch(.8077 .1035 19.57);--__P__-danger-bg: oklch(.5966 .1818 24.51 / .18);--__P__-shadow-panel: 0 8px 24px -8px rgba(0, 0, 0, .6), 0 24px 64px -24px rgba(0, 0, 0, .7);--__P__-shadow-card: 0 1px 2px rgba(0, 0, 0, .4), 0 4px 12px -6px rgba(0, 0, 0, .5);--__P__-backdrop: rgba(0, 0, 0, .65);color-scheme:dark}@media(prefers-color-scheme:dark){:host([data-theme=\"auto\"]){--__P__-bg: oklch(.1783 .0128 278.3);--__P__-bg-elevated: oklch(.2232 .0182 278.3);--__P__-fg: oklch(.9368 .01 278.3);--__P__-fg-muted: oklch(.7048 .0296 278.3);--__P__-border: oklch(.2752 .0266 278.3);--__P__-border-strong: oklch(.335 .0291 278.3);--__P__-hover: oklch(.9368 .01 278.3 / .08);--__P__-bubble-assistant: oklch(.2474 .0311 278.3);--__P__-bubble-assistant-text: var(--__P__-fg);--__P__-shadow-panel: 0 8px 24px -8px rgba(0, 0, 0, .6), 0 24px 64px -24px rgba(0, 0, 0, .7);color-scheme:dark}}@media(prefers-reduced-motion:reduce){:host{--__P__-dur-quick: 1ms;--__P__-dur-base: 1ms;--__P__-dur-slow: 1ms}}\n", "*,*:before,*:after{box-sizing:border-box;margin:0;padding:0;border:0}button{font:inherit;color:inherit;background:none;cursor:pointer;-webkit-appearance:none;appearance:none;line-height:1}button:focus-visible,[tabindex]:focus-visible,textarea:focus-visible,input:focus-visible{outline:2px solid var(--__P__-accent);outline-offset:2px}textarea,input{font:inherit;color:inherit;background:none;border:0;outline:0;resize:none}a{color:var(--__P__-accent);text-decoration:underline;text-underline-offset:2px}img,svg{display:block;max-width:100%}ul,ol{list-style:none}.__P__-app{display:block;width:100%;height:100%;font-family:var(--__P__-font);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-feature-settings:\"cv11\",\"ss01\",\"ss03\"}.__P__-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n", ".__P__-anchor{right:16px;bottom:16px}:host([data-position=\"bottom-left\"]) .__P__-anchor,:host([data-position=\"top-left\"]) .__P__-anchor{right:auto;left:16px}:host([data-position=\"top-right\"]) .__P__-anchor,:host([data-position=\"top-left\"]) .__P__-anchor{bottom:auto;top:16px}.__P__-anchor{position:fixed;display:flex;flex-direction:column;align-items:flex-end;gap:var(--__P__-space-3);pointer-events:none}.__P__-anchor{--__P__-fab-size: 56px}.__P__-anchor[data-launcher-size=sm]{--__P__-fab-size: 44px}.__P__-anchor[data-launcher-size=md]{--__P__-fab-size: 56px}.__P__-anchor[data-launcher-size=lg]{--__P__-fab-size: 68px}.__P__-anchor>*{pointer-events:auto}.__P__-fab[data-size=sm]{--__P__-fab-size: 44px;font-size:var(--__P__-text-sm)}.__P__-fab[data-size=md]{--__P__-fab-size: 56px;font-size:14px}.__P__-fab[data-size=lg]{--__P__-fab-size: 68px;font-size:var(--__P__-text-base)}.__P__-fab{display:inline-flex;align-items:center;justify-content:center;gap:var(--__P__-space-2);color:var(--__P__-accent-text);background:var(--__P__-accent);box-shadow:var(--__P__-shadow-fab);font-weight:600;line-height:1;transform-origin:bottom right;animation:__P__-fab-in var(--__P__-dur-base) var(--__P__-ease) both;transition:transform var(--__P__-dur-base) var(--__P__-ease),opacity var(--__P__-dur-base) var(--__P__-ease),box-shadow var(--__P__-dur-base) var(--__P__-ease)}.__P__-fab:hover{transform:translateY(-2px)}.__P__-fab:active{transform:translateY(0)}.__P__-fab:focus-visible{outline:2px solid var(--__P__-on-accent);outline-offset:3px}.__P__-fab svg{width:24px;height:24px;flex-shrink:0}@keyframes __P__-fab-in{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:none}}.__P__-fab[data-leaving]{position:absolute;right:0;bottom:0;animation:__P__-fab-out var(--__P__-dur-quick) var(--__P__-ease) forwards;pointer-events:none}:host([data-position^=\"top-\"]) .__P__-fab[data-leaving]{bottom:auto;top:0}:host([data-position$=\"-left\"]) .__P__-fab[data-leaving]{right:auto;left:0}@keyframes __P__-fab-out{0%{opacity:1;transform:none}to{opacity:0;transform:scale(.85)}}.__P__-fab[data-variant=circle]{width:var(--__P__-fab-size);height:var(--__P__-fab-size);border-radius:999px}.__P__-fab[data-variant=circle] .__P__-fab-label{display:none}.__P__-fab[data-variant=pill]{height:var(--__P__-fab-size);padding:0 18px 0 16px;border-radius:999px}.__P__-fab[data-variant=bar]{height:var(--__P__-fab-size);padding:0 22px;border-radius:var(--__P__-radius)}.__P__-fab[data-variant=minimal]{height:var(--__P__-fab-size);padding:0 var(--__P__-space-4);border-radius:999px;background:transparent;color:var(--__P__-accent);box-shadow:none;border:1px solid currentColor}.__P__-fab[data-variant=minimal]:hover{background:color-mix(in srgb,var(--__P__-accent) 12%,transparent)}.__P__-callout{--__P__-callout-fab-h: var(--__P__-fab-size, 56px);--__P__-callout-gap: clamp(12px, calc(var(--__P__-callout-fab-h) * .25), 22px);--__P__-callout-nudge-direction: -1;position:absolute;display:inline-flex;align-items:center;gap:var(--__P__-space-2);padding:10px 14px;background:var(--__P__-fg);color:var(--__P__-bg);border-radius:999px;font-size:var(--__P__-text-sm);font-weight:600;line-height:1.2;box-shadow:0 10px 30px -8px #00000059;pointer-events:auto;animation:__P__-callout-in var(--__P__-dur-slow) var(--__P__-ease);z-index:1;max-width:240px;white-space:nowrap}.__P__-callout[data-position=left]{right:calc(100% + var(--__P__-callout-gap));bottom:calc(var(--__P__-callout-fab-h) / 2);transform:translateY(50%)}.__P__-callout[data-position=right]{left:calc(100% + var(--__P__-callout-gap));bottom:calc(var(--__P__-callout-fab-h) / 2);transform:translateY(50%);--__P__-callout-nudge-direction: 1}.__P__-callout[data-position=top]{bottom:calc(100% + var(--__P__-callout-gap));right:0;max-width:min(280px,calc(100vw - 32px))}.__P__-callout[data-position=bottom]{top:calc(100% + var(--__P__-callout-gap));right:0;max-width:min(280px,calc(100vw - 32px))}:host([data-position$=\"-left\"]) .__P__-callout[data-position=top],:host([data-position$=\"-left\"]) .__P__-callout[data-position=bottom]{right:auto;left:0}.__P__-callout[data-shape=bubble]{border-radius:var(--__P__-radius-md);white-space:normal;width:max-content;max-width:min(280px,calc(100vw - 32px))}.__P__-callout[data-shape=callout]{padding:12px 18px;font-size:14px;background:var(--__P__-accent);color:var(--__P__-accent-text)}.__P__-callout:after{content:\"\";position:absolute;width:12px;height:12px;background:inherit;border-radius:2px;transform:rotate(45deg)}.__P__-callout[data-position=left]:after{right:-5px;top:50%;margin-top:-6px}.__P__-callout[data-position=right]:after{left:-5px;top:50%;margin-top:-6px}.__P__-callout[data-position=top]:after{bottom:-5px;right:calc(var(--__P__-fab-size, 56px) / 2 - 6px)}.__P__-callout[data-position=bottom]:after{top:-5px;right:calc(var(--__P__-fab-size, 56px) / 2 - 6px)}:host([data-position$=\"-left\"]) .__P__-callout[data-position=top]:after,:host([data-position$=\"-left\"]) .__P__-callout[data-position=bottom]:after{right:auto;left:calc(var(--__P__-fab-size, 56px) / 2 - 6px)}.__P__-callout[data-animated]{animation:__P__-callout-in var(--__P__-dur-slow) var(--__P__-ease),__P__-callout-nudge 1.6s var(--__P__-ease-in-out) var(--__P__-dur-slow) infinite}@keyframes __P__-callout-in{0%{opacity:0}to{opacity:1}}@keyframes __P__-callout-nudge{0%,to{margin-left:0;margin-right:0}50%{margin-left:calc(6px * var(--__P__-callout-nudge-direction));margin-right:calc(-6px * var(--__P__-callout-nudge-direction))}}@media(prefers-reduced-motion:reduce){.__P__-callout[data-animated]{animation:__P__-callout-in 1ms var(--__P__-ease)}.__P__-icon-btn[data-recording=true],.__P__-typing span{animation:none}}.__P__-callout-close{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:inherit;opacity:.7;cursor:pointer}.__P__-callout-close:hover{opacity:1;background:#ffffff26}.__P__-callout-close svg{width:12px;height:12px}.__P__-panel{width:var(--__P__-panel-w);height:var(--__P__-panel-h);max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);background:var(--__P__-bg);color:var(--__P__-fg);border-radius:var(--__P__-radius-lg);box-shadow:var(--__P__-shadow-panel);display:flex;flex-direction:column;overflow:hidden;transform-origin:bottom right;animation:__P__-panel-in var(--__P__-dur-slow) var(--__P__-ease);border:1px solid var(--__P__-border)}:host([data-mode=\"open\"]) .__P__-panel{width:var(--__P__-widget-w, var(--__P__-panel-w));height:var(--__P__-widget-h, var(--__P__-panel-h));min-width:var(--__P__-resize-min-w, auto);min-height:var(--__P__-resize-min-h, auto);max-width:var(--__P__-resize-max-w, calc(100vw - 32px) );max-height:var(--__P__-resize-max-h, calc(100vh - 32px) )}@keyframes __P__-panel-in{0%{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:none}}:host([data-mode=\"expanded\"]) .__P__-panel{width:var(--__P__-expanded-w, 640px);height:var(--__P__-expanded-h, 820px);max-width:calc(100vw - 32px);max-height:calc(100vh - 32px)}:host([data-mode=\"fullscreen\"]){z-index:var(--__P__-z-panel)!important}:host([data-mode=\"fullscreen\"]) .__P__-anchor{inset:0;align-items:stretch;padding:0}:host([data-mode=\"fullscreen\"]) .__P__-panel{width:100vw;height:100vh;max-width:none;max-height:none;border-radius:0;border:0}:host([data-mode=\"fullscreen\"]) .__P__-fab{display:none}:host([data-mode=\"inline\"]) .__P__-anchor,:host([data-mode=\"standalone\"]) .__P__-anchor{position:static;inset:auto;padding:0;align-items:stretch;width:100%;height:100%;min-height:0}:host([data-mode=\"inline\"]) .__P__-panel,:host([data-mode=\"standalone\"]) .__P__-panel{width:100%;height:100%;min-width:0;min-height:0;max-width:none;max-height:none;animation:none}:host([data-mode=\"inline\"]) .__P__-fab,:host([data-mode=\"standalone\"]) .__P__-fab{display:none}:host([data-mode=\"inline\"]){min-height:320px}:host([data-mode=\"inline\"]) .__P__-panel{border-radius:var(--__P__-radius)}:host([data-mode=\"standalone\"]) .__P__-panel{border-radius:0;border:0;box-shadow:none}:host([data-mode=\"modal\"]){position:fixed!important;inset:0!important;z-index:var(--__P__-z-panel)!important;display:block!important;width:100vw;height:100vh;background:var(--__P__-modal-backdrop, var(--__P__-backdrop));animation:__P__-backdrop-in var(--__P__-dur-base) var(--__P__-ease) both;--__P__-panel-w: min(960px, 92vw);--__P__-panel-h: min(720px, 88vh)}:host([data-mode=\"modal\"]) .__P__-anchor{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:var(--__P__-space-6);pointer-events:none}:host([data-mode=\"modal\"]) .__P__-panel{pointer-events:auto;width:var(--__P__-widget-w, var(--__P__-panel-w));height:var(--__P__-widget-h, var(--__P__-panel-h));max-width:calc(100vw - 48px);max-height:calc(100vh - 48px);min-width:0;min-height:0;border-radius:var(--__P__-radius);box-shadow:var(--__P__-shadow-panel);animation:__P__-modal-in var(--__P__-dur-base) var(--__P__-ease);translate:var(--__P__-modal-dx, 0px) var(--__P__-modal-dy, 0px)}:host([data-mode=\"modal\"]) .__P__-header,:host([data-mode=\"modal\"]) .__P__-back-header,:host([data-mode=\"modal\"]) .__P__-home-hero{cursor:grab;touch-action:none}:host([data-mode=\"modal\"]) .__P__-panel[data-dragging]{cursor:grabbing;user-select:none}:host([data-mode=\"modal\"]) .__P__-panel[data-dragging] *{cursor:grabbing}:host([data-mode=\"modal\"]) .__P__-fab{display:none}@keyframes __P__-backdrop-in{0%{background:#0000}to{background:var(--__P__-modal-backdrop, var(--__P__-backdrop))}}@keyframes __P__-modal-in{0%{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}:host([data-mode=\"drawer\"]){--__P__-panel-w: min(440px, calc(100vw - 32px) );--__P__-panel-h: 100vh}:host([data-mode=\"drawer\"]) .__P__-anchor{position:fixed;--__P__-inset-x: var(--__P__-panel-inset, 12px);--__P__-inset-y: var(--__P__-panel-inset, 3vh);top:var(--__P__-inset-y);bottom:auto;right:var(--__P__-inset-x);left:auto;width:min(var(--__P__-widget-w, var(--__P__-panel-w)),calc(100vw - var(--__P__-inset-x) * 2));height:min(var(--__P__-widget-h, var(--__P__-panel-h)),calc(100vh - var(--__P__-inset-y) * 2));padding:0;display:block;pointer-events:auto}:host([data-mode=\"drawer\"][data-position$=\"-left\"]) .__P__-anchor{right:auto;left:var(--__P__-inset-x)}:host([data-mode=\"drawer\"][data-position^=\"bottom-\"]) .__P__-anchor{top:auto;bottom:var(--__P__-inset-y)}:host([data-mode=\"drawer\"]) .__P__-panel{width:100%;height:100%;max-width:none;max-height:none;min-width:0;min-height:0;border-radius:var(--__P__-radius, 12px);border:1px solid var(--__P__-border);box-shadow:-8px 16px 32px -12px #00000038;animation:__P__-drawer-in var(--__P__-dur-base) var(--__P__-ease)}:host([data-mode=\"drawer\"][data-position$=\"-left\"]) .__P__-panel{box-shadow:8px 16px 32px -12px #00000038;animation:__P__-drawer-in-left var(--__P__-dur-base) var(--__P__-ease)}.__P__-fab[data-edge-tab]{display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--__P__-space-2);position:fixed;top:50%;height:140px;width:auto;min-width:0;padding:10px 8px;writing-mode:vertical-rl;text-orientation:mixed;transform:translateY(-50%);transform-origin:center;animation:__P__-edge-tab-in var(--__P__-dur-base) var(--__P__-ease) both}.__P__-fab[data-edge-tab] svg{writing-mode:horizontal-tb}.__P__-fab[data-edge-tab]:hover{transform:translateY(-50%) scale(1.03)}.__P__-fab[data-edge-tab]:active{transform:translateY(-50%) scale(.97)}:host([data-position$=\"-right\"]) .__P__-fab[data-edge-tab]{right:0;left:auto;border-radius:12px 0 0 12px;box-shadow:-6px 0 18px -8px #0000004d}:host([data-position$=\"-left\"]) .__P__-fab[data-edge-tab]{left:0;right:auto;border-radius:0 12px 12px 0;box-shadow:6px 0 18px -8px #0000004d}.__P__-fab[data-edge-tab][data-leaving]{position:fixed;top:50%;bottom:auto;animation:__P__-edge-tab-out var(--__P__-dur-quick) var(--__P__-ease) forwards}@keyframes __P__-edge-tab-in{0%{opacity:0;transform:translateY(-50%) scale(.9)}to{opacity:1;transform:translateY(-50%) scale(1)}}@keyframes __P__-edge-tab-out{0%{opacity:1;transform:translateY(-50%) scale(1)}to{opacity:0;transform:translateY(-50%) scale(.85)}}@keyframes __P__-drawer-in{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes __P__-drawer-in-left{0%{transform:translate(-100%)}to{transform:translate(0)}}@media(prefers-reduced-motion:reduce){:host([data-mode=\"drawer\"]) .__P__-panel,.__P__-fab[data-edge-tab]{animation:none}}.__P__-header{display:flex;align-items:center;gap:var(--__P__-space-2);padding:12px 14px;border-bottom:1px solid var(--__P__-border);background:var(--__P__-bg)}.__P__-header h1{font-size:14px;font-weight:600;flex:1;letter-spacing:-.01em}.__P__-agent{flex:1;display:inline-flex;align-items:center;gap:10px;min-width:0}.__P__-agent-avatar{position:relative;width:32px;height:32px;border-radius:999px;background:var(--__P__-bg-elevated);color:var(--__P__-fg-muted);display:inline-flex;align-items:center;justify-content:center;font-weight:700;font-size:var(--__P__-text-xs);overflow:hidden;flex-shrink:0}.__P__-agent-avatar img{width:100%;height:100%;object-fit:cover}.__P__-agent-avatar:after{content:\"\";position:absolute;right:-1px;bottom:-1px;width:10px;height:10px;border-radius:999px;border:2px solid var(--__P__-bg);background:var(--__P__-neutral)}.__P__-agent-avatar[data-status=online]:after{background:var(--__P__-success)}.__P__-agent-avatar[data-status=away]:after{background:var(--__P__-warning)}.__P__-agent-avatar[data-status=offline]:after{background:var(--__P__-neutral)}.__P__-agent-meta{display:flex;flex-direction:column;line-height:1.15;min-width:0}.__P__-agent-meta strong{font-size:14px;font-weight:600;letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-agent-meta span{font-size:var(--__P__-text-xs);color:var(--__P__-fg-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-suggestions{display:flex;flex-wrap:nowrap;gap:var(--__P__-space-2);padding:6px 14px 10px;overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;scroll-snap-type:x proximity;scrollbar-width:none;-ms-overflow-style:none;min-width:0}.__P__-suggestions::-webkit-scrollbar{display:none}.__P__-suggestions:before,.__P__-suggestions:after{content:\"\";flex:1 1 0;min-width:0}.__P__-suggestion{flex:0 0 auto;scroll-snap-align:center;padding:7px 14px;border-radius:999px;background:var(--__P__-bg-elevated);border:1px solid var(--__P__-border);color:var(--__P__-fg);font-size:var(--__P__-text-sm);font-weight:500;white-space:nowrap;transition:background var(--__P__-dur-quick) var(--__P__-ease),border-color var(--__P__-dur-quick) var(--__P__-ease),transform var(--__P__-dur-quick) var(--__P__-ease)}.__P__-suggestion:hover{border-color:var(--__P__-accent);color:var(--__P__-accent);transform:translateY(-1px)}.__P__-suggestion:focus-visible{outline:2px solid var(--__P__-accent);outline-offset:2px;border-color:var(--__P__-accent)}.__P__-suggestion:active{transform:translateY(0)}.__P__-icon-btn{width:32px;height:32px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;color:var(--__P__-fg-muted);transition:background var(--__P__-dur-quick) var(--__P__-ease),color var(--__P__-dur-quick) var(--__P__-ease)}.__P__-icon-btn:hover{background:var(--__P__-bg-elevated);color:var(--__P__-fg)}.__P__-icon-btn:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-icon-btn:active{background:var(--__P__-border)}.__P__-icon-btn:disabled{opacity:.45;cursor:not-allowed}.__P__-icon-btn:before{content:\"\";position:absolute;inset:-6px}.__P__-icon-btn{position:relative}.__P__-icon-btn svg{width:18px;height:18px}.__P__-icon-btn[data-recording=true]{color:var(--__P__-accent);background:color-mix(in srgb,var(--__P__-accent) 12%,transparent);animation:__P__-pulse 1.2s var(--__P__-ease) infinite}@keyframes __P__-pulse{0%,to{box-shadow:0 0 color-mix(in srgb,var(--__P__-accent) 40%,transparent)}50%{box-shadow:0 0 0 6px color-mix(in srgb,var(--__P__-accent) 0%,transparent)}}.__P__-list{flex:1;min-height:0;overflow-y:auto;padding:var(--__P__-space-4);display:flex;flex-direction:column;gap:14px;scroll-behavior:smooth;scrollbar-width:thin;scrollbar-color:var(--__P__-border-strong) transparent;scrollbar-gutter:stable}.__P__-list::-webkit-scrollbar{width:8px}.__P__-list::-webkit-scrollbar-thumb{background:var(--__P__-border-strong);border-radius:8px}.__P__-bubble-row{display:flex}.__P__-bubble-row[data-role=user]{justify-content:flex-end}.__P__-bubble-row[data-role=assistant]{justify-content:flex-start}.__P__-bubble{max-width:85%;padding:var(--__P__-space-3) var(--__P__-space-4);border-radius:var(--__P__-radius);line-height:1.6;font-size:14px;word-wrap:break-word;overflow-wrap:anywhere;box-shadow:0 1px 2px #0000000a,0 1px 8px -4px #0000000f;animation:__P__-bubble-in var(--__P__-dur-base) var(--__P__-ease)}.__P__-bubble ::selection{background:color-mix(in srgb,var(--__P__-accent) 30%,transparent)}@keyframes __P__-bubble-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.__P__-bubble-row[data-role=user] .__P__-bubble{background:var(--__P__-bubble-user);color:var(--__P__-bubble-user-text);border-bottom-right-radius:var(--__P__-radius-sm)}.__P__-bubble-row[data-role=assistant] .__P__-bubble{background:var(--__P__-bubble-assistant);color:var(--__P__-bubble-assistant-text);border-bottom-left-radius:var(--__P__-radius-sm)}.__P__-md>*:first-child{margin-top:0}.__P__-md>*:last-child{margin-bottom:0}.__P__-md p{margin:10px 0}.__P__-md h1,.__P__-md h2,.__P__-md h3,.__P__-md h4,.__P__-md h5,.__P__-md h6{margin:18px 0 8px;line-height:1.3;letter-spacing:-.01em;font-weight:700}.__P__-md h1{font-size:1.4em}.__P__-md h2{font-size:1.22em}.__P__-md h3{font-size:1.08em}.__P__-md h4,.__P__-md h5,.__P__-md h6{font-size:1em}.__P__-md>h1:first-child,.__P__-md>h2:first-child,.__P__-md>h3:first-child,.__P__-md>h4:first-child,.__P__-md>h5:first-child,.__P__-md>h6:first-child{margin-top:0}.__P__-md ul,.__P__-md ol{padding-left:1.5em;margin:10px 0}.__P__-md ul{list-style:disc}.__P__-md ol{list-style:decimal}.__P__-md li{margin:6px 0;padding-left:2px}.__P__-md li::marker{color:var(--__P__-fg-muted)}.__P__-md li>p{margin:6px 0}.__P__-md li>ul,.__P__-md li>ol{margin:6px 0}.__P__-md strong,.__P__-md b{font-weight:650;letter-spacing:-.005em}.__P__-md em,.__P__-md i{font-style:italic}.__P__-md code{font-family:var(--__P__-font-mono);font-size:.86em;padding:1px 6px;border-radius:5px;background:color-mix(in srgb,var(--__P__-accent) 10%,transparent);color:var(--__P__-fg);border:1px solid color-mix(in srgb,var(--__P__-accent) 16%,transparent)}.__P__-md pre{font-family:var(--__P__-font-mono);font-size:12.5px;padding:12px 14px;border-radius:var(--__P__-radius-sm);background:#7f7f7f1f;overflow-x:auto;margin:var(--__P__-space-3) 0;line-height:1.5}.__P__-md pre code{padding:0;background:none}.__P__-md a{color:inherit;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px}.__P__-md a:hover{text-decoration-thickness:2px}.__P__-md blockquote{margin:14px 0;padding:6px 14px;border-left:3px solid color-mix(in srgb,var(--__P__-accent) 50%,transparent);background:color-mix(in srgb,var(--__P__-accent) 5%,transparent);border-radius:0 var(--__P__-radius-sm) var(--__P__-radius-sm) 0;color:var(--__P__-fg-muted)}.__P__-md blockquote>:first-child{margin-top:0}.__P__-md blockquote>:last-child{margin-bottom:0}.__P__-md hr{border:0;height:1px;background:color-mix(in srgb,currentColor 18%,transparent);margin:var(--__P__-space-4) 0}.__P__-md table{border-collapse:collapse;margin:var(--__P__-space-3) 0;font-size:.95em;display:block;overflow-x:auto;max-width:100%;font-variant-numeric:tabular-nums}.__P__-md th,.__P__-md td{padding:6px 10px;border-bottom:1px solid color-mix(in srgb,currentColor 12%,transparent);text-align:left}.__P__-md th{font-weight:700;background:color-mix(in srgb,currentColor 6%,transparent)}.__P__-md h1+ul,.__P__-md h1+ol,.__P__-md h2+ul,.__P__-md h2+ol,.__P__-md h3+ul,.__P__-md h3+ol,.__P__-md h4+ul,.__P__-md h4+ol{margin-top:var(--__P__-space-1)}.__P__-loading{display:inline-flex;align-items:center;gap:var(--__P__-space-2);color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm)}.__P__-loading-spinner{width:14px;height:14px;border-radius:999px;border:2px solid currentColor;border-top-color:transparent;animation:__P__-spin .8s linear infinite}@keyframes __P__-spin{to{transform:rotate(1turn)}}.__P__-typing{display:inline-flex;gap:var(--__P__-space-1);padding:var(--__P__-space-1) 0}.__P__-typing span{width:6px;height:6px;border-radius:999px;background:currentColor;opacity:.4;animation:__P__-blink 1.2s var(--__P__-ease) infinite}.__P__-typing span:nth-child(2){animation-delay:.2s}.__P__-typing span:nth-child(3){animation-delay:.4s}@keyframes __P__-blink{0%,80%,to{opacity:.3;transform:translateY(0)}40%{opacity:1;transform:translateY(-2px)}}.__P__-reasoning{margin:var(--__P__-space-1) 0 var(--__P__-space-2);padding-left:var(--__P__-space-3);border-left:2px solid var(--__P__-border-strong)}.__P__-reasoning-summary{display:inline-flex;align-items:center;gap:var(--__P__-space-1);cursor:pointer;list-style:none;user-select:none;color:var(--__P__-fg-muted);font-size:var(--__P__-text-xs);font-weight:600}.__P__-reasoning-summary::-webkit-details-marker{display:none}.__P__-reasoning-summary:before{content:\"\";width:5px;height:5px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);opacity:.7;transition:transform var(--__P__-dur-quick) var(--__P__-ease)}.__P__-reasoning[open] .__P__-reasoning-summary:before{transform:rotate(45deg)}.__P__-reasoning[data-active=true] .__P__-reasoning-label{animation:__P__-reasoning-pulse 1.4s var(--__P__-ease) infinite}@keyframes __P__-reasoning-pulse{0%,to{opacity:.5}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.__P__-reasoning[data-active=true] .__P__-reasoning-label{animation:none}}.__P__-reasoning-body{margin-top:var(--__P__-space-1);color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm)}.__P__-reasoning-body>:first-child{margin-top:0}.__P__-reasoning-body>:last-child{margin-bottom:0}.__P__-tool-chip{display:inline-flex;align-items:center;gap:6px;padding:var(--__P__-space-1) var(--__P__-space-2);margin-top:6px;border-radius:999px;background:var(--__P__-bg-elevated);border:1px solid var(--__P__-border);color:var(--__P__-fg-muted);font-size:var(--__P__-text-xs)}.__P__-composer{border-top:1px solid var(--__P__-border);padding:10px 12px;background:var(--__P__-bg);display:flex;flex-direction:column;gap:var(--__P__-space-2)}.__P__-composer-row{display:flex;align-items:flex-end;gap:6px}.__P__-textarea{flex:1;height:40px;max-height:160px;padding:10px 12px;border-radius:var(--__P__-radius);background:var(--__P__-bg-elevated);border:1px solid transparent;font-size:14px;line-height:1.4;transition:height var(--__P__-dur-quick) var(--__P__-ease),border-color var(--__P__-dur-quick) var(--__P__-ease),box-shadow var(--__P__-dur-quick) var(--__P__-ease)}.__P__-textarea:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:1px;border-color:var(--__P__-focus)}.__P__-send{width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;transition:opacity var(--__P__-dur-quick) var(--__P__-ease),transform var(--__P__-dur-quick) var(--__P__-ease),background var(--__P__-dur-quick) var(--__P__-ease)}.__P__-send[data-shape=circle]{border-radius:999px}.__P__-send[data-shape=square]{border-radius:var(--__P__-radius-sm)}.__P__-send[data-shape=pill]{width:auto;padding:0 18px;border-radius:999px}.__P__-send[data-variant=filled]{background:var(--__P__-accent);color:var(--__P__-accent-text)}.__P__-send[data-variant=outline]{background:transparent;color:var(--__P__-accent);border-color:var(--__P__-accent)}.__P__-send[data-variant=outline]:not(:disabled):hover{background:color-mix(in srgb,var(--__P__-accent) 10%,transparent)}.__P__-send[data-variant=ghost]{background:transparent;color:var(--__P__-accent)}.__P__-send[data-variant=ghost]:not(:disabled):hover{background:color-mix(in srgb,var(--__P__-accent) 8%,transparent)}.__P__-send:disabled{opacity:.4;cursor:not-allowed}.__P__-send:not(:disabled):hover{transform:translateY(-1px)}.__P__-send:not(:disabled):active{transform:translateY(0)}.__P__-send:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-send svg{width:18px;height:18px}.__P__-composer-actions{display:flex;gap:var(--__P__-space-1);flex-wrap:wrap}.__P__-attachments{display:flex;flex-wrap:wrap;gap:6px}.__P__-attachment-chip{display:inline-flex;align-items:center;gap:6px;padding:var(--__P__-space-1) var(--__P__-space-2) var(--__P__-space-1) var(--__P__-space-1);border-radius:999px;background:var(--__P__-bg-elevated);border:1px solid var(--__P__-border);font-size:var(--__P__-text-xs);max-width:200px}.__P__-attachment-thumb{width:24px;height:24px;border-radius:999px;object-fit:cover;background:var(--__P__-border)}.__P__-attachment-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.__P__-attachment-remove{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;color:var(--__P__-fg-muted)}.__P__-attachment-remove:hover{background:var(--__P__-border);color:var(--__P__-fg)}.__P__-dropzone{position:absolute;inset:8px;border:2px dashed var(--__P__-accent);border-radius:var(--__P__-radius);background:color-mix(in srgb,var(--__P__-accent) 8%,transparent);display:flex;align-items:center;justify-content:center;font-weight:600;color:var(--__P__-accent);pointer-events:none;z-index:10;animation:__P__-fade-in var(--__P__-dur-quick) var(--__P__-ease)}@keyframes __P__-fade-in{0%{opacity:0}to{opacity:1}}.__P__-error{margin-top:var(--__P__-space-1);padding:8px 10px;border-radius:var(--__P__-radius-sm);background:var(--__P__-danger-bg);color:var(--__P__-danger-text);font-size:var(--__P__-text-sm);display:flex;align-items:center;gap:var(--__P__-space-2)}.__P__-error button{color:inherit;text-decoration:underline;font-size:var(--__P__-text-sm)}.__P__-history{flex:1;overflow-y:auto;padding:var(--__P__-space-2) var(--__P__-space-1) var(--__P__-space-3)}.__P__-history-footer{flex:none;padding:var(--__P__-space-2) var(--__P__-space-3) var(--__P__-space-3);border-top:1px solid var(--__P__-border);background:var(--__P__-surface)}.__P__-history-new{width:100%;display:flex;align-items:center;justify-content:center;gap:var(--__P__-space-2);padding:var(--__P__-space-2) var(--__P__-space-3);border:none;border-radius:var(--__P__-radius-md);background:var(--__P__-accent);color:var(--__P__-on-accent);cursor:pointer;font-size:var(--__P__-text-sm);font-weight:600;transition:filter var(--__P__-dur-quick) var(--__P__-ease-out),transform var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-history-new svg{width:16px;height:16px}.__P__-history-new:hover{filter:brightness(1.08)}.__P__-history-new:active{transform:translateY(1px)}.__P__-history-new:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-history-empty{flex:1;display:flex;align-items:center;justify-content:center;padding:40px 16px;color:var(--__P__-fg-muted);font-size:14px;text-align:center}.__P__-history-group{display:flex;flex-direction:column;padding:0 var(--__P__-space-2)}.__P__-history-heading{font-size:var(--__P__-text-xs);font-weight:600;color:var(--__P__-fg-muted);padding:12px 8px 6px;text-transform:uppercase;letter-spacing:.04em}.__P__-history-item{all:unset;display:flex;flex-direction:column;gap:3px;padding:10px 12px;border-radius:var(--__P__-radius-md);cursor:pointer;transition:background var(--__P__-dur-base) var(--__P__-ease)}.__P__-history-item:hover{background:var(--__P__-bg-elevated)}.__P__-history-item:focus-visible{background:var(--__P__-bg-elevated);outline:2px solid var(--__P__-focus);outline-offset:-2px}.__P__-history-title{font-size:14px;font-weight:500;color:var(--__P__-fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-history-preview{font-size:var(--__P__-text-xs);color:var(--__P__-fg-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-history-item[data-closed=true] .__P__-history-title:after{content:\"\\2022\";margin-left:var(--__P__-space-2);opacity:.5}.__P__-list-loading{margin:auto;padding:var(--__P__-space-6) var(--__P__-space-4);color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm);text-align:center}.__P__-readonly-banner{display:flex;flex-direction:column;align-items:center;gap:10px;padding:14px 12px;margin:0 var(--__P__-space-3) var(--__P__-space-3);border-radius:var(--__P__-radius-md);background:var(--__P__-bg-elevated);color:var(--__P__-fg-muted);text-align:center;font-size:var(--__P__-text-sm)}.__P__-readonly-label{line-height:1.4}.__P__-readonly-cta{appearance:none;border:0;cursor:pointer;padding:var(--__P__-space-2) var(--__P__-space-4);border-radius:999px;background:var(--__P__-accent);color:var(--__P__-accent-text, #fff);font:inherit;font-weight:600;font-size:var(--__P__-text-sm);transition:filter var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-readonly-cta:hover,.__P__-readonly-cta:focus-visible{filter:brightness(1.1)}.__P__-readonly-cta:focus-visible{outline:2px solid var(--__P__-accent);outline-offset:2px}.__P__-composer-footer{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:10px 16px;margin:0 var(--__P__-space-3);border-top:1px solid var(--__P__-border);text-align:center;font-size:11px;line-height:1.4;color:var(--__P__-fg-muted)}.__P__-disclaimer{max-width:320px;margin:0 auto;opacity:.9;letter-spacing:.01em}.__P__-poweredby{display:inline-flex;align-items:center;justify-content:center;gap:6px;color:inherit;text-decoration:none;opacity:.7;transition:opacity var(--__P__-dur-base) var(--__P__-ease);font-size:11px;letter-spacing:.02em}.__P__-poweredby:hover{opacity:1}.__P__-poweredby-logo{height:12px;width:auto;display:inline-block;vertical-align:middle}.__P__-poweredby-bar{flex:none;display:flex;align-items:center;justify-content:center;padding:6px 16px;border-top:1px solid var(--__P__-border);background:var(--__P__-bg-elevated)}.__P__-menu-wrap{position:relative;display:inline-flex}.__P__-menu{position:absolute;top:100%;right:0;margin-top:6px;min-width:200px;padding:6px;background:var(--__P__-bg);border:1px solid var(--__P__-border);border-radius:var(--__P__-radius-md);box-shadow:var(--__P__-shadow-panel);z-index:10;display:flex;flex-direction:column;gap:2px;animation:__P__-menu-in var(--__P__-dur-base) var(--__P__-ease)}@media(prefers-reduced-motion:reduce){.__P__-menu{animation:none}}@keyframes __P__-menu-in{0%{opacity:0;transform:translateY(-4px) scale(.98);transform-origin:top right}to{opacity:1;transform:none}}.__P__-menu-item{all:unset;display:flex;align-items:center;gap:10px;padding:8px 10px;font-size:var(--__P__-text-sm);color:var(--__P__-fg);border-radius:var(--__P__-radius-sm);cursor:pointer;user-select:none}.__P__-menu-item:hover{background:var(--__P__-bg-elevated)}.__P__-menu-item:focus-visible{background:var(--__P__-bg-elevated);outline:2px solid var(--__P__-focus);outline-offset:-2px}.__P__-menu-item[disabled]{opacity:.45;cursor:not-allowed}.__P__-menu-icon{display:inline-flex;width:16px;height:16px;color:var(--__P__-fg-muted)}.__P__-menu-icon svg{width:16px;height:16px}.__P__-menu-label{flex:1}.__P__-menu-check{display:inline-flex;color:var(--__P__-accent)}.__P__-menu-check svg{width:14px;height:14px}.__P__-resize-grip{position:absolute;width:18px;height:18px;display:flex;align-items:center;justify-content:center;color:var(--__P__-fg-muted);opacity:.45;transition:opacity var(--__P__-dur-base) var(--__P__-ease);z-index:2;touch-action:none;user-select:none}.__P__-resize-grip:hover,.__P__-resize-grip:focus-visible{opacity:1}.__P__-resize-grip svg{width:10px;height:10px}.__P__-resize-grip--bottom-left{bottom:2px;left:2px;cursor:nesw-resize;transform:scaleX(-1)}.__P__-resize-grip--bottom-right{bottom:2px;right:2px;cursor:nwse-resize}.__P__-resize-grip--top-left{top:2px;left:2px;cursor:nwse-resize;transform:rotate(180deg)}.__P__-resize-grip--top-right{top:2px;right:2px;cursor:nesw-resize;transform:scaleY(-1)}:host(:not([data-mode=\"open\"])) .__P__-resize-grip{display:none}.__P__-messenger{display:flex;flex-direction:column;overflow:hidden}.__P__-messenger-body{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.__P__-module{display:flex;flex-direction:column;height:100%;min-height:0}.__P__-module-scroll{flex:1;min-height:0;overflow-y:auto;padding:var(--__P__-space-3);display:flex;flex-direction:column;gap:var(--__P__-space-3)}.__P__-home{background:radial-gradient(125% 65% at 88% 0%,color-mix(in srgb,#fff 16%,transparent),transparent 55%),linear-gradient(180deg,var(--__P__-accent) 0%,var(--__P__-accent) 22%,color-mix(in srgb,var(--__P__-accent) 28%,var(--__P__-surface)) 44%,var(--__P__-surface) 70%);border-radius:var(--__P__-radius-lg) var(--__P__-radius-lg) 0 0}.__P__-home-scroll{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;background:transparent}.__P__-home-cards{display:flex;flex-direction:column;gap:var(--__P__-space-3);padding:4px 14px 16px}.__P__-module-pad{padding:var(--__P__-space-3) var(--__P__-space-3) 0}.__P__-module-empty{display:flex;flex-direction:column;align-items:center;gap:var(--__P__-space-3);padding:var(--__P__-space-8) var(--__P__-space-4);text-align:center;color:var(--__P__-fg-muted);font-size:var(--__P__-text-base)}.__P__-module-retry{padding:var(--__P__-space-2) var(--__P__-space-4);border:1px solid var(--__P__-border-strong);border-radius:var(--__P__-radius-sm);color:var(--__P__-fg);font-weight:600;cursor:pointer;transition:background var(--__P__-dur-quick) var(--__P__-ease-out),border-color var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-module-retry:hover{background:var(--__P__-hover);border-color:var(--__P__-accent)}.__P__-module-retry:active{background:var(--__P__-border)}.__P__-module-retry:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-help-list{flex:1;min-height:0;overflow-y:auto;padding-bottom:var(--__P__-space-3)}.__P__-help-group{display:flex;flex-direction:column}.__P__-help-section-title{position:sticky;top:0;z-index:1;margin:0;padding:var(--__P__-space-3) var(--__P__-space-5) var(--__P__-space-2);font-size:var(--__P__-text-xs);font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--__P__-fg-muted);background:var(--__P__-bg)}.__P__-help-card{margin:0 var(--__P__-space-3) var(--__P__-space-3);background:var(--__P__-surface);border:1px solid var(--__P__-border);border-radius:var(--__P__-radius);overflow:hidden}.__P__-help-card .__P__-list-row{border-bottom:0;border-radius:0}.__P__-help-card .__P__-list-row+.__P__-list-row{border-top:1px solid var(--__P__-border)}.__P__-help-card .__P__-list-row:hover{background:var(--__P__-hover)}.__P__-module-cta{padding:var(--__P__-space-3);border-top:1px solid var(--__P__-border)}.__P__-tabbar{display:flex;border-top:1px solid var(--__P__-border);background:var(--__P__-bg-elevated);flex-shrink:0}.__P__-tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--__P__-space-1);min-height:48px;padding:var(--__P__-space-2) var(--__P__-space-1);color:var(--__P__-fg-muted);font-size:var(--__P__-text-xs);font-weight:600;transition:color var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-tab:hover{color:var(--__P__-fg)}.__P__-tab[aria-selected=true]{color:var(--__P__-accent)}.__P__-tab:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:-2px}.__P__-tab:active{background:var(--__P__-hover)}.__P__-tab-icon{position:relative;display:inline-flex}.__P__-tab-icon svg{width:22px;height:22px}.__P__-tab-badge{position:absolute;top:-4px;left:calc(50% + 6px);min-width:16px;height:16px;padding:0 4px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--__P__-accent);color:var(--__P__-on-accent);font-size:10px;font-weight:600;line-height:1}.__P__-home-hero{flex-shrink:0;padding:var(--__P__-space-5) var(--__P__-space-5) var(--__P__-space-3);color:var(--__P__-on-accent);--__P__-focus: var(--__P__-on-accent)}.__P__-home-hero-top{display:flex;align-items:center;justify-content:space-between;gap:var(--__P__-space-3);min-height:32px}.__P__-home-brand{font-size:14px;font-weight:600;letter-spacing:.01em;color:color-mix(in srgb,#fff 82%,transparent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.__P__-home-brand-spacer{flex:1}.__P__-home-hero-actions{display:flex;align-items:center;gap:var(--__P__-space-2);flex-shrink:0}.__P__-home-close{color:#fff}.__P__-home-greeting{margin:28px 0 0;font-family:var(--__P__-font-display);font-size:var(--__P__-text-2xl);font-weight:800;line-height:1.18;letter-spacing:-.01em;overflow-wrap:anywhere}.__P__-home-lead{margin:2px 0 0;font-family:var(--__P__-font-display);font-size:var(--__P__-text-2xl);font-weight:800;line-height:1.18;letter-spacing:-.01em;color:color-mix(in srgb,var(--__P__-on-accent) 88%,transparent);overflow-wrap:anywhere}.__P__-home-avatars{display:flex}.__P__-home-avatar{width:30px;height:30px;border-radius:999px;overflow:hidden;margin-left:-10px;border:2px solid color-mix(in srgb,#fff 92%,transparent);box-shadow:0 1px 3px #0000002e;background:var(--__P__-surface);display:inline-flex;align-items:center;justify-content:center;font-size:var(--__P__-text-xs);font-weight:700;color:var(--__P__-fg)}.__P__-home-avatar:first-child{margin-left:0}.__P__-home-avatar img{width:100%;height:100%;object-fit:cover}.__P__-home-content{background:var(--__P__-surface);border:1px solid var(--__P__-border);border-radius:var(--__P__-radius);box-shadow:var(--__P__-shadow-card);overflow:hidden}.__P__-home-content-title{padding:var(--__P__-space-4) var(--__P__-space-4) var(--__P__-space-3);font-size:var(--__P__-text-sm);font-weight:700}.__P__-home-content-list{display:flex;flex-direction:column;padding:0 var(--__P__-space-2) var(--__P__-space-2)}.__P__-home-content-list .__P__-list-row+.__P__-list-row{border-top:1px solid var(--__P__-border)}.__P__-home-card{display:block;width:100%;text-align:left;background:var(--__P__-surface);border:1px solid var(--__P__-border);border-radius:var(--__P__-radius);padding:var(--__P__-space-4);box-shadow:var(--__P__-shadow-card)}.__P__-home-card[data-interactive=true]{cursor:pointer;transition:background var(--__P__-dur-quick) var(--__P__-ease-out),transform var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-home-card[data-interactive=true]:hover{background:var(--__P__-bg-elevated)}.__P__-home-card[data-interactive=true]:active{transform:translateY(1px)}.__P__-home-card[data-interactive=true]:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-home-recent-row{display:flex;align-items:center;gap:var(--__P__-space-3)}.__P__-home-recent-avatar{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border-radius:999px;background:color-mix(in oklch,var(--__P__-accent) 14%,transparent);color:var(--__P__-accent)}.__P__-home-recent-avatar svg{width:20px;height:20px}.__P__-home-recent-body{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}.__P__-home-recent-title{font-weight:600;font-size:14px}.__P__-home-recent-preview{color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.__P__-home-recent-at{color:var(--__P__-fg-muted);font-size:var(--__P__-text-xs);flex-shrink:0}.__P__-home-recent-dot{width:8px;height:8px;border-radius:999px;background:var(--__P__-accent);flex-shrink:0}.__P__-home-recent-row[data-unread=true] .__P__-home-recent-title{font-weight:700}.__P__-home-status{display:flex;align-items:center;gap:var(--__P__-space-3)}.__P__-home-status-icon svg{width:22px;height:22px;color:var(--__P__-success)}.__P__-home-status[data-level=degraded] .__P__-home-status-icon svg{color:var(--__P__-warning)}.__P__-home-status[data-level=down] .__P__-home-status-icon svg{color:var(--__P__-danger)}.__P__-home-status-text{font-weight:600;font-size:14px}.__P__-home-search{display:flex;align-items:center;gap:var(--__P__-space-2);width:100%;padding:var(--__P__-space-3) var(--__P__-space-4);border-radius:var(--__P__-radius);border:1px solid var(--__P__-border);background:var(--__P__-surface);text-align:left}.__P__-home-search[data-input=true]{background:var(--__P__-bg-elevated)}.__P__-home-search:hover{border-color:var(--__P__-border-strong)}.__P__-home-search:focus-visible,.__P__-home-search:focus-within{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-home-search-text{flex:1;color:var(--__P__-fg-muted);font-size:14px}.__P__-home-search-input{flex:1;border:0;background:transparent;font-size:14px;color:var(--__P__-fg);outline:none}.__P__-home-search-icon svg{width:18px;height:18px;color:var(--__P__-accent)}.__P__-list-row{display:flex;align-items:center;gap:var(--__P__-space-3);width:100%;min-height:44px;text-align:left;padding:var(--__P__-space-3) var(--__P__-space-2);border-bottom:1px solid var(--__P__-border);border-radius:var(--__P__-radius-sm)}.__P__-list-row:hover{background:var(--__P__-bg-elevated)}.__P__-list-row:active{background:var(--__P__-border)}.__P__-list-row:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:-2px}.__P__-list-row-body{display:flex;flex-direction:column;min-width:0;flex:1}.__P__-list-row-title{font-weight:600;font-size:var(--__P__-text-md);line-height:1.35}.__P__-list-row-sub{color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm);margin-top:var(--__P__-space-1);line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.__P__-list-row-chevron svg{width:18px;height:18px;color:var(--__P__-accent);flex-shrink:0}.__P__-back-header{display:flex;align-items:center;gap:var(--__P__-space-2);padding:10px 12px;border-bottom:1px solid var(--__P__-border);flex-shrink:0}.__P__-back-title{flex:1;text-align:center;font-size:var(--__P__-text-md);font-weight:700;margin:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-back-spacer{width:32px;height:1px}.__P__-content{display:flex;flex-direction:column;gap:14px;width:100%;max-width:68ch;margin:0 auto}.__P__-content-hero{width:100%;border-radius:var(--__P__-radius)}.__P__-content-subtitle{color:var(--__P__-fg-muted);margin:0}.__P__-content-frame{flex:1;min-height:0;width:100%;border:0;background:#fff}.__P__-news-list{display:flex;flex-direction:column;gap:var(--__P__-space-3)}.__P__-news-card{display:block;width:100%;text-align:left;border:1px solid var(--__P__-border);border-radius:var(--__P__-radius);overflow:hidden;background:var(--__P__-surface)}.__P__-news-card{transition:background var(--__P__-dur-quick) var(--__P__-ease-out),transform var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-news-card:hover{background:var(--__P__-bg-elevated)}.__P__-news-card:active{transform:translateY(1px)}.__P__-news-card:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-news-hero{width:100%;display:block}.__P__-news-body{display:flex;flex-direction:column;gap:6px;padding:14px 16px}.__P__-news-tags{display:flex;gap:6px;flex-wrap:wrap}.__P__-news-tag{font-size:var(--__P__-text-xs);font-weight:600;color:var(--__P__-accent);background:color-mix(in srgb,var(--__P__-accent) 12%,transparent);padding:2px 8px;border-radius:999px}.__P__-news-title{font-weight:700;font-size:var(--__P__-text-md)}.__P__-news-summary{color:var(--__P__-fg-muted);font-size:14px;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.__P__-modules-empty{display:flex;align-items:center;justify-content:center;position:relative}.__P__-modules-empty-close{position:absolute;top:12px;right:12px}.__P__-modules-empty-text{color:var(--__P__-fg-muted);font-size:var(--__P__-text-base)}\n", ".__P__-standalone-page{margin:0;height:100vh;background:var(--__P__-bg, #fff);display:grid;place-items:stretch}\n", ":host([data-mode=\"page\"]){min-height:0}:host([data-mode=\"page\"]) .__P__-page-shell{display:grid;grid-template-columns:280px 1fr;width:100%;height:100%;min-height:0;background:var(--__P__-bg);color:var(--__P__-fg);transition:grid-template-columns var(--__P__-dur-base) var(--__P__-ease-in-out)}:host([data-mode=\"page\"]) .__P__-page-shell[data-sidebar-collapsed=true]{grid-template-columns:48px 1fr}:host([data-mode=\"page\"]) .__P__-page-chat{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}:host([data-mode=\"page\"]) .__P__-anchor{position:static;inset:auto;padding:0;align-items:stretch;width:100%;height:100%;min-width:0;min-height:0;flex:1 1 0}:host([data-mode=\"page\"]) .__P__-panel{width:100%;height:100%;min-width:0;min-height:0;max-width:none;max-height:none;border-radius:0;border:0;box-shadow:none;animation:none}:host([data-mode=\"page\"]) .__P__-sidebar{border-right:1px solid var(--__P__-border);display:flex;flex-direction:column;overflow:hidden;background:var(--__P__-surface)}:host([data-mode=\"page\"]) .__P__-sidebar-header{padding:var(--__P__-space-4) var(--__P__-space-5);border-bottom:1px solid var(--__P__-border);display:flex;align-items:center;justify-content:space-between;gap:var(--__P__-space-2);min-height:56px;box-sizing:border-box}:host([data-mode=\"page\"]) .__P__-sidebar[data-collapsed=true] .__P__-sidebar-header{padding:var(--__P__-space-4) var(--__P__-space-2);justify-content:center}:host([data-mode=\"page\"]) .__P__-sidebar[data-collapsed=true] .__P__-sidebar-logo,:host([data-mode=\"page\"]) .__P__-sidebar[data-collapsed=true] .__P__-sidebar-title{display:none}:host([data-mode=\"page\"]) .__P__-sidebar-toggle{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid var(--__P__-border);border-radius:6px;background:transparent;color:var(--__P__-fg);cursor:pointer;transition:background .12s,border-color .12s}:host([data-mode=\"page\"]) .__P__-sidebar-toggle:hover{background:var(--__P__-hover);border-color:var(--__P__-accent)}:host([data-mode=\"page\"]) .__P__-sidebar-toggle:focus-visible{outline:2px solid var(--__P__-accent);outline-offset:2px}:host([data-mode=\"page\"]) .__P__-sidebar-logo{height:32px;display:block}:host([data-mode=\"page\"]) .__P__-sidebar-title{font-size:var(--__P__-text-md);font-weight:600}:host([data-mode=\"page\"]) .__P__-sidebar-section{padding:var(--__P__-space-3) var(--__P__-space-4);border-bottom:1px solid var(--__P__-border)}:host([data-mode=\"page\"]) .__P__-sidebar-nav{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}:host([data-mode=\"page\"]) .__P__-sidebar-nav-item{display:flex;align-items:center;gap:10px;padding:var(--__P__-space-2) var(--__P__-space-3);border-radius:var(--__P__-radius-sm);color:var(--__P__-fg);text-decoration:none;font-size:14px;transition:background var(--__P__-dur-quick) var(--__P__-ease-out)}:host([data-mode=\"page\"]) .__P__-sidebar-nav-item:hover{background:var(--__P__-hover)}:host([data-mode=\"page\"]) .__P__-sidebar-nav-item:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:-2px}:host([data-mode=\"page\"]) .__P__-sidebar-cards{display:grid;grid-template-columns:1fr;gap:var(--__P__-space-2)}:host([data-mode=\"page\"]) .__P__-sidebar-card{display:block;padding:10px 12px;border:1px solid var(--__P__-border);border-radius:var(--__P__-radius-sm);text-decoration:none;color:var(--__P__-fg);transition:border-color var(--__P__-dur-quick) var(--__P__-ease-out)}:host([data-mode=\"page\"]) .__P__-sidebar-card:hover{border-color:var(--__P__-accent)}:host([data-mode=\"page\"]) .__P__-sidebar-card:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}:host([data-mode=\"page\"]) .__P__-sidebar-card-label{font-weight:600;font-size:14px}:host([data-mode=\"page\"]) .__P__-sidebar-card-desc{font-size:var(--__P__-text-xs);color:var(--__P__-muted);margin-top:2px}:host([data-mode=\"page\"]) .__P__-sidebar-conversations{flex:1 1 auto;overflow-y:auto;padding:var(--__P__-space-3) var(--__P__-space-4)}:host([data-mode=\"page\"]) .__P__-page-chat{display:flex;flex-direction:column;overflow:hidden}@media(max-width:768px){:host([data-mode=\"page\"]) .__P__-page-shell{grid-template-columns:1fr}:host([data-mode=\"page\"]) .__P__-sidebar{display:none}}\n", "/**\n * Concatenate every `.css` file in this folder into a single string that gets\n * adopted by the shadow root at mount time.\n *\n * Import convention: `./name.css?raw`.\n * - Vite serves `?raw` natively (default string export).\n * - esbuild (prod) strips the `?raw` suffix and loads the file with the\n * `text` loader (see `scripts/build-brand.mjs`).\n * One import style works for both pipelines.\n */\n\nimport tokens from \"./tokens.css?raw\";\nimport reset from \"./reset.css?raw\";\nimport panel from \"./panel.css?raw\";\nimport standalone from \"./standalone.css?raw\";\nimport page from \"./page.css?raw\";\n\n/**\n * Brand constants substituted at build time from `brands/<id>.json`\n * (`__CSS_PREFIX__` + `defaultTheme.*`). `brands/*.json` is the single source\n * of truth for the brand prefix and default theme; these placeholders are\n * injected into the CSS text below. Fallbacks mirror the shipped `help-ai`\n * brand for tooling contexts where defines aren't present.\n */\ndeclare const __CSS_PREFIX__: string;\ndeclare const __BRAND_ACCENT__: string;\ndeclare const __BRAND_ACCENT_TEXT__: string;\ndeclare const __BRAND_RADIUS__: string;\ndeclare const __BRAND_FONT__: string;\nconst PREFIX = typeof __CSS_PREFIX__ !== \"undefined\" ? __CSS_PREFIX__ : \"helpai\";\nconst ACCENT = typeof __BRAND_ACCENT__ !== \"undefined\" ? __BRAND_ACCENT__ : \"#5B5BD6\";\nconst ACCENT_TEXT = typeof __BRAND_ACCENT_TEXT__ !== \"undefined\" ? __BRAND_ACCENT_TEXT__ : \"#FFFFFF\";\nconst RADIUS = typeof __BRAND_RADIUS__ !== \"undefined\" ? __BRAND_RADIUS__ : \"12px\";\nconst FONT =\n typeof __BRAND_FONT__ !== \"undefined\" ? __BRAND_FONT__ : \"Inter, system-ui, -apple-system, 'Segoe UI', sans-serif\";\n\n/**\n * Full shadow-root stylesheet: every `__P__` swapped for the brand prefix and\n * each `__BRAND_*__` theme placeholder swapped for the brand JSON value.\n */\nexport const CSS_TEXT = [tokens, reset, panel, standalone, page]\n .join(\"\\n\")\n .replace(/__P__/g, PREFIX)\n .replaceAll(\"__BRAND_ACCENT_TEXT__\", ACCENT_TEXT)\n .replaceAll(\"__BRAND_ACCENT__\", ACCENT)\n .replaceAll(\"__BRAND_RADIUS__\", RADIUS)\n .replaceAll(\"__BRAND_FONT__\", FONT);\n", "// Create the shadow host, attach an open shadow root, and adopt our single\n// Constructable stylesheet. CSS_TEXT is injected by the build pipeline as a\n// String export from src/styles/index.ts (esbuild .css text loader).\n\nimport { BRAND } from \"@/core/brand\";\nimport { logger } from \"@/core/logger\";\nimport { CSS_TEXT } from \"@/styles\";\n\nconst log = logger.scope(\"mount\");\n\n/**\n * One constructed stylesheet per document, cached. Two widgets in the\n * *same* document share a single sheet (cheap). Two widgets in *different*\n * documents \u2014 i.e., iframe mode inside a page that also has a top-level\n * widget \u2014 each get their own, because a `CSSStyleSheet` constructed in\n * one document can't be `adoptedStyleSheets`-attached in another\n * (browsers throw `NotAllowedError`).\n */\nconst sheetsByDocument = new WeakMap<Document, CSSStyleSheet>();\n\nexport interface ShadowMountResult {\n hostElement: HTMLElement;\n shadowRoot: ShadowRoot;\n /** Mount point for the Preact tree. */\n appRoot: HTMLDivElement;\n destroy: () => void;\n}\n\nexport interface MountOptions {\n /** Where to insert the host element. Defaults to `document.body`. */\n target?: HTMLElement | null;\n /**\n * Inline-style variant for the host element. See `HOST_CSS` below\n * for the three options. Defaults to `\"flow\"`.\n * \u2022 `\"floating\"` \u2014 overlay (floating FAB, modal, drawer)\n * \u2022 `\"flow\"` \u2014 fills parent (inline mode)\n * \u2022 `\"viewport\"` \u2014 claims viewport (standalone / page modes)\n */\n position?: HostPosition;\n /** When provided, attach to the existing element instead of creating a new host. */\n existingHost?: HTMLElement;\n}\n\n/** Per-mode host-element styles. Kept as inline CSS so host-page rules can't override.\n *\n * Three variants:\n *\n * - **`floating`** \u2014 `position: fixed` overlay. Floating FAB,\n * modal, drawer. Per-mode CSS in `panel.css` positions the anchor +\n * panel inside the fixed host (e.g. bottom-right for the FAB).\n *\n * - **`flow`** \u2014 `position: relative; width/height: 100%` so the host\n * fills its parent box. Used by `inline` mode. Adds `contain: size`\n * so shadow-DOM intrinsic sizes (agent badge, suggestions, textarea)\n * don't bubble out into the embedding page's grid/flex tracks.\n *\n * - **`viewport`** \u2014 `position: absolute; inset: 0; width: 100%;\n * height: 100%`. Used by `standalone` and `page` modes. The widget\n * fills its nearest positioned ancestor, falling back to the\n * initial containing block (the viewport) when none exists.\n * Defensive against host pages that drop `<web-ai-chat>` into a\n * small or flow-only container \u2014 when the host page doesn't\n * position anything, the widget claims the viewport. Host pages\n * that DO want to bound the widget (e.g. a preview frame in a\n * builder UI) wrap it in `position: relative` and the absolute\n * positioning resolves against that ancestor instead. Lives in the\n * INLINE style (not shadow CSS) because the `all: initial` reset\n * would otherwise clobber any external `:host` rule attempting to\n * set width/height/inset.\n *\n * Why every variant starts with `all: initial`: the host element\n * inherits the embedding page's font / color / line-height / etc.\n * Resetting at the host boundary keeps the shadow stylesheet\n * deterministic across host pages. The cascade caveat above (no\n * external `:host` rule for size-related properties) is the cost.\n */\nconst HOST_CSS = {\n floating: \"all: initial; position: fixed; z-index: 2147483600;\",\n flow: \"all: initial; display: block; position: relative; width: 100%; height: 100%; z-index: 1; contain: size;\",\n viewport: \"all: initial; display: block; position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1;\",\n} as const;\n\nexport type HostPosition = keyof typeof HOST_CSS;\n\n/**\n * Re-apply the host element's positioning style.\n *\n * `createShadowMount` sets this once on first mount; this helper lets\n * the entry points (`index.ts`, `web-component.ts`) refresh it after\n * a server-pushed mode change merges new options. Without it, the\n * mount-time choice (typically `\"floating\"` from the resolver default)\n * sticks even when the server later pushes `mode: \"standalone\"`.\n *\n * Idempotent \u2014 writing the same string twice is a no-op.\n */\nexport function applyHostPosition(host: HTMLElement, position: HostPosition): void {\n host.style.cssText = HOST_CSS[position];\n}\n\n/**\n * Map a {@link Mode} to its host-positioning strategy. Centralised so\n * the `mount()` and web-component entry points pick the same variant.\n */\nexport function hostPositionForMode(mode: import(\"@/core/config\").Mode): HostPosition {\n if (mode === \"floating\" || mode === \"modal\" || mode === \"drawer\") return \"floating\";\n if (mode === \"standalone\" || mode === \"page\") return \"viewport\";\n return \"flow\";\n}\n\nexport function createShadowMount(opts: MountOptions = {}): ShadowMountResult {\n const position: HostPosition = opts.position ?? \"flow\";\n log.debug(\"createShadowMount\", { position, hasTarget: !!opts.target, hasHost: !!opts.existingHost });\n const tagName = `${BRAND.cssPrefix}-chat-host`;\n // Define a no-op custom element so the tag participates in the CE registry,\n // Letting frameworks treat it like any HTML element. Idempotent.\n if (typeof customElements !== \"undefined\" && !customElements.get(tagName)) {\n customElements.define(tagName, class extends HTMLElement {});\n }\n\n let host: HTMLElement;\n if (opts.existingHost) {\n host = opts.existingHost;\n } else {\n host = document.createElement(tagName) as HTMLElement;\n (opts.target ?? document.body).appendChild(host);\n }\n host.setAttribute(\"data-brand\", BRAND.id);\n host.style.cssText = HOST_CSS[position];\n\n // Use existing shadow root if already attached (re-init scenario).\n const shadow = host.shadowRoot ?? host.attachShadow({ mode: \"open\" });\n\n // Style the shadow root. Use `adoptedStyleSheets` when available (cheap,\n // shared across instances in the same document). Construct the sheet\n // using the *host's* own document \u2014 in iframe mode that's the iframe's\n // document, not the top-level page's. Cross-document adoption throws\n // `NotAllowedError`, which is why this must be per-document rather than\n // a single module-scope singleton.\n const hostDoc = host.ownerDocument;\n if (!attachAdoptedSheet(shadow, hostDoc)) {\n // Either the browser lacks constructable stylesheets or the adoption\n // itself failed \u2014 fall back to an inline `<style>` inside the shadow.\n const styleEl = hostDoc.createElement(\"style\");\n styleEl.textContent = CSS_TEXT;\n shadow.appendChild(styleEl);\n }\n\n const appRoot = document.createElement(\"div\");\n appRoot.className = `${BRAND.cssPrefix}-app`;\n shadow.appendChild(appRoot);\n\n return {\n appRoot,\n destroy: () => host.remove(),\n hostElement: host,\n shadowRoot: shadow,\n };\n}\n\n/**\n * Attach a constructed, cached stylesheet to a shadow root. Returns `true`\n * when adopted successfully; `false` when the runtime lacks constructable\n * stylesheets or the adoption threw \u2014 caller should fall back to `<style>`.\n *\n * Per-document caching guarantees the sheet and the shadow root share a\n * realm (iframe mode requires this \u2014 sheets from one document can't be\n * adopted by shadow roots in another).\n */\nfunction attachAdoptedSheet(shadow: ShadowRoot, doc: Document): boolean {\n if (!(\"adoptedStyleSheets\" in shadow)) return false;\n const win = doc.defaultView;\n if (!win || typeof win.CSSStyleSheet !== \"function\") return false;\n\n let sheet = sheetsByDocument.get(doc);\n if (!sheet) {\n try {\n sheet = new win.CSSStyleSheet();\n sheet.replaceSync(CSS_TEXT);\n sheetsByDocument.set(doc, sheet);\n } catch {\n return false;\n }\n }\n try {\n shadow.adoptedStyleSheets = [sheet];\n return true;\n } catch {\n return false;\n }\n}\n\n/** Apply theme overrides as inline CSS custom properties on the host. */\nfunction applyThemeOverrides(\n host: HTMLElement,\n overrides: { accent?: string; accentText?: string; radius?: string; fontFamily?: string },\n): void {\n const p = BRAND.cssPrefix;\n if (overrides.accent) host.style.setProperty(`--${p}-accent`, overrides.accent);\n if (overrides.accentText) host.style.setProperty(`--${p}-accent-text`, overrides.accentText);\n if (overrides.radius) host.style.setProperty(`--${p}-radius`, overrides.radius);\n if (overrides.fontFamily) host.style.setProperty(`--${p}-font`, overrides.fontFamily);\n}\n\n/** Set color scheme on the host. 'auto' lets prefers-color-scheme decide via CSS. */\nfunction applyThemeMode(host: HTMLElement, mode: \"auto\" | \"light\" | \"dark\"): void {\n host.dataset.theme = mode;\n}\n\n/**\n * Apply sizing overrides as CSS custom properties on the host.\n *\n * The properties cascade into the shadow root where the panel reads them\n * via `var(--__P__-panel-w)` etc. This lets inline + standalone modes\n * ignore them entirely (their higher-specificity CSS rules set `width:\n * 100%` / `height: 100%` outright) while floating + iframe modes honour\n * them. Previously these were written as *inline styles* on the host,\n * which clobbered the `width: 100%` rule in inline mode \u2014 sizing the\n * widget from config would shrink it below its container.\n *\n * Passing `undefined` for a field leaves any existing custom property\n * alone \u2014 useful for `update()` calls that only tweak one dimension.\n */\nfunction applySize(\n host: HTMLElement,\n size: {\n height?: string;\n width?: string;\n expanded?: { width?: string; height?: string };\n /** Drawer-mode edge inset (gap from viewport edges). */\n inset?: string;\n },\n): void {\n const p = BRAND.cssPrefix;\n // Normal-mode default panel dimensions \u2014 consumed by `.__P__-panel`'s\n // base `width/height: var(--__P__-panel-w/h)` rule in floating mode.\n if (size.width !== undefined) host.style.setProperty(`--${p}-panel-w`, size.width);\n if (size.height !== undefined) host.style.setProperty(`--${p}-panel-h`, size.height);\n // Expanded-mode dimensions \u2014 consumed by the `[data-mode=\"expanded\"]` rule.\n if (size.expanded?.width !== undefined) host.style.setProperty(`--${p}-expanded-w`, size.expanded.width);\n if (size.expanded?.height !== undefined) host.style.setProperty(`--${p}-expanded-h`, size.expanded.height);\n // Drawer-mode edge inset \u2014 turns the full-height dock into a flyout\n // with the given gap from each viewport edge.\n if (size.inset !== undefined) host.style.setProperty(`--${p}-panel-inset`, size.inset);\n}\n\n/** Set position class for the launcher / panel anchor. */\nfunction applyPosition(host: HTMLElement, pos: string): void {\n host.dataset.position = pos;\n}\n\n/** Set the panel display mode (collapsed / expanded / fullscreen / standalone). */\nexport function applyMode(host: HTMLElement, mode: string): void {\n host.dataset.mode = mode;\n}\n\n/**\n * Apply every host-level data attribute + inline CSS var derived from the\n * resolved options in one call. Callers (`index.ts`, `web-component.ts`)\n * used to repeat these five lines; keep the fan-out here.\n */\nexport function applyHostAttributes(\n host: HTMLElement,\n resolved: {\n themeMode: \"auto\" | \"light\" | \"dark\";\n themeOverrides: { accent?: string; accentText?: string; radius?: string; fontFamily?: string };\n position: string;\n size: {\n width?: string;\n height?: string;\n expanded?: { width?: string; height?: string };\n inset?: string;\n };\n },\n): void {\n applyThemeMode(host, resolved.themeMode);\n applyThemeOverrides(host, resolved.themeOverrides);\n applyPosition(host, resolved.position);\n applySize(host, resolved.size);\n // NOTE: `data-mode` is deliberately NOT set here. The App component owns\n // it via `computeHostMode(mode, panelSize, isOpen)` in its mount-time\n // effect \u2014 setting the raw mode string here would transiently land\n // something like `data-mode=\"iframe\"` (which `SIZE_BY_MODE` in\n // `iframe-mount` doesn't recognise, falling back to \"open\" dimensions)\n // and cause a visible flicker until App's effect corrected it. Callers\n // that need an initial value before App mounts (see `index.ts`) call\n // `applyMode` explicitly with the pre-computed host mode.\n}\n", "/**\n * Derives the `data-mode` attribute value from App state.\n *\n * Kept as a pure helper (no DOM, no React) so tests can pin the critical\n * invariants cheaply:\n *\n * - `isOpen === false` \u2192 `\"closed\"` regardless of `panelSize`.\n * This is the reason closing from fullscreen doesn't hide the launcher:\n * the `[data-mode=\"fullscreen\"] .__P__-fab { display: none }` rule never\n * matches when the widget is closed.\n * - `panelSize` is preserved across close \u2192 open, so reopening the widget\n * restores the user's last layout (normal / expanded / fullscreen).\n * - Inline + standalone are terminal modes: they override any panelSize\n * because the widget isn't a popover and can't be \"closed\".\n */\n\nimport type { Mode, SizeOptions } from \"@/core/config\";\n\nexport type PanelSize = \"normal\" | \"expanded\" | \"fullscreen\";\n\nexport type HostMode =\n | \"page\"\n | \"standalone\"\n | \"inline\"\n | \"modal\"\n | \"drawer\"\n | \"fullscreen\"\n | \"expanded\"\n | \"open\"\n | \"closed\";\n\nexport function computeHostMode(configured: Mode, panelSize: PanelSize, isOpen: boolean): HostMode {\n if (configured === \"page\") return \"page\";\n if (configured === \"standalone\") return \"standalone\";\n // `inline` normally sits in-flow inside its container, but the user\n // can still escape to full-viewport via the fullscreen header button.\n // `expanded` has no meaningful mid-tier in inline (the panel already\n // fills its container) \u2014 the state is retained but produces no visual\n // change until the user hits fullscreen or the widget is re-mounted.\n if (configured === \"inline\") return panelSize === \"fullscreen\" ? \"fullscreen\" : \"inline\";\n // Modal + drawer share the same closed / fullscreen / mode-name fallback.\n if (configured === \"modal\" || configured === \"drawer\") {\n if (!isOpen) return \"closed\";\n return panelSize === \"fullscreen\" ? \"fullscreen\" : configured;\n }\n if (!isOpen) return \"closed\";\n if (panelSize === \"fullscreen\") return \"fullscreen\";\n if (panelSize === \"expanded\") return \"expanded\";\n return \"open\";\n}\n\n/** Default breakpoint (px) for `initialSize: \"auto\"` promotion to `expanded`. */\nconst DEFAULT_AUTO_BREAKPOINT = 900;\n\n/**\n * Resolve the panel size for the very first render.\n *\n * Precedence (highest to lowest):\n * 1. `mode: \"standalone\"` \u2192 always `\"fullscreen\"` (the dedicated tab).\n * 2. `size.initialSize` literal (\"normal\" / \"expanded\" / \"fullscreen\").\n * 3. `size.initialSize: \"auto\"` \u2192 `\"expanded\"` when viewport width \u2265 the\n * configured breakpoint (default 900 px), else `\"normal\"`.\n * 4. Fallback \u2192 `\"normal\"`.\n *\n * Pure: pass `viewportWidth` in so tests don't need `window`.\n */\nexport function pickInitialPanelSize(mode: Mode, size: SizeOptions, viewportWidth: number): PanelSize {\n if (mode === \"standalone\" || mode === \"page\") return \"fullscreen\";\n const chosen = size.initialSize;\n if (chosen === \"expanded\" || chosen === \"fullscreen\" || chosen === \"normal\") return chosen;\n if (chosen === \"auto\") {\n const bp = size.autoSizeBreakpoint ?? DEFAULT_AUTO_BREAKPOINT;\n return viewportWidth >= bp ? \"expanded\" : \"normal\";\n }\n return \"normal\";\n}\n\n/**\n * The two pieces of first-render panel state the App + the mount-time\n * host-mode resolver both need:\n *\n * - `panelOpen` \u2014 whether the floating panel starts expanded. For inline /\n * standalone this is always `true` (the panel is part of the page flow\n * or owns the whole viewport). For floating / iframe, the user's\n * persisted pick (last open/close) wins over the page-owner's\n * `startMinimized` default, so closing the panel and refreshing doesn't\n * auto-reopen it.\n * - `panelSize` \u2014 the `normal`/`expanded`/`fullscreen` tier at first\n * render (see `pickInitialPanelSize`).\n *\n * Factored out of App + `resolveInitialHostMode` in `src/index.ts` to\n * prevent drift: these two callers MUST pick the same first frame or the\n * iframe physically resizes between mount and App's first effect (the\n * \"closed \u2192 open \u2192 closed\" flicker this pins against).\n */\nexport interface InitialPanelState {\n panelOpen: boolean;\n panelSize: PanelSize;\n}\n\nexport function resolveInitialPanelState(\n options: { mode: Mode; size: SizeOptions; startMinimized: boolean },\n viewportWidth: number,\n persistedPanelOpen: boolean | null,\n persistedPanelSize: PanelSize | null = null,\n): InitialPanelState {\n const isInlineLike = options.mode === \"inline\" || options.mode === \"standalone\" || options.mode === \"page\";\n const panelOpen = isInlineLike || (persistedPanelOpen ?? !options.startMinimized);\n // The user's last size (expand / fullscreen) wins over the page-owner's\n // `initialSize`, per-device \u2014 so making the panel bigger and refreshing keeps\n // it bigger. Skip the always-fullscreen / container-bound modes, where size\n // isn't a free user choice.\n const honorsPersisted = options.mode !== \"standalone\" && options.mode !== \"page\" && options.mode !== \"inline\";\n const panelSize =\n honorsPersisted && persistedPanelSize\n ? persistedPanelSize\n : pickInitialPanelSize(options.mode, options.size, viewportWidth);\n return { panelOpen, panelSize };\n}\n\n/**\n * Read the current viewport width, gracefully falling back to `0` outside\n * a browser (SSR, unit tests with no global `window`). Four callers used\n * to inline this \u2014 kept here so the `typeof window` guard lives in one\n * place and `pickInitialPanelSize` / `resolveInitialPanelState` stay\n * testable with arbitrary widths.\n */\nexport function currentViewportWidth(): number {\n return typeof window === \"undefined\" ? 0 : window.innerWidth;\n}\n", "/**\n * Auth header builder. Priority mirrors the backend (`module-help-ai-agent-api`):\n * public key (header) > guest (no header at all).\n *\n * The widget runs on third-party sites and does **not** authenticate end users\n * \u2014 there's no per-user token. Requests are authorised by the workspace\n * `publicKey`; the guest fallback is deliberate, since the backend supports\n * unauthenticated sessions for public-facing widgets (rate-limited per IP).\n */\n\nexport interface AuthOptions {\n /** Workspace-scoped public key. Sent as the public-key header when present. */\n publicKey?: string;\n /**\n * Deployment ID (UUID) of the AI agent deployment. When set, sent as\n * `x-ai-agent-deployment-id` header on every request so the backend can\n * disambiguate when one publicKey serves multiple deployments.\n */\n aiAgentDeploymentId?: string;\n /** Override the public-key header name (default: `x-public-key`). */\n publicKeyHeader?: string;\n}\n\n/**\n * Returned by {@link createAuth}. Keep an instance per widget session.\n */\nexport interface AuthHeaderProvider {\n /** Build a fresh `Record<string, string>` of headers for this request. */\n headers: () => Record<string, string>;\n}\n\nconst DEFAULT_PUBLIC_KEY_HEADER = \"x-public-key\";\nconst DEPLOYMENT_ID_HEADER = \"x-ai-agent-deployment-id\";\n\n/**\n * Build an {@link AuthHeaderProvider} from the widget's `AuthOptions`.\n *\n * @example public key\n * ```ts\n * createAuth({ publicKey: 'agent_pk_...' }).headers();\n * // { 'x-public-key': 'agent_pk_...' }\n * ```\n *\n * @example guest (no public key)\n * ```ts\n * createAuth({}).headers();\n * // {}\n * ```\n */\nexport function createAuth(opts: AuthOptions): AuthHeaderProvider {\n return {\n headers() {\n const headers: Record<string, string> = {};\n if (opts.aiAgentDeploymentId) headers[DEPLOYMENT_ID_HEADER] = opts.aiAgentDeploymentId;\n if (opts.publicKey) headers[opts.publicKeyHeader ?? DEFAULT_PUBLIC_KEY_HEADER] = opts.publicKey;\n return headers;\n },\n };\n}\n", "/**\n * UUIDv7 \u2014 RFC 9562 time-ordered identifier.\n *\n * Layout (128 bits / 16 bytes):\n * ```\n * bytes 0..5 \u2192 48-bit Unix-ms timestamp (big-endian)\n * byte 6 \u2192 high nibble = version (0x70), low nibble = rand_a high\n * byte 7 \u2192 rand_a low\n * byte 8 \u2192 high 2 bits = variant (0b10), low 6 bits = rand_b high\n * bytes 9..15 \u2192 rand_b low (56 bits)\n * ```\n *\n * Why v7 over v4: ids minted around the same moment sort lexicographically\n * by creation time, which makes range scans / index locality much better\n * for any backend store keyed by id (chat ids in our `dev-store.json`,\n * message ids on the wire, etc.).\n *\n * Falls back to `Math.random()` for runtimes without `crypto.getRandomValues`\n * \u2014 extremely rare in practice (every modern browser + Bun + Node).\n */\nexport function uuid7(): string {\n const ts = Date.now();\n const bytes = new Uint8Array(16);\n // High 48 bits \u2014 timestamp, big-endian. JS bitwise ops are 32-bit so we\n // split the high half via division.\n bytes[0] = Math.floor(ts / 2 ** 40) & 0xff;\n bytes[1] = Math.floor(ts / 2 ** 32) & 0xff;\n bytes[2] = (ts >>> 24) & 0xff;\n bytes[3] = (ts >>> 16) & 0xff;\n bytes[4] = (ts >>> 8) & 0xff;\n bytes[5] = ts & 0xff;\n // Bottom 80 bits \u2014 random.\n fillRandom(bytes.subarray(6));\n // Version 7 in high nibble of byte 6 (overwrite the random high nibble).\n bytes[6] = (bytes[6]! & 0x0f) | 0x70;\n // Variant 0b10 in high 2 bits of byte 8.\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Generate a short prefixed id, backed by {@link uuid7}. Drops the dashes\n * and truncates to 16 hex chars for the prefix-friendly form (`msg_\u2026`,\n * `visitor_\u2026`). Sort order within a prefix still tracks creation time\n * because the leading 12 hex digits are the timestamp.\n *\n * Used for client-minted chat / message / visitor / file ids \u2014 entities\n * that need to be unique-enough but not security tokens.\n */\nexport function cryptoId(prefix = \"id\"): string {\n return `${prefix}_${uuid7().replaceAll(\"-\", \"\").slice(0, 16)}`;\n}\n\n/**\n * Fill a Uint8Array with random bytes. Uses `crypto.getRandomValues` when\n * available (all modern runtimes); falls back to `Math.random()` only for\n * legacy environments where the Web Crypto API is missing.\n */\nfunction fillRandom(view: Uint8Array): void {\n if (typeof crypto !== \"undefined\" && typeof crypto.getRandomValues === \"function\") {\n // Cast: TS narrows getRandomValues to require an ArrayBuffer-backed view,\n // but Uint8Array<ArrayBufferLike> (the broader runtime type) is fine.\n crypto.getRandomValues(view as Uint8Array<ArrayBuffer>);\n return;\n }\n for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);\n}\n", "// Mirror of Vercel AI SDK v6 UIMessageChunk discriminated union.\n// We do NOT import `ai` \u2014 parsing the wire format ourselves keeps the bundle tiny.\n// Keep this file in lock-step with backend contract (module-help-ai-agent-api).\n\nexport type UIChunk =\n | { type: \"start\"; messageId?: string }\n | { type: \"start-step\" }\n | { type: \"finish-step\" }\n | {\n type: \"finish\";\n finishReason?: \"stop\" | \"length\" | \"tool-calls\" | \"content-filter\" | \"error\" | string;\n /**\n * Backend signal that the conversation is closed and cannot accept\n * further user messages (e.g. workspace hit its message cap, support\n * ticket was auto-closed). Absent or `true` \u2192 composer stays enabled;\n * explicit `false` \u2192 client flips into read-only mode.\n */\n canContinue?: boolean;\n }\n | { type: \"text-start\"; id: string }\n | { type: \"text-delta\"; id: string; delta: string }\n | { type: \"text-end\"; id: string }\n | { type: \"reasoning-start\"; id: string }\n | { type: \"reasoning-delta\"; id: string; delta: string }\n | { type: \"reasoning-end\"; id: string }\n | { type: \"tool-input-start\"; toolCallId: string; toolName: string }\n | { type: \"tool-input-delta\"; toolCallId: string; delta: string }\n | { type: \"tool-input-available\"; toolCallId: string; toolName: string; input: unknown }\n | { type: \"tool-output-available\"; toolCallId: string; output: unknown }\n | { type: \"tool-output-error\"; toolCallId: string; errorText: string }\n | { type: \"file\"; url: string; mediaType: string }\n | { type: \"source-url\"; sourceId: string; url: string; title?: string }\n | {\n type: \"data-notification\";\n data: { message: string; level?: \"info\" | \"warn\" | \"error\" };\n }\n | { type: \"error\"; errorText: string };\n\n// A Part is the UI-level projection of one or more chunks.\nexport type MessagePart =\n | { kind: \"text\"; id: string; text: string; done: boolean }\n | { kind: \"reasoning\"; id: string; text: string; done: boolean }\n | {\n kind: \"tool\";\n toolCallId: string;\n toolName: string;\n inputPartial: string;\n input?: unknown;\n output?: unknown;\n error?: string;\n state: \"input\" | \"awaiting\" | \"output\" | \"error\";\n }\n | { kind: \"file\"; url: string; mediaType: string }\n | { kind: \"source\"; sourceId: string; url: string; title?: string };\n\ninterface AttachmentRef {\n id: string;\n name: string;\n mediaType: string;\n size: number;\n url?: string;\n}\n\nexport interface Message {\n id: string;\n role: \"user\" | \"assistant\" | \"system\";\n createdAt: number;\n parts: MessagePart[];\n attachments?: AttachmentRef[];\n status: \"pending\" | \"streaming\" | \"complete\" | \"error\";\n errorText?: string;\n /** Backend's reason for ending the stream (`'stop'` | `'length'` | \u2026). */\n finishReason?: string;\n}\n\nexport class StreamError extends Error {\n constructor(\n message: string,\n public code: \"no-body\" | \"server\" | \"network\",\n /** HTTP status when the failure came from a response (e.g. 429, 404). */\n public status?: number,\n public override cause?: unknown,\n ) {\n super(message);\n this.name = \"StreamError\";\n }\n}\n", "/**\n * SSE \u2192 typed `UIChunk` parser.\n *\n * The backend emits Vercel AI SDK v6 `UIMessageChunk` frames over\n * `text/event-stream` (see `module-help-ai-agent-api`). Each frame is one event:\n *\n * ```\n * id: 42\\n\n * data: {\"type\":\"text-delta\",\"id\":\"t0\",\"delta\":\"hi\"}\\n\n * \\n\n * ```\n *\n * The only SSE bits we support:\n * - `data:` line \u2014 the JSON payload\n * - `id:` line \u2014 optional per-event id used for resume dedupe\n * - `:` lines \u2014 comments (keep-alives)\n *\n * `event:` and `retry:` are ignored \u2014 the backend does not use them.\n *\n * Corrupt frames (bad JSON) are logged and dropped; the stream continues.\n * Aborts propagate via the provided {@link AbortSignal}.\n */\n\nimport { logger } from \"@/core/logger\";\nimport { StreamError, type UIChunk } from \"./types\";\n\nconst log = logger.scope(\"parser\");\n\n/**\n * Consume `response.body` as SSE and yield one `{chunk, eventId}` tuple per\n * parsed event.\n *\n * @throws {@link StreamError} on non-2xx responses, missing body, or a\n * protocol error.\n * @throws {@link DOMException} with `name === 'AbortError'` when `signal`\n * aborts mid-stream.\n */\nexport async function* parseChatStream(\n response: Response,\n signal: AbortSignal,\n): AsyncGenerator<{ chunk: UIChunk; eventId?: string }, void, void> {\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new StreamError(`server responded ${response.status}: ${text.slice(0, 200)}`, \"server\", response.status);\n }\n if (!response.body) throw new StreamError(\"response has no body\", \"no-body\");\n\n const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();\n let buffer = \"\";\n\n try {\n while (true) {\n if (signal.aborted) throw new DOMException(\"aborted\", \"AbortError\");\n\n const { done, value } = await reader.read();\n if (done) {\n // Flush a trailing frame that wasn't terminated with \\n\\n.\n const trailing = buffer.trim();\n if (trailing) {\n const parsed = parseFrame(trailing);\n if (parsed) yield parsed;\n }\n return;\n }\n\n buffer += value;\n\n // Emit every complete frame currently in the buffer.\n let frameEnd: number;\n while ((frameEnd = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, frameEnd);\n buffer = buffer.slice(frameEnd + 2);\n const parsed = parseFrame(frame);\n if (parsed) yield parsed;\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Parse a single SSE frame string into a typed chunk + optional id.\n * Returns `null` for comment-only frames, `[DONE]` sentinels, and invalid JSON.\n */\nfunction parseFrame(frame: string): { chunk: UIChunk; eventId?: string } | null {\n let dataPayload = \"\";\n let eventId: string | undefined;\n\n for (const line of frame.split(\"\\n\")) {\n if (line === \"\" || line.startsWith(\":\")) continue;\n\n if (line.startsWith(\"data:\")) {\n // SSE spec: a `data:` line may include a leading space that must be stripped.\n // Multi-line data is concatenated with `\\n`.\n const part = line.slice(5).replace(/^ /, \"\");\n dataPayload = dataPayload ? `${dataPayload}\\n${part}` : part;\n continue;\n }\n\n if (line.startsWith(\"id:\")) {\n eventId = line.slice(3).trim();\n }\n // `event:` and `retry:` are intentionally ignored.\n }\n\n if (!dataPayload || dataPayload === \"[DONE]\") return null;\n\n try {\n const chunk = JSON.parse(dataPayload) as UIChunk;\n log.trace(\"chunk\", { type: chunk.type, eventId });\n return { chunk, eventId };\n } catch (err) {\n // One corrupt frame shouldn't kill the whole stream. Trace it so a\n // `?<brand>-debug=trace` session captures the offender; never log\n // beyond trace because corrupted frames CAN be the norm (network\n // splits, mid-frame disconnect) \u2014 flooding the console isn't useful.\n log.trace(\"frame parse failed\", { err, payload: dataPayload.slice(0, 200) });\n return null;\n }\n}\n", "/**\n * Codec for the host-asserted context field (`context`) carried on every\n * `/ai/agent/*` request \u2014 see {@link CONTEXT_PARAM}.\n *\n * The widget bundles the current `userContext` / `pageContext` into one object\n * `{ user?, page? }` and base64url-encodes its JSON. **This is encoding, not\n * encryption** \u2014 the transport is already TLS; base64url only keeps the value\n * body/URL-safe (unicode names) and out of plaintext access logs / the devtools\n * network tab. The backend reverses it with {@link decodeContext}.\n */\n\nimport type { DecodedContext } from \"./protocol\";\n\n/** A sanitised host-asserted context object (`userContext` / `pageContext`). */\nexport type ContextObject = Record<string, string | number | boolean | undefined>;\n\n/** UTF-8-safe base64url (no padding) \u2014 handles emoji / non-Latin names. */\nfunction toBase64Url(json: string): string {\n const bytes = new TextEncoder().encode(json);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Inverse of {@link toBase64Url}. */\nfunction fromBase64Url(b64url: string): string {\n const b64 = b64url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n}\n\nconst nonEmpty = (ctx: ContextObject | undefined): ContextObject | undefined =>\n ctx && Object.keys(ctx).length > 0 ? ctx : undefined;\n\n/**\n * Encode the current `user` / `page` context into the `context` request value.\n * Returns `undefined` when both are empty so the field is simply omitted.\n */\nexport function encodeContext(user: ContextObject | undefined, page: ContextObject | undefined): string | undefined {\n const u = nonEmpty(user);\n const p = nonEmpty(page);\n if (!u && !p) return undefined;\n const envelope: DecodedContext = {};\n if (u) envelope.user = u as DecodedContext[\"user\"];\n if (p) envelope.page = p as DecodedContext[\"page\"];\n return toBase64Url(JSON.stringify(envelope));\n}\n\n/**\n * Decode the `context` value back to `{ user?, page? }`. Returns `undefined`\n * for a missing / malformed value (never throws) \u2014 mirrors the lenient,\n * untrusted-input handling the backend should apply.\n */\nexport function decodeContext(value: string | undefined): DecodedContext | undefined {\n if (!value) return undefined;\n try {\n const parsed = JSON.parse(fromBase64Url(value));\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed) ? (parsed as DecodedContext) : undefined;\n } catch {\n return undefined;\n }\n}\n", "import type { ResponseMode, ServerConfig } from \"@/core/config\";\nimport type { UserPrefs } from \"@/core/persistence\";\nimport type { Message } from \"./types\";\n\n/** A tool reference on the wire: `{ code, config }`. `config` is opaque. */\nexport interface ToolRef {\n code: string;\n config?: Record<string, unknown>;\n}\n\n/**\n * Body of `POST /ai/agent/stream-message`.\n *\n * The **message structure** (`messages`) is the authoritative payload \u2014\n * this is what every request must carry. Everything else is optional and\n * only sent when the developer explicitly configured it.\n *\n * The `data` block exists so advanced integrations can *override* what\n * the backend has configured (currently `tools` and `responseMode`).\n * Model selection (text / image) is owned server-side per deployment\n * and isn't on the wire.\n */\nexport interface SendMessageRequest {\n /** Anonymous browser-level identifier (see {@link Persistence}). */\n visitorId?: string;\n /**\n * Snapshot of the client's current preferences (locale, panel size from\n * drag, sound mute). Sent on every request so the server can reconcile \u2014\n * cheap (<1KB) and keeps the wire protocol stateless.\n */\n userPrefs?: UserPrefs;\n /** Public runtime session id (UUIDv7), persisted server-side. */\n sessionId: string;\n /** What the client is doing. `\"submit-message\"` for a normal user send. */\n trigger?: \"submit-message\" | string;\n messages: WireMessage[];\n /**\n * Per-request overrides. Omit entirely when you want the backend's\n * workspace defaults (the typical case).\n */\n data?: {\n /** Restricts/overrides the tools the assistant may invoke this request. */\n tools?: ToolRef[];\n /**\n * Hint only. The backend ALWAYS streams (SSE); `responseMode` is a pure\n * client rendering choice \u2014 `streaming` paints tokens live, `buffered`\n * shows a loading indicator and paints the whole reply at the finish\n * marker. The wire is identical either way.\n */\n responseMode?: ResponseMode;\n };\n}\n\nexport interface WireMessage {\n id: string;\n role: \"user\" | \"assistant\" | \"system\";\n parts: WirePart[];\n}\n\nexport type WirePart =\n | { type: \"text\"; text: string }\n | {\n type: \"file\";\n /** Canonical URL. May be a custom scheme (e.g. `library-id://<id>`). */\n url: string;\n mediaType: string;\n /** Original filename as uploaded. */\n filename?: string;\n /** Opaque id from the upload response \u2014 lets the backend dedupe + cache. */\n fileId?: string;\n /** Size in bytes. */\n fileSize?: number;\n };\n\n/**\n * Default API paths (relative to `baseUrl`). `upload` and `transcribe` can\n * be overridden per-instance via {@link Endpoints}.\n */\nexport const DEFAULT_PATHS = {\n /** Session bootstrap. `POST` \u2192 deployment config + per-visitor state. */\n startSession: \"/ai/agent/start-session\",\n /** Send a message + stream the reply (SSE). `POST`. */\n streamMessage: \"/ai/agent/stream-message\",\n /**\n * Resume an interrupted reply. `GET` (envelope rides as query params).\n * Stateless: the server re-streams the whole reply SSE again from the start \u2014\n * there is NO `Last-Event-ID`. The client reconciles by id (`seenIds`), so\n * replayed events are skipped and only the continuation surfaces.\n */\n streamResume: \"/ai/agent/stream-resume\",\n /** Abort the in-flight reply stream. `POST`. */\n cancelStream: \"/ai/agent/cancel-stream\",\n /** List the visitor's conversations. `GET`. */\n listSessions: \"/ai/agent/list-sessions\",\n /** Load one conversation's messages. `GET ?sessionId=\u2026`. */\n listMessages: \"/ai/agent/list-messages\",\n /**\n * Persist a user-driven settings change. POST `{ visitorId, userPrefs }` \u2192\n * server persists, returns the merged authoritative copy. Fire-and-forget on\n * the client \u2014 a failure leaves the local cache valid; the next\n * `startSession()` reconciles.\n */\n updateSettings: \"/ai/agent/update-settings\",\n /**\n * Mark a conversation read. POST `{ visitorId, sessionId }` \u2192 the server\n * records `lastReadAt = now` for that (visitor, session) and recomputes\n * `unreadCount` (returned on `/list-sessions`) accordingly. Fire-and-forget\n * on the client; a failure just leaves the badge until the next sync.\n */\n markRead: \"/ai/agent/mark-read\",\n // \u2500\u2500 CMS content (unified \u2014 Home / Help / News / docs) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** All widget content via one filtered endpoint. `GET /ai/agent/content?tags=\u2026`. */\n content: \"/ai/agent/content\",\n} as const;\n\n/**\n * Info the widget sends to the backend on the initial connect call.\n * Lightweight \u2014 just enough context for the backend to personalise the\n * welcome payload (locale-specific copy, user-aware suggestions, etc).\n */\nexport interface StartSessionRequest {\n /**\n * Anonymous browser-level identifier \u2014 minted client-side on first mount,\n * reused forever after. Server uses it to look up per-visitor settings,\n * conversation history, etc. Always sent.\n */\n visitorId: string;\n /** Public runtime session id (UUIDv7). Minted by the widget. */\n sessionId: string;\n /**\n * Cached client-side preferences. Server treats this as a hint \u2014 if the\n * server has a newer / different copy, the response's `settings` field\n * is authoritative.\n */\n userPrefs?: UserPrefs;\n /**\n * Host-asserted context envelope \u2014 see {@link CONTEXT_PARAM}. Injected by the\n * transport on every request (not just this one), so it's listed here only\n * for documentation; callers never set it by hand.\n */\n context?: string;\n /** BCP-47 locale for server-side string selection. */\n locale?: string;\n}\n\n/**\n * Name of the host-asserted context field carried on **every** `/ai/agent/*`\n * request, alongside the required `visitorId` / `sessionId` identity (the request\n * envelope). Placement follows the request shape: JSON body for POSTs, form\n * fields for multipart POSTs (upload / transcribe), query params for GETs.\n *\n * Value: `base64url(JSON.stringify({ user?, page? }))` \u2014 the current, sanitised\n * `userContext` / `page\u200BContext`. base64url keeps it header/URL-safe (unicode\n * names) and out of plaintext logs (obfuscation only \u2014 the transport is TLS).\n * Re-encoded per request so it always reflects the visitor's latest context as\n * they navigate. Unlike `visitorId` / `sessionId`, `context` is **optional**\n * (omitted when empty), informational, and untrusted \u2014 **never** an auth\n * credential; the widget does not authenticate users.\n *\n * Backend: base64url-decode \u2192 `JSON.parse` \u2192 `{ user?, page? }` (both\n * `Record<string, string | number | boolean>`). Treat as untrusted.\n */\nexport const CONTEXT_PARAM = \"context\" as const;\n\n/** Decoded shape of {@link CONTEXT_PARAM} once the backend base64url-decodes it. */\nexport interface DecodedContext {\n user?: Record<string, string | number | boolean>;\n page?: Record<string, string | number | boolean>;\n}\n\n/**\n * Body of `POST /ai/agent/update-settings`. Used to push a user-driven settings\n * change (locale picker, sound toggle, drag-resize) up to the server out\n * of band \u2014 the regular `userPrefs` field on every request also\n * carries the snapshot, but pushing on change is faster than waiting for\n * the next message round-trip.\n */\nexport interface SettingsRequestBody {\n visitorId: string;\n sessionId?: string;\n userPrefs: UserPrefs;\n}\n\n/**\n * Response from `POST /ai/agent/update-settings`. The `userPrefs` echo lets\n * the client confirm what the server actually persisted (it may apply\n * server-side validation / defaults).\n */\nexport interface SettingsResponse {\n ok: true;\n userPrefs: UserPrefs;\n}\n\n/** One-shot bot persona shown in the panel header. */\nexport interface SessionAgent {\n name: string;\n /** Image URL or data URI. When omitted, initials render from `name`. */\n avatar?: string;\n /** Subtitle (\"AI Assistant\", \"Support team\", \u2026). */\n title?: string;\n /** Presence indicator. Default: `'online'`. */\n status?: \"online\" | \"away\" | \"offline\";\n}\n\n/** A clickable quick-reply chip. */\nexport interface SessionSuggestion {\n id: string;\n /** What the chip displays. */\n label: string;\n /** What actually gets sent when clicked. Defaults to `label`. */\n text?: string;\n}\n\n/** Welcome payload pushed into the message list on first open. */\nexport interface SessionWelcome {\n messages?: Array<{ id: string; role: \"assistant\" | \"system\"; text: string }>;\n suggestions?: SessionSuggestion[];\n}\n\n/**\n * Response from `POST /ai/agent/start-session`. Every field is optional so the\n * backend can opt into surfaces incrementally.\n *\n * The `userPrefs` field (when present) is the server's authoritative\n * copy of per-visitor preferences \u2014 the client should adopt it and update\n * its local cache. Lets a user picking French on their phone see the\n * widget in French when they open it on their laptop (assuming the same\n * `visitorId`, e.g. via authenticated session \u2014 out of scope for v1).\n */\nexport interface StartSessionResponse {\n /**\n * Public runtime session id. The widget mints this UUIDv7 and the backend\n * validates that it belongs to the public deployment + visitor.\n */\n sessionId: string;\n visitorId?: string;\n canContinue?: boolean;\n /** Bot persona in the panel header (name, avatar, status). */\n agent?: SessionAgent;\n /** Initial assistant messages + quick-reply chips. */\n welcome?: SessionWelcome;\n /**\n * Deployment-wide widget configuration the dashboard admin saved\n * (mode / theme / launcher / actions / composer / features / \u2026).\n * Client adopts as defaults; customer's `init({...})` still wins\n * per-field. Same for every visitor of this deployment.\n */\n config?: ServerConfig;\n /** Page-mode site config (logo, title). Backend ships it whenever the deployment renders the page-shell sidebar. */\n site?: Record<string, unknown>;\n /** Page-mode sidebar blocks (navigation, link cards). Backend ships it whenever the deployment renders the page-shell sidebar. */\n blocks?: Record<string, unknown>;\n messages?: WireMessage[];\n /**\n * Server's authoritative copy of the visitor's preferences. When present,\n * client adopts it (overrides local cache) and re-applies it to the UI\n * (locale, panel size, sound mute). Absent \u2192 client keeps its cache.\n */\n userPrefs?: UserPrefs;\n /**\n * Optional rebind metadata. When the server returns a `visitorId` /\n * `sessionId` different from what the client sent, this block tells\n * the client WHY \u2014 useful for telemetry and the `visitorRebound` /\n * `sessionRebound` events fired into the host page. Server is free to\n * omit it: the client detects rebinding by comparing request vs\n * response ids regardless.\n *\n * Common reasons: `\"conflict\"` (the id collided with an existing\n * record), `\"expired\"` (the id is too old to be re-used), `\"invalid\"`\n * (malformed / not a UUIDv7). Custom strings are passed through to\n * listeners verbatim.\n */\n rebind?: { visitorId?: string; sessionId?: string };\n}\n\nexport function buildSendMessageRequest(params: {\n sessionId: string;\n messages: Message[];\n tools?: Array<string | ToolRef>;\n responseMode?: ResponseMode;\n /** Override the default `\"submit-message\"` trigger. */\n trigger?: string;\n /** Anonymous browser-level identifier; attached to the body when set. */\n visitorId?: string;\n /** Snapshot of client-side preferences; attached to the body when set. */\n userPrefs?: UserPrefs;\n}): SendMessageRequest {\n const wire: WireMessage[] = params.messages.map((m) => ({\n id: m.id,\n parts: messageToWireParts(m),\n role: m.role,\n }));\n\n const body: SendMessageRequest = {\n messages: wire,\n sessionId: params.sessionId,\n trigger: params.trigger ?? \"submit-message\",\n };\n if (params.visitorId) body.visitorId = params.visitorId;\n if (params.userPrefs) body.userPrefs = params.userPrefs;\n\n const data: NonNullable<SendMessageRequest[\"data\"]> = {};\n const tools = params.tools?.map(normalizeToolRef).filter((t): t is ToolRef => t !== null);\n if (tools?.length) data.tools = tools;\n if (params.responseMode) data.responseMode = params.responseMode;\n if (Object.keys(data).length > 0) body.data = data;\n\n return body;\n}\n\n/** `\"tool:search\"` or `{ code: \"tool:search\", config: {\u2026} }` \u2192 `ToolRef`. */\nexport function normalizeToolRef(ref: string | ToolRef): ToolRef | null {\n if (typeof ref === \"string\") return ref ? { code: ref } : null;\n return ref?.code ? ref : null;\n}\n\nfunction messageToWireParts(m: Message): WirePart[] {\n const out: WirePart[] = [];\n for (const p of m.parts) {\n if (p.kind === \"text\" && p.text) {\n out.push({ text: p.text, type: \"text\" });\n }\n if (p.kind === \"file\" && p.url) {\n out.push({ mediaType: p.mediaType, type: \"file\", url: p.url });\n }\n }\n // Attach uploaded file refs as additional `file` parts (after text).\n for (const a of m.attachments ?? []) {\n if (!a.url) continue;\n out.push({\n type: \"file\",\n url: a.url,\n mediaType: a.mediaType,\n filename: a.name,\n fileId: a.id,\n fileSize: a.size,\n });\n }\n return out;\n}\n\n// ---- Chat history ------------------------------------------------------\n\n/**\n * One row in the chat history list. Returned by `GET /ai/agent/list-sessions`.\n * The backend decides how to compute `title` + `preview` (typically the\n * first user message and the last assistant message respectively).\n */\nexport interface SessionSummary {\n /** UI alias for the backend `sessionId`. */\n sessionId: string;\n title: string;\n /** ISO-8601 timestamp of the most recent message. Used for date grouping. */\n lastMessageAt: string;\n /** One-line preview, shown below the title. */\n preview?: string;\n /**\n * Whether the user can continue posting in this chat. `false` \u2192 viewer\n * mode (composer disabled). `true` \u2192 fully resumable.\n */\n canContinue: boolean;\n /**\n * Unread assistant messages for the current visitor. Drives the\n * Messages-tab badge + the Home \"recent message\" dot. Optional \u2014 older\n * backends omit it (treated as `0`).\n */\n unreadCount?: number;\n}\n\nexport interface ListSessionsResponse {\n sessions: SessionSummary[];\n /** Opaque cursor for the next page. Absent = last page. */\n nextCursor?: string;\n}\n\n/** Full messages for a single chat, fetched when the user taps it in the list. */\nexport interface LoadSessionResponse {\n /** UI alias for the backend `sessionId`. */\n sessionId: string;\n canContinue: boolean;\n messages: WireMessage[];\n /**\n * Optional fresh settings snapshot for this chat (e.g. the agent it was\n * with, which may differ from the current workspace default). Merged with\n * the same `user-wins-per-field` policy as the connect response.\n */\n agent?: SessionAgent;\n /**\n * Suggestion chips to show for *this specific chat*. Distinct from the\n * ones in `StartSessionResponse.welcome.suggestions`, which are new-chat-only\n * (\"What can you do?\", \"Show me pricing\") and become irrelevant once a\n * conversation is in progress. The client always swaps in these values\n * on resume \u2014 `undefined` / `null` means \"clear any connect-time\n * suggestions\", an array (even empty) replaces them.\n */\n suggestions?: SessionSuggestion[] | null;\n}\n\n// ---- CMS content (unified) ---------------------------------------------\n//\n// All widget content is ONE flat collection of `ContentItem` rows, grouped\n// only by `tags` + `categories` (both multi-value) \u2014 no per-type model.\n// Served by a single filtered `GET /ai/agent/content`. The public read returns\n// only published rows assigned to the deployment. Wire contract: `docs/api/content.md`.\n\n/**\n * One CMS content row. Rows are scoped by `tags` and grouped by `categories`.\n * `url` opens as an in-widget iframe (help articles); otherwise `content`\n * (markdown) renders in the native reader. The Home dashboard is NOT a content\n * row \u2014 it's assembled from the tab's `home` config flags; its \"Popular\n * articles\" list is just content fetched by `tags`.\n */\nexport interface ContentItem {\n id: string;\n title: string;\n description?: string;\n /** Markdown body \u2014 rendered in the native reader when there's no `url`. */\n content?: string;\n /** Grouping terms (e.g. Help sections). */\n categories?: string[];\n /** Scope terms (the `tags` filter). */\n tags?: string[];\n /** Hero / thumbnail image URL. */\n image?: string;\n /** External page URL \u2014 opened in a sandboxed in-widget iframe. */\n url?: string;\n /** Sort weight for `sortBy: \"order\"` (lower first). */\n order?: number;\n /** ISO-8601 publish timestamp \u2014 news sort + display. */\n publishedAt?: string;\n /** Custom fields. */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Filters for `GET /ai/agent/content`. Content is scoped by `tags` +\n * `categories` (both multi-value, comma-separated, match ANY); `ids` fetches\n * specific rows (the reader); `search` is full-text; pagination is the standard\n * `page` + `limit`. Usage: a tab's list `{tags}`, grouped by each row's\n * `categories`; news `{tags, sortBy:\"publishedAt\", sortOrder:\"desc\"}`; one row\n * `{ids:[id]}`; search `{tags, search}`.\n */\nexport interface ContentQuery {\n tags?: string | string[];\n categories?: string | string[];\n ids?: string | string[];\n search?: string;\n sortBy?: \"order\" | \"createdAt\" | \"updatedAt\" | \"publishedAt\" | \"title\";\n sortOrder?: \"asc\" | \"desc\";\n /** 1-based page number (standard pagination). */\n page?: number;\n /** Page size (max 200). */\n limit?: number;\n}\n\n/** Standard pagination envelope returned by every list endpoint. */\nexport interface PaginationMeta {\n page: number;\n limit: number;\n total: number;\n totalPages: number;\n hasNext: boolean;\n hasPrev: boolean;\n}\n\n/** Response from `GET /ai/agent/content`. */\nexport interface ListContentResponse {\n items: ContentItem[];\n pagination: PaginationMeta;\n}\n", "/**\n * HTTP transport for the public agent API.\n *\n * The UI can keep using \"chat\" language, but the wire protocol is the\n * deployment-facing `/ai/agent/*` surface: public key in `x-public-key`,\n * deployment id in `x-ai-agent-deployment-id`, raw UUIDv7 `visitorId`,\n * and raw UUIDv7 `sessionId`.\n */\n\nimport type { AuthHeaderProvider } from \"@/core/auth\";\nimport { logger } from \"@/core/logger\";\n\nconst log = logger.scope(\"transport\");\nimport type { UserPrefs } from \"@/core/persistence\";\nimport { parseChatStream } from \"./parser\";\nimport { encodeContext, type ContextObject } from \"./context-codec\";\nimport {\n type SendMessageRequest,\n CONTEXT_PARAM,\n type StartSessionRequest,\n type StartSessionResponse,\n type ContentQuery,\n DEFAULT_PATHS,\n type ListContentResponse,\n type ListSessionsResponse,\n type LoadSessionResponse,\n type SettingsResponse,\n} from \"./protocol\";\nimport { StreamError, type UIChunk } from \"./types\";\n\n/**\n * The validation envelope carried on EVERY `/ai/agent/*` request so the backend\n * can validate the request against the visitor + session it claims to belong to.\n * `visitorId` / `sessionId` are **required** identity (always sent once the\n * session is started); `context` is **optional** host-asserted info (omitted when\n * empty, never auth). Auth (publicKey + deployment id) rides separately in headers.\n *\n * On the wire: JSON body for POSTs, form fields for multipart POSTs, query params\n * for GETs.\n */\ntype RequestEnvelope = { visitorId?: string; sessionId?: string; context?: string };\n\nexport interface TransportOptions {\n /** Backend origin, no trailing slash. */\n baseUrl: string;\n /** Auth header provider. */\n auth: AuthHeaderProvider;\n /** Upload + transcribe endpoint paths (relative to `baseUrl` or absolute URLs). */\n endpoints?: { upload?: string; transcribe?: string };\n /** Inject a custom fetch (used in tests). Defaults to `globalThis.fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface MessageStreamHandle {\n /** Async iterable of `(chunk, eventId)` tuples \u2014 parsed and deduped. */\n iter: AsyncGenerator<ParsedEvent, void, void>;\n /** Cancel the in-flight stream + notify the server. Safe to call multiple times. */\n cancel: () => void;\n}\n\ntype ParsedEvent = { chunk: UIChunk; eventId?: string };\n\n/** Reconnect budget when a reply stream drops before a terminal chunk. */\nconst MAX_RESUME_ATTEMPTS = 3;\n/** Linear backoff base between resume attempts (\u00D7attempt). */\nconst RESUME_BACKOFF_MS = 400;\n\n/** Retry budget for transient request failures (network / retryable status). */\nconst MAX_REQUEST_RETRIES = 3;\n/** Statuses worth retrying \u2014 rate limits, gateway/overload, request timeouts. */\nconst RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);\n\n/** Exponential backoff with jitter: ~0.4s, ~0.8s, ~1.6s (\u00B1 up to 250ms). */\nfunction backoffMs(attempt: number): number {\n return RESUME_BACKOFF_MS * 2 ** attempt + Math.floor(Math.random() * 250);\n}\n\n/**\n * Parse a `Retry-After` header \u2192 ms to wait, or `null` if absent/unparseable.\n * Accepts both forms: delta-seconds (`\"5\"`) and an HTTP-date. Clamped to \u226430s.\n */\nfunction retryAfterMs(headers: Headers): number | null {\n const raw = headers.get(\"retry-after\");\n if (!raw) return null;\n const secs = Number(raw);\n const ms = Number.isFinite(secs) ? secs * 1000 : Date.parse(raw) - Date.now();\n return Number.isFinite(ms) && ms >= 0 ? Math.min(ms, 30_000) : null;\n}\n\n/** Abortable delay \u2014 rejects-free; resolves early if the (optional) signal aborts. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) return resolve();\n const id = setTimeout(done, ms);\n const onAbort = () => done();\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n function done() {\n clearTimeout(id);\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }\n });\n}\n\ninterface AgentSessionsResponse {\n sessions: Array<{\n sessionId: string;\n title: string;\n lastMessageAt: string;\n preview?: string;\n canContinue: boolean;\n unreadCount?: number;\n }>;\n nextCursor?: string;\n}\n\ninterface AgentMessagesResponse {\n sessionId: string;\n canContinue: boolean;\n messages: LoadSessionResponse[\"messages\"];\n agent?: LoadSessionResponse[\"agent\"];\n suggestions?: LoadSessionResponse[\"suggestions\"];\n}\n\nexport class AgentTransport {\n constructor(private readonly opts: TransportOptions) {}\n\n // Identity adopted from the start-session response; rides the request envelope\n // (see `envelope()`) on every subsequent call so the backend can validate the\n // (visitor, session) pair.\n private visitorId: string | undefined;\n private sessionId: string | undefined;\n // Host-asserted context (sanitised `userContext` / `pageContext`), refreshed via\n // `setContext` and carried as the envelope's optional `context` so the backend\n // always sees the visitor's latest context as they navigate.\n private userContext: ContextObject | undefined;\n private pageContext: ContextObject | undefined;\n\n // ---- Public API -------------------------------------------------------\n\n /**\n * Update the host-asserted context carried on every subsequent request.\n * Cheap + synchronous \u2014 call it on mount and whenever `userContext` /\n * `pageContext` change (SPA navigation, runtime `update()`). No re-handshake.\n */\n setContext(userContext: ContextObject | undefined, pageContext: ContextObject | undefined): void {\n this.userContext = userContext;\n this.pageContext = pageContext;\n }\n\n /** Build the per-request envelope from the current transport state. */\n private envelope(): RequestEnvelope {\n const out: RequestEnvelope = {};\n if (this.visitorId) out.visitorId = this.visitorId;\n if (this.sessionId) out.sessionId = this.sessionId;\n const context = encodeContext(this.userContext, this.pageContext);\n if (context) out[CONTEXT_PARAM] = context;\n return out;\n }\n\n /** Merge the envelope into a JSON body (POSTs). Explicit body fields win \u2014 e.g.\n * start-session's client-proposed ids, or a mark-read target session that\n * differs from the active one. */\n private withEnvelope<T extends object>(body: T): T & RequestEnvelope {\n return { ...this.envelope(), ...body };\n }\n\n /** Append the envelope as form fields (multipart POSTs \u2014 upload / transcribe). */\n private appendEnvelope(form: FormData): FormData {\n for (const [key, value] of Object.entries(this.envelope())) form.append(key, value);\n return form;\n }\n\n /** Resolve a path to its full URL and append the envelope as query params (GETs).\n * Won't clobber params the caller already set (e.g. a `sessionId` resource\n * selector on load-session). */\n private withEnvelopeQuery(urlOrPath: string): string {\n const url = new URL(this.url(urlOrPath));\n for (const [key, value] of Object.entries(this.envelope())) {\n if (!url.searchParams.has(key)) url.searchParams.set(key, value);\n }\n return url.toString();\n }\n\n /** One-shot runtime bootstrap. Called once after the widget mounts. */\n async startSession(body: StartSessionRequest): Promise<StartSessionResponse> {\n log.debug(\"startSession \u2192\", { visitorId: body.visitorId, sessionId: body.sessionId, locale: body.locale });\n const res = await this.postJson<StartSessionResponse>(DEFAULT_PATHS.startSession, body, \"startSession\");\n log.debug(\"startSession \u2190\", {\n visitorId: res.visitorId,\n sessionId: res.sessionId,\n canContinue: res.canContinue,\n hasMessages: !!res.messages?.length,\n hasWelcome: !!res.welcome,\n hasConfig: !!res.config,\n rebind: res.rebind,\n });\n // Defensive narrowing: warn (don't throw) on shape drift so the widget\n // keeps working when the backend ships a new optional field. We\n // hand-roll the check (instead of importing the canonical Zod schema)\n // because pulling Zod into the auto-boot IIFE bundle adds ~67KB gz \u2014\n // a non-starter for a CDN script. Zod stays the source of truth for\n // the admin under the `./schema` package subpath.\n if (!isStartSessionResponseShape(res)) {\n log.warn(\"startSession response did not match expected shape; using raw response\", { response: res });\n }\n // Adopt the SERVER's `visitorId` / `sessionId`. When the server\n // returns the same values the client sent, this is a no-op\n // (steady-state case). When the server rebinds \u2014 id collision,\n // expiration, malformed input \u2014 `res.visitorId` / `res.sessionId`\n // are the fresh values the rest of the widget MUST use for every\n // subsequent request. The App detects rebinding (request vs\n // response id mismatch) and fires `visitorRebound` /\n // `sessionRebound` events + persists the new ids.\n this.visitorId = res.visitorId ?? body.visitorId;\n this.sessionId = res.sessionId;\n return res;\n }\n\n /** Upload a file attachment. Returns the `{id, url}` the backend assigns. */\n async uploadFile(file: File): Promise<{ id: string; url: string }> {\n log.debug(\"uploadFile \u2192\", { name: file.name, size: file.size, type: file.type });\n const fd = new FormData();\n fd.append(\"file\", file, file.name);\n const res = await this.postForm<{ id: string; url: string }>(this.uploadPath, fd, \"upload\");\n log.debug(\"uploadFile \u2190\", { id: res.id, url: res.url });\n return res;\n }\n\n /**\n * Upload a captured audio blob for server-side speech-to-text.\n * Only used when `features.voice === 'server'`.\n */\n async transcribe(audio: Blob, mimeType = \"audio/webm\"): Promise<{ text: string }> {\n log.debug(\"transcribe \u2192\", { size: audio.size, mimeType });\n const fd = new FormData();\n fd.append(\"audio\", audio, `voice.${extensionFor(mimeType)}`);\n fd.append(\"mimeType\", mimeType);\n const res = await this.postForm<{ text: string }>(this.transcribePath, fd, \"transcribe\");\n log.debug(\"transcribe \u2190\", { textLen: res.text.length });\n return res;\n }\n\n async listSessions(\n params: { visitorId?: string; limit?: number; cursor?: string } = {},\n ): Promise<ListSessionsResponse> {\n const url = new URL(this.url(DEFAULT_PATHS.listSessions));\n // `visitorId` is supplied by the request envelope; only set it here to\n // override the transport's current visitor (rare \u2014 used by some callers).\n if (params.visitorId) url.searchParams.set(\"visitorId\", params.visitorId);\n if (params.limit) url.searchParams.set(\"limit\", String(params.limit));\n if (params.cursor) url.searchParams.set(\"cursor\", params.cursor);\n\n log.debug(\"listSessions \u2192\", {\n visitorId: params.visitorId ?? this.visitorId,\n limit: params.limit,\n cursor: params.cursor,\n });\n const res = await this.getJson<AgentSessionsResponse>(url.toString(), \"listSessions\");\n log.debug(\"listSessions \u2190\", { count: res.sessions.length, nextCursor: res.nextCursor });\n return {\n sessions: res.sessions.map((session) => ({\n sessionId: session.sessionId,\n title: session.title,\n lastMessageAt: session.lastMessageAt,\n preview: session.preview,\n canContinue: session.canContinue,\n unreadCount: session.unreadCount,\n })),\n nextCursor: res.nextCursor,\n };\n }\n\n async loadSession(sessionId: string): Promise<LoadSessionResponse> {\n // `visitorId` rides the envelope; `sessionId` here is the resource selector\n // (the thread to load \u2014 may differ from the active session), so it's explicit.\n const url = new URL(this.url(DEFAULT_PATHS.listMessages));\n url.searchParams.set(\"sessionId\", sessionId);\n\n log.debug(\"loadSession \u2192\", { sessionId, visitorId: this.visitorId });\n const res = await this.getJson<AgentMessagesResponse>(url.toString(), \"loadSession\");\n log.debug(\"loadSession \u2190\", {\n sessionId: res.sessionId,\n canContinue: res.canContinue,\n messageCount: res.messages.length,\n hasSuggestions: !!res.suggestions,\n });\n return {\n sessionId: res.sessionId,\n canContinue: res.canContinue,\n messages: res.messages,\n agent: res.agent,\n suggestions: res.suggestions,\n };\n }\n\n /**\n * Mark a conversation read up to its latest message. Fire-and-forget \u2014 the\n * server records `lastReadAt` and clears that session's `unreadCount` on the\n * next `/sessions`. A failure is non-fatal (the badge just lingers until the\n * next sync), so callers don't await this.\n */\n async markRead(sessionId: string): Promise<void> {\n if (!this.visitorId || !sessionId) return;\n log.debug(\"markRead \u2192\", { sessionId, visitorId: this.visitorId });\n try {\n // `sessionId` here is the thread being marked (may differ from the active\n // session), so it's explicit; `visitorId` rides the envelope.\n await this.postJson<{ ok: boolean }>(DEFAULT_PATHS.markRead, { sessionId }, \"markRead\");\n } catch (err) {\n log.debug(\"markRead failed (non-fatal)\", { err });\n }\n }\n\n // ---- CMS content (unified) --------------------------------------------\n //\n // One endpoint for every content surface (Home / Help / News / docs).\n // Callers pass filters; a thrown StreamError (e.g. 404 on an older backend)\n // is treated as \"no content\" by the module and the tab degrades gracefully.\n\n /** Fetch content rows by filter. `GET /ai/agent/content`. */\n async listContent(query: ContentQuery = {}): Promise<ListContentResponse> {\n const url = new URL(this.url(DEFAULT_PATHS.content));\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) continue;\n url.searchParams.set(key, Array.isArray(value) ? value.join(\",\") : String(value));\n }\n log.debug(\"listContent \u2192\", query);\n const res = await this.getJson<ListContentResponse>(url.toString(), \"listContent\");\n log.debug(\"listContent \u2190\", { count: res.items.length, total: res.pagination?.total });\n return res;\n }\n\n async saveUserPrefs(userPrefs: UserPrefs): Promise<SettingsResponse> {\n // `visitorId` / `sessionId` ride the request envelope.\n log.debug(\"saveUserPrefs \u2192\", {\n visitorId: this.visitorId,\n sessionId: this.sessionId,\n settingsKeys: Object.keys(userPrefs),\n });\n const res = await this.postJson<SettingsResponse>(DEFAULT_PATHS.updateSettings, { userPrefs }, \"saveUserPrefs\");\n log.debug(\"saveUserPrefs \u2190\", { ok: res.ok });\n return res;\n }\n\n private get uploadPath(): string {\n return this.opts.endpoints?.upload ?? \"/ai/agent/upload-file\";\n }\n\n private get transcribePath(): string {\n return this.opts.endpoints?.transcribe ?? \"/ai/agent/transcribe-audio\";\n }\n\n sendMessage(body: SendMessageRequest): MessageStreamHandle {\n log.debug(\"message \u2192\", {\n sessionId: body.sessionId,\n visitorId: body.visitorId,\n messageCount: body.messages.length,\n trigger: body.trigger,\n responseMode: body.data?.responseMode,\n tools: body.data?.tools?.map((t) => t.code),\n });\n const ctrl = new AbortController();\n const seenIds = new Set<string>();\n\n const iter = this.runStream(body, ctrl, seenIds);\n const cancel = () => this.cancelStream(ctrl, body.sessionId);\n return { iter, cancel };\n }\n\n // ---- Stream orchestration ---------------------------------------------\n\n private async *runStream(\n body: SendMessageRequest,\n ctrl: AbortController,\n seenIds: Set<string>,\n ): AsyncGenerator<ParsedEvent, void, void> {\n // First attempt: POST /stream-message. If it ends before a terminal chunk\n // (`finish`/`error`) \u2014 a dropped connection \u2014 try to re-attach to the\n // in-flight reply via GET /stream-resume. The server answers 200 (SSE,\n // still generating) or 204 (nothing in-flight \u2192 the reply is already done).\n // Replayed events are deduped by id, so only the continuation surfaces.\n if (yield* this.drain(this.openMessageStream(body, ctrl.signal), seenIds, ctrl)) return;\n\n for (let attempt = 1; attempt <= MAX_RESUME_ATTEMPTS && !ctrl.signal.aborted; attempt++) {\n await sleep(RESUME_BACKOFF_MS * attempt, ctrl.signal);\n if (ctrl.signal.aborted) return;\n let res: Response;\n try {\n res = await this.fetchImpl(this.resumeUrl(seenIds.size), {\n method: \"GET\",\n credentials: \"omit\",\n headers: { accept: \"text/event-stream\", ...this.opts.auth.headers() },\n signal: ctrl.signal,\n });\n } catch (err) {\n if (ctrl.signal.aborted) return;\n log.debug(\"resume fetch failed \u2014 retrying\", { attempt, err });\n continue;\n }\n // 204 (or any non-OK): nothing in-flight to resume \u2014 stop with what we have.\n if (res.status === 204 || !res.ok) {\n log.debug(\"resume \u2192 no in-flight stream\", { status: res.status });\n return;\n }\n if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;\n }\n if (ctrl.signal.aborted) return;\n // Exhausted the resume budget without a terminal chunk \u2014 surface an error so\n // the UI shows a retry instead of a half-finished reply hanging forever.\n throw new StreamError(\"stream incomplete after resume attempts\", \"server\");\n }\n\n /**\n * Drain one stream segment: yield its (deduped) events. Returns `true` once a\n * terminal chunk (`finish`/`error`) is seen OR the run is aborted \u2014 i.e. the\n * caller should stop. Returns `false` if the segment ended without finishing\n * (a drop), so the caller can attempt a resume.\n */\n private async *drain(\n source: AsyncIterable<ParsedEvent>,\n seenIds: Set<string>,\n ctrl: AbortController,\n ): AsyncGenerator<ParsedEvent, boolean, void> {\n try {\n for await (const evt of source) {\n if (ctrl.signal.aborted) return true;\n if (evt.eventId) {\n if (seenIds.has(evt.eventId)) continue;\n seenIds.add(evt.eventId);\n }\n yield evt;\n if (evt.chunk.type === \"finish\" || evt.chunk.type === \"error\") return true;\n }\n } catch (err) {\n if (ctrl.signal.aborted) return true;\n // An HTTP error (non-2xx with a status) is terminal \u2014 the request was\n // rejected, not dropped mid-flight. Propagate it so the UI shows the\n // error (with the status) instead of silently attempting a resume.\n if (err instanceof StreamError && err.status !== undefined) throw err;\n log.debug(\"stream segment dropped\", { err });\n }\n return false;\n }\n\n /** Abort + fire-and-forget POST to `/ai/agent/cancel-stream`. Idempotent. */\n private cancelStream(ctrl: AbortController, sessionId: string | undefined): void {\n if (ctrl.signal.aborted) return;\n log.debug(\"cancel stream\", { sessionId, visitorId: this.visitorId });\n ctrl.abort();\n if (!this.visitorId || !sessionId) return;\n this.fetchImpl(this.url(DEFAULT_PATHS.cancelStream), {\n method: \"POST\",\n credentials: \"omit\",\n headers: { \"content-type\": \"application/json\", ...this.opts.auth.headers() },\n body: JSON.stringify(this.withEnvelope({})),\n keepalive: true,\n }).catch((err) => log.debug(\"cancel POST failed (non-fatal)\", { err }));\n }\n\n // ---- Low-level fetch helpers ------------------------------------------\n\n private async *openMessageStream(body: SendMessageRequest, signal: AbortSignal): AsyncGenerator<ParsedEvent> {\n const res = await this.fetchImpl(this.url(DEFAULT_PATHS.streamMessage), {\n method: \"POST\",\n credentials: \"omit\",\n headers: {\n accept: \"text/event-stream\",\n \"content-type\": \"application/json\",\n ...this.opts.auth.headers(),\n },\n body: JSON.stringify(this.withEnvelope(body)),\n signal,\n });\n yield* parseChatStream(res, signal);\n }\n\n /**\n * Resume URL: `GET /stream-resume` with the envelope (visitorId / sessionId /\n * context) as query params plus `resumeAt` \u2014 the count of events already\n * consumed, so the server knows where the client is. The server replies 200\n * (SSE, still in-flight) or 204 (nothing to resume).\n */\n private resumeUrl(resumeAt: number): string {\n const url = new URL(this.withEnvelopeQuery(DEFAULT_PATHS.streamResume));\n url.searchParams.set(\"resumeAt\", String(resumeAt));\n return url.toString();\n }\n\n // JSON requests are idempotent (reads, or full-snapshot writes like\n // update-settings / mark-read / start-session), so they auto-retry transient\n // failures. The message POST is NOT in here \u2014 re-sending could duplicate the\n // reply, so it surfaces an error for the user to retry (see `openMessageStream`).\n private async postJson<T>(path: string, body: object, label: string): Promise<T> {\n const res = await this.fetchWithRetry(\n () => [\n this.url(path),\n {\n method: \"POST\",\n credentials: \"omit\",\n headers: { \"content-type\": \"application/json\", ...this.opts.auth.headers() },\n body: JSON.stringify(this.withEnvelope(body)),\n },\n ],\n label,\n );\n return this.assertOk<T>(res, label);\n }\n\n private async postForm<T>(path: string, body: FormData, label: string): Promise<T> {\n // No auto-retry: large multipart bodies + non-idempotent uploads. The user\n // re-triggers (re-attach / re-record) on failure.\n const res = await this.fetchImpl(this.url(path), {\n method: \"POST\",\n credentials: \"omit\",\n headers: this.opts.auth.headers(),\n body: this.appendEnvelope(body),\n });\n return this.assertOk<T>(res, label);\n }\n\n private async getJson<T>(urlOrPath: string, label: string): Promise<T> {\n const res = await this.fetchWithRetry(\n () => [\n this.withEnvelopeQuery(urlOrPath),\n { method: \"GET\", credentials: \"omit\", headers: { accept: \"application/json\", ...this.opts.auth.headers() } },\n ],\n label,\n );\n return this.assertOk<T>(res, label);\n }\n\n /**\n * Fetch with bounded retries for *transient* failures: retryable statuses\n * (429 / 5xx / \u2026) and network errors, honoring `Retry-After` (else backoff +\n * jitter). Terminal statuses (4xx) return immediately for the caller to throw.\n * Use only for idempotent requests.\n */\n private async fetchWithRetry(build: () => readonly [string, RequestInit], label: string): Promise<Response> {\n for (let attempt = 0; ; attempt++) {\n const [reqUrl, init] = build();\n let res: Response;\n try {\n res = await this.fetchImpl(reqUrl, init);\n } catch (err) {\n if (attempt >= MAX_REQUEST_RETRIES) {\n throw new StreamError(`${label} failed: network error`, \"network\", undefined, err);\n }\n log.debug(\"request network error \u2014 retrying\", { label, attempt });\n await sleep(backoffMs(attempt));\n continue;\n }\n if (res.ok || !RETRYABLE_STATUS.has(res.status) || attempt >= MAX_REQUEST_RETRIES) return res;\n const wait = retryAfterMs(res.headers) ?? backoffMs(attempt);\n log.debug(\"request retryable status \u2014 retrying\", { label, status: res.status, attempt, wait });\n await sleep(wait);\n }\n }\n\n /** Throw on non-2xx (with the status attached), otherwise parse the JSON body. */\n private async assertOk<T>(res: Response, label: string): Promise<T> {\n if (!res.ok) throw new StreamError(`${label} failed: ${res.status}`, \"server\", res.status);\n return (await res.json()) as T;\n }\n\n private url(pathOrUrl: string): string {\n if (/^https?:\\/\\//i.test(pathOrUrl)) return pathOrUrl;\n return `${this.opts.baseUrl}${pathOrUrl}`;\n }\n\n private get fetchImpl(): typeof fetch {\n return this.opts.fetchImpl ?? globalThis.fetch.bind(globalThis);\n }\n}\n\nfunction extensionFor(mimeType: string): string {\n if (mimeType.includes(\"webm\")) return \"webm\";\n if (mimeType.includes(\"ogg\")) return \"ogg\";\n if (mimeType.includes(\"mp4\") || mimeType.includes(\"m4a\")) return \"m4a\";\n if (mimeType.includes(\"mpeg\")) return \"mp3\";\n if (mimeType.includes(\"wav\")) return \"wav\";\n return \"bin\";\n}\n\n/**\n * Lightweight runtime check that the start-session response carries the\n * always-present fields the widget actually consumes. Mirrors the strict\n * fields of `startSessionResponseSchema` (visitorId, sessionId, canContinue) \u2014\n * everything else is optional or `.loose()` so we don't validate it here.\n *\n * Hand-rolled instead of `startSessionResponseSchema.safeParse(raw)` because\n * pulling Zod into the IIFE / auto-boot path is ~67KB gz. Zod remains the\n * source of truth via the `./schema` package subpath.\n */\nfunction isStartSessionResponseShape(raw: unknown): raw is StartSessionResponse {\n if (!raw || typeof raw !== \"object\") return false;\n const r = raw as Record<string, unknown>;\n return typeof r.visitorId === \"string\" && typeof r.sessionId === \"string\" && typeof r.canContinue === \"boolean\";\n}\n", "/**\n * Reactive-message shape + factories.\n *\n * A {@link ReactiveMessage} wraps a {@link Message} where every text field\n * is a `Signal<string>` \u2014 that's what makes per-token streaming cheap. The\n * reducer writes into those signals; the UI binds directly to them.\n *\n * This module exposes the types, the factories for the four canonical\n * message shapes (user / assistant / typewriter-welcome / instant-welcome),\n * and the conversion helpers `toReactive` / `fromReactive` used to bridge\n * with the wire format (`fromWireMessage`) and the request body builder\n * (`buildSendMessageRequest` consumes the plain `Message` shape).\n */\n\nimport { signal, type Signal } from \"@preact/signals\";\nimport { uuid7 } from \"@/core/ids\";\nimport type { WireMessage } from \"./protocol\";\nimport type { Message, MessagePart } from \"./types\";\n\n// ---- Reactive types ----------------------------------------------------\n\n/** A message stored in the App's `messagesSig`. */\nexport interface ReactiveMessage extends Omit<Message, \"parts\"> {\n partsSig: Signal<ReactivePart[]>;\n}\n\nexport type ReactivePart =\n | { kind: \"text\"; id: string; textSig: Signal<string>; doneSig: Signal<boolean> }\n | { kind: \"reasoning\"; id: string; textSig: Signal<string>; doneSig: Signal<boolean> }\n | {\n kind: \"tool\";\n toolCallId: string;\n toolName: string;\n inputPartialSig: Signal<string>;\n inputSig: Signal<unknown | undefined>;\n outputSig: Signal<unknown | undefined>;\n errorSig: Signal<string | undefined>;\n stateSig: Signal<\"input\" | \"awaiting\" | \"output\" | \"error\">;\n }\n | { kind: \"file\"; url: string; mediaType: string }\n | { kind: \"source\"; sourceId: string; url: string; title?: string };\n\n// ---- Factories ---------------------------------------------------------\n\n/** A user message \u2014 always complete, optional text + attachments. */\nexport function makeUserMessage(text: string, attachments: Message[\"attachments\"]): ReactiveMessage {\n const parts: ReactivePart[] = text ? [{ kind: \"text\", id: \"t0\", textSig: signal(text), doneSig: signal(true) }] : [];\n return {\n id: uuid7(),\n role: \"user\",\n createdAt: Date.now(),\n status: \"complete\",\n attachments,\n partsSig: signal(parts),\n };\n}\n\n/** An empty assistant shell that the reducer fills as chunks arrive. */\nexport function makeAssistantMessage(): ReactiveMessage {\n return {\n id: uuid7(),\n role: \"assistant\",\n createdAt: Date.now(),\n status: \"streaming\",\n partsSig: signal([]),\n };\n}\n\n/** A pushed-from-server welcome message. The two factories below build the\n * reactive shape for it \u2014 pick based on the configured animation mode.\n */\nexport interface WelcomeMessage {\n id: string;\n role: \"assistant\" | \"system\";\n text: string;\n}\n\n/**\n * A welcome message whose text signal starts empty \u2014 fills by the\n * typewriter animator (see `ui/welcome-animator.ts`).\n */\nexport function makeTypewriterMessage(m: WelcomeMessage): ReactiveMessage {\n return {\n id: m.id,\n role: m.role,\n createdAt: Date.now(),\n status: \"streaming\",\n partsSig: signal([{ kind: \"text\", id: `${m.id}-t0`, textSig: signal(\"\"), doneSig: signal(false) }]),\n };\n}\n\n/**\n * A welcome message rendered instantly (no typewriter). Used when\n * `welcome.animation === \"instant\"`.\n */\nexport function makeInstantWelcomeMessage(m: WelcomeMessage): ReactiveMessage {\n return toReactive({\n id: m.id,\n role: m.role,\n createdAt: Date.now(),\n status: \"complete\",\n parts: [{ kind: \"text\", id: `${m.id}-t0`, text: m.text, done: true }],\n });\n}\n\n// ---- Conversions -------------------------------------------------------\n\n/** Plain-data `Message` \u2192 reactive signals. */\nexport function toReactive(m: Message): ReactiveMessage {\n return {\n id: m.id,\n role: m.role,\n createdAt: m.createdAt,\n status: m.status,\n errorText: m.errorText,\n finishReason: m.finishReason,\n attachments: m.attachments,\n partsSig: signal(m.parts.map(partToReactive)),\n };\n}\n\n/**\n * Wire-format `WireMessage` \u2192 reactive. Used when hydrating a past chat\n * from `/ai/agent/list-messages?sessionId=...`. Text parts become signal-backed; file\n * parts pass through as-is (the wire shape for `file` already matches\n * the reactive shape for `kind: \"file\"`).\n */\nexport function fromWireMessage(w: WireMessage): ReactiveMessage {\n const parts: ReactivePart[] = w.parts\n .map((part, i): ReactivePart | null => {\n if (part.type === \"text\") {\n return { kind: \"text\", id: `${w.id}-t${i}`, textSig: signal(part.text), doneSig: signal(true) };\n }\n if (part.type === \"file\") {\n return { kind: \"file\", url: part.url, mediaType: part.mediaType };\n }\n return null;\n })\n .filter((p): p is ReactivePart => p !== null);\n return {\n id: w.id,\n role: w.role,\n createdAt: Date.now(),\n status: \"complete\",\n partsSig: signal(parts),\n };\n}\n\n/** Reactive \u2192 plain-data. Used when serialising messages for the wire. */\nexport function fromReactive(m: ReactiveMessage): Message {\n return {\n id: m.id,\n role: m.role,\n createdAt: m.createdAt,\n status: m.status,\n errorText: m.errorText,\n finishReason: m.finishReason,\n attachments: m.attachments,\n parts: m.partsSig.value.map(partFromReactive),\n };\n}\n\n/** Concatenate every text part into a single string. */\nexport function assistantText(m: ReactiveMessage): string {\n let out = \"\";\n for (const part of m.partsSig.value) {\n if (part.kind === \"text\") out += part.textSig.value;\n }\n return out;\n}\n\n// ---- internals ---------------------------------------------------------\n\nfunction partToReactive(p: MessagePart): ReactivePart {\n if (p.kind === \"text\" || p.kind === \"reasoning\") {\n return { kind: p.kind, id: p.id, textSig: signal(p.text), doneSig: signal(p.done) };\n }\n if (p.kind === \"tool\") {\n return {\n kind: \"tool\",\n toolCallId: p.toolCallId,\n toolName: p.toolName,\n inputPartialSig: signal(p.inputPartial),\n inputSig: signal(p.input),\n outputSig: signal(p.output),\n errorSig: signal(p.error),\n stateSig: signal(p.state),\n };\n }\n // Plain data parts (file / source) carry no signals \u2014 pass through.\n return p;\n}\n\nfunction partFromReactive(p: ReactivePart): MessagePart {\n if (p.kind === \"text\" || p.kind === \"reasoning\") {\n return { kind: p.kind, id: p.id, text: p.textSig.value, done: p.doneSig.value };\n }\n if (p.kind === \"tool\") {\n return {\n kind: \"tool\",\n toolCallId: p.toolCallId,\n toolName: p.toolName,\n inputPartial: p.inputPartialSig.value,\n input: p.inputSig.value,\n output: p.outputSig.value,\n error: p.errorSig.value,\n state: p.stateSig.value,\n };\n }\n return p;\n}\n", "/**\n * Stream reducer \u2014 applies {@link UIChunk}s to a reactive message.\n *\n * Signal-aware by design: each text part holds a `Signal<string>` internally,\n * so a token delta is *one* signal write that patches one DOM text node.\n * The outer `messagesSig` array is only mutated on *structural* changes\n * (new tool part arriving, error, finish) \u2014 list reconciliation stays cheap.\n *\n * Public API: `attach(msg)` to start tracking, `apply(chunk)` per chunk,\n * `detach()` when done.\n */\n\nimport { logger } from \"@/core/logger\";\nimport { signal, type Signal } from \"@preact/signals\";\nimport type { ReactiveMessage, ReactivePart } from \"./messages\";\nimport type { UIChunk } from \"./types\";\n\nconst log = logger.scope(\"reducer\");\n\nexport class StreamReducer {\n /** Currently-streaming assistant message; null when no stream is active. */\n private active: ReactiveMessage | null = null;\n\n constructor(private messagesSig: Signal<ReactiveMessage[]>) {}\n\n attach(msg: ReactiveMessage): void {\n this.active = msg;\n }\n\n detach(): void {\n this.active = null;\n }\n\n apply(chunk: UIChunk): void {\n const m = this.active;\n if (!m) return;\n\n switch (chunk.type) {\n case \"start\":\n case \"start-step\":\n case \"finish-step\":\n return;\n\n case \"finish\":\n log.debug(\"finish\", { reason: chunk.finishReason, canContinue: chunk.canContinue });\n m.status = \"complete\";\n if (chunk.finishReason) m.finishReason = chunk.finishReason;\n this.bumpDone(m);\n return;\n\n case \"error\":\n log.warn(\"stream error chunk\", { errorText: chunk.errorText });\n m.status = \"error\";\n m.errorText = chunk.errorText;\n this.bumpDone(m);\n return;\n\n case \"text-start\":\n ensureTextPart(m, \"text\", chunk.id);\n return;\n case \"text-delta\": {\n const p = ensureTextPart(m, \"text\", chunk.id);\n p.textSig.value += chunk.delta;\n return;\n }\n case \"text-end\":\n ensureTextPart(m, \"text\", chunk.id).doneSig.value = true;\n return;\n\n case \"reasoning-start\":\n // Backends may reuse one reasoning id across multiple thinking phases\n // (think \u2192 tool \u2192 think). Re-opening must clear `done` so the appended\n // deltas keep rendering instead of being treated as a finished block.\n ensureTextPart(m, \"reasoning\", chunk.id).doneSig.value = false;\n return;\n case \"reasoning-delta\": {\n const p = ensureTextPart(m, \"reasoning\", chunk.id);\n p.textSig.value += chunk.delta;\n return;\n }\n case \"reasoning-end\":\n ensureTextPart(m, \"reasoning\", chunk.id).doneSig.value = true;\n return;\n\n case \"tool-input-start\":\n case \"tool-input-delta\":\n case \"tool-input-available\":\n case \"tool-output-available\":\n case \"tool-output-error\":\n applyTool(m, chunk);\n return;\n\n case \"file\":\n appendPart(m, { kind: \"file\", url: chunk.url, mediaType: chunk.mediaType });\n return;\n\n case \"source-url\": {\n // De-duplicate by sourceId. Some backends emit a `source-url`\n // chunk every time the model cites a source mid-stream, so the\n // same id arrives multiple times for one logical citation. The\n // user-visible UI shows one row per source either way; appending\n // duplicates only produces a Preact \"two children with the same\n // key\" warning + a redundant link. Keep the first occurrence's\n // metadata so the title doesn't flicker if a later chunk is\n // missing it.\n const parts = m.partsSig.value;\n if (parts.some((p) => p.kind === \"source\" && p.sourceId === chunk.sourceId)) return;\n appendPart(m, { kind: \"source\", sourceId: chunk.sourceId, url: chunk.url, title: chunk.title });\n return;\n }\n\n case \"data-notification\":\n // Notifications surface in the panel toast, not the message body.\n return;\n\n default: {\n const _exhaustive: never = chunk;\n void _exhaustive;\n }\n }\n }\n\n /**\n * Terminal-state bump (`finish`/`error`). Touches the message's own `partsSig`\n * AND the list so subscribers re-render: a buffered bubble (subscribed to its\n * parts) flips out of the loading indicator into the finished reply, and the\n * list reflects the new `status`.\n */\n private bumpDone(m: ReactiveMessage): void {\n m.partsSig.value = [...m.partsSig.value];\n this.messagesSig.value = [...this.messagesSig.value];\n }\n}\n\n// ---- helpers -----------------------------------------------------------\n\ntype TextKind = \"text\" | \"reasoning\";\ntype TextPart = Extract<ReactivePart, { kind: TextKind }>;\n\nfunction ensureTextPart(m: ReactiveMessage, kind: TextKind, id: string): TextPart {\n const existing = m.partsSig.value.find((p): p is TextPart => p.kind === kind && p.id === id);\n if (existing) return existing;\n const part: TextPart = { kind, id, textSig: signal(\"\"), doneSig: signal(false) };\n appendPart(m, part);\n return part;\n}\n\ntype ToolPart = Extract<ReactivePart, { kind: \"tool\" }>;\n\nfunction ensureToolPart(m: ReactiveMessage, toolCallId: string, toolName?: string): ToolPart {\n const existing = m.partsSig.value.find((p): p is ToolPart => p.kind === \"tool\" && p.toolCallId === toolCallId);\n if (existing) return existing;\n const part: ToolPart = {\n kind: \"tool\",\n toolCallId,\n toolName: toolName ?? \"tool\",\n inputPartialSig: signal(\"\"),\n inputSig: signal(undefined),\n outputSig: signal(undefined),\n errorSig: signal(undefined),\n stateSig: signal(\"input\"),\n };\n appendPart(m, part);\n return part;\n}\n\nfunction appendPart(m: ReactiveMessage, part: ReactivePart): void {\n m.partsSig.value = [...m.partsSig.value, part];\n}\n\nfunction applyTool(m: ReactiveMessage, chunk: UIChunk): void {\n switch (chunk.type) {\n case \"tool-input-start\":\n ensureToolPart(m, chunk.toolCallId, chunk.toolName);\n return;\n case \"tool-input-delta\": {\n const p = ensureToolPart(m, chunk.toolCallId);\n p.inputPartialSig.value += chunk.delta;\n return;\n }\n case \"tool-input-available\": {\n const p = ensureToolPart(m, chunk.toolCallId, chunk.toolName);\n p.inputSig.value = chunk.input;\n p.stateSig.value = \"awaiting\";\n return;\n }\n case \"tool-output-available\": {\n const p = ensureToolPart(m, chunk.toolCallId);\n p.outputSig.value = chunk.output;\n p.stateSig.value = \"output\";\n return;\n }\n case \"tool-output-error\": {\n const p = ensureToolPart(m, chunk.toolCallId);\n p.errorSig.value = chunk.errorText;\n p.stateSig.value = \"error\";\n return;\n }\n default:\n // applyTool only called for tool-* chunks; other types are unreachable.\n return;\n }\n}\n", "/**\n * Per-widget client-side state.\n *\n * Storage holds three things, each under its own key:\n *\n * 1. **`visitorId`** \u2014 anonymous browser-level identifier, minted once per\n * widget instance and reused forever. Sent on every backend request so\n * the server can attach per-visitor settings, history, etc.\n * 2. **`sessionId`** \u2014 the active conversation thread. The backend is the\n * source of truth for messages; we store only the id and re-fetch on\n * reload via `transport.loadSession()`.\n * 3. **`userPrefs`** \u2014 user preferences (locale, panel size from drag,\n * sound mute). Cached locally for instant render-before-network, then\n * reconciled with the server's copy on each `connect()`.\n *\n * Keys are namespaced as `${brand.cssPrefix}.${widgetId}.${field}` so two\n * widgets on the same brand keep their state cleanly separated.\n *\n * Storage is pluggable via {@link ClientStorage} \u2014 pass a custom adapter\n * to add encryption, logging, or use a non-localStorage backend.\n */\n\nimport { BRAND } from \"./brand\";\nimport { uuid7 } from \"./ids\";\nimport { logger } from \"./logger\";\n\nconst log = logger.scope(\"persistence\");\nimport { type ClientStorage, defaultStorage } from \"./storage\";\n\n/** Drag-resized panel dimensions. Persisted only when resize is enabled. */\nexport interface WidgetSize {\n width: number;\n height: number;\n}\n\n/**\n * Panel size tier. Mirrors `ui/host-mode.ts`'s `PanelSize` \u2014 duplicated as a\n * literal here to keep `core` free of a `ui` import (one-way layering).\n */\nexport type PanelSizeTier = \"normal\" | \"expanded\" | \"fullscreen\";\n\n/**\n * User-changeable preferences. Sent to the backend on every request as a\n * `userPrefs` field so the server can detect drift (e.g. user changed\n * locale on another tab) and reconcile.\n *\n * Every field is optional \u2014 an unset key inherits the server / page-owner\n * default. Empty object `{}` means \"no preference recorded yet.\"\n */\nexport interface UserPrefs {\n /** BCP-47 tag picked from the language switcher. */\n locale?: string;\n /** Drag-resized panel dimensions. */\n widgetSize?: WidgetSize;\n /** Sound on/off toggle (icon button). */\n soundMuted?: boolean;\n /** Theme picked from the theme switcher. */\n themeMode?: \"auto\" | \"light\" | \"dark\";\n}\n\n/** Per-widget storage bundle. Create one per App instance. */\nexport interface Persistence {\n /**\n * Stable anonymous browser id. Minted on first call, then reused.\n * Performs a write on the first call to persist the mint \u2014 subsequent\n * calls return the cached value without touching storage.\n */\n getVisitorId(): string;\n /**\n * Replace the stored visitor id \u2014 only called when the server's\n * `/start-session` response rebinds the client-minted one (id\n * conflict, expiration, etc.). Most embeds never trigger this:\n * the mint-once-on-load path covers the common case. After\n * saving, subsequent `getVisitorId()` calls return the new id.\n */\n setVisitorId(id: string): void;\n /** Active conversation id, or `undefined` if no thread is in progress. */\n loadSessionId(): string | undefined;\n saveSessionId(id: string | undefined): void;\n /** Cached user preferences. Empty object when nothing is recorded. */\n loadUserPrefs(): UserPrefs;\n saveUserPrefs(s: UserPrefs): void;\n /**\n * Patch one or more fields without overwriting siblings. Internally a\n * load \u2192 spread \u2192 save round-trip \u2014 safe for concurrent calls within\n * a single tab (storage I/O is synchronous).\n */\n patchUserPrefs(patch: Partial<UserPrefs>): void;\n /**\n * Last open/closed state of the floating panel. `null` means the user\n * has never explicitly opened or closed it \u2014 the App falls back to\n * `options.startMinimized`. Stored as a separate key (not in\n * `userPrefs`) so it stays *per-device* \u2014 opening on a phone\n * doesn't force-open every other device's widget.\n */\n loadPanelOpen(): boolean | null;\n savePanelOpen(open: boolean): void;\n /**\n * Last panel size tier the user picked (expand / fullscreen). `null` = never\n * changed \u2192 caller falls back to `size.initialSize`. Per-device, like\n * {@link loadPanelOpen}, so making the panel bigger and refreshing keeps it\n * bigger. Ignored by always-fullscreen modes (standalone / page).\n */\n loadPanelSize(): PanelSizeTier | null;\n savePanelSize(size: PanelSizeTier): void;\n /**\n * Whether the user has dismissed the CURRENT hint copy. We persist the\n * exact text that was dismissed so reconfiguring the callout (a\n * different message) bypasses the prior dismissal \u2014 a NEW message is\n * a new ask, not a duplicate of the dismissed one.\n *\n * `currentText` is the callout text the widget intends to show right now.\n * Returns `true` only when the persisted \"last dismissed\" text matches.\n * `null`/empty `currentText` returns `false` (nothing to suppress).\n */\n loadCalloutDismissed(currentText: string | undefined): boolean;\n /**\n * Mark `currentText` as dismissed. Subsequent loads with the SAME text\n * suppress; loads with a different text show the callout again.\n */\n saveCalloutDismissed(currentText: string): void;\n /**\n * Page-mode-only: sidebar collapsed state. `null` means the user has\n * never explicitly toggled it \u2014 caller falls back to a default\n * (expanded). Per-device, like {@link loadPanelOpen}.\n */\n loadSidebarCollapsed(): boolean | null;\n saveSidebarCollapsed(collapsed: boolean): void;\n /**\n * Messenger-modules: the last active tab (its module `id`). `null` means the\n * user has never switched tabs \u2014 caller falls back to the first module.\n * Per-device, like {@link loadPanelOpen}; the caller is responsible for\n * ignoring a stored id that is no longer present.\n */\n loadActiveModule(): string | null;\n saveActiveModule(id: string): void;\n /**\n * Forget the active conversation. visitorId + settings + panelOpen are\n * deliberately preserved \u2014 clearing chat thread state is not the same\n * as forgetting who the user is or what they prefer.\n */\n clearSession(): void;\n}\n\n/**\n * Build a persistence bundle scoped to a widgetId. Calling twice with the\n * same id yields independent closures aimed at the same storage slots\n * \u2014 safe, just redundant.\n *\n * Pass a custom {@link ClientStorage} to add encryption, change backing\n * store, or hook reads + writes for analytics. Defaults to\n * `LocalStorageAdapter` (wraps `localStorage`).\n */\nexport function createPersistence(widgetId: string, storage: ClientStorage = defaultStorage): Persistence {\n const prefix = `${BRAND.cssPrefix}.${widgetId}`;\n const KEY_VISITOR = `${prefix}.visitorId`;\n const KEY_CHAT = `${prefix}.sessionId`;\n const KEY_USER_PREFS = `${prefix}.userPrefs`;\n const KEY_PANEL_OPEN = `${prefix}.panelOpen`;\n const KEY_PANEL_SIZE = `${prefix}.panelSize`;\n const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;\n const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;\n const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;\n\n const persistence: Persistence = {\n getVisitorId(): string {\n const existing = storage.get(KEY_VISITOR);\n if (existing) return existing;\n const minted = uuid7();\n storage.set(KEY_VISITOR, minted);\n return minted;\n },\n setVisitorId(id: string): void {\n log.debug(\"setVisitorId (rebind)\", { id, widgetId });\n storage.set(KEY_VISITOR, id);\n },\n loadSessionId(): string | undefined {\n return storage.get(KEY_CHAT) ?? undefined;\n },\n saveSessionId(id: string | undefined): void {\n log.trace(\"saveSessionId\", { id, widgetId });\n if (id) storage.set(KEY_CHAT, id);\n else storage.remove(KEY_CHAT);\n },\n loadUserPrefs(): UserPrefs {\n const raw = storage.get(KEY_USER_PREFS);\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return isPlainObject(parsed) ? (parsed as UserPrefs) : {};\n } catch (error) {\n log.warn(\"loadUserPrefs parse failed\", { error });\n return {};\n }\n },\n saveUserPrefs(s: UserPrefs): void {\n try {\n storage.set(KEY_USER_PREFS, JSON.stringify(s));\n } catch (error) {\n log.warn(\"saveUserPrefs serialise failed\", { error });\n }\n },\n patchUserPrefs(patch: Partial<UserPrefs>): void {\n const current = persistence.loadUserPrefs();\n persistence.saveUserPrefs({ ...current, ...patch });\n },\n loadPanelOpen(): boolean | null {\n const raw = storage.get(KEY_PANEL_OPEN);\n if (raw === \"1\") return true;\n if (raw === \"0\") return false;\n return null;\n },\n savePanelOpen(open: boolean): void {\n storage.set(KEY_PANEL_OPEN, open ? \"1\" : \"0\");\n },\n loadPanelSize(): PanelSizeTier | null {\n const raw = storage.get(KEY_PANEL_SIZE);\n return raw === \"normal\" || raw === \"expanded\" || raw === \"fullscreen\" ? raw : null;\n },\n savePanelSize(size: PanelSizeTier): void {\n storage.set(KEY_PANEL_SIZE, size);\n },\n loadCalloutDismissed(currentText: string | undefined): boolean {\n if (!currentText) return false;\n const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);\n return !!dismissedText && dismissedText === currentText;\n },\n saveCalloutDismissed(currentText: string): void {\n if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);\n else storage.remove(KEY_CALLOUT_DISMISSED);\n },\n loadSidebarCollapsed(): boolean | null {\n const raw = storage.get(KEY_SIDEBAR_COLLAPSED);\n if (raw === \"1\") return true;\n if (raw === \"0\") return false;\n return null;\n },\n saveSidebarCollapsed(collapsed: boolean): void {\n storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? \"1\" : \"0\");\n },\n loadActiveModule(): string | null {\n return storage.get(KEY_ACTIVE_MODULE) || null;\n },\n saveActiveModule(id: string): void {\n storage.set(KEY_ACTIVE_MODULE, id);\n },\n clearSession(): void {\n storage.remove(KEY_CHAT);\n // visitorId + userPrefs preserved \u2014 they're identity / preference,\n // not conversation state. Users expect their language and panel size\n // to survive a \"new conversation\" click.\n },\n };\n\n return persistence;\n}\n\n/**\n * Defensive parse guard \u2014 `JSON.parse('\"oops\"')` would otherwise hand back\n * a string and the spread in `patchUserPrefs` would silently produce\n * something nonsense. We only accept object-shaped settings.\n */\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n", "/**\n * Tiny Web Audio wrapper for synthesised notification tones.\n *\n * The `AudioContext` is created lazily on the first user gesture (browser\n * autoplay policy). Each play creates a fresh `OscillatorNode` chained\n * through a gain envelope \u2014 nodes are cheap; reusing them risks\n * `InvalidStateError` from the double-start() guard.\n *\n * Also supports decoded URL audio: the first `play(url)` fetches + decodes\n * into a shared `AudioBuffer` cache, then plays via `BufferSourceNode`.\n */\n\nimport { logger } from \"@/core/logger\";\nimport type { ToneSpec } from \"./defaults\";\n\nconst log = logger.scope(\"feedback:audio\");\n\ninterface MaybeWindow {\n AudioContext?: typeof AudioContext;\n webkitAudioContext?: typeof AudioContext;\n}\n\nexport class AudioEngine {\n private ctx: AudioContext | null = null;\n private master: GainNode | null = null;\n private readonly buffers = new Map<string, AudioBuffer | Promise<AudioBuffer | null>>();\n\n /** Lazily create the AudioContext. Safe to call on every user gesture. */\n unlock(): void {\n if (this.ctx) {\n // Context may have been auto-suspended; resume on user gesture.\n if (this.ctx.state === \"suspended\") void this.ctx.resume();\n return;\n }\n const Ctor = (globalThis as MaybeWindow).AudioContext ?? (globalThis as MaybeWindow).webkitAudioContext;\n if (!Ctor) return;\n try {\n this.ctx = new Ctor();\n this.master = this.ctx.createGain();\n this.master.gain.value = 1;\n this.master.connect(this.ctx.destination);\n } catch (err) {\n log.warn(\"AudioContext init failed\", { err });\n }\n }\n\n /** Route all sounds through this multiplier. Call on volume change. */\n setVolume(volume: number): void {\n if (!this.master || !this.ctx) return;\n // Equal-power taper on the low end feels more natural than linear.\n const clamped = Math.max(0, Math.min(1, volume));\n this.master.gain.setTargetAtTime(clamped, this.ctx.currentTime, 0.01);\n }\n\n /** Play a synthesised tone spec. Silently noops if context isn't live. */\n playTone(spec: ToneSpec): void {\n const ctx = this.ctx;\n const master = this.master;\n if (!ctx || !master || ctx.state !== \"running\") return;\n const now = ctx.currentTime;\n const noteSec = spec.noteMs / 1000;\n spec.notes.forEach((freq, i) => {\n const osc = ctx.createOscillator();\n osc.type = spec.type;\n osc.frequency.value = freq;\n\n // Per-note envelope: 5ms attack, linear decay to silent over the note.\n const env = ctx.createGain();\n const start = now + i * noteSec;\n env.gain.setValueAtTime(0.0001, start);\n env.gain.exponentialRampToValueAtTime(spec.peakGain, start + 0.005);\n env.gain.exponentialRampToValueAtTime(0.0001, start + noteSec);\n\n osc.connect(env).connect(master);\n osc.start(start);\n osc.stop(start + noteSec + 0.01);\n });\n }\n\n /** Play a URL-backed sample. First call decodes; subsequent calls are cached. */\n async playUrl(url: string): Promise<void> {\n const ctx = this.ctx;\n const master = this.master;\n if (!ctx || !master || ctx.state !== \"running\") return;\n const buf = await this.getBuffer(url);\n if (!buf) return;\n const src = ctx.createBufferSource();\n src.buffer = buf;\n src.connect(master);\n src.start();\n }\n\n destroy(): void {\n if (this.ctx) void this.ctx.close().catch(() => undefined);\n this.ctx = null;\n this.master = null;\n this.buffers.clear();\n }\n\n private async getBuffer(url: string): Promise<AudioBuffer | null> {\n if (!this.ctx) return null;\n const cached = this.buffers.get(url);\n if (cached) return cached instanceof Promise ? cached : cached;\n const ctx = this.ctx;\n const promise = fetch(url)\n .then((r) => (r.ok ? r.arrayBuffer() : Promise.reject(new Error(`fetch ${r.status}`))))\n .then((ab) => ctx.decodeAudioData(ab))\n .then((buf) => {\n this.buffers.set(url, buf);\n return buf;\n })\n .catch((err) => {\n log.warn(\"audio fetch/decode failed\", { url, err });\n this.buffers.delete(url);\n return null;\n });\n this.buffers.set(url, promise);\n return promise;\n }\n}\n", "/**\n * Per-event default tones + vibration patterns.\n *\n * Tones are described declaratively (frequencies + envelope timing) and\n * synthesised on demand via OscillatorNode in `audio.ts`. Shipping no\n * audio asset keeps the bundle tiny and avoids CDN coupling.\n */\n\nimport type { FeedbackEvent } from \"@/core/config/types\";\n\nexport interface ToneSpec {\n /** One or more frequencies (Hz). Each plays as a note in sequence. */\n notes: readonly number[];\n /** Per-note duration (ms). Total tone length = notes.length * noteMs. */\n noteMs: number;\n /** Oscillator waveform. Sine is softest; triangle/square are brighter. */\n type: OscillatorType;\n /** Peak gain (0..1). Scaled further by the user's `volume` setting. */\n peakGain: number;\n}\n\n/** Vibration patterns per event (Android `navigator.vibrate` shape). */\nexport const DEFAULT_HAPTIC_PATTERNS: Record<FeedbackEvent, number | number[]> = {\n messageReceived: 10,\n messageSent: 0, // no haptic on send (the user initiated it)\n error: [20, 40, 20],\n voiceStart: 10,\n voiceStop: 0,\n};\n\n/** Tones keyed by event. Designed to be subtle + distinguishable. */\nexport const DEFAULT_TONES: Record<FeedbackEvent, ToneSpec | null> = {\n // Soft two-note ascending chime \u2014 \"new message\" pattern iMessage + Slack use.\n messageReceived: { notes: [880, 1318.51], noteMs: 90, type: \"sine\", peakGain: 0.45 },\n // Single high soft tick \u2014 confirms the send.\n messageSent: { notes: [1568], noteMs: 60, type: \"sine\", peakGain: 0.25 },\n // Descending two-note \u2014 warning feel.\n error: { notes: [523.25, 392], noteMs: 120, type: \"triangle\", peakGain: 0.55 },\n // Tick-up for mic on.\n voiceStart: { notes: [1318.51], noteMs: 50, type: \"sine\", peakGain: 0.3 },\n // Tick-down for mic off.\n voiceStop: { notes: [880], noteMs: 50, type: \"sine\", peakGain: 0.3 },\n};\n", "/**\n * Haptic feedback.\n *\n * Two paths:\n * - Android / any browser exposing `navigator.vibrate` \u2192 pass the pattern through.\n * - iOS Safari 17.4+ \u2192 fabricate an invisible `<input type=\"checkbox\" switch>`\n * + `<label>`, programmatically `click()` the label. WebKit emits a native\n * system haptic. It's one pattern (no customisation) but it's better than\n * nothing on iOS \u2014 the standard Vibration API still isn't shipped.\n *\n * Everything else (older Safari, desktop without vibrators) silently noops.\n */\n\nimport { BRAND } from \"@/core/brand\";\n\ntype SwitchEl = { label: HTMLLabelElement; input: HTMLInputElement };\n\n/**\n * Module-level cache of the hidden `<input type=\"checkbox\" switch>` +\n * `<label>` used for the iOS 17.4+ haptic hack. Ref-counted so destroying\n * one widget doesn't rip the DOM out from under its siblings:\n * `acquireHaptics()` bumps, `releaseHaptics()` decrements, and we only\n * actually remove the nodes when the count hits zero.\n */\nlet switchEl: SwitchEl | null = null;\nlet refCount = 0;\n\nfunction canVibrate(): boolean {\n return typeof navigator !== \"undefined\" && typeof navigator.vibrate === \"function\";\n}\n\nfunction createSwitch(): SwitchEl | null {\n if (typeof document === \"undefined\") return null;\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.setAttribute(\"switch\", \"\");\n input.setAttribute(\"aria-hidden\", \"true\");\n input.tabIndex = -1;\n // ID is brand-prefixed for grep-ability (multi-brand repo). Random suffix\n // keeps it unique across co-mounted widgets sharing a document.\n input.id = `__${BRAND.cssPrefix}_haptic_${Math.random().toString(36).slice(2)}`;\n const label = document.createElement(\"label\");\n label.htmlFor = input.id;\n label.setAttribute(\"aria-hidden\", \"true\");\n // Off-screen but still clickable.\n const hideCss =\n \"position:absolute!important;width:1px!important;height:1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;opacity:0!important;pointer-events:none!important;\";\n input.style.cssText = hideCss;\n label.style.cssText = hideCss;\n document.body.append(input, label);\n return { input, label };\n}\n\n/**\n * Fire a haptic. `pattern` is the `navigator.vibrate` shape \u2014 a single ms\n * duration or an alternating `[vibrate, pause, vibrate, \u2026]` array. On iOS\n * the pattern is ignored (one system tick is all you get).\n */\nexport function haptic(pattern: number | number[]): void {\n // Skip \"no haptic\" sentinel.\n if (pattern === 0 || (Array.isArray(pattern) && pattern.every((n) => n === 0))) return;\n\n if (canVibrate()) {\n try {\n navigator.vibrate(pattern);\n return;\n } catch {\n // Fall through to iOS hack on browsers that partially implement.\n }\n }\n\n // Lazy-create the iOS switch on first haptic demand.\n if (!switchEl) switchEl = createSwitch();\n if (!switchEl) return;\n try {\n switchEl.label.click();\n } catch {\n // No-op.\n }\n}\n\n/**\n * Register a user of the haptics module. Must be paired with\n * `releaseHaptics()` on destroy. Returns an idempotent release function \u2014\n * calling it twice only decrements once.\n */\nexport function acquireHaptics(): () => void {\n refCount++;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n refCount = Math.max(0, refCount - 1);\n if (refCount === 0 && switchEl) {\n switchEl.input.remove();\n switchEl.label.remove();\n switchEl = null;\n }\n };\n}\n\n/** Test-only: force-reset the cache between cases. Not part of the public API. */\nexport function disposeHaptics(): void {\n if (switchEl) {\n switchEl.input.remove();\n switchEl.label.remove();\n }\n switchEl = null;\n refCount = 0;\n}\n", "/**\n * Public entry for the feedback module \u2014 audio + haptics in one surface.\n *\n * ```ts\n * const fb = new FeedbackBus(resolvedOptions.sound, resolvedOptions.haptics);\n * hostElement.addEventListener(\"click\", () => fb.unlock(), { once: true });\n * fb.play(\"messageReceived\"); // plays tone + vibrates\n * fb.setMuted(true); // killswitch\n * ```\n *\n * The bus is **stateless about persistence** \u2014 `mutedSig` is a plain\n * reactive signal. The App layer subscribes to it and persists changes\n * via the unified `Persistence` layer (`userPrefs.soundMuted`),\n * pushing them to the backend at the same time.\n */\n\nimport type { FeedbackEvent, HapticsOptions, SoundOptions } from \"@/core/config/types\";\nimport { logger } from \"@/core/logger\";\nimport { signal, type Signal } from \"@preact/signals\";\nimport { AudioEngine } from \"./audio\";\nimport { DEFAULT_HAPTIC_PATTERNS, DEFAULT_TONES } from \"./defaults\";\n\nconst log = logger.scope(\"feedback\");\nimport { acquireHaptics, haptic } from \"./haptics\";\n\nconst DEDUP_WINDOW_MS = 100;\n\nexport class FeedbackBus {\n /**\n * User/session mute state. Reactive \u2014 UI subscribes to drive the toggle\n * icon, and the App layer subscribes to persist changes via the unified\n * `Persistence` layer.\n */\n readonly mutedSig: Signal<boolean> = signal(false);\n\n private readonly audio = new AudioEngine();\n private readonly releaseHaptics: () => void;\n private sound: SoundOptions;\n private haptics: HapticsOptions;\n private lastPlayed = new Map<FeedbackEvent, number>();\n private reducedMotionMq: MediaQueryList | null = null;\n\n constructor(sound: SoundOptions, haptics: HapticsOptions) {\n this.sound = sound;\n this.haptics = haptics;\n // Ref-count the iOS haptic switch so a sibling widget surviving past\n // this one's destroy() still has its switch DOM available.\n this.releaseHaptics = acquireHaptics();\n this.audio.setVolume(this.effectiveVolume());\n if (typeof matchMedia !== \"undefined\") {\n this.reducedMotionMq = matchMedia(\"(prefers-reduced-motion: reduce)\");\n }\n }\n\n /**\n * Unlock the AudioContext. Must be called from inside a user gesture\n * (click, keydown, touchstart) \u2014 browsers reject any earlier creation.\n * Safe to call repeatedly.\n */\n unlock(): void {\n this.audio.unlock();\n this.audio.setVolume(this.effectiveVolume());\n }\n\n /** Update config without re-constructing. Called on server-settings merge. */\n update(sound: SoundOptions, haptics: HapticsOptions): void {\n this.sound = sound;\n this.haptics = haptics;\n this.audio.setVolume(this.effectiveVolume());\n }\n\n /**\n * Set the mute state explicitly. Used by the App on mount (to apply the\n * cached `userPrefs.soundMuted`) and after the server pushes a\n * different value via `connect()`. No-op when value is unchanged so\n * subscribers don't double-fire.\n */\n setMuted(muted: boolean): void {\n if (this.mutedSig.value !== muted) this.mutedSig.value = muted;\n }\n\n /**\n * Toggle mute. Persistence is handled by the App's `mutedSig.subscribe`\n * callback \u2014 this method just flips the signal.\n */\n toggleMuted(): void {\n this.mutedSig.value = !this.mutedSig.value;\n }\n\n /** Fire sound + haptic for an event. Respects all guards. */\n play(event: FeedbackEvent): void {\n if (this.mutedSig.value) return;\n if (this.isDeduped(event)) return;\n this.lastPlayed.set(event, performance.now());\n\n if (this.sound.enabled) this.playSound(event);\n if (this.haptics.enabled && this.isTouchDevice()) this.playHaptic(event);\n }\n\n /** Release the audio context + iOS hack DOM. Call on widget unmount. */\n destroy(): void {\n this.audio.destroy();\n this.releaseHaptics();\n }\n\n // ---- internals --------------------------------------------------------\n\n private isDeduped(event: FeedbackEvent): boolean {\n const prev = this.lastPlayed.get(event);\n if (prev === undefined) return false;\n return performance.now() - prev < DEDUP_WINDOW_MS;\n }\n\n private playSound(event: FeedbackEvent): void {\n const override = this.sound.events?.[event];\n if (override === false) return;\n if (typeof override === \"string\") {\n this.audio.playUrl(override).catch((err) => log.warn(\"audio play failed\", { err, url: override }));\n return;\n }\n const tone = DEFAULT_TONES[event];\n if (!tone) return;\n this.audio.playTone(tone);\n }\n\n private playHaptic(event: FeedbackEvent): void {\n // prefers-reduced-motion \u2192 disable haptics entirely. They're a form of\n // motion feedback and can trigger vestibular discomfort for some users.\n if (this.reducedMotionMq?.matches) return;\n const override = this.haptics.events?.[event];\n if (override === false) return;\n const pattern = override === true || override === undefined ? DEFAULT_HAPTIC_PATTERNS[event] : override;\n haptic(pattern);\n }\n\n /** prefers-reduced-motion halves the volume as a \"quiet please\" signal. */\n private effectiveVolume(): number {\n const base = this.sound.volume ?? 0.3;\n return this.reducedMotionMq?.matches ? base * 0.5 : base;\n }\n\n private isTouchDevice(): boolean {\n if (typeof window === \"undefined\") return false;\n return matchMedia(\"(hover: none) and (pointer: coarse)\").matches;\n }\n}\n", "/**\n * User-facing message for a caught request error \u2014 friendly + status-aware.\n * Never leaks raw `\"server responded 429\u2026\"` text. Shared by the chat error\n * bubble and the content surfaces (help / news) so every \"request failed\" path\n * speaks the same language.\n */\n\nimport type { Strings } from \"@/core/config\";\nimport { StreamError } from \"@/stream/types\";\n\nexport function errorMessageFor(error: unknown, strings: Strings): string {\n if (error instanceof StreamError && error.status === 429) return strings.errorRateLimited;\n return strings.errorGeneric;\n}\n", "const ENCODED_ENTITIES = /[\"&<]/;\n\n/** @param {string} str */\nexport function encodeEntities(str) {\n\t// Skip all work for strings with no entities needing encoding:\n\tif (str.length === 0 || ENCODED_ENTITIES.test(str) === false) return str;\n\n\tlet last = 0,\n\t\ti = 0,\n\t\tout = '',\n\t\tch = '';\n\n\t// Seek forward in str until the next entity char:\n\tfor (; i < str.length; i++) {\n\t\tswitch (str.charCodeAt(i)) {\n\t\t\tcase 34:\n\t\t\t\tch = '"';\n\t\t\t\tbreak;\n\t\t\tcase 38:\n\t\t\t\tch = '&';\n\t\t\t\tbreak;\n\t\t\tcase 60:\n\t\t\t\tch = '<';\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tcontinue;\n\t\t}\n\t\t// Append skipped/buffered characters and the encoded entity:\n\t\tif (i !== last) out += str.slice(last, i);\n\t\tout += ch;\n\t\t// Start the next seek/buffer after the entity's offset:\n\t\tlast = i + 1;\n\t}\n\tif (i !== last) out += str.slice(last, i);\n\treturn out;\n}\n", "/** Normal hydration that attaches to a DOM tree but does not diff it. */\nexport const MODE_HYDRATE = 1 << 5;\n/** Signifies this VNode suspended on the previous render */\nexport const MODE_SUSPENDED = 1 << 7;\n/** Indicates that this node needs to be inserted while patching children */\nexport const INSERT_VNODE = 1 << 2;\n/** Indicates a VNode has been matched with another VNode in the diff */\nexport const MATCHED = 1 << 1;\n\n/** Reset all mode flags */\nexport const RESET_MODE = ~(MODE_HYDRATE | MODE_SUSPENDED);\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\nexport const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\nexport const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n\nexport const NULL = null;\nexport const UNDEFINED = undefined;\nexport const EMPTY_OBJ = /** @type {any} */ ({});\nexport const EMPTY_ARR = [];\nexport const IS_NON_DIMENSIONAL =\n\t/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;\n", "import { options, Fragment } from 'preact';\nimport { encodeEntities } from './utils';\nimport { IS_NON_DIMENSIONAL } from '../../src/constants';\n\nlet vnodeId = 0;\n\nconst isArray = Array.isArray;\n\n/**\n * @fileoverview\n * This file exports various methods that implement Babel's \"automatic\" JSX runtime API:\n * - jsx(type, props, key)\n * - jsxs(type, props, key)\n * - jsxDEV(type, props, key, __source, __self)\n *\n * The implementation of createVNode here is optimized for performance.\n * Benchmarks: https://esbench.com/bench/5f6b54a0b4632100a7dcd2b3\n */\n\n/**\n * JSX.Element factory used by Babel's {runtime:\"automatic\"} JSX transform\n * @param {VNode['type']} type\n * @param {VNode['props']} props\n * @param {VNode['key']} [key]\n * @param {unknown} [isStaticChildren]\n * @param {unknown} [__source]\n * @param {unknown} [__self]\n */\nfunction createVNode(type, props, key, isStaticChildren, __source, __self) {\n\tif (!props) props = {};\n\t// We'll want to preserve `ref` in props to get rid of the need for\n\t// forwardRef components in the future, but that should happen via\n\t// a separate PR.\n\tlet normalizedProps = props,\n\t\tref,\n\t\ti;\n\n\tif ('ref' in normalizedProps) {\n\t\tnormalizedProps = {};\n\t\tfor (i in props) {\n\t\t\tif (i == 'ref') {\n\t\t\t\tref = props[i];\n\t\t\t} else {\n\t\t\t\tnormalizedProps[i] = props[i];\n\t\t\t}\n\t\t}\n\t}\n\n\t/** @type {VNode & { __source: any; __self: any }} */\n\tconst vnode = {\n\t\ttype,\n\t\tprops: normalizedProps,\n\t\tkey,\n\t\tref,\n\t\t_children: null,\n\t\t_parent: null,\n\t\t_depth: 0,\n\t\t_dom: null,\n\t\t_component: null,\n\t\tconstructor: undefined,\n\t\t_original: --vnodeId,\n\t\t_index: -1,\n\t\t_flags: 0,\n\t\t__source,\n\t\t__self\n\t};\n\n\t// If a Component VNode, check for and apply defaultProps.\n\t// Note: `type` is often a String, and can be `undefined` in development.\n\tif (typeof type === 'function' && (ref = type.defaultProps)) {\n\t\tfor (i in ref)\n\t\t\tif (normalizedProps[i] === undefined) {\n\t\t\t\tnormalizedProps[i] = ref[i];\n\t\t\t}\n\t}\n\n\tif (options.vnode) options.vnode(vnode);\n\treturn vnode;\n}\n\n/**\n * Create a template vnode. This function is not expected to be\n * used directly, but rather through a precompile JSX transform\n * @param {string[]} templates\n * @param {Array<string | null | VNode>} exprs\n * @returns {VNode}\n */\nfunction jsxTemplate(templates, ...exprs) {\n\tconst vnode = createVNode(Fragment, { tpl: templates, exprs });\n\t// Bypass render to string top level Fragment optimization\n\tvnode.key = vnode._vnode;\n\treturn vnode;\n}\n\nconst JS_TO_CSS = {};\nconst CSS_REGEX = /[A-Z]/g;\n\n/**\n * Unwrap potential signals.\n * @param {*} value\n * @returns {*}\n */\nfunction normalizeAttrValue(value) {\n\treturn value !== null &&\n\t\ttypeof value === 'object' &&\n\t\ttypeof value.valueOf === 'function'\n\t\t? value.valueOf()\n\t\t: value;\n}\n\n/**\n * Serialize an HTML attribute to a string. This function is not\n * expected to be used directly, but rather through a precompile\n * JSX transform\n * @param {string} name The attribute name\n * @param {*} value The attribute value\n * @returns {string}\n */\nfunction jsxAttr(name, value) {\n\tif (options.attr) {\n\t\tconst result = options.attr(name, value);\n\t\tif (typeof result === 'string') return result;\n\t}\n\n\tvalue = normalizeAttrValue(value);\n\n\tif (name === 'ref' || name === 'key') return '';\n\tif (name === 'style' && typeof value === 'object') {\n\t\tlet str = '';\n\t\tfor (let prop in value) {\n\t\t\tlet val = value[prop];\n\t\t\tif (val != null && val !== '') {\n\t\t\t\tconst name =\n\t\t\t\t\tprop[0] == '-'\n\t\t\t\t\t\t? prop\n\t\t\t\t\t\t: JS_TO_CSS[prop] ||\n\t\t\t\t\t\t\t(JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());\n\n\t\t\t\tlet suffix = ';';\n\t\t\t\tif (\n\t\t\t\t\ttypeof val === 'number' &&\n\t\t\t\t\t// Exclude custom-attributes\n\t\t\t\t\t!name.startsWith('--') &&\n\t\t\t\t\t!IS_NON_DIMENSIONAL.test(name)\n\t\t\t\t) {\n\t\t\t\t\tsuffix = 'px;';\n\t\t\t\t}\n\t\t\t\tstr = str + name + ':' + val + suffix;\n\t\t\t}\n\t\t}\n\t\treturn name + '=\"' + encodeEntities(str) + '\"';\n\t}\n\n\tif (\n\t\tvalue == null ||\n\t\tvalue === false ||\n\t\ttypeof value === 'function' ||\n\t\ttypeof value === 'object'\n\t) {\n\t\treturn '';\n\t} else if (value === true) return name;\n\n\treturn name + '=\"' + encodeEntities('' + value) + '\"';\n}\n\n/**\n * Escape a dynamic child passed to `jsxTemplate`. This function\n * is not expected to be used directly, but rather through a\n * precompile JSX transform\n * @param {*} value\n * @returns {string | null | VNode | Array<string | null | VNode>}\n */\nfunction jsxEscape(value) {\n\tif (\n\t\tvalue == null ||\n\t\ttypeof value === 'boolean' ||\n\t\ttypeof value === 'function'\n\t) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === 'object') {\n\t\t// Check for VNode\n\t\tif (value.constructor === undefined) return value;\n\n\t\tif (isArray(value)) {\n\t\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\t\tvalue[i] = jsxEscape(value[i]);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t}\n\n\treturn encodeEntities('' + value);\n}\n\nexport {\n\tcreateVNode as jsx,\n\tcreateVNode as jsxs,\n\tcreateVNode as jsxDEV,\n\tFragment,\n\t// precompiled JSX transform\n\tjsxTemplate,\n\tjsxAttr,\n\tjsxEscape\n};\n", "// Inline SVG icons. Tiny, tree-shaken, no icon font load.\n// Each icon is a stateless functional component returning an <svg>.\n\nimport type { JSX } from \"preact\";\n\nconst props = {\n fill: \"none\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\" as const,\n strokeLinejoin: \"round\" as const,\n strokeWidth: 2,\n viewBox: \"0 0 24 24\",\n};\n\nexport const ChatIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M21 12a8 8 0 0 1-12 6.93L3 21l1.65-5.16A8 8 0 1 1 21 12Z\" />\n </svg>\n);\n\nexport const CloseIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n);\n\n/** Plus / \"new\" icon. */\nexport const PlusIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M12 5v14M5 12h14\" />\n </svg>\n);\n\n/** \"Paper-plane\" style send icon (triangular envelope). */\nexport const PaperPlaneIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m22 2-7 20-4-9-9-4Z\" />\n <path d=\"M22 2 11 13\" />\n </svg>\n);\n\n/** Vertical arrow pointing up \u2014 the iMessage / ChatGPT default. */\nexport const ArrowUpIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M12 19V5M5 12l7-7 7 7\" />\n </svg>\n);\n\n/** Chat-bubble send (right-pointing triangle inside a rounded square). */\nexport const SendIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 20V4l18 8-18 8Z\" />\n </svg>\n);\n\n/** Chevron-right \u2014 minimal, for \"continue\" / \"submit\" feel. */\nexport const ChevronRightIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n);\n\nexport const StopIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n </svg>\n);\n\nexport const PaperclipIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m21 11-9 9a5.5 5.5 0 0 1-7.78-7.78l8.49-8.49a3.67 3.67 0 0 1 5.19 5.19l-8.49 8.49a1.83 1.83 0 0 1-2.6-2.6l7.78-7.78\" />\n </svg>\n);\n\nexport const MicIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <rect x=\"9\" y=\"2\" width=\"6\" height=\"12\" rx=\"3\" />\n <path d=\"M5 10a7 7 0 0 0 14 0M12 19v3\" />\n </svg>\n);\n\nexport const ExpandIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 8V3h5M21 8V3h-5M3 16v5h5M21 16v5h-5\" />\n </svg>\n);\n\nexport const CollapseIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M8 3v5H3M16 3v5h5M8 21v-5H3M16 21v-5h5\" />\n </svg>\n);\n\nexport const FullscreenIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 3h7v2H5v5H3zM21 3h-7v2h5v5h2zM3 21h7v-2H5v-5H3zM21 21h-7v-2h5v-5h2z\" />\n </svg>\n);\n\nexport const FullscreenExitIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M10 3v5H3M14 3v5h7M10 21v-5H3M14 21v-5h7\" />\n </svg>\n);\n\nexport const ResizeHandleIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\" viewBox=\"0 0 10 10\">\n <path d=\"M9 1L1 9M9 5L5 9M9 9H9\" strokeWidth=\"1.5\" />\n </svg>\n);\n\nexport const MoreIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"5\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"12\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"12\" cy=\"19\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n);\n\nexport const SpeakerIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M11 5 6 9H2v6h4l5 4zM15 9a5 5 0 0 1 0 6M18 7a9 9 0 0 1 0 10\" />\n </svg>\n);\n\nexport const SpeakerMutedIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M11 5 6 9H2v6h4l5 4zM22 9l-6 6M16 9l6 6\" />\n </svg>\n);\n\nexport const CheckIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n);\n\nexport const HistoryIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 3v6h6\" />\n <path d=\"M3.05 13A9 9 0 1 0 6 5.3L3 9\" />\n <path d=\"M12 7v5l3 2\" />\n </svg>\n);\n\nexport const GlobeIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M2 12h20\" />\n <path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\" />\n </svg>\n);\n\nexport const BackIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M19 12H5M12 19l-7-7 7-7\" />\n </svg>\n);\n\nexport const PopOutIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M15 3h6v6M10 14 21 3M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5\" />\n </svg>\n);\n\nexport const TrashIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" />\n </svg>\n);\n\nexport const FileIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z\" />\n <path d=\"M14 2v6h6\" />\n </svg>\n);\n\nexport const SparkIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m12 3-1.5 4.5L6 9l4.5 1.5L12 15l1.5-4.5L18 9l-4.5-1.5Z\" />\n <path d=\"M19 15v4M17 17h4M5 3v2M4 4h2\" />\n </svg>\n);\n\nexport const MessageIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z\" />\n </svg>\n);\n\nexport const HelpIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\" />\n <path d=\"M12 17h.01\" />\n </svg>\n);\n\nexport const WandIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m15 4 3 3-9 9-3-3z\" />\n <path d=\"M18 2v2M20 4h2M3 20l3-3M3 16h2M5 14v2\" />\n </svg>\n);\n\n/** Friendly robot face \u2014 the \"AI assistant\" preset. */\nexport const BotIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <rect x=\"4\" y=\"8\" width=\"16\" height=\"12\" rx=\"2\" />\n <path d=\"M12 8V4\" />\n <circle cx=\"12\" cy=\"3\" r=\"1\" />\n <path d=\"M2 14h2M20 14h2M9 13h.01M15 13h.01M9.5 17h5\" />\n </svg>\n);\n\n/** Support headset with mic boom \u2014 the \"live support / talk to a human\" preset. */\nexport const HeadsetIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M4 14v-2a8 8 0 0 1 16 0v2\" />\n <rect x=\"2\" y=\"14\" width=\"4\" height=\"6\" rx=\"1.5\" />\n <rect x=\"18\" y=\"14\" width=\"4\" height=\"6\" rx=\"1.5\" />\n <path d=\"M20 20v1a3 3 0 0 1-3 3h-5\" />\n </svg>\n);\n\n/** Life-ring \u2014 the \"help / rescue\" preset. */\nexport const LifebuoyIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"m4.93 4.93 4.24 4.24M14.83 14.83l4.24 4.24M14.83 9.17l4.24-4.24M4.93 19.07l4.24-4.24\" />\n </svg>\n);\n\n/** Magnifier \u2014 search bars (Home + Help). */\nexport const SearchIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n);\n\n/** Green check in a circle \u2014 Home status card \"operational\". */\nexport const StatusOkIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"m8.5 12 2.5 2.5 4.5-5\" />\n </svg>\n);\n\n/** House \u2014 the Home tab in the messenger bottom nav. */\nexport const HomeIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 9.5 12 3l9 6.5\" />\n <path d=\"M5 9v11a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9\" />\n </svg>\n);\n\n/** Megaphone \u2014 the News tab in the messenger bottom nav. */\nexport const NewsIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m3 11 15-5v12L3 13z\" />\n <path d=\"M3 11v2a2 2 0 0 0 2 2h1V9H5a2 2 0 0 0-2 2z\" />\n <path d=\"M7 15v3a2 2 0 0 0 4 0v-2\" />\n <path d=\"M21 10a3 3 0 0 1 0 4\" />\n </svg>\n);\n\n/** Two overlapping speech bubbles \u2014 the \"conversation / chat\" preset. */\nexport const BubblesIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M14 9a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z\" />\n <path d=\"M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1\" />\n </svg>\n);\n\nexport const SunIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41\" />\n </svg>\n);\n\nexport const MoonIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n);\n\n/** Trigger for the theme switcher \u2014 half-sun/half-moon hints at \"system\" (auto). */\nexport const ThemeAutoIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 3v18\" />\n <path d=\"M12 3a9 9 0 0 1 0 18z\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n);\n\n/** Magnifier with a smile \u2014 friendly \"search / ask\" launcher. */\nexport const SearchSmileIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\" />\n <path d=\"M8 11a2.5 2.5 0 0 0 5 0\" />\n <path d=\"m20 20-4.4-4.4\" />\n </svg>\n);\n\n/** Multi-point sparkles \u2014 an \"AI / magic\" launcher. */\nexport const SparklesIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M12 3.5 13.6 9 19 10.5 13.6 12 12 17.5 10.4 12 5 10.5 10.4 9z\" />\n <path d=\"M18.5 4v3M20 5.5h-3M5.5 16v2.5M6.75 17.25h-2.5\" />\n </svg>\n);\n\n/** Concierge bell \u2014 a \"support / front-desk\" launcher. */\nexport const ConciergeIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M4 18h16\" />\n <path d=\"M5 18a7 7 0 0 1 14 0\" />\n <path d=\"M12 8V6\" />\n <path d=\"M10 6h4\" />\n </svg>\n);\n\n/** Waving hand \u2014 a warm \"say hi\" launcher. */\nexport const WaveIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M7 11V6.5a1.5 1.5 0 0 1 3 0V10\" />\n <path d=\"M10 10V5.5a1.5 1.5 0 0 1 3 0V10\" />\n <path d=\"M13 10V6.5a1.5 1.5 0 0 1 3 0V12\" />\n <path d=\"M16 9.5a1.5 1.5 0 0 1 3 0v2.5a7 7 0 0 1-7 7 7 7 0 0 1-6.4-4.2L4 12.5a1.5 1.5 0 0 1 2.6-1.5L7 11\" />\n </svg>\n);\n\n/**\n * Map a preset name to its icon component. Consumed by the launcher\n * (`launcher.icon`) \u2014 a name not listed here is treated as raw SVG.\n */\nexport const ICON_PRESETS: Record<string, () => JSX.Element> = {\n chat: ChatIcon,\n message: MessageIcon,\n bubbles: BubblesIcon,\n spark: SparkIcon,\n wand: WandIcon,\n bot: BotIcon,\n help: HelpIcon,\n lifebuoy: LifebuoyIcon,\n headset: HeadsetIcon,\n search: SearchSmileIcon,\n sparkles: SparklesIcon,\n concierge: ConciergeIcon,\n wave: WaveIcon,\n};\n", "/**\n * Test-id catalogue for every interactive element in the widget.\n *\n * Every entry is brand-prefixed at runtime via `BRAND.cssPrefix` so\n * `help-ai` and `go-ai` widgets co-hosted on the same page can be\n * targeted independently:\n *\n * document.querySelector(`[data-testid=\"${TID.composerSend}\"]`)\n *\n * For Playwright / RTL, pierce the shadow root:\n *\n * page.locator(`${chatHostSelector} >> [data-testid=\"${TID.composerSend}\"]`)\n *\n * Stable across releases \u2014 additions are append-only, renames are a\n * breaking change. Adding a new interactive element? Add an entry\n * here and attach `data-testid={TID.foo}` at the JSX site.\n */\n\nimport { BRAND } from \"@/core/brand\";\n\nconst p = BRAND.cssPrefix;\n\nexport const TID = {\n // \u2500\u2500 Launcher / callout \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\u2500\u2500\u2500\u2500\u2500\u2500\n /** Floating launcher FAB. */\n launcher: `${p}-launcher`,\n /** Pre-engagement callout bubble. */\n callout: `${p}-callout`,\n /** Callout close (\u00D7) button. */\n calloutDismiss: `${p}-callout-dismiss`,\n\n // \u2500\u2500 Panel chrome \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Panel root (the floating / drawer / modal card). */\n panel: `${p}-panel`,\n /** Panel header bar. */\n panelHeader: `${p}-panel-header`,\n /** Resize grip (corner handle). */\n resizeGrip: `${p}-resize-grip`,\n\n // \u2500\u2500 Header actions \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Header `clear` (new conversation) button. */\n actionClear: `${p}-action-clear`,\n /** Header `expand` (toggle 1.5\u00D7) button. */\n actionExpand: `${p}-action-expand`,\n /** Header `fullscreen` button. */\n actionFullscreen: `${p}-action-fullscreen`,\n /** Header `popOut` button. */\n actionPopOut: `${p}-action-popout`,\n /** Header `sound` (mute toggle) button. */\n actionSound: `${p}-action-sound`,\n /** Header `history` (show past conversations) button. */\n actionHistory: `${p}-action-history`,\n /** Header `language` (locale switcher) trigger. */\n actionLanguage: `${p}-action-language`,\n /** Header `theme` (auto/light/dark switcher) trigger. */\n actionTheme: `${p}-action-theme`,\n /** Header `close` (\u00D7) button. */\n actionClose: `${p}-action-close`,\n /** Header overflow (\u22EF) menu trigger. */\n actionOverflow: `${p}-action-overflow`,\n\n // \u2500\u2500 Language / theme switcher items \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 /** Language switcher: single locale option (suffix with locale tag at site). */\n languageOption: `${p}-language-option`,\n /** Theme switcher: single mode option (suffix with `-auto|-light|-dark`). */\n themeOption: `${p}-theme-option`,\n\n // \u2500\u2500 Composer \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Composer root container. */\n composer: `${p}-composer`,\n /** Composer textarea. */\n composerInput: `${p}-composer-input`,\n /** Composer send button. */\n composerSend: `${p}-composer-send`,\n /** Composer stop-streaming button (replaces send while streaming). */\n composerStop: `${p}-composer-stop`,\n /** Composer paperclip / attach button. */\n composerAttach: `${p}-composer-attach`,\n /** Composer hidden `<input type=file>` (the picker the paperclip opens). */\n composerFileInput: `${p}-composer-file-input`,\n /** Composer mic / voice-record button. */\n composerVoice: `${p}-composer-voice`,\n /** Composer attachment chip \u2014 suffix `-{id}` at the JSX site. */\n attachmentChip: `${p}-attachment-chip`,\n /** Attachment chip's remove (\u00D7) button. */\n attachmentRemove: `${p}-attachment-remove`,\n\n // \u2500\u2500 Suggestions \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Suggested-message chip \u2014 suffix `-{index}` at the JSX site. */\n suggestion: `${p}-suggestion`,\n\n // \u2500\u2500 Messages \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Message bubble \u2014 suffix `-{id}` at the JSX site. */\n messageBubble: `${p}-message`,\n /** \"Stop\" / retry inline button inside an error bubble. */\n messageRetry: `${p}-message-retry`,\n\n // \u2500\u2500 History pane \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** History pane root (list view inside the panel). */\n historyPane: `${p}-history-pane`,\n /** History pane back / close button. */\n historyBack: `${p}-history-back`,\n /** Single history list row \u2014 suffix `-{sessionId}` at the JSX site. */\n historyItem: `${p}-history-item`,\n\n // \u2500\u2500 Page-mode sidebar \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Page-mode sidebar (full-bleed left rail). */\n sidebar: `${p}-sidebar`,\n /** Sidebar collapse / expand toggle. */\n sidebarToggle: `${p}-sidebar-toggle`,\n /** Sidebar \"New chat\" button. */\n sidebarNewChat: `${p}-sidebar-new-chat`,\n /** Sidebar history row \u2014 suffix `-{sessionId}` at the JSX site. */\n sidebarItem: `${p}-sidebar-item`,\n\n // \u2500\u2500 Messenger modules \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Messenger-home surface root. */\n messengerHome: `${p}-messenger-home`,\n /** Empty state shown when no module is enabled. */\n modulesEmpty: `${p}-modules-empty`,\n /** Bottom-nav tab button \u2014 suffix `-{moduleId}` at the JSX site. */\n tab: `${p}-tab`,\n /** Unread badge on the Messages tab. */\n tabBadge: `${p}-tab-badge`,\n /** Home module root. */\n homeView: `${p}-home-view`,\n /** Home greeting headline. */\n homeGreeting: `${p}-home-greeting`,\n /** Home hero brand wordmark. */\n homeBrand: `${p}-home-brand`,\n /** Content reader: detail header, scroll container, article. */\n backHeader: `${p}-back-header`,\n contentView: `${p}-content-view`,\n contentScroll: `${p}-content-scroll`,\n /** Home / Help search bar. */\n homeSearch: `${p}-home-search`,\n helpSearch: `${p}-help-search`,\n /** Home recent-message card. */\n homeRecent: `${p}-home-recent`,\n /** Home content-list row (\"Popular articles\") \u2014 suffix `-{id}`. */\n homeContentRow: `${p}-home-content-row`,\n /** Help article row \u2014 suffix `-{id}`. */\n helpArticle: `${p}-help-article`,\n /** News list item \u2014 suffix `-{id}`. */\n newsItem: `${p}-news-item`,\n} as const;\n\nexport type TestId = (typeof TID)[keyof typeof TID];\n\n/** Suffix helper \u2014 keeps generated ids predictable across the codebase. */\nexport function tid(base: TestId, suffix: string | number): string {\n return `${base}-${suffix}`;\n}\n\n/**\n * Map a header `ActionName` to its test-id. The action-name strings\n * (`clear` / `expand` / \u2026) come from the configurator schema; this\n * table mirrors them so the JSX site can render `data-testid` with\n * a single lookup.\n */\nexport const ACTION_TID = {\n clear: TID.actionClear,\n expand: TID.actionExpand,\n fullscreen: TID.actionFullscreen,\n popOut: TID.actionPopOut,\n sound: TID.actionSound,\n history: TID.actionHistory,\n language: TID.actionLanguage,\n theme: TID.actionTheme,\n close: TID.actionClose,\n} as const;\n", "/**\n * Launcher (FAB) + the unified `<LauncherCallout>` bubble that sits\n * next to it (greeting / nudge / onboarding tip \u2014 one component,\n * configured per use case via `launcher.callout`).\n *\n * Both render only in floating modes when `options.launcher.hidden` is\n * false. Visuals are driven by data-attrs + CSS in `styles/panel.css`\n * \u2014 no inline styles here.\n */\n\nimport { useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { CalloutPosition, Position, ResolvedOptions } from \"@/core/config\";\nimport { CloseIcon, ICON_PRESETS } from \"./icons\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface LauncherProps {\n onToggle: () => void;\n launcher: ResolvedOptions[\"launcher\"];\n strings: ResolvedOptions[\"strings\"];\n /**\n * The panel just opened and the FAB is mid-exit. Triggers the\n * `__P__-fab-out` keyframe via the `data-leaving` attribute and disables\n * pointer events so a stray click during the fade can't double-fire\n * `onToggle`. The component itself unmounts shortly after.\n */\n leaving?: boolean;\n /**\n * Drawer mode: render the launcher as a vertical handle docked flush\n * to the screen edge (reusing the same launcher config) instead of a\n * corner FAB. Styling is driven by the `data-edge-tab` attribute in\n * `styles/panel.css`.\n */\n edgeTab?: boolean;\n}\n\n/**\n * Floating launcher FAB. Only rendered while the panel is *closed* \u2014 the\n * App swaps to `<Panel>` on open, which carries its own close button. In\n * drawer mode it docks to the edge as a vertical tab (`edgeTab`).\n */\nexport function Launcher({ onToggle, launcher, strings, leaving, edgeTab }: LauncherProps) {\n const showLabel = launcher.variant !== \"circle\" && !!launcher.label;\n\n return (\n <button\n type=\"button\"\n class={`${p}-fab`}\n data-variant={launcher.variant}\n data-size={launcher.size}\n data-edge-tab={edgeTab ? \"true\" : undefined}\n data-leaving={leaving ? \"true\" : undefined}\n data-testid={TID.launcher}\n onClick={onToggle}\n aria-label={strings.launcherOpen}\n >\n {renderIcon(launcher.icon)}\n {showLabel ? <span class={`${p}-fab-label`}>{launcher.label}</span> : null}\n </button>\n );\n}\n\nfunction renderIcon(icon: string) {\n const Preset = ICON_PRESETS[icon];\n if (Preset) return <Preset />;\n // Treat anything else as raw SVG source. Parsed via DOMParser so no\n // innerHTML is ever written to a live element.\n return <InlineSvg source={icon} />;\n}\n\nfunction InlineSvg({ source }: { source: string }) {\n return (\n <span\n ref={(el) => {\n if (el) mountRawSvg(el, source);\n }}\n aria-hidden=\"true\"\n />\n );\n}\n\nfunction mountRawSvg(host: HTMLElement, source: string): void {\n if (host.dataset.svgMounted === source) return;\n try {\n const doc = new DOMParser().parseFromString(source, \"image/svg+xml\");\n const root = doc.documentElement;\n if (root && root.tagName.toLowerCase() === \"svg\") {\n host.replaceChildren(document.importNode(root, true));\n host.dataset.svgMounted = source;\n }\n } catch {\n // Malformed SVG \u2014 leave the span empty.\n }\n}\n\n// ---- LauncherCallout ------------------------------------------------------\n\ninterface LauncherCalloutProps {\n callout: NonNullable<ResolvedOptions[\"launcher\"][\"callout\"]>;\n /** FAB corner \u2014 drives the auto-position fallback (callout anchors away from this corner). */\n launcherPosition: Position;\n closeLabel: string;\n /** Called when the user clicks X. App persists + sets dismissed state. */\n onDismiss: () => void;\n}\n\n/**\n * Pre-engagement message bubble next to the FAB. Used for greetings\n * (\"\uD83D\uDC4B Hi, need help?\"), nudges (\"Click here \u2192\"), and onboarding tips\n * \u2014 same component, configured for the use case.\n *\n * Rendering is gated by App: when `callout.persistent === false`,\n * App suppresses it after a prior dismissal of the same text;\n * `persistent === true` re-shows it on every visit.\n */\nexport function LauncherCallout({ callout, launcherPosition, closeLabel, onDismiss }: LauncherCalloutProps) {\n const [visible, setVisible] = useState(false);\n\n // Reveal after the configured delay (gives the page time to settle).\n useEffect(() => {\n const id = setTimeout(() => setVisible(true), callout.delayMs);\n return () => clearTimeout(id);\n }, [callout.delayMs]);\n\n // Auto-dismiss schedule (optional \u2014 `0` disables).\n useEffect(() => {\n if (!visible || callout.autoDismissMs <= 0) return;\n const id = setTimeout(onDismiss, callout.autoDismissMs);\n return () => clearTimeout(id);\n }, [visible, callout.autoDismissMs, onDismiss]);\n\n if (!visible) return null;\n\n return (\n <div\n class={`${p}-callout`}\n role=\"status\"\n data-shape={callout.shape}\n data-position={resolveCalloutPosition(callout.position, launcherPosition)}\n data-animated={callout.animated ? \"true\" : undefined}\n data-testid={TID.callout}\n >\n <span>{callout.text}</span>\n <button\n type=\"button\"\n class={`${p}-callout-close`}\n onClick={onDismiss}\n aria-label={closeLabel}\n title={closeLabel}\n data-testid={TID.calloutDismiss}\n >\n <CloseIcon />\n </button>\n </div>\n );\n}\n\n/**\n * Resolve a coarse `auto | horizontal | vertical` choice to a concrete\n * side (`left | right | top | bottom`) based on the FAB's corner.\n *\n * - `auto` \u2192 side facing INTO the page (bottom-right FAB \u2192 left).\n * - `horizontal` \u2192 opposite of the FAB's horizontal edge (FAB on the\n * right \u2192 callout on the left).\n * - `vertical` \u2192 opposite of the FAB's vertical edge (FAB at the\n * bottom \u2192 callout on top).\n *\n * The mapping always picks the on-screen side so the bubble can't be\n * pushed off the viewport regardless of the FAB's corner.\n */\nexport type ResolvedSide = \"left\" | \"right\" | \"top\" | \"bottom\";\n\nexport function resolveCalloutPosition(configured: CalloutPosition, launcherPosition: Position): ResolvedSide {\n const isRight = launcherPosition.endsWith(\"-right\");\n const isBottom = launcherPosition.startsWith(\"bottom-\");\n if (configured === \"vertical\") return isBottom ? \"top\" : \"bottom\";\n // `auto` and `horizontal` both anchor along the FAB's horizontal axis.\n return isRight ? \"left\" : \"right\";\n}\n", "/**\n * User-drag resize grip for the panel.\n *\n * Renders a small handle in the corner pointing *away from* the launcher\n * (so dragging always enlarges the panel outward). On drag, writes the new\n * dimensions as CSS custom properties on the panel \u2014 NOT inline width /\n * height \u2014 so higher-specificity mode selectors (`expanded`, `fullscreen`)\n * can still override cleanly without an `!important` ladder.\n *\n * Persistence is delegated:\n * - `initialSize` seeds the CSS vars on mount (typically from the\n * widget's cached `userPrefs.widgetSize`).\n * - `onSizeChange(size)` fires on pointer-up with the resolved (post-clamp)\n * dimensions \u2014 the App pipes that to `persistence.patchUserPrefs(...)` and\n * the backend.\n *\n * The grip is only meaningful in \"normal\" panel size; expanded / fullscreen\n * override via CSS, and the user-set vars are simply unused there.\n */\n\nimport { useEffect, useRef } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResizeOptions, Strings } from \"@/core/config\";\nimport type { WidgetSize } from \"@/core/persistence\";\nimport { ResizeHandleIcon } from \"./icons\";\nimport { TID } from \"./test-ids\";\n\ninterface Props {\n /** The panel element whose size we control. */\n panelEl: HTMLElement | null;\n /** Resize limits + enabled flag. */\n resize: ResizeOptions;\n /** Position string on host \u2014 `'bottom-right'` / `'top-left'` / etc. */\n position: string;\n /** Previously-persisted size to restore on mount. `null` \u2192 use defaults. */\n initialSize: WidgetSize | null;\n /** Called on pointer-up with the resolved (post-clamp) dimensions. */\n onSizeChange: (size: WidgetSize) => void;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n}\n\nexport function ResizeGrip({ panelEl, resize, position, initialSize, onSizeChange, strings }: Props) {\n const p = BRAND.cssPrefix;\n const dragRef = useRef<{\n pointerId: number;\n startX: number;\n startY: number;\n startW: number;\n startH: number;\n dirX: number; // 1 = grow right; -1 = grow left\n dirY: number; // 1 = grow down; -1 = grow up\n } | null>(null);\n\n // Apply limits + restore persisted size as CSS custom properties on the\n // panel. Crucially NOT inline `width`/`max-width` \u2014 those would beat the\n // selectors that drive `data-mode=\"fullscreen\"` / `\"expanded\"` and clamp\n // those modes visibly wrong.\n useEffect(() => {\n if (!panelEl) return;\n const style = panelEl.style;\n if (resize.minWidth) style.setProperty(`--${p}-resize-min-w`, resize.minWidth);\n if (resize.maxWidth) style.setProperty(`--${p}-resize-max-w`, resize.maxWidth);\n if (resize.minHeight) style.setProperty(`--${p}-resize-min-h`, resize.minHeight);\n if (resize.maxHeight) style.setProperty(`--${p}-resize-max-h`, resize.maxHeight);\n if (initialSize) {\n style.setProperty(`--${p}-widget-w`, `${initialSize.width}px`);\n style.setProperty(`--${p}-widget-h`, `${initialSize.height}px`);\n }\n }, [panelEl, resize.minWidth, resize.maxWidth, resize.minHeight, resize.maxHeight, p, initialSize]);\n\n if (!panelEl) return null;\n\n // Which corner gets the grip depends on the host's position. The grip\n // always sits on the corner pointing *away* from the launcher so dragging\n // enlarges the panel outward.\n const isTop = position.startsWith(\"top-\");\n const isRight = position.endsWith(\"-right\");\n const cornerClass = `${p}-resize-grip ${p}-resize-grip--${isTop ? \"bottom\" : \"top\"}-${isRight ? \"left\" : \"right\"}`;\n\n const onPointerDown = (e: PointerEvent) => {\n if (!panelEl) return;\n const target = e.currentTarget as HTMLElement;\n const rect = panelEl.getBoundingClientRect();\n dragRef.current = {\n pointerId: e.pointerId,\n startX: e.clientX,\n startY: e.clientY,\n startW: rect.width,\n startH: rect.height,\n dirX: isRight ? -1 : 1, // bottom-right launcher \u2192 grip on bottom-left \u2192 drag left grows width\n dirY: isTop ? 1 : -1, // top-* launcher \u2192 grip on bottom \u2192 drag down grows height\n };\n target.setPointerCapture(e.pointerId);\n e.preventDefault();\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const d = dragRef.current;\n if (!d || e.pointerId !== d.pointerId || !panelEl) return;\n const dx = (e.clientX - d.startX) * d.dirX;\n const dy = (e.clientY - d.startY) * d.dirY;\n // Apply via CSS custom properties \u2014 not inline `style.width` \u2014 so\n // expanded/fullscreen CSS can override without an !important ladder.\n panelEl.style.setProperty(`--${p}-widget-w`, `${d.startW + dx}px`);\n panelEl.style.setProperty(`--${p}-widget-h`, `${d.startH + dy}px`);\n };\n\n const onPointerUp = (e: PointerEvent) => {\n const d = dragRef.current;\n if (!d || e.pointerId !== d.pointerId || !panelEl) return;\n (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);\n // Read the resolved (post-clamp) size from the DOM and hand it to the\n // owner \u2014 App persists locally + pushes to the backend.\n const rect = panelEl.getBoundingClientRect();\n onSizeChange({ width: Math.round(rect.width), height: Math.round(rect.height) });\n dragRef.current = null;\n };\n\n return (\n <div\n class={cornerClass}\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label={strings.resizeHandle}\n title={strings.resizeHandle}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerUp}\n data-testid={TID.resizeGrip}\n >\n <ResizeHandleIcon />\n </div>\n );\n}\n", "/**\n * Make a modal panel draggable by its chrome bars.\n *\n * Modal mode centres the panel via flex; this hook lets the user grab a\n * \"chrome\" region \u2014 the chat header, a back-header, or the home hero \u2014 and\n * reposition the dialog. The offset is written as CSS custom properties\n * (`--<p>-modal-dx` / `--<p>-modal-dy`) consumed by a `transform: translate()`\n * on the modal panel, so it never fights the panel's width/height vars.\n *\n * Pointerdown is delegated on the panel element and only starts a drag over a\n * handle that isn't an interactive control (so header buttons, the composer,\n * inputs, and tabs keep working). Move/up listen on `window` so a fast drag\n * that leaves the panel still tracks. The offset lives on the panel element's\n * inline style, which unmounts when the modal closes \u2014 so each open re-centres.\n *\n * No-op unless `enabled` (i.e. `mode === \"modal\"`).\n */\n\nimport { useEffect } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\n\nconst p = BRAND.cssPrefix;\n/** Grabbable chrome \u2014 the top bars of every modal surface. */\nconst HANDLE = `.${p}-header, .${p}-back-header, .${p}-home-hero`;\n/** Interactive descendants that must keep their own pointer behaviour. */\nconst BLOCK = `button, a, input, textarea, select, [role=\"tab\"], [contenteditable]`;\n/** Keep at least this many px of the panel on-screen when dragged. */\nconst KEEP_VISIBLE = 56;\n\nexport function useDragMove(panelEl: HTMLElement | null, enabled: boolean): void {\n useEffect(() => {\n if (!enabled || !panelEl) return;\n const num = (v: string) => Number.parseFloat(v) || 0;\n const clamp = (v: number, max: number) => Math.min(max, Math.max(-max, v));\n let drag: { id: number; x: number; y: number; dx: number; dy: number; maxX: number; maxY: number } | null = null;\n\n const onDown = (e: PointerEvent) => {\n const t = e.target;\n if (!(t instanceof Element) || !t.closest(HANDLE) || t.closest(BLOCK)) return;\n const rect = panelEl.getBoundingClientRect();\n drag = {\n id: e.pointerId,\n x: e.clientX,\n y: e.clientY,\n dx: num(panelEl.style.getPropertyValue(`--${p}-modal-dx`)),\n dy: num(panelEl.style.getPropertyValue(`--${p}-modal-dy`)),\n // Centred origin: |offset| \u2264 (viewport + panel)/2 \u2212 keepVisible keeps a\n // strip of the dialog reachable so it can never be lost off-screen.\n maxX: (window.innerWidth + rect.width) / 2 - KEEP_VISIBLE,\n maxY: (window.innerHeight + rect.height) / 2 - KEEP_VISIBLE,\n };\n panelEl.dataset.dragging = \"true\";\n e.preventDefault();\n };\n\n const onMove = (e: PointerEvent) => {\n if (!drag || e.pointerId !== drag.id) return;\n const nx = clamp(drag.dx + e.clientX - drag.x, drag.maxX);\n const ny = clamp(drag.dy + e.clientY - drag.y, drag.maxY);\n panelEl.style.setProperty(`--${p}-modal-dx`, `${nx}px`);\n panelEl.style.setProperty(`--${p}-modal-dy`, `${ny}px`);\n };\n\n const onUp = (e: PointerEvent) => {\n if (!drag || e.pointerId !== drag.id) return;\n drag = null;\n delete panelEl.dataset.dragging;\n };\n\n panelEl.addEventListener(\"pointerdown\", onDown);\n window.addEventListener(\"pointermove\", onMove);\n window.addEventListener(\"pointerup\", onUp);\n window.addEventListener(\"pointercancel\", onUp);\n return () => {\n panelEl.removeEventListener(\"pointerdown\", onDown);\n window.removeEventListener(\"pointermove\", onMove);\n window.removeEventListener(\"pointerup\", onUp);\n window.removeEventListener(\"pointercancel\", onUp);\n };\n }, [panelEl, enabled]);\n}\n", "/**\n * Per-mode visibility + behaviour rules for header actions.\n *\n * Pure helpers \u2014 no DOM, no React. Deliberately split from `panel.tsx` so\n * the same rules drive (a) the production filter applied before rendering\n * the header buttons and (b) the test suite. Keeping a parallel copy in\n * the test would have been a trip-wire that doesn't actually exercise\n * production code.\n */\n\nimport type { ActionName, Mode } from \"@/core/config\";\nimport type { PanelSize } from \"./host-mode\";\n\n/**\n * Whether an action button makes sense in the current mode + panel state.\n * The page-owner / server lists actions in `main` / `overflow`; this filter\n * drops the ones that would render a no-op button and confuse the user.\n *\n * - `expand` \u2014 only floating modes (inline has no mid-tier between\n * container-fill and fullscreen; standalone IS fullscreen).\n * - `fullscreen` \u2014 everywhere except standalone (already fullscreen).\n * - `popOut` \u2014 everywhere except standalone (can't pop out of itself)\n * AND requires `popOutUrl` to be resolved (otherwise the button is a\n * no-op \u2014 hide it).\n * - `close` \u2014 floating modes always; inline only when in fullscreen (X\n * exits fullscreen back to the in-flow layout); standalone and page never\n * (the widget IS the page \u2014 there's nothing to close it back to).\n * - `clear`, `sound`, `history`, `language`, `theme` \u2014 all modes.\n */\nexport interface VisibilityContext {\n /** Resolved popOut URL \u2014 gate the `popOut` action when empty. */\n popOutUrl?: string;\n}\n\nexport function isActionVisible(name: ActionName, mode: Mode, panelSize: PanelSize, ctx?: VisibilityContext): boolean {\n switch (name) {\n case \"clear\":\n case \"sound\":\n case \"history\":\n case \"language\":\n case \"theme\":\n return true;\n case \"expand\":\n return mode === \"floating\";\n case \"fullscreen\":\n return mode !== \"standalone\";\n case \"popOut\":\n return mode !== \"standalone\" && !!ctx?.popOutUrl;\n case \"close\":\n if (mode === \"standalone\" || mode === \"page\") return false;\n if (mode === \"inline\") return panelSize === \"fullscreen\";\n return true;\n }\n}\n\n/** Filter an action list down to the ones visible in the current mode + state. */\nexport function visibleActions(\n actions: readonly ActionName[],\n mode: Mode,\n panelSize: PanelSize,\n ctx?: VisibilityContext,\n): readonly ActionName[] {\n return actions.filter((name) => isActionVisible(name, mode, panelSize, ctx));\n}\n\n/**\n * The history pane's X button mirrors the main header's close visibility.\n * Returns `true` when both (a) the close action is visible per the rules\n * above AND (b) the deployment actually listed `close` in `main` /\n * `overflow` \u2014 there's no point showing the history-pane X if the close\n * action is otherwise omitted.\n */\nexport function canShowClose(\n mode: Mode,\n panelSize: PanelSize,\n actions: { main: readonly ActionName[]; overflow: readonly ActionName[] },\n): boolean {\n if (!isActionVisible(\"close\", mode, panelSize)) return false;\n return actions.main.includes(\"close\") || actions.overflow.includes(\"close\");\n}\n\n/**\n * What clicking the X / Escape should *do* given the current mode + state:\n *\n * - `\"exit-fullscreen\"` \u2014 only inline + fullscreen. The widget can't be\n * \"closed\" in inline (it's part of the page flow), but it can return\n * to its in-flow layout from a viewport-fill state. The handler resets\n * `panelSize` to `\"normal\"` and emits `fullscreen: false` on the bus.\n * - `\"close\"` \u2014 every other case. Hides the panel (floating) or no-ops\n * (when the rules above already hide the button \u2014 defensive).\n */\nexport type CloseAction = \"close\" | \"exit-fullscreen\";\n\nexport function resolveCloseAction(mode: Mode, panelSize: PanelSize): CloseAction {\n if (mode === \"inline\" && panelSize === \"fullscreen\") return \"exit-fullscreen\";\n return \"close\";\n}\n", "// Focus trap for the panel + a small helper to dispatch announcements via aria-live.\n// All queries scoped to the shadow root \u2014 no leakage into host page focusables.\n\nconst FOCUSABLE_SELECTOR =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]):not([type=\"hidden\"]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nexport function trapFocus(container: HTMLElement): () => void {\n const onKeydown = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\") {\n return;\n }\n const focusables = [...container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)].filter(\n (el) => el.offsetParent !== null || el === document.activeElement,\n );\n if (focusables.length === 0) {\n e.preventDefault();\n return;\n }\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n if (!first || !last) {\n return;\n }\n\n const active = (container.getRootNode() as ShadowRoot).activeElement as HTMLElement | null;\n if (e.shiftKey && active === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && active === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n container.addEventListener(\"keydown\", onKeydown);\n return () => container.removeEventListener(\"keydown\", onKeydown);\n}\n", "/** First letters of the first two words of a name, upper-cased. \"Sam Carter\" \u2192 \"SC\". */\nexport function initials(name: string): string {\n return name\n .trim()\n .split(/\\s+/)\n .slice(0, 2)\n .map((part) => part.charAt(0).toUpperCase())\n .join(\"\");\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { SessionAgent } from \"@/stream/protocol\";\nimport { initials } from \"./initials\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n agent: SessionAgent;\n}\n\n/**\n * Bot persona shown in the panel header. Renders an avatar (image or\n * initials), name, and optional subtitle + presence dot.\n */\nexport function AgentBadge({ agent }: Props) {\n const status = agent.status ?? \"online\";\n return (\n <div class={`${p}-agent`}>\n <div class={`${p}-agent-avatar`} data-status={status}>\n {agent.avatar ? <img src={agent.avatar} alt=\"\" /> : <span>{initials(agent.name)}</span>}\n </div>\n <div class={`${p}-agent-meta`}>\n <strong>{agent.name}</strong>\n {agent.title ? <span>{agent.title}</span> : null}\n </div>\n </div>\n );\n}\n", "// Unified ingestion pipeline for files arriving via drop, paste, or picker.\n// Returns LocalAttachments with object-URL previews ready for the composer.\n\nimport type { AttachmentLimits } from \"./config\";\nimport { cryptoId } from \"./ids\";\nimport { logger } from \"./logger\";\n\nconst log = logger.scope(\"attachments\");\n\nexport interface LocalAttachment {\n id: string;\n file: File;\n name: string;\n size: number;\n mediaType: string;\n /** Object URL for preview thumbnails. Caller MUST revoke when removed. */\n previewUrl: string;\n}\n\nexport interface IngestResult {\n accepted: LocalAttachment[];\n rejected: { file: File; reason: \"count\" | \"bytes\" | \"mime\" }[];\n}\n\ntype Limits = Required<Pick<AttachmentLimits, \"maxBytes\" | \"maxCount\">> & { accept: string };\ntype Source = DataTransferItemList | FileList | File[] | null | undefined;\n\nexport function ingest(source: Source, existing: LocalAttachment[], limits: Limits): IngestResult {\n const incoming = collectFiles(source);\n const accepted: LocalAttachment[] = [];\n const rejected: IngestResult[\"rejected\"] = [];\n const acceptList = parseAcceptList(limits.accept);\n\n let totalBytes = existing.reduce((sum, a) => sum + a.size, 0);\n let totalCount = existing.length;\n\n for (const file of incoming) {\n const reason = reject(file, totalCount, totalBytes, limits, acceptList);\n if (reason) {\n rejected.push({ file, reason });\n continue;\n }\n accepted.push(toLocalAttachment(file));\n totalBytes += file.size;\n totalCount += 1;\n }\n\n if (accepted.length || rejected.length) {\n log.debug(\"ingest\", {\n accepted: accepted.length,\n rejected: rejected.map((r) => ({ name: r.file.name, reason: r.reason })),\n });\n }\n return { accepted, rejected };\n}\n\nexport function revoke(att: LocalAttachment): void {\n try {\n URL.revokeObjectURL(att.previewUrl);\n } catch (error) {\n log.debug(\"revokeObjectURL failed\", { error });\n }\n}\n\nfunction reject(\n file: File,\n count: number,\n bytes: number,\n limits: Limits,\n accept: AcceptRule[],\n): IngestResult[\"rejected\"][number][\"reason\"] | null {\n if (count >= limits.maxCount) return \"count\";\n if (bytes + file.size > limits.maxBytes) return \"bytes\";\n if (!matchesAccept(file, accept)) return \"mime\";\n return null;\n}\n\nfunction collectFiles(source: Source): File[] {\n if (!source) return [];\n if (Array.isArray(source)) return source.filter((f): f is File => f instanceof File);\n // `DataTransferItemList` and `FileList` are both iterable; spread to a\n // real array. `DataTransferItem` is not a File \u2014 unwrap with `getAsFile`.\n return [...(source as Iterable<unknown>)]\n .map((entry) => (entry instanceof File ? entry : toFile(entry)))\n .filter((f): f is File => f !== null);\n}\n\nfunction toFile(entry: unknown): File | null {\n const item = entry as { kind?: string; getAsFile?: () => File | null } | null;\n if (!item?.getAsFile || item.kind !== \"file\") return null;\n return item.getAsFile();\n}\n\nfunction toLocalAttachment(file: File): LocalAttachment {\n return {\n file,\n id: cryptoId(\"att\"),\n mediaType: file.type || \"application/octet-stream\",\n name: file.name || \"pasted-file\",\n previewUrl: URL.createObjectURL(file),\n size: file.size,\n };\n}\n\ntype AcceptRule = { kind: \"mime\"; type: string; subtype: string } | { kind: \"ext\"; ext: string };\n\n// Mirror the HTML `<input accept>` grammar: a comma-separated mix of MIME\n// patterns (`image/*`, `application/pdf`) and extensions (`.pdf`, `.docx`).\n// Extension entries match against the filename suffix; MIME entries match\n// against the file's `type`. Empty list = accept anything.\nfunction parseAcceptList(accept: string): AcceptRule[] {\n return accept\n .split(\",\")\n .map((s) => s.trim().toLowerCase())\n .filter(Boolean)\n .map((s): AcceptRule => {\n if (s.startsWith(\".\")) return { kind: \"ext\", ext: s };\n const [type = \"*\", subtype = \"*\"] = s.split(\"/\");\n return { kind: \"mime\", type, subtype };\n });\n}\n\nfunction matchesAccept(file: File, rules: AcceptRule[]): boolean {\n if (rules.length === 0) return true;\n const [type = \"*\", subtype = \"*\"] = (file.type || \"\").toLowerCase().split(\"/\");\n const name = (file.name || \"\").toLowerCase();\n return rules.some((r) => {\n if (r.kind === \"ext\") return name.endsWith(r.ext);\n return (r.type === \"*\" || r.type === type) && (r.subtype === \"*\" || r.subtype === subtype);\n });\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { LocalAttachment } from \"@/core/attachments\";\nimport { CloseIcon, FileIcon } from \"./icons\";\nimport { tid, TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n attachment: LocalAttachment;\n onRemove: () => void;\n}\n\nexport function AttachmentChip({ attachment, onRemove }: Props) {\n const isImage = attachment.mediaType.startsWith(\"image/\");\n return (\n <span class={`${p}-attachment-chip`} title={attachment.name} data-testid={tid(TID.attachmentChip, attachment.id)}>\n {isImage ? (\n <img class={`${p}-attachment-thumb`} src={attachment.previewUrl} alt=\"\" />\n ) : (\n <span class={`${p}-attachment-thumb`} aria-hidden=\"true\">\n <FileIcon />\n </span>\n )}\n <span class={`${p}-attachment-name`}>{attachment.name}</span>\n <button\n type=\"button\"\n class={`${p}-attachment-remove`}\n onClick={onRemove}\n aria-label={`Remove ${attachment.name}`}\n data-testid={tid(TID.attachmentRemove, attachment.id)}\n >\n <CloseIcon />\n </button>\n </span>\n );\n}\n", "/**\n * Voice-input controllers.\n *\n * Two backends, one interface:\n *\n * - `'local'` \u2014 Web Speech API (`SpeechRecognition`). Free, on-device,\n * streams interim results. Browser support varies (Chrome/Edge/Safari\n * yes, Firefox no).\n * - `'server'` \u2014 `MediaRecorder` captures audio, we POST the blob to the\n * configured transcription endpoint when the user clicks stop. More\n * consistent quality + works anywhere MediaRecorder does.\n *\n * `'disabled'` collapses to an unsupported controller \u2014 the mic button\n * is hidden by the composer.\n */\n\nimport type { VoiceMode } from \"@/core/config\";\n\nexport interface VoiceController {\n /** Whether the mic button should render at all. */\n isSupported: boolean;\n /** Begin capture. `onText` fires for interim (local) and final transcripts. */\n start(onText: (text: string, isFinal: boolean) => void, onEnd: () => void): void;\n /** Stop capture \u2014 triggers transcription upload in `server` mode. */\n stop(): void;\n}\n\ntype Transcribe = (audio: Blob, mimeType: string) => Promise<{ text: string }>;\n\nconst UNSUPPORTED: VoiceController = {\n isSupported: false,\n start: () => undefined,\n stop: () => undefined,\n};\n\nexport function createVoice(mode: VoiceMode, locale: string, transcribe: Transcribe): VoiceController {\n if (mode === \"disabled\") return UNSUPPORTED;\n if (mode === \"server\") return createServerVoice(transcribe);\n return createLocalVoice(locale);\n}\n\n// ---- local: Web Speech API ---------------------------------------------\n\ninterface SpeechRecognitionLike extends EventTarget {\n continuous: boolean;\n interimResults: boolean;\n lang: string;\n start: () => void;\n stop: () => void;\n abort: () => void;\n}\n\ninterface RecognitionResultEvent extends Event {\n results: { isFinal: boolean; 0: { transcript: string } }[];\n resultIndex: number;\n}\n\ntype RecognitionCtor = new () => SpeechRecognitionLike;\n\nfunction createLocalVoice(locale: string): VoiceController {\n const Ctor = getSpeechRecognitionCtor();\n if (!Ctor) return UNSUPPORTED;\n\n let rec: SpeechRecognitionLike | null = null;\n\n return {\n isSupported: true,\n start(onText, onEnd) {\n rec = new Ctor();\n rec.continuous = true;\n rec.interimResults = true;\n rec.lang = locale;\n\n rec.addEventListener(\"result\", (e) => {\n const evt = e as RecognitionResultEvent;\n let text = \"\";\n let isFinal = false;\n for (let i = evt.resultIndex; i < evt.results.length; i++) {\n const r = evt.results[i];\n if (!r) continue;\n text += r[0].transcript;\n if (r.isFinal) isFinal = true;\n }\n onText(text, isFinal);\n });\n const end = () => {\n rec = null;\n onEnd();\n };\n rec.addEventListener(\"end\", end);\n rec.addEventListener(\"error\", end);\n rec.start();\n },\n stop() {\n rec?.stop();\n },\n };\n}\n\nfunction getSpeechRecognitionCtor(): RecognitionCtor | undefined {\n const g = globalThis as unknown as {\n SpeechRecognition?: RecognitionCtor;\n webkitSpeechRecognition?: RecognitionCtor;\n };\n return g.SpeechRecognition ?? g.webkitSpeechRecognition;\n}\n\n// ---- server: MediaRecorder \u2192 /transcribe -------------------------------\n\nconst CANDIDATE_MIME_TYPES = [\"audio/webm;codecs=opus\", \"audio/webm\", \"audio/ogg;codecs=opus\", \"audio/mp4\"];\n\nfunction createServerVoice(transcribe: Transcribe): VoiceController {\n if (typeof MediaRecorder === \"undefined\" || !navigator?.mediaDevices?.getUserMedia) {\n return UNSUPPORTED;\n }\n\n let recorder: MediaRecorder | null = null;\n let stream: MediaStream | null = null;\n let chunks: Blob[] = [];\n\n const releaseStream = () => {\n for (const track of stream?.getTracks() ?? []) track.stop();\n stream = null;\n };\n\n return {\n isSupported: true,\n async start(onText, onEnd) {\n try {\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const mimeType = pickSupportedMimeType();\n recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);\n chunks = [];\n\n recorder.addEventListener(\"dataavailable\", (e) => {\n if (e.data.size > 0) chunks.push(e.data);\n });\n recorder.addEventListener(\"stop\", async () => {\n const type = recorder?.mimeType ?? \"audio/webm\";\n releaseStream();\n const blob = new Blob(chunks, { type });\n chunks = [];\n try {\n const { text } = await transcribe(blob, type);\n if (text) onText(text, true);\n } catch {\n // Swallow transcription errors \u2014 the composer's error state\n // would need plumbing. Refine once we have a UX spec for this.\n } finally {\n onEnd();\n }\n });\n recorder.start();\n } catch {\n releaseStream();\n onEnd();\n }\n },\n stop() {\n recorder?.stop();\n },\n };\n}\n\nfunction pickSupportedMimeType(): string | undefined {\n for (const type of CANDIDATE_MIME_TYPES) {\n if (MediaRecorder.isTypeSupported(type)) return type;\n }\n return undefined;\n}\n", "import { useEffect, useRef, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResolvedOptions } from \"@/core/config\";\nimport type { EventBus } from \"@/core/events\";\nimport type { FeedbackBus } from \"@/core/feedback\";\nimport { ingest, type LocalAttachment, revoke } from \"@/core/attachments\";\nimport { logger } from \"@/core/logger\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport { AttachmentChip } from \"./attachment-chip\";\nimport { ArrowUpIcon, ChevronRightIcon, MicIcon, PaperPlaneIcon, PaperclipIcon, SendIcon, StopIcon } from \"./icons\";\nimport { TID } from \"./test-ids\";\nimport { createVoice } from \"./voice\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"composer\");\n\n/** Sum the resolved sizes \u2014 used in attach event payloads. */\nfunction totalBytes(items: LocalAttachment[]): number {\n return items.reduce((acc, a) => acc + a.size, 0);\n}\n\nconst SEND_ICONS = {\n arrowUp: ArrowUpIcon,\n paperPlane: PaperPlaneIcon,\n send: SendIcon,\n chevron: ChevronRightIcon,\n} as const;\n\n/**\n * Imperative API the Composer exposes via `attachApiRef` so the\n * Panel (which owns the drag-drop target \u2014 the whole panel area, not\n * just the composer footer) can route dropped files into the\n * composer's attachment state instead of sending them as an empty\n * message. Composer keeps ownership of the `atts` state + the\n * `ingest()`-driven dedupe/clamp policy; Panel just hands over the\n * raw `DataTransferItemList`.\n */\nexport interface ComposerAttachAPI {\n /** Run dropped items through `ingest()` and append accepted ones to the composer's pending attachments. */\n attachFromDrop: (items: DataTransferItemList) => void;\n}\n\ninterface Props {\n options: ResolvedOptions;\n transport: AgentTransport;\n feedback: FeedbackBus;\n bus: EventBus;\n isStreaming: boolean;\n onSend: (text: string, attachments: LocalAttachment[]) => void;\n onStop: () => void;\n /**\n * Optional. Composer assigns an imperative API into this ref on\n * mount so callers (Panel's drop handler) can append attachments\n * without re-implementing the composer's ingest pipeline.\n */\n attachApiRef?: { current: ComposerAttachAPI | null };\n}\n\nexport function Composer({ options, transport, feedback, bus, isStreaming, onSend, onStop, attachApiRef }: Props) {\n const s = options.strings;\n const [text, setText] = useState(\"\");\n const [atts, setAtts] = useState<LocalAttachment[]>([]);\n const [voiceOn, setVoiceOn] = useState(false);\n const taRef = useRef<HTMLTextAreaElement>(null);\n const fileRef = useRef<HTMLInputElement>(null);\n\n const filesEnabled = options.features.files;\n const voiceMode = options.features.voice;\n\n // Voice controller \u2014 rebuilt when locale / mode / transport change.\n // `useState` (not `useRef`) so changes flow into the next render and\n // the mic-button branch re-evaluates `voice.isSupported`. Setting\n // `features.voice = \"disabled\"` returns an UNSUPPORTED controller \u2014\n // without state, the ref-mutation in a useEffect did NOT trigger a\n // re-render, leaving the mic visible after the user disabled voice.\n const [voice, setVoice] = useState(() =>\n createVoice(voiceMode, options.locale, (blob, type) => transport.transcribe(blob, type)),\n );\n // Hold off on swapping the controller mid-recording \u2014 destroying it\n // would drop the in-flight transcription. `voiceOn` is in the deps\n // so the swap fires the instant the user releases the mic, picking\n // up whatever locale / mode / transport changes accumulated.\n // Disabled mode returns the frozen UNSUPPORTED constant \u2014 short-circuit\n // so a locale switch doesn't churn through `createVoice` for no effect.\n useEffect(() => {\n if (voiceOn || voiceMode === \"disabled\") return;\n setVoice(createVoice(voiceMode, options.locale, (blob, type) => transport.transcribe(blob, type)));\n }, [options.locale, voiceMode, transport, voiceOn]);\n\n // Autofocus the textarea when the composer mounts. Composer remounts at\n // every \"user is now ready to type\" moment \u2014 panel opens, history pane\n // closes, read-only banner flips back to sendable \u2014 so a single\n // mount-only effect covers all of them without an explicit `isOpen` dep.\n // Only autofocus when no other element in the panel already has focus,\n // so keyboard users mid-tab-cycle aren't yanked back to the input.\n useEffect(() => {\n const el = taRef.current;\n if (!el) return;\n const root = el.getRootNode() as ShadowRoot | Document;\n const active = \"activeElement\" in root ? root.activeElement : null;\n const focusedInsidePanel = active && el.closest(`.${p}-panel`)?.contains(active as Node);\n if (!focusedInsidePanel) el.focus({ preventScroll: true });\n // Mount-only.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Autosize textarea on input. When empty, clear the inline height so\n // CSS `min-height: 40px` drives the single-line appearance \u2014 reading\n // `scrollHeight` before layout has settled (iframe mode, first mount)\n // can return values that then get clamped to `max-height: 160px` and\n // stick as inline, leaving the input looking weirdly tall.\n useEffect(() => {\n const el = taRef.current;\n if (!el) return;\n if (text.length === 0) {\n el.style.height = \"\";\n return;\n }\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, 160)}px`;\n }, [text]);\n\n // Cleanup attachment object URLs on unmount. Use a ref so the cleanup\n // sees the LATEST attachments, not the empty list at mount time\n // (naive `[]` deps would capture the initial value and leak blob URLs).\n const attsRef = useRef(atts);\n attsRef.current = atts;\n useEffect(() => () => attsRef.current.forEach(revoke), []);\n\n // Expose an imperative API to the parent Panel so its drop handler\n // (the panel-wide drop target is wider than the composer footer)\n // can route accepted files INTO the composer's pending attachments\n // \u2014 not auto-send them. Without this hand-off the drop fell back to\n // calling `onSend(\"\", [...])`, which submitted an empty-text message\n // the user never typed.\n useEffect(() => {\n if (!attachApiRef) return;\n attachApiRef.current = {\n attachFromDrop: (items) => {\n const result = ingest(items, attsRef.current, options.attachments);\n if (result.accepted.length === 0) return;\n log.info(\"attach (drop)\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n bus.emit(\"attach\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n setAtts((prev) => [...prev, ...result.accepted]);\n },\n };\n return () => {\n if (attachApiRef.current) attachApiRef.current = null;\n };\n }, [attachApiRef, options.attachments]);\n\n // Panel-wide paste: text \u2192 append to textarea, files \u2192 attachment chips \u2014\n // regardless of which element inside the panel is focused. The focus-trap\n // in `Panel` keeps focus inside the panel, so this listener catches every\n // paste the user can produce while interacting with the widget. Skips\n // text paste when the textarea is itself the target so native paste keeps\n // cursor/selection semantics.\n useEffect(() => {\n const ta = taRef.current;\n if (!ta) return;\n const panel = ta.closest(`.${p}-panel`);\n if (!panel) return;\n\n const onPaste = (e: ClipboardEvent) => {\n const data = e.clipboardData;\n if (!data) return;\n\n if (filesEnabled) {\n const result = ingest(data.items, attsRef.current, options.attachments);\n if (result.accepted.length > 0) {\n e.preventDefault();\n log.info(\"attach (paste)\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n bus.emit(\"attach\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n setAtts((prev) => [...prev, ...result.accepted]);\n return;\n }\n }\n\n if (e.target === ta) return;\n const pastedText = data.getData(\"text/plain\");\n if (!pastedText) return;\n e.preventDefault();\n setText((prev) => prev + pastedText);\n ta.focus();\n };\n\n panel.addEventListener(\"paste\", onPaste as EventListener);\n return () => panel.removeEventListener(\"paste\", onPaste as EventListener);\n }, [filesEnabled, options.attachments, p]);\n\n const send = () => {\n const trimmed = text.trim();\n if (!trimmed && atts.length === 0) return;\n if (isStreaming) return;\n onSend(trimmed, atts);\n setText(\"\");\n setAtts([]);\n };\n\n const onKeydown = (e: KeyboardEvent) => {\n if (e.key !== \"Enter\" || e.shiftKey || e.isComposing) return;\n e.preventDefault();\n if (isStreaming) onStop();\n else send();\n };\n\n const onFilePick = (e: Event) => {\n const input = e.target as HTMLInputElement;\n const result = ingest(input.files, atts, options.attachments);\n if (result.accepted.length > 0) {\n log.info(\"attach (file picker)\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n bus.emit(\"attach\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n setAtts((prev) => [...prev, ...result.accepted]);\n }\n input.value = \"\";\n };\n\n const removeAtt = (id: string) => {\n log.info(\"removeAttachment\", { id });\n bus.emit(\"removeAttachment\", { id });\n setAtts((prev) => {\n const target = prev.find((a) => a.id === id);\n if (target) revoke(target);\n return prev.filter((a) => a.id !== id);\n });\n };\n\n const voiceStartedAtRef = useRef<number | null>(null);\n const toggleVoice = () => {\n if (!voice.isSupported) return;\n if (voiceOn) {\n const durationMs = voiceStartedAtRef.current ? Date.now() - voiceStartedAtRef.current : 0;\n log.info(\"voiceStop\", { durationMs });\n bus.emit(\"voiceStop\", { durationMs });\n voice.stop();\n setVoiceOn(false);\n feedback.play(\"voiceStop\");\n voiceStartedAtRef.current = null;\n return;\n }\n voiceStartedAtRef.current = Date.now();\n log.info(\"voiceStart\");\n bus.emit(\"voiceStart\", undefined);\n setVoiceOn(true);\n feedback.play(\"voiceStart\");\n voice.start(\n (transcript, isFinal) => setText((prev) => (isFinal ? `${prev}${transcript} ` : prev)),\n () => {\n setVoiceOn(false);\n // Auto-stop fired by the recognizer (e.g. silence detection) \u2014\n // treat as a normal stop; cancel paths use a separate UX flow.\n if (voiceStartedAtRef.current) {\n const durationMs = Date.now() - voiceStartedAtRef.current;\n log.debug(\"voiceStop (auto)\", { durationMs });\n bus.emit(\"voiceStop\", { durationMs });\n voiceStartedAtRef.current = null;\n }\n },\n );\n };\n\n // Pre-resolve the send-button icon component so the JSX below stays\n // declarative. `SEND_ICONS` is a small lookup over the four icon variants.\n const SendGlyph = SEND_ICONS[options.composer.sendButton.icon];\n\n return (\n <div class={`${p}-composer`} data-testid={TID.composer}>\n {atts.length > 0 ? (\n <div class={`${p}-attachments`}>\n {atts.map((a) => (\n <AttachmentChip key={a.id} attachment={a} onRemove={() => removeAtt(a.id)} />\n ))}\n </div>\n ) : null}\n <div class={`${p}-composer-row`}>\n <div class={`${p}-composer-actions`}>\n {filesEnabled ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={() => fileRef.current?.click()}\n aria-label={s.attach}\n data-testid={TID.composerAttach}\n >\n <PaperclipIcon />\n </button>\n ) : null}\n {voice.isSupported ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={toggleVoice}\n aria-label={voiceOn ? s.micStop : s.micStart}\n aria-pressed={voiceOn}\n data-recording={voiceOn ? \"true\" : undefined}\n data-testid={TID.composerVoice}\n >\n <MicIcon />\n </button>\n ) : null}\n </div>\n <textarea\n ref={taRef}\n class={`${p}-textarea`}\n rows={1}\n value={text}\n placeholder={s.composerPlaceholder}\n onInput={(e) => setText((e.target as HTMLTextAreaElement).value)}\n onKeyDown={onKeydown}\n aria-label={s.composerPlaceholder}\n data-testid={TID.composerInput}\n />\n {isStreaming ? (\n <button type=\"button\" class={`${p}-send`} onClick={onStop} aria-label={s.stop} data-testid={TID.composerStop}>\n <StopIcon />\n </button>\n ) : (\n <button\n type=\"button\"\n class={`${p}-send`}\n data-shape={options.composer.sendButton.shape}\n data-variant={options.composer.sendButton.variant}\n onClick={send}\n disabled={!text.trim() && atts.length === 0}\n aria-label={s.send}\n data-testid={TID.composerSend}\n >\n <SendGlyph />\n </button>\n )}\n </div>\n <input\n ref={fileRef}\n type=\"file\"\n multiple\n accept={options.attachments.accept}\n onChange={onFilePick}\n class={`${p}-sr-only`}\n data-testid={TID.composerFileInput}\n />\n </div>\n );\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n visible: boolean;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n}\n\nexport function DropZone({ visible, strings }: Props) {\n if (!visible) return null;\n return (\n <div class={`${p}-dropzone`} role=\"status\" aria-live=\"polite\">\n {strings.dropZone}\n </div>\n );\n}\n", "/**\n * Wire HTML5 file-drop on a container ref. Returns a `visible` flag\n * for the drop-zone overlay; the consumer renders `<DropZone\n * visible={visible} />` and reads dropped files through the `onDrop`\n * callback.\n *\n * Pulled out of `Panel` because the file-drop choreography (depth\n * counter to ignore child-element dragenter/leave noise; `Files`\n * type-list gate; preventDefault on dragover for the OS to allow the\n * drop) is mechanical plumbing that has nothing to do with panel\n * chrome. Keeping it inline made `Panel` re-render reasoning harder\n * than it needs to be.\n */\n\nimport type { RefObject } from \"preact\";\nimport { useEffect, useState } from \"preact/hooks\";\n\ninterface UseFileDropOptions {\n /** Container the file-drop is wired on. Drop anywhere inside fires `onDrop`. */\n containerRef: RefObject<HTMLElement | null>;\n /**\n * Fires with the raw `DataTransferItemList` from the drop event.\n * The caller decides how to parse / filter / ingest the items\n * (mime / extension checks, count limits, etc.).\n */\n onDrop: (items: DataTransferItemList) => void;\n}\n\nexport function useFileDrop({ containerRef, onDrop }: UseFileDropOptions): { visible: boolean } {\n const [visible, setVisible] = useState(false);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n // Depth counter \u2014 `dragenter`/`dragleave` fire for every child\n // element the cursor crosses. Without counting, the overlay\n // flickers as the cursor moves between children. Track NESTING\n // depth and only hide when we've left every level.\n let depth = 0;\n const onEnter = (e: DragEvent) => {\n depth += 1;\n if (!e.dataTransfer?.types.includes(\"Files\")) return;\n e.preventDefault();\n setVisible(true);\n };\n const onLeave = () => {\n depth -= 1;\n if (depth > 0) return;\n depth = 0;\n setVisible(false);\n };\n const onOver = (e: DragEvent) => {\n // `preventDefault` is what tells the OS \"yes, drop here is OK\"\n // \u2014 without it the cursor stays on the \"no drop\" indicator.\n if (e.dataTransfer?.types.includes(\"Files\")) e.preventDefault();\n };\n const onDropEvent = (e: DragEvent) => {\n depth = 0;\n setVisible(false);\n if (!e.dataTransfer) return;\n e.preventDefault();\n onDrop(e.dataTransfer.items);\n };\n el.addEventListener(\"dragenter\", onEnter);\n el.addEventListener(\"dragleave\", onLeave);\n el.addEventListener(\"dragover\", onOver);\n el.addEventListener(\"drop\", onDropEvent);\n return () => {\n el.removeEventListener(\"dragenter\", onEnter);\n el.removeEventListener(\"dragleave\", onLeave);\n el.removeEventListener(\"dragover\", onOver);\n el.removeEventListener(\"drop\", onDropEvent);\n };\n }, [containerRef, onDrop]);\n\n return { visible };\n}\n", "/*\nStreaming Markdown Parser and Renderer\nMIT License\nCopyright 2024 Damian Tarnawski\nhttps://github.com/thetarnav/streaming-markdown\n*/\n\nexport const\n DOCUMENT = 1,\n PARAGRAPH = 2,\n HEADING_1 = 3,\n HEADING_2 = 4,\n HEADING_3 = 5,\n HEADING_4 = 6,\n HEADING_5 = 7,\n HEADING_6 = 8,\n CODE_BLOCK = 9,\n CODE_FENCE = 10,\n CODE_INLINE = 11,\n ITALIC_AST = 12,\n ITALIC_UND = 13,\n STRONG_AST = 14,\n STRONG_UND = 15,\n STRIKE = 16,\n LINK = 17,\n RAW_URL = 18,\n IMAGE = 19,\n BLOCKQUOTE = 20,\n LINE_BREAK = 21,\n RULE = 22,\n LIST_UNORDERED = 23,\n LIST_ORDERED = 24,\n LIST_ITEM = 25,\n CHECKBOX = 26,\n TABLE = 27,\n TABLE_ROW = 28,\n TABLE_CELL = 29,\n EQUATION_BLOCK = 30,\n EQUATION_INLINE = 31,\n NEWLINE = 101,\n MAYBE_URL = 102,\n MAYBE_TASK = 103,\n MAYBE_BR = 104,\n MAYBE_EQ_BLOCK = 105\n\n/** @enum {(typeof Token)[keyof typeof Token]} */\nexport const Token = /** @type {const} */({\n Document: DOCUMENT,\n Blockquote: BLOCKQUOTE,\n Paragraph: PARAGRAPH,\n Heading_1: HEADING_1,\n Heading_2: HEADING_2,\n Heading_3: HEADING_3,\n Heading_4: HEADING_4,\n Heading_5: HEADING_5,\n Heading_6: HEADING_6,\n Code_Block: CODE_BLOCK,\n Code_Fence: CODE_FENCE,\n Code_Inline: CODE_INLINE,\n Italic_Ast: ITALIC_AST,\n Italic_Und: ITALIC_UND,\n Strong_Ast: STRONG_AST,\n Strong_Und: STRONG_UND,\n Strike: STRIKE,\n Link: LINK,\n Raw_URL: RAW_URL,\n Image: IMAGE,\n Line_Break: LINE_BREAK,\n Rule: RULE,\n List_Unordered: LIST_UNORDERED,\n List_Ordered: LIST_ORDERED,\n List_Item: LIST_ITEM,\n Checkbox: CHECKBOX,\n Table: TABLE,\n Table_Row: TABLE_ROW,\n Table_Cell: TABLE_CELL,\n Equation_Block: EQUATION_BLOCK,\n Equation_Inline:EQUATION_INLINE,\n})\n\n/**\n * @param {Token} type\n * @returns {string } */\nexport function token_to_string(type) {\n switch (type) {\n case DOCUMENT: return \"Document\"\n case BLOCKQUOTE: return \"Blockquote\"\n case PARAGRAPH: return \"Paragraph\"\n case HEADING_1: return \"Heading_1\"\n case HEADING_2: return \"Heading_2\"\n case HEADING_3: return \"Heading_3\"\n case HEADING_4: return \"Heading_4\"\n case HEADING_5: return \"Heading_5\"\n case HEADING_6: return \"Heading_6\"\n case CODE_BLOCK: return \"Code_Block\"\n case CODE_FENCE: return \"Code_Fence\"\n case CODE_INLINE: return \"Code_Inline\"\n case ITALIC_AST: return \"Italic_Ast\"\n case ITALIC_UND: return \"Italic_Und\"\n case STRONG_AST: return \"Strong_Ast\"\n case STRONG_UND: return \"Strong_Und\"\n case STRIKE: return \"Strike\"\n case LINK: return \"Link\"\n case RAW_URL: return \"Raw URL\"\n case IMAGE: return \"Image\"\n case LINE_BREAK: return \"Line_Break\"\n case RULE: return \"Rule\"\n case LIST_UNORDERED: return \"List_Unordered\"\n case LIST_ORDERED: return \"List_Ordered\"\n case LIST_ITEM: return \"List_Item\"\n case CHECKBOX: return \"Checkbox\"\n case TABLE: return \"Table\"\n case TABLE_ROW: return \"Table_Row\"\n case TABLE_CELL: return \"Table_Cell\"\n case EQUATION_BLOCK: return \"Equation_Block\"\n case EQUATION_INLINE:return \"Equation_Inline\"\n }\n}\n\nexport const\n HREF = 1,\n SRC = 2,\n LANG = 4,\n CHECKED = 8,\n START = 16\n\n/** @enum {(typeof Attr)[keyof typeof Attr]} */\nexport const Attr = /** @type {const} */({\n Href : HREF,\n Src : SRC,\n Lang : LANG,\n Checked: CHECKED,\n Start : START,\n})\n\n/**\n * @param {Attr} type\n * @returns {string } */\nexport function attr_to_html_attr(type) {\n switch (type) {\n case HREF: return \"href\"\n case SRC : return \"src\"\n case LANG: return \"class\"\n case CHECKED: return \"checked\"\n case START: return \"start\"\n }\n}\n\n/**\n * @param {number} level\n * @returns {Token } */\nexport const level_to_heading = (level) => {\n switch (level) {\n case 1: return HEADING_1\n case 2: return HEADING_2\n case 3: return HEADING_3\n case 4: return HEADING_4\n case 5: return HEADING_5\n default: return HEADING_6\n }\n}\nexport const heading_from_level = level_to_heading\n\n/**\n * @param {Token} token\n * @returns {number} */\nexport const heading_to_level = (token) => {\n switch (token) {\n case HEADING_1: return 1\n case HEADING_2: return 2\n case HEADING_3: return 3\n case HEADING_4: return 4\n case HEADING_5: return 5\n case HEADING_6: return 6\n default: return 0\n }\n}\n\n/**\n * @typedef {object } Parser\n * @property {Any_Renderer} renderer - {@link Renderer} interface\n * @property {string } text - Text to be added to the last token in the next flush\n * @property {string } pending - Characters for identifying tokens\n * @property {Uint32Array } tokens - Current token and it's parents (a slice of a tree)\n * @property {number } len - Number of tokens in types without root\n * @property {number } token - Last token in the tree\n * @property {Uint8Array } spaces\n * @property {string } indent\n * @property {number } indent_len\n * @property {number } fence_end - For {@link Token.Code_Fence} parsing\n * @property {number } fence_start\n * @property {number } blockquote_idx - For Blockquote parsing\n * @property {string } hr_char - For horizontal rule parsing\n * @property {number } hr_chars - For horizontal rule parsing\n * @property {number } table_state\n */\n\nconst TOKEN_ARRAY_CAP = 24\n\n/**\n * Makes a new Parser object.\n * @param {Any_Renderer} renderer\n * @returns {Parser } */\nexport function parser(renderer) {\n const tokens = new Uint32Array(TOKEN_ARRAY_CAP)\n tokens[0] = DOCUMENT\n return {\n renderer : renderer,\n text : \"\",\n pending : \"\",\n tokens : tokens,\n len : 0,\n token : DOCUMENT,\n fence_end: 0,\n blockquote_idx: 0,\n hr_char : '',\n hr_chars : 0,\n fence_start: 0,\n spaces : new Uint8Array(TOKEN_ARRAY_CAP),\n indent : \"\",\n indent_len : 0,\n table_state: 0,\n }\n}\n\n/**\n * Finish rendering the markdown - flushes any remaining text.\n * @param {Parser} p\n * @returns {void } */\nexport function parser_end(p) {\n if (p.pending.length > 0) {\n parser_write(p, \"\\n\")\n }\n}\n\n/**\n * @param {Parser} p\n * @returns {void } */\nfunction add_text(p) {\n if (p.text.length === 0) return\n console.assert(p.len > 0, \"Never adding text to root\")\n p.renderer.add_text(p.renderer.data, p.text)\n p.text = \"\"\n}\n\n/**\n * @param {Parser} p\n * @returns {void } */\nfunction ensure_paragraph(p) {\n switch (p.token) {\n case LINE_BREAK:\n case DOCUMENT:\n case BLOCKQUOTE:\n case LIST_ORDERED:\n case LIST_UNORDERED:\n add_token(p, PARAGRAPH)\n }\n}\n\n/**\n * @param {Parser} p\n * @param {string} text\n * @returns {void } */\nfunction push_text(p, text) {\n ensure_paragraph(p)\n p.text += text\n}\n\n/**\n * @param {Parser} p\n * @returns {void } */\nfunction end_token(p) {\n console.assert(p.len > 0, \"No nodes to end\")\n p.len -= 1\n p.token = /** @type {Token} */ (p.tokens[p.len])\n p.renderer.end_token(p.renderer.data)\n}\n\n/**\n * @param {Parser} p\n * @param {Token } token\n * @returns {void } */\nfunction add_token(p, token) {\n /*\n If a list doesn't start with a list item\n it means that there was a newline after the list:\n\n 1. foo\n 2. bar\n <empty line>\n <not_a_list_item> <- new token\n */\n if ((p.tokens[p.len] === LIST_ORDERED || p.tokens[p.len] === LIST_UNORDERED) &&\n token !== LIST_ITEM\n ) {\n end_token(p)\n }\n\n p.len += 1\n p.tokens[p.len] = token\n p.token = token\n p.renderer.add_token(p.renderer.data, token)\n}\n\n/**\n * @param {Parser} p\n * @param {number} token\n * @param {number} start_idx\n * @returns {number} */\nfunction idx_of_token(p, token, start_idx) {\n while (start_idx <= p.len) {\n if (p.tokens[start_idx] === token) {\n return start_idx\n }\n start_idx += 1\n }\n return -1\n}\n\n/**\n * End tokens until the parser has the given length.\n * @param {Parser} p\n * @param {number} len\n * @returns {void } */\nfunction end_tokens_to_len(p, len) {\n\n // TODO: specific token state should be reset only when the token ends\n p.fence_start = 0\n\n while (p.len > len) {\n end_token(p)\n }\n}\n\n/**\n * @param {Parser} p\n * @param {number} indent\n * @returns {number} */\nfunction end_tokens_to_indent(p, indent) {\n\n let idx = 0\n for (let i = 0; i <= p.len; i += 1) {\n indent -= p.spaces[i]\n if (indent < 0) {\n break \n }\n switch (p.tokens[i]) {\n case CODE_BLOCK:\n case CODE_FENCE:\n case BLOCKQUOTE:\n case LIST_ITEM:\n idx = i\n break\n }\n }\n\n while (p.len > idx) {end_token(p)}\n \n return indent\n}\n\n/**\n * @param {Parser } p\n * @param {Token } list_token\n * @returns {boolean} added a new list */\nfunction continue_or_add_list(p, list_token) {\n /* will create a new list inside the last item\n if the amount of spaces is greater than the last one (with prefix)\n 1. foo\n - bar <- new nested ul\n - baz <- new nested ul\n 12. qux <- cannot be nested in \"baz\" or \"bar\",\n so it's a new list in \"foo\"\n */\n let list_idx = -1\n let item_idx = -1\n\n for (let i = p.blockquote_idx+1; i <= p.len; i += 1) {\n if (p.tokens[i] === LIST_ITEM) {\n if (p.indent_len < p.spaces[i]) {\n item_idx = -1\n break\n }\n item_idx = i\n } else if (p.tokens[i] === list_token) {\n list_idx = i\n }\n }\n\n if (item_idx === -1) {\n if (list_idx === -1) {\n end_tokens_to_len(p, p.blockquote_idx)\n add_token(p, list_token)\n return true\n }\n end_tokens_to_len(p, list_idx)\n return false\n }\n end_tokens_to_len(p, item_idx)\n add_token(p, list_token)\n return true\n}\n\n/**\n * Create a new list\n * or continue the last one\n * @param {Parser } p\n * @param {number } prefix_length\n * @returns {void } */\nfunction add_list_item(p, prefix_length) {\n add_token(p, LIST_ITEM)\n p.spaces[p.len] = p.indent_len + prefix_length\n clear_root_pending(p)\n p.token = MAYBE_TASK\n}\n\n/**\n * @param {Parser} p\n * @returns {void } */\nfunction clear_root_pending(p) {\n p.indent = \"\"\n p.indent_len = 0\n p.pending = \"\"\n}\n\n/**\n * @param {number} charcode\n * @returns {boolean} */\nfunction is_digit(charcode) {\n switch (charcode) {\n case 48: case 49: case 50: case 51: case 52:\n case 53: case 54: case 55: case 56: case 57:\n return true\n default:\n return false\n }\n}\n\n/**\n * @param {number} charcode\n * @returns {boolean} */\nfunction is_delimeter(charcode) {\n switch (charcode) {\n // \" \" \":\" \";\" \")\" \",\" \"!\" \".\" \"?\" \"]\" \"\\n\"\n case 32: case 58: case 59: case 41: case 44: case 33: case 46: case 63: case 93: case 10: \n return true\n default:\n return false\n }\n}\n\n/**\n * @param {number} charcode\n * @returns {boolean} */\nfunction is_delimeter_or_number(charcode) {\n return is_digit(charcode) || is_delimeter(charcode)\n}\n\n/**\n * @param {number} charcode\n * @returns {boolean} */\nfunction is_alnum(charcode) {\n return is_digit(charcode) || // 0-9\n (charcode >= 65 && charcode <= 90) || // A-Z\n (charcode >= 97 && charcode <= 122) // a-z\n}\n\n/**\n * Parse and render another chunk of markdown.\n * @param {Parser} p\n * @param {string} chunk\n * @returns {void } */\nexport function parser_write(p, chunk) {\n for (const char of chunk) {\n\n /*\n Handle newlines\n */\n if (p.token === NEWLINE) {\n switch (char) {\n case ' ':\n p.indent_len += 1\n continue\n case '\\t':\n p.indent_len += 4\n continue\n }\n \n let indent = end_tokens_to_indent(p, p.indent_len)\n \n p.indent_len = 0\n p.token = p.tokens[p.len]\n\n if (indent > 0) {\n parser_write(p, \" \".repeat(indent))\n }\n }\n\n const pending_with_char = p.pending + char\n\n /*\n Token specific checks\n */\n switch (p.token) {\n case LINE_BREAK:\n case DOCUMENT:\n case BLOCKQUOTE:\n case LIST_ORDERED:\n case LIST_UNORDERED:\n console.assert(p.text.length === 0, \"Root should not have any text\")\n\n switch (p.pending[0]) {\n case undefined:\n p.pending = char\n continue\n case ' ':\n console.assert(p.pending.length === 1)\n p.pending = char\n p.indent += ' '\n p.indent_len += 1\n continue\n case '\\t':\n console.assert(p.pending.length === 1)\n p.pending = char\n p.indent += '\\t'\n p.indent_len += 4\n continue\n case '\\n':\n console.assert(p.pending.length === 1)\n /*\n Lists can have an empty line in between items:\n 1. foo\n <empty>\n 2. bar\n */\n if (p.tokens[p.len] === LIST_ITEM && p.token === LINE_BREAK) {\n end_token(p)\n clear_root_pending(p)\n p.pending = char\n continue\n }\n /*\n Exit out of tokens\n And ignore newlines in root\n */\n end_tokens_to_len(p, p.blockquote_idx)\n clear_root_pending(p)\n p.blockquote_idx = 0\n p.fence_start = 0\n p.pending = char\n continue\n /* Heading */\n case '#':\n switch (char) {\n case '#':\n if (p.pending.length < 6) {\n p.pending = pending_with_char\n continue\n }\n break // fail\n case ' ':\n end_tokens_to_indent(p, p.indent_len)\n add_token(p, heading_from_level(p.pending.length))\n clear_root_pending(p)\n continue\n }\n break // fail\n /* Blockquote */\n case '>': {\n const next_blockquote_idx = idx_of_token(p, BLOCKQUOTE, p.blockquote_idx+1)\n\n /*\n Only when there is no blockquote to the right of blockquote_idx\n a new blockquote can be created\n */\n if (next_blockquote_idx === -1) {\n end_tokens_to_len(p, p.blockquote_idx)\n p.blockquote_idx += 1\n p.fence_start = 0\n add_token(p, BLOCKQUOTE)\n } else {\n p.blockquote_idx = next_blockquote_idx\n }\n\n clear_root_pending(p)\n p.pending = char\n continue\n }\n /* Horizontal Rule\n \"-- - --- - --\"\n */\n case '-':\n case '*':\n case '_':\n if (p.hr_chars === 0) {\n console.assert(p.pending.length === 1, \"Pending should be one character\")\n p.hr_chars = 1\n p.hr_char = p.pending\n }\n\n if (p.hr_chars > 0) {\n switch (char) {\n case p.hr_char:\n p.hr_chars += 1\n p.pending = pending_with_char\n continue\n case ' ':\n p.pending = pending_with_char\n continue\n case '\\n':\n if (p.hr_chars < 3) break\n end_tokens_to_indent(p, p.indent_len)\n p.renderer.add_token(p.renderer.data, RULE)\n p.renderer.end_token(p.renderer.data)\n clear_root_pending(p)\n p.hr_chars = 0\n continue\n }\n\n p.hr_chars = 0\n }\n\n /* Unordered list\n / * foo\n / * *bar*\n / * **baz**\n /*/\n if ('_' !== p.pending[0] &&\n ' ' === p.pending[1]\n ) {\n continue_or_add_list(p, LIST_UNORDERED)\n add_list_item(p, 2)\n parser_write(p, pending_with_char.slice(2))\n continue\n }\n\n break // fail\n /* Code Fence */\n case '`':\n /* ``?\n ^\n */\n if (p.pending.length < 3) {\n if ('`' === char) {\n p.pending = pending_with_char\n p.fence_start = pending_with_char.length\n continue\n }\n p.fence_start = 0\n break // fail\n }\n\n switch (char) {\n case '`':\n /* ````?\n ^\n */\n if (p.pending.length === p.fence_start) {\n p.pending = pending_with_char\n p.fence_start = pending_with_char.length\n }\n /* ```code`\n ^\n */\n else {\n add_token(p, PARAGRAPH)\n clear_root_pending(p)\n p.fence_start = 0\n parser_write(p, pending_with_char)\n }\n continue\n case '\\n': {\n /* ```lang\\n\n ^\n */\n end_tokens_to_indent(p, p.indent_len)\n \n add_token(p, CODE_FENCE)\n if (p.pending.length > p.fence_start) {\n p.renderer.set_attr(p.renderer.data, LANG, p.pending.slice(p.fence_start))\n }\n clear_root_pending(p)\n p.token = NEWLINE\n continue\n }\n default:\n /* ```lang\\n\n ^\n */\n p.pending = pending_with_char\n continue\n }\n /*\n List Unordered for '+'\n The other list types are handled with HORIZONTAL_RULE\n */\n case '+':\n if (' ' !== char) break // fail\n\n continue_or_add_list(p, LIST_UNORDERED)\n add_list_item(p, 2)\n continue\n /* List Ordered */\n case '0': case '1': case '2': case '3': case '4':\n case '5': case '6': case '7': case '8': case '9':\n /*\n 12. foo\n ^\n */\n if ('.' === p.pending[p.pending.length-1]) {\n if (' ' !== char) break // fail\n\n if (continue_or_add_list(p, LIST_ORDERED) && p.pending !== \"1.\") {\n p.renderer.set_attr(p.renderer.data, START, p.pending.slice(0, -1))\n }\n add_list_item(p, p.pending.length+1)\n continue\n } else {\n const char_code = char.charCodeAt(0)\n if (46 === char_code || // '.'\n is_digit(char_code) // 0-9\n ) {\n p.pending = pending_with_char\n continue\n }\n }\n break // fail\n /* Table */\n case '|':\n end_tokens_to_len(p, p.blockquote_idx)\n\n add_token(p, TABLE)\n add_token(p, TABLE_ROW)\n\n p.pending = \"\"\n parser_write(p, char)\n\n continue\n }\n\n let to_write = pending_with_char\n\n /* Add a line break and continue in previous token */\n if (p.token === LINE_BREAK) {\n p.token = p.tokens[p.len]\n p.renderer.add_token(p.renderer.data, LINE_BREAK)\n p.renderer.end_token(p.renderer.data)\n }\n /* Code Block */\n else if (p.indent_len >= 4) {\n /*\n Case where there are additional spaces\n after the indent that makes the code block\n _________________________\n code\n ^^^^----indent\n ^^^-part of code\n _________________________\n \\t code\n ^^-----indent\n ^^^-part of code\n */\n let code_start = 0\n for (; code_start < 4; code_start += 1) {\n if (p.indent[code_start] === '\\t') {\n code_start = code_start+1\n break\n }\n }\n to_write = p.indent.slice(code_start) + pending_with_char\n add_token(p, CODE_BLOCK)\n }\n /* Paragraph */\n else {\n add_token(p, PARAGRAPH)\n }\n\n clear_root_pending(p)\n parser_write(p, to_write)\n continue\n case TABLE:\n if (p.table_state === 1) {\n switch (char) {\n case '-':\n case ' ':\n case '|':\n case ':':\n p.pending = pending_with_char\n continue\n case '\\n':\n p.table_state = 2\n p.pending = \"\"\n continue\n default:\n end_token(p)\n p.table_state = 0\n break\n }\n } else {\n switch (p.pending) {\n case \"|\":\n add_token(p, TABLE_ROW)\n p.pending = \"\"\n parser_write(p, char)\n continue\n case \"\\n\":\n end_token(p)\n p.pending = \"\"\n p.table_state = 0\n parser_write(p, char)\n continue\n }\n }\n break\n case TABLE_ROW:\n switch (p.pending) {\n case \"\":\n break\n case \"|\":\n add_token(p, TABLE_CELL)\n end_token(p)\n p.pending = \"\"\n parser_write(p, char)\n continue\n case \"\\n\":\n end_token(p)\n p.table_state = Math.min(p.table_state+1, 2)\n p.pending = \"\"\n parser_write(p, char)\n continue\n default:\n add_token(p, TABLE_CELL)\n parser_write(p, char)\n continue\n }\n break\n case TABLE_CELL:\n if (p.pending === \"|\") {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n parser_write(p, char)\n continue\n }\n break\n case CODE_BLOCK:\n switch (pending_with_char) {\n case \"\\n \":\n case \"\\n \\t\":\n case \"\\n \\t\":\n case \"\\n \\t\":\n case \"\\n\\t\":\n p.text += \"\\n\"\n p.pending = \"\"\n continue\n case \"\\n\":\n case \"\\n \":\n case \"\\n \":\n case \"\\n \":\n p.pending = pending_with_char\n continue\n default:\n if (p.pending.length !== 0) {\n add_text(p)\n end_token(p)\n p.pending = char\n } else {\n p.text += char\n }\n continue\n }\n case CODE_FENCE:\n switch (char) {\n case '`':\n /* ```\\n<code>\\n``??\n | ^\n */\n p.pending = pending_with_char\n continue\n case '\\n':\n /* ```\\n<code>\\n```\\n\n | ^\n */\n if (pending_with_char.length === p.fence_start + p.fence_end + 1) {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n p.fence_start = 0\n p.fence_end = 0\n p.token = NEWLINE\n continue\n }\n p.token = NEWLINE\n break\n case ' ':\n /* ```\\n<code>\\n ??\n | ^ (space after newline is allowed)\n */\n if (p.pending[0] === '\\n') {\n p.pending = pending_with_char\n p.fence_end += 1\n continue\n }\n break\n }\n // any other char\n p.text += p.pending\n p.pending = char\n p.fence_end = 1\n continue\n case CODE_INLINE:\n switch (char) {\n case '`':\n if (pending_with_char.length ===\n p.fence_start + Number(p.pending[0] === ' ') // 0 or 1 for space\n ) {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n p.fence_start = 0\n } else {\n p.pending = pending_with_char\n }\n continue\n case '\\n':\n p.text += p.pending\n p.pending = \"\"\n p.token = LINE_BREAK\n p.blockquote_idx = 0\n add_text(p)\n continue\n /* Trim space before ` */\n case ' ':\n p.text += p.pending\n p.pending = char\n continue\n default:\n p.text += pending_with_char\n p.pending = \"\"\n continue\n }\n /* Checkboxes */\n case MAYBE_TASK:\n switch (p.pending.length) {\n case 0:\n if ('[' !== char) break // fail\n p.pending = pending_with_char\n continue\n case 1:\n if (' ' !== char && 'x' !== char) break // fail\n p.pending = pending_with_char\n continue\n case 2:\n if (']' !== char) break // fail\n p.pending = pending_with_char\n continue\n case 3:\n if (' ' !== char) break // fail\n p.renderer.add_token(p.renderer.data, CHECKBOX)\n if ('x' === p.pending[1]) {\n p.renderer.set_attr(p.renderer.data, CHECKED, \"\")\n }\n p.renderer.end_token(p.renderer.data)\n p.pending = \" \"\n continue\n }\n\n p.token = p.tokens[p.len]\n p.pending = \"\"\n parser_write(p, pending_with_char)\n continue\n case STRONG_AST:\n case STRONG_UND: {\n /** @type {string} */ let symbol = '*'\n /** @type {Token } */ let italic = ITALIC_AST\n if (p.token === STRONG_UND) {\n symbol = '_'\n italic = ITALIC_UND\n }\n\n if (symbol === p.pending) {\n add_text(p)\n /* **Bold**\n ^\n */\n if (symbol === char) {\n end_token(p)\n p.pending = \"\"\n continue\n }\n /* **Bold*Bold->Em*\n ^\n */\n add_token(p, italic)\n p.pending = char\n continue\n }\n\n break\n }\n case ITALIC_AST:\n case ITALIC_UND: {\n /** @type {string} */ let symbol = '*'\n /** @type {Token } */ let strong = STRONG_AST\n if (p.token === ITALIC_UND) {\n symbol = '_'\n strong = STRONG_UND\n }\n\n switch (p.pending) {\n case symbol:\n if (symbol === char) {\n /* Decide between ***bold>em**em* and **bold*bold>em***\n ^ ^\n With the help of the next character\n */\n if (p.tokens[p.len-1] === strong) {\n p.pending = pending_with_char\n }\n /* *em**bold\n ^\n */\n else {\n add_text(p)\n add_token(p, strong)\n p.pending = \"\"\n }\n }\n /* *em*foo\n ^\n */\n else {\n add_text(p)\n end_token(p)\n p.pending = char\n }\n continue\n case symbol+symbol:\n const italic = p.token\n add_text(p)\n end_token(p)\n end_token(p)\n /* ***bold>em**em* or **bold*bold>em***\n ^ ^\n */\n if (symbol !== char) {\n add_token(p, italic)\n p.pending = char\n } else {\n p.pending = \"\"\n }\n continue\n }\n break\n }\n case STRIKE:\n if (\"~~\" === pending_with_char) {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n continue\n }\n break\n case MAYBE_EQ_BLOCK:\n /*\n \\[? or $$?\n ^ ^\n */\n if (char === '\\n') {\n add_text(p)\n add_token(p, EQUATION_BLOCK)\n p.pending = \"\"\n } else {\n p.token = p.tokens[p.len]\n if (p.pending[0] === '\\\\') {\n p.text += '['\n } else {\n p.text += '$$'\n }\n p.pending = \"\"\n parser_write(p, char)\n }\n continue\n case EQUATION_BLOCK:\n if (\"\\\\]\" === pending_with_char || \"$$\" === pending_with_char) {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n continue\n }\n break\n case EQUATION_INLINE:\n if (\"\\\\)\" === pending_with_char || \"$\" === p.pending[0]) {\n add_text(p)\n end_token(p)\n\n if(char === ')'){\n p.pending = \"\"\n } else {\n p.pending = char\n }\n continue\n }\n break\n /* Raw URLs */\n case MAYBE_URL:\n if (\"http://\" === pending_with_char ||\n \"https://\" === pending_with_char\n ) {\n add_text(p)\n add_token(p, RAW_URL)\n p.pending = pending_with_char\n p.text = pending_with_char\n }\n else\n if (\"http:/\" [p.pending.length] === char ||\n \"https:/\"[p.pending.length] === char\n ) {\n p.pending = pending_with_char\n }\n else {\n p.token = p.tokens[p.len]\n parser_write(p, char)\n }\n continue\n case LINK:\n case IMAGE:\n if (\"]\" === p.pending) {\n /*\n [Link](url)\n ^\n */\n add_text(p)\n if ('(' === char) {\n p.pending = pending_with_char\n } else {\n end_token(p)\n p.pending = char\n }\n continue\n }\n if (']' === p.pending[0] &&\n '(' === p.pending[1]\n ) {\n /*\n [Link](url)\n ^\n */\n if (')' === char) {\n const type = p.token === LINK ? HREF : SRC\n const url = p.pending.slice(2)\n p.renderer.set_attr(p.renderer.data, type, url)\n end_token(p)\n p.pending = \"\"\n } else {\n p.pending += char\n }\n continue\n }\n break\n case RAW_URL:\n /* http://example.com?\n ^\n */\n if (' ' === char ||\n '\\n'=== char ||\n '\\\\'=== char\n ) {\n p.renderer.set_attr(p.renderer.data, HREF, p.pending)\n add_text(p)\n end_token(p)\n p.pending = char\n } else {\n p.text += char\n p.pending = pending_with_char\n }\n continue\n case MAYBE_BR:\n if (pending_with_char.startsWith(\"<br\")) {\n if (/* \"<br\" */\n pending_with_char.length === 3 ||\n /* \"<br \" */\n char === ' ' ||\n /* \"<br/\" | \"<br /\" */\n char === '/' && (pending_with_char.length === 4 ||\n p.pending[p.pending.length-1] === ' ')\n ) {\n p.pending = pending_with_char\n continue\n }\n\n /* \"<br>\" | \"<br/>\" */\n if (char === '>') {\n add_text(p)\n p.token = p.tokens[p.len]\n p.renderer.add_token(p.renderer.data, LINE_BREAK)\n p.renderer.end_token(p.renderer.data)\n p.pending = \"\"\n continue\n }\n }\n // Fail\n p.token = p.tokens[p.len]\n p.text += '<'\n p.pending = p.pending.slice(1)\n parser_write(p, char)\n continue\n }\n\n /*\n Common checks\n */\n switch (p.pending[0]) {\n /* Escape character */\n case '\\\\':\n if (p.token === IMAGE ||\n p.token === EQUATION_BLOCK ||\n p.token === EQUATION_INLINE)\n break\n\n switch (char) {\n case '(':\n add_text(p)\n add_token(p, EQUATION_INLINE)\n p.pending = \"\"\n continue\n case '[':\n p.token = MAYBE_EQ_BLOCK\n p.pending = pending_with_char\n continue\n case '\\n':\n // Escaped newline has the same affect as unescaped one\n p.pending = char\n continue\n default:\n let charcode = char.charCodeAt(0)\n p.pending = \"\"\n p.text += is_digit(charcode) || // 0-9\n (charcode >= 65 && charcode <= 90) || // A-Z\n (charcode >= 97 && charcode <= 122) // a-z\n ? pending_with_char\n : char\n continue\n }\n /* Newline */\n case '\\n':\n switch (p.token) {\n case IMAGE:\n case EQUATION_BLOCK:\n case EQUATION_INLINE:\n break\n case HEADING_1:\n case HEADING_2:\n case HEADING_3:\n case HEADING_4:\n case HEADING_5:\n case HEADING_6:\n add_text(p)\n end_tokens_to_len(p, p.blockquote_idx)\n p.blockquote_idx = 0\n p.pending = char\n continue\n default:\n add_text(p)\n p.pending = char\n p.token = LINE_BREAK\n p.blockquote_idx = 0\n continue\n }\n break\n /* <br> */\n case '<':\n if (p.token !== IMAGE &&\n p.token !== EQUATION_BLOCK &&\n p.token !== EQUATION_INLINE\n ) {\n add_text(p)\n p.pending = pending_with_char\n p.token = MAYBE_BR\n continue\n }\n break\n /* `Code Inline` */\n case '`':\n if (p.token === IMAGE) break\n\n if ('`' === char) {\n p.fence_start += 1\n p.pending = pending_with_char\n } else {\n p.fence_start += 1 // started at 0, and first wasn't counted\n add_text(p)\n add_token(p, CODE_INLINE)\n p.text = ' ' === char || '\\n' === char ? \"\" : char // trim leading space\n p.pending = \"\"\n }\n continue\n case '_':\n case '*': {\n if (p.token === IMAGE ||\n p.token === EQUATION_BLOCK ||\n p.token === EQUATION_INLINE ||\n p.token === STRONG_AST)\n break\n\n /** @type {Token} */ let italic = ITALIC_AST\n /** @type {Token} */ let strong = STRONG_AST\n const symbol = p.pending[0]\n if ('_' === symbol) {\n italic = ITALIC_UND\n strong = STRONG_UND\n }\n\n if (p.pending.length === 1) {\n /* **Strong**\n ^\n */\n if (symbol === char) {\n p.pending = pending_with_char\n continue\n }\n /* *Em*\n ^\n */\n if (' ' !== char && '\\n' !== char) {\n add_text(p)\n add_token(p, italic)\n p.pending = char\n continue\n }\n } else {\n /* ***Strong->Em***\n ^\n */\n if (symbol === char) {\n add_text(p)\n add_token(p, strong)\n add_token(p, italic)\n p.pending = \"\"\n continue\n }\n /* **Strong**\n ^\n */\n if (' ' !== char && '\\n' !== char) {\n add_text(p)\n add_token(p, strong)\n p.pending = char\n continue\n }\n }\n\n break\n }\n case '~':\n if (p.token !== IMAGE &&\n p.token !== STRIKE\n ) {\n if (\"~\" === p.pending) {\n /* ~~Strike~~\n ^\n */\n if ('~' === char) {\n p.pending = pending_with_char\n continue\n }\n } else {\n /* ~~Strike~~\n | ^\n */\n if (' ' !== char && '\\n' !== char) {\n add_text(p)\n add_token(p, STRIKE)\n p.pending = char\n continue\n }\n }\n }\n break\n /* $eq$ | $$eq$$ */\n case '$':\n if (p.token !== IMAGE &&\n p.token !== STRIKE &&\n \"$\" === p.pending\n ) {\n /* $$EQUATION_BLOCK$$\n ^\n */\n if ('$' === char) {\n p.token = MAYBE_EQ_BLOCK\n p.pending = pending_with_char\n continue\n }\n /* $123\n ^\n */\n else if (is_delimeter_or_number(char.charCodeAt(0))) {\n break\n }\n /* $EQUATION_INLINE$\n ^\n */\n else {\n add_text(p)\n add_token(p, EQUATION_INLINE)\n p.pending = char\n continue\n }\n }\n break\n /* [Image](url) */\n case '[':\n if (p.token !== IMAGE &&\n p.token !== LINK &&\n p.token !== EQUATION_BLOCK &&\n p.token !== EQUATION_INLINE &&\n ']' !== char\n ) {\n add_text(p)\n add_token(p, LINK)\n p.pending = char\n continue\n }\n break\n /*  */\n case '!':\n if (!(p.token === IMAGE) &&\n '[' === char\n ) {\n add_text(p)\n add_token(p, IMAGE)\n p.pending = \"\"\n continue\n }\n break\n /* Trim spaces */\n case ' ':\n if (p.pending.length === 1 && ' ' === char) {\n continue\n }\n break\n }\n\n /* foo http://...\n | ^\n */\n if (p.token !== IMAGE &&\n p.token !== LINK &&\n p.token !== EQUATION_BLOCK &&\n p.token !== EQUATION_INLINE &&\n 'h' === char &&\n (\" \" === p.pending ||\n \"\" === p.pending)\n ) {\n p.text += p.pending\n p.pending = char\n\n p.token = MAYBE_URL\n continue\n }\n\n /*\n No check hit\n */\n p.text += p.pending\n p.pending = char\n }\n\n add_text(p)\n}\n\n\n/**\n * @template T\n * @callback Renderer_Add_Token\n * @param {T } data\n * @param {Token} type\n * @returns {void } */\n\n/**\n * @template T\n * @callback Renderer_End_Token\n * @param {T } data\n * @returns {void } */\n\n/**\n * @template T\n * @callback Renderer_Add_Text\n * @param {T } data\n * @param {string} text\n * @returns {void } */\n\n/**\n * @template T\n * @callback Renderer_Set_Attr\n * @param {T } data\n * @param {Attr } type\n * @param {string} value\n * @returns {void } */\n\n/**\n * The renderer interface.\n * @template T\n * @typedef {object } Renderer\n * @property {T } data User data object. Available as first param in callbacks.\n * @property {Renderer_Add_Token<T>} add_token When the tokens starts.\n * @property {Renderer_End_Token<T>} end_token When the token ends.\n * @property {Renderer_Add_Text <T>} add_text To append text to current token. Can be called multiple times or none.\n * @property {Renderer_Set_Attr <T>} set_attr Set additional attributes of current token eg. the link url.\n */\n\n/** @typedef {Renderer<any>} Any_Renderer */\n\n\n/**\n * @typedef {object} Default_Renderer_Data\n * @property {HTMLElement[]} nodes\n * @property {number } index\n *\n * @typedef {Renderer <Default_Renderer_Data>} Default_Renderer\n * @typedef {Renderer_Add_Token<Default_Renderer_Data>} Default_Renderer_Add_Token\n * @typedef {Renderer_End_Token<Default_Renderer_Data>} Default_Renderer_End_Token\n * @typedef {Renderer_Add_Text <Default_Renderer_Data>} Default_Renderer_Add_Text\n * @typedef {Renderer_Set_Attr <Default_Renderer_Data>} Default_Renderer_Set_Attr\n */\n\n/**\n * @param {HTMLElement } root\n * @returns {Default_Renderer} */\nexport function default_renderer(root) {\n return {\n add_token: default_add_token,\n end_token: default_end_token,\n add_text: default_add_text,\n set_attr: default_set_attr,\n data : {\n nodes: /**@type {*}*/([root,,,,,]),\n index: 0,\n },\n }\n}\n\n/** @type {Default_Renderer_Add_Token} */\nexport function default_add_token(data, type) {\n\n /**@type {Element}*/ let parent = data.nodes[data.index]\n\n /**@type {HTMLElement}*/ let slot\n\n switch (type) {\n case DOCUMENT: return // document is provided\n case BLOCKQUOTE: slot = document.createElement(\"blockquote\");break\n case PARAGRAPH: slot = document.createElement(\"p\") ;break\n case LINE_BREAK: slot = document.createElement(\"br\") ;break\n case RULE: slot = document.createElement(\"hr\") ;break\n case HEADING_1: slot = document.createElement(\"h1\") ;break\n case HEADING_2: slot = document.createElement(\"h2\") ;break\n case HEADING_3: slot = document.createElement(\"h3\") ;break\n case HEADING_4: slot = document.createElement(\"h4\") ;break\n case HEADING_5: slot = document.createElement(\"h5\") ;break\n case HEADING_6: slot = document.createElement(\"h6\") ;break\n case ITALIC_AST:\n case ITALIC_UND: slot = document.createElement(\"em\") ;break\n case STRONG_AST:\n case STRONG_UND: slot = document.createElement(\"strong\") ;break\n case STRIKE: slot = document.createElement(\"s\") ;break\n case CODE_INLINE: slot = document.createElement(\"code\") ;break\n case RAW_URL:\n case LINK: slot = document.createElement(\"a\") ;break\n case IMAGE: slot = document.createElement(\"img\") ;break\n case LIST_UNORDERED:slot = document.createElement(\"ul\") ;break\n case LIST_ORDERED: slot = document.createElement(\"ol\") ;break\n case LIST_ITEM: slot = document.createElement(\"li\") ;break\n case CHECKBOX:\n let checkbox = slot = document.createElement(\"input\")\n checkbox.type = \"checkbox\"\n checkbox.disabled = true\n break\n case CODE_BLOCK:\n case CODE_FENCE:\n parent = parent.appendChild(document.createElement(\"pre\"))\n slot = document.createElement(\"code\")\n break\n case TABLE:\n slot = document.createElement(\"table\")\n break\n case TABLE_ROW:\n switch (parent.children.length) {\n case 0:\n parent = parent.appendChild(document.createElement(\"thead\"))\n break\n case 1:\n parent = parent.appendChild(document.createElement(\"tbody\"))\n break\n default:\n parent = parent.children[1]\n }\n slot = document.createElement(\"tr\")\n break\n case TABLE_CELL:\n slot = document.createElement(parent.parentElement?.tagName === \"THEAD\" ? \"th\" : \"td\")\n break\n case EQUATION_BLOCK: slot = document.createElement(\"equation-block\"); break\n case EQUATION_INLINE: slot = document.createElement(\"equation-inline\"); break\n }\n\n data.nodes[++data.index] = parent.appendChild(slot)\n}\n\n/** @type {Default_Renderer_End_Token} */\nexport function default_end_token(data) {\n data.index -= 1\n}\n\n/** @type {Default_Renderer_Add_Text} */\nexport function default_add_text(data, text) {\n data.nodes[data.index].appendChild(document.createTextNode(text))\n}\n\n/** @type {Default_Renderer_Set_Attr} */\nexport function default_set_attr(data, type, value) {\n data.nodes[data.index].setAttribute(attr_to_html_attr(type), value)\n}\n\n\n/**\n * @typedef {undefined} Logger_Renderer_Data\n *\n * @typedef {Renderer <Logger_Renderer_Data>} Logger_Renderer\n * @typedef {Renderer_Add_Token<Logger_Renderer_Data>} Logger_Renderer_Add_Token\n * @typedef {Renderer_End_Token<Logger_Renderer_Data>} Logger_Renderer_End_Token\n * @typedef {Renderer_Add_Text <Logger_Renderer_Data>} Logger_Renderer_Add_Text\n * @typedef {Renderer_Set_Attr <Logger_Renderer_Data>} Logger_Renderer_Set_Attr\n */\n\n/** @returns {Logger_Renderer} */\nexport function logger_renderer() {\n return {\n data: undefined,\n add_token: logger_add_token,\n end_token: logger_end_token,\n add_text: logger_add_text,\n set_attr: logger_set_attr,\n }\n}\n\n/** @type {Logger_Renderer_Add_Token} */\nexport function logger_add_token(data, type) {\n console.log(\"add_token:\", token_to_string(type))\n}\n\n/** @type {Logger_Renderer_End_Token} */\nexport function logger_end_token(data) {\n console.log(\"end_token\")\n}\n\n/** @type {Logger_Renderer_Add_Text} */\nexport function logger_add_text(data, text) {\n console.log('add_text: \"%s\"', text)\n}\n\n/** @type {Logger_Renderer_Set_Attr} */\nexport function logger_set_attr(data, type, value) {\n console.log('set_attr: %s=\"%s\"', attr_to_html_attr(type), value)\n}\n", "/**\n * Streaming markdown renderer.\n *\n * Wraps `streaming-markdown` \u2014 a tiny (~3 KB) parser purpose-built for\n * incremental token rendering. Unlike `marked`, which re-parses the full\n * string on every delta (O(n\u00B2) for streaming), `streaming-markdown` feeds\n * only the new bytes into its state machine and emits DOM nodes directly.\n *\n * Strategy: each `MarkdownView` instance owns one parser bound to a DOM\n * container. A signal `effect` watches `textSig` and feeds the *delta*\n * (computed from the previous length) into the parser on each change.\n * If the signal text shrinks (e.g. stream reset), the container is\n * cleared and a fresh parser created.\n */\n\nimport { useEffect, useMemo, useRef } from \"preact/hooks\";\nimport { effect, signal, type Signal } from \"@preact/signals\";\nimport * as smd from \"streaming-markdown\";\nimport { BRAND } from \"@/core/brand\";\n\ninterface MarkdownViewProps {\n /** Live markdown source; updates on every stream delta. */\n textSig: Signal<string>;\n /** `true` once the text part is final \u2014 triggers `parser_end()`. */\n doneSig: Signal<boolean>;\n}\n\nexport function MarkdownView({ textSig, doneSig }: MarkdownViewProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n el.classList.add(`${BRAND.cssPrefix}-md`);\n\n let renderer = smd.default_renderer(el);\n let parser = smd.parser(renderer);\n let lastLen = 0;\n\n const dispose = effect(() => {\n const current = textSig.value;\n const isDone = doneSig.value;\n\n // Reset on shrink (message restart / new conversation).\n if (current.length < lastLen) {\n el.replaceChildren();\n renderer = smd.default_renderer(el);\n parser = smd.parser(renderer);\n lastLen = 0;\n }\n\n const delta = current.slice(lastLen);\n if (delta) smd.parser_write(parser, delta);\n lastLen = current.length;\n\n if (isDone) smd.parser_end(parser);\n });\n\n // Harden links emitted by streaming-markdown.\n //\n // 1. Force `target=\"_blank\"` so clicking a citation never navigates\n // the host page away from the chat.\n // 2. `rel=\"noopener noreferrer\"` strips the back-channel + referrer\n // leak. Required for any user-supplied URL.\n // 3. Click delegation as a defense-in-depth: if anything strips the\n // attributes (host-page rewrites, Preact reconcile reset), the\n // listener still opens the link in a new window safely.\n //\n // streaming-markdown emits anchors directly into `el` \u2014 we don't\n // own the renderer, so a MutationObserver is the cleanest hook\n // that catches every anchor as it streams in.\n const observer = new MutationObserver((records) => {\n for (const r of records) {\n for (const node of r.addedNodes) {\n if (!(node instanceof HTMLElement)) continue;\n const anchors = node.matches?.(\"a\") ? [node] : [...(node.querySelectorAll?.(\"a\") ?? [])];\n for (const a of anchors) hardenLink(a);\n }\n }\n });\n observer.observe(el, { childList: true, subtree: true });\n // Cover the (rare) case where anchors already exist before this\n // observer attaches.\n for (const a of el.querySelectorAll(\"a\")) hardenLink(a);\n\n const onClick = (event: Event) => {\n const target = (event.target as Element | null)?.closest?.(\"a\");\n if (!target || !(target instanceof HTMLAnchorElement)) return;\n // Internal in-page anchors (`#section`) stay on the page.\n const href = target.getAttribute(\"href\") ?? \"\";\n if (!href || href.startsWith(\"#\")) return;\n event.preventDefault();\n window.open(href, \"_blank\", \"noopener,noreferrer\");\n };\n el.addEventListener(\"click\", onClick);\n\n return () => {\n dispose();\n observer.disconnect();\n el.removeEventListener(\"click\", onClick);\n };\n }, [textSig, doneSig]);\n\n return <div ref={ref} />;\n}\n\n/**\n * Render a fixed (non-streaming) markdown string \u2014 help articles, news\n * bodies. Wraps {@link MarkdownView} with one-shot signals so the same\n * battle-tested renderer (and link hardening) is reused without pulling\n * in a second markdown dependency.\n */\nexport function StaticMarkdown({ text }: { text: string }) {\n const textSig = useMemo(() => signal(text), [text]);\n const doneSig = useMemo(() => signal(true), []);\n return <MarkdownView textSig={textSig} doneSig={doneSig} />;\n}\n\n/** Apply safe defaults to a link emitted by the markdown renderer. */\nfunction hardenLink(a: HTMLAnchorElement): void {\n const href = a.getAttribute(\"href\") ?? \"\";\n // Skip in-page anchors \u2014 `target=\"_blank\"` on `#foo` would open a\n // useless empty new tab.\n if (!href || href.startsWith(\"#\")) return;\n if (a.target !== \"_blank\") a.target = \"_blank\";\n // Use `rel.split` to preserve any tokens streaming-markdown may\n // already have set.\n const tokens = new Set((a.rel || \"\").split(/\\s+/).filter(Boolean));\n tokens.add(\"noopener\");\n tokens.add(\"noreferrer\");\n a.rel = [...tokens].join(\" \");\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\nimport type { ReactivePart } from \"@/stream/messages\";\n\ninterface Props {\n part: Extract<ReactivePart, { kind: \"tool\" }>;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n}\n\nexport function ToolChip({ part, strings }: Props) {\n const p = BRAND.cssPrefix;\n return (\n <span class={`${p}-tool-chip`} role=\"status\">\n <span>{strings.usedTool}:</span>\n <strong>{part.toolName}</strong>\n </span>\n );\n}\n", "import { useComputed } from \"@preact/signals\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResponseMode, Strings } from \"@/core/config\";\nimport type { ReactiveMessage, ReactivePart } from \"@/stream/messages\";\nimport { MarkdownView } from \"./markdown\";\nimport { tid, TID } from \"./test-ids\";\nimport { ToolChip } from \"./tool-chip\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n message: ReactiveMessage;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n responseMode: ResponseMode;\n /** Render reasoning (\"thoughts\") disclosures. Default `false` (hidden). */\n showReasoning: boolean;\n /** Render tool-call chips. Default `false` (hidden). */\n showToolCalls: boolean;\n}\n\nexport function MessageBubble({ message, strings, responseMode, showReasoning, showToolCalls }: Props) {\n // Read the parts array unconditionally so the bubble always subscribes to its\n // identity. The reducer bumps it on `finish`, which is how a *buffered* bubble\n // learns the reply is complete and flips from \"loading\" to the full message.\n const parts = useComputed(() => message.partsSig.value);\n const partList = parts.value;\n // Reactive to text *content*, not just part identity: the streaming indicator\n // must stay up until the first visible answer token lands \u2014 reasoning doesn't count.\n const hasAnswerText = useComputed(() =>\n message.partsSig.value.some((part) => part.kind === \"text\" && part.textSig.value.length > 0),\n );\n // A *visible* reasoning block (present AND not hidden by config) carries the\n // live \"Thinking\u2026\" state itself, so the typing dots stand down for it.\n const reasoningVisible = useComputed(\n () => showReasoning && message.partsSig.value.some((part) => part.kind === \"reasoning\"),\n );\n const streaming = message.role === \"assistant\" && message.status === \"streaming\";\n // Buffered: hold the whole reply behind a neutral LOADING indicator and paint\n // it only once the stream finishes. Loading \u2260 thinking \u2014 a thinking block is\n // message *content* (shown inside the rendered reply when `showReasoning`).\n const bufferedHold = responseMode === \"buffered\" && streaming;\n // \"Working\" = streaming with no answer text yet. Drives the reasoning\n // disclosure's expanded \"Thinking\u2026\" pulse (collapses once the answer starts).\n const working = streaming && !hasAnswerText.value;\n // Streaming: typing dots until the first answer token (the reasoning\n // disclosure, when shown, carries its own live pulse instead).\n const showTypingDots = !bufferedHold && working && !reasoningVisible.value;\n\n return (\n <div class={`${p}-bubble-row`} data-role={message.role} data-testid={tid(TID.messageBubble, message.id)}>\n <div class={`${p}-bubble`}>\n {bufferedHold ? (\n <LoadingSpinner label={strings.loading} />\n ) : (\n partList.map((part) => (\n <PartView\n key={partKey(part)}\n part={part}\n active={working}\n strings={strings}\n showReasoning={showReasoning}\n showToolCalls={showToolCalls}\n />\n ))\n )}\n {showTypingDots && <TypingDots />}\n {message.status === \"error\" && message.errorText ? (\n <div class={`${p}-error`} role=\"alert\">\n <span>{message.errorText}</span>\n </div>\n ) : null}\n </div>\n </div>\n );\n}\n\nfunction PartView({\n part,\n active,\n strings,\n showReasoning,\n showToolCalls,\n}: {\n part: ReactivePart;\n active: boolean;\n strings: Strings;\n showReasoning: boolean;\n showToolCalls: boolean;\n}) {\n switch (part.kind) {\n case \"text\":\n return <MarkdownView textSig={part.textSig} doneSig={part.doneSig} />;\n case \"reasoning\":\n return showReasoning ? <ReasoningView part={part} active={active} strings={strings} /> : null;\n case \"tool\":\n return showToolCalls ? <ToolChip part={part} strings={strings} /> : null;\n case \"file\":\n if (part.mediaType.startsWith(\"image/\")) {\n return <img src={part.url} alt=\"\" loading=\"lazy\" />;\n }\n return (\n <a href={part.url} target=\"_blank\" rel=\"noreferrer noopener\">\n {part.url}\n </a>\n );\n case \"source\":\n // Citations are intentionally not rendered in the visible\n // transcript \u2014 we keep them out of the user-facing thread (the\n // reducer still tracks them for de-dup / future surfacing).\n return null;\n }\n}\n\n/**\n * Reasoning (\"thinking\") renders as a collapsible disclosure, visually distinct\n * from the answer \u2014 never inlined into the message body. It's expanded with a\n * live \"Thinking\u2026\" pulse while the model works, then auto-collapses to a quiet\n * \"Thoughts\" toggle once the answer starts streaming. Reasoning is ephemeral:\n * the backend doesn't replay it, so it simply won't be present after a reload \u2014\n * but the answer body now reads identically live and on refresh.\n */\nfunction ReasoningView({\n part,\n active,\n strings,\n}: {\n part: Extract<ReactivePart, { kind: \"reasoning\" }>;\n active: boolean;\n strings: Strings;\n}) {\n return (\n <details class={`${p}-reasoning`} open={active} data-active={active ? \"true\" : undefined}>\n <summary class={`${p}-reasoning-summary`}>\n <span class={`${p}-reasoning-label`}>{active ? strings.thinking : strings.thoughts}</span>\n </summary>\n <div class={`${p}-reasoning-body`}>\n <MarkdownView textSig={part.textSig} doneSig={part.doneSig} />\n </div>\n </details>\n );\n}\n\nfunction partKey(part: ReactivePart): string {\n switch (part.kind) {\n case \"text\":\n case \"reasoning\":\n return `${part.kind}:${part.id}`;\n case \"tool\":\n return `tool:${part.toolCallId}`;\n case \"file\":\n return `file:${part.url}`;\n case \"source\":\n return `source:${part.sourceId}`;\n }\n}\n\nfunction TypingDots() {\n return (\n <span class={`${p}-typing`} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n );\n}\n\n/** Neutral \"loading\" spinner for buffered replies \u2014 distinct from the reasoning\n * (\"Thinking\u2026\") disclosure, which is message content, not a wait state. */\nfunction LoadingSpinner({ label }: { label: string }) {\n return (\n <span class={`${p}-loading`} role=\"status\">\n <span class={`${p}-loading-spinner`} aria-hidden=\"true\" />\n <span class={`${p}-loading-label`}>{label}</span>\n </span>\n );\n}\n", "import { useEffect, useLayoutEffect, useRef } from \"preact/hooks\";\nimport { type Signal, useComputed } from \"@preact/signals\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResponseMode, Strings } from \"@/core/config\";\nimport type { ReactiveMessage } from \"@/stream/messages\";\nimport { MessageBubble } from \"./message-bubble\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n messagesSig: Signal<ReactiveMessage[]>;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n responseMode: ResponseMode;\n /** Show reasoning (\"thoughts\") blocks. Default `false` (resolved upstream). */\n showReasoning: boolean;\n /** Show tool-call chips. Default `false` (resolved upstream). */\n showToolCalls: boolean;\n /**\n * True while the App is fetching message history from the backend\n * (resume path). Surfaces a small placeholder so reload of a long thread\n * doesn't flash empty before the network settles.\n */\n loading?: boolean;\n}\n\n/** Distance-from-bottom (px) below which \"stick to bottom\" still applies. */\nconst STICK_THRESHOLD = 120;\n\nexport function MessageList({ messagesSig, strings, responseMode, showReasoning, showToolCalls, loading }: Props) {\n const ref = useRef<HTMLDivElement>(null);\n const messages = useComputed(() => messagesSig.value);\n\n // Tracks whether the list has ever held a message. Flips from `false` to\n // `true` exactly once \u2014 when the resume-load (or a fresh send) seeds the\n // first message into an empty list. Used to distinguish \"initial\n // hydration\" (always pin-to-bottom) from \"new-message-while-scrolled-up\"\n // (only pin if near bottom \u2014 respect the user's read position).\n const hasHydratedRef = useRef(false);\n\n // ---- 1. Mount + hydration: pin to the bottom as content settles --------\n // Runs on mount AND when the first message arrives post-mount. Inline\n // mode's worst case: panel stays mounted across reloads, then `loadSession`\n // drops the full thread into `messagesSig` ~300-1000 ms later \u2014 well\n // after any fixed observer window would have torn itself down.\n //\n // The subtle problem: each <MessageBubble> contains a <MarkdownView> whose\n // `useEffect` writes the historical text into its ref AFTER this effect\n // runs. Parent `useLayoutEffect` + `requestAnimationFrame` both fire\n // BEFORE the children's useEffects flush \u2014 so reading `scrollHeight`\n // immediately gives us the height of *empty* bubble divs, not the real\n // post-markdown height. Worse: when that markdown expands, it grows the\n // list's *scrollHeight* but not its own clientHeight (list is flex-sized),\n // so a `ResizeObserver` on the list alone misses the expansion entirely.\n //\n // Fix: combine two observers during a 300 ms hydration window:\n // (a) `MutationObserver` \u2014 fires whenever a descendant's DOM changes\n // (markdown injecting text nodes, code blocks highlighting, etc.).\n // This is what actually catches the async markdown renders.\n // (b) `ResizeObserver` \u2014 fires when sibling rails below the list\n // (suggestions row appearing, composer growing) shrink the list's\n // clientHeight; without it, pinBottom would read a stale clientHeight\n // and leave the last line clipped behind the suggestions row.\n // Both trigger the same `pinBottom`, which is safe to call repeatedly.\n const firstMessageId = messages.value[0]?.id;\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n if (messages.value.length === 0) return; // wait for content\n hasHydratedRef.current = true;\n const pinBottom = () => {\n el.scrollTop = el.scrollHeight;\n };\n pinBottom();\n const resizeObs = new ResizeObserver(pinBottom);\n resizeObs.observe(el);\n const mutationObs = new MutationObserver(pinBottom);\n mutationObs.observe(el, { childList: true, subtree: true, characterData: true });\n const timerId = setTimeout(() => {\n resizeObs.disconnect();\n mutationObs.disconnect();\n }, 300);\n return () => {\n resizeObs.disconnect();\n mutationObs.disconnect();\n clearTimeout(timerId);\n };\n // Key on `firstMessageId`: re-run when we go from empty \u2192 populated, or\n // when the chat swaps (history pane \u2192 different chat loaded). Length\n // alone would miss the chat-swap case when both chats have the same\n // count. Intentionally not depending on `messages.value` (every token\n // would retrigger).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [firstMessageId]);\n\n // ---- 2. Length change: stick to the bottom if the user is near it -----\n // Skips the hydration moment (effect #1 handles that unconditionally).\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n if (!hasHydratedRef.current) return;\n const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;\n if (distanceFromBottom < STICK_THRESHOLD) el.scrollTop = el.scrollHeight;\n }, [messages.value.length]);\n\n // ---- 3. Streaming + finish ---------------------------------------------\n // While the last message is streaming, poll-pin the view to the bottom so\n // tokens arriving mid-paragraph don't drift the reader off-screen. When\n // the stream finishes we do ONE more rAF-deferred scroll \u2014 by that time\n // streaming-markdown has flushed its final block (closing list items,\n // code-block highlighting, etc.), and `scrollHeight` reflects the\n // post-paint layout. Without this rAF the previous polling tick can fire\n // *before* the final block paints, leaving the bubble's last line clipped.\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const last = messages.value.at(-1);\n if (!last) return;\n\n if (last.status !== \"streaming\") {\n const rafId = requestAnimationFrame(() => {\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n if (distance < STICK_THRESHOLD * 2) el.scrollTop = el.scrollHeight;\n });\n return () => cancelAnimationFrame(rafId);\n }\n\n const id = setInterval(() => {\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n if (distance < STICK_THRESHOLD * 2) el.scrollTop = el.scrollHeight;\n }, 250);\n return () => clearInterval(id);\n }, [messages.value]);\n\n return (\n <div ref={ref} class={`${p}-list`} role=\"log\" aria-live=\"polite\" aria-relevant=\"additions text\">\n {loading && messages.value.length === 0 ? (\n <div class={`${p}-list-loading`} role=\"status\">\n {strings.messagesLoading}\n </div>\n ) : null}\n {messages.value.map((m) => (\n <MessageBubble\n key={m.id}\n message={m}\n strings={strings}\n responseMode={responseMode}\n showReasoning={showReasoning}\n showToolCalls={showToolCalls}\n />\n ))}\n </div>\n );\n}\n", "/**\n * Shared state machine for an accessible popover menu.\n *\n * Three header components (`OverflowMenu`, `LanguageSwitcher`,\n * `ThemeSwitcher`) all render the same pattern: an icon trigger, a\n * dropdown of items, arrow-key nav, `Escape` to close, click-outside to\n * close, and focus return to the trigger on dismiss. Before this hook\n * each maintained its own copy of the state + effects \u2014 ~60 LOC\n * triplicated.\n *\n * The hook owns only the state machine and the keyboard / click handlers.\n * Callers render their own trigger + menu JSX around it, so shape-specific\n * concerns (icon, items heterogeneity, option labels) stay in the caller.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"preact/hooks\";\nimport type { RefObject } from \"preact\";\n\nexport interface UsePopoverMenuOptions {\n /** Total number of selectable items \u2014 drives arrow-key bounds. */\n itemCount: number;\n /**\n * Which item to focus when the menu opens. Runs once per open\n * transition \u2014 the typical use is \"focus the currently-active option\"\n * so arrow-nav starts from the user's current pick. Returning `-1`\n * falls back to item 0.\n */\n initialFocusIndex?: () => number;\n}\n\nexport interface PopoverMenu {\n open: boolean;\n focusIndex: number;\n triggerRef: RefObject<HTMLButtonElement>;\n menuRef: RefObject<HTMLDivElement>;\n /** Toggle open state. Typical binding: `onClick` on the trigger. */\n toggle: () => void;\n /** Explicitly close + return focus to the trigger. */\n close: () => void;\n /** Keyboard handler for the trigger button \u2014 opens on Down/Enter/Space. */\n onTriggerKey: (e: KeyboardEvent) => void;\n /** Keyboard handler for the menu \u2014 arrows, Home/End, Escape. */\n onMenuKey: (e: KeyboardEvent) => void;\n}\n\nexport function usePopoverMenu({ itemCount, initialFocusIndex }: UsePopoverMenuOptions): PopoverMenu {\n const [open, setOpen] = useState(false);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const [focusIndex, setFocusIndex] = useState(-1);\n\n const close = useCallback(() => {\n setOpen(false);\n setFocusIndex(-1);\n triggerRef.current?.focus();\n }, []);\n\n const toggle = useCallback(() => setOpen((v) => !v), []);\n\n // Click-outside closes. Uses composed path so it works inside shadow DOM.\n useEffect(() => {\n if (!open) return;\n const onDocClick = (e: Event) => {\n const menu = menuRef.current;\n const trigger = triggerRef.current;\n if (!menu || !trigger) return;\n const path = e.composedPath();\n if (path.includes(menu) || path.includes(trigger)) return;\n setOpen(false);\n setFocusIndex(-1);\n };\n document.addEventListener(\"click\", onDocClick, true);\n return () => document.removeEventListener(\"click\", onDocClick, true);\n }, [open]);\n\n // Pick the initial focus target on open. Clamped to valid bounds so a\n // bad `initialFocusIndex` can't leave focusIndex pointing past the last\n // item (the subsequent effect would silently fail to focus anything).\n useEffect(() => {\n if (!open) return;\n if (itemCount === 0) return;\n const desired = initialFocusIndex?.() ?? 0;\n const clamped = desired < 0 || desired >= itemCount ? 0 : desired;\n setFocusIndex(clamped);\n }, [open, itemCount, initialFocusIndex]);\n\n // Apply focus to the current item.\n useEffect(() => {\n if (!open || focusIndex < 0) return;\n const items = menuRef.current?.querySelectorAll<HTMLElement>(\"[role='menuitem']\");\n items?.[focusIndex]?.focus();\n }, [open, focusIndex]);\n\n const onTriggerKey = useCallback((e: KeyboardEvent) => {\n if (e.key === \"ArrowDown\" || e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setOpen(true);\n }\n }, []);\n\n const onMenuKey = useCallback(\n (e: KeyboardEvent) => {\n if (itemCount === 0) return;\n switch (e.key) {\n case \"Escape\":\n e.preventDefault();\n close();\n return;\n case \"ArrowDown\":\n e.preventDefault();\n setFocusIndex((i) => (i + 1) % itemCount);\n return;\n case \"ArrowUp\":\n e.preventDefault();\n setFocusIndex((i) => (i - 1 + itemCount) % itemCount);\n return;\n case \"Home\":\n e.preventDefault();\n setFocusIndex(0);\n return;\n case \"End\":\n e.preventDefault();\n setFocusIndex(itemCount - 1);\n }\n },\n [itemCount, close],\n );\n\n return { open, focusIndex, triggerRef, menuRef, toggle, close, onTriggerKey, onMenuKey };\n}\n", "/**\n * 3-dot overflow menu for the panel header.\n *\n * Minimal accessible dropdown \u2014 `role=\"menu\"` + `role=\"menuitem\"`, arrow\n * nav, `Home` / `End`, `Enter` / `Space` / click to select, `Escape` +\n * click-outside to close. State + keyboard wiring is shared with\n * `LanguageSwitcher` and `ThemeSwitcher` via {@link usePopoverMenu}; this\n * file only owns the item layout + checkmark-on-switch rendering.\n *\n * Items are either `\"button\"` (click \u2192 `onSelect`) or `\"switch\"`\n * (toggleable, shows a right-aligned checkmark when `on=true`). Either\n * way, selecting an item closes the menu immediately.\n */\n\nimport type { JSX } from \"preact\";\nimport { BRAND } from \"@/core/brand\";\nimport { CheckIcon, MoreIcon } from \"./icons\";\nimport { usePopoverMenu } from \"./use-popover-menu\";\n\nexport interface MenuItem {\n id: string;\n label: string;\n icon?: JSX.Element;\n onSelect: () => void;\n /** Switch renders a right-aligned checkmark when `on=true`. */\n type?: \"button\" | \"switch\";\n on?: boolean;\n disabled?: boolean;\n /**\n * BCP-47 tag applied to the rendered item \u2014 used by the expanded\n * language picker so each locale's name renders in its own script.\n */\n lang?: string;\n}\n\ninterface Props {\n items: MenuItem[];\n triggerLabel: string;\n}\n\nexport function OverflowMenu({ items, triggerLabel }: Props) {\n const p = BRAND.cssPrefix;\n const menu = usePopoverMenu({ itemCount: items.length });\n\n const handleSelect = (item: MenuItem) => {\n if (item.disabled) return;\n item.onSelect();\n // Every item closes the menu immediately on select \u2014 including\n // switch-style toggles (sound / language). Picking an action and\n // seeing the menu dismiss is the expected behaviour.\n menu.close();\n };\n\n return (\n <div class={`${p}-menu-wrap`}>\n <button\n ref={menu.triggerRef}\n type=\"button\"\n class={`${p}-icon-btn`}\n aria-label={triggerLabel}\n aria-haspopup=\"menu\"\n aria-expanded={menu.open}\n title={triggerLabel}\n onClick={menu.toggle}\n onKeyDown={menu.onTriggerKey}\n >\n <MoreIcon />\n </button>\n {menu.open ? (\n <div\n ref={menu.menuRef}\n class={`${p}-menu`}\n role=\"menu\"\n aria-label={triggerLabel}\n onKeyDown={menu.onMenuKey}\n // Prevents the panel's focus-trap from stealing focus on open.\n tabIndex={-1}\n >\n {items.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"menuitem\"\n class={`${p}-menu-item`}\n aria-pressed={item.type === \"switch\" ? item.on : undefined}\n disabled={item.disabled}\n lang={item.lang}\n onClick={() => handleSelect(item)}\n >\n {item.icon ? <span class={`${p}-menu-icon`}>{item.icon}</span> : null}\n <span class={`${p}-menu-label`}>{item.label}</span>\n {item.type === \"switch\" && item.on ? (\n <span class={`${p}-menu-check`} aria-hidden=\"true\">\n <CheckIcon />\n </span>\n ) : null}\n </button>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n", "/**\n * Group chat-history rows by friendly date buckets \u2014 Today / Yesterday /\n * Last 7 days / Older. Pure function, no i18n lookups; UI layer does the\n * label translation so this stays cheap to test.\n *\n * Input timestamps are ISO-8601 strings. Unparseable rows fall into \"Older\".\n */\n\nimport type { SessionSummary } from \"@/stream/protocol\";\n\nexport type HistoryBucket = \"today\" | \"yesterday\" | \"lastWeek\" | \"older\";\n\nexport interface HistoryGroup {\n bucket: HistoryBucket;\n sessions: SessionSummary[];\n}\n\n/** Visible group order, in the order the UI should render them. */\nexport const HISTORY_BUCKETS: readonly HistoryBucket[] = [\"today\", \"yesterday\", \"lastWeek\", \"older\"];\n\n/** Given a reference \"now\" and a row timestamp, return the bucket. */\nexport function bucketFor(nowMs: number, iso: string): HistoryBucket {\n const t = Date.parse(iso);\n if (!Number.isFinite(t)) return \"older\";\n const diffDays = Math.floor((startOfDay(nowMs) - startOfDay(t)) / (24 * 60 * 60 * 1000));\n if (diffDays <= 0) return \"today\";\n if (diffDays === 1) return \"yesterday\";\n if (diffDays < 7) return \"lastWeek\";\n return \"older\";\n}\n\n/**\n * Partition `sessions` into ordered buckets. Each bucket's `sessions` stays in\n * the server-provided order (assumed: newest first within the bucket).\n */\nexport function groupByBucket(nowMs: number, sessions: readonly SessionSummary[]): HistoryGroup[] {\n const buckets = new Map<HistoryBucket, SessionSummary[]>();\n for (const chat of sessions) {\n const b = bucketFor(nowMs, chat.lastMessageAt);\n const list = buckets.get(b);\n if (list) list.push(chat);\n else buckets.set(b, [chat]);\n }\n return HISTORY_BUCKETS.filter((b) => buckets.has(b)).map((b) => ({ bucket: b, sessions: buckets.get(b)! }));\n}\n\n/**\n * UTC-aligned day boundary. UTC keeps the function pure + deterministic\n * across timezones; the worst-case UX skew is ~1 day boundary drift for\n * users far from UTC, which is acceptable for history grouping.\n */\nfunction startOfDay(ms: number): number {\n const d = new Date(ms);\n d.setUTCHours(0, 0, 0, 0);\n return d.getTime();\n}\n", "/**\n * Persistent conversation list \u2014 rendered by the chat-history-pane\n * (slide-over view in widget/inline modes) and the page-mode sidebar.\n *\n * Pure data + presentation: fetches via transport.listSessions() on mount,\n * groups by date bucket, calls `onSelect` when a row is tapped. No\n * container styling \u2014 wrapper supplies the panel chrome.\n */\n\nimport { useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\nimport { logger } from \"@/core/logger\";\nimport type { StringKey } from \"@/i18n/strings\";\nimport { PlusIcon } from \"./icons\";\nimport { tid, TID } from \"./test-ids\";\n\nconst log = logger.scope(\"history\");\nimport type { SessionSummary } from \"@/stream/protocol\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport { type HistoryBucket, groupByBucket } from \"./history-groups\";\n\nexport interface ConversationListProps {\n transport: AgentTransport;\n strings: Strings;\n visitorId: string;\n onSelect: (chat: SessionSummary) => void;\n /** Optional: surfaces a \"+ New chat\" affordance above the list. */\n onNewChat?: () => void;\n}\n\nconst BUCKET_TO_STRING: Record<HistoryBucket, StringKey> = {\n today: \"dateToday\",\n yesterday: \"dateYesterday\",\n lastWeek: \"dateLastWeek\",\n older: \"dateOlder\",\n};\n\nexport function ConversationList({ transport, strings, visitorId, onSelect, onNewChat }: ConversationListProps) {\n const p = BRAND.cssPrefix;\n const [state, setState] = useState<\"loading\" | \"loaded\" | \"error\">(\"loading\");\n const [sessions, setChats] = useState<SessionSummary[]>([]);\n\n useEffect(() => {\n let cancelled = false;\n transport\n .listSessions({ visitorId })\n .then((res) => {\n if (cancelled) return;\n setChats(res.sessions);\n setState(\"loaded\");\n })\n .catch((err) => {\n if (cancelled) return;\n log.warn(\"listSessions failed\", { err });\n setState(\"error\");\n });\n return () => {\n cancelled = true;\n };\n }, [transport, visitorId]);\n\n // Pinned footer action \u2014 sits BELOW the scrolling list, not on top of it.\n const newChatButton = onNewChat ? (\n <div class={`${p}-history-footer`}>\n <button type=\"button\" class={`${p}-history-new`} onClick={onNewChat} data-testid={TID.sidebarNewChat}>\n <PlusIcon />\n {strings.historyNewChat}\n </button>\n </div>\n ) : null;\n\n if (state === \"loading\") {\n return (\n <>\n <div class={`${p}-history-empty`}>{strings.historyLoading}</div>\n {newChatButton}\n </>\n );\n }\n if (state === \"error\" || sessions.length === 0) {\n return (\n <>\n <div class={`${p}-history-empty`}>{strings.historyEmpty}</div>\n {newChatButton}\n </>\n );\n }\n\n const groups = groupByBucket(Date.now(), sessions);\n return (\n <>\n <div class={`${p}-history`} role=\"list\">\n {groups.map((group) => (\n <div key={group.bucket} class={`${p}-history-group`}>\n <div class={`${p}-history-heading`}>{strings[BUCKET_TO_STRING[group.bucket]]}</div>\n {group.sessions.map((chat) => (\n <button\n key={chat.sessionId}\n type=\"button\"\n role=\"listitem\"\n class={`${p}-history-item`}\n onClick={() => onSelect(chat)}\n data-closed={chat.canContinue ? undefined : \"true\"}\n data-testid={tid(TID.historyItem, chat.sessionId)}\n >\n <span class={`${p}-history-title`}>{chat.title}</span>\n {chat.preview ? <span class={`${p}-history-preview`}>{chat.preview}</span> : null}\n </button>\n ))}\n </div>\n ))}\n </div>\n {newChatButton}\n </>\n );\n}\n", "/**\n * Slide-over chat history view (widget + inline modes).\n *\n * Thin wrapper around {@link ConversationList} so the persistent\n * page-mode sidebar can share the same data + UI.\n */\n\nimport { ConversationList, type ConversationListProps } from \"./conversation-list\";\n\nexport function ChatHistoryPane(props: ConversationListProps) {\n return <ConversationList {...props} />;\n}\n", "/**\n * Language picker \u2014 globe icon button + dropdown of `availableLocales`.\n *\n * State + keyboard wiring is shared with `OverflowMenu` and\n * `ThemeSwitcher` via {@link usePopoverMenu}; this file only owns the\n * locale-specific rendering (display name, active checkmark, lang\n * attribute so each row renders in its own script).\n *\n * Display names come from {@link Intl.DisplayNames} expressed in the\n * **active** UI locale \u2014 so a French user sees \"fran\u00E7ais\", \"anglais\",\n * \"espagnol\" instead of \"French\", \"English\", \"Spanish\".\n */\n\nimport { useCallback } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport { localeDisplayName } from \"@/i18n/strings\";\nimport { CheckIcon, GlobeIcon } from \"./icons\";\nimport { usePopoverMenu } from \"./use-popover-menu\";\n\ninterface Props {\n available: readonly string[];\n /** Currently active locale \u2014 gets the checkmark in the menu. */\n active: string;\n onSelect: (locale: string) => void;\n triggerLabel: string;\n}\n\nexport function LanguageSwitcher({ available, active, onSelect, triggerLabel }: Props) {\n const p = BRAND.cssPrefix;\n // Open the menu with focus on the active locale \u2014 so arrow-nav starts\n // from the user's current pick rather than the top of the list.\n const initialFocusIndex = useCallback(() => available.indexOf(active), [available, active]);\n const menu = usePopoverMenu({ itemCount: available.length, initialFocusIndex });\n\n const handleSelect = (locale: string) => {\n onSelect(locale);\n menu.close();\n };\n\n return (\n <div class={`${p}-menu-wrap`}>\n <button\n ref={menu.triggerRef}\n type=\"button\"\n class={`${p}-icon-btn`}\n aria-label={triggerLabel}\n aria-haspopup=\"menu\"\n aria-expanded={menu.open}\n title={triggerLabel}\n onClick={menu.toggle}\n onKeyDown={menu.onTriggerKey}\n >\n <GlobeIcon />\n </button>\n {menu.open ? (\n <div\n ref={menu.menuRef}\n class={`${p}-menu`}\n role=\"menu\"\n aria-label={triggerLabel}\n onKeyDown={menu.onMenuKey}\n tabIndex={-1}\n >\n {available.map((locale) => {\n const isActive = locale === active;\n return (\n <button\n key={locale}\n type=\"button\"\n role=\"menuitem\"\n class={`${p}-menu-item`}\n lang={locale}\n aria-current={isActive ? \"true\" : undefined}\n onClick={() => handleSelect(locale)}\n >\n <span class={`${p}-menu-label`}>{localeDisplayName(locale, active)}</span>\n {isActive ? (\n <span class={`${p}-menu-check`} aria-hidden=\"true\">\n <CheckIcon />\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n ) : null}\n </div>\n );\n}\n", "/**\n * Theme picker \u2014 sun/moon/auto icon button + dropdown with three options.\n *\n * State + keyboard wiring is shared with `OverflowMenu` and\n * `LanguageSwitcher` via {@link usePopoverMenu}; this file only owns the\n * theme-specific rendering (trigger icon reflects the active pick,\n * checkmark on the selected option).\n *\n * The component is controlled \u2014 state for the actual theme lives in\n * `App` (which writes `data-theme` on the host + persists the choice).\n * This component just surfaces the dropdown UI.\n */\n\nimport { useCallback } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ThemePreference } from \"@/core/config\";\nimport { CheckIcon, MoonIcon, SunIcon, ThemeAutoIcon } from \"./icons\";\nimport { usePopoverMenu } from \"./use-popover-menu\";\n\ninterface Props {\n active: ThemePreference;\n onSelect: (mode: ThemePreference) => void;\n triggerLabel: string;\n /** Per-option labels (resolved from i18n strings by the caller). */\n optionLabels: Record<ThemePreference, string>;\n}\n\nconst OPTIONS: readonly ThemePreference[] = [\"auto\", \"light\", \"dark\"];\n\nfunction iconFor(mode: ThemePreference) {\n switch (mode) {\n case \"light\":\n return <SunIcon />;\n case \"dark\":\n return <MoonIcon />;\n case \"auto\":\n return <ThemeAutoIcon />;\n }\n}\n\nexport function ThemeSwitcher({ active, onSelect, triggerLabel, optionLabels }: Props) {\n const p = BRAND.cssPrefix;\n const initialFocusIndex = useCallback(() => OPTIONS.indexOf(active), [active]);\n const menu = usePopoverMenu({ itemCount: OPTIONS.length, initialFocusIndex });\n\n const handleSelect = (mode: ThemePreference) => {\n onSelect(mode);\n menu.close();\n };\n\n return (\n <div class={`${p}-menu-wrap`}>\n <button\n ref={menu.triggerRef}\n type=\"button\"\n class={`${p}-icon-btn`}\n aria-label={triggerLabel}\n aria-haspopup=\"menu\"\n aria-expanded={menu.open}\n title={triggerLabel}\n data-theme-active={active}\n onClick={menu.toggle}\n onKeyDown={menu.onTriggerKey}\n >\n {iconFor(active)}\n </button>\n {menu.open ? (\n <div\n ref={menu.menuRef}\n class={`${p}-menu`}\n role=\"menu\"\n aria-label={triggerLabel}\n onKeyDown={menu.onMenuKey}\n tabIndex={-1}\n >\n {OPTIONS.map((mode) => {\n const isActive = mode === active;\n return (\n <button\n key={mode}\n type=\"button\"\n role=\"menuitem\"\n class={`${p}-menu-item`}\n aria-current={isActive ? \"true\" : undefined}\n onClick={() => handleSelect(mode)}\n >\n <span class={`${p}-menu-icon`}>{iconFor(mode)}</span>\n <span class={`${p}-menu-label`}>{optionLabels[mode]}</span>\n {isActive ? (\n <span class={`${p}-menu-check`} aria-hidden=\"true\">\n <CheckIcon />\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n ) : null}\n </div>\n );\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { SessionSuggestion } from \"@/stream/protocol\";\nimport { tid, TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n suggestions: SessionSuggestion[];\n onPick: (s: SessionSuggestion) => void;\n}\n\n/**\n * Quick-reply chips rendered below the welcome message list. One-shot \u2014\n * dismissed as soon as the user sends anything (handled by the App).\n */\nexport function Suggestions({ suggestions, onPick }: Props) {\n if (suggestions.length === 0) return null;\n return (\n <div class={`${p}-suggestions`} role=\"group\" aria-label=\"Suggested replies\">\n {suggestions.map((s, i) => (\n <button\n type=\"button\"\n class={`${p}-suggestion`}\n onClick={() => onPick(s)}\n key={s.id}\n data-testid={tid(TID.suggestion, i)}\n >\n {s.label}\n </button>\n ))}\n </div>\n );\n}\n", "import { useCallback, useEffect, useRef } from \"preact/hooks\";\nimport { ResizeGrip } from \"./resize-grip\";\nimport { useDragMove } from \"./use-drag-move\";\nimport { type Signal, useComputed } from \"@preact/signals\";\nimport type { LocalAttachment } from \"@/core/attachments\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ActionName, ResolvedOptions, Strings } from \"@/core/config\";\nimport { canShowClose, visibleActions } from \"./action-visibility\";\nimport type { EventBus } from \"@/core/events\";\nimport type { FeedbackBus } from \"@/core/feedback\";\nimport type { WidgetSize } from \"@/core/persistence\";\nimport { trapFocus } from \"@/shadow/a11y\";\nimport type { ReactiveMessage } from \"@/stream/messages\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport type { SessionAgent, SessionSuggestion } from \"@/stream/protocol\";\nimport { AgentBadge } from \"./agent-badge\";\nimport { Composer, type ComposerAttachAPI } from \"./composer\";\nimport { DropZone } from \"./drop-zone\";\nimport { useFileDrop } from \"./use-file-drop\";\nimport type { JSX } from \"preact\";\nimport {\n BackIcon,\n CloseIcon,\n CollapseIcon,\n ExpandIcon,\n FullscreenExitIcon,\n FullscreenIcon,\n HistoryIcon,\n PopOutIcon,\n SpeakerIcon,\n SpeakerMutedIcon,\n TrashIcon,\n} from \"./icons\";\nimport { MessageList } from \"./message-list\";\nimport { OverflowMenu, type MenuItem } from \"./overflow-menu\";\nimport { ChatHistoryPane } from \"./chat-history-pane\";\nimport { LanguageSwitcher } from \"./language-switcher\";\nimport { ACTION_TID, TID } from \"./test-ids\";\nimport { ThemeSwitcher } from \"./theme-switcher\";\nimport { Suggestions } from \"./suggestions\";\n\nimport type { PanelSize } from \"./host-mode\";\n\nconst p = BRAND.cssPrefix;\n\nexport type PanelView = \"chat\" | \"history\";\n\ninterface Props {\n options: ResolvedOptions;\n transport: AgentTransport;\n feedback: FeedbackBus;\n bus: EventBus;\n panelSize: PanelSize;\n view: PanelView;\n /** `false` disables the composer (read-only chat). */\n canSend: boolean;\n isStreaming: boolean;\n /**\n * True while the App is fetching message history for a resumed chat.\n * Drives a placeholder in MessageList so reload of a long thread doesn't\n * flash empty before the network settles.\n */\n loadingMessages: boolean;\n messagesSig: Signal<ReactiveMessage[]>;\n agent: SessionAgent | null;\n suggestions: SessionSuggestion[];\n /** Cached drag-resized panel dimensions. `null` = use CSS defaults. */\n initialWidgetSize: WidgetSize | null;\n /** Anonymous browser id \u2014 needed for visitor-scoped history queries. */\n visitorId: string;\n /** Close the widget. Absent in non-closeable modes (standalone / page). */\n onClose?: () => void;\n /**\n * When set, the chat-view header renders a leading Back chevron (used\n * when the Panel is mounted as a drilled-in thread inside the messenger\n * home). Undefined \u2192 no Back button (floating / page / inline panels).\n */\n onBack?: () => void;\n /**\n * Start a fresh conversation. Bound to both the `clear` header action\n * and the read-only banner's \"Start new conversation\" CTA \u2014 same effect.\n */\n onClear: () => void;\n /**\n * Toggle the expanded / fullscreen tier. The buttons that call these\n * are gated per-mode by `visibleActions` (expand only shows in\n * floating; fullscreen everywhere except standalone), so passing\n * non-applicable modes is harmless \u2014 the user can't click what isn't\n * rendered.\n */\n onExpand: () => void;\n onFullscreen: () => void;\n onPopOut: () => void;\n onSoundToggle: () => void;\n onToggleHistory: () => void;\n onSelectHistoryChat: (sessionId: string) => void;\n /** Start a fresh conversation and return to the chat view (also used by the\n * history pane's empty-state CTA). */\n onNewChat: () => void;\n /** End-user picked a different language from the picker. */\n onLocaleChange: (locale: string) => void;\n /** End-user picked a different theme from the theme switcher. */\n onThemeChange: (mode: \"auto\" | \"light\" | \"dark\") => void;\n /** End-user dragged the resize grip \u2014 fired with resolved dimensions. */\n onWidgetSizeChange: (size: WidgetSize) => void;\n onSend: (text: string, attachments: LocalAttachment[]) => void;\n onStop: () => void;\n onSuggestion: (s: SessionSuggestion) => void;\n}\n\n/**\n * Action names that render via a plain icon button (or as an overflow\n * menu row). `language` and `theme` are excluded \u2014 the Header renders\n * them as dedicated popovers via `LanguageSwitcher` / `ThemeSwitcher`,\n * so they don't need a descriptor entry.\n */\ntype DescribedAction = Exclude<ActionName, \"language\" | \"theme\">;\ntype DescriptorTable = Record<DescribedAction, ActionDescriptor>;\n\ninterface ActionDescriptor {\n icon: JSX.Element;\n label: string;\n onSelect: () => void;\n /** Only used by the `sound` action \u2014 drives the switch UI. */\n switchOn?: boolean;\n /** Render a switch item in the overflow menu (vs. a plain button). */\n switchable?: boolean;\n}\n\nexport function Panel(props: Props) {\n const { options, onClose } = props;\n const s = options.strings;\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Trap focus inside panel + Escape to close.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const dispose = trapFocus(el);\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose?.();\n };\n el.addEventListener(\"keydown\", onKey);\n return () => {\n dispose();\n el.removeEventListener(\"keydown\", onKey);\n };\n }, [onClose]);\n\n // File-drop on the panel container \u2014 overlay visibility + drop\n // handler wired by `useFileDrop`. The dropped items are routed\n // into the Composer's `atts` state via the imperative API ref it\n // populates on mount (see `ComposerAttachAPI`). NOT auto-sent \u2014\n // a file drop means \"stage this file for my next message\", not\n // \"submit a message containing this file with no text\". The user\n // still has to type and press send.\n const composerAttachApiRef = useRef<ComposerAttachAPI | null>(null);\n const onDropItems = useCallback((items: DataTransferItemList) => {\n composerAttachApiRef.current?.attachFromDrop(items);\n }, []);\n const { visible: dragOver } = useFileDrop({ containerRef, onDrop: onDropItems });\n\n // Modal mode: let the user grab the header to reposition the centred dialog.\n useDragMove(containerRef.current, options.mode === \"modal\");\n\n return (\n <div\n ref={containerRef}\n class={`${p}-panel`}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-label={s.panelTitle}\n style={{ position: \"relative\" }}\n data-testid={TID.panel}\n >\n <PanelContent\n {...props}\n containerEl={containerRef.current}\n dragOver={dragOver}\n composerAttachApiRef={composerAttachApiRef}\n />\n {/* Global attribution \u2014 single-chat surface has no tab bar, so it pins\n to the bottom of the panel. */}\n <PoweredByBar poweredBy={props.options.poweredBy} />\n </div>\n );\n}\n\ninterface PanelContentProps extends Props {\n /** The panel card element (for the resize grip). */\n containerEl: HTMLElement | null;\n /** File-drag overlay visibility (owned by the card container). */\n dragOver: boolean;\n /** Imperative attach API the Composer populates on mount. */\n composerAttachApiRef: { current: ComposerAttachAPI | null };\n}\n\n/**\n * Panel inner content \u2014 header + (history | chat body) + resize grip.\n * Split out from {@link Panel}'s card wrapper so the messenger-home\n * surface can render a chat thread inside its OWN card (one dialog, one\n * focus trap, with the bottom tab bar reachable) instead of nesting a\n * second `.__P__-panel`.\n */\nexport function PanelContent({\n options,\n transport,\n feedback,\n bus,\n panelSize,\n view,\n canSend,\n isStreaming,\n loadingMessages,\n messagesSig,\n agent,\n suggestions,\n initialWidgetSize,\n visitorId,\n onClose,\n onBack,\n onClear,\n onExpand,\n onFullscreen,\n onPopOut,\n onSoundToggle,\n onToggleHistory,\n onSelectHistoryChat,\n onNewChat,\n onLocaleChange,\n onThemeChange,\n onWidgetSizeChange,\n onSend,\n onStop,\n onSuggestion,\n containerEl,\n dragOver,\n composerAttachApiRef,\n}: PanelContentProps) {\n const s = options.strings;\n return (\n <>\n {view === \"history\" ? (\n <HistoryHeader\n strings={s}\n onBack={onToggleHistory}\n onClose={onClose}\n showClose={canShowClose(options.mode, panelSize, options.actions)}\n />\n ) : (\n <Header\n agent={agent}\n onBack={onBack}\n backLabel={s.moduleBack}\n moreActionsLabel={s.moreActions}\n languageLabel={s.language}\n themeLabel={s.theme}\n themeOptionLabels={{ auto: s.themeAuto, light: s.themeLight, dark: s.themeDark }}\n activeThemeMode={options.themeMode}\n onThemeChange={onThemeChange}\n panelTitle={s.panelTitle}\n activeLocale={options.locale}\n availableLocales={options.availableLocales}\n onLocaleChange={onLocaleChange}\n main={visibleActions(options.actions.main, options.mode, panelSize, { popOutUrl: options.popOutUrl })}\n overflow={visibleActions(options.actions.overflow, options.mode, panelSize, { popOutUrl: options.popOutUrl })}\n descriptors={buildDescriptors({\n strings: s,\n panelSize,\n feedback,\n onClear,\n onExpand,\n onFullscreen,\n onPopOut,\n onSoundToggle,\n onToggleHistory,\n onClose,\n })}\n />\n )}\n {view === \"history\" ? (\n <ChatHistoryPane\n transport={transport}\n strings={s}\n visitorId={visitorId}\n onSelect={(chat) => onSelectHistoryChat(chat.sessionId)}\n onNewChat={onNewChat}\n />\n ) : (\n <>\n <DropZone visible={dragOver} strings={s} />\n <MessageList\n messagesSig={messagesSig}\n strings={s}\n responseMode={options.responseMode}\n showReasoning={options.showReasoning}\n showToolCalls={options.showToolCalls}\n loading={loadingMessages}\n />\n {canSend ? (\n <>\n <Suggestions suggestions={suggestions} onPick={onSuggestion} />\n <Composer\n options={options}\n transport={transport}\n feedback={feedback}\n bus={bus}\n isStreaming={isStreaming}\n onSend={onSend}\n onStop={onStop}\n attachApiRef={composerAttachApiRef}\n />\n </>\n ) : (\n <ReadOnlyBanner label={s.chatClosed} ctaLabel={s.startNewConversation} onNewChat={onClear} />\n )}\n <ComposerFooter disclaimer={options.composerDisclaimer} />\n </>\n )}\n {options.size.resize?.enabled ? (\n <ResizeGrip\n panelEl={containerEl}\n resize={options.size.resize}\n position={options.position}\n initialSize={initialWidgetSize}\n onSizeChange={onWidgetSizeChange}\n strings={s}\n />\n ) : null}\n </>\n );\n}\n\n// ---- chat history header -------------------------------------------------\n\nfunction HistoryHeader({\n strings,\n onBack,\n onClose,\n showClose,\n}: {\n strings: Strings;\n onBack: () => void;\n onClose?: () => void;\n showClose: boolean;\n}) {\n return (\n <header class={`${p}-header`}>\n <button\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={onBack}\n aria-label={strings.historyBack}\n title={strings.historyBack}\n >\n <BackIcon />\n </button>\n <h1>{strings.historyTitle}</h1>\n {showClose ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={onClose}\n aria-label={strings.close}\n title={strings.close}\n >\n <CloseIcon />\n </button>\n ) : null}\n </header>\n );\n}\n\n/**\n * Banner shown in place of the composer when the active chat is read-only\n * (server returned `canContinue: false`). Carries a CTA so the user can\n * start fresh without hunting for the trash icon \u2014 especially important\n * in mobile/floating layouts where the header overflow menu is hidden\n * behind a tap.\n */\nfunction ReadOnlyBanner({ label, ctaLabel, onNewChat }: { label: string; ctaLabel: string; onNewChat: () => void }) {\n return (\n <div class={`${p}-readonly-banner`} role=\"note\">\n <span class={`${p}-readonly-label`}>{label}</span>\n <button type=\"button\" class={`${p}-readonly-cta`} onClick={onNewChat}>\n {ctaLabel}\n </button>\n </div>\n );\n}\n\n// ---- composer footer ----------------------------------------------------\n\n/**\n * Chat-only disclaimer line (\"AI can make mistakes\") under the composer.\n * \"Powered by \u2026\" is NOT here \u2014 it's a global widget footer (see {@link PoweredByBar}).\n */\nfunction ComposerFooter({ disclaimer }: { disclaimer: string }) {\n if (!disclaimer) return null;\n return (\n <div class={`${p}-composer-footer`}>\n <div class={`${p}-disclaimer`}>{disclaimer}</div>\n </div>\n );\n}\n\n/**\n * Global \"Powered by \u2026\" strip \u2014 pinned at the bottom of the whole widget\n * surface (below the tab bar in the messenger; below the composer in single-chat),\n * so it shows on every tab, not just the chat. Renders nothing when unset.\n */\nexport function PoweredByBar({ poweredBy }: { poweredBy: ResolvedOptions[\"poweredBy\"] }) {\n if (!poweredBy) return null;\n return (\n <div class={`${p}-poweredby-bar`}>\n <PoweredBy logoUrl={poweredBy.logoUrl} text={poweredBy.text} href={poweredBy.href} />\n </div>\n );\n}\n\nfunction PoweredBy({ logoUrl, text, href }: { logoUrl?: string; text?: string; href?: string }) {\n const inner = (\n <>\n {logoUrl ? <img class={`${p}-poweredby-logo`} src={logoUrl} alt=\"\" loading=\"lazy\" /> : null}\n {text ? <span>{text}</span> : null}\n </>\n );\n if (href) {\n return (\n <a class={`${p}-poweredby`} href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n {inner}\n </a>\n );\n }\n return <span class={`${p}-poweredby`}>{inner}</span>;\n}\n\n// ---- header internals ---------------------------------------------------\n\ninterface HeaderProps {\n agent: SessionAgent | null;\n /** When set, render a leading Back chevron (drilled-in thread). */\n onBack?: () => void;\n /** Pre-resolved label for the Back chevron (`strings.moduleBack`). */\n backLabel: string;\n /** Pre-resolved label for the overflow trigger (`strings.moreActions`). */\n moreActionsLabel: string;\n /** Pre-resolved label for the language switcher trigger (`strings.language`). */\n languageLabel: string;\n /** Pre-resolved label for the theme switcher trigger (`strings.theme`). */\n themeLabel: string;\n /** Pre-resolved labels for each theme option \u2014 shown in the dropdown. */\n themeOptionLabels: Record<\"auto\" | \"light\" | \"dark\", string>;\n activeThemeMode: \"auto\" | \"light\" | \"dark\";\n onThemeChange: (mode: \"auto\" | \"light\" | \"dark\") => void;\n panelTitle: string;\n /** Currently active UI locale \u2014 drives the language switcher's checkmark. */\n activeLocale: string;\n /** Locales the user can pick from. Empty hides the language switcher. */\n availableLocales: readonly string[];\n onLocaleChange: (locale: string) => void;\n main: readonly ActionName[];\n overflow: readonly ActionName[];\n descriptors: DescriptorTable;\n}\n\nfunction Header({\n agent,\n onBack,\n backLabel,\n moreActionsLabel,\n languageLabel,\n themeLabel,\n themeOptionLabels,\n activeThemeMode,\n onThemeChange,\n panelTitle,\n activeLocale,\n availableLocales,\n onLocaleChange,\n main,\n overflow,\n descriptors,\n}: HeaderProps) {\n // Hide the language switcher if there's nothing meaningful to switch to\n // (zero or one available locale). This lets a deployment leave `language`\n // in `actions.main` permanently \u2014 it self-hides when not configured.\n const showLanguage = availableLocales.length > 1;\n\n // Build overflow items. `theme` is always filtered (rendered as its own\n // popover alongside the overflow trigger when placed in overflow, since\n // a dropdown-inside-a-dropdown would be poor UX). `language` is expanded\n // inline: each available locale becomes a menu item with a checkmark on\n // the active one \u2014 simpler than a submenu and still accessible.\n const overflowItems: MenuItem[] = [];\n for (const name of overflow) {\n if (name === \"theme\") continue; // rendered as a dedicated popover below\n if (name === \"language\") {\n if (!showLanguage) continue;\n for (const locale of availableLocales) {\n overflowItems.push({\n id: `language:${locale}`,\n label: languageLabel + ` \u2014 ${locale}`,\n lang: locale,\n onSelect: () => onLocaleChange(locale),\n type: \"switch\",\n on: locale === activeLocale,\n });\n }\n continue;\n }\n const d = descriptors[name];\n overflowItems.push({\n id: name,\n icon: d.icon,\n label: d.label,\n onSelect: d.onSelect,\n type: d.switchable ? \"switch\" : \"button\",\n on: d.switchOn,\n });\n }\n\n // Theme switcher placed in overflow still renders as its own popover\n // (right next to the 3-dot trigger) \u2014 a dropdown inside a dropdown is\n // too fiddly for a v1 picker, so we surface it as a sibling control\n // that accepts placement in either bucket without changing its UX.\n const themeInOverflow = overflow.includes(\"theme\");\n\n return (\n <header class={`${p}-header`} data-testid={TID.panelHeader}>\n {onBack ? (\n <button type=\"button\" class={`${p}-icon-btn`} onClick={onBack} aria-label={backLabel} title={backLabel}>\n <BackIcon />\n </button>\n ) : null}\n {agent ? <AgentBadge agent={agent} /> : <h1>{panelTitle}</h1>}\n {main.map((name) => {\n if (name === \"language\") {\n if (!showLanguage) return null;\n return (\n <LanguageSwitcher\n key=\"language\"\n available={availableLocales}\n active={activeLocale}\n onSelect={onLocaleChange}\n triggerLabel={languageLabel}\n />\n );\n }\n if (name === \"theme\") {\n return (\n <ThemeSwitcher\n key=\"theme\"\n active={activeThemeMode}\n onSelect={onThemeChange}\n triggerLabel={themeLabel}\n optionLabels={themeOptionLabels}\n />\n );\n }\n const d = descriptors[name];\n return (\n <button\n key={name}\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={d.onSelect}\n aria-label={d.label}\n aria-pressed={d.switchable ? d.switchOn : undefined}\n title={d.label}\n data-testid={ACTION_TID[name as keyof typeof ACTION_TID]}\n >\n {d.icon}\n </button>\n );\n })}\n {themeInOverflow ? (\n <ThemeSwitcher\n active={activeThemeMode}\n onSelect={onThemeChange}\n triggerLabel={themeLabel}\n optionLabels={themeOptionLabels}\n />\n ) : null}\n {overflowItems.length > 0 ? <OverflowMenu items={overflowItems} triggerLabel={moreActionsLabel} /> : null}\n </header>\n );\n}\n\ninterface BuildDescriptorsArgs {\n strings: Strings;\n panelSize: PanelSize;\n feedback: FeedbackBus;\n onClear: () => void;\n onExpand: () => void;\n onFullscreen: () => void;\n onPopOut: () => void;\n onSoundToggle: () => void;\n onToggleHistory: () => void;\n onClose?: () => void;\n}\n\n/**\n * Build the full table of `ActionName \u2192 ActionDescriptor`. This is a plain\n * function so the Header can pluck any action by name regardless of whether\n * it's placed in the main bar or the overflow menu. Keeping the logic in\n * one place means new actions only need one row added.\n */\nfunction buildDescriptors(args: BuildDescriptorsArgs): DescriptorTable {\n const {\n strings,\n panelSize,\n feedback,\n onClear,\n onExpand,\n onFullscreen,\n onPopOut,\n onSoundToggle,\n onToggleHistory,\n onClose,\n } = args;\n const expandedNow = panelSize === \"expanded\";\n const fullscreenNow = panelSize === \"fullscreen\";\n // Subscribe to the mute signal so the switch label/icon stay live.\n const muted = useComputed(() => feedback.mutedSig.value).value;\n return {\n clear: { icon: <TrashIcon />, label: strings.newConversation, onSelect: onClear },\n expand: {\n icon: expandedNow ? <CollapseIcon /> : <ExpandIcon />,\n label: expandedNow ? strings.collapse : strings.expand,\n onSelect: onExpand,\n switchable: true,\n switchOn: expandedNow,\n },\n fullscreen: {\n icon: fullscreenNow ? <FullscreenExitIcon /> : <FullscreenIcon />,\n label: fullscreenNow ? strings.exitFullscreen : strings.fullscreen,\n onSelect: onFullscreen,\n switchable: true,\n switchOn: fullscreenNow,\n },\n popOut: { icon: <PopOutIcon />, label: strings.popOut, onSelect: onPopOut },\n sound: {\n icon: muted ? <SpeakerMutedIcon /> : <SpeakerIcon />,\n label: muted ? strings.soundOff : strings.soundOn,\n onSelect: onSoundToggle,\n switchable: true,\n // The switch is \"on\" when sound is ENABLED (not muted).\n switchOn: !muted,\n },\n history: { icon: <HistoryIcon />, label: strings.history, onSelect: onToggleHistory },\n close: { icon: <CloseIcon />, label: strings.close, onSelect: onClose ?? (() => {}) },\n };\n}\n", "/**\n * Page-mode sidebar \u2014 logo, navigation, link cards, conversation list,\n * and a collapse toggle in the header. Reads `site` and `blocks` parsed\n * from the start-session response.\n *\n * Persistent \u2014 not a slide-over. Always rendered when mode === 'page'.\n * The collapsed state is owned by App (persisted via `Persistence`); the\n * sidebar just renders the `data-collapsed` attribute and the toggle button\n * \u2014 CSS in `styles/page.css` handles the visual collapse to a thin rail.\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\nimport type { BlocksConfig, Link, SiteConfig } from \"@/schema\";\nimport type { SessionSummary } from \"@/stream/protocol\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport { TID } from \"./test-ids\";\nimport { ConversationList } from \"./conversation-list\";\n\nexport interface SidebarProps {\n site: SiteConfig | undefined;\n blocks: BlocksConfig | undefined;\n transport: AgentTransport;\n strings: Strings;\n visitorId: string;\n /** Render the conversation list + new-chat (only when `messages` is enabled). */\n showConversations: boolean;\n onSelectChat: (chat: SessionSummary) => void;\n onNewChat: () => void;\n collapsed: boolean;\n onToggleCollapsed: () => void;\n}\n\nexport function Sidebar(props: SidebarProps) {\n const p = BRAND.cssPrefix;\n const { site, blocks, strings, collapsed } = props;\n const navigation = blocks?.navigation ?? [];\n const linkCards = blocks?.linkCards ?? [];\n const toggleLabel = collapsed ? strings.expandSidebar : strings.collapseSidebar;\n\n return (\n <aside class={`${p}-sidebar`} data-collapsed={collapsed ? \"true\" : \"false\"} data-testid={TID.sidebar}>\n <div class={`${p}-sidebar-header`}>\n <SidebarBrand site={site} />\n <button\n type=\"button\"\n class={`${p}-sidebar-toggle`}\n aria-label={toggleLabel}\n aria-expanded={collapsed ? \"false\" : \"true\"}\n title={toggleLabel}\n onClick={props.onToggleCollapsed}\n data-testid={TID.sidebarToggle}\n >\n <SidebarToggleIcon collapsed={collapsed} />\n </button>\n </div>\n {collapsed ? null : (\n <>\n {navigation.length > 0 ? (\n <nav class={`${p}-sidebar-section`} data-section=\"navigation\">\n <SidebarNav items={navigation} />\n </nav>\n ) : null}\n {linkCards.length > 0 ? (\n <div class={`${p}-sidebar-section`} data-section=\"link-cards\">\n <SidebarCards items={linkCards} />\n </div>\n ) : null}\n {props.showConversations ? (\n <div class={`${p}-sidebar-conversations`}>\n <ConversationList\n transport={props.transport}\n strings={props.strings}\n visitorId={props.visitorId}\n onSelect={props.onSelectChat}\n onNewChat={props.onNewChat}\n />\n </div>\n ) : null}\n </>\n )}\n </aside>\n );\n}\n\nfunction SidebarBrand({ site }: { site: SiteConfig | undefined }) {\n const p = BRAND.cssPrefix;\n if (site?.logo?.url) {\n const alt = site.logo.alt ?? site.title ?? \"Logo\";\n // <picture> swaps to `logoDark` automatically when the user's OS /\n // host preference is dark. Brands that ship only one logo just\n // render the fallback <img>.\n return (\n <picture>\n {site.logoDark?.url ? <source srcset={site.logoDark.url} media=\"(prefers-color-scheme: dark)\" /> : null}\n <img class={`${p}-sidebar-logo`} src={site.logo.url} alt={alt} />\n </picture>\n );\n }\n return <div class={`${p}-sidebar-title`}>{site?.title ?? BRAND.name}</div>;\n}\n\nfunction SidebarToggleIcon({ collapsed }: { collapsed: boolean }) {\n // Chevron-left when expanded (click \u2192 collapses leftward); chevron-right\n // when collapsed (click \u2192 expands rightward).\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n {collapsed ? <polyline points=\"9 6 15 12 9 18\" /> : <polyline points=\"15 6 9 12 15 18\" />}\n </svg>\n );\n}\n\nfunction SidebarNav({ items }: { items: Link[] }) {\n const p = BRAND.cssPrefix;\n return (\n <ul class={`${p}-sidebar-nav`}>\n {items.map((item) => (\n <li key={item.id ?? item.label}>\n <a\n class={`${p}-sidebar-nav-item`}\n href={item.href}\n target={item.href ? \"_blank\" : undefined}\n rel={item.href ? \"noreferrer\" : undefined}\n >\n {item.icon ? <span class={`${p}-sidebar-nav-icon`} data-icon={item.icon} /> : null}\n <span class={`${p}-sidebar-nav-label`}>{item.label}</span>\n </a>\n </li>\n ))}\n </ul>\n );\n}\n\nfunction SidebarCards({ items }: { items: Link[] }) {\n const p = BRAND.cssPrefix;\n return (\n <div class={`${p}-sidebar-cards`}>\n {items.map((item) => (\n <a\n key={item.id ?? item.label}\n class={`${p}-sidebar-card`}\n href={item.href}\n target={item.href ? \"_blank\" : undefined}\n rel={item.href ? \"noreferrer\" : undefined}\n >\n <div class={`${p}-sidebar-card-label`}>{item.label}</div>\n {item.description ? <div class={`${p}-sidebar-card-desc`}>{item.description}</div> : null}\n </a>\n ))}\n </div>\n );\n}\n", "/**\n * Page-mode root layout \u2014 full-viewport sidebar + chat split.\n *\n * Sidebar renders site/blocks config + persistent conversation list.\n * Chat area reuses the existing Panel component verbatim. Layout grid\n * lives in src/styles/page.css scoped to [data-mode=\"page\"].\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\nimport type { BlocksConfig, SiteConfig } from \"@/schema\";\nimport type { SessionSummary } from \"@/stream/protocol\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport type { ComponentChildren, ComponentProps } from \"preact\";\nimport { Panel } from \"./panel\";\nimport { Sidebar } from \"./sidebar\";\n\nconst p = BRAND.cssPrefix;\n\nexport type PanelProps = ComponentProps<typeof Panel>;\n\nexport interface PageShellProps {\n site: SiteConfig | undefined;\n blocks: BlocksConfig | undefined;\n transport: AgentTransport;\n strings: Strings;\n visitorId: string;\n /** Show the conversation list + new-chat in the sidebar (i.e. `messages` is enabled). */\n showConversations: boolean;\n onSelectChat: (chat: SessionSummary) => void;\n onNewChat: () => void;\n sidebarCollapsed: boolean;\n onToggleSidebarCollapsed: () => void;\n /** The main surface \u2014 the same messenger module surface used in every mode. */\n children: ComponentChildren;\n}\n\nexport function PageShell(props: PageShellProps) {\n // Semantic outer <main> wraps the page surface; sidebar is <aside>\n // (rendered inside the Sidebar component) and the chat column is a\n // <section>. The host page is expected to provide its own <header>\n // ABOVE this widget \u2014 page mode does not render its own page header.\n return (\n <main class={`${p}-page-shell`} data-sidebar-collapsed={props.sidebarCollapsed ? \"true\" : \"false\"}>\n <Sidebar\n site={props.site}\n blocks={props.blocks}\n transport={props.transport}\n strings={props.strings}\n visitorId={props.visitorId}\n showConversations={props.showConversations}\n onSelectChat={props.onSelectChat}\n onNewChat={props.onNewChat}\n collapsed={props.sidebarCollapsed}\n onToggleCollapsed={props.onToggleSidebarCollapsed}\n />\n <section class={`${p}-page-chat`} aria-label=\"Chat\">\n {props.children}\n </section>\n </main>\n );\n}\n", "/**\n * Messenger-home navigation state.\n *\n * `activeTab` (a module `id`) + a per-tab drill-in **stack** so Back returns\n * to the tab root and switching tabs preserves each tab's depth. Created\n * per App instance (not a module singleton) so two widgets on one page\n * keep independent navigation \u2014 mirrors how `messagesSig` is per-instance.\n *\n * Tabs are data-driven (the resolved `modules` list), so stacks are keyed by\n * the dynamic module ids passed in at construction.\n *\n * Transitions reassign the signal by value (never mutate in place) so\n * `useComputed` subscribers re-render reliably.\n */\n\nimport { signal, type Signal } from \"@preact/signals\";\n\n/** A tab key \u2014 a resolved module `id`. */\nexport type HomeTab = string;\n\n/** A drill-in screen pushed on top of a tab root. */\nexport type HomeScreen =\n /** A single content row rendered natively in the reader (markdown). */\n | { kind: \"content\"; id: string; title: string }\n /** External page in a sandboxed in-widget iframe (help article, status, \u2026). */\n | { kind: \"iframe\"; url: string; title: string };\n\nexport interface HomeNavState {\n activeTab: HomeTab;\n /** Per-tab drill-in stack, keyed by module id. Empty array = tab root (tab bar visible). */\n stacks: Record<HomeTab, HomeScreen[]>;\n}\n\n/** Navigation API handed to module screens. */\nexport interface HomeNav {\n sig: Signal<HomeNavState>;\n switchTab(tab: HomeTab): void;\n push(screen: HomeScreen): void;\n pop(): void;\n reset(tab: HomeTab): void;\n}\n\nconst emptyStacks = (tabs: HomeTab[]): Record<HomeTab, HomeScreen[]> => Object.fromEntries(tabs.map((t) => [t, []]));\n\n/**\n * Build a navigation controller over the given tab ids. `onSwitch` fires\n * whenever the active tab changes (used to persist the last tab) \u2014 not called\n * by `reset`, which is the programmatic open-time restore.\n */\nexport function createHomeNav(initialTab: HomeTab, tabs: HomeTab[], onSwitch?: (tab: HomeTab) => void): HomeNav {\n const sig = signal<HomeNavState>({ activeTab: initialTab, stacks: emptyStacks(tabs) });\n return {\n sig,\n switchTab(tab) {\n if (sig.value.activeTab === tab) return;\n sig.value = { ...sig.value, activeTab: tab };\n onSwitch?.(tab);\n },\n push(screen) {\n const s = sig.value;\n const stack = s.stacks[s.activeTab] ?? [];\n sig.value = { ...s, stacks: { ...s.stacks, [s.activeTab]: [...stack, screen] } };\n },\n pop() {\n const s = sig.value;\n const stack = s.stacks[s.activeTab] ?? [];\n if (stack.length === 0) return;\n sig.value = { ...s, stacks: { ...s.stacks, [s.activeTab]: stack.slice(0, -1) } };\n },\n reset(tab) {\n sig.value = { activeTab: tab, stacks: emptyStacks(tabs) };\n },\n };\n}\n\nexport const activeStack = (s: HomeNavState): HomeScreen[] => s.stacks[s.activeTab] ?? [];\nexport const topScreen = (s: HomeNavState): HomeScreen | undefined => activeStack(s).at(-1);\nexport const atTabRoot = (s: HomeNavState): boolean => activeStack(s).length === 0;\n", "/**\n * Chat layout \u2014 the conversation (the widget's core experience): resumes the\n * last conversation, shows the composer, and browses history via the panel\n * header. The actual surface is the existing `Panel`, which `MessengerHome`\n * renders for a `chat`-layout tab; this layout only supplies the bottom-nav icon.\n */\n\nimport { BubblesIcon } from \"../icons\";\nimport type { WidgetLayout } from \"./types\";\n\nexport const chatLayout: WidgetLayout = {\n Icon: BubblesIcon,\n // No `Root`: MessengerHome renders the existing chat panel for this tab.\n};\n", "/** Detail-screen header: Back chevron + centered title + optional close. */\n\nimport { BRAND } from \"@/core/brand\";\nimport { BackIcon, CloseIcon } from \"./icons\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n title: string;\n backLabel: string;\n onBack: () => void;\n onClose?: () => void;\n closeLabel?: string;\n testid?: string;\n}\n\n/** Tab-root header: centered title + optional close (no Back). */\nexport function TitleBar({ title, onClose, closeLabel }: { title: string; onClose?: () => void; closeLabel?: string }) {\n return (\n <header class={`${p}-back-header`} data-variant=\"title\">\n <span class={`${p}-back-spacer`} aria-hidden=\"true\" />\n <h1 class={`${p}-back-title`}>{title}</h1>\n {onClose ? (\n <button type=\"button\" class={`${p}-icon-btn`} onClick={onClose} aria-label={closeLabel} title={closeLabel}>\n <CloseIcon />\n </button>\n ) : (\n <span class={`${p}-back-spacer`} aria-hidden=\"true\" />\n )}\n </header>\n );\n}\n\nexport function BackHeader({ title, backLabel, onBack, onClose, closeLabel, testid }: Props) {\n return (\n <header class={`${p}-back-header`} data-testid={testid}>\n <button type=\"button\" class={`${p}-icon-btn`} onClick={onBack} aria-label={backLabel} title={backLabel}>\n <BackIcon />\n </button>\n <h1 class={`${p}-back-title`}>{title}</h1>\n {onClose ? (\n <button type=\"button\" class={`${p}-icon-btn`} onClick={onClose} aria-label={closeLabel} title={closeLabel}>\n <CloseIcon />\n </button>\n ) : (\n <span class={`${p}-back-spacer`} aria-hidden=\"true\" />\n )}\n </header>\n );\n}\n", "/**\n * \"Search for help\" bar. On Home it's a button that jumps to the Help tab\n * (`onActivate`); on Help it's a live input (`onInput` + `value`).\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport { SearchIcon } from \"./icons\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface ButtonProps {\n placeholder: string;\n onActivate: () => void;\n}\n\n/** Tappable placeholder bar (Home). */\nexport function HomeSearchButton({ placeholder, onActivate }: ButtonProps) {\n return (\n <button type=\"button\" class={`${p}-home-search`} onClick={onActivate} data-testid={TID.homeSearch}>\n <span class={`${p}-home-search-text`}>{placeholder}</span>\n <span class={`${p}-home-search-icon`} aria-hidden=\"true\">\n <SearchIcon />\n </span>\n </button>\n );\n}\n\ninterface InputProps {\n placeholder: string;\n value: string;\n onInput: (value: string) => void;\n}\n\n/** Live search input (Help tab). */\nexport function HelpSearchInput({ placeholder, value, onInput }: InputProps) {\n return (\n <div class={`${p}-home-search`} data-input=\"true\">\n <input\n type=\"search\"\n class={`${p}-home-search-input`}\n placeholder={placeholder}\n value={value}\n onInput={(e) => onInput((e.currentTarget as HTMLInputElement).value)}\n data-testid={TID.helpSearch}\n />\n <span class={`${p}-home-search-icon`} aria-hidden=\"true\">\n <SearchIcon />\n </span>\n </div>\n );\n}\n", "/** Generic tappable list row: title + subtitle, trailing chevron. */\n\nimport { BRAND } from \"@/core/brand\";\nimport { ChevronRightIcon } from \"./icons\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n title: string;\n subtitle?: string;\n onClick: () => void;\n testid?: string;\n}\n\nexport function ListRow({ title, subtitle, onClick, testid }: Props) {\n return (\n <button type=\"button\" class={`${p}-list-row`} onClick={onClick} data-testid={testid}>\n <span class={`${p}-list-row-body`}>\n <span class={`${p}-list-row-title`}>{title}</span>\n {subtitle ? <span class={`${p}-list-row-sub`}>{subtitle}</span> : null}\n </span>\n <span class={`${p}-list-row-chevron`} aria-hidden=\"true\">\n <ChevronRightIcon />\n </span>\n </button>\n );\n}\n", "/**\n * Shared loading / empty / error placeholder for module screens.\n *\n * Gives every state the right a11y semantics (`role=\"status\"` + `aria-live`\n * for loading/empty, `role=\"alert\"` for errors) instead of a bare muted\n * `<div>`, and offers an optional retry affordance for recoverable errors.\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/i18n/strings\";\n\nconst p = BRAND.cssPrefix;\n\nexport function ModuleState({\n tone = \"info\",\n message,\n onRetry,\n strings,\n}: {\n tone?: \"info\" | \"error\";\n message: string;\n /** When present (error tone), renders a \"Retry\" button. */\n onRetry?: () => void;\n strings: Strings;\n}) {\n return (\n <div class={`${p}-module-empty`} role={tone === \"error\" ? \"alert\" : \"status\"} aria-live=\"polite\">\n <span>{message}</span>\n {onRetry ? (\n <button type=\"button\" class={`${p}-module-retry`} onClick={onRetry}>\n {strings.errorRetry}\n </button>\n ) : null}\n </div>\n );\n}\n", "/**\n * Help layout \u2014 content rows scoped by the tab's `contentTags`, fetched once\n * (one large page) and grouped client-side into iOS-style category sections\n * with sticky headers. Search is a small client-side fuzzy filter over the\n * loaded rows (no server round-trip). An article with a `url` opens as an\n * in-widget sandboxed iframe; otherwise its `content` renders in the reader.\n */\n\nimport { useEffect, useMemo, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport { logger } from \"@/core/logger\";\nimport type { ContentItem } from \"@/stream/protocol\";\nimport { TitleBar } from \"../back-header\";\nimport { HelpIcon } from \"../icons\";\nimport { errorMessageFor } from \"../error-message\";\nimport { HelpSearchInput } from \"../home-search\";\nimport { ListRow } from \"../list-row\";\nimport { ModuleState } from \"../module-state\";\nimport { tid, TID } from \"../test-ids\";\nimport type { ModuleNav, ModuleScreenProps, WidgetLayout } from \"./types\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"help\");\n\n/** Open a content row: external `url` \u2192 in-widget iframe; inline `content` \u2192 reader. */\nconst openArticle = (nav: ModuleNav, a: ContentItem) =>\n a.url ? nav.push({ kind: \"iframe\", url: a.url, title: a.title }) : nav.push({ kind: \"content\", id: a.id, title: a.title }); // prettier-ignore\n\n/** Group rows into ordered `[category, items]` sections by each row's first category. */\nfunction groupByCategory(items: ContentItem[]): Array<[string, ContentItem[]]> {\n const groups = new Map<string, ContentItem[]>();\n for (const item of items) {\n const key = item.categories?.[0] ?? \"\";\n (groups.get(key) ?? groups.set(key, []).get(key)!).push(item);\n }\n return [...groups.entries()];\n}\n\n/**\n * Tiny fuzzy match: case-insensitive substring (strong) or in-order subsequence\n * (weak). Returns 0 for no match; higher is a better match. Deliberately simple\n * \u2014 no tokenizing or weighting beyond \"substring beats scattered letters\".\n */\nfunction fuzzyScore(query: string, text: string): number {\n const t = text.toLowerCase();\n const idx = t.indexOf(query);\n if (idx !== -1) return 100 - Math.min(idx, 99); // substring; earlier = better\n let from = 0;\n for (const ch of query) {\n from = t.indexOf(ch, from);\n if (from === -1) return 0;\n from += 1;\n }\n return 1; // scattered subsequence\n}\n\n/** Rank rows by best fuzzy score across title (weighted) + description. */\nfunction fuzzySearch(items: ContentItem[], query: string): ContentItem[] {\n const q = query.toLowerCase().trim();\n if (!q) return [];\n return items\n .map((item) => ({ item, score: Math.max(fuzzyScore(q, item.title) * 2, fuzzyScore(q, item.description ?? \"\")) }))\n .filter((r) => r.score > 0)\n .toSorted((a, b) => b.score - a.score)\n .map((r) => r.item);\n}\n\nfunction ArticleRow({ article, nav }: { article: ContentItem; nav: ModuleNav }) {\n return (\n <ListRow\n title={article.title}\n subtitle={article.description}\n onClick={() => openArticle(nav, article)}\n testid={tid(TID.helpArticle, article.id)}\n />\n );\n}\n\nfunction HelpRoot({ transport, strings, config, nav, panelProps }: ModuleScreenProps) {\n const tags = config.contentTags;\n const [state, setState] = useState<\"loading\" | \"loaded\" | \"error\">(\"loading\");\n const [errorMsg, setErrorMsg] = useState(strings.errorGeneric);\n const [items, setItems] = useState<ContentItem[]>([]);\n const [query, setQuery] = useState(\"\");\n const [reloadKey, setReloadKey] = useState(0);\n\n useEffect(() => {\n let cancelled = false;\n setState(\"loading\");\n transport\n // One large page, grouped into category sections client-side \u2014 help\n // catalogues are bounded (dozens of articles), so a single fetch beats\n // paging through a list the visitor scrolls as one. Search filters this\n // same set locally, so typing never hits the network.\n .listContent({ tags, sortBy: \"order\", limit: 100 })\n .then((res) => {\n if (cancelled) return;\n setItems(res.items);\n setState(\"loaded\");\n })\n .catch((err) => {\n if (cancelled) return;\n log.warn(\"listContent (help) failed\", { err });\n setErrorMsg(errorMessageFor(err, strings));\n setState(\"error\");\n });\n return () => {\n cancelled = true;\n };\n }, [transport, tags, reloadKey]);\n\n const results = useMemo(() => fuzzySearch(items, query), [items, query]);\n\n function renderBody() {\n if (query.trim().length > 0) {\n if (results.length === 0) return <ModuleState message={strings.helpSearchEmpty} strings={strings} />;\n return (\n <div class={`${p}-help-card`}>\n {results.map((a) => (\n <ArticleRow key={a.id} article={a} nav={nav} />\n ))}\n </div>\n );\n }\n if (state === \"loading\") return <ModuleState message={strings.helpLoading} strings={strings} />;\n if (state === \"error\") {\n return (\n <ModuleState tone=\"error\" message={errorMsg} onRetry={() => setReloadKey((k) => k + 1)} strings={strings} />\n );\n }\n if (items.length === 0) return <ModuleState message={strings.helpEmpty} strings={strings} />;\n return groupByCategory(items).map(([category, rows]) => (\n <section key={category} class={`${p}-help-group`}>\n {category ? <h2 class={`${p}-help-section-title`}>{category}</h2> : null}\n <div class={`${p}-help-card`}>\n {rows.map((a) => (\n <ArticleRow key={a.id} article={a} nav={nav} />\n ))}\n </div>\n </section>\n ));\n }\n\n return (\n <div class={`${p}-module`}>\n <TitleBar title={strings.helpTitle} onClose={panelProps.onClose} closeLabel={strings.close} />\n <div class={`${p}-module-pad`}>\n <HelpSearchInput placeholder={strings.helpSearchPlaceholder} value={query} onInput={setQuery} />\n </div>\n <div class={`${p}-help-list`}>{renderBody()}</div>\n </div>\n );\n}\n\nexport const helpLayout: WidgetLayout = {\n Icon: HelpIcon,\n Root: (props) => <HelpRoot {...props} />,\n};\n", "/** Rounded surface card used on the Home tab. Clickable when `onClick` is set. */\n\nimport type { ComponentChildren } from \"preact\";\nimport { BRAND } from \"@/core/brand\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n onClick?: () => void;\n children?: ComponentChildren;\n testid?: string;\n}\n\nexport function HomeCard({ onClick, children, testid }: Props) {\n if (onClick) {\n return (\n <button type=\"button\" class={`${p}-home-card`} data-interactive=\"true\" onClick={onClick} data-testid={testid}>\n {children}\n </button>\n );\n }\n return (\n <div class={`${p}-home-card`} data-testid={testid}>\n {children}\n </div>\n );\n}\n", "/**\n * Home layout \u2014 a fully config-driven dashboard (no block authoring). The tab's\n * `home` config flags assemble a fixed set of sections, in order:\n * greeting \u2192 search bar \u2192 recent conversation \u2192 status \u2192 content list.\n * The content list is built from the tab's `contentTags` (titled\n * `contentBlockTitle`, default \"Popular articles\").\n */\n\nimport { useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport { logger } from \"@/core/logger\";\nimport type { ContentItem } from \"@/stream/protocol\";\nimport type { SessionSummary } from \"@/stream/protocol\";\nimport { HomeCard } from \"../home-card\";\nimport { HomeSearchButton } from \"../home-search\";\nimport { BubblesIcon, CloseIcon, HomeIcon, StatusOkIcon } from \"../icons\";\nimport { initials } from \"../initials\";\nimport { ListRow } from \"../list-row\";\nimport { moduleLabel } from \"@/i18n/strings\";\nimport { tid, TID } from \"../test-ids\";\nimport type { ModuleNav, ModuleScreenProps, WidgetLayout } from \"./types\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"home\");\n\nfunction resolveGreeting(props: ModuleScreenProps): { title: string; subtitle?: string } {\n const name = props.options.userContext?.name;\n const fromConfig = props.config.greetingText;\n if (fromConfig) {\n const title = name ? fromConfig.replace(\"{name}\", name) : fromConfig.replace(/\\s*\\{name\\}\\s*/, \" \").trim();\n return { title, subtitle: props.strings.homeGreetingLead };\n }\n return name\n ? { title: props.strings.homeGreetingNamed.replace(\"{name}\", name), subtitle: props.strings.homeGreetingLead }\n : { title: props.strings.homeGreeting };\n}\n\n/** Open a content row: external `url` \u2192 in-widget iframe; otherwise the native reader. */\nconst openContent = (nav: ModuleNav, item: ContentItem) =>\n item.url ? nav.push({ kind: \"iframe\", url: item.url, title: item.title }) : nav.push({ kind: \"content\", id: item.id, title: item.title }); // prettier-ignore\n\nfunction HomeRoot(props: ModuleScreenProps) {\n const { transport, strings, config, nav, panelProps } = props;\n const [recent, setRecent] = useState<SessionSummary | null>(null);\n const [content, setContent] = useState<ContentItem[]>([]);\n const tagsKey = config.contentTags?.join(\",\");\n\n // Recent conversation \u2014 the visitor's latest session (fetched, not authored).\n useEffect(() => {\n if (!config.showRecentMessages) return;\n let cancelled = false;\n transport\n .listSessions({ limit: 1 })\n .then((res) => !cancelled && setRecent(res.sessions[0] ?? null))\n .catch((err) => !cancelled && log.warn(\"listSessions (home) failed\", { err }));\n return () => {\n cancelled = true;\n };\n }, [transport, config.showRecentMessages]);\n\n // Content list (\"Popular articles\") \u2014 rows matching the tab's contentTags.\n useEffect(() => {\n if (!config.contentTags?.length) return;\n let cancelled = false;\n transport\n .listContent({ tags: config.contentTags, limit: 6 })\n .then((res) => !cancelled && setContent(res.items))\n .catch((err) => !cancelled && log.warn(\"listContent (home) failed\", { err }));\n return () => {\n cancelled = true;\n };\n }, [transport, tagsKey]);\n\n const greeting = resolveGreeting(props);\n const avatars = config.userAvatars ?? [];\n const status = config.status;\n const contentTitle = config.contentBlockTitle\n ? moduleLabel(strings, config.contentBlockTitle)\n : strings.homeContentTitle;\n\n return (\n <div class={`${p}-module ${p}-home`} data-testid={TID.homeView}>\n <div class={`${p}-home-scroll`}>\n <div class={`${p}-home-hero`}>\n <div class={`${p}-home-hero-top`}>\n {config.brandName ? (\n <span class={`${p}-home-brand`} data-testid={TID.homeBrand}>\n {config.brandName}\n </span>\n ) : (\n <span class={`${p}-home-brand-spacer`} aria-hidden=\"true\" />\n )}\n <div class={`${p}-home-hero-actions`}>\n {avatars.length > 0 ? (\n <div class={`${p}-home-avatars`}>\n {avatars.map((a) => (\n <span key={a.name} class={`${p}-home-avatar`} title={a.role ? `${a.name} \u00B7 ${a.role}` : a.name}>\n {a.avatar ? <img src={a.avatar} alt=\"\" loading=\"lazy\" /> : <span>{initials(a.name)}</span>}\n </span>\n ))}\n </div>\n ) : null}\n {panelProps.onClose ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn ${p}-home-close`}\n onClick={panelProps.onClose}\n aria-label={strings.close}\n title={strings.close}\n >\n <CloseIcon />\n </button>\n ) : null}\n </div>\n </div>\n {config.showGreeting !== false ? (\n <>\n <h1 class={`${p}-home-greeting`} data-testid={TID.homeGreeting}>\n {greeting.title}\n </h1>\n {greeting.subtitle ? <p class={`${p}-home-lead`}>{greeting.subtitle}</p> : null}\n </>\n ) : null}\n </div>\n\n <div class={`${p}-home-cards`}>\n {config.showSearchBar !== false ? (\n <HomeSearchButton\n placeholder={strings.homeSearchPlaceholder}\n onActivate={() => nav.switchToLayout(\"help\")}\n />\n ) : null}\n\n {recent ? (\n <HomeCard onClick={() => nav.selectSession(recent.sessionId)} testid={TID.homeRecent}>\n <div class={`${p}-home-recent-row`} data-unread={(recent.unreadCount ?? 0) > 0 ? \"true\" : undefined}>\n <span class={`${p}-home-recent-avatar`} aria-hidden=\"true\">\n <BubblesIcon />\n </span>\n <span class={`${p}-home-recent-body`}>\n <span class={`${p}-home-recent-title`}>{recent.title}</span>\n {recent.preview ? <span class={`${p}-home-recent-preview`}>{recent.preview}</span> : null}\n </span>\n {(recent.unreadCount ?? 0) > 0 ? <span class={`${p}-home-recent-dot`} aria-label=\"Unread\" /> : null}\n </div>\n </HomeCard>\n ) : null}\n\n {status ? (\n <HomeCard\n onClick={\n status.url ? () => nav.push({ kind: \"iframe\", url: status.url!, title: status.text }) : undefined\n }\n >\n <div class={`${p}-home-status`} data-level={status.level ?? \"operational\"}>\n <span class={`${p}-home-status-icon`} aria-hidden=\"true\">\n <StatusOkIcon />\n </span>\n <span class={`${p}-home-status-text`}>{status.text}</span>\n </div>\n </HomeCard>\n ) : null}\n\n {content.length > 0 ? (\n <section class={`${p}-home-content`}>\n <div class={`${p}-home-content-title`}>{contentTitle}</div>\n <div class={`${p}-home-content-list`}>\n {content.map((item) => (\n <ListRow\n key={item.id}\n title={item.title}\n subtitle={item.description}\n onClick={() => openContent(nav, item)}\n testid={tid(TID.homeContentRow, item.id)}\n />\n ))}\n </div>\n </section>\n ) : null}\n </div>\n </div>\n </div>\n );\n}\n\nexport const homeLayout: WidgetLayout = {\n Icon: HomeIcon,\n Root: (props) => <HomeRoot {...props} />,\n};\n", "/** News module \u2014 announcement cards (`type=news`) \u2192 the content reader. */\n\nimport { useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport { logger } from \"@/core/logger\";\nimport type { ContentItem } from \"@/stream/protocol\";\nimport { TitleBar } from \"../back-header\";\nimport { errorMessageFor } from \"../error-message\";\nimport { NewsIcon } from \"../icons\";\nimport { ModuleState } from \"../module-state\";\nimport { tid, TID } from \"../test-ids\";\nimport type { ModuleScreenProps, WidgetLayout } from \"./types\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"news\");\n\nfunction NewsRoot({ transport, strings, config, nav, panelProps }: ModuleScreenProps) {\n const tags = config.contentTags;\n const [state, setState] = useState<\"loading\" | \"loaded\" | \"error\">(\"loading\");\n const [errorMsg, setErrorMsg] = useState(strings.errorGeneric);\n const [items, setItems] = useState<ContentItem[]>([]);\n const [reloadKey, setReloadKey] = useState(0);\n\n useEffect(() => {\n let cancelled = false;\n setState(\"loading\");\n transport\n .listContent({ tags, sortBy: \"publishedAt\", sortOrder: \"desc\" })\n .then((res) => {\n if (cancelled) return;\n setItems(res.items);\n setState(\"loaded\");\n })\n .catch((err) => {\n if (cancelled) return;\n log.warn(\"listContent (news) failed\", { err });\n setErrorMsg(errorMessageFor(err, strings));\n setState(\"error\");\n });\n return () => {\n cancelled = true;\n };\n }, [transport, tags, reloadKey]);\n\n function renderBody() {\n if (state === \"loading\") return <ModuleState message={strings.newsLoading} strings={strings} />;\n if (state === \"error\") {\n return (\n <ModuleState tone=\"error\" message={errorMsg} onRetry={() => setReloadKey((k) => k + 1)} strings={strings} />\n );\n }\n if (items.length === 0) return <ModuleState message={strings.newsEmpty} strings={strings} />;\n return (\n <div class={`${p}-news-list`}>\n {items.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n class={`${p}-news-card`}\n onClick={() => nav.push({ kind: \"content\", id: item.id, title: item.title })}\n data-testid={tid(TID.newsItem, item.id)}\n >\n {item.image ? <img class={`${p}-news-hero`} src={item.image} alt=\"\" loading=\"lazy\" /> : null}\n <span class={`${p}-news-body`}>\n {item.tags && item.tags.length > 0 ? (\n <span class={`${p}-news-tags`}>\n {item.tags.map((t) => (\n <span key={t} class={`${p}-news-tag`}>\n {t}\n </span>\n ))}\n </span>\n ) : null}\n <span class={`${p}-news-title`}>{item.title}</span>\n {item.description ? <span class={`${p}-news-summary`}>{item.description}</span> : null}\n </span>\n </button>\n ))}\n </div>\n );\n }\n\n return (\n <div class={`${p}-module`}>\n <TitleBar title={strings.newsTitle} onClose={panelProps.onClose} closeLabel={strings.close} />\n <div class={`${p}-module-scroll`}>{renderBody()}</div>\n </div>\n );\n}\n\nexport const newsLayout: WidgetLayout = {\n Icon: NewsIcon,\n Root: (props) => <NewsRoot {...props} />,\n};\n", "/** Layout registry \u2014 `ModuleLayout \u2192 WidgetLayout`. Add a layout here. */\n\nimport type { ModuleLayout } from \"@/core/config\";\nimport { chatLayout } from \"./messages\";\nimport { helpLayout } from \"./help\";\nimport { homeLayout } from \"./home\";\nimport { newsLayout } from \"./news\";\nimport type { WidgetLayout } from \"./types\";\n\nexport const LAYOUTS: Record<ModuleLayout, WidgetLayout> = {\n chat: chatLayout,\n home: homeLayout,\n help: helpLayout,\n news: newsLayout,\n};\n", "/** Bottom navigation for the messenger home. One button per tab (resolved module). */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { ResolvedModule } from \"@/core/config\";\nimport { moduleLabel, type Strings } from \"@/i18n/strings\";\nimport { LAYOUTS } from \"./modules/registry\";\nimport type { HomeTab } from \"./home-nav\";\nimport { tid, TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n modules: ResolvedModule[];\n activeTab: HomeTab;\n strings: Strings;\n /** Total unread across conversations \u2014 shows a badge on the chat tab. */\n unreadCount: number;\n onSelect: (tab: HomeTab) => void;\n}\n\nexport function HomeTabBar({ modules, activeTab, strings, unreadCount, onSelect }: Props) {\n return (\n <nav class={`${p}-tabbar`} role=\"tablist\" aria-label={strings.panelTitle}>\n {modules.map((m) => {\n const Icon = LAYOUTS[m.layout].Icon;\n const selected = m.id === activeTab;\n // Badge the chat tab when there are unread replies. Cap the display at\n // 9+ so a long backlog never blows out the tab width.\n const badge = m.layout === \"chat\" && unreadCount > 0 ? (unreadCount > 9 ? \"9+\" : String(unreadCount)) : null;\n return (\n <button\n key={m.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n class={`${p}-tab`}\n onClick={() => onSelect(m.id)}\n data-testid={tid(TID.tab, m.id)}\n >\n <span class={`${p}-tab-icon`} aria-hidden=\"true\">\n <Icon />\n {badge ? (\n <span class={`${p}-tab-badge`} data-testid={TID.tabBadge}>\n {badge}\n </span>\n ) : null}\n </span>\n <span class={`${p}-tab-label`}>{moduleLabel(strings, m.label)}</span>\n </button>\n );\n })}\n </nav>\n );\n}\n", "/**\n * In-widget sandboxed iframe viewer \u2014 help articles, status pages, and any\n * `{kind:\"iframe\"}` drill-in. The host expands the panel for reading (see\n * MessengerHome) and restores the prior size on Back.\n *\n * Sandbox intentionally omits `allow-same-origin` so a cross-origin page\n * can't script the embedder. `referrerpolicy` strips the referrer.\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/i18n/strings\";\nimport { BackHeader } from \"./back-header\";\n\nconst p = BRAND.cssPrefix;\nconst SANDBOX = \"allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads\";\n\ninterface Props {\n url: string;\n title: string;\n strings: Strings;\n onBack: () => void;\n onClose?: () => void;\n}\n\nexport function IframeView({ url, title, strings, onBack, onClose }: Props) {\n return (\n <div class={`${p}-module`}>\n <BackHeader\n title={title || strings.moduleBack}\n backLabel={strings.moduleBack}\n onBack={onBack}\n onClose={onClose}\n closeLabel={strings.close}\n />\n <iframe\n class={`${p}-content-frame`}\n src={url}\n title={title || \"content\"}\n sandbox={SANDBOX}\n referrerpolicy=\"no-referrer\"\n loading=\"lazy\"\n allow=\"fullscreen\"\n />\n </div>\n );\n}\n", "/**\n * Content reader \u2014 fetches one {@link ContentItem} by id and renders it\n * natively: hero image, title, then its markdown `content`. Backs every\n * `{kind:\"content\"}` screen (news items, inline articles, docs).\n */\n\nimport { useCallback, useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/i18n/strings\";\nimport { logger } from \"@/core/logger\";\nimport type { ContentItem } from \"@/stream/protocol\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport { BackHeader } from \"./back-header\";\nimport { StaticMarkdown } from \"./markdown\";\nimport { ModuleState } from \"./module-state\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"content\");\n\ninterface Props {\n id: string;\n title: string;\n transport: AgentTransport;\n strings: Strings;\n onBack: () => void;\n onClose?: () => void;\n}\n\nexport function ContentView({ id, title, transport, strings, onBack, onClose }: Props) {\n const [item, setItem] = useState<ContentItem | null>(null);\n const [failed, setFailed] = useState(false);\n const [reloadKey, setReloadKey] = useState(0);\n const retry = useCallback(() => {\n setFailed(false);\n setItem(null);\n setReloadKey((k) => k + 1);\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n transport\n .listContent({ ids: [id], limit: 1 })\n .then((res) => {\n if (cancelled) return;\n const found = res.items[0];\n if (found) setItem(found);\n else setFailed(true);\n })\n .catch((err) => {\n if (cancelled) return;\n log.warn(\"listContent (reader) failed\", { err });\n setFailed(true);\n });\n return () => {\n cancelled = true;\n };\n }, [transport, id, reloadKey]);\n\n function renderBody() {\n if (failed) return <ModuleState tone=\"error\" message={strings.errorGeneric} onRetry={retry} strings={strings} />;\n if (item === null) return <ModuleState message={strings.contentLoading} strings={strings} />;\n return (\n <article class={`${p}-content`} data-testid={TID.contentView}>\n {item.image ? <img class={`${p}-content-hero`} src={item.image} alt=\"\" loading=\"lazy\" /> : null}\n {item.description ? <p class={`${p}-content-subtitle`}>{item.description}</p> : null}\n {item.tags && item.tags.length > 0 ? (\n <span class={`${p}-news-tags`}>\n {item.tags.map((t) => (\n <span key={t} class={`${p}-news-tag`}>\n {t}\n </span>\n ))}\n </span>\n ) : null}\n <StaticMarkdown text={item.content ?? \"\"} />\n </article>\n );\n }\n\n return (\n <div class={`${p}-module`}>\n <BackHeader\n title={item?.title || title || strings.moduleBack}\n backLabel={strings.moduleBack}\n onBack={onBack}\n onClose={onClose}\n closeLabel={strings.close}\n testid={TID.backHeader}\n />\n <div class={`${p}-module-scroll`} data-testid={TID.contentScroll}>\n {renderBody()}\n </div>\n </div>\n );\n}\n", "/**\n * Messenger-home surface \u2014 the tabbed container shown when \u22652 modules are\n * enabled. Owns ONE panel card (dialog + focus trap + file-drop) so the\n * bottom tab bar stays keyboard-reachable. Routes the active tab root and\n * cross-cutting drill-ins (thread / iframe / content) itself; module-owned\n * drill-ins (help-collection / news-item) go through the module's `Detail`.\n * Opening an iframe/content screen grows the panel to `expanded` and Back\n * restores the prior size.\n */\n\nimport { useCallback, useEffect, useRef } from \"preact/hooks\";\nimport { useComputed } from \"@preact/signals\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResolvedModule } from \"@/core/config\";\nimport { trapFocus } from \"@/shadow/a11y\";\nimport type { ComposerAttachAPI } from \"./composer\";\nimport { atTabRoot, type HomeNav, topScreen } from \"./home-nav\";\nimport type { PanelSize } from \"./host-mode\";\nimport { HomeTabBar } from \"./home-tab-bar\";\nimport { IframeView } from \"./iframe-view\";\nimport { ContentView } from \"./content-view\";\nimport { LAYOUTS } from \"./modules/registry\";\nimport type { ModuleNav, ModuleScreenProps } from \"./modules/types\";\nimport { PanelContent, PoweredByBar } from \"./panel\";\nimport { ResizeGrip } from \"./resize-grip\";\nimport type { PanelProps } from \"./page-shell\";\nimport { useFileDrop } from \"./use-file-drop\";\nimport { useDragMove } from \"./use-drag-move\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n panelProps: PanelProps;\n enabledModules: ResolvedModule[];\n nav: HomeNav;\n /** Total unread across conversations \u2014 drives the Messages-tab badge. */\n unreadCount: number;\n onSelectSession: (sessionId: string) => void;\n onStartNewSession: () => void;\n /** Set panel size (used to grow to `expanded` while reading an iframe/content). */\n onSetPanelSize: (size: PanelSize) => void;\n}\n\nexport function MessengerHome({\n panelProps,\n enabledModules,\n nav,\n unreadCount,\n onSelectSession,\n onStartNewSession,\n onSetPanelSize,\n}: Props) {\n const options = panelProps.options;\n const strings = options.strings;\n const containerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const dispose = trapFocus(el);\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") panelProps.onClose?.();\n };\n el.addEventListener(\"keydown\", onKey);\n return () => {\n dispose();\n el.removeEventListener(\"keydown\", onKey);\n };\n }, [panelProps.onClose]);\n\n const composerAttachApiRef = useRef<ComposerAttachAPI | null>(null);\n const onDropItems = useCallback((items: DataTransferItemList) => {\n composerAttachApiRef.current?.attachFromDrop(items);\n }, []);\n const { visible: dragOver } = useFileDrop({ containerRef, onDrop: onDropItems });\n\n // Modal mode: grab a chrome bar (header / back-header / home hero) to move\n // the centred dialog. One controller for the whole messenger card.\n useDragMove(containerRef.current, options.mode === \"modal\");\n\n const navState = useComputed(() => nav.sig.value).value;\n const top = topScreen(navState);\n const activeModule = options.modules.byId[navState.activeTab];\n\n // A \"reader\" is the final, immersive content view (article iframe, structured\n // content doc, news item) \u2014 it expands the panel and hides the bottom tab bar.\n // List/browsing drill-ins (help collections \u2192 sub-collections \u2192 sections)\n // KEEP the tab bar, so the visitor stays oriented while navigating the\n // hierarchy. Tabs show at a tab root OR on a non-reader drill-in.\n const isReader = top?.kind === \"iframe\" || top?.kind === \"content\";\n const showTabBar = enabledModules.length > 1 && (atTabRoot(navState) || !isReader);\n const savedSize = useRef<PanelSize | null>(null);\n useEffect(() => {\n if (isReader && savedSize.current === null) {\n savedSize.current = panelProps.panelSize;\n if (panelProps.panelSize === \"normal\") onSetPanelSize(\"expanded\");\n } else if (!isReader && savedSize.current !== null) {\n const prev = savedSize.current;\n savedSize.current = null;\n onSetPanelSize(prev);\n }\n }, [isReader]);\n\n const moduleNav: ModuleNav = {\n push: nav.push,\n pop: nav.pop,\n switchTab: nav.switchTab,\n switchToLayout: (layout) => {\n const target = enabledModules.find((m) => m.layout === layout);\n if (target) nav.switchTab(target.id);\n },\n selectSession: onSelectSession,\n startNewSession: onStartNewSession,\n };\n\n const screenProps = (module: ResolvedModule): ModuleScreenProps => ({\n options,\n transport: panelProps.transport,\n strings,\n config: module,\n visitorId: panelProps.visitorId,\n nav: moduleNav,\n panelProps,\n });\n\n let body: preact.JSX.Element | null;\n if (top?.kind === \"iframe\") {\n body = (\n <IframeView url={top.url} title={top.title} strings={strings} onBack={nav.pop} onClose={panelProps.onClose} />\n );\n } else if (top?.kind === \"content\") {\n body = (\n <ContentView\n id={top.id}\n title={top.title}\n transport={panelProps.transport}\n strings={strings}\n onBack={nav.pop}\n onClose={panelProps.onClose}\n />\n );\n } else if (activeModule?.layout === \"chat\") {\n // A `chat`-layout tab IS the chat \u2014 the existing panel (header history +\n // composer), driven by the App's chat/history view.\n body = (\n <PanelContent\n {...panelProps}\n containerEl={containerRef.current}\n dragOver={dragOver}\n composerAttachApiRef={composerAttachApiRef}\n />\n );\n } else if (activeModule) {\n const Root = LAYOUTS[activeModule.layout].Root;\n body = Root ? <Root key={activeModule.id} {...screenProps(activeModule)} /> : null;\n } else {\n body = null;\n }\n\n return (\n <div\n ref={containerRef}\n class={`${p}-panel ${p}-messenger`}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-label={strings.panelTitle}\n style={{ position: \"relative\" }}\n data-testid={TID.messengerHome}\n >\n <div class={`${p}-messenger-body`}>{body}</div>\n {showTabBar ? (\n <HomeTabBar\n modules={enabledModules}\n activeTab={navState.activeTab}\n strings={strings}\n unreadCount={unreadCount}\n onSelect={nav.switchTab}\n />\n ) : null}\n {/* Global \"Powered by\" \u2014 below the tab bar, shown on every tab. */}\n <PoweredByBar poweredBy={options.poweredBy} />\n {/* Drag-resize the whole floating panel \u2014 available on every tab. The\n Messages chat renders PanelContent, which carries its own grip, so we\n skip ours there to avoid a duplicate. */}\n {options.size.resize?.enabled && !(activeModule?.layout === \"chat\" && !top) ? (\n <ResizeGrip\n panelEl={containerRef.current}\n resize={options.size.resize}\n position={options.position}\n initialSize={panelProps.initialWidgetSize}\n onSizeChange={panelProps.onWidgetSizeChange}\n strings={strings}\n />\n ) : null}\n </div>\n );\n}\n", "/** Shown in the panel when no module is enabled (every tab toggled off). */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/i18n/strings\";\nimport { CloseIcon } from \"./icons\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\nexport function ModulesEmpty({ strings, onClose }: { strings: Strings; onClose?: () => void }) {\n return (\n <div\n class={`${p}-panel ${p}-modules-empty`}\n role=\"dialog\"\n aria-label={strings.panelTitle}\n data-testid={TID.modulesEmpty}\n >\n {onClose ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn ${p}-modules-empty-close`}\n onClick={onClose}\n aria-label={strings.close}\n title={strings.close}\n >\n <CloseIcon />\n </button>\n ) : null}\n <p class={`${p}-modules-empty-text`}>{strings.modulesEmpty}</p>\n </div>\n );\n}\n", "/**\n * Typewriter animator for server-pushed welcome messages.\n *\n * Each message's text signal starts empty; this module fills it at a target\n * chars-per-second rate using `requestAnimationFrame` for smooth playback.\n * Multiple welcome messages animate sequentially so they feel like a real\n * greeting, not a wall of text.\n *\n * Cancellation is driven by an `AbortSignal` \u2014 the App aborts on clear /\n * unmount and the in-flight rAF is cancelled immediately.\n */\n\nimport type { ReactiveMessage } from \"@/stream/messages\";\n\n/** Type each welcome message in sequence. Aborts cleanly on `abort`. */\nexport async function animateWelcomeSequence(\n reactives: ReactiveMessage[],\n payloads: Array<{ text: string }>,\n charsPerSecond: number,\n abort: AbortSignal,\n): Promise<void> {\n for (let i = 0; i < reactives.length; i++) {\n if (abort.aborted) return;\n const msg = reactives[i];\n const payload = payloads[i];\n if (!msg || !payload) continue;\n await animateTypewriter(msg, payload.text, charsPerSecond, abort);\n }\n}\n\n/**\n * Animate `text` into the message's first text part at `charsPerSecond`.\n *\n * Resolves after the full text is rendered (or immediately on abort /\n * when rAF is unavailable, falling back to an instant paint).\n */\nexport function animateTypewriter(\n msg: ReactiveMessage,\n text: string,\n charsPerSecond: number,\n abort: AbortSignal,\n): Promise<void> {\n return new Promise((resolve) => {\n const part = msg.partsSig.value[0];\n if (!part || part.kind !== \"text\") {\n resolve();\n return;\n }\n\n const finish = () => {\n part.textSig.value = text;\n part.doneSig.value = true;\n msg.status = \"complete\";\n resolve();\n };\n\n // Fallback for non-browser runtimes (SSR-ish, tests without rAF polyfill).\n if (typeof requestAnimationFrame === \"undefined\") {\n finish();\n return;\n }\n\n const totalMs = (text.length / Math.max(1, charsPerSecond)) * 1000;\n const start = performance.now();\n\n const tick = (now: number) => {\n if (abort.aborted) return finish();\n const progress = totalMs === 0 ? 1 : Math.min(1, (now - start) / totalMs);\n part.textSig.value = text.slice(0, Math.floor(progress * text.length));\n if (progress >= 1) return finish();\n rafId = requestAnimationFrame(tick);\n };\n let rafId = requestAnimationFrame(tick);\n\n abort.addEventListener(\n \"abort\",\n () => {\n cancelAnimationFrame(rafId);\n finish();\n },\n { once: true },\n );\n });\n}\n", "/**\n * Launcher-callout dismissal state.\n *\n * Tracks whether the current callout copy is dismissed for THIS\n * session, with a configurable \"persistent\" mode:\n *\n * \u2022 `persistent: true` (default) \u2014 the bubble re-shows on every\n * visit regardless of prior dismissal. The \"recurring greeting\"\n * behaviour. `dismissCallout()` only flips the in-session flag;\n * reloading the page surfaces the bubble again.\n *\n * \u2022 `persistent: false` \u2014 `dismissCallout()` writes the dismissed\n * TEXT to persistence. Subsequent loads of the SAME text stay\n * hidden; changing the text bypasses the dismissal (a new\n * message is a new ask, not a duplicate of the dismissed one).\n *\n * Callers read `dismissed` + call `dismissCallout()` from the\n * launcher / panel-open code paths.\n */\n\nimport { useCallback, useEffect, useState } from \"preact/hooks\";\nimport type { LauncherCalloutOptions } from \"@/core/config\";\nimport type { Persistence } from \"@/core/persistence\";\n\ninterface UseLauncherCalloutOptions {\n callout: LauncherCalloutOptions | null;\n persistence: Persistence;\n}\n\ninterface UseLauncherCalloutResult {\n dismissed: boolean;\n /** Mark the current callout copy as dismissed (in-session + persistent variants). */\n dismissCallout: () => void;\n}\n\nexport function useLauncherCallout({ callout, persistence }: UseLauncherCalloutOptions): UseLauncherCalloutResult {\n const textKey = callout?.text ?? \"\";\n // `null` callout means \"no bubble configured\"; treat as non-persistent\n // for the type narrowing. Default to `true` when callout exists but\n // doesn't explicitly opt out (matches the schema `.default(true)`).\n const persistent = callout?.persistent ?? true;\n\n const [dismissed, setDismissed] = useState(() => (persistent ? false : persistence.loadCalloutDismissed(textKey)));\n\n // Re-read dismissal on text / persistence change \u2014 a different\n // configured message bypasses the previous dismissal.\n useEffect(() => {\n setDismissed(persistent ? false : persistence.loadCalloutDismissed(textKey));\n }, [textKey, persistent, persistence]);\n\n const dismissCallout = useCallback(() => {\n setDismissed(true);\n if (!persistent) persistence.saveCalloutDismissed(textKey);\n }, [persistence, textKey, persistent]);\n\n return { dismissed, dismissCallout };\n}\n", "import { useCallback, useEffect, useMemo, useRef, useState } from \"preact/hooks\";\nimport { useSignal } from \"@preact/signals\";\nimport { BRAND } from \"@/core/brand\";\nimport { applyOptionOverrides, type PageContext, type ResolvedOptions, type ThemePreference } from \"@/core/config\";\nimport { isBlocksConfigShape, isSiteConfigShape } from \"@/core/start-session-shape\";\nimport type { BlocksConfig, SiteConfig } from \"@/schema\";\nimport { bindHostCommands } from \"@/core/host-commands\";\nimport { applyMode } from \"@/shadow/mount\";\nimport {\n computeHostMode,\n currentViewportWidth,\n type PanelSize,\n pickInitialPanelSize,\n resolveInitialPanelState,\n} from \"./host-mode\";\nimport { createAuth } from \"@/core/auth\";\nimport { uuid7 } from \"@/core/ids\";\nimport { AgentTransport } from \"@/stream/transport\";\nimport {\n buildSendMessageRequest,\n type SessionSummary,\n type SessionAgent,\n type StartSessionResponse,\n type SessionSuggestion,\n} from \"@/stream/protocol\";\nimport {\n assistantText,\n fromReactive,\n fromWireMessage,\n makeAssistantMessage,\n makeInstantWelcomeMessage,\n makeTypewriterMessage,\n makeUserMessage,\n type ReactiveMessage,\n} from \"@/stream/messages\";\nimport { StreamReducer } from \"@/stream/reducer\";\nimport { createPersistence, type UserPrefs, type WidgetSize } from \"@/core/persistence\";\nimport { logger } from \"@/core/logger\";\n\nconst log = logger.scope(\"app\");\nimport type { EventBus } from \"@/core/events\";\nimport type { LocalAttachment } from \"@/core/attachments\";\nimport { FeedbackBus } from \"@/core/feedback\";\nimport type { Message } from \"@/stream/types\";\nimport { errorMessageFor } from \"./error-message\";\nimport { Launcher, LauncherCallout } from \"./launcher\";\nimport { Panel, type PanelView } from \"./panel\";\nimport { PageShell } from \"./page-shell\";\nimport { createHomeNav, type HomeNav } from \"./home-nav\";\nimport { MessengerHome } from \"./messenger-home\";\nimport { ModulesEmpty } from \"./modules-empty\";\nimport { isActionVisible, resolveCloseAction } from \"./action-visibility\";\nimport { animateWelcomeSequence } from \"./welcome-animator\";\nimport { useLauncherCallout } from \"./use-launcher-callout\";\n\nconst p = BRAND.cssPrefix;\n\ninterface AppProps {\n options: ResolvedOptions;\n hostElement: HTMLElement;\n bus: EventBus;\n}\n\nexport function App({ options, hostElement, bus }: AppProps) {\n // Each widgetId gets its own storage namespace \u2014 two widgets on the\n // same brand (different deployments on one page) stay isolated.\n const [persistence] = useState(() => createPersistence(options.widgetId, options.storage));\n // Stable anonymous browser id. Minted on first ever load, reused forever.\n // Sent on every backend request so the server can attach per-visitor\n // settings, conversation history, etc.\n // `setVisitorId` is only called when the server rebinds the id via\n // `/start-session` (collision / expiration). Steady state \u2014 every common\n // mount \u2014 this stays the same value `useState`'s lazy initializer\n // read on mount.\n const [visitorId, setVisitorId] = useState(() => persistence.getVisitorId());\n // Cached preferences. Empty `{}` means \"no client-side cache yet\" \u2014 the\n // server's reply on `connect()` becomes the seed.\n const initialSettings = persistence.loadUserPrefs();\n // The end-user's last picked locale (from the language switcher) wins\n // over anything the page-owner / server set \u2014 it's their explicit\n // personal choice. Falls through to `options.locale` (which itself\n // already accounts for `defaultLocale: \"auto\"` detection).\n const [activeLocale, setActiveLocale] = useState<string>(() => initialSettings.locale ?? options.locale);\n // End-user's last picked theme mode. Same precedence as locale \u2014 the\n // picker writes both to persisted `userPrefs` and to the host's\n // `data-theme` attribute so the panel styling reflects the choice\n // immediately without waiting for `update()` to round-trip.\n const [activeThemeMode, setActiveThemeMode] = useState<ThemePreference>(\n () => initialSettings.themeMode ?? options.themeMode,\n );\n // Conversation thread id \u2014 persisted across reloads. The backend is the\n // source of truth for the actual messages; we re-fetch them via\n // `transport.loadSession()` after `connect()` resolves.\n const sessionIdSig = useSignal<string | undefined>(persistence.loadSessionId());\n // Messages start empty \u2014 historical content arrives async via loadSession().\n const messagesSig = useSignal<ReactiveMessage[]>([]);\n // Total unread across the visitor's conversations \u2014 drives the Messages-tab\n // badge. Server-owned: summed from `/sessions` `unreadCount` (set by the\n // backend's per-(visitor,session) `lastReadAt`), refreshed on open + after a\n // thread is marked read.\n const unreadCountSig = useSignal(0);\n // True while the resume `loadSession()` is in flight \u2014 drives a placeholder\n // in MessageList so reload of a long thread doesn't flash empty.\n const [loadingMessages, setLoadingMessages] = useState(false);\n // Messenger-home navigation (active tab + per-tab drill-in stacks).\n // Created once and kept across open/close so the last tab/screen is\n // preserved; on a fresh load it starts on the persisted (or default)\n // tab. `switchTab` persists the choice.\n function landingTab(): string {\n const list = options.modules.list;\n const ids = new Set(list.map((m) => m.id));\n const persisted = persistence.loadActiveModule();\n if (persisted && ids.has(persisted)) return persisted;\n return list[0]?.id ?? \"\";\n }\n /** The id of the chat-layout tab (where conversations open), if any. */\n function chatTabId(): string | undefined {\n return options.modules.list.find((m) => m.layout === \"chat\")?.id;\n }\n const homeNavRef = useRef<HomeNav>();\n if (!homeNavRef.current) {\n const ids = options.modules.list.map((m) => m.id);\n homeNavRef.current = createHomeNav(landingTab(), ids, (tab) => persistence.saveActiveModule(tab));\n }\n const homeNav = homeNavRef.current;\n // Gates the FIRST render. Stays `false` until the initial start-session\n // resolves (success OR error) so the widget never paints with stale\n // mount-time defaults that the server immediately overrides \u2014 e.g.\n // a `mode: \"modal\"` deployment briefly showing a floating launcher\n // before the merged options kick in. After the first start-session the\n // flag stays `true` for the lifetime of the instance; subsequent\n // re-connects (`handleClear`) don't reset it.\n const [sessionReady, setSessionReady] = useState(false);\n\n const isInlineLike = options.mode === \"standalone\" || options.mode === \"inline\";\n // Initial open + panel-size state. Shared helper with `index.ts`'s\n // `resolveInitialHostMode` so the first App render and the pre-mount\n // iframe sizing agree \u2014 otherwise the iframe paints at the wrong size\n // and snaps to the right one on the next tick (the flicker bug).\n //\n // Inline / standalone are always \"open\" (panel in flow / full viewport).\n // Floating modes: user's persisted pick wins over `startMinimized` \u2014\n // closing then reloading doesn't force an auto-reopen.\n //\n // `useRef` (not `useState`) to compute exactly once on mount \u2014 we'd\n // otherwise call `resolveInitialPanelState` twice, once per useState\n // initializer, even though every field comes from the same snapshot.\n //\n // The values are recomputed below in a one-shot effect once the\n // start-session resolves \u2014 see `initialPanelApplied`. This ref's value\n // seeds the FIRST render (which is gated by `sessionReady`, so\n // it never paints to the user), then the effect overlays the\n // server-merged options.\n const initialPanelRef = useRef(\n resolveInitialPanelState(options, currentViewportWidth(), persistence.loadPanelOpen(), persistence.loadPanelSize()),\n );\n const [isOpen, setIsOpen] = useState(initialPanelRef.current.panelOpen);\n // Flips to `true` after the first post-start-session `isOpen` / `panelSize`\n // sync below runs. Stays `true` for the lifetime of the App so subsequent\n // server-settings merges don't fight the user's manual open/close +\n // expand/fullscreen choices.\n const initialPanelApplied = useRef(false);\n // True for the brief window between user-clicking-launcher and the\n // launcher actually unmounting \u2014 lets the FAB run a fade-out animation\n // in parallel with the panel's fade-in, avoiding a visible \"FAB \u2192 blank\n // \u2192 panel\" flicker. Cleared by a timer below.\n const [launcherLeaving, setLauncherLeaving] = useState(false);\n // Callout dismissal state \u2014 see `useLauncherCallout` for the\n // persistent vs in-session behaviour. The destructured names below\n // (`calloutDismissed` / `dismissCallout`) keep the call sites\n // unchanged from when this lived inline.\n const { dismissed: calloutDismissed, dismissCallout: dismissCalloutRaw } = useLauncherCallout({\n callout: options.launcher.callout,\n persistence,\n });\n const calloutPersistent = options.launcher.callout?.persistent ?? true;\n // Wrap the hook's dismiss so the action surfaces as both a log line\n // and a bus event \u2014 keeps the hook free of cross-cutting concerns.\n const dismissCallout = useCallback(() => {\n log.info(\"calloutDismiss\");\n bus.emit(\"calloutDismiss\", undefined);\n dismissCalloutRaw();\n }, [bus, dismissCalloutRaw]);\n const [panelSize, setPanelSize] = useState<PanelSize>(initialPanelRef.current.panelSize);\n // One-shot flag so the server-push effect below doesn't override the user\n // after they've manually expanded / fullscreen-ed.\n const initialSizeApplied = useRef(false);\n const [view, setView] = useState<PanelView>(\"chat\");\n // `canSend = false` switches the composer to a read-only banner when a\n // past, closed chat is being viewed (`canContinue = false` from the API).\n const [canSend, setCanSend] = useState(true);\n const [streaming, setStreaming] = useState(false);\n const [agent, setAgent] = useState<SessionAgent | null>(null);\n const [suggestions, setSuggestions] = useState<SessionSuggestion[]>([]);\n const [activeCancel, setActiveCancel] = useState<(() => void) | null>(null);\n const welcomeAbortRef = useRef<AbortController | null>(null);\n // Page-mode-only payloads parsed from the start-session. Both stay\n // `undefined` for widget/inline/standalone \u2014 the sidebar simply renders\n // empty sections when blocks are missing, so the parse-failure fallback\n // is a no-op rather than a hard error.\n const [parsedSite, setParsedSite] = useState<SiteConfig | undefined>(undefined);\n const [parsedBlocks, setParsedBlocks] = useState<BlocksConfig | undefined>(undefined);\n // Page-mode-only: sidebar collapse state, persisted per-device. `null`\n // from `loadSidebarCollapsed()` means user has never toggled \u2014 default\n // to expanded so first-visit users see the conversation list.\n const [sidebarCollapsed, setSidebarCollapsed] = useState(() => persistence.loadSidebarCollapsed() ?? false);\n\n const [transport] = useState(\n () =>\n new AgentTransport({\n baseUrl: options.baseUrl,\n auth: createAuth({\n publicKey: options.publicKey,\n aiAgentDeploymentId: options.aiAgentDeploymentId,\n publicKeyHeader: \"x-public-key\",\n }),\n endpoints: options.endpoints,\n }),\n );\n const [reducer] = useState(() => new StreamReducer(messagesSig));\n const [feedback] = useState(() => new FeedbackBus(options.sound, options.haptics));\n\n /**\n * Apply a settings patch locally + push to the backend (fire-and-forget).\n * Server failure leaves the local cache valid; the next `connect()` re-syncs.\n */\n const patchAndSync = useCallback(\n (patch: Partial<UserPrefs>) => {\n persistence.patchUserPrefs(patch);\n transport.saveUserPrefs(persistence.loadUserPrefs()).catch((err) => {\n log.warn(\"saveUserPrefs failed\", { err });\n });\n },\n [persistence, transport],\n );\n\n // Apply the cached `soundMuted` preference once on mount.\n useEffect(() => {\n if (initialSettings.soundMuted !== undefined) feedback.setMuted(initialSettings.soundMuted);\n // Mount-only init.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Keep the host's `data-theme` attribute in lockstep with `activeThemeMode`.\n // `applyHostAttributes` (called from index.ts on mount AND on every\n // server-settings merge) writes `options.themeMode` to the host \u2014 which\n // would stomp a user's picked/persisted theme unless we reclaim it here.\n // Depending on `options` makes this effect re-fire after the connect\n // merge too, so the user's pick survives the server round-trip.\n useEffect(() => {\n hostElement.dataset.theme = activeThemeMode;\n }, [hostElement, activeThemeMode, options]);\n\n // Config can change after a server-settings merge \u2014 keep the bus in sync.\n useEffect(() => {\n feedback.update(options.sound, options.haptics);\n }, [feedback, options.sound, options.haptics]);\n\n // Release audio resources on unmount.\n useEffect(() => () => feedback.destroy(), [feedback]);\n\n // Subscribe to mute toggles \u2192 persist + push to backend. Skip the very\n // first emission (signal subscription always fires the current value\n // synchronously on subscribe), since the *initial* state isn't a user\n // action and we don't want to round-trip it.\n useEffect(() => {\n let firstRun = true;\n return feedback.mutedSig.subscribe((muted) => {\n if (firstRun) {\n firstRun = false;\n return;\n }\n patchAndSync({ soundMuted: muted });\n });\n }, [feedback, patchAndSync]);\n\n // `options.size.initialSize` often arrives via server settings AFTER the\n // first render. The ref guard keeps this idempotent across the (cheap)\n // re-evaluations triggered when `options.size` is re-spread by the merge.\n useEffect(() => {\n if (initialSizeApplied.current || !options.size.initialSize) return;\n initialSizeApplied.current = true;\n if (persistence.loadPanelSize()) return; // the user's persisted size wins over `initialSize`\n setPanelSize(pickInitialPanelSize(options.mode, options.size, currentViewportWidth()));\n }, [options.mode, options.size, persistence]);\n\n // Re-derive `isOpen` + `panelSize` from the SERVER-MERGED options once\n // the start-session resolves. The mount-time `useState` initializer ran\n // with pre-start-session options (no `mode`, default `startMinimized:\n // true`), so a deployment with `startMinimized: false` + `mode:\n // \"modal\"` would otherwise paint as closed because the initial state\n // was captured before the server's overrides arrived.\n //\n // One-shot via `initialPanelApplied` \u2014 subsequent merges (e.g. a\n // server-pushed locale change) must NOT reopen a panel the user just\n // closed, or reset their drag-resize, etc.\n useEffect(() => {\n if (!sessionReady || initialPanelApplied.current) return;\n initialPanelApplied.current = true;\n const state = resolveInitialPanelState(\n options,\n currentViewportWidth(),\n persistence.loadPanelOpen(),\n persistence.loadPanelSize(),\n );\n setIsOpen(state.panelOpen);\n setPanelSize(state.panelSize);\n }, [sessionReady, options, persistence]);\n\n // Seed the messenger landing tab once the start-session's module config has\n // merged (the nav controller was created from pre-start-session options). The\n // panel is gated on `sessionReady`, so the user can't have navigated yet.\n const homeNavSeeded = useRef(false);\n useEffect(() => {\n if (!sessionReady || homeNavSeeded.current) return;\n homeNavSeeded.current = true;\n homeNav.reset(landingTab());\n }, [sessionReady]);\n\n /** Render the welcome payload \u2014 typewriter or instant per options. */\n const playWelcome = useCallback(\n (welcomeMessages: NonNullable<StartSessionResponse[\"welcome\"]>[\"messages\"]) => {\n if (!welcomeMessages || welcomeMessages.length === 0) return;\n const { animation, speed } = options.welcome;\n if (animation === \"instant\") {\n messagesSig.value = welcomeMessages.map(makeInstantWelcomeMessage);\n return;\n }\n welcomeAbortRef.current?.abort();\n const ctrl = new AbortController();\n welcomeAbortRef.current = ctrl;\n const reactives = welcomeMessages.map(makeTypewriterMessage);\n messagesSig.value = reactives;\n void animateWelcomeSequence(reactives, welcomeMessages, speed, ctrl.signal);\n },\n [messagesSig, options.welcome],\n );\n\n // ---- connect / re-connect ------------------------------------------------\n // `runStartSession` is the one place that talks to `/ai/agent/start-session`. Called:\n // 1. Once on mount (resume or first-visit welcome).\n // 2. Again whenever the user clicks \"New conversation\" \u2014 `newConversation`\n // flag tells the backend to mint a fresh sessionId + return fresh welcome.\n // Race safety: a generation counter discards any in-flight response if a\n // newer call has started or the component has unmounted.\n const connectGenRef = useRef(0);\n\n const runStartSession = useCallback(\n async ({ newConversation }: { newConversation: boolean }) => {\n const myGen = ++connectGenRef.current;\n const isStale = () => myGen !== connectGenRef.current;\n const persistedChatId = sessionIdSig.value;\n const sessionId = newConversation || !persistedChatId ? uuid7() : persistedChatId;\n if (sessionId !== persistedChatId) {\n sessionIdSig.value = sessionId;\n persistence.saveSessionId(sessionId);\n }\n\n let res: StartSessionResponse;\n try {\n res = await transport.startSession({\n visitorId,\n sessionId,\n userPrefs: persistence.loadUserPrefs(),\n locale: options.locale,\n });\n } catch (err) {\n if (isStale()) return;\n bus.emit(\"error\", err);\n options.onError?.(err);\n // Unblock the first render anyway \u2014 leaving the widget invisible\n // forever on a network failure is worse than rendering with the\n // local mount-time options. The error event already surfaced the\n // problem to the host page.\n if (!newConversation) setSessionReady(true);\n return;\n }\n if (isStale()) return;\n\n // Visitor / session rebind \u2014 the server returned a fresh id\n // (collision, expiration, server policy). Persist the new id\n // and notify host listeners so analytics / external systems\n // pinned to the previous value can re-link. Idempotent: if the\n // server echoed back what we sent, this is a no-op. The\n // session-rebind side also flows through `sessionIdSig` updates\n // a few lines below in the !isResume branch \u2014 keep them\n // consistent.\n if (res.visitorId && res.visitorId !== visitorId) {\n log.info(\"visitor rebound\", { previous: visitorId, current: res.visitorId, reason: res.rebind?.visitorId });\n persistence.setVisitorId(res.visitorId);\n setVisitorId(res.visitorId);\n bus.emit(\"visitorRebound\", {\n previous: visitorId,\n current: res.visitorId,\n reason: res.rebind?.visitorId,\n });\n }\n if (res.sessionId && res.sessionId !== sessionId) {\n log.info(\"session rebound\", { previous: sessionId, current: res.sessionId, reason: res.rebind?.sessionId });\n bus.emit(\"sessionRebound\", {\n previous: sessionId,\n current: res.sessionId,\n reason: res.rebind?.sessionId,\n });\n }\n\n if (res.agent) setAgent(res.agent);\n if (res.welcome?.suggestions) setSuggestions(res.welcome.suggestions);\n // Page-mode site/blocks. Permissive-narrow so an unknown future\n // field doesn't blank the sidebar; on shape mismatch we keep\n // whatever was last set (typically `undefined` \u2192 sidebar shows\n // empty sections). Hand-rolled narrowers (not zod) so the IIFE\n // auto-boot path stays free of the ~70 KB gz zod runtime \u2014\n // canonical Zod schemas live under `@/schema`. Skip entirely\n // when not in page mode: `parsedSite` / `parsedBlocks` are only\n // ever read by `<PageShell>` (rendered only when `mode === \"page\"`),\n // so validating + storing them outside that mode is dead work.\n if (options.mode === \"page\") {\n if (res.site && isSiteConfigShape(res.site)) setParsedSite(res.site);\n else if (res.site) log.warn(\"invalid site config, using fallback\", { site: res.site });\n if (res.blocks && isBlocksConfigShape(res.blocks)) setParsedBlocks(res.blocks);\n else if (res.blocks) log.warn(\"invalid blocks config, using fallback\", { blocks: res.blocks });\n }\n // Server's authoritative copy of preferences wins on the *initial*\n // bootstrap (cross-device sync) \u2014 but NOT on a \"New conversation\"\n // re-connect. The user's per-device UI state (locale picker, mute\n // toggle, dragged panel size) is local-authoritative once the widget\n // is running; reapplying server's snapshot would visibly reset\n // anything the user changed since mount.\n if (res.userPrefs && !newConversation) {\n persistence.saveUserPrefs(res.userPrefs);\n if (res.userPrefs.locale) setActiveLocale(res.userPrefs.locale);\n if (res.userPrefs.soundMuted !== undefined) feedback.setMuted(res.userPrefs.soundMuted);\n if (res.userPrefs.themeMode) setActiveThemeMode(res.userPrefs.themeMode);\n }\n\n // Resume an existing chat OR start fresh. Resume is suppressed for\n // newConversation so we always render welcome on a clear.\n const isResume = !newConversation && persistedChatId && res.sessionId === persistedChatId;\n if (isResume) {\n setLoadingMessages(true);\n try {\n const chat = res.messages\n ? { sessionId: persistedChatId, canContinue: res.canContinue ?? true, messages: res.messages }\n : await transport.loadSession(persistedChatId);\n if (isStale()) return;\n messagesSig.value = chat.messages.map(fromWireMessage);\n setCanSend(chat.canContinue);\n // Connect returned new-chat suggestions (\"Show me pricing\", etc.);\n // those don't belong on a resumed conversation. Swap them out for\n // whatever loadSession says (including clearing them entirely when\n // the backend returns `undefined` / `null` \u2014 a resumed chat with\n // no suggestions is a valid state).\n setSuggestions(chat.suggestions ?? []);\n } catch (err) {\n if (isStale()) return;\n log.warn(\"loadSession failed; resetting sessionId\", { err, sessionId: persistedChatId });\n sessionIdSig.value = undefined;\n persistence.saveSessionId(undefined);\n } finally {\n if (!isStale()) setLoadingMessages(false);\n }\n } else {\n sessionIdSig.value = res.sessionId;\n persistence.saveSessionId(res.sessionId);\n playWelcome(res.welcome?.messages);\n }\n\n // Only the initial bootstrap fires \"sessionStarted\". Page-owner listeners\n // treat that event as \"widget is ready\" \u2014 firing it again on every\n // \"New conversation\" would mislead them. It also prevents index.ts\n // from re-merging `res.config` + re-resolving options, which would\n // hand the App a fresh `options.size.*` object and cause ResizeGrip\n // to re-fire its useEffect \u2014 visibly resetting the user's drag-size.\n if (!newConversation) {\n bus.emit(\"sessionStarted\", res);\n // Unblock the first render. Sequenced AFTER `bus.emit(\"sessionStarted\")`\n // so the merged options from index.ts are applied before the gate\n // lifts \u2014 guarantees no flash of stale mount-time defaults.\n setSessionReady(true);\n }\n },\n [transport, visitorId, persistence, options, bus, messagesSig, sessionIdSig, feedback, playWelcome],\n );\n\n // Keep the transport's context fresh as `userContext` / `pageContext` change \u2014\n // the host calls `instance.update(...)` after login or on SPA navigation. The\n // transport carries it as the `context` field on EVERY request, so the new\n // context rides the NEXT request with no extra round-trip (the non-obtrusive\n // path). Gated by value signatures, not object identity. Declared BEFORE the\n // mount bootstrap so the very first start-session already carries context.\n const userSig = JSON.stringify(options.userContext ?? null);\n const pageSig = JSON.stringify(options.pageContext ?? null);\n useEffect(() => {\n transport.setContext(options.userContext, toWirePageContext(options.pageContext));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [transport, userSig, pageSig]);\n\n // Mount-only bootstrap. Subsequent re-connects come from `handleClear`.\n // Other options changes that need a full re-mount go through index.ts.\n useEffect(() => {\n void runStartSession({ newConversation: false });\n return () => {\n // Invalidate any pending in-flight connect on unmount so its response\n // doesn't try to set state on a torn-down tree.\n connectGenRef.current++;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Only a USER-identity change re-runs start-session \u2014 to re-personalise the\n // greeting / welcome. A `pageContext` navigation must NOT reconnect (it just\n // refreshes the transport context above), so the session and thread are never\n // disturbed.\n const lastUserSig = useRef(userSig);\n useEffect(() => {\n if (!sessionReady || lastUserSig.current === userSig) return;\n lastUserSig.current = userSig;\n void runStartSession({ newConversation: false });\n }, [userSig, sessionReady, runStartSession]);\n\n // ---- host data-mode attribute -------------------------------------------\n useEffect(() => {\n applyMode(hostElement, computeHostMode(options.mode, panelSize, isOpen));\n }, [hostElement, isOpen, panelSize, options.mode]);\n\n // ---- modal: backdrop click closes ---------------------------------------\n // The host element IS the backdrop in modal mode (full-viewport fixed\n // overlay). Clicks on the host but outside the panel close it. Clicks\n // inside the panel propagate up via `e.target` so we filter by hit.\n useEffect(() => {\n if (options.mode !== \"modal\" || !isOpen) return;\n const onClick = (e: MouseEvent) => {\n // `composedPath` pierces the shadow DOM so we can tell whether the\n // click landed on the panel or on the bare host backdrop.\n const path = e.composedPath();\n const clickedPanel = path.some(\n (n) => n instanceof HTMLElement && n.classList.contains(`${BRAND.cssPrefix}-panel`),\n );\n if (!clickedPanel) setIsOpen(false);\n };\n hostElement.addEventListener(\"click\", onClick);\n return () => hostElement.removeEventListener(\"click\", onClick);\n }, [hostElement, isOpen, options.mode]);\n\n // ---- drawer: Escape closes from anywhere on the page --------------------\n // Drawer is non-modal \u2014 page stays interactive \u2014 so focus may sit\n // outside the panel when the user presses Escape. The Panel's own\n // keydown handler only fires on focus inside the panel; we add a\n // document-level listener to catch the rest.\n useEffect(() => {\n if (options.mode !== \"drawer\" || !isOpen) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setIsOpen(false);\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [isOpen, options.mode]);\n\n // ---- send / stop / clear ------------------------------------------------\n const handleSend = useCallback(\n async (text: string, attachments: LocalAttachment[]) => {\n log.info(\"send\", { textLen: text.length, attachments: attachments.length, streaming });\n if (streaming) return;\n bus.emit(\"send\", { text, attachmentCount: attachments.length });\n if (suggestions.length > 0) setSuggestions([]);\n\n const uploaded = await uploadAttachments(transport, attachments, (err) => {\n log.warn(\"upload failed\", { err });\n bus.emit(\"error\", err);\n options.onError?.(err);\n });\n\n // The send click is itself a user gesture \u2014 unlock the AudioContext\n // here so the very first inbound reply can ping.\n feedback.unlock();\n feedback.play(\"messageSent\");\n\n const userMsg = makeUserMessage(text, uploaded);\n const assistantMsg = makeAssistantMessage();\n messagesSig.value = [...messagesSig.value, userMsg, assistantMsg];\n reducer.attach(assistantMsg);\n emitMessage(bus, options, \"user\", text);\n const activeSessionId = sessionIdSig.value ?? uuid7();\n if (!sessionIdSig.value) {\n sessionIdSig.value = activeSessionId;\n persistence.saveSessionId(activeSessionId);\n }\n\n const body = buildSendMessageRequest({\n messages: messagesSig.value.slice(0, -1).map(fromReactive),\n sessionId: activeSessionId,\n // textModel + imageModel are backend-only \u2014 selected per\n // deployment in the dashboard, never passed up by the widget.\n tools: options.features.tools,\n responseMode: options.responseMode,\n // Identity + preferences ride on every message \u2014 server may need\n // them to route, persist, or reconcile in-flight.\n visitorId,\n userPrefs: persistence.loadUserPrefs(),\n });\n\n setStreaming(true);\n const handle = transport.sendMessage(body);\n setActiveCancel(() => handle.cancel);\n\n try {\n // Drain the reducer. We deliberately do NOT adopt `chunk.messageId`\n // from the backend's `start` frame \u2014 our client-minted `assistantMsg.id`\n // is already globally unique and serves as the React key for the\n // bubble. Swapping the id mid-stream would force Preact to remount\n // the bubble on the next list re-render (on `finish`), which would\n // tear down + rebuild the streaming-markdown DOM \u2192 a visible flicker\n // the moment the assistant finishes talking.\n for await (const evt of handle.iter) {\n reducer.apply(evt.chunk);\n // Backend can close a conversation mid-stream (e.g. message cap).\n // Explicit `false` on the finish chunk flips us into read-only.\n if (evt.chunk.type === \"finish\" && evt.chunk.canContinue === false) {\n setCanSend(false);\n }\n }\n assistantMsg.status = \"complete\";\n feedback.play(\"messageReceived\");\n emitMessage(bus, options, \"assistant\", assistantText(assistantMsg));\n } catch (error) {\n assistantMsg.status = \"error\";\n // Friendly, status-aware text \u2014 never leak raw \"server responded 429\u2026\".\n assistantMsg.errorText = errorMessageFor(error, options.strings);\n feedback.play(\"error\");\n bus.emit(\"error\", error);\n options.onError?.(error);\n } finally {\n reducer.detach();\n setStreaming(false);\n setActiveCancel(null);\n // Trigger a re-render so the bubble's terminal status (complete /\n // error) is reflected. No persistence call \u2014 backend is the source\n // of truth for messages now.\n messagesSig.value = [...messagesSig.value];\n }\n },\n [\n streaming,\n transport,\n reducer,\n messagesSig,\n sessionIdSig,\n options,\n bus,\n suggestions.length,\n feedback,\n persistence,\n visitorId,\n ],\n );\n\n const handleStop = useCallback(() => {\n log.info(\"stop\");\n bus.emit(\"stop\", undefined);\n activeCancel?.();\n }, [activeCancel, bus]);\n\n const handleSoundToggle = useCallback(() => {\n feedback.toggleMuted();\n const muted = feedback.mutedSig.value;\n log.info(\"soundToggle\", { muted });\n bus.emit(\"soundToggle\", { muted });\n }, [feedback, bus]);\n\n const handleClose = useCallback(() => {\n // `resolveCloseAction` decides what X means in this context:\n // exit-fullscreen for inline+fullscreen (the only way to \"close\" an\n // in-flow widget is to leave fullscreen back into its container),\n // a real close for everything else.\n if (resolveCloseAction(options.mode, panelSize) === \"exit-fullscreen\") {\n log.info(\"close \u2192 exit-fullscreen\", { mode: options.mode });\n setPanelSize(\"normal\");\n bus.emit(\"fullscreen\", false);\n return;\n }\n log.info(\"close \u2192 panel closed\", { mode: options.mode });\n setIsOpen(false);\n persistence.savePanelOpen(false);\n // `panelSize` is intentionally preserved \u2014 the user's last layout\n // (expanded / fullscreen / normal) is restored on reopen. The launcher\n // still shows while closed because `computeHostMode` returns \"closed\"\n // before it ever looks at panelSize.\n bus.emit(\"close\", undefined);\n options.onClose?.();\n }, [bus, options, persistence, panelSize]);\n\n // Recompute the unread badge from the server's per-session `unreadCount`.\n // No-op when there's no chat tab (no badge to show). Fire-and-forget.\n const refreshUnread = useCallback(() => {\n if (!options.modules.list.some((m) => m.layout === \"chat\")) return;\n transport\n .listSessions({ limit: 50 })\n .then((res) => {\n unreadCountSig.value = res.sessions.reduce((sum, c) => sum + (c.unreadCount ?? 0), 0);\n })\n .catch((err) => log.debug(\"refreshUnread failed (non-fatal)\", { err }));\n }, [transport, options.modules, unreadCountSig]);\n\n // Seed the badge once the start-session resolves \u2014 covers always-open modes\n // (inline / standalone / page) where `handleOpen` never fires.\n const unreadSeeded = useRef(false);\n useEffect(() => {\n if (!sessionReady || unreadSeeded.current) return;\n unreadSeeded.current = true;\n refreshUnread();\n }, [sessionReady, refreshUnread]);\n\n const handleOpen = useCallback(() => {\n log.info(\"open\", { mode: options.mode });\n // Open click is a user gesture \u2014 take the chance to unlock audio so the\n // first reply after open can ping without needing a send.\n feedback.unlock();\n // Cross-fade: keep the FAB rendered for one animation tick (with a\n // `leaving` flag \u2192 CSS exit animation) while the panel slides in.\n // After the same duration the FAB unmounts cleanly.\n setLauncherLeaving(true);\n setIsOpen(true);\n persistence.savePanelOpen(true);\n // Engaging with the widget at all dismisses any non-persistent\n // callout \u2014 no need to keep telling the user \"click here\" once\n // they've clicked. Callouts with `persistent: true` are immune;\n // they re-appear on every visit by design.\n if (!calloutDismissed && !calloutPersistent) dismissCallout();\n setTimeout(() => setLauncherLeaving(false), LAUNCHER_EXIT_MS);\n // Reopening is the moment to pick up replies that arrived while closed.\n refreshUnread();\n bus.emit(\"open\", undefined);\n options.onOpen?.();\n }, [bus, options, feedback, persistence, calloutDismissed, calloutPersistent, dismissCallout, refreshUnread]);\n\n const handleClear = useCallback(() => {\n log.info(\"clear \u2192 new conversation\", { wasStreaming: streaming });\n bus.emit(\"clear\", undefined);\n if (streaming) activeCancel?.();\n welcomeAbortRef.current?.abort();\n messagesSig.value = [];\n sessionIdSig.value = undefined;\n persistence.clearSession();\n // A brand-new conversation is always sendable.\n setCanSend(true);\n // Re-connect to the backend with `newConversation: true`. This:\n // - Lets the backend mint + persist the new chat upfront (so the\n // `userPrefs`/history/etc. layer sees it immediately, not on\n // the next message send).\n // - Returns a fresh agent intro / welcome / suggestions that the\n // UI renders right away \u2014 without this the panel sits empty until\n // the user types the first message.\n // - Re-applies any server-side per-visitor settings (locale, mute,\n // etc.) that may have changed since mount.\n void runStartSession({ newConversation: true });\n }, [streaming, activeCancel, messagesSig, sessionIdSig, persistence, runStartSession, bus]);\n\n /**\n * Page-mode \"New conversation\" entry-point. Shares the implementation\n * with the in-panel clear button \u2014 same backend round-trip, same local\n * reset \u2014 so there's only one path that mints a fresh session. Also\n * forces the panel back to the chat view in case some other surface\n * had toggled it (page mode mostly doesn't surface history-overlay,\n * but keep the invariant cheap).\n */\n const handleNewChat = useCallback(() => {\n handleClear();\n setView(\"chat\");\n // New chat always lands in a fresh chat-tab conversation, whatever surface\n // triggered it (panel header, page sidebar, \"send us a message\"). No-op when\n // chat is the only/active surface; switches the tab when a multi-tab\n // messenger had Home/Help/News focused.\n const chatId = options.modules.list.find((m) => m.layout === \"chat\")?.id;\n if (chatId) homeNav.switchTab(chatId);\n }, [handleClear, homeNav, options]);\n\n const handleExpand = useCallback(() => {\n const nextSize = panelSize === \"expanded\" ? \"normal\" : \"expanded\";\n log.info(\"expand\", { from: panelSize, expanded: nextSize === \"expanded\" });\n setPanelSize(nextSize);\n persistence.savePanelSize(nextSize); // remembered per-device across refresh\n bus.emit(\"expand\", nextSize === \"expanded\");\n }, [bus, panelSize, persistence]);\n\n const handleFullscreen = useCallback(() => {\n const nextSize = panelSize === \"fullscreen\" ? \"normal\" : \"fullscreen\";\n log.info(\"fullscreen\", { from: panelSize, fullscreen: nextSize === \"fullscreen\" });\n setPanelSize(nextSize);\n persistence.savePanelSize(nextSize);\n bus.emit(\"fullscreen\", nextSize === \"fullscreen\");\n }, [bus, panelSize, persistence]);\n\n const handlePopOut = useCallback(() => {\n if (!options.popOutUrl) return;\n const url = new URL(options.popOutUrl);\n if (sessionIdSig.value) url.searchParams.set(\"chat\", sessionIdSig.value);\n // Carry the widgetId across so the standalone tab reads the same\n // storage namespace (otherwise a non-default widget pops out into\n // an empty thread).\n if (options.widgetId !== \"default\") url.searchParams.set(\"widgetId\", options.widgetId);\n log.info(\"popOut\", { url: url.toString() });\n window.open(url.toString(), \"_blank\", \"noopener,noreferrer\");\n bus.emit(\"popOut\", undefined);\n }, [bus, sessionIdSig, options.popOutUrl, options.widgetId]);\n\n /** Flip between the active chat view and the history list. */\n const handleToggleHistory = useCallback(() => {\n setView((v) => {\n const next = v === \"history\" ? \"chat\" : \"history\";\n log.info(\"toggleHistory\", { view: next });\n bus.emit(\"toggleHistory\", { view: next });\n return next;\n });\n }, [bus]);\n\n /**\n * User picked a locale from the language switcher. Persist it locally\n * (so the next reload renders in their language even before the network\n * resolves) and push to the backend so other devices / future visits\n * carrying the same `visitorId` see the same choice.\n */\n const handleLocaleChange = useCallback(\n (locale: string) => {\n log.info(\"localeChange\", { locale });\n setActiveLocale(locale);\n patchAndSync({ locale });\n bus.emit(\"localeChange\", locale);\n },\n [patchAndSync, bus],\n );\n\n /**\n * User picked a theme from the theme switcher. Write through to the\n * host's `data-theme` attribute so the CSS flip is instant (before\n * React re-renders with the new `themeMode` option), persist locally,\n * and sync to the backend alongside locale + sound preferences.\n */\n const handleThemeChange = useCallback(\n (mode: ThemePreference) => {\n log.info(\"themeChange\", { mode });\n setActiveThemeMode(mode);\n hostElement.dataset.theme = mode;\n patchAndSync({ themeMode: mode });\n bus.emit(\"themeChange\", mode);\n },\n [hostElement, patchAndSync, bus],\n );\n\n /** Page-mode sidebar toggle \u2014 persist so refresh keeps the same layout. */\n const handleToggleSidebarCollapsed = useCallback(() => {\n setSidebarCollapsed((prev) => {\n const next = !prev;\n log.info(\"sidebarToggle\", { collapsed: next });\n persistence.saveSidebarCollapsed(next);\n bus.emit(\"sidebarToggle\", { collapsed: next });\n return next;\n });\n }, [persistence, bus]);\n\n /** User dragged the resize grip \u2014 persist + push the resolved size. */\n const handleWidgetSizeChange = useCallback(\n (size: WidgetSize) => {\n log.info(\"resize\", size);\n patchAndSync({ widgetSize: size });\n bus.emit(\"resize\", size);\n },\n [patchAndSync, bus],\n );\n\n /**\n * Tap a row in the history pane: fetch the full chat from the backend,\n * swap our `messagesSig` + `sessionIdSig` into it, flip view back to chat,\n * and set `canSend` based on the backend's `canContinue` flag.\n */\n const handleSelectHistoryChat = useCallback(\n async (targetChatId: string) => {\n log.info(\"selectChat\", { sessionId: targetChatId });\n bus.emit(\"selectChat\", { sessionId: targetChatId });\n try {\n const res = await transport.loadSession(targetChatId);\n const hydrated = res.messages.map(fromWireMessage);\n messagesSig.value = hydrated;\n sessionIdSig.value = res.sessionId;\n persistence.saveSessionId(res.sessionId);\n if (res.agent) setAgent(res.agent);\n setCanSend(res.canContinue);\n // Same contract as runStartSession's resume path: loadSession owns the\n // suggestion set for existing sessions. Absent/null clears whatever\n // connect seeded (typically new-chat chips that are stale here).\n setSuggestions(res.suggestions ?? []);\n setView(\"chat\");\n // Opening a thread reads it: tell the server, then refresh the badge.\n await transport.markRead(targetChatId);\n refreshUnread();\n } catch (err) {\n bus.emit(\"error\", err);\n options.onError?.(err);\n }\n },\n [transport, messagesSig, sessionIdSig, bus, options, persistence, refreshUnread],\n );\n\n // ---- bridge programmatic CustomEvents \u2192 handlers -------------------------\n useEffect(() => {\n const unsub = bindHostCommands(hostElement, {\n open: handleOpen,\n close: handleClose,\n expand: handleExpand,\n fullscreen: handleFullscreen,\n popout: handlePopOut,\n });\n return unsub;\n }, [hostElement, handleOpen, handleClose, handleExpand, handleFullscreen, handlePopOut]);\n\n // ---- render --------------------------------------------------------------\n // Apply user-driven runtime overrides (language picker, theme picker)\n // on top of the resolved baseline. Identity-preserving when neither\n // override differs from the resolved value \u2014 common case.\n const effectiveOptions = useMemo<ResolvedOptions>(\n () => applyOptionOverrides(options, activeLocale, activeThemeMode),\n [options, activeLocale, activeThemeMode],\n );\n\n const panelProps = {\n options: effectiveOptions,\n transport,\n feedback,\n bus,\n view,\n canSend,\n isStreaming: streaming,\n loadingMessages,\n messagesSig,\n agent,\n suggestions,\n initialWidgetSize: initialSettings.widgetSize ?? null,\n visitorId,\n onClose: handleClose,\n onClear: handleClear,\n onExpand: handleExpand,\n onFullscreen: handleFullscreen,\n onPopOut: handlePopOut,\n onSoundToggle: handleSoundToggle,\n onToggleHistory: handleToggleHistory,\n onSelectHistoryChat: handleSelectHistoryChat,\n onNewChat: handleNewChat,\n onLocaleChange: handleLocaleChange,\n onThemeChange: handleThemeChange,\n onWidgetSizeChange: handleWidgetSizeChange,\n onSend: handleSend,\n onStop: handleStop,\n onSuggestion: (s: SessionSuggestion) => {\n const text = s.text ?? s.label;\n log.info(\"suggestion\", { text });\n bus.emit(\"suggestion\", { text });\n void handleSend(text, []);\n },\n };\n\n // Messenger-home: open a past conversation (load it + switch to the\n // Messages tab, which is the chat), or start a fresh one. Both reuse the\n // existing chat plumbing \u2014 no separate \"thread\" surface.\n const onSelectSession = (sessionId: string) => {\n void handleSelectHistoryChat(sessionId);\n const chatId = chatTabId();\n if (chatId) homeNav.switchTab(chatId);\n };\n\n // First-paint gate. Render nothing until the initial start-session resolves\n // so server-pushed overrides (e.g. `mode: \"modal\"`) are applied before\n // the user sees anything. On error the flag is still flipped so the\n // widget renders with local defaults rather than staying invisible.\n if (!sessionReady) return null;\n\n // The single open surface, shared by EVERY mode (floating / drawer / modal /\n // inline / standalone / page): 0 enabled modules \u2192 empty state; only the\n // `messages` module \u2192 the plain chat panel; otherwise the tabbed messenger\n // home. Page mode wraps this in its sidebar; the rest render it directly.\n const enabledModules = effectiveOptions.modules.list;\n const renderSurface = (size: PanelSize) => {\n // Non-closeable modes (standalone / page, and inline below fullscreen) have\n // nothing to close back to \u2014 drop the X from the messenger chrome. The chat\n // Panel header self-gates the close action via `isActionVisible`.\n const closeable = isActionVisible(\"close\", effectiveOptions.mode, size);\n if (enabledModules.length === 0) {\n return <ModulesEmpty strings={effectiveOptions.strings} onClose={closeable ? handleClose : undefined} />;\n }\n if (enabledModules.length === 1 && enabledModules[0]?.layout === \"chat\") {\n return <Panel {...panelProps} panelSize={size} />;\n }\n return (\n <MessengerHome\n panelProps={{ ...panelProps, panelSize: size, onClose: closeable ? handleClose : undefined }}\n enabledModules={enabledModules}\n nav={homeNav}\n unreadCount={unreadCountSig.value}\n onSelectSession={onSelectSession}\n onStartNewSession={handleNewChat}\n onSetPanelSize={setPanelSize}\n />\n );\n };\n\n if (options.mode === \"page\") {\n // Page mode is always full-viewport: the sidebar owns conversation history,\n // and the main area renders the SAME messenger module surface as every\n // other mode (pinned to \"fullscreen\" so size-conditional rendering agrees).\n // Sidebar's onSelect signature differs from the in-panel history\n // pane (it forwards a SessionSummary instead of a sessionId string). Bridge\n // it back to the same handler so both surfaces share one resume path.\n const onSelectChatFromSidebar = (chat: SessionSummary) => {\n void handleSelectHistoryChat(chat.sessionId);\n };\n // The sidebar's conversation list + new-chat ARE the chat, so they only\n // make sense when the `messages` module is enabled. With messages off\n // (e.g. a help-center page of home/help/news), the sidebar shows just\n // site/nav/cards and the main area renders the tabbed module surface.\n const messagesEnabled = enabledModules.some((m) => m.layout === \"chat\");\n return (\n <div class={`${p}-anchor`}>\n <PageShell\n site={parsedSite}\n blocks={parsedBlocks}\n transport={transport}\n strings={effectiveOptions.strings}\n visitorId={visitorId}\n showConversations={messagesEnabled}\n onSelectChat={onSelectChatFromSidebar}\n onNewChat={handleNewChat}\n sidebarCollapsed={sidebarCollapsed}\n onToggleSidebarCollapsed={handleToggleSidebarCollapsed}\n >\n {renderSurface(\"fullscreen\")}\n </PageShell>\n </div>\n );\n }\n\n if (isInlineLike) {\n // Standalone mode is always fullscreen by design (dedicated tab).\n // Inline mode respects the panel-size state so the fullscreen button\n // can escape the container and take over the viewport. Expand in\n // inline is collapsed to fullscreen semantics in `computeHostMode`\n // (no sensible mid-tier when the panel already fills its container).\n const inlineSize: PanelSize = options.mode === \"standalone\" ? \"fullscreen\" : panelSize;\n return <div class={`${p}-anchor`}>{renderSurface(inlineSize)}</div>;\n }\n\n // Floating (floating / iframe). When the panel is open it takes the anchor\n // slot entirely \u2014 the FAB is hidden. Panel header carries its own close.\n // During the open transition both render briefly (`launcherLeaving`) so\n // the FAB can fade out while the panel fades in, instead of a hard swap.\n // Drawer mode renders the launcher too, but as a vertical edge tab\n // (`drawerEdgeTab`) docked to the screen border. Modal mode never renders\n // a launcher \u2014 the host page brings its own trigger and calls `open()`.\n // Either mode can opt out with `launcher.hidden: true`.\n const drawerEdgeTab = options.mode === \"drawer\";\n const triggerOwnedByPage = options.mode === \"modal\";\n const launcherVisible = !triggerOwnedByPage && !options.launcher.hidden && (!isOpen || launcherLeaving);\n // Callout: only when the launcher is visible (the panel covers it\n // otherwise), configured (`callout` resolved to non-null), and the\n // user hasn't dismissed (or `persistent: true` ignores dismissal).\n // Suppressed for the drawer edge tab \u2014 a floating bubble next to a\n // vertical edge handle doesn't read well. Combining the gate with the\n // resolved value into a single nullable lets the JSX render with one\n // truthy check + narrowed type.\n const calloutToRender =\n launcherVisible && !launcherLeaving && !calloutDismissed && !drawerEdgeTab\n ? effectiveOptions.launcher.callout\n : null;\n // Open surface (floating / drawer / modal) \u2014 the same module surface used by\n // every other mode, gated by `isOpen` and laid over the launcher.\n return (\n <div class={`${p}-anchor`} data-launcher-size={effectiveOptions.launcher.size}>\n {isOpen ? renderSurface(panelSize) : null}\n {launcherVisible ? (\n <Launcher\n onToggle={handleOpen}\n launcher={effectiveOptions.launcher}\n strings={effectiveOptions.strings}\n leaving={launcherLeaving}\n edgeTab={drawerEdgeTab}\n />\n ) : null}\n {calloutToRender ? (\n <LauncherCallout\n callout={calloutToRender}\n launcherPosition={effectiveOptions.position}\n closeLabel={effectiveOptions.strings.close}\n onDismiss={dismissCallout}\n />\n ) : null}\n </div>\n );\n}\n\n// ---- internals ------------------------------------------------------------\n\n/**\n * Auto-fill `pageContext.url` from the host page's `location.href` when the\n * host didn't set it, so the agent always knows the current page. The rest is\n * already sanitised by `resolveOptions`. Returns `undefined` when there's no\n * context at all (and no URL to capture).\n */\nfunction toWirePageContext(pageContext: ResolvedOptions[\"pageContext\"]): PageContext | undefined {\n const url = pageContext?.url ?? (typeof location !== \"undefined\" ? location.href : undefined);\n if (!pageContext && !url) return undefined;\n return { ...pageContext, ...(url ? { url } : {}) };\n}\n\n/**\n * Duration the FAB stays mounted with the `leaving` class after the user\n * clicks it. Must match the `__P__-fab-out` keyframe + `--__P__-dur-quick`\n * in panel.css. Keep them in lockstep \u2014 too short and the exit animation\n * gets cut; too long and the FAB visibly hangs over the panel.\n */\nconst LAUNCHER_EXIT_MS = 120;\n\n/** Upload each attachment; capture its `{id, url}`. Errors are reported but don't block. */\nasync function uploadAttachments(\n transport: AgentTransport,\n attachments: LocalAttachment[],\n onError: (err: unknown) => void,\n): Promise<Message[\"attachments\"]> {\n const out: NonNullable<Message[\"attachments\"]> = [];\n for (const a of attachments) {\n try {\n const { id, url } = await transport.uploadFile(a.file);\n out.push({ id, name: a.name, mediaType: a.mediaType, size: a.size, url });\n } catch (err) {\n onError(err);\n }\n }\n return out;\n}\n\n/** Emit both the bus event and the `onMessage` callback in one shot. */\nfunction emitMessage(bus: EventBus, options: ResolvedOptions, role: string, text: string): void {\n const payload = { role, text };\n bus.emit(\"message\", payload);\n options.onMessage?.(payload);\n}\n", "/**\n * Render-error boundary. Catches a throw anywhere in the widget's Preact tree\n * so a render crash degrades to a quiet fallback instead of white-screening the\n * host page \u2014 and routes the error to the widget's observability funnel (the\n * `error` event \u2192 hooks / `on(\"error\")` / `options.onError`, plus the logger).\n *\n * Error boundaries only catch *render* errors. Async errors (event handlers,\n * promises, the stream) are caught where they happen and reported through the\n * same `error` event \u2014 see `app.tsx` `handleSend`.\n */\n\nimport { Component, type ComponentChildren } from \"preact\";\nimport { logger } from \"@/core/logger\";\n\nconst log = logger.scope(\"error-boundary\");\n\ninterface Props {\n /** Report the caught error (\u2192 `error` event + `options.onError`). */\n onError: (error: unknown, componentStack?: string) => void;\n /** Shown after a crash. Defaults to nothing \u2014 the widget simply unmounts. */\n fallback?: ComponentChildren;\n children?: ComponentChildren;\n}\n\nexport class ErrorBoundary extends Component<Props, { crashed: boolean }> {\n override state = { crashed: false };\n\n override componentDidCatch(error: unknown, info: { componentStack?: string }): void {\n log.error(\"widget render crashed\", { error, componentStack: info?.componentStack });\n this.setState({ crashed: true });\n this.props.onError(error, info?.componentStack);\n }\n\n override render() {\n return this.state.crashed ? this.props.fallback : this.props.children;\n }\n}\n", "/**\n * Iframe-wrapped mount.\n *\n * Hosts the widget inside a same-origin iframe for strict CSS + JS isolation\n * beyond shadow DOM. Orthogonal to the `mode` option \u2014 any layout can opt\n * in via `mount({ mode, iframe: true })`:\n *\n * - **Floating** (`mode: \"floating\"`): iframe is `position: fixed` in the\n * host page, precisely sized to the current state (`data-mode` on the\n * inner host) so pointer events fall through to the page everywhere else.\n * `closed` = 88\u00D788 (FAB), `open` = normal panel dims, `expanded` = larger,\n * `fullscreen` = 100vw \u00D7 100vh.\n *\n * - **Inline** (`mode: \"inline\"`): iframe is `display: block` in the target\n * container, `width/height: 100%` \u2014 fills its parent like any other inline\n * element. Escapes to `position: fixed; inset: 0` when the user clicks\n * the fullscreen header button (data-mode flips to \"fullscreen\").\n *\n * - **Standalone** (`mode: \"standalone\"`): iframe is the full viewport \u2014\n * same as floating + fullscreen, never resizes.\n *\n * A MutationObserver watches `data-mode` on the inner host (written by\n * App's render-mode effect) and re-sizes the iframe on every transition.\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Mode } from \"@/core/config\";\nimport { logger } from \"@/core/logger\";\nimport { createShadowMount, type MountOptions, type ShadowMountResult } from \"./mount\";\n\nconst log = logger.scope(\"iframe\");\n\nconst IFRAME_SRCDOC =\n '<!doctype html><html><head><meta charset=\"utf-8\"><style>html,body{margin:0;padding:0;background:transparent;overflow:hidden}</style></head><body></body></html>';\n\n// Internal return shape \u2014 extends `ShadowMountResult` with the outer\n// iframe element so `index.ts` can reach it for cleanup. Not exported:\n// no caller outside this module consumes the type.\ninterface IframeMountResult extends ShadowMountResult {\n iframeElement: HTMLIFrameElement;\n}\n\nexport function createIframeMount(\n opts: Omit<MountOptions, \"target\">,\n position: string,\n initialMode = \"closed\",\n layoutMode: Mode = \"floating\",\n target: HTMLElement | null = null,\n): IframeMountResult {\n log.debug(\"createIframeMount\", { position, initialMode, layoutMode, hasTarget: !!target });\n const iframe = document.createElement(\"iframe\");\n iframe.title = `${BRAND.name} chat`;\n iframe.setAttribute(\"allow\", \"microphone; clipboard-write\");\n // Size the iframe at its *eventual* first-render dimensions, not a\n // generic \"closed\" fallback. A user reloading a page with the widget\n // previously open would otherwise see a 88\u00D788 iframe flash before\n // App's first effect corrects the size to the full panel.\n sizeIframe(iframe, position, initialMode, layoutMode);\n // Inline iframes go into the host-page target container (flow layout);\n // floating + standalone attach to `document.body` (position: fixed).\n const parent = layoutMode === \"inline\" && target ? target : document.body;\n parent.appendChild(iframe);\n\n const doc = iframe.contentDocument;\n if (!doc) throw new Error(\"iframe contentDocument unavailable (cross-origin?)\");\n doc.open();\n doc.write(IFRAME_SRCDOC);\n doc.close();\n\n // `createShadowMount` attaches the widget's stylesheet to the shadow\n // root using the host's `ownerDocument` \u2014 that's the iframe's document\n // here, so the sheet is constructed against the right realm. We\n // deliberately don't inject widget CSS into the iframe's `<head>`:\n // shadow DOM doesn't cascade from host-document styles, so a duplicate\n // up there would just be dead weight.\n const mount = createShadowMount({ ...opts, target: doc.body });\n\n const mo = new MutationObserver(() => {\n sizeIframe(iframe, position, mount.hostElement.dataset.mode ?? \"closed\", layoutMode);\n });\n mo.observe(mount.hostElement, { attributes: true, attributeFilter: [\"data-mode\"] });\n\n return {\n ...mount,\n iframeElement: iframe,\n destroy() {\n mo.disconnect();\n mount.destroy();\n iframe.remove();\n },\n };\n}\n\nconst BASE_CSS = \"border:0;background:transparent;z-index:2147483647;pointer-events:auto;\";\n\n/** Dimensions per `data-mode` in floating layouts. Fullscreen is special-cased. */\nconst FLOATING_SIZE_BY_MODE: Record<string, { w: string; h: string }> = {\n closed: { w: \"88px\", h: \"88px\" },\n expanded: { w: \"min(calc(100vw - 32px), 680px)\", h: \"min(calc(100vh - 32px), 860px)\" },\n // `open` (normal) \u2014 also the fallback for any other non-fullscreen state.\n open: { w: \"min(calc(100vw - 32px), 440px)\", h: \"min(calc(100vh - 32px), 700px)\" },\n};\n\n/**\n * Apply position + size inline CSS to the outer iframe.\n *\n * - Fullscreen (any layout) \u2192 `position: fixed; inset: 0; 100vw \u00D7 100vh`.\n * - Floating (`floating`) \u2192 `position: fixed` in a corner, size per data-mode.\n * - Inline (`inline`) \u2192 `display: block; width/height: 100%`, flows with target.\n * - Standalone \u2192 always fullscreen.\n */\nfunction sizeIframe(iframe: HTMLIFrameElement, position: string, mode: string, layoutMode: Mode): void {\n if (mode === \"fullscreen\" || layoutMode === \"standalone\") {\n iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;\n return;\n }\n if (layoutMode === \"inline\") {\n // In-flow iframe: fills its target container. No corner offsets;\n // the container's own layout (margin/padding/grid cell) positions it.\n iframe.style.cssText = `${BASE_CSS}position:static;display:block;width:100%;height:100%;`;\n return;\n }\n // Floating layout: fixed-position corner dock, size by data-mode.\n const vKey = position.startsWith(\"top-\") ? \"top\" : \"bottom\";\n const hKey = position.endsWith(\"-left\") ? \"left\" : \"right\";\n const { w, h } = FLOATING_SIZE_BY_MODE[mode] ?? FLOATING_SIZE_BY_MODE.open!;\n iframe.style.cssText = `${BASE_CSS}position:fixed;${vKey}:16px;${hKey}:16px;width:${w};height:${h};`;\n}\n", "/**\n * Tiny typed pub/sub for widget lifecycle events.\n *\n * Insulates UI components from the public callback shape, and gives us room\n * to add listeners (analytics, logging) without touching the UI.\n *\n * All listeners are isolated \u2014 a throw in one never breaks the widget or\n * prevents other listeners from firing.\n */\n\nimport type { ThemePreference } from \"@/core/config\";\nimport type { StartSessionResponse } from \"@/stream/protocol\";\n\n/**\n * Payload shape per event. Keyed by event name; use `void` for events that\n * carry no data.\n *\n * **Every user-visible action fires here.** Subscribe via\n * `chat.on(\"eventName\", fn)` to add analytics, side effects, or audit\n * trails without forking the widget. The same names also stream to the\n * structured logger at `info` level when `debug` is enabled.\n */\nexport interface EventMap {\n // \u2500\u2500 Panel lifecycle \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Panel opened (floating / modal / drawer). */\n open: void;\n /** Panel closed. */\n close: void;\n /** Expanded mode toggled (1.5\u00D7 size). Payload is the new state. */\n expand: boolean;\n /** Fullscreen mode toggled. Payload is the new state. */\n fullscreen: boolean;\n /** Widget was popped out into a standalone tab. */\n popOut: void;\n /** End-user dragged the resize grip \u2014 payload is the resolved size. */\n resize: { width: number; height: number };\n\n // \u2500\u2500 Conversation \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** A message was exchanged. Fires for both user and assistant messages. */\n message: { role: string; text: string };\n /** User submitted a message (fires before the request is sent). */\n send: { text: string; attachmentCount: number };\n /** User cancelled an in-flight stream. */\n stop: void;\n /** Clear / new-conversation requested. */\n clear: void;\n /** User picked a chat from the history pane / sidebar. */\n selectChat: { sessionId: string };\n /** User clicked a suggested-message chip. */\n suggestion: { text: string };\n /** History pane toggled. Payload is the resulting view. */\n toggleHistory: { view: \"chat\" | \"history\" };\n\n // \u2500\u2500 Composer / attachments / voice \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 /** Files attached (drag-drop, file picker, paste). */\n attach: { count: number; totalBytes: number };\n /** An attachment was removed before send. */\n removeAttachment: { id: string };\n /** Voice recording started. */\n voiceStart: void;\n /** Voice recording stopped and was sent for transcription. */\n voiceStop: { durationMs: number };\n /** Voice recording cancelled (e.g. user released outside the button). */\n voiceCancel: void;\n\n // \u2500\u2500 Preferences \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** End-user picked a different UI locale. Payload is the new BCP-47 tag. */\n localeChange: string;\n /** End-user picked a different theme mode (`auto` / `light` / `dark`). */\n themeChange: ThemePreference;\n /** End-user toggled the global sound mute. */\n soundToggle: { muted: boolean };\n /** Page-mode sidebar collapsed / expanded. */\n sidebarToggle: { collapsed: boolean };\n /** Pre-engagement launcher callout dismissed. */\n calloutDismiss: void;\n\n // \u2500\u2500 Lifecycle / errors \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** An unhandled error occurred somewhere in the transport or UI. */\n error: unknown;\n /** Connect bootstrap succeeded. Payload is the typed server response. */\n sessionStarted: StartSessionResponse;\n /**\n * Server rebound the `visitorId` \u2014 e.g. the client-minted id\n * conflicted with an existing record, was expired, or otherwise\n * rejected, and the server returned a fresh one in the\n * `/start-session` response. The widget has already adopted the new\n * id and persisted it; this event is informational so host pages\n * can re-link analytics / external systems that pinned the\n * previous value.\n *\n * `reason` is the server's explanation, when provided\n * (`rebind.visitorId` in the start-session response). Common values:\n * `\"conflict\"` / `\"expired\"` / `\"invalid\"`. May be `undefined` if\n * the server didn't include a `rebind` block.\n */\n visitorRebound: { previous: string; current: string; reason?: string };\n /**\n * Server rebound the `sessionId` \u2014 same shape and semantics as\n * `visitorRebound` but for the conversation-thread id. Fires when\n * the server's response carries a different `sessionId` than the\n * client sent.\n */\n sessionRebound: { previous: string; current: string; reason?: string };\n}\n\nexport type EventName = keyof EventMap;\ntype Handler<E extends EventName> = (payload: EventMap[E]) => void;\n\n/**\n * Minimal event bus used by `ChatWidgetInstance`.\n *\n * Memory model: listeners are held in a `Set` keyed by event name. `on()`\n * returns an `off` function; callers must retain it to unsubscribe. A single\n * `clear()` drops all listeners (called on instance `destroy()`).\n */\nexport class EventBus {\n private handlers = new Map<EventName, Set<Handler<EventName>>>();\n\n /**\n * Register a listener for `name`.\n * @returns an `off` function that removes this listener.\n */\n on<E extends EventName>(name: E, fn: Handler<E>): () => void {\n let set = this.handlers.get(name);\n if (!set) {\n set = new Set();\n this.handlers.set(name, set);\n }\n set.add(fn as Handler<EventName>);\n // Look up the set on unsubscribe so post-`clear()` `off()` calls\n // don't mutate a detached set (the invariant the bus advertises:\n // \"safe to call after destroy\").\n return () => this.handlers.get(name)?.delete(fn as Handler<EventName>);\n }\n\n /**\n * Dispatch an event synchronously to all listeners.\n * Listener errors are swallowed \u2014 the widget must not crash on user callbacks.\n */\n emit<E extends EventName>(name: E, payload: EventMap[E]): void {\n const set = this.handlers.get(name);\n if (!set) return;\n for (const fn of set) {\n try {\n (fn as Handler<E>)(payload);\n } catch {\n // Listener errors must never break the widget.\n }\n }\n }\n\n /** Remove every listener. Called by the instance `destroy()`. */\n clear(): void {\n this.handlers.clear();\n }\n}\n", "/**\n * Global lifecycle-hook registry.\n *\n * Per-instance events (`open`, `close`, `message`, \u2026) are already delivered\n * to subscribers via each widget's {@link EventBus}. That works well for\n * callers who hold a direct handle to the instance \u2014 but not for the\n * zero-JS `<script data-*>` embed path, and not for analytics wiring that\n * wants to hear from *every* widget without knowing their ids in advance.\n *\n * This module plugs that gap. Hooks are registered against a widgetId\n * (exact or `\"*\"`) and an event name (exact or `\"*\"`), and fire with a\n * unified signature \u2014 `(instance, event, data)` \u2014 so a single callback can\n * handle any event on any widget:\n *\n * ```ts\n * HelpAI.hook(\"*\", \"*\", (inst, event, data) => analytics.track(event, { widget: inst.widgetId }));\n * HelpAI.hook(\"support\", \"message\", (inst, _event, data) => log(`[${inst.widgetId}] ${data.role}: ${data.text}`));\n * ```\n *\n * Registration order is irrelevant \u2014 hooks may be added before the bundle\n * loads (via the pre-boot queue) or after any number of instances are\n * already mounted. Live instances are subscribed retroactively.\n *\n * Listeners are isolated: a throw in one never breaks the widget or\n * prevents other listeners from firing. Same contract as the per-instance\n * bus.\n */\n\nimport type { ChatWidgetInstance } from \"..\";\nimport { EventBus, type EventMap, type EventName } from \"./events\";\nimport { logger } from \"./logger\";\n\nconst log = logger.scope(\"hooks\");\n\n/** Wildcard that matches every widget id / every event name. */\nexport const HOOK_WILDCARD = \"*\";\n\n/**\n * Unified lifecycle callback signature. `data` is `unknown` \u2014 narrow it\n * via the `event` discriminator using {@link EventMap} if needed:\n *\n * ```ts\n * HelpAI.hook(\"*\", \"*\", (inst, event, data) => {\n * if (event === \"message\") {\n * const msg = data as EventMap[\"message\"]; // { role, text }\n * // ...\n * }\n * });\n * ```\n */\nexport type HookListener = (instance: ChatWidgetInstance, event: EventName, data: unknown) => void;\n\ninterface HookEntry {\n widgetId: string;\n event: EventName | typeof HOOK_WILDCARD;\n listener: HookListener;\n}\n\n// Module-scope because there is one global registry per page \u2014 the whole\n// point is to wire listeners registered before any particular instance\n// exists. Tests reset via `__resetHooksForTest`.\nconst entries = new Set<HookEntry>();\n\n/**\n * Register a listener that fires for every matching (widgetId, event) pair.\n * Pass `\"*\"` for either to match all.\n *\n * Matching live instances are subscribed immediately \u2014 so listeners added\n * after a widget has already emitted its boot events still receive the\n * next round. Returns an `off` function; call it to unsubscribe.\n */\nexport function registerHook(\n widgetId: string,\n event: EventName | typeof HOOK_WILDCARD,\n listener: HookListener,\n): () => void {\n const entry: HookEntry = { widgetId, event, listener };\n entries.add(entry);\n return () => {\n entries.delete(entry);\n };\n}\n\n/**\n * Called from `mount()` for each event a new instance fires. Walks the\n * entries set and invokes matching hooks with the unified signature.\n *\n * Kept internal: `mount()` forwards every event through `dispatchHook` by\n * wiring one bus subscription per known event name (see `subscribeInstance`).\n */\nexport function dispatchHook(instance: ChatWidgetInstance, event: EventName, data: unknown): void {\n // Iterate a snapshot \u2014 a listener that registers a new hook during\n // dispatch must not re-enter this dispatch and receive its own event.\n const snapshot = [...entries];\n for (const entry of snapshot) {\n if (!matchesId(entry.widgetId, instance.widgetId)) continue;\n if (entry.event !== HOOK_WILDCARD && entry.event !== event) continue;\n try {\n entry.listener(instance, event, data);\n } catch (error) {\n // Listener errors must never break the widget. Same contract as EventBus.\n log.warn(\"hook listener threw\", { event, error });\n }\n }\n}\n\n/**\n * Wire a newly-mounted instance into the global hook bus.\n *\n * Subscribes every known event name on the instance's bus \u2014 each emit\n * delegates to {@link dispatchHook} so registered hooks receive it with\n * the unified `(instance, event, data)` signature. Returns a cleanup\n * function that the instance's `destroy()` must invoke.\n */\nexport function subscribeInstance(instance: ChatWidgetInstance, bus: EventBus): () => void {\n const unsubs: Array<() => void> = [];\n for (const name of EVENT_NAMES) {\n unsubs.push(\n bus.on(name, (payload) => {\n dispatchHook(instance, name, payload);\n }),\n );\n }\n return () => {\n for (const off of unsubs) off();\n };\n}\n\n/**\n * Replay pre-boot hook registrations pushed onto `window.<Brand>Hooks` by\n * the loader snippet before the bundle arrived. Each entry is a\n * `[widgetId, event, listener]` tuple or a rich `{ widgetId, event,\n * listener }` object \u2014 both forms are accepted to match the loader\n * pattern's existing shape.\n */\nexport function drainPreBootQueue(queue: unknown): void {\n if (!Array.isArray(queue)) return;\n for (const entry of queue) {\n const parsed = parsePreBootEntry(entry);\n if (!parsed) continue;\n registerHook(parsed.widgetId, parsed.event, parsed.listener);\n }\n}\n\n/** @internal \u2014 test hook. Clear every registered listener. */\nexport function __resetHooksForTest(): void {\n entries.clear();\n}\n\n/** @internal \u2014 test hook. How many listeners are live right now. */\nexport function __hookCountForTest(): number {\n return entries.size;\n}\n\n/** Exact match OR wildcard match for the widget-id side. */\nfunction matchesId(registered: string, actual: string): boolean {\n return registered === HOOK_WILDCARD || registered === actual;\n}\n\n/**\n * Build-time-enumerable list of every event name so we can wire one\n * bus-subscription per event in `subscribeInstance`. Kept here (not on\n * `EventMap`) because TS types are erased at runtime \u2014 the widget needs\n * a concrete array to iterate.\n *\n * The trailing exhaustiveness check fails compilation if a new event is\n * added to {@link EventMap} but forgotten here \u2014 `satisfies readonly\n * EventName[]` alone permits omissions, which previously silently\n * dropped `visitorRebound` / `sessionRebound` from `*`-hook delivery.\n */\nexport const EVENT_NAMES = [\n // Panel lifecycle\n \"open\",\n \"close\",\n \"expand\",\n \"fullscreen\",\n \"popOut\",\n \"resize\",\n // Conversation\n \"message\",\n \"send\",\n \"stop\",\n \"clear\",\n \"selectChat\",\n \"suggestion\",\n \"toggleHistory\",\n // Composer / attachments / voice\n \"attach\",\n \"removeAttachment\",\n \"voiceStart\",\n \"voiceStop\",\n \"voiceCancel\",\n // Preferences\n \"localeChange\",\n \"themeChange\",\n \"soundToggle\",\n \"sidebarToggle\",\n \"calloutDismiss\",\n // Lifecycle / errors\n \"error\",\n \"sessionStarted\",\n \"visitorRebound\",\n \"sessionRebound\",\n] as const satisfies readonly EventName[];\n\ntype MissingEventNames = Exclude<EventName, (typeof EVENT_NAMES)[number]>;\n// If this fails to typecheck, an `EventMap` member is missing from `EVENT_NAMES` above.\nconst eventNamesExhaustive: MissingEventNames extends never ? true : never = true;\nvoid eventNamesExhaustive;\n\n/** Accept either a `[widgetId, event, listener]` tuple or a rich object. */\nfunction parsePreBootEntry(entry: unknown): HookEntry | null {\n let widgetId: unknown, event: unknown, listener: unknown;\n if (Array.isArray(entry)) {\n [widgetId, event, listener] = entry;\n } else if (entry && typeof entry === \"object\") {\n ({ widgetId, event, listener } = entry as Record<string, unknown>);\n } else {\n return null;\n }\n if (typeof widgetId !== \"string\" || typeof event !== \"string\" || typeof listener !== \"function\") return null;\n return { widgetId, event: event as EventName | typeof HOOK_WILDCARD, listener: listener as HookListener };\n}\n\n// Re-export for convenience \u2014 consumers importing from `core/hooks` get\n// the event-map types without needing a second import.\nexport type { EventMap, EventName };\n", "/**\n * Public ESM entry for `@<brand>/elements`.\n *\n * This module is side-effect free \u2014 importing it does not mount anything or\n * touch the DOM. Call {@link mount} or {@link init} to render the widget.\n *\n * The Web Component (`<{brand}-chat>`) lives in a separate sub-entry\n * (`./web-component`) so tree-shakers can drop the custom-element machinery\n * when it isn't used.\n *\n * @example\n * ```ts\n * import { mount } from '@helpai/elements'; // or '@goai/elements' for go.ai\n *\n * const chat = mount({\n * publicKey: 'agent_pk_...',\n * mode: 'inline',\n * target: document.getElementById('chat'),\n * });\n *\n * chat.on('message', (m) => console.log(m));\n * ```\n *\n * @packageDocumentation\n */\n\nimport { h, render } from \"preact\";\nimport { App } from \"./ui/app\";\nimport { ErrorBoundary } from \"./ui/error-boundary\";\nimport { BRAND } from \"./core/brand\";\nimport {\n type ChatWidgetOptions,\n mergeServerConfig,\n type PageContext,\n type ResolvedOptions,\n resolveOptions,\n type UserContext,\n} from \"./core/config\";\nimport { dispatchHostCommand } from \"./core/host-commands\";\nimport { createPersistence } from \"./core/persistence\";\nimport { getLogLevel, type LogEntry, type LogLevel, logger, setLogLevel, setLogSink } from \"./core/logger\";\nimport {\n applyHostAttributes,\n applyHostPosition,\n applyMode,\n createShadowMount,\n hostPositionForMode,\n} from \"./shadow/mount\";\nimport { createIframeMount } from \"./shadow/iframe-mount\";\nimport { EventBus, type EventMap, type EventName } from \"./core/events\";\nimport {\n HOOK_WILDCARD,\n type HookListener,\n registerHook,\n subscribeInstance as subscribeInstanceHooks,\n} from \"./core/hooks\";\nimport { computeHostMode, currentViewportWidth, type HostMode, resolveInitialPanelState } from \"./ui/host-mode\";\n\nexport type { ChatWidgetOptions, ResolvedOptions } from \"./core/config\";\nexport type { EventMap, EventName } from \"./core/events\";\nexport type { LogEntry, LogLevel } from \"./core/logger\";\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Canonical schema types (runtime values live on \"./schema\" subpath) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// We export ONLY types from the package main. Runtime Zod schema values\n// (e.g. `widgetConfigSchema`, `startSessionResponseSchema`) are exported\n// from the dedicated `./schema` subpath so the IIFE / auto-boot bundle\n// doesn't pay for the ~67KB Zod runtime. Admin / dashboard / 3rd-party\n// tooling that needs runtime validation:\n// `import { widgetConfigSchema } from \"@your-org/web-help-ai-elements/schema\"`.\nexport type {\n Asset,\n BlocksConfig,\n ConnectionConfig,\n ConnectionConfigPartial,\n StartSessionResponse,\n Link,\n ServerConfig,\n SiteConfig,\n WidgetConfig,\n WidgetConfigPartial,\n WidgetSettings,\n WidgetSettingsPartial,\n} from \"./schema\";\n\n/**\n * Handle returned by {@link mount} / {@link init}. All methods are safe to call\n * after `destroy()` \u2014 they become no-ops rather than throwing.\n */\nexport interface ChatWidgetInstance {\n /** Stable identifier \u2014 matches `options.widgetId ?? \"default\"`. */\n readonly widgetId: string;\n /** Open the floating panel (no-op in `inline` / `standalone` mode). */\n open: () => void;\n /** Close the floating panel (no-op in `inline` / `standalone` mode). */\n close: () => void;\n /** Unmount the widget and release all subscriptions. */\n destroy: () => void;\n /** Toggle fullscreen mode. In `standalone` mode this is a no-op. */\n expand: () => void;\n /**\n * Open the widget in a new browser tab at `behavior.popOutUrl`\n * (mount option / start-session-pushable), or the brand's\n * `BRAND.standaloneUrl` build-time default. Carries the active\n * `chat` + `widgetId` as query params. No-ops when the resolved\n * URL is empty.\n */\n popOut: () => void;\n /**\n * Subscribe to a lifecycle event. Returns an `off` function. See\n * {@link EventMap} for the full set + payload shapes \u2014 currently\n * `open | close | message | error | expand | fullscreen | popOut |\n * sessionStarted | localeChange`.\n */\n on: <E extends EventName>(name: E, fn: (payload: EventMap[E]) => void) => () => void;\n /**\n * Merge new options into the running instance without a full remount.\n * Useful for theme toggles or other live config tweaks.\n */\n update: (patch: Partial<ChatWidgetOptions>) => void;\n /**\n * Set / merge the host-asserted end-user context (`id`, `email`, `name`, plus\n * any custom scalar fields). Informational only \u2014 not auth. Merges into the\n * current context, refreshes the Home greeting, and re-handshakes so the\n * agent gets it. Call this after the visitor logs in. Untrusted input is\n * sanitised; bad fields are dropped with a `warn`.\n *\n * @example\n * ```ts\n * getInstance().setUserContext({ id: \"u_1\", name: \"Mohamed\", email: \"mo@acme.com\", plan: \"pro\" });\n * ```\n */\n setUserContext: (userContext: UserContext) => void;\n /** Clear the end-user context (e.g. on logout) and re-start-session anonymously. */\n clearUserContext: () => void;\n /**\n * Set / merge the host-asserted page/visit context (`area`, `url`, `path`,\n * `title`, plus custom scalar fields). Use it to tell the agent which surface\n * the visitor is on (e.g. `area: \"pricing\"`) and update it on SPA navigation.\n * Merges into the current context and re-handshakes.\n *\n * @example\n * ```ts\n * getInstance().setPageContext({ area: \"pricing\", path: \"/pricing/enterprise\" });\n * ```\n */\n setPageContext: (pageContext: PageContext) => void;\n /** Clear the page/visit context and re-start-session. */\n clearPageContext: () => void;\n /**\n * Set the runtime log level. Accepts `\"off\"` / `\"error\"` / `\"warn\"` /\n * `\"info\"` / `\"debug\"` / `\"trace\"`, or `true` (\u2192 `\"debug\"`) /\n * `false` (\u2192 `\"off\"`), or a number 0..5. Persists to localStorage so\n * a page refresh keeps the same verbosity \u2014 useful for asking a\n * customer to reproduce a bug and capture the console transcript.\n *\n * Affects every widget on the page (logger is module-global; setting\n * it from one instance applies everywhere). Equivalent to calling\n * `HelpAI.setDebug(level)` on the brand global.\n */\n setDebug: (level: LogLevel | boolean | number | string) => void;\n /** Current log level. Read-only. */\n readonly debugLevel: () => LogLevel;\n /**\n * Forward every log line (that passes the active level) to an external sink \u2014\n * pipe errors/warnings/debug into Sentry, Datadog, your own logger, etc. Pass\n * `null` to clear. One global sink per bundle (logger is module-global); use\n * `setDebug(\"trace\")` to capture everything, not just warnings + errors.\n */\n onLog: (sink: ((entry: LogEntry) => void) | null) => void;\n}\n\n/**\n * Registry of live widget instances, keyed by `widgetId`. Calling `mount()`\n * with a `widgetId` that's already in the registry destroys the old instance\n * first. Different `widgetId`s coexist on the same page \u2014 each with its own\n * shadow host, its own client-storage namespace (visitorId + active sessionId\n * + cached userPrefs), and its own `FeedbackBus` / `AgentTransport`.\n *\n * The `\"default\"` id is used when no `widgetId` is supplied \u2014 keeps the\n * zero-JS `<script data-public-key>` embed working as before.\n */\nconst instances = new Map<string, ChatWidgetInstance>();\n\n/**\n * Mount a new widget. If an instance with the same `widgetId` already\n * exists, that one is destroyed first (a reload of the same \"slot\").\n *\n * @example Single widget (default):\n * ```ts\n * mount({ publicKey, mode: 'inline', target: document.getElementById('chat') });\n * ```\n * @example Two widgets on one page:\n * ```ts\n * mount({ widgetId: 'support', publicKey: KEY_A, target: supportEl });\n * mount({ widgetId: 'sales', publicKey: KEY_B, target: salesEl });\n * ```\n */\nexport function mount(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance {\n // Set the log level FIRST, before anything else logs. Customer\n // `debug` passes through `parseLevel`: `true` \u2192 \"debug\", a level\n // string (\"trace\" / \"info\" / \u2026) used verbatim, `false` / \"off\"\n // silences. URL query + persisted localStorage already took effect\n // at module load (see `resolveInitialLevel` in `core/logger.ts`);\n // explicit `options.debug` overrides them so a `mount({ debug:\n // \"trace\" })` call wins for this mount even on machines that had\n // a lower level persisted.\n if (opts.debug !== undefined) setLogLevel(opts.debug);\n\n const log = logger.scope(\"mount\");\n log.info(\"mount\", { widgetId: opts.widgetId, publicKey: opts.publicKey, mode: opts.presentation?.mode });\n\n let rawOptions: ChatWidgetOptions = opts;\n let currentOptions = resolveOptions(rawOptions);\n const widgetId = currentOptions.widgetId;\n\n // Replace any prior instance at the same slot.\n const existing = instances.get(widgetId);\n if (existing) {\n log.debug(\"destroy existing instance at slot\", { widgetId });\n existing.destroy();\n }\n\n // Compute the host mode the App's first render will produce *before*\n // we create the iframe / host \u2014 so `createIframeMount` can size the\n // iframe correctly and `applyMode` below sets `data-mode` to a value\n // that matches the first paint, avoiding a visible closed\u2192open\u2192closed\n // flicker on refresh (especially in iframe mode where the iframe itself\n // has to physically resize when data-mode changes).\n const initialHostMode = resolveInitialHostMode(currentOptions);\n\n // The iframe wrapper is orthogonal to the layout mode \u2014 any mode can\n // opt into it for strict JS + CSS isolation. `floating` + iframe is the\n // classic floating-FAB-in-iframe. `inline` + iframe is an in-flow chat\n // block whose JS runs in the iframe's realm (can't have its `window.*`\n // stomped by the host page).\n // Pick the host-element positioning strategy from the resolved mode.\n // See `hostPositionForMode` in `shadow/mount.ts` for the table.\n // Re-applied in the `sessionStarted` handler below so server-pushed mode\n // changes refresh the host's inline style.\n const mountResult = currentOptions.iframe\n ? createIframeMount(\n { position: hostPositionForMode(currentOptions.mode) },\n currentOptions.position,\n initialHostMode,\n currentOptions.mode,\n opts.target ?? null,\n )\n : createShadowMount({\n position: hostPositionForMode(currentOptions.mode),\n target: opts.target ?? null,\n });\n // App's useState resolves activeThemeMode as `cachedSettings.themeMode ??\n // options.themeMode`. Mirror that precedence on every host-attribute\n // write so the user's persisted pick survives both the initial paint\n // AND the `sessionStarted` server-settings merge \u2014 otherwise there's a\n // window between the synchronous `applyHostAttributes` write\n // (`options.themeMode`, e.g. \"auto\") and App's effect (cached pick,\n // e.g. \"light\") where the attribute is wrong. WebKit's effect\n // scheduling exposes that race to Playwright reads.\n const persistence = createPersistence(currentOptions.widgetId, currentOptions.storage);\n const applyResolvedHostAttributes = () => {\n const cachedThemeMode = persistence.loadUserPrefs().themeMode;\n applyHostAttributes(\n mountResult.hostElement,\n cachedThemeMode ? { ...currentOptions, themeMode: cachedThemeMode } : currentOptions,\n );\n };\n applyResolvedHostAttributes();\n applyMode(mountResult.hostElement, initialHostMode);\n mountResult.hostElement.dataset.widgetId = widgetId;\n\n const bus = new EventBus();\n\n // Funnel for any caught error \u2014 render crashes (ErrorBoundary), the stream,\n // and async handlers all converge here: the `error` event (\u2192 hooks /\n // `on(\"error\")`) plus the per-instance `onError` callback.\n const reportError = (error: unknown) => {\n bus.emit(\"error\", error);\n currentOptions.onError?.(error);\n };\n\n const renderApp = () => {\n render(\n h(\n ErrorBoundary,\n { onError: reportError },\n h(App, { bus, hostElement: mountResult.hostElement, options: currentOptions }),\n ),\n mountResult.appRoot,\n );\n };\n renderApp();\n\n // Server-driven settings arrive via `sessionStarted` event from the App's\n // connect() handler. User options still win per-field.\n bus.on(\"sessionStarted\", (response) => {\n if (!response.config) return;\n rawOptions = mergeServerConfig(rawOptions, response.config);\n currentOptions = resolveOptions(rawOptions);\n // Re-apply the host positioning style so a server-pushed mode\n // change (e.g. `floating` \u2192 `standalone`) re-styles the host's\n // inline `position` / `inset` / `width` / `height`. Without this\n // refresh the mount-time choice (typically the resolver default\n // `\"floating\"`) sticks and the widget never claims the viewport.\n applyHostPosition(mountResult.hostElement, hostPositionForMode(currentOptions.mode));\n applyResolvedHostAttributes();\n renderApp();\n });\n\n const host = mountResult.hostElement;\n // `unsubscribeHooks` is set after the instance handle is constructed so\n // hook listeners can reference the fully-populated handle. Declared here\n // to be in scope for `destroy()`.\n let unsubscribeHooks: (() => void) | null = null;\n\n // Merge a patch into the running options and re-render. `debug` is a\n // runtime-only knob applied eagerly. App re-renders with the new options;\n // a host-`user` change additionally triggers a re-start-session inside App.\n const applyUpdate = (patch: Partial<ChatWidgetOptions>) => {\n if (patch.debug !== undefined) setLogLevel(patch.debug);\n rawOptions = { ...rawOptions, ...patch };\n currentOptions = resolveOptions(rawOptions);\n applyResolvedHostAttributes();\n renderApp();\n };\n\n const instance: ChatWidgetInstance = {\n widgetId,\n open: () => dispatchHostCommand(host, \"open\"),\n close: () => dispatchHostCommand(host, \"close\"),\n expand: () => dispatchHostCommand(host, \"expand\"),\n popOut: () => dispatchHostCommand(host, \"popout\"),\n on: (name, fn) => bus.on(name, fn),\n update: (patch) => {\n log.debug(\"update\", { patch });\n applyUpdate(patch);\n },\n // Host-asserted context (NOT our visitor/session \u2014 those are minted +\n // persisted by the widget and untouched here). Merge into the current\n // context; App re-handshakes with the SAME visitor/session.\n setUserContext: (userContext) => {\n log.debug(\"setUserContext\", { keys: Object.keys(userContext) });\n applyUpdate({ userContext: { ...rawOptions.userContext, ...userContext } });\n },\n clearUserContext: () => {\n log.debug(\"clearUserContext\");\n applyUpdate({ userContext: undefined });\n },\n setPageContext: (pageContext) => {\n log.debug(\"setPageContext\", { keys: Object.keys(pageContext) });\n applyUpdate({ pageContext: { ...rawOptions.pageContext, ...pageContext } });\n },\n clearPageContext: () => {\n log.debug(\"clearPageContext\");\n applyUpdate({ pageContext: undefined });\n },\n setDebug: (level) => {\n setLogLevel(level);\n log.info(\"debug level set\", { level: getLogLevel() });\n },\n debugLevel: () => getLogLevel(),\n onLog: (logSink) => setLogSink(logSink),\n destroy: () => {\n log.info(\"destroy\", { widgetId });\n unsubscribeHooks?.();\n bus.clear();\n render(null, mountResult.appRoot);\n mountResult.destroy();\n if (instances.get(widgetId) === instance) instances.delete(widgetId);\n },\n };\n\n // Subscribe *after* building the handle so hook listeners can receive\n // the fully-populated `ChatWidgetInstance` (e.g. `instance.open()` must\n // be callable from within a `sessionStarted` hook).\n unsubscribeHooks = subscribeInstanceHooks(instance, bus);\n\n instances.set(widgetId, instance);\n return instance;\n}\n\n/**\n * Idempotent init per `widgetId`. If an instance exists at that slot, its\n * options are merged (via {@link ChatWidgetInstance.update}). Otherwise it\n * behaves like {@link mount}. Safe to call on every route change in an SPA.\n */\nexport function init(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance {\n const widgetId = opts.widgetId ?? \"default\";\n const existing = instances.get(widgetId);\n if (existing) {\n existing.update(opts);\n return existing;\n }\n return mount(opts);\n}\n\n/**\n * Destroy a specific instance by `widgetId`, or \u2014 when no id is supplied \u2014\n * every live widget on the page.\n */\nexport function destroy(widgetId?: string): void {\n if (widgetId !== undefined) {\n instances.get(widgetId)?.destroy();\n return;\n }\n // Snapshot first \u2014 `destroy()` mutates the registry via its own cleanup.\n const live = [...instances.values()];\n for (const inst of live) inst.destroy();\n}\n\n/**\n * Look up a live instance by `widgetId` (default: `\"default\"`). Returns\n * `null` if nothing is mounted at that slot.\n */\nexport function getInstance(widgetId = \"default\"): ChatWidgetInstance | null {\n return instances.get(widgetId) ?? null;\n}\n\n/** All live widget instances on the page. */\nexport function getAllInstances(): ChatWidgetInstance[] {\n return [...instances.values()];\n}\n\n// ---- Global lifecycle hooks ---------------------------------------------\n\nexport type { HookListener };\nexport { HOOK_WILDCARD };\n\n/**\n * Register a lifecycle hook that fires for every matching\n * `(widgetId, event)` pair across all live widgets.\n *\n * Use `\"*\"` for either argument (or the exported `HOOK_WILDCARD`) to match\n * everything. Listeners receive a unified signature \u2014\n * `(instance, event, data)` \u2014 so one callback can react to any event on\n * any widget without branching by type.\n *\n * @example\n * ```ts\n * // Audit every message exchanged in any widget.\n * HelpAI.hook(\"*\", \"message\", (inst, _event, data) => analytics.track({\n * widget: inst.widgetId,\n * role: (data as EventMap[\"message\"]).role,\n * }));\n *\n * // Fire once per open on the \"support\" widget only.\n * HelpAI.hook(\"support\", \"open\", (inst) => mixpanel.track(\"chat_opened\", { id: inst.widgetId }));\n * ```\n *\n * Returns an `off` function \u2014 call it to unsubscribe. Hooks are held in a\n * module-scope registry, so they persist across `mount()` / `destroy()`\n * cycles until explicitly unregistered.\n */\nexport function hook(widgetId: string, event: string, listener: HookListener): () => void {\n return registerHook(widgetId, event as Parameters<typeof registerHook>[1], listener);\n}\n\n/** Semver of the published package. */\nexport const version = \"0.1.0\";\n\n/** Compile-time brand constants inlined at build time (name, CDN URL, theme). */\nexport const brand = BRAND;\n\n/**\n * Determine what `data-mode` the host should start at \u2014 matching what\n * App's mount-time `useEffect` will produce on its first render. Exposed\n * only for the initial paint path in `mount()` (and the test suite);\n * after that, App owns `data-mode` for the lifetime of the instance.\n */\nexport function resolveInitialHostMode(options: ResolvedOptions): HostMode {\n if (options.mode === \"standalone\") return \"standalone\";\n if (options.mode === \"inline\") return \"inline\";\n // Floating modes (`floating` / `iframe`). `resolveInitialPanelState` is\n // the one source of truth for \"what does the first render look like\" \u2014\n // App.tsx uses it too. If these two paths ever pick different initial\n // states, the iframe physically resizes between mount and App's first\n // effect (the flicker bug).\n const persistence = createPersistence(options.widgetId, options.storage);\n const { panelOpen, panelSize } = resolveInitialPanelState(\n options,\n currentViewportWidth(),\n persistence.loadPanelOpen(),\n persistence.loadPanelSize(),\n );\n return computeHostMode(options.mode, panelSize, panelOpen);\n}\n"],
|
|
4
|
+
"sourcesContent": ["/** Normal hydration that attaches to a DOM tree but does not diff it. */\nexport const MODE_HYDRATE = 1 << 5;\n/** Signifies this VNode suspended on the previous render */\nexport const MODE_SUSPENDED = 1 << 7;\n/** Indicates that this node needs to be inserted while patching children */\nexport const INSERT_VNODE = 1 << 2;\n/** Indicates a VNode has been matched with another VNode in the diff */\nexport const MATCHED = 1 << 1;\n\n/** Reset all mode flags */\nexport const RESET_MODE = ~(MODE_HYDRATE | MODE_SUSPENDED);\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\nexport const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\nexport const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n\nexport const NULL = null;\nexport const UNDEFINED = undefined;\nexport const EMPTY_OBJ = /** @type {any} */ ({});\nexport const EMPTY_ARR = [];\nexport const IS_NON_DIMENSIONAL =\n\t/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;\n", "import { EMPTY_ARR } from './constants';\n\nexport const isArray = Array.isArray;\n\n/**\n * Assign properties from `props` to `obj`\n * @template O, P The obj and props types\n * @param {O} obj The object to copy properties to\n * @param {P} props The object to copy properties from\n * @returns {O & P}\n */\nexport function assign(obj, props) {\n\t// @ts-expect-error We change the type of `obj` to be `O & P`\n\tfor (let i in props) obj[i] = props[i];\n\treturn /** @type {O & P} */ (obj);\n}\n\n/**\n * Remove a child node from its parent if attached. This is a workaround for\n * IE11 which doesn't support `Element.prototype.remove()`. Using this function\n * is smaller than including a dedicated polyfill.\n * @param {import('./index').ContainerNode} node The node to remove\n */\nexport function removeNode(node) {\n\tif (node && node.parentNode) node.parentNode.removeChild(node);\n}\n\nexport const slice = EMPTY_ARR.slice;\n", "import { _catchError } from './diff/catch-error';\n\n/**\n * The `option` object can potentially contain callback functions\n * that are called during various stages of our renderer. This is the\n * foundation on which all our addons like `preact/debug`, `preact/compat`,\n * and `preact/hooks` are based on. See the `Options` type in `internal.d.ts`\n * for a full list of available option hooks (most editors/IDEs allow you to\n * ctrl+click or cmd+click on mac the type definition below).\n * @type {import('./internal').Options}\n */\nconst options = {\n\t_catchError\n};\n\nexport default options;\n", "import { slice } from './util';\nimport options from './options';\nimport { NULL, UNDEFINED } from './constants';\n\nlet vnodeId = 0;\n\n/**\n * Create an virtual node (used for JSX)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component constructor for this\n * virtual node\n * @param {object | null | undefined} [props] The properties of the virtual node\n * @param {Array<import('.').ComponentChildren>} [children] The children of the\n * virtual node\n * @returns {import('./internal').VNode}\n */\nexport function createElement(type, props, children) {\n\tlet normalizedProps = {},\n\t\tkey,\n\t\tref,\n\t\ti;\n\tfor (i in props) {\n\t\tif (i == 'key') key = props[i];\n\t\telse if (i == 'ref') ref = props[i];\n\t\telse normalizedProps[i] = props[i];\n\t}\n\n\tif (arguments.length > 2) {\n\t\tnormalizedProps.children =\n\t\t\targuments.length > 3 ? slice.call(arguments, 2) : children;\n\t}\n\n\t// If a Component VNode, check for and apply defaultProps\n\t// Note: type may be undefined in development, must never error here.\n\tif (typeof type == 'function' && type.defaultProps != NULL) {\n\t\tfor (i in type.defaultProps) {\n\t\t\tif (normalizedProps[i] === UNDEFINED) {\n\t\t\t\tnormalizedProps[i] = type.defaultProps[i];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn createVNode(type, normalizedProps, key, ref, NULL);\n}\n\n/**\n * Create a VNode (used internally by Preact)\n * @param {import('./internal').VNode[\"type\"]} type The node name or Component\n * Constructor for this virtual node\n * @param {object | string | number | null} props The properties of this virtual node.\n * If this virtual node represents a text node, this is the text of the node (string or number).\n * @param {string | number | null} key The key for this virtual node, used when\n * diffing it against its children\n * @param {import('./internal').VNode[\"ref\"]} ref The ref property that will\n * receive a reference to its created child\n * @returns {import('./internal').VNode}\n */\nexport function createVNode(type, props, key, ref, original) {\n\t// V8 seems to be better at detecting type shapes if the object is allocated from the same call site\n\t// Do not inline into createElement and coerceToVNode!\n\t/** @type {import('./internal').VNode} */\n\tconst vnode = {\n\t\ttype,\n\t\tprops,\n\t\tkey,\n\t\tref,\n\t\t_children: NULL,\n\t\t_parent: NULL,\n\t\t_depth: 0,\n\t\t_dom: NULL,\n\t\t_component: NULL,\n\t\tconstructor: UNDEFINED,\n\t\t_original: original == NULL ? ++vnodeId : original,\n\t\t_index: -1,\n\t\t_flags: 0\n\t};\n\n\t// Only invoke the vnode hook if this was *not* a direct copy:\n\tif (original == NULL && options.vnode != NULL) options.vnode(vnode);\n\n\treturn vnode;\n}\n\nexport function createRef() {\n\treturn { current: NULL };\n}\n\nexport function Fragment(props) {\n\treturn props.children;\n}\n\n/**\n * Check if a the argument is a valid Preact VNode.\n * @param {*} vnode\n * @returns {vnode is VNode}\n */\nexport const isValidElement = vnode =>\n\tvnode != NULL && vnode.constructor === UNDEFINED;\n", "import { assign } from './util';\nimport { diff, commitRoot } from './diff/index';\nimport options from './options';\nimport { Fragment } from './create-element';\nimport { MODE_HYDRATE, NULL } from './constants';\n\n/**\n * Base Component class. Provides `setState()` and `forceUpdate()`, which\n * trigger rendering\n * @param {object} props The initial component props\n * @param {object} context The initial context from parent components'\n * getChildContext\n */\nexport function BaseComponent(props, context) {\n\tthis.props = props;\n\tthis.context = context;\n}\n\n/**\n * Update component state and schedule a re-render.\n * @this {import('./internal').Component}\n * @param {object | ((s: object, p: object) => object)} update A hash of state\n * properties to update with new values or a function that given the current\n * state and props returns a new partial state\n * @param {() => void} [callback] A function to be called once component state is\n * updated\n */\nBaseComponent.prototype.setState = function (update, callback) {\n\t// only clone state when copying to nextState the first time.\n\tlet s;\n\tif (this._nextState != NULL && this._nextState != this.state) {\n\t\ts = this._nextState;\n\t} else {\n\t\ts = this._nextState = assign({}, this.state);\n\t}\n\n\tif (typeof update == 'function') {\n\t\t// Some libraries like `immer` mark the current state as readonly,\n\t\t// preventing us from mutating it, so we need to clone it. See #2716\n\t\tupdate = update(assign({}, s), this.props);\n\t}\n\n\tif (update) {\n\t\tassign(s, update);\n\t}\n\n\t// Skip update if updater function returned null\n\tif (update == NULL) return;\n\n\tif (this._vnode) {\n\t\tif (callback) {\n\t\t\tthis._stateCallbacks.push(callback);\n\t\t}\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Immediately perform a synchronous re-render of the component\n * @this {import('./internal').Component}\n * @param {() => void} [callback] A function to be called after component is\n * re-rendered\n */\nBaseComponent.prototype.forceUpdate = function (callback) {\n\tif (this._vnode) {\n\t\t// Set render mode so that we can differentiate where the render request\n\t\t// is coming from. We need this because forceUpdate should never call\n\t\t// shouldComponentUpdate\n\t\tthis._force = true;\n\t\tif (callback) this._renderCallbacks.push(callback);\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Accepts `props` and `state`, and returns a new Virtual DOM tree to build.\n * Virtual DOM is generally constructed via [JSX](https://jasonformat.com/wtf-is-jsx).\n * @param {object} props Props (eg: JSX attributes) received from parent\n * element/component\n * @param {object} state The component's current state\n * @param {object} context Context object, as returned by the nearest\n * ancestor's `getChildContext()`\n * @returns {ComponentChildren | void}\n */\nBaseComponent.prototype.render = Fragment;\n\n/**\n * @param {import('./internal').VNode} vnode\n * @param {number | null} [childIndex]\n */\nexport function getDomSibling(vnode, childIndex) {\n\tif (childIndex == NULL) {\n\t\t// Use childIndex==null as a signal to resume the search from the vnode's sibling\n\t\treturn vnode._parent\n\t\t\t? getDomSibling(vnode._parent, vnode._index + 1)\n\t\t\t: NULL;\n\t}\n\n\tlet sibling;\n\tfor (; childIndex < vnode._children.length; childIndex++) {\n\t\tsibling = vnode._children[childIndex];\n\n\t\tif (sibling != NULL && sibling._dom != NULL) {\n\t\t\t// Since updateParentDomPointers keeps _dom pointer correct,\n\t\t\t// we can rely on _dom to tell us if this subtree contains a\n\t\t\t// rendered DOM node, and what the first rendered DOM node is\n\t\t\treturn sibling._dom;\n\t\t}\n\t}\n\n\t// If we get here, we have not found a DOM node in this vnode's children.\n\t// We must resume from this vnode's sibling (in it's parent _children array)\n\t// Only climb up and search the parent if we aren't searching through a DOM\n\t// VNode (meaning we reached the DOM parent of the original vnode that began\n\t// the search)\n\treturn typeof vnode.type == 'function' ? getDomSibling(vnode) : NULL;\n}\n\n/**\n * Trigger in-place re-rendering of a component.\n * @param {import('./internal').Component} component The component to rerender\n */\nfunction renderComponent(component) {\n\tif (component._parentDom && component._dirty) {\n\t\tlet oldVNode = component._vnode,\n\t\t\toldDom = oldVNode._dom,\n\t\t\tcommitQueue = [],\n\t\t\trefQueue = [],\n\t\t\tnewVNode = assign({}, oldVNode);\n\t\tnewVNode._original = oldVNode._original + 1;\n\t\tif (options.vnode) options.vnode(newVNode);\n\n\t\tdiff(\n\t\t\tcomponent._parentDom,\n\t\t\tnewVNode,\n\t\t\toldVNode,\n\t\t\tcomponent._globalContext,\n\t\t\tcomponent._parentDom.namespaceURI,\n\t\t\toldVNode._flags & MODE_HYDRATE ? [oldDom] : NULL,\n\t\t\tcommitQueue,\n\t\t\toldDom == NULL ? getDomSibling(oldVNode) : oldDom,\n\t\t\t!!(oldVNode._flags & MODE_HYDRATE),\n\t\t\trefQueue\n\t\t);\n\n\t\tnewVNode._original = oldVNode._original;\n\t\tnewVNode._parent._children[newVNode._index] = newVNode;\n\t\tcommitRoot(commitQueue, newVNode, refQueue);\n\t\toldVNode._dom = oldVNode._parent = null;\n\n\t\tif (newVNode._dom != oldDom) {\n\t\t\tupdateParentDomPointers(newVNode);\n\t\t}\n\t}\n}\n\n/**\n * @param {import('./internal').VNode} vnode\n */\nfunction updateParentDomPointers(vnode) {\n\tif ((vnode = vnode._parent) != NULL && vnode._component != NULL) {\n\t\tvnode._dom = vnode._component.base = NULL;\n\t\tvnode._children.some(child => {\n\t\t\tif (child != NULL && child._dom != NULL) {\n\t\t\t\treturn (vnode._dom = vnode._component.base = child._dom);\n\t\t\t}\n\t\t});\n\n\t\treturn updateParentDomPointers(vnode);\n\t}\n}\n\n/**\n * The render queue\n * @type {Array<import('./internal').Component>}\n */\nlet rerenderQueue = [];\n\n/*\n * The value of `Component.debounce` must asynchronously invoke the passed in callback. It is\n * important that contributors to Preact can consistently reason about what calls to `setState`, etc.\n * do, and when their effects will be applied. See the links below for some further reading on designing\n * asynchronous APIs.\n * * [Designing APIs for Asynchrony](https://blog.izs.me/2013/08/designing-apis-for-asynchrony)\n * * [Callbacks synchronous and asynchronous](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)\n */\n\nlet prevDebounce;\n\nconst defer =\n\ttypeof Promise == 'function'\n\t\t? Promise.prototype.then.bind(Promise.resolve())\n\t\t: setTimeout;\n\n/**\n * Enqueue a rerender of a component\n * @param {import('./internal').Component} c The component to rerender\n */\nexport function enqueueRender(c) {\n\tif (\n\t\t(!c._dirty &&\n\t\t\t(c._dirty = true) &&\n\t\t\trerenderQueue.push(c) &&\n\t\t\t!process._rerenderCount++) ||\n\t\tprevDebounce != options.debounceRendering\n\t) {\n\t\tprevDebounce = options.debounceRendering;\n\t\t(prevDebounce || defer)(process);\n\t}\n}\n\n/**\n * @param {import('./internal').Component} a\n * @param {import('./internal').Component} b\n */\nconst depthSort = (a, b) => a._vnode._depth - b._vnode._depth;\n\n/** Flush the render queue by rerendering all queued components */\nfunction process() {\n\ttry {\n\t\tlet c,\n\t\t\tl = 1;\n\n\t\t// Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary\n\t\t// process() calls from getting scheduled while `queue` is still being consumed.\n\t\twhile (rerenderQueue.length) {\n\t\t\t// Keep the rerender queue sorted by (depth, insertion order). The queue\n\t\t\t// will initially be sorted on the first iteration only if it has more than 1 item.\n\t\t\t//\n\t\t\t// New items can be added to the queue e.g. when rerendering a provider, so we want to\n\t\t\t// keep the order from top to bottom with those new items so we can handle them in a\n\t\t\t// single pass\n\t\t\tif (rerenderQueue.length > l) {\n\t\t\t\trerenderQueue.sort(depthSort);\n\t\t\t}\n\n\t\t\tc = rerenderQueue.shift();\n\t\t\tl = rerenderQueue.length;\n\n\t\t\trenderComponent(c);\n\t\t}\n\t} finally {\n\t\trerenderQueue.length = process._rerenderCount = 0;\n\t}\n}\n\nprocess._rerenderCount = 0;\n", "import { IS_NON_DIMENSIONAL, NULL, SVG_NAMESPACE } from '../constants';\nimport options from '../options';\n\n// Per-instance unique key for event clock stamps. Each Preact copy on the page\n// gets its own random suffix so that `_dispatched` / `_attached` properties on\n// shared event objects and handler functions cannot collide across instances.\n// ~1 in 60M collision odds - if you have that many praect versions on the page,\n// you deserve some weird bugs.\n// In 11 we can replace this with a\n// Symbol\nlet _id = Math.random().toString(8),\n\tEVENT_DISPATCHED = '__d' + _id,\n\tEVENT_ATTACHED = '__a' + _id;\n\nfunction setStyle(style, key, value) {\n\tif (key[0] == '-') {\n\t\tstyle.setProperty(key, value == NULL ? '' : value);\n\t} else if (value == NULL) {\n\t\tstyle[key] = '';\n\t} else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {\n\t\tstyle[key] = value;\n\t} else {\n\t\tstyle[key] = value + 'px';\n\t}\n}\n\nconst CAPTURE_REGEX = /(PointerCapture)$|Capture$/i;\n\n// A logical clock to solve issues like https://github.com/preactjs/preact/issues/3927.\n// When the DOM performs an event it leaves micro-ticks in between bubbling up which means that\n// an event can trigger on a newly reated DOM-node while the event bubbles up.\n//\n// Originally inspired by Vue\n// (https://github.com/vuejs/core/blob/caeb8a68811a1b0f79/packages/runtime-dom/src/modules/events.ts#L90-L101),\n// but modified to use a logical clock instead of Date.now() in case event handlers get attached\n// and events get dispatched during the same millisecond.\n//\n// The clock is incremented after each new event dispatch. This allows 1 000 000 new events\n// per second for over 280 years before the value reaches Number.MAX_SAFE_INTEGER (2**53 - 1).\nlet eventClock = 0;\n\n/**\n * Set a property value on a DOM node\n * @param {import('../internal').PreactElement} dom The DOM node to modify\n * @param {string} name The name of the property to set\n * @param {*} value The value to set the property to\n * @param {*} oldValue The old value the property had\n * @param {string} namespace Whether or not this DOM node is an SVG node or not\n */\nexport function setProperty(dom, name, value, oldValue, namespace) {\n\tlet useCapture;\n\n\to: if (name == 'style') {\n\t\tif (typeof value == 'string') {\n\t\t\tdom.style.cssText = value;\n\t\t} else {\n\t\t\tif (typeof oldValue == 'string') {\n\t\t\t\tdom.style.cssText = oldValue = '';\n\t\t\t}\n\n\t\t\tif (oldValue) {\n\t\t\t\tfor (name in oldValue) {\n\t\t\t\t\tif (!(value && name in value)) {\n\t\t\t\t\t\tsetStyle(dom.style, name, '');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (value) {\n\t\t\t\tfor (name in value) {\n\t\t\t\t\tif (!oldValue || value[name] != oldValue[name]) {\n\t\t\t\t\t\tsetStyle(dom.style, name, value[name]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6\n\telse if (name[0] == 'o' && name[1] == 'n') {\n\t\tuseCapture = name != (name = name.replace(CAPTURE_REGEX, '$1'));\n\t\tconst lowerCaseName = name.toLowerCase();\n\n\t\t// Infer correct casing for DOM built-in events:\n\t\tif (lowerCaseName in dom || name == 'onFocusOut' || name == 'onFocusIn')\n\t\t\tname = lowerCaseName.slice(2);\n\t\telse name = name.slice(2);\n\n\t\tif (!dom._listeners) dom._listeners = {};\n\t\tdom._listeners[name + useCapture] = value;\n\n\t\tif (value) {\n\t\t\tif (!oldValue) {\n\t\t\t\tvalue[EVENT_ATTACHED] = eventClock;\n\t\t\t\tdom.addEventListener(\n\t\t\t\t\tname,\n\t\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\t\tuseCapture\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tvalue[EVENT_ATTACHED] = oldValue[EVENT_ATTACHED];\n\t\t\t}\n\t\t} else {\n\t\t\tdom.removeEventListener(\n\t\t\t\tname,\n\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\tuseCapture\n\t\t\t);\n\t\t}\n\t} else {\n\t\tif (namespace == SVG_NAMESPACE) {\n\t\t\t// Normalize incorrect prop usage for SVG:\n\t\t\t// - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)\n\t\t\t// - className --> class\n\t\t\tname = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's');\n\t\t} else if (\n\t\t\tname != 'width' &&\n\t\t\tname != 'height' &&\n\t\t\tname != 'href' &&\n\t\t\tname != 'list' &&\n\t\t\tname != 'form' &&\n\t\t\t// Default value in browsers is `-1` and an empty string is\n\t\t\t// cast to `0` instead\n\t\t\tname != 'tabIndex' &&\n\t\t\tname != 'download' &&\n\t\t\tname != 'rowSpan' &&\n\t\t\tname != 'colSpan' &&\n\t\t\tname != 'role' &&\n\t\t\tname != 'popover' &&\n\t\t\tname in dom\n\t\t) {\n\t\t\ttry {\n\t\t\t\tdom[name] = value == NULL ? '' : value;\n\t\t\t\t// labelled break is 1b smaller here than a return statement (sorry)\n\t\t\t\tbreak o;\n\t\t\t} catch (e) {}\n\t\t}\n\n\t\t// aria- and data- attributes have no boolean representation.\n\t\t// A `false` value is different from the attribute not being\n\t\t// present, so we can't remove it. For non-boolean aria\n\t\t// attributes we could treat false as a removal, but the\n\t\t// amount of exceptions would cost too many bytes. On top of\n\t\t// that other frameworks generally stringify `false`.\n\n\t\tif (typeof value == 'function') {\n\t\t\t// never serialize functions as attribute values\n\t\t} else if (value != NULL && (value !== false || name[4] == '-')) {\n\t\t\tdom.setAttribute(name, name == 'popover' && value == true ? '' : value);\n\t\t} else {\n\t\t\tdom.removeAttribute(name);\n\t\t}\n\t}\n}\n\n/**\n * Create an event proxy function.\n * @param {boolean} useCapture Is the event handler for the capture phase.\n * @private\n */\nfunction createEventProxy(useCapture) {\n\t/**\n\t * Proxy an event to hooked event handlers\n\t * @param {import('../internal').PreactEvent} e The event object from the browser\n\t * @private\n\t */\n\treturn function (e) {\n\t\tif (this._listeners) {\n\t\t\tconst eventHandler = this._listeners[e.type + useCapture];\n\t\t\tif (e[EVENT_DISPATCHED] == NULL) {\n\t\t\t\te[EVENT_DISPATCHED] = eventClock++;\n\n\t\t\t\t// When `e[EVENT_DISPATCHED]` is smaller than the time when the targeted event\n\t\t\t\t// handler was attached we know we have bubbled up to an element that was added\n\t\t\t\t// during patching the DOM.\n\t\t\t} else if (e[EVENT_DISPATCHED] < eventHandler[EVENT_ATTACHED]) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn eventHandler(options.event ? options.event(e) : e);\n\t\t}\n\t};\n}\n\nconst eventProxy = createEventProxy(false);\nconst eventProxyCapture = createEventProxy(true);\n", "import { enqueueRender } from './component';\nimport { NULL } from './constants';\n\nexport let i = 0;\n\nexport function createContext(defaultValue) {\n\tfunction Context(props) {\n\t\tif (!this.getChildContext) {\n\t\t\t/** @type {Set<import('./internal').Component> | null} */\n\t\t\tlet subs = new Set();\n\t\t\tlet ctx = {};\n\t\t\tctx[Context._id] = this;\n\n\t\t\tthis.getChildContext = () => ctx;\n\n\t\t\tthis.componentWillUnmount = () => {\n\t\t\t\tsubs = NULL;\n\t\t\t};\n\n\t\t\tthis.shouldComponentUpdate = function (_props) {\n\t\t\t\t// @ts-expect-error even\n\t\t\t\tif (this.props.value != _props.value) {\n\t\t\t\t\tsubs.forEach(c => {\n\t\t\t\t\t\tc._force = true;\n\t\t\t\t\t\tenqueueRender(c);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.sub = c => {\n\t\t\t\tsubs.add(c);\n\t\t\t\tlet old = c.componentWillUnmount;\n\t\t\t\tc.componentWillUnmount = () => {\n\t\t\t\t\tif (subs) {\n\t\t\t\t\t\tsubs.delete(c);\n\t\t\t\t\t}\n\t\t\t\t\tif (old) old.call(c);\n\t\t\t\t};\n\t\t\t};\n\t\t}\n\n\t\treturn props.children;\n\t}\n\n\tContext._id = '__cC' + i++;\n\tContext._defaultValue = defaultValue;\n\n\t/** @type {import('./internal').FunctionComponent} */\n\tContext.Consumer = (props, contextValue) => {\n\t\treturn props.children(contextValue);\n\t};\n\n\t// we could also get rid of _contextRef entirely\n\tContext.Provider =\n\t\tContext._contextRef =\n\t\tContext.Consumer.contextType =\n\t\t\tContext;\n\n\treturn Context;\n}\n", "import { diff, unmount, applyRef } from './index';\nimport { createVNode, Fragment } from '../create-element';\nimport {\n\tEMPTY_OBJ,\n\tEMPTY_ARR,\n\tINSERT_VNODE,\n\tMATCHED,\n\tUNDEFINED,\n\tNULL\n} from '../constants';\nimport { isArray } from '../util';\nimport { getDomSibling } from '../component';\n\n/**\n * @typedef {import('../internal').ComponentChildren} ComponentChildren\n * @typedef {import('../internal').Component} Component\n * @typedef {import('../internal').PreactElement} PreactElement\n * @typedef {import('../internal').VNode} VNode\n */\n\n/**\n * Diff the children of a virtual node\n * @param {PreactElement} parentDom The DOM element whose children are being\n * diffed\n * @param {ComponentChildren[]} renderResult\n * @param {VNode} newParentVNode The new virtual node whose children should be\n * diff'ed against oldParentVNode\n * @param {VNode} oldParentVNode The old virtual node whose children should be\n * diff'ed against newParentVNode\n * @param {object} globalContext The current context object - modified by\n * getChildContext\n * @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)\n * @param {Array<PreactElement>} excessDomChildren\n * @param {Array<Component>} commitQueue List of components which have callbacks\n * to invoke in commitRoot\n * @param {PreactElement} oldDom The current attached DOM element any new dom\n * elements should be placed around. Likely `null` on first render (except when\n * hydrating). Can be a sibling DOM element when diffing Fragments that have\n * siblings. In most cases, it starts out as `oldChildren[0]._dom`.\n * @param {boolean} isHydrating Whether or not we are in hydration\n * @param {any[]} refQueue an array of elements needed to invoke refs\n */\nexport function diffChildren(\n\tparentDom,\n\trenderResult,\n\tnewParentVNode,\n\toldParentVNode,\n\tglobalContext,\n\tnamespace,\n\texcessDomChildren,\n\tcommitQueue,\n\toldDom,\n\tisHydrating,\n\trefQueue\n) {\n\tlet i,\n\t\t/** @type {VNode} */\n\t\toldVNode,\n\t\t/** @type {VNode} */\n\t\tchildVNode,\n\t\t/** @type {PreactElement} */\n\t\tnewDom,\n\t\t/** @type {PreactElement} */\n\t\tfirstChildDom;\n\n\t// This is a compression of oldParentVNode!=null && oldParentVNode != EMPTY_OBJ && oldParentVNode._children || EMPTY_ARR\n\t// as EMPTY_OBJ._children should be `undefined`.\n\t/** @type {VNode[]} */\n\tlet oldChildren = (oldParentVNode && oldParentVNode._children) || EMPTY_ARR;\n\n\tlet newChildrenLength = renderResult.length;\n\n\toldDom = constructNewChildrenArray(\n\t\tnewParentVNode,\n\t\trenderResult,\n\t\toldChildren,\n\t\toldDom,\n\t\tnewChildrenLength\n\t);\n\n\tfor (i = 0; i < newChildrenLength; i++) {\n\t\tchildVNode = newParentVNode._children[i];\n\t\tif (childVNode == NULL) continue;\n\n\t\t// At this point, constructNewChildrenArray has assigned _index to be the\n\t\t// matchingIndex for this VNode's oldVNode (or -1 if there is no oldVNode).\n\t\toldVNode =\n\t\t\t(childVNode._index != -1 && oldChildren[childVNode._index]) || EMPTY_OBJ;\n\n\t\t// Update childVNode._index to its final index\n\t\tchildVNode._index = i;\n\n\t\t// Morph the old element into the new one, but don't append it to the dom yet\n\t\tlet result = diff(\n\t\t\tparentDom,\n\t\t\tchildVNode,\n\t\t\toldVNode,\n\t\t\tglobalContext,\n\t\t\tnamespace,\n\t\t\texcessDomChildren,\n\t\t\tcommitQueue,\n\t\t\toldDom,\n\t\t\tisHydrating,\n\t\t\trefQueue\n\t\t);\n\n\t\t// Adjust DOM nodes\n\t\tnewDom = childVNode._dom;\n\t\tif (childVNode.ref && oldVNode.ref != childVNode.ref) {\n\t\t\tif (oldVNode.ref) {\n\t\t\t\tapplyRef(oldVNode.ref, NULL, childVNode);\n\t\t\t}\n\t\t\trefQueue.push(\n\t\t\t\tchildVNode.ref,\n\t\t\t\tchildVNode._component || newDom,\n\t\t\t\tchildVNode\n\t\t\t);\n\t\t}\n\n\t\tif (firstChildDom == NULL && newDom != NULL) {\n\t\t\tfirstChildDom = newDom;\n\t\t}\n\n\t\tlet shouldPlace = !!(childVNode._flags & INSERT_VNODE);\n\t\tif (shouldPlace || oldVNode._children === childVNode._children) {\n\t\t\toldDom = insert(childVNode, oldDom, parentDom, shouldPlace);\n\n\t\t\t// When a matched VNode is physically moved via INSERT_VNODE, its old\n\t\t\t// _dom pointer becomes a stale positional reference. Clear it so that\n\t\t\t// getDomSibling (called from nested diffs) won't return this stale\n\t\t\t// reference and mis-place subsequent DOM nodes. See #5065.\n\t\t\tif (shouldPlace && oldVNode._dom) {\n\t\t\t\toldVNode._dom = NULL;\n\t\t\t}\n\t\t} else if (typeof childVNode.type == 'function' && result !== UNDEFINED) {\n\t\t\toldDom = result;\n\t\t} else if (newDom) {\n\t\t\toldDom = newDom.nextSibling;\n\t\t}\n\n\t\t// Unset diffing flags\n\t\tchildVNode._flags &= ~(INSERT_VNODE | MATCHED);\n\t}\n\n\tnewParentVNode._dom = firstChildDom;\n\n\treturn oldDom;\n}\n\n/**\n * @param {VNode} newParentVNode\n * @param {ComponentChildren[]} renderResult\n * @param {VNode[]} oldChildren\n */\nfunction constructNewChildrenArray(\n\tnewParentVNode,\n\trenderResult,\n\toldChildren,\n\toldDom,\n\tnewChildrenLength\n) {\n\t/** @type {number} */\n\tlet i;\n\t/** @type {VNode} */\n\tlet childVNode;\n\t/** @type {VNode} */\n\tlet oldVNode;\n\n\tlet oldChildrenLength = oldChildren.length,\n\t\tremainingOldChildren = oldChildrenLength;\n\n\tlet skew = 0;\n\n\tnewParentVNode._children = new Array(newChildrenLength);\n\tfor (i = 0; i < newChildrenLength; i++) {\n\t\t// @ts-expect-error We are reusing the childVNode variable to hold both the\n\t\t// pre and post normalized childVNode\n\t\tchildVNode = renderResult[i];\n\n\t\tif (\n\t\t\tchildVNode == NULL ||\n\t\t\ttypeof childVNode == 'boolean' ||\n\t\t\ttypeof childVNode == 'function'\n\t\t) {\n\t\t\tnewParentVNode._children[i] = NULL;\n\t\t\tcontinue;\n\t\t}\n\t\t// If this newVNode is being reused (e.g. <div>{reuse}{reuse}</div>) in the same diff,\n\t\t// or we are rendering a component (e.g. setState) copy the oldVNodes so it can have\n\t\t// it's own DOM & etc. pointers\n\t\telse if (\n\t\t\ttypeof childVNode == 'string' ||\n\t\t\ttypeof childVNode == 'number' ||\n\t\t\t// eslint-disable-next-line valid-typeof\n\t\t\ttypeof childVNode == 'bigint' ||\n\t\t\tchildVNode.constructor == String\n\t\t) {\n\t\t\tchildVNode = newParentVNode._children[i] = createVNode(\n\t\t\t\tNULL,\n\t\t\t\tchildVNode,\n\t\t\t\tNULL,\n\t\t\t\tNULL,\n\t\t\t\tNULL\n\t\t\t);\n\t\t} else if (isArray(childVNode)) {\n\t\t\tchildVNode = newParentVNode._children[i] = createVNode(\n\t\t\t\tFragment,\n\t\t\t\t{ children: childVNode },\n\t\t\t\tNULL,\n\t\t\t\tNULL,\n\t\t\t\tNULL\n\t\t\t);\n\t\t} else if (childVNode.constructor === UNDEFINED && childVNode._depth > 0) {\n\t\t\t// VNode is already in use, clone it. This can happen in the following\n\t\t\t// scenario:\n\t\t\t// const reuse = <div />\n\t\t\t// <div>{reuse}<span />{reuse}</div>\n\t\t\tchildVNode = newParentVNode._children[i] = createVNode(\n\t\t\t\tchildVNode.type,\n\t\t\t\tchildVNode.props,\n\t\t\t\tchildVNode.key,\n\t\t\t\tchildVNode.ref ? childVNode.ref : NULL,\n\t\t\t\tchildVNode._original\n\t\t\t);\n\t\t} else {\n\t\t\tnewParentVNode._children[i] = childVNode;\n\t\t}\n\n\t\tconst skewedIndex = i + skew;\n\t\tchildVNode._parent = newParentVNode;\n\t\tchildVNode._depth = newParentVNode._depth + 1;\n\n\t\t// Temporarily store the matchingIndex on the _index property so we can pull\n\t\t// out the oldVNode in diffChildren. We'll override this to the VNode's\n\t\t// final index after using this property to get the oldVNode\n\t\tconst matchingIndex = (childVNode._index = findMatchingIndex(\n\t\t\tchildVNode,\n\t\t\toldChildren,\n\t\t\tskewedIndex,\n\t\t\tremainingOldChildren\n\t\t));\n\n\t\toldVNode = NULL;\n\t\tif (matchingIndex != -1) {\n\t\t\toldVNode = oldChildren[matchingIndex];\n\t\t\tremainingOldChildren--;\n\t\t\tif (oldVNode) {\n\t\t\t\toldVNode._flags |= MATCHED;\n\t\t\t}\n\t\t}\n\n\t\t// Here, we define isMounting for the purposes of the skew diffing\n\t\t// algorithm. Nodes that are unsuspending are considered mounting and we detect\n\t\t// this by checking if oldVNode._original == null\n\t\tconst isMounting = oldVNode == NULL || oldVNode._original == NULL;\n\n\t\tif (isMounting) {\n\t\t\tif (matchingIndex == -1) {\n\t\t\t\t// When the array of children is growing we need to decrease the skew\n\t\t\t\t// as we are adding a new element to the array.\n\t\t\t\t// Example:\n\t\t\t\t// [1, 2, 3] --> [0, 1, 2, 3]\n\t\t\t\t// oldChildren newChildren\n\t\t\t\t//\n\t\t\t\t// The new element is at index 0, so our skew is 0,\n\t\t\t\t// we need to decrease the skew as we are adding a new element.\n\t\t\t\t// The decrease will cause us to compare the element at position 1\n\t\t\t\t// with value 1 with the element at position 0 with value 0.\n\t\t\t\t//\n\t\t\t\t// A linear concept is applied when the array is shrinking,\n\t\t\t\t// if the length is unchanged we can assume that no skew\n\t\t\t\t// changes are needed.\n\t\t\t\tif (newChildrenLength > oldChildrenLength) {\n\t\t\t\t\tskew--;\n\t\t\t\t} else if (newChildrenLength < oldChildrenLength) {\n\t\t\t\t\tskew++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we are mounting a DOM VNode, mark it for insertion\n\t\t\tif (typeof childVNode.type != 'function') {\n\t\t\t\tchildVNode._flags |= INSERT_VNODE;\n\t\t\t}\n\t\t} else if (matchingIndex != skewedIndex) {\n\t\t\t// When we move elements around i.e. [0, 1, 2] --> [1, 0, 2]\n\t\t\t// --> we diff 1, we find it at position 1 while our skewed index is 0 and our skew is 0\n\t\t\t// we set the skew to 1 as we found an offset.\n\t\t\t// --> we diff 0, we find it at position 0 while our skewed index is at 2 and our skew is 1\n\t\t\t// this makes us increase the skew again.\n\t\t\t// --> we diff 2, we find it at position 2 while our skewed index is at 4 and our skew is 2\n\t\t\t//\n\t\t\t// this becomes an optimization question where currently we see a 1 element offset as an insertion\n\t\t\t// or deletion i.e. we optimize for [0, 1, 2] --> [9, 0, 1, 2]\n\t\t\t// while a more than 1 offset we see as a swap.\n\t\t\t// We could probably build heuristics for having an optimized course of action here as well, but\n\t\t\t// might go at the cost of some bytes.\n\t\t\t//\n\t\t\t// If we wanted to optimize for i.e. only swaps we'd just do the last two code-branches and have\n\t\t\t// only the first item be a re-scouting and all the others fall in their skewed counter-part.\n\t\t\t// We could also further optimize for swaps\n\t\t\tif (matchingIndex == skewedIndex - 1) {\n\t\t\t\tskew--;\n\t\t\t} else if (matchingIndex == skewedIndex + 1) {\n\t\t\t\tskew++;\n\t\t\t} else {\n\t\t\t\tif (matchingIndex > skewedIndex) {\n\t\t\t\t\tskew--;\n\t\t\t\t} else {\n\t\t\t\t\tskew++;\n\t\t\t\t}\n\n\t\t\t\t// Move this VNode's DOM if the original index (matchingIndex) doesn't\n\t\t\t\t// match the new skew index (i + new skew)\n\t\t\t\t// In the former two branches we know that it matches after skewing\n\t\t\t\tchildVNode._flags |= INSERT_VNODE;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Remove remaining oldChildren if there are any. Loop forwards so that as we\n\t// unmount DOM from the beginning of the oldChildren, we can adjust oldDom to\n\t// point to the next child, which needs to be the first DOM node that won't be\n\t// unmounted.\n\tif (remainingOldChildren) {\n\t\tfor (i = 0; i < oldChildrenLength; i++) {\n\t\t\toldVNode = oldChildren[i];\n\t\t\tif (oldVNode != NULL && (oldVNode._flags & MATCHED) == 0) {\n\t\t\t\tif (oldVNode._dom == oldDom) {\n\t\t\t\t\toldDom = getDomSibling(oldVNode);\n\t\t\t\t}\n\n\t\t\t\tunmount(oldVNode, oldVNode);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn oldDom;\n}\n\n/**\n * @param {VNode} parentVNode\n * @param {PreactElement} oldDom\n * @param {PreactElement} parentDom\n * @param {boolean} shouldPlace\n * @returns {PreactElement}\n */\nfunction insert(parentVNode, oldDom, parentDom, shouldPlace) {\n\t// Note: VNodes in nested suspended trees may be missing _children.\n\n\tif (typeof parentVNode.type == 'function') {\n\t\tlet children = parentVNode._children;\n\t\tfor (let i = 0; children && i < children.length; i++) {\n\t\t\tif (children[i]) {\n\t\t\t\t// If we enter this code path on sCU bailout, where we copy\n\t\t\t\t// oldVNode._children to newVNode._children, we need to update the old\n\t\t\t\t// children's _parent pointer to point to the newVNode (parentVNode\n\t\t\t\t// here).\n\t\t\t\tchildren[i]._parent = parentVNode;\n\t\t\t\toldDom = insert(children[i], oldDom, parentDom, shouldPlace);\n\t\t\t}\n\t\t}\n\n\t\treturn oldDom;\n\t} else if (parentVNode._dom != oldDom) {\n\t\tif (shouldPlace) {\n\t\t\tif (oldDom && parentVNode.type && !oldDom.parentNode) {\n\t\t\t\toldDom = getDomSibling(parentVNode);\n\t\t\t}\n\t\t\tparentDom.insertBefore(parentVNode._dom, oldDom || NULL);\n\t\t}\n\t\toldDom = parentVNode._dom;\n\t}\n\n\tdo {\n\t\toldDom = oldDom && oldDom.nextSibling;\n\t} while (oldDom != NULL && oldDom.nodeType == 8);\n\n\treturn oldDom;\n}\n\n/**\n * Flatten and loop through the children of a virtual node\n * @param {ComponentChildren} children The unflattened children of a virtual\n * node\n * @returns {VNode[]}\n */\nexport function toChildArray(children, out) {\n\tout = out || [];\n\tif (children == NULL || typeof children == 'boolean') {\n\t} else if (isArray(children)) {\n\t\tchildren.some(child => {\n\t\t\ttoChildArray(child, out);\n\t\t});\n\t} else {\n\t\tout.push(children);\n\t}\n\treturn out;\n}\n\n/**\n * @param {VNode} childVNode\n * @param {VNode[]} oldChildren\n * @param {number} skewedIndex\n * @param {number} remainingOldChildren\n * @returns {number}\n */\nfunction findMatchingIndex(\n\tchildVNode,\n\toldChildren,\n\tskewedIndex,\n\tremainingOldChildren\n) {\n\tconst key = childVNode.key;\n\tconst type = childVNode.type;\n\tlet oldVNode = oldChildren[skewedIndex];\n\tconst matched = oldVNode != NULL && (oldVNode._flags & MATCHED) == 0;\n\n\t// We only need to perform a search if there are more children\n\t// (remainingOldChildren) to search. However, if the oldVNode we just looked\n\t// at skewedIndex was not already used in this diff, then there must be at\n\t// least 1 other (so greater than 1) remainingOldChildren to attempt to match\n\t// against. So the following condition checks that ensuring\n\t// remainingOldChildren > 1 if the oldVNode is not already used/matched. Else\n\t// if the oldVNode was null or matched, then there could needs to be at least\n\t// 1 (aka `remainingOldChildren > 0`) children to find and compare against.\n\t//\n\t// If there is an unkeyed functional VNode, that isn't a built-in like our Fragment,\n\t// we should not search as we risk re-using state of an unrelated VNode. (reverted for now)\n\tlet shouldSearch =\n\t\t// (typeof type != 'function' || type === Fragment || key) &&\n\t\tremainingOldChildren > (matched ? 1 : 0);\n\n\tif (\n\t\t(oldVNode === NULL && key == null) ||\n\t\t(matched && key == oldVNode.key && type == oldVNode.type)\n\t) {\n\t\treturn skewedIndex;\n\t} else if (shouldSearch) {\n\t\tlet x = skewedIndex - 1;\n\t\tlet y = skewedIndex + 1;\n\t\twhile (x >= 0 || y < oldChildren.length) {\n\t\t\tconst childIndex = x >= 0 ? x-- : y++;\n\t\t\toldVNode = oldChildren[childIndex];\n\t\t\tif (\n\t\t\t\toldVNode != NULL &&\n\t\t\t\t(oldVNode._flags & MATCHED) == 0 &&\n\t\t\t\tkey == oldVNode.key &&\n\t\t\t\ttype == oldVNode.type\n\t\t\t) {\n\t\t\t\treturn childIndex;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn -1;\n}\n", "import {\n\tEMPTY_ARR,\n\tEMPTY_OBJ,\n\tMATH_NAMESPACE,\n\tMODE_HYDRATE,\n\tMODE_SUSPENDED,\n\tNULL,\n\tRESET_MODE,\n\tSVG_NAMESPACE,\n\tUNDEFINED,\n\tXHTML_NAMESPACE\n} from '../constants';\nimport { BaseComponent, getDomSibling } from '../component';\nimport { Fragment } from '../create-element';\nimport { diffChildren } from './children';\nimport { setProperty } from './props';\nimport { assign, isArray, removeNode, slice } from '../util';\nimport options from '../options';\n\n/**\n * @typedef {import('../internal').ComponentChildren} ComponentChildren\n * @typedef {import('../internal').Component} Component\n * @typedef {import('../internal').PreactElement} PreactElement\n * @typedef {import('../internal').VNode} VNode\n */\n\n/**\n * @template {any} T\n * @typedef {import('../internal').Ref<T>} Ref<T>\n */\n\n/**\n * Diff two virtual nodes and apply proper changes to the DOM\n * @param {PreactElement} parentDom The parent of the DOM element\n * @param {VNode} newVNode The new virtual node\n * @param {VNode} oldVNode The old virtual node\n * @param {object} globalContext The current context object. Modified by\n * getChildContext\n * @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)\n * @param {Array<PreactElement>} excessDomChildren\n * @param {Array<Component>} commitQueue List of components which have callbacks\n * to invoke in commitRoot\n * @param {PreactElement} oldDom The current attached DOM element any new dom\n * elements should be placed around. Likely `null` on first render (except when\n * hydrating). Can be a sibling DOM element when diffing Fragments that have\n * siblings. In most cases, it starts out as `oldChildren[0]._dom`.\n * @param {boolean} isHydrating Whether or not we are in hydration\n * @param {any[]} refQueue an array of elements needed to invoke refs\n */\nexport function diff(\n\tparentDom,\n\tnewVNode,\n\toldVNode,\n\tglobalContext,\n\tnamespace,\n\texcessDomChildren,\n\tcommitQueue,\n\toldDom,\n\tisHydrating,\n\trefQueue\n) {\n\t/** @type {any} */\n\tlet tmp,\n\t\tnewType = newVNode.type;\n\n\t// When passing through createElement it assigns the object\n\t// constructor as undefined. This to prevent JSON-injection.\n\tif (newVNode.constructor !== UNDEFINED) return NULL;\n\n\t// If the previous diff bailed out, resume creating/hydrating.\n\tif (oldVNode._flags & MODE_SUSPENDED) {\n\t\tisHydrating = !!(oldVNode._flags & MODE_HYDRATE);\n\t\toldDom = newVNode._dom = oldVNode._dom;\n\t\texcessDomChildren = [oldDom];\n\t}\n\n\tif ((tmp = options._diff)) tmp(newVNode);\n\n\touter: if (typeof newType == 'function') {\n\t\ttry {\n\t\t\tlet c, isNew, oldProps, oldState, snapshot, clearProcessingException;\n\t\t\tlet newProps = newVNode.props;\n\t\t\tconst isClassComponent = newType.prototype && newType.prototype.render;\n\n\t\t\t// Necessary for createContext api. Setting this property will pass\n\t\t\t// the context value as `this.context` just for this component.\n\t\t\ttmp = newType.contextType;\n\t\t\tlet provider = tmp && globalContext[tmp._id];\n\t\t\tlet componentContext = tmp\n\t\t\t\t? provider\n\t\t\t\t\t? provider.props.value\n\t\t\t\t\t: tmp._defaultValue\n\t\t\t\t: globalContext;\n\n\t\t\t// Get component and set it to `c`\n\t\t\tif (oldVNode._component) {\n\t\t\t\tc = newVNode._component = oldVNode._component;\n\t\t\t\tclearProcessingException = c._processingException = c._pendingError;\n\t\t\t} else {\n\t\t\t\t// Instantiate the new component\n\t\t\t\tif (isClassComponent) {\n\t\t\t\t\t// @ts-expect-error The check above verifies that newType is suppose to be constructed\n\t\t\t\t\tnewVNode._component = c = new newType(newProps, componentContext); // eslint-disable-line new-cap\n\t\t\t\t} else {\n\t\t\t\t\t// @ts-expect-error Trust me, Component implements the interface we want\n\t\t\t\t\tnewVNode._component = c = new BaseComponent(\n\t\t\t\t\t\tnewProps,\n\t\t\t\t\t\tcomponentContext\n\t\t\t\t\t);\n\t\t\t\t\tc.constructor = newType;\n\t\t\t\t\tc.render = doRender;\n\t\t\t\t}\n\t\t\t\tif (provider) provider.sub(c);\n\n\t\t\t\tif (!c.state) c.state = {};\n\t\t\t\tc._globalContext = globalContext;\n\t\t\t\tisNew = c._dirty = true;\n\t\t\t\tc._renderCallbacks = [];\n\t\t\t\tc._stateCallbacks = [];\n\t\t\t}\n\n\t\t\t// Invoke getDerivedStateFromProps\n\t\t\tif (isClassComponent && c._nextState == NULL) {\n\t\t\t\tc._nextState = c.state;\n\t\t\t}\n\n\t\t\tif (isClassComponent && newType.getDerivedStateFromProps != NULL) {\n\t\t\t\tif (c._nextState == c.state) {\n\t\t\t\t\tc._nextState = assign({}, c._nextState);\n\t\t\t\t}\n\n\t\t\t\tassign(\n\t\t\t\t\tc._nextState,\n\t\t\t\t\tnewType.getDerivedStateFromProps(newProps, c._nextState)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\toldProps = c.props;\n\t\t\toldState = c.state;\n\t\t\tc._vnode = newVNode;\n\n\t\t\t// Invoke pre-render lifecycle methods\n\t\t\tif (isNew) {\n\t\t\t\tif (\n\t\t\t\t\tisClassComponent &&\n\t\t\t\t\tnewType.getDerivedStateFromProps == NULL &&\n\t\t\t\t\tc.componentWillMount != NULL\n\t\t\t\t) {\n\t\t\t\t\tc.componentWillMount();\n\t\t\t\t}\n\n\t\t\t\tif (isClassComponent && c.componentDidMount != NULL) {\n\t\t\t\t\tc._renderCallbacks.push(c.componentDidMount);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tisClassComponent &&\n\t\t\t\t\tnewType.getDerivedStateFromProps == NULL &&\n\t\t\t\t\tnewProps !== oldProps &&\n\t\t\t\t\tc.componentWillReceiveProps != NULL\n\t\t\t\t) {\n\t\t\t\t\tc.componentWillReceiveProps(newProps, componentContext);\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\tnewVNode._original == oldVNode._original ||\n\t\t\t\t\t(!c._force &&\n\t\t\t\t\t\tc.shouldComponentUpdate != NULL &&\n\t\t\t\t\t\tc.shouldComponentUpdate(\n\t\t\t\t\t\t\tnewProps,\n\t\t\t\t\t\t\tc._nextState,\n\t\t\t\t\t\t\tcomponentContext\n\t\t\t\t\t\t) === false)\n\t\t\t\t) {\n\t\t\t\t\t// More info about this here: https://gist.github.com/JoviDeCroock/bec5f2ce93544d2e6070ef8e0036e4e8\n\t\t\t\t\tif (newVNode._original != oldVNode._original) {\n\t\t\t\t\t\t// When we are dealing with a bail because of sCU we have to update\n\t\t\t\t\t\t// the props, state and dirty-state.\n\t\t\t\t\t\t// when we are dealing with strict-equality we don't as the child could still\n\t\t\t\t\t\t// be dirtied see #3883\n\t\t\t\t\t\tc.props = newProps;\n\t\t\t\t\t\tc.state = c._nextState;\n\t\t\t\t\t\tc._dirty = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tnewVNode._dom = oldVNode._dom;\n\t\t\t\t\tnewVNode._children = oldVNode._children;\n\t\t\t\t\tnewVNode._children.some(vnode => {\n\t\t\t\t\t\tif (vnode) vnode._parent = newVNode;\n\t\t\t\t\t});\n\n\t\t\t\t\tEMPTY_ARR.push.apply(c._renderCallbacks, c._stateCallbacks);\n\t\t\t\t\tc._stateCallbacks = [];\n\n\t\t\t\t\tif (c._renderCallbacks.length) {\n\t\t\t\t\t\tcommitQueue.push(c);\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak outer;\n\t\t\t\t}\n\n\t\t\t\tif (c.componentWillUpdate != NULL) {\n\t\t\t\t\tc.componentWillUpdate(newProps, c._nextState, componentContext);\n\t\t\t\t}\n\n\t\t\t\tif (isClassComponent && c.componentDidUpdate != NULL) {\n\t\t\t\t\tc._renderCallbacks.push(() => {\n\t\t\t\t\t\tc.componentDidUpdate(oldProps, oldState, snapshot);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tc.context = componentContext;\n\t\t\tc.props = newProps;\n\t\t\tc._parentDom = parentDom;\n\t\t\tc._force = false;\n\n\t\t\tlet renderHook = options._render,\n\t\t\t\tcount = 0;\n\t\t\tif (isClassComponent) {\n\t\t\t\tc.state = c._nextState;\n\t\t\t\tc._dirty = false;\n\n\t\t\t\tif (renderHook) renderHook(newVNode);\n\n\t\t\t\ttmp = c.render(c.props, c.state, c.context);\n\n\t\t\t\tEMPTY_ARR.push.apply(c._renderCallbacks, c._stateCallbacks);\n\t\t\t\tc._stateCallbacks = [];\n\t\t\t} else {\n\t\t\t\tdo {\n\t\t\t\t\tc._dirty = false;\n\t\t\t\t\tif (renderHook) renderHook(newVNode);\n\n\t\t\t\t\ttmp = c.render(c.props, c.state, c.context);\n\n\t\t\t\t\t// Handle setState called in render, see #2553\n\t\t\t\t\tc.state = c._nextState;\n\t\t\t\t} while (c._dirty && ++count < 25);\n\t\t\t}\n\n\t\t\t// Handle setState called in render, see #2553\n\t\t\tc.state = c._nextState;\n\n\t\t\tif (c.getChildContext != NULL) {\n\t\t\t\tglobalContext = assign(assign({}, globalContext), c.getChildContext());\n\t\t\t}\n\n\t\t\tif (isClassComponent && !isNew && c.getSnapshotBeforeUpdate != NULL) {\n\t\t\t\tsnapshot = c.getSnapshotBeforeUpdate(oldProps, oldState);\n\t\t\t}\n\n\t\t\tlet renderResult =\n\t\t\t\ttmp != NULL && tmp.type === Fragment && tmp.key == NULL\n\t\t\t\t\t? cloneNode(tmp.props.children)\n\t\t\t\t\t: tmp;\n\n\t\t\toldDom = diffChildren(\n\t\t\t\tparentDom,\n\t\t\t\tisArray(renderResult) ? renderResult : [renderResult],\n\t\t\t\tnewVNode,\n\t\t\t\toldVNode,\n\t\t\t\tglobalContext,\n\t\t\t\tnamespace,\n\t\t\t\texcessDomChildren,\n\t\t\t\tcommitQueue,\n\t\t\t\toldDom,\n\t\t\t\tisHydrating,\n\t\t\t\trefQueue\n\t\t\t);\n\n\t\t\tc.base = newVNode._dom;\n\n\t\t\t// We successfully rendered this VNode, unset any stored hydration/bailout state:\n\t\t\tnewVNode._flags &= RESET_MODE;\n\n\t\t\tif (c._renderCallbacks.length) {\n\t\t\t\tcommitQueue.push(c);\n\t\t\t}\n\n\t\t\tif (clearProcessingException) {\n\t\t\t\tc._pendingError = c._processingException = NULL;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tnewVNode._original = NULL;\n\t\t\t// if hydrating or creating initial tree, bailout preserves DOM:\n\t\t\tif (isHydrating || excessDomChildren != NULL) {\n\t\t\t\tif (e.then) {\n\t\t\t\t\tnewVNode._flags |= isHydrating\n\t\t\t\t\t\t? MODE_HYDRATE | MODE_SUSPENDED\n\t\t\t\t\t\t: MODE_SUSPENDED;\n\n\t\t\t\t\twhile (oldDom && oldDom.nodeType == 8 && oldDom.nextSibling) {\n\t\t\t\t\t\toldDom = oldDom.nextSibling;\n\t\t\t\t\t}\n\n\t\t\t\t\texcessDomChildren[excessDomChildren.indexOf(oldDom)] = NULL;\n\t\t\t\t\tnewVNode._dom = oldDom;\n\t\t\t\t} else {\n\t\t\t\t\tfor (let i = excessDomChildren.length; i--; ) {\n\t\t\t\t\t\tremoveNode(excessDomChildren[i]);\n\t\t\t\t\t}\n\t\t\t\t\tmarkAsForce(newVNode);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnewVNode._dom = oldVNode._dom;\n\t\t\t\tnewVNode._children = oldVNode._children;\n\t\t\t\tif (!e.then) markAsForce(newVNode);\n\t\t\t}\n\t\t\toptions._catchError(e, newVNode, oldVNode);\n\t\t}\n\t} else if (\n\t\texcessDomChildren == NULL &&\n\t\tnewVNode._original == oldVNode._original\n\t) {\n\t\tnewVNode._children = oldVNode._children;\n\t\tnewVNode._dom = oldVNode._dom;\n\t} else {\n\t\toldDom = newVNode._dom = diffElementNodes(\n\t\t\toldVNode._dom,\n\t\t\tnewVNode,\n\t\t\toldVNode,\n\t\t\tglobalContext,\n\t\t\tnamespace,\n\t\t\texcessDomChildren,\n\t\t\tcommitQueue,\n\t\t\tisHydrating,\n\t\t\trefQueue\n\t\t);\n\t}\n\n\tif ((tmp = options.diffed)) tmp(newVNode);\n\n\treturn newVNode._flags & MODE_SUSPENDED ? undefined : oldDom;\n}\n\nfunction markAsForce(vnode) {\n\tif (vnode) {\n\t\tif (vnode._component) vnode._component._force = true;\n\t\tif (vnode._children) vnode._children.some(markAsForce);\n\t}\n}\n\n/**\n * @param {Array<Component>} commitQueue List of components\n * which have callbacks to invoke in commitRoot\n * @param {VNode} root\n */\nexport function commitRoot(commitQueue, root, refQueue) {\n\tfor (let i = 0; i < refQueue.length; i++) {\n\t\tapplyRef(refQueue[i], refQueue[++i], refQueue[++i]);\n\t}\n\n\tif (options._commit) options._commit(root, commitQueue);\n\n\tcommitQueue.some(c => {\n\t\ttry {\n\t\t\t// @ts-expect-error Reuse the commitQueue variable here so the type changes\n\t\t\tcommitQueue = c._renderCallbacks;\n\t\t\tc._renderCallbacks = [];\n\t\t\tcommitQueue.some(cb => {\n\t\t\t\t// @ts-expect-error See above comment on commitQueue\n\t\t\t\tcb.call(c);\n\t\t\t});\n\t\t} catch (e) {\n\t\t\toptions._catchError(e, c._vnode);\n\t\t}\n\t});\n}\n\nfunction cloneNode(node) {\n\tif (typeof node != 'object' || node == NULL || node._depth > 0) {\n\t\treturn node;\n\t}\n\n\tif (isArray(node)) {\n\t\treturn node.map(cloneNode);\n\t}\n\n\tif (node.constructor !== UNDEFINED) return null;\n\n\treturn assign({}, node);\n}\n\n/**\n * Diff two virtual nodes representing DOM element\n * @param {PreactElement} dom The DOM element representing the virtual nodes\n * being diffed\n * @param {VNode} newVNode The new virtual node\n * @param {VNode} oldVNode The old virtual node\n * @param {object} globalContext The current context object\n * @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML)\n * @param {Array<PreactElement>} excessDomChildren\n * @param {Array<Component>} commitQueue List of components which have callbacks\n * to invoke in commitRoot\n * @param {boolean} isHydrating Whether or not we are in hydration\n * @param {any[]} refQueue an array of elements needed to invoke refs\n * @returns {PreactElement}\n */\nfunction diffElementNodes(\n\tdom,\n\tnewVNode,\n\toldVNode,\n\tglobalContext,\n\tnamespace,\n\texcessDomChildren,\n\tcommitQueue,\n\tisHydrating,\n\trefQueue\n) {\n\tlet oldProps = oldVNode.props || EMPTY_OBJ;\n\tlet newProps = newVNode.props;\n\tlet nodeType = /** @type {string} */ (newVNode.type);\n\t/** @type {any} */\n\tlet i;\n\t/** @type {{ __html?: string }} */\n\tlet newHtml;\n\t/** @type {{ __html?: string }} */\n\tlet oldHtml;\n\t/** @type {ComponentChildren} */\n\tlet newChildren;\n\tlet value;\n\tlet inputValue;\n\tlet checked;\n\n\t// Tracks entering and exiting namespaces when descending through the tree.\n\tif (nodeType == 'svg') namespace = SVG_NAMESPACE;\n\telse if (nodeType == 'math') namespace = MATH_NAMESPACE;\n\telse if (!namespace) namespace = XHTML_NAMESPACE;\n\n\tif (excessDomChildren != NULL) {\n\t\tfor (i = 0; i < excessDomChildren.length; i++) {\n\t\t\tvalue = excessDomChildren[i];\n\n\t\t\t// if newVNode matches an element in excessDomChildren or the `dom`\n\t\t\t// argument matches an element in excessDomChildren, remove it from\n\t\t\t// excessDomChildren so it isn't later removed in diffChildren\n\t\t\tif (\n\t\t\t\tvalue &&\n\t\t\t\t'setAttribute' in value == !!nodeType &&\n\t\t\t\t(nodeType ? value.localName == nodeType : value.nodeType == 3)\n\t\t\t) {\n\t\t\t\tdom = value;\n\t\t\t\texcessDomChildren[i] = NULL;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (dom == NULL) {\n\t\tif (nodeType == NULL) {\n\t\t\treturn document.createTextNode(newProps);\n\t\t}\n\n\t\tdom = document.createElementNS(\n\t\t\tnamespace,\n\t\t\tnodeType,\n\t\t\tnewProps.is && newProps\n\t\t);\n\n\t\t// we are creating a new node, so we can assume this is a new subtree (in\n\t\t// case we are hydrating), this deopts the hydrate\n\t\tif (isHydrating) {\n\t\t\tif (options._hydrationMismatch)\n\t\t\t\toptions._hydrationMismatch(newVNode, excessDomChildren);\n\t\t\tisHydrating = false;\n\t\t}\n\t\t// we created a new parent, so none of the previously attached children can be reused:\n\t\texcessDomChildren = NULL;\n\t}\n\n\tif (nodeType == NULL) {\n\t\t// During hydration, we still have to split merged text from SSR'd HTML.\n\t\tif (oldProps !== newProps && (!isHydrating || dom.data != newProps)) {\n\t\t\tdom.data = newProps;\n\t\t}\n\t} else {\n\t\t// If excessDomChildren was not null, repopulate it with the current element's children:\n\t\texcessDomChildren =\n\t\t\tnodeType == 'textarea' && newProps.defaultValue != NULL\n\t\t\t\t? NULL\n\t\t\t\t: excessDomChildren && slice.call(dom.childNodes);\n\n\t\t// If we are in a situation where we are not hydrating but are using\n\t\t// existing DOM (e.g. replaceNode) we should read the existing DOM\n\t\t// attributes to diff them\n\t\tif (!isHydrating && excessDomChildren != NULL) {\n\t\t\toldProps = {};\n\t\t\tfor (i = 0; i < dom.attributes.length; i++) {\n\t\t\t\tvalue = dom.attributes[i];\n\t\t\t\toldProps[value.name] = value.value;\n\t\t\t}\n\t\t}\n\n\t\tfor (i in oldProps) {\n\t\t\tvalue = oldProps[i];\n\t\t\tif (i == 'dangerouslySetInnerHTML') {\n\t\t\t\toldHtml = value;\n\t\t\t} else if (\n\t\t\t\ti != 'children' &&\n\t\t\t\t!(i in newProps) &&\n\t\t\t\t!(i == 'value' && 'defaultValue' in newProps) &&\n\t\t\t\t!(i == 'checked' && 'defaultChecked' in newProps)\n\t\t\t) {\n\t\t\t\tsetProperty(dom, i, NULL, value, namespace);\n\t\t\t}\n\t\t}\n\n\t\t// During hydration, props are not diffed at all (including dangerouslySetInnerHTML)\n\t\t// @TODO we should warn in debug mode when props don't match here.\n\t\tfor (i in newProps) {\n\t\t\tvalue = newProps[i];\n\t\t\tif (i == 'children') {\n\t\t\t\tnewChildren = value;\n\t\t\t} else if (i == 'dangerouslySetInnerHTML') {\n\t\t\t\tnewHtml = value;\n\t\t\t} else if (i == 'value') {\n\t\t\t\tinputValue = value;\n\t\t\t} else if (i == 'checked') {\n\t\t\t\tchecked = value;\n\t\t\t} else if (\n\t\t\t\t(!isHydrating || typeof value == 'function') &&\n\t\t\t\toldProps[i] !== value\n\t\t\t) {\n\t\t\t\tsetProperty(dom, i, value, oldProps[i], namespace);\n\t\t\t}\n\t\t}\n\n\t\t// If the new vnode didn't have dangerouslySetInnerHTML, diff its children\n\t\tif (newHtml) {\n\t\t\t// Avoid re-applying the same '__html' if it did not changed between re-render\n\t\t\tif (\n\t\t\t\t!isHydrating &&\n\t\t\t\t(!oldHtml ||\n\t\t\t\t\t(newHtml.__html != oldHtml.__html && newHtml.__html != dom.innerHTML))\n\t\t\t) {\n\t\t\t\tdom.innerHTML = newHtml.__html;\n\t\t\t}\n\n\t\t\tnewVNode._children = [];\n\t\t} else {\n\t\t\tif (oldHtml) dom.innerHTML = '';\n\n\t\t\tdiffChildren(\n\t\t\t\t// @ts-expect-error\n\t\t\t\tnewVNode.type == 'template' ? dom.content : dom,\n\t\t\t\tisArray(newChildren) ? newChildren : [newChildren],\n\t\t\t\tnewVNode,\n\t\t\t\toldVNode,\n\t\t\t\tglobalContext,\n\t\t\t\tnodeType == 'foreignObject' ? XHTML_NAMESPACE : namespace,\n\t\t\t\texcessDomChildren,\n\t\t\t\tcommitQueue,\n\t\t\t\texcessDomChildren\n\t\t\t\t\t? excessDomChildren[0]\n\t\t\t\t\t: oldVNode._children && getDomSibling(oldVNode, 0),\n\t\t\t\tisHydrating,\n\t\t\t\trefQueue\n\t\t\t);\n\n\t\t\t// Remove children that are not part of any vnode.\n\t\t\tif (excessDomChildren != NULL) {\n\t\t\t\tfor (i = excessDomChildren.length; i--; ) {\n\t\t\t\t\tremoveNode(excessDomChildren[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// As above, don't diff props during hydration\n\t\tif (!isHydrating || nodeType == 'textarea') {\n\t\t\ti = 'value';\n\t\t\tif (nodeType == 'progress' && inputValue == NULL) {\n\t\t\t\tdom.removeAttribute('value');\n\t\t\t} else if (\n\t\t\t\tinputValue != UNDEFINED &&\n\t\t\t\t// #2756 For the <progress>-element the initial value is 0,\n\t\t\t\t// despite the attribute not being present. When the attribute\n\t\t\t\t// is missing the progress bar is treated as indeterminate.\n\t\t\t\t// To fix that we'll always update it when it is 0 for progress elements\n\t\t\t\t(inputValue !== dom[i] ||\n\t\t\t\t\t(nodeType == 'progress' && !inputValue) ||\n\t\t\t\t\t// This is only for IE 11 to fix <select> value not being updated.\n\t\t\t\t\t// To avoid a stale select value we need to set the option.value\n\t\t\t\t\t// again, which triggers IE11 to re-evaluate the select value\n\t\t\t\t\t(nodeType == 'option' && inputValue != oldProps[i]))\n\t\t\t) {\n\t\t\t\tsetProperty(dom, i, inputValue, oldProps[i], namespace);\n\t\t\t}\n\n\t\t\ti = 'checked';\n\t\t\tif (checked != UNDEFINED && checked != dom[i]) {\n\t\t\t\tsetProperty(dom, i, checked, oldProps[i], namespace);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dom;\n}\n\n/**\n * Invoke or update a ref, depending on whether it is a function or object ref.\n * @param {Ref<any> & { _unmount?: unknown }} ref\n * @param {any} value\n * @param {VNode} vnode\n */\nexport function applyRef(ref, value, vnode) {\n\ttry {\n\t\tif (typeof ref == 'function') {\n\t\t\tlet hasRefUnmount = typeof ref._unmount == 'function';\n\t\t\tif (hasRefUnmount) {\n\t\t\t\t// @ts-ignore TS doesn't like moving narrowing checks into variables\n\t\t\t\tref._unmount();\n\t\t\t}\n\n\t\t\tif (!hasRefUnmount || value != NULL) {\n\t\t\t\t// Store the cleanup function on the function\n\t\t\t\t// instance object itself to avoid shape\n\t\t\t\t// transitioning vnode\n\t\t\t\tref._unmount = ref(value);\n\t\t\t}\n\t\t} else ref.current = value;\n\t} catch (e) {\n\t\toptions._catchError(e, vnode);\n\t}\n}\n\n/**\n * Unmount a virtual node from the tree and apply DOM changes\n * @param {VNode} vnode The virtual node to unmount\n * @param {VNode} parentVNode The parent of the VNode that initiated the unmount\n * @param {boolean} [skipRemove] Flag that indicates that a parent node of the\n * current element is already detached from the DOM.\n */\nexport function unmount(vnode, parentVNode, skipRemove) {\n\tlet r;\n\tif (options.unmount) options.unmount(vnode);\n\n\tif ((r = vnode.ref)) {\n\t\tif (!r.current || r.current == vnode._dom) {\n\t\t\tapplyRef(r, NULL, parentVNode);\n\t\t}\n\t}\n\n\tif ((r = vnode._component) != NULL) {\n\t\tif (r.componentWillUnmount) {\n\t\t\ttry {\n\t\t\t\tr.componentWillUnmount();\n\t\t\t} catch (e) {\n\t\t\t\toptions._catchError(e, parentVNode);\n\t\t\t}\n\t\t}\n\n\t\tr.base = r._parentDom = NULL;\n\t}\n\n\tif ((r = vnode._children)) {\n\t\tfor (let i = 0; i < r.length; i++) {\n\t\t\tif (r[i]) {\n\t\t\t\tunmount(\n\t\t\t\t\tr[i],\n\t\t\t\t\tparentVNode,\n\t\t\t\t\tskipRemove || typeof vnode.type != 'function'\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!skipRemove) {\n\t\tremoveNode(vnode._dom);\n\t}\n\n\tvnode._component = vnode._parent = vnode._dom = UNDEFINED;\n}\n\n/** The `.render()` method for a PFC backing instance. */\nfunction doRender(props, state, context) {\n\treturn this.constructor(props, context);\n}\n", "import { EMPTY_OBJ, NULL } from './constants';\nimport { commitRoot, diff } from './diff/index';\nimport { createElement, Fragment } from './create-element';\nimport options from './options';\nimport { slice } from './util';\n\n/**\n * Render a Preact virtual node into a DOM element\n * @param {import('./internal').ComponentChild} vnode The virtual node to render\n * @param {import('./internal').PreactElement} parentDom The DOM element to render into\n * @param {import('./internal').PreactElement | object} [replaceNode] Optional: Attempt to re-use an\n * existing DOM tree rooted at `replaceNode`\n */\nexport function render(vnode, parentDom, replaceNode) {\n\t// https://github.com/preactjs/preact/issues/3794\n\tif (parentDom == document) {\n\t\tparentDom = document.documentElement;\n\t}\n\n\tif (options._root) options._root(vnode, parentDom);\n\n\t// We abuse the `replaceNode` parameter in `hydrate()` to signal if we are in\n\t// hydration mode or not by passing the `hydrate` function instead of a DOM\n\t// element..\n\tlet isHydrating = typeof replaceNode == 'function';\n\n\t// To be able to support calling `render()` multiple times on the same\n\t// DOM node, we need to obtain a reference to the previous tree. We do\n\t// this by assigning a new `_children` property to DOM nodes which points\n\t// to the last rendered tree. By default this property is not present, which\n\t// means that we are mounting a new tree for the first time.\n\tlet oldVNode = isHydrating\n\t\t? NULL\n\t\t: (replaceNode && replaceNode._children) || parentDom._children;\n\n\tvnode = ((!isHydrating && replaceNode) || parentDom)._children =\n\t\tcreateElement(Fragment, NULL, [vnode]);\n\n\t// List of effects that need to be called after diffing.\n\tlet commitQueue = [],\n\t\trefQueue = [];\n\tdiff(\n\t\tparentDom,\n\t\t// Determine the new vnode tree and store it on the DOM element on\n\t\t// our custom `_children` property.\n\t\tvnode,\n\t\toldVNode || EMPTY_OBJ,\n\t\tEMPTY_OBJ,\n\t\tparentDom.namespaceURI,\n\t\t!isHydrating && replaceNode\n\t\t\t? [replaceNode]\n\t\t\t: oldVNode\n\t\t\t\t? NULL\n\t\t\t\t: parentDom.firstChild\n\t\t\t\t\t? slice.call(parentDom.childNodes)\n\t\t\t\t\t: NULL,\n\t\tcommitQueue,\n\t\t!isHydrating && replaceNode\n\t\t\t? replaceNode\n\t\t\t: oldVNode\n\t\t\t\t? oldVNode._dom\n\t\t\t\t: parentDom.firstChild,\n\t\tisHydrating,\n\t\trefQueue\n\t);\n\n\t// Flush all queued effects\n\tcommitRoot(commitQueue, vnode, refQueue);\n}\n\n/**\n * Update an existing DOM element with data from a Preact virtual node\n * @param {import('./internal').ComponentChild} vnode The virtual node to render\n * @param {import('./internal').PreactElement} parentDom The DOM element to update\n */\nexport function hydrate(vnode, parentDom) {\n\trender(vnode, parentDom, hydrate);\n}\n", "import { assign, slice } from './util';\nimport { createVNode } from './create-element';\nimport { NULL, UNDEFINED } from './constants';\n\n/**\n * Clones the given VNode, optionally adding attributes/props and replacing its\n * children.\n * @param {import('./internal').VNode} vnode The virtual DOM element to clone\n * @param {object} props Attributes/props to add when cloning\n * @param {Array<import('./internal').ComponentChildren>} rest Any additional arguments will be used\n * as replacement children.\n * @returns {import('./internal').VNode}\n */\nexport function cloneElement(vnode, props, children) {\n\tlet normalizedProps = assign({}, vnode.props),\n\t\tkey,\n\t\tref,\n\t\ti;\n\n\tlet defaultProps;\n\n\tif (vnode.type && vnode.type.defaultProps) {\n\t\tdefaultProps = vnode.type.defaultProps;\n\t}\n\n\tfor (i in props) {\n\t\tif (i == 'key') key = props[i];\n\t\telse if (i == 'ref') ref = props[i];\n\t\telse if (props[i] === UNDEFINED && defaultProps != UNDEFINED) {\n\t\t\tnormalizedProps[i] = defaultProps[i];\n\t\t} else {\n\t\t\tnormalizedProps[i] = props[i];\n\t\t}\n\t}\n\n\tif (arguments.length > 2) {\n\t\tnormalizedProps.children =\n\t\t\targuments.length > 3 ? slice.call(arguments, 2) : children;\n\t}\n\n\treturn createVNode(\n\t\tvnode.type,\n\t\tnormalizedProps,\n\t\tkey || vnode.key,\n\t\tref || vnode.ref,\n\t\tNULL\n\t);\n}\n", "import { NULL } from '../constants';\n\n/**\n * Find the closest error boundary to a thrown error and call it\n * @param {object} error The thrown value\n * @param {import('../internal').VNode} vnode The vnode that threw the error that was caught (except\n * for unmounting when this parameter is the highest parent that was being\n * unmounted)\n * @param {import('../internal').VNode} [oldVNode]\n * @param {import('../internal').ErrorInfo} [errorInfo]\n */\nexport function _catchError(error, vnode, oldVNode, errorInfo) {\n\t/** @type {import('../internal').Component} */\n\tlet component,\n\t\t/** @type {import('../internal').ComponentType} */\n\t\tctor,\n\t\t/** @type {boolean} */\n\t\thandled;\n\n\tfor (; (vnode = vnode._parent); ) {\n\t\tif ((component = vnode._component) && !component._processingException) {\n\t\t\ttry {\n\t\t\t\tctor = component.constructor;\n\n\t\t\t\tif (ctor && ctor.getDerivedStateFromError != NULL) {\n\t\t\t\t\tcomponent.setState(ctor.getDerivedStateFromError(error));\n\t\t\t\t\thandled = component._dirty;\n\t\t\t\t}\n\n\t\t\t\tif (component.componentDidCatch != NULL) {\n\t\t\t\t\tcomponent.componentDidCatch(error, errorInfo || {});\n\t\t\t\t\thandled = component._dirty;\n\t\t\t\t}\n\n\t\t\t\t// This is an error boundary. Mark it as having bailed out, and whether it was mid-hydration.\n\t\t\t\tif (handled) {\n\t\t\t\t\treturn (component._pendingError = component);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\terror = e;\n\t\t\t}\n\t\t}\n\t}\n\n\tthrow error;\n}\n", "import { options as _options } from 'preact';\n\n/** @type {number} */\nlet currentIndex;\n\n/** @type {import('./internal').Component} */\nlet currentComponent;\n\n/** @type {import('./internal').Component} */\nlet previousComponent;\n\n/** @type {number} */\nlet currentHook = 0;\n\n/** @type {Array<import('./internal').Component>} */\nlet afterPaintEffects = [];\n\n// Cast to use internal Options type\nconst options = /** @type {import('./internal').Options} */ (_options);\n\nlet oldBeforeDiff = options._diff;\nlet oldBeforeRender = options._render;\nlet oldAfterDiff = options.diffed;\nlet oldCommit = options._commit;\nlet oldBeforeUnmount = options.unmount;\nlet oldRoot = options._root;\n\n// We take the minimum timeout for requestAnimationFrame to ensure that\n// the callback is invoked after the next frame. 35ms is based on a 30hz\n// refresh rate, which is the minimum rate for a smooth user experience.\nconst RAF_TIMEOUT = 35;\nlet prevRaf;\n\n/** @type {(vnode: import('./internal').VNode) => void} */\noptions._diff = vnode => {\n\tcurrentComponent = null;\n\tif (oldBeforeDiff) oldBeforeDiff(vnode);\n};\n\noptions._root = (vnode, parentDom) => {\n\tif (vnode && parentDom._children && parentDom._children._mask) {\n\t\tvnode._mask = parentDom._children._mask;\n\t}\n\n\tif (oldRoot) oldRoot(vnode, parentDom);\n};\n\n/** @type {(vnode: import('./internal').VNode) => void} */\noptions._render = vnode => {\n\tif (oldBeforeRender) oldBeforeRender(vnode);\n\n\tcurrentComponent = vnode._component;\n\tcurrentIndex = 0;\n\n\tconst hooks = currentComponent.__hooks;\n\tif (hooks) {\n\t\tif (previousComponent === currentComponent) {\n\t\t\thooks._pendingEffects = [];\n\t\t\tcurrentComponent._renderCallbacks = [];\n\t\t\thooks._list.some(hookItem => {\n\t\t\t\tif (hookItem._nextValue) {\n\t\t\t\t\thookItem._value = hookItem._nextValue;\n\t\t\t\t}\n\t\t\t\thookItem._pendingArgs = hookItem._nextValue = undefined;\n\t\t\t});\n\t\t} else {\n\t\t\thooks._pendingEffects.some(invokeCleanup);\n\t\t\thooks._pendingEffects.some(invokeEffect);\n\t\t\thooks._pendingEffects = [];\n\t\t\tcurrentIndex = 0;\n\t\t}\n\t}\n\tpreviousComponent = currentComponent;\n};\n\n/** @type {(vnode: import('./internal').VNode) => void} */\noptions.diffed = vnode => {\n\tif (oldAfterDiff) oldAfterDiff(vnode);\n\n\tconst c = vnode._component;\n\tif (c && c.__hooks) {\n\t\tif (c.__hooks._pendingEffects.length) afterPaint(afterPaintEffects.push(c));\n\t\tc.__hooks._list.some(hookItem => {\n\t\t\tif (hookItem._pendingArgs) {\n\t\t\t\thookItem._args = hookItem._pendingArgs;\n\t\t\t}\n\t\t\thookItem._pendingArgs = undefined;\n\t\t});\n\t}\n\tpreviousComponent = currentComponent = null;\n};\n\n// TODO: Improve typing of commitQueue parameter\n/** @type {(vnode: import('./internal').VNode, commitQueue: any) => void} */\noptions._commit = (vnode, commitQueue) => {\n\tcommitQueue.some(component => {\n\t\ttry {\n\t\t\tcomponent._renderCallbacks.some(invokeCleanup);\n\t\t\tcomponent._renderCallbacks = component._renderCallbacks.filter(cb =>\n\t\t\t\tcb._value ? invokeEffect(cb) : true\n\t\t\t);\n\t\t} catch (e) {\n\t\t\tcommitQueue.some(c => {\n\t\t\t\tif (c._renderCallbacks) c._renderCallbacks = [];\n\t\t\t});\n\t\t\tcommitQueue = [];\n\t\t\toptions._catchError(e, component._vnode);\n\t\t}\n\t});\n\n\tif (oldCommit) oldCommit(vnode, commitQueue);\n};\n\n/** @type {(vnode: import('./internal').VNode) => void} */\noptions.unmount = vnode => {\n\tif (oldBeforeUnmount) oldBeforeUnmount(vnode);\n\n\tconst c = vnode._component;\n\tif (c && c.__hooks) {\n\t\tlet hasErrored;\n\t\tc.__hooks._list.some(s => {\n\t\t\ttry {\n\t\t\t\tinvokeCleanup(s);\n\t\t\t} catch (e) {\n\t\t\t\thasErrored = e;\n\t\t\t}\n\t\t});\n\t\tc.__hooks = undefined;\n\t\tif (hasErrored) options._catchError(hasErrored, c._vnode);\n\t}\n};\n\n/**\n * Get a hook's state from the currentComponent\n * @param {number} index The index of the hook to get\n * @param {number} type The index of the hook to get\n * @returns {any}\n */\nfunction getHookState(index, type) {\n\tif (options._hook) {\n\t\toptions._hook(currentComponent, index, currentHook || type);\n\t}\n\tcurrentHook = 0;\n\n\t// Largely inspired by:\n\t// * https://github.com/michael-klein/funcy.js/blob/f6be73468e6ec46b0ff5aa3cc4c9baf72a29025a/src/hooks/core_hooks.mjs\n\t// * https://github.com/michael-klein/funcy.js/blob/650beaa58c43c33a74820a3c98b3c7079cf2e333/src/renderer.mjs\n\t// Other implementations to look at:\n\t// * https://codesandbox.io/s/mnox05qp8\n\tconst hooks =\n\t\tcurrentComponent.__hooks ||\n\t\t(currentComponent.__hooks = {\n\t\t\t_list: [],\n\t\t\t_pendingEffects: []\n\t\t});\n\n\tif (index >= hooks._list.length) {\n\t\thooks._list.push({});\n\t}\n\n\treturn hooks._list[index];\n}\n\n/**\n * @template {unknown} S\n * @param {import('./index').Dispatch<import('./index').StateUpdater<S>>} [initialState]\n * @returns {[S, (state: S) => void]}\n */\nexport function useState(initialState) {\n\tcurrentHook = 1;\n\treturn useReducer(invokeOrReturn, initialState);\n}\n\n/**\n * @template {unknown} S\n * @template {unknown} A\n * @param {import('./index').Reducer<S, A>} reducer\n * @param {import('./index').Dispatch<import('./index').StateUpdater<S>>} initialState\n * @param {(initialState: any) => void} [init]\n * @returns {[ S, (state: S) => void ]}\n */\nexport function useReducer(reducer, initialState, init) {\n\t/** @type {import('./internal').ReducerHookState} */\n\tconst hookState = getHookState(currentIndex++, 2);\n\thookState._reducer = reducer;\n\tif (!hookState._component) {\n\t\thookState._value = [\n\t\t\t!init ? invokeOrReturn(undefined, initialState) : init(initialState),\n\n\t\t\taction => {\n\t\t\t\tconst currentValue = hookState._nextValue\n\t\t\t\t\t? hookState._nextValue[0]\n\t\t\t\t\t: hookState._value[0];\n\t\t\t\tconst nextValue = hookState._reducer(currentValue, action);\n\n\t\t\t\tif (currentValue !== nextValue) {\n\t\t\t\t\thookState._nextValue = [nextValue, hookState._value[1]];\n\t\t\t\t\thookState._component.setState({});\n\t\t\t\t}\n\t\t\t}\n\t\t];\n\n\t\thookState._component = currentComponent;\n\n\t\tif (!currentComponent._hasScuFromHooks) {\n\t\t\tcurrentComponent._hasScuFromHooks = true;\n\t\t\tlet prevScu = currentComponent.shouldComponentUpdate;\n\t\t\tconst prevCWU = currentComponent.componentWillUpdate;\n\n\t\t\t// If we're dealing with a forced update `shouldComponentUpdate` will\n\t\t\t// not be called. But we use that to update the hook values, so we\n\t\t\t// need to call it.\n\t\t\tcurrentComponent.componentWillUpdate = function (p, s, c) {\n\t\t\t\tif (this._force) {\n\t\t\t\t\tlet tmp = prevScu;\n\t\t\t\t\t// Clear to avoid other sCU hooks from being called\n\t\t\t\t\tprevScu = undefined;\n\t\t\t\t\tupdateHookState(p, s, c);\n\t\t\t\t\tprevScu = tmp;\n\t\t\t\t}\n\n\t\t\t\tif (prevCWU) prevCWU.call(this, p, s, c);\n\t\t\t};\n\n\t\t\t// This SCU has the purpose of bailing out after repeated updates\n\t\t\t// to stateful hooks.\n\t\t\t// we store the next value in _nextValue[0] and keep doing that for all\n\t\t\t// state setters, if we have next states and\n\t\t\t// all next states within a component end up being equal to their original state\n\t\t\t// we are safe to bail out for this specific component.\n\t\t\t/**\n\t\t\t *\n\t\t\t * @type {import('./internal').Component[\"shouldComponentUpdate\"]}\n\t\t\t */\n\t\t\t// @ts-ignore - We don't use TS to downtranspile\n\t\t\t// eslint-disable-next-line no-inner-declarations\n\t\t\tfunction updateHookState(p, s, c) {\n\t\t\t\tif (!hookState._component.__hooks) return true;\n\n\t\t\t\tconst stateHooks = hookState._component.__hooks._list.filter(\n\t\t\t\t\tx => x._component\n\t\t\t\t);\n\n\t\t\t\tconst allHooksEmpty = stateHooks.every(x => !x._nextValue);\n\t\t\t\t// When we have no updated hooks in the component we invoke the previous SCU or\n\t\t\t\t// traverse the VDOM tree further.\n\t\t\t\tif (allHooksEmpty) {\n\t\t\t\t\treturn prevScu ? prevScu.call(this, p, s, c) : true;\n\t\t\t\t}\n\n\t\t\t\t// We check whether we have components with a nextValue set that\n\t\t\t\t// have values that aren't equal to one another this pushes\n\t\t\t\t// us to update further down the tree\n\t\t\t\tlet shouldUpdate = hookState._component.props !== p;\n\t\t\t\tstateHooks.some(hookItem => {\n\t\t\t\t\tif (hookItem._nextValue) {\n\t\t\t\t\t\tconst currentValue = hookItem._value[0];\n\t\t\t\t\t\thookItem._value = hookItem._nextValue;\n\t\t\t\t\t\thookItem._nextValue = undefined;\n\t\t\t\t\t\tif (currentValue !== hookItem._value[0]) shouldUpdate = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn prevScu\n\t\t\t\t\t? prevScu.call(this, p, s, c) || shouldUpdate\n\t\t\t\t\t: shouldUpdate;\n\t\t\t}\n\n\t\t\tcurrentComponent.shouldComponentUpdate = updateHookState;\n\t\t}\n\t}\n\n\treturn hookState._nextValue || hookState._value;\n}\n\n/**\n * @param {import('./internal').Effect} callback\n * @param {unknown[]} args\n * @returns {void}\n */\nexport function useEffect(callback, args) {\n\t/** @type {import('./internal').EffectHookState} */\n\tconst state = getHookState(currentIndex++, 3);\n\tif (!options._skipEffects && argsChanged(state._args, args)) {\n\t\tstate._value = callback;\n\t\tstate._pendingArgs = args;\n\n\t\tcurrentComponent.__hooks._pendingEffects.push(state);\n\t}\n}\n\n/**\n * @param {import('./internal').Effect} callback\n * @param {unknown[]} args\n * @returns {void}\n */\nexport function useLayoutEffect(callback, args) {\n\t/** @type {import('./internal').EffectHookState} */\n\tconst state = getHookState(currentIndex++, 4);\n\tif (!options._skipEffects && argsChanged(state._args, args)) {\n\t\tstate._value = callback;\n\t\tstate._pendingArgs = args;\n\n\t\tcurrentComponent._renderCallbacks.push(state);\n\t}\n}\n\n/** @type {(initialValue: unknown) => unknown} */\nexport function useRef(initialValue) {\n\tcurrentHook = 5;\n\treturn useMemo(() => ({ current: initialValue }), []);\n}\n\n/**\n * @param {object} ref\n * @param {() => object} createHandle\n * @param {unknown[]} args\n * @returns {void}\n */\nexport function useImperativeHandle(ref, createHandle, args) {\n\tcurrentHook = 6;\n\tuseLayoutEffect(\n\t\t() => {\n\t\t\tif (typeof ref == 'function') {\n\t\t\t\tconst result = ref(createHandle());\n\t\t\t\treturn () => {\n\t\t\t\t\tref(null);\n\t\t\t\t\tif (result && typeof result == 'function') result();\n\t\t\t\t};\n\t\t\t} else if (ref) {\n\t\t\t\tref.current = createHandle();\n\t\t\t\treturn () => (ref.current = null);\n\t\t\t}\n\t\t},\n\t\targs == null ? args : args.concat(ref)\n\t);\n}\n\n/**\n * @template {unknown} T\n * @param {() => T} factory\n * @param {unknown[]} args\n * @returns {T}\n */\nexport function useMemo(factory, args) {\n\t/** @type {import('./internal').MemoHookState<T>} */\n\tconst state = getHookState(currentIndex++, 7);\n\tif (argsChanged(state._args, args)) {\n\t\tstate._value = factory();\n\t\tstate._args = args;\n\t\tstate._factory = factory;\n\t}\n\n\treturn state._value;\n}\n\n/**\n * @param {() => void} callback\n * @param {unknown[]} args\n * @returns {() => void}\n */\nexport function useCallback(callback, args) {\n\tcurrentHook = 8;\n\treturn useMemo(() => callback, args);\n}\n\n/**\n * @param {import('./internal').PreactContext} context\n */\nexport function useContext(context) {\n\tconst provider = currentComponent.context[context._id];\n\t// We could skip this call here, but than we'd not call\n\t// `options._hook`. We need to do that in order to make\n\t// the devtools aware of this hook.\n\t/** @type {import('./internal').ContextHookState} */\n\tconst state = getHookState(currentIndex++, 9);\n\t// The devtools needs access to the context object to\n\t// be able to pull of the default value when no provider\n\t// is present in the tree.\n\tstate._context = context;\n\tif (!provider) return context._defaultValue;\n\t// This is probably not safe to convert to \"!\"\n\tif (state._value == null) {\n\t\tstate._value = true;\n\t\tprovider.sub(currentComponent);\n\t}\n\treturn provider.props.value;\n}\n\n/**\n * Display a custom label for a custom hook for the devtools panel\n * @type {<T>(value: T, cb?: (value: T) => string | number) => void}\n */\nexport function useDebugValue(value, formatter) {\n\tif (options.useDebugValue) {\n\t\toptions.useDebugValue(\n\t\t\tformatter ? formatter(value) : /** @type {any}*/ (value)\n\t\t);\n\t}\n}\n\n/**\n * @param {(error: unknown, errorInfo: import('preact').ErrorInfo) => void} cb\n * @returns {[unknown, () => void]}\n */\nexport function useErrorBoundary(cb) {\n\t/** @type {import('./internal').ErrorBoundaryHookState} */\n\tconst state = getHookState(currentIndex++, 10);\n\tconst errState = useState();\n\tstate._value = cb;\n\tif (!currentComponent.componentDidCatch) {\n\t\tcurrentComponent.componentDidCatch = (err, errorInfo) => {\n\t\t\tif (state._value) state._value(err, errorInfo);\n\t\t\terrState[1](err);\n\t\t};\n\t}\n\treturn [\n\t\terrState[0],\n\t\t() => {\n\t\t\terrState[1](undefined);\n\t\t}\n\t];\n}\n\n/** @type {() => string} */\nexport function useId() {\n\t/** @type {import('./internal').IdHookState} */\n\tconst state = getHookState(currentIndex++, 11);\n\tif (!state._value) {\n\t\t// Grab either the root node or the nearest async boundary node.\n\t\t/** @type {import('./internal').VNode} */\n\t\tlet root = currentComponent._vnode;\n\t\twhile (root !== null && !root._mask && root._parent !== null) {\n\t\t\troot = root._parent;\n\t\t}\n\n\t\tlet mask = root._mask || (root._mask = [0, 0]);\n\t\tstate._value = 'P' + mask[0] + '-' + mask[1]++;\n\t}\n\n\treturn state._value;\n}\n\n/**\n * After paint effects consumer.\n */\nfunction flushAfterPaintEffects() {\n\tlet component;\n\twhile ((component = afterPaintEffects.shift())) {\n\t\tconst hooks = component.__hooks;\n\t\tif (!component._parentDom || !hooks) continue;\n\t\ttry {\n\t\t\thooks._pendingEffects.some(invokeCleanup);\n\t\t\thooks._pendingEffects.some(invokeEffect);\n\t\t\thooks._pendingEffects = [];\n\t\t} catch (e) {\n\t\t\thooks._pendingEffects = [];\n\t\t\toptions._catchError(e, component._vnode);\n\t\t}\n\t}\n}\n\nlet HAS_RAF = typeof requestAnimationFrame == 'function';\n\n/**\n * Schedule a callback to be invoked after the browser has a chance to paint a new frame.\n * Do this by combining requestAnimationFrame (rAF) + setTimeout to invoke a callback after\n * the next browser frame.\n *\n * Also, schedule a timeout in parallel to the the rAF to ensure the callback is invoked\n * even if RAF doesn't fire (for example if the browser tab is not visible)\n *\n * @param {() => void} callback\n */\nfunction afterNextFrame(callback) {\n\tconst done = () => {\n\t\tclearTimeout(timeout);\n\t\tif (HAS_RAF) cancelAnimationFrame(raf);\n\t\tsetTimeout(callback);\n\t};\n\tconst timeout = setTimeout(done, RAF_TIMEOUT);\n\n\tlet raf;\n\tif (HAS_RAF) {\n\t\traf = requestAnimationFrame(done);\n\t}\n}\n\n// Note: if someone used options.debounceRendering = requestAnimationFrame,\n// then effects will ALWAYS run on the NEXT frame instead of the current one, incurring a ~16ms delay.\n// Perhaps this is not such a big deal.\n/**\n * Schedule afterPaintEffects flush after the browser paints\n * @param {number} newQueueLength\n * @returns {void}\n */\nfunction afterPaint(newQueueLength) {\n\tif (newQueueLength === 1 || prevRaf !== options.requestAnimationFrame) {\n\t\tprevRaf = options.requestAnimationFrame;\n\t\t(prevRaf || afterNextFrame)(flushAfterPaintEffects);\n\t}\n}\n\n/**\n * @param {import('./internal').HookState} hook\n * @returns {void}\n */\nfunction invokeCleanup(hook) {\n\t// A hook cleanup can introduce a call to render which creates a new root, this will call options.vnode\n\t// and move the currentComponent away.\n\tconst comp = currentComponent;\n\tlet cleanup = hook._cleanup;\n\tif (typeof cleanup == 'function') {\n\t\thook._cleanup = undefined;\n\t\tcleanup();\n\t}\n\n\tcurrentComponent = comp;\n}\n\n/**\n * Invoke a Hook's effect\n * @param {import('./internal').EffectHookState} hook\n * @returns {void}\n */\nfunction invokeEffect(hook) {\n\t// A hook call can introduce a call to render which creates a new root, this will call options.vnode\n\t// and move the currentComponent away.\n\tconst comp = currentComponent;\n\thook._cleanup = hook._value();\n\tcurrentComponent = comp;\n}\n\n/**\n * @param {unknown[]} oldArgs\n * @param {unknown[]} newArgs\n * @returns {boolean}\n */\nfunction argsChanged(oldArgs, newArgs) {\n\treturn (\n\t\t!oldArgs ||\n\t\toldArgs.length !== newArgs.length ||\n\t\tnewArgs.some((arg, index) => arg !== oldArgs[index])\n\t);\n}\n\n/**\n * @template Arg\n * @param {Arg} arg\n * @param {(arg: Arg) => any} f\n * @returns {any}\n */\nfunction invokeOrReturn(arg, f) {\n\treturn typeof f == 'function' ? f(arg) : f;\n}\n", "// An named symbol/brand for detecting Signal instances even when they weren't\n// created using the same signals library version.\nconst BRAND_SYMBOL = Symbol.for(\"preact-signals\");\n\n// Flags for Computed and Effect.\nconst RUNNING = 1 << 0;\nconst NOTIFIED = 1 << 1;\nconst OUTDATED = 1 << 2;\nconst DISPOSED = 1 << 3;\nconst HAS_ERROR = 1 << 4;\nconst TRACKING = 1 << 5;\n\n// A linked list node used to track dependencies (sources) and dependents (targets).\n// Also used to remember the source's last version number that the target saw.\ntype Node = {\n\t// A source whose value the target depends on.\n\t_source: Signal;\n\t_prevSource?: Node;\n\t_nextSource?: Node;\n\n\t// A target that depends on the source and should be notified when the source changes.\n\t_target: Computed | Effect;\n\t_prevTarget?: Node;\n\t_nextTarget?: Node;\n\n\t// The version number of the source that target has last seen. We use version numbers\n\t// instead of storing the source value, because source values can take arbitrary amount\n\t// of memory, and computeds could hang on to them forever because they're lazily evaluated.\n\t// Use the special value -1 to mark potentially unused but recyclable nodes.\n\t_version: number;\n\n\t// Used to remember & roll back the source's previous `._node` value when entering &\n\t// exiting a new evaluation context.\n\t_rollbackNode?: Node;\n};\n\nfunction startBatch() {\n\tbatchDepth++;\n}\n\nfunction endBatch() {\n\tif (batchDepth > 1) {\n\t\tbatchDepth--;\n\t\treturn;\n\t}\n\n\tlet error: unknown;\n\tlet hasError = false;\n\treconcileBatchSnapshots();\n\n\twhile (batchedEffect !== undefined) {\n\t\tlet effect: Effect | undefined = batchedEffect;\n\t\tbatchedEffect = undefined;\n\n\t\tbatchIteration++;\n\n\t\twhile (effect !== undefined) {\n\t\t\tconst next: Effect | undefined = effect._nextBatchedEffect;\n\t\t\teffect._nextBatchedEffect = undefined;\n\t\t\teffect._flags &= ~NOTIFIED;\n\n\t\t\tif (!(effect._flags & DISPOSED) && needsToRecompute(effect)) {\n\t\t\t\ttry {\n\t\t\t\t\teffect._callback();\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (!hasError) {\n\t\t\t\t\t\terror = err;\n\t\t\t\t\t\thasError = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\teffect = next;\n\t\t}\n\t}\n\tbatchIteration = 0;\n\tbatchDepth--;\n\n\tif (hasError) {\n\t\tthrow error;\n\t}\n}\n\n/**\n * Combine multiple value updates into one \"commit\" at the end of the provided callback.\n *\n * Batches can be nested and changes are only flushed once the outermost batch callback\n * completes.\n *\n * Accessing a signal that has been modified within a batch will reflect its updated\n * value.\n *\n * @param fn The callback function.\n * @returns The value returned by the callback.\n */\nfunction batch<T>(fn: () => T): T {\n\tif (batchDepth > 0) {\n\t\treturn fn();\n\t}\n\tcurrentBatchSnapshotVersion = ++batchSnapshotVersion;\n\t/*@__INLINE__**/ startBatch();\n\ttry {\n\t\treturn fn();\n\t} finally {\n\t\tendBatch();\n\t}\n}\n\n// Currently evaluated computed or effect.\nlet evalContext: Computed | Effect | undefined = undefined;\n\n/**\n * Run a callback function that can access signal values without\n * subscribing to the signal updates.\n *\n * @param fn The callback function.\n * @returns The value returned by the callback.\n */\nfunction untracked<T>(fn: () => T): T {\n\tconst prevContext = evalContext;\n\tevalContext = undefined;\n\ttry {\n\t\treturn fn();\n\t} finally {\n\t\tevalContext = prevContext;\n\t}\n}\n\n// Effects collected into a batch.\nlet batchedEffect: Effect | undefined = undefined;\nlet batchDepth = 0;\nlet batchIteration = 0;\n\ntype BatchSnapshot = {\n\t_source: Signal;\n\t_value: unknown;\n\t_version: number;\n\t_next?: BatchSnapshot;\n};\n\nlet batchSnapshotVersion = 0;\nlet currentBatchSnapshotVersion = 0;\nlet batchSnapshots: BatchSnapshot | undefined = undefined;\n\n// A global version number for signals, used for fast-pathing repeated\n// computed.peek()/computed.value calls when nothing has changed globally.\nlet globalVersion = 0;\n\nfunction recordBatchSnapshot(source: Signal) {\n\t// Only capture writes during the user-visible batch callback, not during effect flush.\n\tif (batchDepth === 0 || batchIteration !== 0) {\n\t\treturn;\n\t}\n\n\tif (source._batchSnapshotVersion !== currentBatchSnapshotVersion) {\n\t\tsource._batchSnapshotVersion = currentBatchSnapshotVersion;\n\t\tbatchSnapshots = {\n\t\t\t_source: source,\n\t\t\t_value: source._value,\n\t\t\t_version: source._version,\n\t\t\t_next: batchSnapshots,\n\t\t};\n\t}\n}\n\nfunction reconcileBatchSnapshots() {\n\tlet snapshots = batchSnapshots;\n\tbatchSnapshots = undefined;\n\n\twhile (snapshots !== undefined) {\n\t\tif (snapshots._source._value === snapshots._value) {\n\t\t\tsnapshots._source._version = snapshots._version;\n\t\t}\n\t\tsnapshots = snapshots._next;\n\t}\n}\n\nfunction addDependency(signal: Signal): Node | undefined {\n\tif (evalContext === undefined) {\n\t\treturn undefined;\n\t}\n\n\tlet node = signal._node;\n\tif (node === undefined || node._target !== evalContext) {\n\t\t/**\n\t\t * `signal` is a new dependency. Create a new dependency node, and set it\n\t\t * as the tail of the current context's dependency list. e.g:\n\t\t *\n\t\t * { A <-> B }\n\t\t * ↑ ↑\n\t\t * tail node (new)\n\t\t * ↓\n\t\t * { A <-> B <-> C }\n\t\t * ↑\n\t\t * tail (evalContext._sources)\n\t\t */\n\t\tnode = {\n\t\t\t_version: 0,\n\t\t\t_source: signal,\n\t\t\t_prevSource: evalContext._sources,\n\t\t\t_nextSource: undefined,\n\t\t\t_target: evalContext,\n\t\t\t_prevTarget: undefined,\n\t\t\t_nextTarget: undefined,\n\t\t\t_rollbackNode: node,\n\t\t};\n\n\t\tif (evalContext._sources !== undefined) {\n\t\t\tevalContext._sources._nextSource = node;\n\t\t}\n\t\tevalContext._sources = node;\n\t\tsignal._node = node;\n\n\t\t// Subscribe to change notifications from this dependency if we're in an effect\n\t\t// OR evaluating a computed signal that in turn has subscribers.\n\t\tif (evalContext._flags & TRACKING) {\n\t\t\tsignal._subscribe(node);\n\t\t}\n\t\treturn node;\n\t} else if (node._version === -1) {\n\t\t// `signal` is an existing dependency from a previous evaluation. Reuse it.\n\t\tnode._version = 0;\n\n\t\t/**\n\t\t * If `node` is not already the current tail of the dependency list (i.e.\n\t\t * there is a next node in the list), then make the `node` the new tail. e.g:\n\t\t *\n\t\t * { A <-> B <-> C <-> D }\n\t\t * ↑ ↑\n\t\t * node ┌─── tail (evalContext._sources)\n\t\t * └─────│─────┐\n\t\t * ↓ ↓\n\t\t * { A <-> C <-> D <-> B }\n\t\t * ↑\n\t\t * tail (evalContext._sources)\n\t\t */\n\t\tif (node._nextSource !== undefined) {\n\t\t\tnode._nextSource._prevSource = node._prevSource;\n\n\t\t\tif (node._prevSource !== undefined) {\n\t\t\t\tnode._prevSource._nextSource = node._nextSource;\n\t\t\t}\n\n\t\t\tnode._prevSource = evalContext._sources;\n\t\t\tnode._nextSource = undefined;\n\n\t\t\tevalContext._sources!._nextSource = node;\n\t\t\tevalContext._sources = node;\n\t\t}\n\n\t\t// We can assume that the currently evaluated effect / computed signal is already\n\t\t// subscribed to change notifications from `signal` if needed.\n\t\treturn node;\n\t}\n\treturn undefined;\n}\n\n//#region Signal\n\n/**\n * The base class for plain and computed signals.\n */\n//\n// A function with the same name is defined later, so we need to ignore TypeScript's\n// warning about a redeclared variable.\n//\n// The class is declared here, but later implemented with ES5-style prototypes.\n// This enables better control of the transpiled output size.\n// @ts-ignore: \"Cannot redeclare exported variable 'Signal'.\"\ndeclare class Signal<T = any> {\n\t/** @internal */\n\t_value: unknown;\n\n\t/**\n\t * @internal\n\t * Version numbers should always be >= 0, because the special value -1 is used\n\t * by Nodes to signify potentially unused but recyclable nodes.\n\t */\n\t_version: number;\n\n\t/** @internal */\n\t_node?: Node;\n\n\t/** @internal */\n\t_targets?: Node;\n\n\t/** @internal */\n\t_batchSnapshotVersion: number;\n\n\tconstructor(value?: T, options?: SignalOptions<T>);\n\n\t/** @internal */\n\t_refresh(): boolean;\n\n\t/** @internal */\n\t_subscribe(node: Node): void;\n\n\t/** @internal */\n\t_unsubscribe(node: Node): void;\n\n\t/** @internal */\n\t_watched?(this: Signal<T>): void;\n\n\t/** @internal */\n\t_unwatched?(this: Signal<T>): void;\n\n\tsubscribe(fn: (value: T) => void): () => void;\n\n\tname?: string;\n\n\tvalueOf(): T;\n\n\ttoString(): string;\n\n\ttoJSON(): T;\n\n\tpeek(): T;\n\n\tbrand: typeof BRAND_SYMBOL;\n\n\tget value(): T;\n\tset value(value: T);\n}\n\nexport interface SignalOptions<T = any> {\n\twatched?: (this: Signal<T>) => void;\n\tunwatched?: (this: Signal<T>) => void;\n\tname?: string;\n}\n\n/** @internal */\n// A class with the same name has already been declared, so we need to ignore\n// TypeScript's warning about a redeclared variable.\n//\n// The previously declared class is implemented here with ES5-style prototypes.\n// This enables better control of the transpiled output size.\n// @ts-ignore: \"Cannot redeclare exported variable 'Signal'.\"\nfunction Signal(this: Signal, value?: unknown, options?: SignalOptions) {\n\tthis._value = value;\n\tthis._version = 0;\n\tthis._node = undefined;\n\tthis._targets = undefined;\n\tthis._batchSnapshotVersion = 0;\n\tthis._watched = options?.watched;\n\tthis._unwatched = options?.unwatched;\n\tthis.name = options?.name;\n}\n\nSignal.prototype.brand = BRAND_SYMBOL;\n\nSignal.prototype._refresh = function () {\n\treturn true;\n};\n\nSignal.prototype._subscribe = function (node) {\n\tconst targets = this._targets;\n\tif (targets !== node && node._prevTarget === undefined) {\n\t\tnode._nextTarget = targets;\n\t\tthis._targets = node;\n\n\t\tif (targets !== undefined) {\n\t\t\ttargets._prevTarget = node;\n\t\t} else {\n\t\t\tuntracked(() => {\n\t\t\t\tthis._watched?.call(this);\n\t\t\t});\n\t\t}\n\t}\n};\n\nSignal.prototype._unsubscribe = function (node) {\n\t// Only run the unsubscribe step if the signal has any subscribers to begin with.\n\tif (this._targets !== undefined) {\n\t\tconst prev = node._prevTarget;\n\t\tconst next = node._nextTarget;\n\t\tif (prev !== undefined) {\n\t\t\tprev._nextTarget = next;\n\t\t\tnode._prevTarget = undefined;\n\t\t}\n\n\t\tif (next !== undefined) {\n\t\t\tnext._prevTarget = prev;\n\t\t\tnode._nextTarget = undefined;\n\t\t}\n\n\t\tif (node === this._targets) {\n\t\t\tthis._targets = next;\n\t\t\tif (next === undefined) {\n\t\t\t\tuntracked(() => {\n\t\t\t\t\tthis._unwatched?.call(this);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n};\n\nSignal.prototype.subscribe = function (fn) {\n\treturn effect(\n\t\t() => {\n\t\t\tconst value = this.value;\n\t\t\tconst prevContext = evalContext;\n\t\t\tevalContext = undefined;\n\t\t\ttry {\n\t\t\t\tfn(value);\n\t\t\t} finally {\n\t\t\t\tevalContext = prevContext;\n\t\t\t}\n\t\t},\n\t\t{ name: \"sub\" }\n\t);\n};\n\nSignal.prototype.valueOf = function () {\n\treturn this.value;\n};\n\nSignal.prototype.toString = function () {\n\treturn this.value + \"\";\n};\n\nSignal.prototype.toJSON = function () {\n\treturn this.value;\n};\n\nSignal.prototype.peek = function () {\n\treturn untracked(() => this.value);\n};\n\nObject.defineProperty(Signal.prototype, \"value\", {\n\tget(this: Signal) {\n\t\tconst node = addDependency(this);\n\t\tif (node !== undefined) {\n\t\t\tnode._version = this._version;\n\t\t}\n\t\treturn this._value;\n\t},\n\tset(this: Signal, value) {\n\t\tif (value !== this._value) {\n\t\t\tif (batchIteration > 100) {\n\t\t\t\tthrow new Error(\"Cycle detected\");\n\t\t\t}\n\n\t\t\trecordBatchSnapshot(this);\n\t\t\tthis._value = value;\n\t\t\tthis._version++;\n\t\t\tglobalVersion++;\n\n\t\t\t/**@__INLINE__*/ startBatch();\n\t\t\ttry {\n\t\t\t\tfor (\n\t\t\t\t\tlet node = this._targets;\n\t\t\t\t\tnode !== undefined;\n\t\t\t\t\tnode = node._nextTarget\n\t\t\t\t) {\n\t\t\t\t\tnode._target._notify();\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tendBatch();\n\t\t\t}\n\t\t}\n\t},\n});\n\n/**\n * Create a new plain signal.\n *\n * @param value The initial value for the signal.\n * @returns A new signal.\n */\nexport function signal<T>(value: T, options?: SignalOptions<T>): Signal<T>;\nexport function signal<T = undefined>(): Signal<T | undefined>;\nexport function signal<T>(value?: T, options?: SignalOptions<T>): Signal<T> {\n\treturn new Signal(value, options);\n}\n\n//#endregion Signal\n\n//#region Computed\n\nfunction needsToRecompute(target: Computed | Effect): boolean {\n\t// Check the dependencies for changed values. The dependency list is already\n\t// in order of use. Therefore if multiple dependencies have changed values, only\n\t// the first used dependency is re-evaluated at this point.\n\tfor (\n\t\tlet node = target._sources;\n\t\tnode !== undefined;\n\t\tnode = node._nextSource\n\t) {\n\t\tif (\n\t\t\t// If the dependency has definitely been updated since its version number\n\t\t\t// was observed, then we need to recompute. This first check is not strictly\n\t\t\t// necessary for correctness, but allows us to skip the refresh call if the\n\t\t\t// dependency has already been updated.\n\t\t\tnode._source._version !== node._version ||\n\t\t\t// Refresh the dependency. If there's something blocking the refresh (e.g. a\n\t\t\t// dependency cycle), then we need to recompute.\n\t\t\t!node._source._refresh() ||\n\t\t\t// If the dependency got a new version after the refresh, then we need to recompute.\n\t\t\tnode._source._version !== node._version\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t}\n\t// If none of the dependencies have changed values since last recompute then\n\t// there's no need to recompute.\n\treturn false;\n}\n\nfunction prepareSources(target: Computed | Effect) {\n\t/**\n\t * 1. Mark all current sources as re-usable nodes (version: -1)\n\t * 2. Set a rollback node if the current node is being used in a different context\n\t * 3. Point 'target._sources' to the tail of the doubly-linked list, e.g:\n\t *\n\t * { undefined <- A <-> B <-> C -> undefined }\n\t * ↑ ↑\n\t * │ └──────┐\n\t * target._sources = A; (node is head) │\n\t * ↓ │\n\t * target._sources = C; (node is tail) ─┘\n\t */\n\tfor (\n\t\tlet node = target._sources;\n\t\tnode !== undefined;\n\t\tnode = node._nextSource\n\t) {\n\t\tconst rollbackNode = node._source._node;\n\t\tif (rollbackNode !== undefined) {\n\t\t\tnode._rollbackNode = rollbackNode;\n\t\t}\n\t\tnode._source._node = node;\n\t\tnode._version = -1;\n\n\t\tif (node._nextSource === undefined) {\n\t\t\ttarget._sources = node;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nfunction cleanupSources(target: Computed | Effect) {\n\tlet node = target._sources;\n\tlet head: Node | undefined = undefined;\n\n\t/**\n\t * At this point 'target._sources' points to the tail of the doubly-linked list.\n\t * It contains all existing sources + new sources in order of use.\n\t * Iterate backwards until we find the head node while dropping old dependencies.\n\t */\n\twhile (node !== undefined) {\n\t\tconst prev = node._prevSource;\n\n\t\t/**\n\t\t * The node was not re-used, unsubscribe from its change notifications and remove itself\n\t\t * from the doubly-linked list. e.g:\n\t\t *\n\t\t * { A <-> B <-> C }\n\t\t * ↓\n\t\t * { A <-> C }\n\t\t */\n\t\tif (node._version === -1) {\n\t\t\tnode._source._unsubscribe(node);\n\n\t\t\tif (prev !== undefined) {\n\t\t\t\tprev._nextSource = node._nextSource;\n\t\t\t}\n\t\t\tif (node._nextSource !== undefined) {\n\t\t\t\tnode._nextSource._prevSource = prev;\n\t\t\t}\n\t\t} else {\n\t\t\t/**\n\t\t\t * The new head is the last node seen which wasn't removed/unsubscribed\n\t\t\t * from the doubly-linked list. e.g:\n\t\t\t *\n\t\t\t * { A <-> B <-> C }\n\t\t\t * ↑ ↑ ↑\n\t\t\t * │ │ └ head = node\n\t\t\t * │ └ head = node\n\t\t\t * └ head = node\n\t\t\t */\n\t\t\thead = node;\n\t\t}\n\n\t\tnode._source._node = node._rollbackNode;\n\t\tif (node._rollbackNode !== undefined) {\n\t\t\tnode._rollbackNode = undefined;\n\t\t}\n\n\t\tnode = prev;\n\t}\n\n\ttarget._sources = head;\n}\n\n/**\n * The base class for computed signals.\n */\ndeclare class Computed<T = any> extends Signal<T> {\n\t_fn: () => T;\n\t_sources?: Node;\n\t_globalVersion: number;\n\t_flags: number;\n\n\tconstructor(fn: () => T, options?: SignalOptions<T>);\n\n\t_notify(): void;\n\tget value(): T;\n}\n\n/** @internal */\nfunction Computed(this: Computed, fn: () => unknown, options?: SignalOptions) {\n\tSignal.call(this, undefined);\n\n\tthis._fn = fn;\n\tthis._sources = undefined;\n\tthis._globalVersion = globalVersion - 1;\n\tthis._flags = OUTDATED;\n\tthis._watched = options?.watched;\n\tthis._unwatched = options?.unwatched;\n\tthis.name = options?.name;\n}\n\nComputed.prototype = new Signal() as Computed;\n\nComputed.prototype._refresh = function () {\n\tthis._flags &= ~NOTIFIED;\n\n\tif (this._flags & RUNNING) {\n\t\treturn false;\n\t}\n\n\t// If this computed signal has subscribed to updates from its dependencies\n\t// (TRACKING flag set) and none of them have notified about changes (OUTDATED\n\t// flag not set), then the computed value can't have changed.\n\tif ((this._flags & (OUTDATED | TRACKING)) === TRACKING) {\n\t\treturn true;\n\t}\n\tthis._flags &= ~OUTDATED;\n\n\tif (this._globalVersion === globalVersion) {\n\t\treturn true;\n\t}\n\tthis._globalVersion = globalVersion;\n\n\t// Mark this computed signal running before checking the dependencies for value\n\t// changes, so that the RUNNING flag can be used to notice cyclical dependencies.\n\tthis._flags |= RUNNING;\n\tif (this._version > 0 && !needsToRecompute(this)) {\n\t\tthis._flags &= ~RUNNING;\n\t\treturn true;\n\t}\n\n\tconst prevContext = evalContext;\n\ttry {\n\t\tprepareSources(this);\n\t\tevalContext = this;\n\t\tconst value = this._fn();\n\t\tif (\n\t\t\tthis._flags & HAS_ERROR ||\n\t\t\tthis._value !== value ||\n\t\t\tthis._version === 0\n\t\t) {\n\t\t\tthis._value = value;\n\t\t\tthis._flags &= ~HAS_ERROR;\n\t\t\tthis._version++;\n\t\t}\n\t} catch (err) {\n\t\tthis._value = err;\n\t\tthis._flags |= HAS_ERROR;\n\t\tthis._version++;\n\t}\n\tevalContext = prevContext;\n\tcleanupSources(this);\n\tthis._flags &= ~RUNNING;\n\treturn true;\n};\n\nComputed.prototype._subscribe = function (node) {\n\tif (this._targets === undefined) {\n\t\tthis._flags |= OUTDATED | TRACKING;\n\n\t\t// A computed signal subscribes lazily to its dependencies when it\n\t\t// gets its first subscriber.\n\t\tfor (\n\t\t\tlet node = this._sources;\n\t\t\tnode !== undefined;\n\t\t\tnode = node._nextSource\n\t\t) {\n\t\t\tnode._source._subscribe(node);\n\t\t}\n\t}\n\tSignal.prototype._subscribe.call(this, node);\n};\n\nComputed.prototype._unsubscribe = function (node) {\n\t// Only run the unsubscribe step if the computed signal has any subscribers.\n\tif (this._targets !== undefined) {\n\t\tSignal.prototype._unsubscribe.call(this, node);\n\n\t\t// Computed signal unsubscribes from its dependencies when it loses its last subscriber.\n\t\t// This makes it possible for unreferences subgraphs of computed signals to get garbage collected.\n\t\tif (this._targets === undefined) {\n\t\t\tthis._flags &= ~TRACKING;\n\n\t\t\tfor (\n\t\t\t\tlet node = this._sources;\n\t\t\t\tnode !== undefined;\n\t\t\t\tnode = node._nextSource\n\t\t\t) {\n\t\t\t\tnode._source._unsubscribe(node);\n\t\t\t}\n\t\t}\n\t}\n};\n\nComputed.prototype._notify = function () {\n\tif (!(this._flags & NOTIFIED)) {\n\t\tthis._flags |= OUTDATED | NOTIFIED;\n\n\t\tfor (\n\t\t\tlet node = this._targets;\n\t\t\tnode !== undefined;\n\t\t\tnode = node._nextTarget\n\t\t) {\n\t\t\tnode._target._notify();\n\t\t}\n\t}\n};\n\nObject.defineProperty(Computed.prototype, \"value\", {\n\tget(this: Computed) {\n\t\tif (this._flags & RUNNING) {\n\t\t\tthrow new Error(\"Cycle detected\");\n\t\t}\n\t\tconst node = addDependency(this);\n\t\tthis._refresh();\n\t\tif (node !== undefined) {\n\t\t\tnode._version = this._version;\n\t\t}\n\t\tif (this._flags & HAS_ERROR) {\n\t\t\tthrow this._value;\n\t\t}\n\t\treturn this._value;\n\t},\n});\n\n/**\n * An interface for read-only signals.\n */\ninterface ReadonlySignal<T = any> {\n\treadonly value: T;\n\tpeek(): T;\n\n\tsubscribe(fn: (value: T) => void): () => void;\n\tvalueOf(): T;\n\ttoString(): string;\n\ttoJSON(): T;\n\tbrand: typeof BRAND_SYMBOL;\n}\n\n/**\n * Create a new signal that is computed based on the values of other signals.\n *\n * The returned computed signal is read-only, and its value is automatically\n * updated when any signals accessed from within the callback function change.\n *\n * @param fn The effect callback.\n * @returns A new read-only signal.\n */\nfunction computed<T>(\n\tfn: () => T,\n\toptions?: SignalOptions<T>\n): ReadonlySignal<T> {\n\treturn new Computed(fn, options);\n}\n\n//#endregion Computed\n\n//#region Effect\n\nfunction cleanupEffect(effect: Effect) {\n\tconst cleanup = effect._cleanup;\n\teffect._cleanup = undefined;\n\n\tif (typeof cleanup === \"function\") {\n\t\t/*@__INLINE__**/ startBatch();\n\n\t\t// Run cleanup functions always outside of any context.\n\t\tconst prevContext = evalContext;\n\t\tevalContext = undefined;\n\t\ttry {\n\t\t\tcleanup();\n\t\t} catch (err) {\n\t\t\teffect._flags &= ~RUNNING;\n\t\t\teffect._flags |= DISPOSED;\n\t\t\tdisposeEffect(effect);\n\t\t\tthrow err;\n\t\t} finally {\n\t\t\tevalContext = prevContext;\n\t\t\tendBatch();\n\t\t}\n\t}\n}\n\nfunction disposeEffect(effect: Effect) {\n\tfor (\n\t\tlet node = effect._sources;\n\t\tnode !== undefined;\n\t\tnode = node._nextSource\n\t) {\n\t\tnode._source._unsubscribe(node);\n\t}\n\teffect._fn = undefined;\n\teffect._sources = undefined;\n\n\tcleanupEffect(effect);\n}\n\nfunction endEffect(this: Effect, prevContext?: Computed | Effect) {\n\tif (evalContext !== this) {\n\t\tthrow new Error(\"Out-of-order effect\");\n\t}\n\tcleanupSources(this);\n\tevalContext = prevContext;\n\n\tthis._flags &= ~RUNNING;\n\tif (this._flags & DISPOSED) {\n\t\tdisposeEffect(this);\n\t}\n\tendBatch();\n}\n\ntype EffectFn =\n\t| ((this: { dispose: () => void }) => void | (() => void))\n\t| (() => void | (() => void));\n\n// Avoid hard-requiring the ESNext.Disposable lib in consuming tsconfigs.\n// When `Symbol.dispose` is available, this becomes a symbol-keyed disposer type.\ntype DisposeSymbol = typeof Symbol extends { readonly dispose: infer TDispose }\n\t? TDispose\n\t: never;\ntype DisposableLike = {\n\t[K in DisposeSymbol & PropertyKey]: () => void;\n};\ntype DisposeFn = (() => void) & DisposableLike;\n\n/**\n * The base class for reactive effects.\n */\ndeclare class Effect {\n\t_fn?: EffectFn;\n\t_cleanup?: () => void;\n\t_sources?: Node;\n\t_nextBatchedEffect?: Effect;\n\t_flags: number;\n\t_debugCallback?: () => void;\n\tname?: string;\n\n\tconstructor(fn: EffectFn, options?: EffectOptions);\n\n\t_callback(): void;\n\t_start(): () => void;\n\t_notify(): void;\n\t_dispose(): void;\n\tdispose(): void;\n}\n\nexport interface EffectOptions {\n\tname?: string;\n}\n\nlet capturedEffects: Effect[] | undefined;\n\n/** @internal */\nfunction Effect(this: Effect, fn: EffectFn, options?: EffectOptions) {\n\tthis._fn = fn;\n\tthis._cleanup = undefined;\n\tthis._sources = undefined;\n\tthis._nextBatchedEffect = undefined;\n\tthis._flags = TRACKING;\n\tthis.name = options?.name;\n\n\tif (capturedEffects) {\n\t\tcapturedEffects.push(this);\n\t}\n}\n\nEffect.prototype._callback = function () {\n\tconst finish = this._start();\n\ttry {\n\t\tif (this._flags & DISPOSED) return;\n\t\tif (this._fn === undefined) return;\n\n\t\tconst cleanup = this._fn();\n\t\tif (typeof cleanup === \"function\") {\n\t\t\tthis._cleanup = cleanup;\n\t\t}\n\t} finally {\n\t\tfinish();\n\t}\n};\n\nEffect.prototype._start = function () {\n\tif (this._flags & RUNNING) {\n\t\tthrow new Error(\"Cycle detected\");\n\t}\n\tthis._flags |= RUNNING;\n\tthis._flags &= ~DISPOSED;\n\tcleanupEffect(this);\n\tprepareSources(this);\n\n\t/*@__INLINE__**/ startBatch();\n\tconst prevContext = evalContext;\n\tevalContext = this;\n\treturn endEffect.bind(this, prevContext);\n};\n\nEffect.prototype._notify = function () {\n\tif (!(this._flags & NOTIFIED)) {\n\t\tthis._flags |= NOTIFIED;\n\t\tthis._nextBatchedEffect = batchedEffect;\n\t\tbatchedEffect = this;\n\t}\n};\n\nEffect.prototype._dispose = function () {\n\tthis._flags |= DISPOSED;\n\n\tif (!(this._flags & RUNNING)) {\n\t\tdisposeEffect(this);\n\t}\n};\n\nEffect.prototype.dispose = function () {\n\tthis._dispose();\n};\n/**\n * Create an effect to run arbitrary code in response to signal changes.\n *\n * An effect tracks which signals are accessed within the given callback\n * function `fn`, and re-runs the callback when those signals change.\n *\n * The callback may return a cleanup function. The cleanup function gets\n * run once, either when the callback is next called or when the effect\n * gets disposed, whichever happens first.\n *\n * @param fn The effect callback.\n * @returns A function for disposing the effect.\n */\nfunction effect(fn: EffectFn, options?: EffectOptions): DisposeFn {\n\tconst effect = new Effect(fn, options);\n\ttry {\n\t\teffect._callback();\n\t} catch (err) {\n\t\teffect._dispose();\n\t\tthrow err;\n\t}\n\t// Return a bound function instead of a wrapper like `() => effect._dispose()`,\n\t// because bound functions seem to be just as fast and take up a lot less memory.\n\tconst dispose = effect._dispose.bind(effect);\n\t(dispose as any)[Symbol.dispose] = dispose;\n\treturn dispose as DisposeFn;\n}\n\n//#endregion Effect\n\n//#region Action\n\nfunction action<TArgs extends unknown[], TReturn>(\n\tfn: (...args: TArgs) => TReturn\n): (...args: TArgs) => TReturn {\n\treturn function actionWrapper(this: unknown, ...args: TArgs) {\n\t\treturn batch(() => untracked(() => fn.apply(this, args)));\n\t};\n}\n\n//#endregion Action\n\n//#region createModel\n\n/** Models should only contain signals, actions, and nested objects containing only signals and actions. */\ntype ValidateModel<TModel> = {\n\t[Key in keyof TModel]: TModel[Key] extends ReadonlySignal<unknown>\n\t\t? TModel[Key]\n\t\t: TModel[Key] extends (...args: any[]) => any\n\t\t\t? TModel[Key]\n\t\t\t: TModel[Key] extends object\n\t\t\t\t? ValidateModel<TModel[Key]>\n\t\t\t\t: `Property ${Key extends string ? `'${Key}' ` : \"\"}is not a Signal, Action, or an object that contains only Signals and Actions.`;\n};\n\nexport type Model<TModel> = ValidateModel<TModel> & DisposableLike;\n\nexport type ModelFactory<TModel, TFactoryArgs extends any[] = []> = (\n\t...args: TFactoryArgs\n) => ValidateModel<TModel>;\nexport type ModelConstructor<TModel, TFactoryArgs extends any[] = []> = new (\n\t...args: TFactoryArgs\n) => Model<TModel>;\n\n/**\n * The public types for ModelConstructor require using `new` to help\n * disambiguate the function passed into `createModel` and the returned\n * constructor function. It is easier to say that `createModel` accepts\n * a factory and returns a class, then to say it accepts a factory and\n * returns a factory. In other words, this example:\n *\n * ```ts\n * const PersonModel = createModel((name: string) => ({ ... }));\n * const person = new PersonModel(\"John\");\n * ```\n *\n * is easier to understand than this example:\n *\n * ```ts\n * const createPerson = createModel((name: string) => ({ ... }));\n * const person = createPerson(\"John\");\n * ```\n *\n * However, internally we implement `createModel` to return a function\n * that can be called without `new` for simplicity. To bridge the gap\n * between the public types and the internal implementation, we define\n * this internal interface that extends the public interface but also\n * allows calling without `new`.\n *\n * This pattern is used by the Preact & React adapters to make instantiating\n * a model or a function that returns a model easier.\n *\n * @internal\n */\ninterface InternalModelConstructor<\n\tTModel,\n\tTFactoryArgs extends any[],\n> extends ModelConstructor<TModel, TFactoryArgs> {\n\t(...args: TFactoryArgs): Model<TModel>;\n}\n\nfunction startCapturingEffects(): () => Effect[] | undefined {\n\tlet prevCapturedEffects = capturedEffects;\n\tcapturedEffects = [];\n\n\treturn function stopCapturingEffects() {\n\t\tlet modelEffects = capturedEffects;\n\t\tif (capturedEffects && prevCapturedEffects) {\n\t\t\tprevCapturedEffects = prevCapturedEffects.concat(capturedEffects);\n\t\t}\n\n\t\tcapturedEffects = prevCapturedEffects;\n\n\t\treturn modelEffects;\n\t};\n}\n\nconst wrapInAction = (value: Record<string, unknown>) => {\n\tfor (const key in value) {\n\t\tconst val = value[key];\n\t\tif (typeof val === \"function\") {\n\t\t\tvalue[key] = action(val as (...args: unknown[]) => unknown);\n\t\t} else if (typeof val === \"object\" && val !== null && !(\"brand\" in val)) {\n\t\t\t// Recursively wrap nested object properties in actions. This allows users to write\n\t\t\t// nested models without worrying about wrapping their functions in `action`.\n\t\t\twrapInAction(val as Record<string, unknown>);\n\t\t}\n\t}\n};\n\nfunction createModel<TModel, TFactoryArgs extends any[] = []>(\n\tmodelFactory: ModelFactory<TModel, TFactoryArgs>\n): ModelConstructor<TModel, TFactoryArgs> {\n\treturn function SignalModel(...args: TFactoryArgs): Model<TModel> {\n\t\tlet modelEffects: Effect[] | undefined;\n\t\tlet model: Model<TModel>;\n\n\t\tconst stopCapturingEffects = startCapturingEffects();\n\t\ttry {\n\t\t\tmodel = modelFactory(...args) as Model<TModel>;\n\t\t} catch (err) {\n\t\t\t// Drop any captured effects on error. Errors from nested models will bubble\n\t\t\t// up here and recursively reset `capturedEffects` to `undefined` preventing\n\t\t\t// any captured effects from leaking\n\t\t\tcapturedEffects = undefined;\n\t\t\tthrow err;\n\t\t} finally {\n\t\t\tmodelEffects = stopCapturingEffects();\n\t\t}\n\n\t\twrapInAction(model);\n\n\t\tmodel[Symbol.dispose] = action(function disposeModel() {\n\t\t\tif (modelEffects) {\n\t\t\t\tfor (let i = 0; i < modelEffects.length; i++) {\n\t\t\t\t\tmodelEffects[i].dispose();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmodelEffects = undefined;\n\t\t});\n\n\t\treturn model;\n\t} as InternalModelConstructor<TModel, TFactoryArgs>;\n}\n\n//#endregion createModel\n\nexport {\n\tcomputed,\n\teffect,\n\tbatch,\n\tuntracked,\n\taction,\n\tcreateModel,\n\tSignal,\n\tReadonlySignal,\n\tEffect,\n\tComputed,\n};\n", "import { options, Component, isValidElement, Fragment } from \"preact\";\nimport { useRef, useMemo, useEffect } from \"preact/hooks\";\nimport {\n\tsignal,\n\tcomputed,\n\tbatch,\n\teffect,\n\taction,\n\tcreateModel,\n\ttype Model,\n\ttype ModelConstructor,\n\tSignal,\n\ttype ReadonlySignal,\n\tuntracked,\n\tSignalOptions,\n\tEffectOptions,\n} from \"@preact/signals-core\";\nimport {\n\tVNode,\n\tOptionsTypes,\n\tHookFn,\n\tEffect,\n\tPropertyUpdater,\n\tAugmentedComponent,\n\tAugmentedElement as Element,\n} from \"./internal\";\n\nexport {\n\tsignal,\n\tcomputed,\n\tbatch,\n\teffect,\n\taction,\n\ttype Model,\n\ttype ModelConstructor,\n\tcreateModel,\n\tSignal,\n\ttype ReadonlySignal,\n\tuntracked,\n};\n\nconst DEVTOOLS_ENABLED =\n\ttypeof window !== \"undefined\" && !!window.__PREACT_SIGNALS_DEVTOOLS__;\n\nconst HAS_PENDING_UPDATE = 1 << 0;\nconst HAS_HOOK_STATE = 1 << 1;\nconst HAS_COMPUTEDS = 1 << 2;\n\nlet oldNotify: (this: Effect) => void,\n\teffectsQueue: Array<Effect> = [],\n\tdomQueue: Array<Effect> = [];\n\n// Capture the original `Effect.prototype._notify` method so that we can install\n// custom `._notify`s for each different use-case but still call the original\n// implementation in the end. Dispose the temporary effect immediately afterwards.\neffect(function (this: Effect) {\n\toldNotify = this._notify;\n})();\n\n// Install a Preact options hook\nfunction hook<T extends OptionsTypes>(hookName: T, hookFn: HookFn<T>) {\n\t// @ts-ignore-next-line private options hooks usage\n\toptions[hookName] = hookFn.bind(null, options[hookName] || (() => {}));\n}\n\nlet currentComponent: AugmentedComponent | undefined;\nlet finishUpdate: (() => void) | undefined;\n\nfunction setCurrentUpdater(updater?: Effect) {\n\t// end tracking for the current update:\n\tif (finishUpdate) {\n\t\tconst finish = finishUpdate;\n\t\tfinishUpdate = undefined;\n\t\tfinish();\n\t}\n\t// start tracking the new update:\n\tfinishUpdate = updater && updater._start();\n}\n\nfunction createUpdater(update: () => void, name: string) {\n\tlet updater!: Effect;\n\teffect(\n\t\tfunction (this: Effect) {\n\t\t\tupdater = this;\n\t\t},\n\t\t{ name }\n\t);\n\tupdater._callback = update;\n\treturn updater;\n}\n\n/** @todo This may be needed for complex prop value detection. */\n// function isSignalValue(value: any): value is Signal {\n// \tif (typeof value !== \"object\" || value == null) return false;\n// \tif (value instanceof Signal) return true;\n// \t// @TODO: uncomment this when we land Reactive (ideally behind a brand check)\n// \t// for (let i in value) if (value[i] instanceof Signal) return true;\n// \treturn false;\n// }\n\n/**\n * A wrapper component that renders a Signal directly as a Text node.\n * @todo: in Preact 11, just decorate Signal with `type:null`\n */\nfunction SignalValue(this: AugmentedComponent, { data }: { data: Signal }) {\n\t// hasComputeds.add(this);\n\n\t// Store the props.data signal in another signal so that\n\t// passing a new signal reference re-runs the text computed:\n\tconst currentSignal = useSignal(data);\n\tcurrentSignal.value = data;\n\n\tconst [isText, s] = useMemo(() => {\n\t\tlet self = this;\n\t\t// mark the parent component as having computeds so it gets optimized\n\t\tlet v = this.__v;\n\t\twhile ((v = v.__!)) {\n\t\t\tif (v.__c) {\n\t\t\t\tv.__c._updateFlags |= HAS_COMPUTEDS;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tconst wrappedSignal = computed(() => {\n\t\t\tlet s = currentSignal.value.value;\n\t\t\treturn s === 0 ? 0 : s === true ? \"\" : s || \"\";\n\t\t});\n\n\t\tconst isText = computed(\n\t\t\t() =>\n\t\t\t\t!Array.isArray(wrappedSignal.value) &&\n\t\t\t\t!isValidElement(wrappedSignal.value)\n\t\t);\n\t\t// Update text nodes directly without rerendering when the new value\n\t\t// is also text.\n\t\tconst dispose = effect(function (this: Effect) {\n\t\t\tthis._notify = notifyDomUpdates;\n\n\t\t\t// Subscribe to wrappedSignal updates only when its values are text...\n\t\t\tif (isText.value) {\n\t\t\t\t// ...but regardless of `self.base`'s current value, as it can be\n\t\t\t\t// undefined before mounting or a non-text node. In both of those cases\n\t\t\t\t// the update gets handled by a full rerender.\n\t\t\t\tconst value = wrappedSignal.value;\n\t\t\t\tif (self.__v && self.__v.__e && self.__v.__e.nodeType === 3) {\n\t\t\t\t\t(self.__v.__e as Text).data = value;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Piggyback this._updater's disposal to ensure that the text updater effect\n\t\t// above also gets disposed on unmount.\n\t\tconst oldDispose = this._updater!._dispose;\n\t\tthis._updater!._dispose = function () {\n\t\t\tdispose();\n\t\t\toldDispose.call(this);\n\t\t};\n\n\t\treturn [isText, wrappedSignal];\n\t}, []);\n\n\t// Rerender the component whenever `data.value` changes from a VNode\n\t// to another VNode, from text to a VNode, or from a VNode to text.\n\t// That is, everything else except text-to-text updates.\n\t//\n\t// This also ensures that the backing DOM node types gets updated to\n\t// text nodes and back when needed.\n\t//\n\t// For text-to-text updates, `.peek()` is used to skip full rerenders,\n\t// leaving them to the optimized path above.\n\treturn isText.value ? s.peek() : s.value;\n}\n\nSignalValue.displayName = \"ReactiveTextNode\";\n\nObject.defineProperties(Signal.prototype, {\n\tconstructor: { configurable: true, value: undefined },\n\ttype: { configurable: true, value: SignalValue },\n\tprops: {\n\t\tconfigurable: true,\n\t\tget() {\n\t\t\tconst s: Signal = this;\n\t\t\treturn {\n\t\t\t\tdata: {\n\t\t\t\t\tget value() {\n\t\t\t\t\t\treturn s.value;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t},\n\t// Setting a VNode's _depth to 1 forces Preact to clone it before modifying:\n\t// https://github.com/preactjs/preact/blob/d7a433ee8463a7dc23a05111bb47de9ec729ad4d/src/diff/children.js#L77\n\t// @todo remove this for Preact 11\n\t__b: { configurable: true, value: 1 },\n});\n\n/** Inject low-level property/attribute bindings for Signals into Preact's diff */\nhook(OptionsTypes.DIFF, (old, vnode) => {\n\tif (typeof vnode.type === \"string\") {\n\t\tlet signalProps: Record<string, any> | undefined;\n\n\t\tlet props = vnode.props;\n\t\tfor (let i in props) {\n\t\t\tif (i === \"children\") continue;\n\n\t\t\tlet value = props[i];\n\t\t\tif (value instanceof Signal) {\n\t\t\t\tif (!signalProps) vnode.__np = signalProps = {};\n\t\t\t\tsignalProps[i] = value;\n\t\t\t\tprops[i] = value.peek();\n\t\t\t}\n\t\t}\n\t}\n\n\told(vnode);\n});\n\n/** Set up Updater before rendering a component */\nhook(OptionsTypes.RENDER, (old, vnode) => {\n\told(vnode);\n\t// Ignore the Fragment inserted by preact.createElement().\n\tif (vnode.type !== Fragment) {\n\t\tsetCurrentUpdater();\n\n\t\tlet updater: Effect | undefined;\n\n\t\tlet component = vnode.__c;\n\t\tif (component) {\n\t\t\tcomponent._updateFlags &= ~HAS_PENDING_UPDATE;\n\n\t\t\tupdater = component._updater;\n\t\t\tif (updater === undefined) {\n\t\t\t\tcomponent._updater = updater = createUpdater(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tif (DEVTOOLS_ENABLED) updater!._debugCallback?.call(updater);\n\t\t\t\t\t\tcomponent._updateFlags |= HAS_PENDING_UPDATE;\n\t\t\t\t\t\tcomponent.setState({});\n\t\t\t\t\t},\n\t\t\t\t\ttypeof vnode.type === \"function\"\n\t\t\t\t\t\t? vnode.type.displayName || vnode.type.name\n\t\t\t\t\t\t: \"\"\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tcurrentComponent = component;\n\t\tsetCurrentUpdater(updater);\n\t}\n});\n\n/** Finish current updater if a component errors */\nhook(OptionsTypes.CATCH_ERROR, (old, error, vnode, oldVNode) => {\n\tsetCurrentUpdater();\n\tcurrentComponent = undefined;\n\told(error, vnode, oldVNode);\n});\n\n/** Finish current updater after rendering any VNode */\nhook(OptionsTypes.DIFFED, (old, vnode) => {\n\tsetCurrentUpdater();\n\tcurrentComponent = undefined;\n\n\tlet dom: Element;\n\n\t// vnode._dom is undefined during string rendering,\n\t// so we use this to skip prop subscriptions during SSR.\n\tif (typeof vnode.type === \"string\" && (dom = vnode.__e as Element)) {\n\t\tlet props = vnode.__np;\n\t\tlet renderedProps = vnode.props;\n\t\tif (props) {\n\t\t\tlet updaters = dom._updaters;\n\t\t\tif (updaters) {\n\t\t\t\tfor (let prop in updaters) {\n\t\t\t\t\tlet updater = updaters[prop];\n\t\t\t\t\tif (updater !== undefined && !(prop in props)) {\n\t\t\t\t\t\tupdater._dispose();\n\t\t\t\t\t\t// @todo we could just always invoke _dispose() here\n\t\t\t\t\t\tupdaters[prop] = undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tupdaters = {};\n\t\t\t\tdom._updaters = updaters;\n\t\t\t}\n\n\t\t\tfor (let prop in props) {\n\t\t\t\tlet updater = updaters[prop];\n\t\t\t\tlet signal = props[prop];\n\t\t\t\tif (updater === undefined) {\n\t\t\t\t\tupdater = createPropUpdater(dom, prop, signal, renderedProps);\n\t\t\t\t\tupdaters[prop] = updater;\n\t\t\t\t} else {\n\t\t\t\t\tupdater._update(signal, renderedProps);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\told(vnode);\n});\n\nfunction createPropUpdater(\n\tdom: Element,\n\tprop: string,\n\tpropSignal: Signal,\n\tprops: Record<string, any>\n): PropertyUpdater {\n\tconst setAsProperty =\n\t\tprop in dom &&\n\t\t// SVG elements need to go through `setAttribute` because they\n\t\t// expect things like SVGAnimatedTransformList instead of strings.\n\t\t// @ts-ignore\n\t\tdom.ownerSVGElement === undefined;\n\n\tconst changeSignal = signal(propSignal);\n\t// Track the last value we know was applied to the DOM, so we can skip\n\t// redundant updates without writing back to vnode.props (which would\n\t// clobber the Signal reference and break unmount/remount cycles).\n\tlet lastRenderedValue: any = propSignal.peek();\n\treturn {\n\t\t_update: (newSignal: Signal, newProps: typeof props) => {\n\t\t\tchangeSignal.value = newSignal;\n\t\t\tprops = newProps;\n\t\t\tlastRenderedValue = newSignal.peek();\n\t\t},\n\t\t_dispose: effect(function (this: Effect) {\n\t\t\tthis._notify = notifyDomUpdates;\n\t\t\tconst value = changeSignal.value.value;\n\t\t\t// If Preact just rendered this value, don't render it again:\n\t\t\tif (lastRenderedValue === value) {\n\t\t\t\tlastRenderedValue = undefined;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlastRenderedValue = undefined;\n\t\t\tif (setAsProperty) {\n\t\t\t\t// @ts-ignore-next-line silly\n\t\t\t\tdom[prop] = value;\n\t\t\t\t// Match Preact's attribute handling: data-* and aria-* attributes\n\t\t\t\t// https://github.com/preactjs/preact/blob/main/src/diff/props.js#L132\n\t\t\t} else if (value != null && (value !== false || prop[4] === \"-\")) {\n\t\t\t\tdom.setAttribute(prop, value);\n\t\t\t} else {\n\t\t\t\tdom.removeAttribute(prop);\n\t\t\t}\n\t\t}),\n\t};\n}\n\n/** Unsubscribe from Signals when unmounting components/vnodes */\nhook(OptionsTypes.UNMOUNT, (old, vnode: VNode) => {\n\tif (typeof vnode.type === \"string\") {\n\t\tlet dom = vnode.__e as Element | undefined;\n\t\t// vnode._dom is undefined during string rendering\n\t\tif (dom) {\n\t\t\tconst updaters = dom._updaters;\n\t\t\tif (updaters) {\n\t\t\t\tdom._updaters = undefined;\n\t\t\t\tfor (let prop in updaters) {\n\t\t\t\t\tlet updater = updaters[prop];\n\t\t\t\t\tif (updater) updater._dispose();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Restore Signal references into vnode.props so that, if this vnode\n\t\t// instance is reused for a remount, the DIFF hook can re-detect the\n\t\t// signal-bound props (they were replaced with peeked values during diff).\n\t\tlet signalProps = vnode.__np;\n\t\tif (signalProps) {\n\t\t\tlet props = vnode.props;\n\t\t\tfor (let prop in signalProps) {\n\t\t\t\tprops[prop] = signalProps[prop];\n\t\t\t}\n\t\t}\n\t\tvnode.__np = undefined;\n\t} else {\n\t\tlet component = vnode.__c;\n\t\tif (component) {\n\t\t\tconst updater = component._updater;\n\t\t\tif (updater) {\n\t\t\t\tcomponent._updater = undefined;\n\t\t\t\tupdater._dispose();\n\t\t\t}\n\t\t}\n\t}\n\told(vnode);\n});\n\n/** Mark components that use hook state so we can skip sCU optimization. */\nhook(OptionsTypes.HOOK, (old, component, index, type) => {\n\tif (type < 3 || type === 9)\n\t\t(component as AugmentedComponent)._updateFlags |= HAS_HOOK_STATE;\n\told(component, index, type);\n});\n\n/**\n * Auto-memoize components that use Signals/Computeds.\n * Note: Does _not_ optimize components that use hook/class state.\n */\nComponent.prototype.shouldComponentUpdate = function (\n\tthis: AugmentedComponent,\n\tprops,\n\tstate\n) {\n\t// Suspended vnodes should always update:\n\tif (this.__R) return true;\n\n\t// @todo: Once preactjs/preact#3671 lands, this could just use `currentUpdater`:\n\tconst updater = this._updater;\n\tconst hasSignals = updater && updater._sources !== undefined;\n\n\t// If this is a component using state, rerender\n\t// @ts-ignore\n\tfor (let i in state) return true;\n\n\tif (this.__f || (typeof this.u == \"boolean\" && this.u === true)) {\n\t\tconst hasHooksState = this._updateFlags & HAS_HOOK_STATE;\n\t\t// if this component used no signals or computeds and no hooks state, update:\n\t\tif (!hasSignals && !hasHooksState && !(this._updateFlags & HAS_COMPUTEDS))\n\t\t\treturn true;\n\n\t\t// if there is a pending re-render triggered from Signals,\n\t\t// or if there is hooks state, update:\n\t\tif (this._updateFlags & HAS_PENDING_UPDATE) return true;\n\t} else {\n\t\t// if this component used no signals or computeds, update:\n\t\tif (!hasSignals && !(this._updateFlags & HAS_COMPUTEDS)) return true;\n\n\t\t// if there is a pending re-render triggered from Signals,\n\t\t// or if there is hooks state, update:\n\t\tif (this._updateFlags & (HAS_PENDING_UPDATE | HAS_HOOK_STATE)) return true;\n\t}\n\n\t// if any non-Signal props changed, update:\n\tfor (let i in props) {\n\t\tif (i !== \"__source\" && props[i] !== this.props[i]) return true;\n\t}\n\tfor (let i in this.props) if (!(i in props)) return true;\n\n\t// this is a purely Signal-driven component, don't update:\n\treturn false;\n};\n\nexport function useSignal<T>(value: T, options?: SignalOptions<T>): Signal<T>;\nexport function useSignal<T = undefined>(): Signal<T | undefined>;\nexport function useSignal<T>(value?: T, options?: SignalOptions<T>) {\n\treturn useMemo(\n\t\t() => signal<T | undefined>(value, options as SignalOptions),\n\t\t[]\n\t);\n}\n\nexport function useComputed<T>(compute: () => T, options?: SignalOptions<T>) {\n\tconst $compute = useRef(compute);\n\t$compute.current = compute;\n\t(currentComponent as AugmentedComponent)._updateFlags |= HAS_COMPUTEDS;\n\treturn useMemo(() => computed<T>(() => $compute.current(), options), []);\n}\n\nfunction safeRaf(callback: () => void) {\n\tconst done = () => {\n\t\tclearTimeout(timeout);\n\t\tcancelAnimationFrame(raf);\n\t\tcallback();\n\t};\n\n\tconst timeout = setTimeout(done, 35);\n\tconst raf = requestAnimationFrame(done);\n}\n\nconst deferEffects =\n\ttypeof requestAnimationFrame === \"undefined\" ? setTimeout : safeRaf;\n\nconst deferDomUpdates = (cb: any) => {\n\tqueueMicrotask(() => {\n\t\tqueueMicrotask(cb);\n\t});\n};\n\nfunction flushEffects() {\n\tbatch(() => {\n\t\tlet inst: Effect | undefined;\n\t\twhile ((inst = effectsQueue.shift())) {\n\t\t\toldNotify.call(inst);\n\t\t}\n\t});\n}\n\nfunction notifyEffects(this: Effect) {\n\tif (effectsQueue.push(this) === 1) {\n\t\t(options.requestAnimationFrame || deferEffects)(flushEffects);\n\t}\n}\n\nfunction flushDomUpdates() {\n\tbatch(() => {\n\t\tlet inst: Effect | undefined;\n\t\twhile ((inst = domQueue.shift())) {\n\t\t\toldNotify.call(inst);\n\t\t}\n\t});\n}\n\nfunction notifyDomUpdates(this: Effect) {\n\tif (domQueue.push(this) === 1) {\n\t\t(options.requestAnimationFrame || deferDomUpdates)(flushDomUpdates);\n\t}\n}\n\nexport function useSignalEffect(\n\tcb: () => void | (() => void),\n\toptions?: EffectOptions\n) {\n\tconst callback = useRef(cb);\n\tcallback.current = cb;\n\n\tuseEffect(() => {\n\t\treturn effect(function (this: Effect) {\n\t\t\tthis._notify = notifyEffects;\n\t\t\treturn callback.current();\n\t\t}, options);\n\t}, []);\n}\n\n/** See comment in packages/core/src/index.ts on the same interface for an explanation */\ninterface InternalModelConstructor<\n\tTModel,\n\tTArgs extends any[],\n> extends ModelConstructor<TModel, TArgs> {\n\t(...args: TArgs): Model<TModel>;\n}\n\nexport function useModel<TModel>(\n\tfactory: ModelConstructor<TModel, []> | (() => Model<TModel>)\n): Model<TModel> {\n\ttype InternalFactory =\n\t\t| InternalModelConstructor<TModel, []>\n\t\t| (() => Model<TModel>);\n\n\tconst inst = useMemo(() => (factory as InternalFactory)(), []);\n\tuseEffect(() => inst[Symbol.dispose], [inst]);\n\treturn inst;\n}\n\n/**\n * @todo Determine which Reactive implementation we'll be using.\n * @internal\n */\n// export function useReactive<T extends object>(value: T): Reactive<T> {\n// \treturn useMemo(() => reactive<T>(value), []);\n// }\n\n/**\n * @internal\n * Update a Reactive's using the properties of an object or other Reactive.\n * Also works for Signals.\n * @example\n * // Update a Reactive with Object.assign()-like syntax:\n * const r = reactive({ name: \"Alice\" });\n * update(r, { name: \"Bob\" });\n * update(r, { age: 42 }); // property 'age' does not exist in type '{ name?: string }'\n * update(r, 2); // '2' has no properties in common with '{ name?: string }'\n * console.log(r.name.value); // \"Bob\"\n *\n * @example\n * // Update a Reactive with the properties of another Reactive:\n * const A = reactive({ name: \"Alice\" });\n * const B = reactive({ name: \"Bob\", age: 42 });\n * update(A, B);\n * console.log(`${A.name} is ${A.age}`); // \"Bob is 42\"\n *\n * @example\n * // Update a signal with assign()-like syntax:\n * const s = signal(42);\n * update(s, \"hi\"); // Argument type 'string' not assignable to type 'number'\n * update(s, {}); // Argument type '{}' not assignable to type 'number'\n * update(s, 43);\n * console.log(s.value); // 43\n *\n * @param obj The Reactive or Signal to be updated\n * @param update The value, Signal, object or Reactive to update `obj` to match\n * @param overwrite If `true`, any properties `obj` missing from `update` are set to `undefined`\n */\n/*\nexport function update<T extends SignalOrReactive>(\n\tobj: T,\n\tupdate: Partial<Unwrap<T>>,\n\toverwrite = false\n) {\n\tif (obj instanceof Signal) {\n\t\tobj.value = peekValue(update);\n\t} else {\n\t\tfor (let i in update) {\n\t\t\tif (i in obj) {\n\t\t\t\tobj[i].value = peekValue(update[i]);\n\t\t\t} else {\n\t\t\t\tlet sig = signal(peekValue(update[i]));\n\t\t\t\tsig[KEY] = i;\n\t\t\t\tobj[i] = sig;\n\t\t\t}\n\t\t}\n\t\tif (overwrite) {\n\t\t\tfor (let i in obj) {\n\t\t\t\tif (!(i in update)) {\n\t\t\t\t\tobj[i].value = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n*/\n", "/**\n * Brand constants, baked in at build time by `scripts/build-brand.mjs`.\n *\n * esbuild `define` replaces every `__BRAND_*__` / `__CSS_PREFIX__` reference\n * with the JSON-encoded value from the brand JSON. We use `typeof \u2026 !==\n * \"undefined\"` guards so the module still imports cleanly in tooling\n * contexts (Vitest, the Vite dev server) where defines come from separate\n * config files.\n */\n\ndeclare const __BRAND_ID__: string;\ndeclare const __BRAND_NAME__: string;\ndeclare const __BRAND_GLOBAL__: string;\ndeclare const __BRAND_TAG_NAME__: string;\ndeclare const __BRAND_DEFAULT_BASE_URL__: string;\ndeclare const __BRAND_STANDALONE_URL__: string;\ndeclare const __BRAND_DISPLAY_NAME__: string;\ndeclare const __BRAND_DOMAIN__: string;\ndeclare const __BRAND_SITE_URL__: string;\ndeclare const __CSS_PREFIX__: string;\n\nexport const BRAND = {\n id: typeof __BRAND_ID__ !== \"undefined\" ? __BRAND_ID__ : \"help-ai\",\n name: typeof __BRAND_NAME__ !== \"undefined\" ? __BRAND_NAME__ : \"Help AI\",\n global: typeof __BRAND_GLOBAL__ !== \"undefined\" ? __BRAND_GLOBAL__ : \"HelpAI\",\n // Custom-element tag \u2014 `web-ai-chat` for every brand. Brand-neutral\n // by design so docs and copy snippets work the same across @helpai\n // and @goai builds.\n tagName: typeof __BRAND_TAG_NAME__ !== \"undefined\" ? __BRAND_TAG_NAME__ : \"web-ai-chat\",\n cssPrefix: typeof __CSS_PREFIX__ !== \"undefined\" ? __CSS_PREFIX__ : \"helpai\",\n defaultBaseUrl: typeof __BRAND_DEFAULT_BASE_URL__ !== \"undefined\" ? __BRAND_DEFAULT_BASE_URL__ : \"\",\n standaloneUrl: typeof __BRAND_STANDALONE_URL__ !== \"undefined\" ? __BRAND_STANDALONE_URL__ : \"\",\n /** Public-facing brand label, e.g. \"Help AI\" / \"Go AI\". */\n displayName: typeof __BRAND_DISPLAY_NAME__ !== \"undefined\" ? __BRAND_DISPLAY_NAME__ : \"Help AI\",\n /** Bare host without protocol, e.g. \"help.ai\" / \"go.ai\". */\n domain: typeof __BRAND_DOMAIN__ !== \"undefined\" ? __BRAND_DOMAIN__ : \"help.ai\",\n /** Full marketing site URL, e.g. \"https://help.ai\". */\n siteUrl: typeof __BRAND_SITE_URL__ !== \"undefined\" ? __BRAND_SITE_URL__ : \"https://help.ai\",\n} as const;\n", "/**\n * UI strings \u2014 built-in defaults, type contracts, and runtime resolution.\n *\n * The widget ships English as a built-in (always complete). Every additional\n * locale is supplied either by the page owner (`init({ strings })`) or by\n * the SaaS backend in the `connect` response \u2014 both pushed *partially*: only\n * the keys they want to override.\n *\n * To keep render hot-paths cheap, the merged shape is **pre-computed once**\n * (at config-resolve time and after each server-settings merge) into a fully\n * populated {@link Strings} map. UI components then read `strings.send` as a\n * normal property access \u2014 no per-render lookup, no per-render allocation.\n */\n\n// ---- Keys ---------------------------------------------------------------\n\nexport type StringKey =\n | \"launcherOpen\"\n | \"panelTitle\"\n | \"composerPlaceholder\"\n | \"send\"\n | \"stop\"\n | \"attach\"\n | \"micStart\"\n | \"micStop\"\n | \"micUnsupported\"\n | \"expand\"\n | \"collapse\"\n | \"fullscreen\"\n | \"exitFullscreen\"\n | \"resizeHandle\"\n | \"popOut\"\n | \"close\"\n | \"moreActions\"\n | \"soundOn\"\n | \"soundOff\"\n | \"language\"\n | \"theme\"\n | \"themeAuto\"\n | \"themeLight\"\n | \"themeDark\"\n | \"history\"\n | \"historyTitle\"\n | \"historyEmpty\"\n | \"historyLoading\"\n | \"historyNewChat\"\n | \"historyBack\"\n | \"messagesLoading\"\n | \"chatClosed\"\n | \"startNewConversation\"\n | \"dateToday\"\n | \"dateYesterday\"\n | \"dateLastWeek\"\n | \"dateOlder\"\n | \"newConversation\"\n | \"dropZone\"\n | \"attachmentTooLarge\"\n | \"attachmentTooMany\"\n | \"attachmentMimeRejected\"\n | \"errorRetry\"\n | \"errorGeneric\"\n | \"errorRateLimited\"\n | \"loading\"\n | \"thinking\"\n | \"thoughts\"\n | \"usedTool\"\n | \"collapseSidebar\"\n | \"expandSidebar\"\n // \u2500\u2500 Messenger modules \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\u2500\u2500\u2500\u2500\u2500\u2500\n | \"tabHome\"\n | \"tabMessages\"\n | \"tabHelp\"\n | \"tabNews\"\n | \"modulesEmpty\"\n | \"moduleBack\"\n | \"contentLoading\"\n | \"homeGreeting\"\n | \"homeGreetingNamed\"\n | \"homeGreetingLead\"\n | \"homeSearchPlaceholder\"\n | \"homeContentTitle\"\n | \"helpTitle\"\n | \"helpSearchPlaceholder\"\n | \"helpEmpty\"\n | \"helpLoading\"\n | \"helpSearchEmpty\"\n | \"newsTitle\"\n | \"newsEmpty\"\n | \"newsLoading\"\n | \"newsBack\"\n | \"newsPublishedAt\";\n\n// ---- Public types -------------------------------------------------------\n\n/** A partial map for one locale \u2014 what overrides look like on the wire. */\nexport type LocaleStrings = Partial<Record<StringKey, string>>;\n\n/**\n * The post-resolution map \u2014 every key guaranteed present. UI components\n * receive this shape and read it via direct property access (no `t()`).\n */\nexport type Strings = Record<StringKey, string>;\n\n/**\n * Override shape accepted by both `init({ strings })` and the server's\n * `settings.strings`. Two forms supported:\n *\n * 1. **Flat** \u2014 `{ send: \"Click\", stop: \"Halt\" }`. Applies to the active\n * locale. Convenient for single-language deployments.\n * 2. **Nested** \u2014 `{ en: { send: \"Click\" }, fr: { send: \"Cliquer\" } }`.\n * Lets a multilingual backend push every translation in one payload \u2014\n * the widget picks the entry matching the user's locale and falls back\n * to the `en` entry (then to built-in English) for any missing keys.\n *\n * Both forms may be **partial** at every level \u2014 anything you don't supply\n * inherits the built-in default. Detection is by value type: if any\n * top-level value is an object, the whole override is treated as nested.\n */\nexport type StringsOverride = LocaleStrings | Partial<Record<string, LocaleStrings>>;\n\n// ---- Built-in defaults --------------------------------------------------\n\n/**\n * English \u2014 the only locale that MUST be complete. Every other locale (and\n * every override) layers on top of this map, so unknown keys never surface\n * as raw `StringKey` strings in the UI.\n */\nexport const STRINGS_EN: Strings = {\n attach: \"Attach files\",\n attachmentMimeRejected: \"File type not supported\",\n attachmentTooLarge: \"File is too large\",\n attachmentTooMany: \"Attachment limit reached\",\n chatClosed: \"This conversation is closed\",\n close: \"Close\",\n collapse: \"Collapse\",\n collapseSidebar: \"Collapse sidebar\",\n composerPlaceholder: \"Type a message\u2026\",\n dateLastWeek: \"Last 7 days\",\n dateOlder: \"Older\",\n dateToday: \"Today\",\n dateYesterday: \"Yesterday\",\n dropZone: \"Drop files to attach\",\n errorGeneric: \"Something went wrong\",\n errorRateLimited: \"Too many requests \u2014 please try again in a moment.\",\n errorRetry: \"Retry\",\n expand: \"Expand\",\n expandSidebar: \"Expand sidebar\",\n exitFullscreen: \"Exit fullscreen\",\n fullscreen: \"Fullscreen\",\n history: \"History\",\n historyBack: \"Back to chat\",\n historyEmpty: \"No previous conversations yet\",\n historyLoading: \"Loading your chats\u2026\",\n historyNewChat: \"New chat\",\n historyTitle: \"Chat history\",\n language: \"Language\",\n launcherOpen: \"Open chat\",\n messagesLoading: \"Loading conversation\u2026\",\n micStart: \"Start voice input\",\n micStop: \"Stop voice input\",\n micUnsupported: \"Voice input is not supported in this browser\",\n moreActions: \"More actions\",\n newConversation: \"New conversation\",\n panelTitle: \"Chat\",\n popOut: \"Open in a new tab\",\n resizeHandle: \"Drag to resize\",\n send: \"Send\",\n soundOff: \"Sound off\",\n soundOn: \"Sound on\",\n startNewConversation: \"Start a new conversation\",\n stop: \"Stop\",\n theme: \"Theme\",\n themeAuto: \"Auto (system)\",\n themeDark: \"Dark\",\n themeLight: \"Light\",\n loading: \"Loading\u2026\",\n thinking: \"Thinking\u2026\",\n thoughts: \"Thoughts\",\n usedTool: \"Used tool\",\n // \u2500\u2500 Messenger modules \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\u2500\u2500\u2500\u2500\u2500\u2500\n tabHome: \"Home\",\n tabMessages: \"Messages\",\n tabHelp: \"Help\",\n tabNews: \"News\",\n modulesEmpty: \"Nothing here yet\",\n moduleBack: \"Back\",\n contentLoading: \"Loading\u2026\",\n homeGreeting: \"How can we help?\",\n homeGreetingNamed: \"Hi {name} \uD83D\uDC4B\",\n homeGreetingLead: \"How can we help?\",\n homeSearchPlaceholder: \"Search for help\",\n homeContentTitle: \"Popular articles\",\n helpTitle: \"Help\",\n helpSearchPlaceholder: \"Search for help\",\n helpEmpty: \"No help articles yet\",\n helpLoading: \"Loading help\u2026\",\n helpSearchEmpty: \"No results found\",\n newsTitle: \"News\",\n newsEmpty: \"No news yet\",\n newsLoading: \"Loading news\u2026\",\n newsBack: \"Back to news\",\n newsPublishedAt: \"Published {date}\",\n};\n\n// ---- Resolution ---------------------------------------------------------\n\n/**\n * Merge built-in defaults + override into a fully-populated {@link Strings}\n * map for one locale. Call once per options-resolve / server-merge \u2014 the UI\n * then reads from the resulting map without further function calls.\n *\n * Resolution order (later wins):\n * 1. Built-in English (always complete) \u2014 guarantees no undefined keys.\n * 2. Override's `en` entry (nested form only) \u2014 backend's English defaults.\n * 3. Override's locale-specific entry \u2014 backend's per-language strings.\n *\n * Locale negotiation: the BCP-47 tag is folded to its primary subtag and\n * lowercased (`en-US` \u2192 `en`, `fr-CA` \u2192 `fr`). Non-English UI strings ship\n * via the backend's `i18n.strings` payload \u2014 no built-in non-English maps.\n */\nexport function resolveStrings(locale: string, override?: StringsOverride): Strings {\n const tag = primaryTag(locale);\n const isNested = isNestedShape(override);\n\n // Override's per-locale slice (or the whole flat object treated as the active locale).\n const localeOverride: LocaleStrings = isNested\n ? ((override as Record<string, LocaleStrings>)[tag] ?? {})\n : ((override as LocaleStrings | undefined) ?? {});\n\n // Override's English slice \u2014 used as a secondary fallback so nested-form\n // backends can ship \"English defaults + per-locale spot overrides\".\n const enOverride: LocaleStrings = isNested ? ((override as Record<string, LocaleStrings>).en ?? {}) : {};\n\n return {\n ...STRINGS_EN,\n ...enOverride,\n ...localeOverride,\n };\n}\n\n/** Primary BCP-47 subtag, lowercased. `en-US` \u2192 `en`. */\nfunction primaryTag(locale: string): string {\n return (locale.split(\"-\")[0] ?? \"en\").toLowerCase();\n}\n\n/**\n * Resolve a module/tab `label` to display text. The label is an i18n **key**\n * (built-in like `tabHome`, or a custom key the backend ships via\n * `i18n.strings`); if no translation exists it renders literally. This is how\n * dynamic tab labels stay translatable through the same `i18n.strings` path as\n * everything else.\n */\nexport function moduleLabel(strings: Strings, label: string): string {\n return (strings as Record<string, string>)[label] ?? label;\n}\n\n/**\n * Detect whether `override` is in nested (per-locale) form. The cheapest\n * reliable signal: if any top-level value is an object, it's nested. A flat\n * override only ever has string values.\n */\nexport function isNestedShape(override: StringsOverride | undefined): boolean {\n if (!override) return false;\n for (const v of Object.values(override)) {\n if (v && typeof v === \"object\") return true;\n }\n return false;\n}\n\n/**\n * Friendly name for a locale tag, expressed *in the UI's current locale*.\n * Uses {@link Intl.DisplayNames} (browser-native, zero bundle cost).\n *\n * `localeDisplayName(\"fr\", \"en\")` \u2192 `\"French\"`.\n * `localeDisplayName(\"fr\", \"fr\")` \u2192 `\"fran\u00E7ais\"`.\n *\n * Falls back to the upper-cased tag if the browser doesn't recognise it\n * (or pre-Intl runtimes \u2014 happy-dom does, but defensive code is cheap).\n */\nexport function localeDisplayName(target: string, displayInLocale: string): string {\n try {\n const dn = new Intl.DisplayNames([displayInLocale], { type: \"language\" });\n return dn.of(target) ?? target.toUpperCase();\n } catch {\n return target.toUpperCase();\n }\n}\n", "/**\n * Pluggable client-side key-value storage.\n *\n * Drop in a custom adapter to:\n * - Add logging / analytics around reads + writes\n * - Encrypt or sign values\n * - Use a different backing store (sessionStorage, IndexedDB shim, in-memory)\n * - Add quota / migration handling\n *\n * The default implementation is {@link LocalStorageAdapter} \u2014 a thin wrapper\n * over `localStorage` with try/catch around every call so storage being\n * disabled (private mode pre-iOS 11, floating webviews, quota exceeded)\n * silently degrades to no-op rather than throwing.\n *\n * Values are stored as opaque strings \u2014 the persistence layer above\n * serialises typed values to JSON before calling `set`.\n */\n\nexport interface ClientStorage {\n /** Returns the stored string for `key`, or `null` when absent / unreadable. */\n get(key: string): string | null;\n /** Stores `value` under `key`. Best-effort \u2014 silent on quota / disabled storage. */\n set(key: string, value: string): void;\n /** Removes `key`. No-op if absent. */\n remove(key: string): void;\n}\n\n/**\n * Default adapter \u2014 wraps `globalThis.localStorage`. Every call is wrapped\n * in try/catch so callers can treat storage as best-effort: a private-mode\n * Safari that throws on `setItem` simply degrades to no persistence rather\n * than crashing the widget.\n */\nexport class LocalStorageAdapter implements ClientStorage {\n get(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // Best-effort \u2014 quota / disabled storage / private mode all fall through.\n }\n }\n\n remove(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n // Best-effort.\n }\n }\n}\n\n/**\n * Process-wide singleton used when the developer doesn't pass a custom\n * `storage` option. Reusing the same instance across widget mounts is\n * harmless \u2014 `LocalStorageAdapter` is stateless.\n */\nexport const defaultStorage: ClientStorage = new LocalStorageAdapter();\n", "/**\n * Comprehensive logger for the widget runtime.\n *\n * Designed for \"I have a customer reporting a weird bug on their\n * site, I want every relevant detail without rebuilding\" debugging:\n *\n * \u2022 Five levels (off / error / warn / info / debug / trace) that\n * gate output. Default `warn` \u2014 errors + warnings always go\n * through, info / debug / trace stay silent until opted in.\n * \u2022 Scoped child loggers \u2014 `logger.scope(\"transport\")` returns a\n * logger that tags every line `[helpai:transport] \u2026`. Every\n * subsystem has its own scope so the console transcript reads\n * like a flight recorder.\n * \u2022 Structured payloads \u2014 every call accepts an object as the\n * second arg; the browser console renders it inline, expandable.\n * \u2022 Remote toggle \u2014 `chat.setDebug(\"trace\")`, `HelpAI.setDebug(\u2026)`,\n * `?helpai-debug=trace`, or the `options.debug` mount param all\n * converge on the same internal level. Persisted to\n * `localStorage[\"helpai.debug\"]` so a refresh keeps visibility.\n * \u2022 Co-hosted brand safety \u2014 every signal (query, global key, log\n * tag) is brand-prefixed so `help-ai` and `go-ai` widgets on the\n * same page don't fight over flags or pollute each other's\n * transcripts.\n */\n\nimport { BRAND } from \"./brand\";\n\nexport type LogLevel = \"off\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\";\n\nconst LEVELS: LogLevel[] = [\"off\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"];\n\nexport interface Logger {\n error: (message: string, payload?: unknown) => void;\n warn: (message: string, payload?: unknown) => void;\n info: (message: string, payload?: unknown) => void;\n debug: (message: string, payload?: unknown) => void;\n trace: (message: string, payload?: unknown) => void;\n /** Child logger with appended scope: `[brand:parent:child] \u2026`. */\n scope: (childScope: string) => Logger;\n}\n\nconst p = BRAND.cssPrefix;\nconst STORAGE_KEY = `${p}.debug`;\nconst GLOBAL_FLAG_KEY = `__${BRAND.global}_DEBUG__`;\nconst URL_QUERY_KEY = `${p}-debug`;\n\nlet levelIndex = 2; // \"warn\"\nlet sink: ((entry: LogEntry) => void) | null = null;\n\nfunction resolveInitial(): number {\n if (typeof window === \"undefined\") return 2;\n try {\n const qs = new URLSearchParams(window.location.search).get(URL_QUERY_KEY);\n if (qs !== null) {\n const i = parseToIndex(qs);\n if (i >= 0) return i;\n }\n } catch {\n /* unsupported URL \u2014 ignore */\n }\n try {\n const persisted = window.localStorage?.getItem(STORAGE_KEY);\n if (persisted) {\n const i = parseToIndex(persisted);\n if (i >= 0) return i;\n }\n } catch {\n /* private-mode Safari etc. */\n }\n if ((window as unknown as Record<string, unknown>)[GLOBAL_FLAG_KEY]) return 4;\n return 2;\n}\n\nfunction parseToIndex(input: unknown): number {\n if (input === true || input === \"1\" || input === \"true\") return 4;\n if (input === false || input === \"0\" || input === \"false\" || input === \"off\") return 0;\n if (typeof input === \"number\") return Math.max(0, Math.min(5, Math.floor(input)));\n if (typeof input === \"string\") {\n const i = LEVELS.indexOf(input as LogLevel);\n if (i >= 0) return i;\n }\n return -1;\n}\n\nexport function parseLevel(input: unknown): LogLevel | null {\n const i = parseToIndex(input);\n return i >= 0 ? (LEVELS[i] as LogLevel) : null;\n}\n\n/**\n * Set the active level. Persisted so the next page load keeps the\n * same visibility \u2014 critical for the \"ask a customer to reproduce\n * and send the console\" workflow.\n *\n * Available on the public surface via:\n * \u2022 `chat.setDebug(level)` on any `ChatWidgetInstance`.\n * \u2022 `HelpAI.setDebug(level)` on the global (CDN auto-boot).\n * \u2022 `HelpAI(\"setDebug\", level)` via the queueable command form.\n */\nexport function setLogLevel(input: LogLevel | boolean | number | string): void {\n const i = parseToIndex(input);\n if (i < 0) return;\n levelIndex = i;\n try {\n window.localStorage?.setItem(STORAGE_KEY, LEVELS[i] as string);\n } catch {\n /* private mode \u2014 keep in-memory level */\n }\n}\n\nexport function getLogLevel(): LogLevel {\n return LEVELS[levelIndex] as LogLevel;\n}\n\nlevelIndex = resolveInitial();\n\ntype ConsoleMethod = \"error\" | \"warn\" | \"info\" | \"debug\";\n\nfunction makeLogger(scope: string): Logger {\n const tag = `[${p}:${scope}]`;\n // Look up console.X at CALL time so test spies (and host consoles\n // that monkey-patch) actually see the writes.\n const emit = (threshold: number, key: ConsoleMethod, level: LogLevel) => (message: string, payload?: unknown) => {\n if (levelIndex < threshold) return;\n if (payload === undefined) console[key](tag, message);\n else console[key](tag, message, payload);\n if (sink) {\n try {\n sink({ level, scope, message, payload });\n } catch {\n // A sink throw must never break the widget.\n }\n }\n };\n return {\n // 0 off / 1 error / 2 warn / 3 info / 4 debug / 5 trace\n error: emit(1, \"error\", \"error\"),\n warn: emit(2, \"warn\", \"warn\"),\n info: emit(3, \"info\", \"info\"),\n debug: emit(4, \"debug\", \"debug\"),\n trace: emit(5, \"debug\", \"trace\"),\n scope: (childScope) => makeLogger(`${scope}:${childScope}`),\n };\n}\n\nexport const logger: Logger = makeLogger(\"core\");\n\n/** One structured line, as forwarded to an external {@link setLogSink} sink. */\nexport interface LogEntry {\n level: LogLevel;\n /** Subsystem tag, e.g. `\"transport\"` or `\"core:hooks\"`. */\n scope: string;\n message: string;\n payload?: unknown;\n}\n\n/**\n * Forward every emitted log line (that passes the active level gate) to an\n * external sink \u2014 pipe errors/warnings/debug into Sentry breadcrumbs, Datadog,\n * your own logger, etc. `null` clears it. One global sink per bundle; filter by\n * `scope` if you host multiple widgets. Wired publicly via `HelpAI.onLog(fn)` /\n * `chat.onLog(fn)`. Raise visibility with `setDebug(\"trace\")` to capture more.\n */\nexport function setLogSink(fn: ((entry: LogEntry) => void) | null): void {\n sink = fn;\n}\n", "/**\n * Sanitise host-asserted context objects ({@link UserContext},\n * {@link PageContext}) before they leave the browser.\n *\n * These hold data the embedding site asserts about its visitor (the widget\n * runs on third-party origins like `example.com`). It's **informational\n * context for the agent \u2014 never an auth credential** \u2014 so we clean rather\n * than reject: drop anything malformed, coerce/limit values, strip keys that\n * would collide with the widget's own identifiers, and emit `warn`s. Never\n * throws.\n */\n\nimport { logger } from \"../logger\";\nimport type { PageContext, UserContext } from \"./types\";\n\nconst log = logger.scope(\"context\");\n\n/** Keys reserved for the widget's own minted identity / auth \u2014 never host-settable. */\nconst RESERVED_KEYS = new Set([\"visitorId\", \"sessionId\", \"sessionId\", \"jwt\", \"token\", \"authorization\", \"password\"]);\nconst MAX_KEYS = 50;\nconst MAX_STRING = 1024;\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Sanitise {@link UserContext} (who the visitor is). Validates `email`. */\nexport function sanitizeUserContext(raw: unknown): UserContext | undefined {\n return sanitizeContext(raw, \"userContext\", true) as UserContext | undefined;\n}\n\n/** Sanitise {@link PageContext} (where the visit is). */\nexport function sanitizePageContext(raw: unknown): PageContext | undefined {\n return sanitizeContext(raw, \"pageContext\", false) as PageContext | undefined;\n}\n\nfunction sanitizeContext(\n raw: unknown,\n label: string,\n validateEmail: boolean,\n): Record<string, string | number | boolean> | undefined {\n if (raw == null) return undefined;\n if (typeof raw !== \"object\" || Array.isArray(raw)) {\n log.warn(`ignored ${label} \u2014 expected an object`, { type: Array.isArray(raw) ? \"array\" : typeof raw });\n return undefined;\n }\n\n const out: Record<string, string | number | boolean> = {};\n let kept = 0;\n\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n if (RESERVED_KEYS.has(key)) {\n log.warn(`dropped reserved ${label} key \"${key}\" \u2014 managed by the widget, not host-settable`);\n continue;\n }\n if (value == null) continue; // skip null/undefined silently\n if (kept >= MAX_KEYS) {\n log.warn(`${label} truncated at ${MAX_KEYS} keys \u2014 dropped \"${key}\"`);\n continue;\n }\n const clean = sanitizeValue(label, key, value);\n if (clean === undefined) continue;\n out[key] = clean;\n kept++;\n }\n\n if (validateEmail && typeof out.email === \"string\" && !EMAIL_RE.test(out.email)) {\n log.warn(`dropped malformed ${label}.email \"${out.email}\"`);\n delete out.email;\n }\n\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nfunction sanitizeValue(label: string, key: string, value: unknown): string | number | boolean | undefined {\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n log.warn(`dropped non-finite ${label}.${key}`);\n return undefined;\n }\n return value;\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") return undefined;\n if (trimmed.length > MAX_STRING) {\n log.warn(`truncated ${label}.${key} to ${MAX_STRING} chars`);\n return trimmed.slice(0, MAX_STRING);\n }\n return trimmed;\n }\n log.warn(`dropped ${label}.${key} \u2014 only string/number/boolean values are allowed`, { type: typeof value });\n return undefined;\n}\n", "/**\n * Resolver fallbacks. Every value here mirrors a `.default()` on the\n * Zod schema \u2014 single source of truth lives on the schema, this file\n * exists so the resolver has fast lookups without re-running the\n * schema definitions. **If you change one, change the other.** The\n * configurator / dashboard surfaces use Zod's `.default()` values\n * directly; the IIFE runtime path doesn't run Zod (size budget \u2014 see\n * the `bun run size` checks), so these literals are what\n * `resolveOptions` falls back to when input fields are missing.\n *\n * Precedence at runtime: customer `mount({...})` > server-pushed\n * settings > the literals in this file.\n */\n\nimport type {\n Endpoints,\n HapticsOptions,\n HeaderActionsOption,\n LauncherIconPreset,\n LauncherSize,\n LauncherVariant,\n Mode,\n ModulesOptions,\n SoundOptions,\n VoiceMode,\n WelcomeOptions,\n} from \"./types\";\n\n/**\n * Mirror of `attachmentLimitsSchema` defaults in `src/schema/widget.ts`.\n */\nexport const DEFAULT_ATTACHMENTS = {\n maxBytes: 10 * 1024 * 1024,\n maxCount: 10,\n accept: \"*/*\",\n};\n\nexport const DEFAULT_WELCOME: Required<WelcomeOptions> = { animation: \"typewriter\", speed: 60 };\n\n/** Capability defaults. Voice mode defaults to local Web Speech when available. */\nexport const DEFAULT_FEATURES = {\n files: true,\n voice: \"local\" as VoiceMode,\n};\n\n/** HTTP endpoint defaults. Paths, appended to `baseUrl`. */\nexport const DEFAULT_ENDPOINTS: Required<Endpoints> = {\n upload: \"/ai/agent/upload-file\",\n transcribe: \"/ai/agent/transcribe-audio\",\n};\n\nexport const DEFAULT_LAUNCHER = {\n variant: \"circle\" as LauncherVariant,\n size: \"md\" as LauncherSize,\n label: undefined as string | undefined,\n icon: \"chat\" as LauncherIconPreset | string,\n hidden: false,\n};\n\n/**\n * Default header-action layout per mode. Items listed in `main` appear as\n * icon buttons in the header bar; items in `overflow` appear as rows under\n * the 3-dot menu. Order is preserved; unlisted actions are hidden.\n *\n * - `floating`/`iframe` \u2014 close visible, everything else under the menu.\n * - `inline` \u2014 close listed but visibility-gated to fullscreen\n * (X exits fullscreen back to in-flow layout);\n * `isActionVisible` filters it out in the normal\n * in-flow state so the deployment doesn't have to.\n * - `standalone` \u2014 minimal chrome (already in a dedicated tab).\n * - `page` \u2014 full-viewport with sidebar; same minimal chrome as standalone.\n */\nexport const DEFAULT_ACTIONS_BY_MODE: Record<Mode, Required<HeaderActionsOption>> = {\n floating: {\n main: [\"theme\", \"history\", \"close\"],\n overflow: [\"clear\", \"expand\", \"fullscreen\", \"popOut\", \"sound\", \"language\"],\n },\n inline: {\n main: [\"theme\", \"history\", \"close\"],\n overflow: [\"clear\", \"fullscreen\", \"popOut\", \"sound\", \"language\"],\n },\n standalone: {\n main: [\"theme\", \"history\"],\n overflow: [\"clear\", \"sound\", \"language\"],\n },\n page: {\n main: [\"theme\", \"history\"],\n overflow: [\"clear\", \"sound\", \"language\"],\n },\n modal: {\n main: [\"theme\", \"history\", \"close\"],\n overflow: [\"clear\", \"fullscreen\", \"popOut\", \"sound\", \"language\"],\n },\n drawer: {\n main: [\"theme\", \"history\", \"close\"],\n overflow: [\"clear\", \"fullscreen\", \"popOut\", \"sound\", \"language\"],\n },\n};\n\n/**\n * Feedback defaults. Sound + haptics both default to enabled \u2014 sound\n * waits for a user gesture before it can play (browser policy), so\n * starting \"on\" is safe and the page owner doesn't have to opt in to\n * get the feedback their users expect. Per-event presets live in\n * `core/feedback/defaults.ts` \u2014 keeping them there avoids pulling the\n * audio module into every bundle that only reads config types.\n */\nexport const DEFAULT_SOUND: Required<Omit<SoundOptions, \"events\">> & { events: SoundOptions[\"events\"] } = {\n enabled: true,\n volume: 0.3,\n events: undefined,\n};\n\nexport const DEFAULT_HAPTICS: Required<Omit<HapticsOptions, \"events\">> & { events: HapticsOptions[\"events\"] } = {\n enabled: true,\n events: undefined,\n};\n\n/**\n * Messenger modules \u2014 mirrors `src/schema/widget/modules.ts`. Defaults to a\n * single `chat` tab, so a deployment that configures nothing is just the\n * conversation with no tab bar. A deployment opts into the tabbed surface by\n * declaring more tabs.\n */\nexport const DEFAULT_MODULES: ModulesOptions = [{ label: \"tabMessages\", layout: \"chat\" }];\n\n/** `home` layout defaults (mirrors `moduleSchema`). */\nexport const DEFAULT_HOME_MODULE = { showSearchBar: true };\n", "/**\n * Normalise raw {@link ChatWidgetOptions} into {@link ResolvedOptions}.\n *\n * No network, no DOM. Called on every `mount()`, `init()`, `update()`, and\n * after a server settings merge. The one side effect is sanitising the\n * untrusted host-provided `userContext` / `pageContext`, which may log\n * `warn`s about dropped/malformed fields (see `sanitize*Context`).\n */\n\nimport { resolveStrings } from \"@/i18n/strings\";\nimport { BRAND } from \"../brand\";\nimport { defaultStorage } from \"../storage\";\nimport { sanitizePageContext, sanitizeUserContext } from \"./user-context\";\nimport {\n DEFAULT_ACTIONS_BY_MODE,\n DEFAULT_ATTACHMENTS,\n DEFAULT_ENDPOINTS,\n DEFAULT_FEATURES,\n DEFAULT_HAPTICS,\n DEFAULT_HOME_MODULE,\n DEFAULT_LAUNCHER,\n DEFAULT_MODULES,\n DEFAULT_SOUND,\n DEFAULT_WELCOME,\n} from \"./defaults\";\nimport type {\n ChatWidgetOptions,\n ComposerOptions,\n HapticsOptions,\n HeaderActionsOption,\n LauncherOptions,\n Mode,\n ModulesOptions,\n PoweredByOption,\n ResolvedModule,\n ResolvedModules,\n ResolvedOptions,\n SoundOptions,\n ThemeOverrides,\n ThemePreference,\n} from \"./types\";\n\nexport function resolveOptions(rawOpts: ChatWidgetOptions): ResolvedOptions {\n // Guard against a non-object root (string / number / null) by\n // substituting `{}` so every `opts.foo ?? default` below resolves\n // safely. Bad VALUES on specific fields are handled by the\n // `?? default` fallbacks themselves \u2014 no validation pass needed.\n // Zod-driven validation lives in the configurator / dashboard path\n // via `@/schema`; the IIFE auto-boot bundle stays tight.\n const opts = rawOpts !== null && typeof rawOpts === \"object\" ? rawOpts : ({} as ChatWidgetOptions);\n const presentation = opts.presentation ?? {};\n const behavior = opts.behavior ?? {};\n const i18n = opts.i18n ?? {};\n const feedback = opts.feedback ?? {};\n const footer = opts.footer ?? {};\n const header = opts.header ?? {};\n\n const { themeMode, themeOverrides } = parseTheme(opts.theme);\n const mode: Mode = presentation.mode ?? \"floating\";\n // Locale resolution. `i18n.locale` (page-owner explicit) wins;\n // otherwise we resolve `i18n.defaultLocale` (a concrete tag, or\n // `\"auto\"` \u2192 `navigator.language`). The end-user's persisted picker\n // choice is layered on top in App \u2014 resolveOptions stays pure /\n // DOM-free. `availableLocales` defaults to a single-entry list of\n // the active locale, which hides the language switcher (it requires\n // \u22652 to render).\n const locale = i18n.locale ?? resolveDefaultLocale(i18n.defaultLocale);\n const availableLocales = i18n.availableLocales ?? [locale];\n\n return {\n widgetId: opts.widgetId ?? \"default\",\n publicKey: opts.publicKey,\n aiAgentDeploymentId: opts.aiAgentDeploymentId,\n baseUrl: (opts.baseUrl ?? BRAND.defaultBaseUrl).replace(/\\/$/, \"\"),\n userContext: sanitizeUserContext(opts.userContext),\n pageContext: sanitizePageContext(opts.pageContext),\n position: presentation.position ?? \"bottom-right\",\n themeMode,\n themeOverrides,\n mode,\n iframe: presentation.iframe ?? false,\n responseMode: behavior.responseMode ?? \"streaming\",\n showReasoning: behavior.showReasoning ?? false,\n showToolCalls: behavior.showToolCalls ?? false,\n popOutUrl: behavior.popOutUrl ?? BRAND.standaloneUrl,\n welcome: { ...DEFAULT_WELCOME, ...opts.welcome },\n features: {\n files: opts.features?.files ?? DEFAULT_FEATURES.files,\n voice: opts.features?.voice ?? DEFAULT_FEATURES.voice,\n tools: opts.features?.tools,\n },\n endpoints: {\n upload: opts.endpoints?.upload ?? DEFAULT_ENDPOINTS.upload,\n transcribe: opts.endpoints?.transcribe ?? DEFAULT_ENDPOINTS.transcribe,\n },\n attachments: {\n accept: opts.composer?.attachments?.accept ?? DEFAULT_ATTACHMENTS.accept,\n maxBytes: opts.composer?.attachments?.maxBytes ?? DEFAULT_ATTACHMENTS.maxBytes,\n maxCount: opts.composer?.attachments?.maxCount ?? DEFAULT_ATTACHMENTS.maxCount,\n },\n locale,\n availableLocales,\n startMinimized: behavior.startMinimized ?? true,\n actions: resolveActions(mode, header.actions),\n size: presentation.size ?? {},\n launcher: resolveLauncher(opts.launcher),\n sound: resolveSound(feedback.sound),\n haptics: resolveHaptics(feedback.haptics),\n strings: resolveStrings(locale, i18n.strings),\n stringsOverride: i18n.strings,\n composerDisclaimer: footer.composerDisclaimer ?? \"\",\n poweredBy: resolvePoweredBy(footer.poweredBy),\n composer: resolveComposer(opts.composer),\n modules: resolveModules(opts.modules),\n storage: opts.storage ?? defaultStorage,\n onMessage: opts.onMessage,\n onOpen: opts.onOpen,\n onClose: opts.onClose,\n onError: opts.onError,\n };\n}\n\n/**\n * Split the union-shaped `theme` input into the two slots\n * `ResolvedOptions` exposes \u2014 a preference enum plus an overrides\n * object.\n */\nfunction parseTheme(input: ChatWidgetOptions[\"theme\"]): { themeMode: ThemePreference; themeOverrides: ThemeOverrides } {\n if (typeof input === \"string\") return { themeMode: input, themeOverrides: {} };\n if (input && typeof input === \"object\") return { themeMode: \"auto\", themeOverrides: input };\n return { themeMode: \"auto\", themeOverrides: {} };\n}\n\n/**\n * Resolve the `defaultLocale` config field down to a concrete BCP-47 tag.\n *\n * - Unset or `\"auto\"` \u2192 sniff `navigator.language` (or `\"en\"` outside browsers).\n * - Any other value \u2192 returned as-is (page-owner / server explicit override).\n */\nfunction resolveDefaultLocale(input: string | undefined): string {\n if (input && input !== \"auto\") return input;\n return typeof navigator !== \"undefined\" && navigator.language ? navigator.language : \"en\";\n}\n\nfunction resolveActions(mode: Mode, overrides: HeaderActionsOption | undefined): Required<HeaderActionsOption> {\n const defaults = DEFAULT_ACTIONS_BY_MODE[mode];\n return {\n main: overrides?.main ?? defaults.main,\n overflow: overrides?.overflow ?? defaults.overflow,\n };\n}\n\nfunction resolveSound(overrides: SoundOptions | undefined): ResolvedOptions[\"sound\"] {\n return {\n enabled: overrides?.enabled ?? DEFAULT_SOUND.enabled,\n volume: overrides?.volume ?? DEFAULT_SOUND.volume,\n events: overrides?.events,\n };\n}\n\nfunction resolveHaptics(overrides: HapticsOptions | undefined): ResolvedOptions[\"haptics\"] {\n return {\n enabled: overrides?.enabled ?? DEFAULT_HAPTICS.enabled,\n events: overrides?.events,\n };\n}\n\n/**\n * Normalise the attribution object. The brand provides a sensible\n * default (`Powered by <DisplayName>` linking to the brand site) so\n * page-owners don't need to wire anything; explicit overrides win\n * per-field. Pass `poweredBy: { text: \"\" }` to opt out.\n */\nfunction resolvePoweredBy(overrides: PoweredByOption | undefined): PoweredByOption | null {\n const brandDefault: PoweredByOption = {\n text: `Powered by ${BRAND.displayName}`,\n href: BRAND.siteUrl,\n };\n const merged = { ...brandDefault, ...overrides };\n // Empty `text` + no `logoUrl` \u2192 caller is opting out.\n const hasContent = Boolean(merged.text || merged.logoUrl);\n return hasContent ? merged : null;\n}\n\nfunction resolveComposer(overrides: ComposerOptions | undefined): ResolvedOptions[\"composer\"] {\n const sb = overrides?.sendButton;\n return {\n sendButton: {\n shape: sb?.shape ?? \"circle\",\n icon: sb?.icon ?? \"arrowUp\",\n variant: sb?.variant ?? \"filled\",\n },\n };\n}\n\n/**\n * Resolve the messenger `modules` list: keep declared order, cap at 4, fill a\n * stable `id` (`id` ?? first `contentTags` ?? `layout`, deduped), and default\n * `home.searchEnabled`. An ABSENT list (undefined) falls back to\n * {@link DEFAULT_MODULES} (a single `chat` tab); an explicit EMPTY list stays\n * empty (\u2192 the empty state). Entries missing `label`/`layout` are dropped.\n */\nfunction resolveModules(overrides: ModulesOptions | undefined): ResolvedModules {\n const raw = overrides === undefined ? DEFAULT_MODULES : overrides;\n const seen = new Set<string>();\n const list: ResolvedModule[] = [];\n for (const m of raw.slice(0, 4)) {\n if (!m?.label || !m?.layout) continue;\n const tags = m.contentTags?.filter(Boolean);\n let id = m.id ?? tags?.[0] ?? m.layout;\n while (seen.has(id)) id = `${id}-${list.length}`;\n seen.add(id);\n const resolved: ResolvedModule = { id, label: m.label, layout: m.layout };\n if (tags && tags.length > 0) resolved.contentTags = tags;\n if (m.layout === \"home\") {\n resolved.brandName = m.brandName;\n resolved.greetingText = m.greetingText;\n resolved.showGreeting = m.showGreeting ?? true;\n resolved.showSearchBar = m.showSearchBar ?? DEFAULT_HOME_MODULE.showSearchBar;\n resolved.showRecentMessages = m.showRecentMessages ?? true;\n if (m.userAvatars && m.userAvatars.length > 0) resolved.userAvatars = m.userAvatars;\n if (m.status) resolved.status = m.status;\n resolved.contentBlockTitle = m.contentBlockTitle;\n }\n list.push(resolved);\n }\n const byId = Object.fromEntries(list.map((m) => [m.id, m]));\n return { list, byId };\n}\n\nfunction resolveLauncher(overrides: LauncherOptions | undefined): ResolvedOptions[\"launcher\"] {\n // Callout defaults: shape \"pill\", auto-position (component picks\n // left/right based on launcher corner), 1.2s reveal delay, no\n // auto-dismiss, animated, dismiss-once. Empty / missing `text`\n // collapses the whole callout to `null` so the UI never renders\n // an empty bubble.\n const callout = overrides?.callout?.text\n ? {\n shape: \"pill\" as const,\n position: \"auto\" as const,\n delayMs: 1200,\n autoDismissMs: 0,\n animated: true,\n persistent: true,\n ...overrides.callout,\n }\n : null;\n return {\n variant: overrides?.variant ?? DEFAULT_LAUNCHER.variant,\n size: overrides?.size ?? DEFAULT_LAUNCHER.size,\n label: overrides?.label ?? DEFAULT_LAUNCHER.label,\n icon: overrides?.icon ?? DEFAULT_LAUNCHER.icon,\n hidden: overrides?.hidden ?? DEFAULT_LAUNCHER.hidden,\n callout,\n };\n}\n", "/**\n * Apply user-driven runtime overrides (active locale, active theme\n * mode) on top of an already-resolved `ResolvedOptions` snapshot.\n *\n * `resolveOptions(rawOptions)` happens once per mount / option update\n * and produces the immutable baseline. The user can then change\n * locale (language switcher) or theme (theme switcher) at runtime;\n * `applyOptionOverrides` produces a fresh `ResolvedOptions` with\n * those two fields swapped. Identity-preserving when neither override\n * differs from the resolved baseline, so downstream `useMemo` deps on\n * the returned object stay stable in the common no-change case.\n *\n * Used by `<App>` via `useOptionsWithOverrides()` \u2014 the hook just\n * wraps this function in a `useMemo` for ergonomics.\n */\n\nimport { resolveStrings } from \"@/i18n/strings\";\nimport type { ResolvedOptions, ThemePreference } from \"./types\";\n\nexport function applyOptionOverrides(\n options: ResolvedOptions,\n activeLocale: string,\n activeThemeMode: ThemePreference,\n): ResolvedOptions {\n // Identity-preserving fast path \u2014 every common render hits this.\n if (activeLocale === options.locale && activeThemeMode === options.themeMode) return options;\n\n const next: ResolvedOptions = { ...options };\n if (activeLocale !== options.locale) {\n next.locale = activeLocale;\n next.strings = resolveStrings(activeLocale, options.stringsOverride);\n }\n if (activeThemeMode !== options.themeMode) next.themeMode = activeThemeMode;\n return next;\n}\n", "/**\n * Merge server-pushed settings into raw user options.\n *\n * Policy: **user wins per-field.** If the developer explicitly passed a\n * value to `init()`, it stays. Only fields the developer left\n * `undefined` take their value from the server. Section-shaped fields\n * merge at the leaf level so both sides can contribute partial configs.\n *\n * Both sides use the nested shape \u2014 `mode`/`position` live on\n * `presentation`, `startMinimized`/`responseMode` on `behavior`, etc.\n * Theme is a union (preference enum or overrides object); it's\n * merged scalar-style since a preference value can't sit alongside\n * overrides.\n */\n\nimport { isNestedShape, type LocaleStrings } from \"@/i18n/strings\";\nimport type { ChatWidgetOptions, ServerConfig, StringsOverride } from \"./types\";\n\n/** Top-level keys that the server can push and we merge object-style. */\nconst SECTION_KEYS = [\n \"presentation\",\n \"behavior\",\n \"theme\",\n \"launcher\",\n \"header\",\n \"composer\",\n \"welcome\",\n \"feedback\",\n \"i18n\",\n \"footer\",\n \"features\",\n \"endpoints\",\n] as const satisfies readonly (keyof ServerConfig & keyof ChatWidgetOptions)[];\n\nexport function mergeServerConfig(user: ChatWidgetOptions, server: ServerConfig | undefined): ChatWidgetOptions {\n if (!server) return user;\n const out = { ...user } as Record<string, unknown>;\n\n for (const key of SECTION_KEYS) {\n const sv = server[key];\n if (sv === undefined) continue;\n const uv = (user as Record<string, unknown>)[key];\n\n // `theme` is a union (preference string OR object). Treat\n // scalar-style: user wins, otherwise server fills in.\n if (key === \"theme\") {\n if (uv === undefined) out[key] = sv;\n continue;\n }\n\n // `i18n` is handled below \u2014 `strings` needs the locale-aware merge,\n // and folding it into the generic leaf merge here would replace\n // the result a second time. Other `i18n.*` keys (defaultLocale,\n // locale, availableLocales) merge alongside in that single pass.\n if (key === \"i18n\") continue;\n\n // Section / object \u2014 leaf-merge with user winning per-key.\n out[key] = mergeLeaves(sv as Record<string, unknown>, uv as Record<string, unknown> | undefined);\n }\n\n // `modules` is an ordered ARRAY (the messenger tabs) \u2014 there's no sane\n // per-key merge, so it's replace-whole: an explicit `init({ modules })` wins,\n // otherwise the server's list is adopted.\n if (server.modules !== undefined && user.modules === undefined) out.modules = server.modules;\n\n // Single-pass `i18n` merge: leaf-merge the scalar fields\n // (defaultLocale / locale / availableLocales) AND apply the\n // locale-aware override merge for `strings`.\n const userI18n = user.i18n;\n const serverI18n = server.i18n;\n if (userI18n || serverI18n) {\n const merged = mergeLeaves(\n (serverI18n ?? {}) as Record<string, unknown>,\n userI18n as Record<string, unknown> | undefined,\n );\n const userStrings = userI18n?.strings;\n const serverStrings = serverI18n?.strings;\n if (userStrings || serverStrings) {\n merged.strings = mergeStringsOverride(serverStrings, userStrings);\n }\n out.i18n = merged;\n }\n\n return out as ChatWidgetOptions;\n}\n\n/**\n * Shallow leaf merge: every key from `server` is kept, then any key\n * the user explicitly set (non-undefined) overrides it. Used for\n * every section above except `theme` (union) and `i18n.strings`\n * (locale-aware).\n */\nfunction mergeLeaves(\n server: Record<string, unknown>,\n user: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n if (!user) return server;\n const out: Record<string, unknown> = { ...server };\n for (const [k, v] of Object.entries(user)) {\n if (v !== undefined) out[k] = v;\n }\n return out;\n}\n\n/**\n * Merge `strings` overrides from server + user.\n *\n * Both sides may use either the **flat** form (`{ send: \"Click\" }` \u2014 applies\n * to the active locale) or the **nested** form\n * (`{ en: { send: \"Click\" }, fr: { send: \"Cliquer\" } }` \u2014 per-locale).\n *\n * If either side is nested, the result is normalised to nested so no\n * per-locale strings are silently dropped. User wins per-key (consistent\n * with the rest of the merge \u2014 page owner overrides server defaults).\n */\nfunction mergeStringsOverride(server: StringsOverride | undefined, user: StringsOverride | undefined): StringsOverride {\n if (!server) return user as StringsOverride;\n if (!user) return server;\n const serverNested = isNestedShape(server);\n const userNested = isNestedShape(user);\n if (!serverNested && !userNested) {\n // Both flat \u2014 shallow merge with user winning.\n return { ...(server as LocaleStrings), ...(user as LocaleStrings) };\n }\n // At least one is nested \u2014 promote the flat side under \"en\" so no\n // per-key intent is lost, then deep-merge per locale.\n const out: Record<string, LocaleStrings> = {};\n const serverNestedMap = serverNested ? (server as Record<string, LocaleStrings>) : { en: server as LocaleStrings };\n const userNestedMap = userNested ? (user as Record<string, LocaleStrings>) : { en: user as LocaleStrings };\n for (const [loc, map] of Object.entries(serverNestedMap)) out[loc] = { ...map };\n for (const [loc, map] of Object.entries(userNestedMap)) out[loc] = { ...out[loc], ...map };\n return out;\n}\n", "/**\n * Parse kebab-case attributes into {@link ChatWidgetOptions}.\n *\n * Shared by the `<script data-*>` auto-boot path and the Web Component.\n * HTML attributes are flat (`data-mode=\"floating\"`); the nested\n * `ChatWidgetOptions` shape is the canonical input form. This parser\n * is the boundary that turns one into the other so customer-facing\n * embeds don't have to type `data-presentation-mode`.\n */\n\nimport type {\n ActionName,\n BehaviorOptions,\n ChatWidgetOptions,\n Endpoints,\n FeatureFlags,\n HeaderActionsOption,\n LauncherOptions,\n LauncherSize,\n LauncherVariant,\n Mode,\n Position,\n PresentationOptions,\n ResponseMode,\n PageContext,\n SizeOptions,\n ThemePreference,\n UserContext,\n VoiceMode,\n} from \"./types\";\n\n/**\n * Action names recognised by the data-`{main,overflow}-actions`\n * attributes. Mirrors `actionNameSchema` in\n * `src/schema/widget/header.ts` \u2014 the schema is the source of truth\n * for the dashboard / validation surface, but we duplicate the literal\n * here so the IIFE auto-boot path doesn't import `@/schema`'s Zod\n * barrel (~70 KB gz). A unit test in `tests/unit/action-names-sync.test.ts`\n * pins the two arrays in lock-step so this hand mirror can't drift.\n */\nexport const ACTION_NAME_LITERALS = [\n \"clear\",\n \"expand\",\n \"fullscreen\",\n \"popOut\",\n \"sound\",\n \"history\",\n \"language\",\n \"theme\",\n \"close\",\n] as const satisfies readonly ActionName[];\n\n/**\n * Read an attribute value. The kebab-case `name` is passed in \u2014 the\n * implementation maps it to whatever storage backs this source.\n */\nexport type AttrGetter = (name: string) => string | null;\n\ntype Str = (name: string) => string | undefined;\n\n/** `true` for any string other than the literal `\"false\"`. Useful for boolean attrs. */\nconst boolAttr = (v: string) => v !== \"false\";\n\n/** `,`-separated \u2192 `string[]` (trimmed, empty segments dropped). */\nconst csv = (v: string) =>\n v\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n\n// ---- embed-time (L2) attrs \u2014 top-level on ChatWidgetOptions ------------\n\ntype EmbedScalarSpec = [attr: string, key: keyof ChatWidgetOptions, cast?: (raw: string) => unknown];\n\nconst EMBED_SCALAR_ATTRS: EmbedScalarSpec[] = [\n [\"public-key\", \"publicKey\"],\n [\"ai-agent-deployment-id\", \"aiAgentDeploymentId\"],\n [\"base-url\", \"baseUrl\"],\n [\"theme\", \"theme\", (v) => v as ThemePreference],\n];\n\n// ---- presentation section attrs ---------------------------------------\n\ntype PresentationScalarSpec = [attr: string, key: keyof PresentationOptions, cast?: (v: string) => unknown];\n\nconst PRESENTATION_ATTRS: PresentationScalarSpec[] = [\n [\"mode\", \"mode\", (v) => v as Mode],\n [\"position\", \"position\", (v) => v as Position],\n [\"iframe\", \"iframe\", boolAttr],\n];\n\nconst PRESENTATION_SIZE_ATTRS: Array<[attr: string, key: keyof SizeOptions]> = [\n [\"width\", \"width\"],\n [\"height\", \"height\"],\n];\n\n// ---- behavior section attrs -------------------------------------------\n\ntype BehaviorScalarSpec = [attr: string, key: keyof BehaviorOptions, cast?: (v: string) => unknown];\n\nconst BEHAVIOR_ATTRS: BehaviorScalarSpec[] = [\n [\"response-mode\", \"responseMode\", (v) => v as ResponseMode],\n [\"start-minimized\", \"startMinimized\", boolAttr],\n [\"show-reasoning\", \"showReasoning\", boolAttr],\n [\"show-tool-calls\", \"showToolCalls\", boolAttr],\n];\n\n// ---- i18n section attrs -----------------------------------------------\n\nconst I18N_ATTRS: Array<[attr: string, key: keyof NonNullable<ChatWidgetOptions[\"i18n\"]>]> = [[\"locale\", \"locale\"]];\n\n// ---- header / launcher / composer / endpoints / features ---------------\n\n/**\n * Recognised action names \u2014 anything else is dropped from the parsed\n * list. Mirrors `actionNameSchema` in the schema package; the unit\n * test linked in `ACTION_NAME_LITERALS` above pins them in lock-step.\n */\nconst ACTION_NAMES = new Set<ActionName>(ACTION_NAME_LITERALS);\n\nconst onlyValidActions = (v: string): ActionName[] =>\n csv(v).filter((name): name is ActionName => ACTION_NAMES.has(name as ActionName));\n\nconst LAUNCHER_ATTRS: Array<[attr: string, key: keyof LauncherOptions, cast?: (v: string) => unknown]> = [\n [\"launcher-variant\", \"variant\", (v) => v as LauncherVariant],\n [\"launcher-size\", \"size\", (v) => v as LauncherSize],\n [\"launcher-icon\", \"icon\"],\n [\"launcher-hidden\", \"hidden\", boolAttr],\n];\n\nconst FEATURE_ATTRS: Array<[attr: string, key: keyof FeatureFlags, cast?: (v: string) => unknown]> = [\n [\"features-files\", \"files\", boolAttr],\n [\"features-voice\", \"voice\", (v) => v as VoiceMode],\n [\"features-tools\", \"tools\", csv],\n];\n\nconst ENDPOINT_ATTRS: Array<[attr: string, key: keyof Endpoints]> = [\n [\"endpoint-upload\", \"upload\"],\n [\"endpoint-transcribe\", \"transcribe\"],\n];\n\n// ---- host-asserted context attrs --------------------------------------\n// Informational context the embedding site asserts about its visitor \u2014\n// distinct from the widget's own visitor/session, and never auth. Custom\n// fields go through `data-user-context` / `data-page-context` as JSON.\n\nconst USER_CONTEXT_ATTRS: Array<[attr: string, key: \"id\" | \"email\" | \"name\"]> = [\n [\"user-id\", \"id\"],\n [\"user-email\", \"email\"],\n [\"user-name\", \"name\"],\n];\n\nconst PAGE_CONTEXT_ATTRS: Array<[attr: string, key: \"area\" | \"url\" | \"path\" | \"title\"]> = [\n [\"page-area\", \"area\"],\n [\"page-url\", \"url\"],\n [\"page-path\", \"path\"],\n [\"page-title\", \"title\"],\n];\n\n/**\n * Attributes that don't fit a generic table because their parser\n * needs custom logic (multi-attr aggregation, empty-string accepted,\n * etc.). Listed here so they're folded into `REACTIVE_ATTRS` without\n * a free-floating string literal further down.\n */\nconst SPECIAL_REACTIVE_ATTRS = [\n \"launcher-label\",\n \"callout-text\",\n \"callout-delay\",\n \"actions-main\",\n \"actions-overflow\",\n \"user-context\",\n \"page-context\",\n] as const;\n\n/**\n * Every flat-attribute table in this file. Single source of truth \u2014\n * both the runtime parser and `REACTIVE_ATTRS` derive from this list.\n */\nconst ATTR_TABLES = [\n EMBED_SCALAR_ATTRS,\n PRESENTATION_ATTRS,\n PRESENTATION_SIZE_ATTRS,\n BEHAVIOR_ATTRS,\n I18N_ATTRS,\n LAUNCHER_ATTRS,\n FEATURE_ATTRS,\n ENDPOINT_ATTRS,\n USER_CONTEXT_ATTRS,\n PAGE_CONTEXT_ATTRS,\n] as const;\n\n/**\n * Attributes the web component observes for runtime mutation.\n *\n * `iframe` and `start-minimized` are filtered out because both\n * require a full re-mount path (not just `attributeChangedCallback`).\n */\nconst NON_REACTIVE_SCALARS = new Set<string>([\"iframe\", \"start-minimized\"]);\nexport const REACTIVE_ATTRS: readonly string[] = [\n ...ATTR_TABLES.flatMap((table) =>\n (table as readonly (readonly [string, ...unknown[]])[])\n .map(([attr]) => attr)\n .filter((attr) => !NON_REACTIVE_SCALARS.has(attr)),\n ),\n ...SPECIAL_REACTIVE_ATTRS,\n];\n\n/**\n * Parse the flat HTML data-* attribute surface into nested\n * `ChatWidgetOptions`. Sections (`presentation`, `behavior`, `i18n`,\n * `header`, `launcher`, `composer`, `features`) get instantiated only\n * when at least one of their fields is present, so the resulting\n * object is sparse.\n */\nexport function parseAttributes(get: AttrGetter): ChatWidgetOptions {\n const opts: ChatWidgetOptions = {};\n const str: Str = (name) => {\n const v = get(name);\n return v === null || v === \"\" ? undefined : v;\n };\n\n // Embed-time (L2) scalars sit at the top level.\n for (const [attr, key, cast] of EMBED_SCALAR_ATTRS) {\n const raw = str(attr);\n if (raw === undefined) continue;\n (opts as Record<string, unknown>)[key] = cast ? cast(raw) : raw;\n }\n\n // Presentation section.\n const presentationFlat = collectSubset<PresentationOptions>(str, PRESENTATION_ATTRS);\n const size = collectSubset<SizeOptions>(str, PRESENTATION_SIZE_ATTRS);\n if (presentationFlat || size) {\n opts.presentation = { ...presentationFlat, ...(size ? { size } : {}) };\n }\n\n // Behavior section.\n const behavior = collectSubset<BehaviorOptions>(str, BEHAVIOR_ATTRS);\n if (behavior) opts.behavior = behavior;\n\n // i18n section.\n const i18n = collectSubset<NonNullable<ChatWidgetOptions[\"i18n\"]>>(str, I18N_ATTRS);\n if (i18n) opts.i18n = i18n;\n\n // Launcher (incl. callout sub-block + `launcher-label` empty-OK).\n const launcher = parseLauncher(get, str);\n if (launcher) opts.launcher = launcher;\n\n // Features.\n const features = collectSubset<FeatureFlags>(str, FEATURE_ATTRS);\n if (features) opts.features = features;\n\n // Endpoints.\n const endpoints = collectSubset<Endpoints>(str, ENDPOINT_ATTRS);\n if (endpoints) opts.endpoints = endpoints;\n\n // Host-asserted context (informational, untrusted, sanitised later in\n // `resolveOptions`). Recognised scalar fields + an optional JSON object of\n // extra custom fields. Explicit scalar attrs win over JSON keys.\n const userContext = collectContext(str, USER_CONTEXT_ATTRS, \"user-context\");\n if (userContext) opts.userContext = userContext as UserContext;\n const pageContext = collectContext(str, PAGE_CONTEXT_ATTRS, \"page-context\");\n if (pageContext) opts.pageContext = pageContext as PageContext;\n\n // Header actions.\n const actions = parseActions(str);\n if (actions) opts.header = { actions };\n\n return opts;\n}\n\n/**\n * Parse a `<script>` tag's `dataset` into {@link ChatWidgetOptions}.\n * Converts camelCase `dataset` keys back to kebab-case for {@link parseAttributes}.\n */\nexport function optionsFromScriptDataset(ds: DOMStringMap | undefined): ChatWidgetOptions {\n if (!ds) return {};\n return parseAttributes((attr) => ds[kebabToCamel(attr)] ?? null);\n}\n\n/**\n * Look for the two `actions-*` lists and build {@link HeaderActionsOption}.\n * Returns `null` when neither is present so the resolver falls back to\n * per-mode defaults.\n */\nfunction parseActions(str: Str): HeaderActionsOption | null {\n const mainRaw = str(\"actions-main\");\n const overflowRaw = str(\"actions-overflow\");\n if (mainRaw === undefined && overflowRaw === undefined) return null;\n const out: HeaderActionsOption = {};\n if (mainRaw !== undefined) out.main = onlyValidActions(mainRaw);\n if (overflowRaw !== undefined) out.overflow = onlyValidActions(overflowRaw);\n return out;\n}\n\n// ---- helpers -----------------------------------------------------------\n\nfunction kebabToCamel(kebab: string): string {\n return kebab.replaceAll(/-([a-z])/g, (_, c) => c.toUpperCase());\n}\n\n/**\n * Apply a table of `[attr, key, cast]` tuples to a nested options block.\n * Returns `null` when no attributes matched \u2014 so callers can omit the\n * empty object from the parent.\n */\nfunction collectSubset<T extends object>(\n str: Str,\n table: ReadonlyArray<readonly [string, keyof T, ((v: string) => unknown)?]>,\n): T | null {\n const out: Record<string, unknown> = {};\n for (const [attr, key, cast] of table) {\n const raw = str(attr);\n if (raw === undefined) continue;\n out[key as string] = cast ? cast(raw) : raw;\n }\n return Object.keys(out).length > 0 ? (out as T) : null;\n}\n\n/**\n * Parse a host-asserted context block (`userContext` / `pageContext`):\n * recognised scalar attrs from `table`, merged onto an optional JSON object in\n * `jsonAttr` (extra custom fields). Explicit scalar attrs win over JSON keys.\n * Malformed JSON is ignored \u2014 the scalar fields still apply. Values are\n * sanitised later in `resolveOptions`. Returns `null` when nothing matched.\n */\nfunction collectContext(\n str: Str,\n table: ReadonlyArray<readonly [string, string]>,\n jsonAttr: string,\n): Record<string, string> | null {\n const scalars: Record<string, string> = {};\n for (const [attr, key] of table) {\n const raw = str(attr);\n if (raw !== undefined) scalars[key] = raw;\n }\n\n let fromJson: Record<string, string> = {};\n const jsonRaw = str(jsonAttr);\n if (jsonRaw) {\n try {\n const parsed = JSON.parse(jsonRaw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) fromJson = parsed as Record<string, string>;\n } catch {\n // Ignore malformed JSON \u2014 recognised scalar fields still apply.\n }\n }\n\n const out = { ...fromJson, ...scalars };\n return Object.keys(out).length > 0 ? out : null;\n}\n\n/** Launcher has a wrinkle (`callout-text` + `callout-delay` pair), so it gets its own parser. */\nfunction parseLauncher(get: AttrGetter, str: Str): LauncherOptions | null {\n const base = collectSubset<LauncherOptions>(str, LAUNCHER_ATTRS) ?? {};\n\n // `launcher-label` accepts the empty string (renders a wordless pill),\n // so use the raw getter rather than `str`, which strips empties.\n const label = get(\"launcher-label\");\n if (label !== null) base.label = label;\n\n const text = str(\"callout-text\");\n if (text) {\n const delay = Number.parseInt(str(\"callout-delay\") ?? \"\", 10);\n base.callout = { text, ...(Number.isFinite(delay) ? { delayMs: delay } : {}) };\n }\n\n return Object.keys(base).length > 0 ? base : null;\n}\n", "/**\n * Lightweight runtime type-guards for the page-mode payloads on the\n * start-session response (`site` + `blocks`). The canonical Zod schemas\n * in `src/schema/deployment.ts` (`siteConfigSchema` /\n * `blocksConfigSchema`) are the source of truth for the wire\n * contract, but the IIFE auto-boot bundle can't import Zod (size\n * budget \u2014 see `bun run size`).\n *\n * Both `siteConfigSchema` and `blocksConfigSchema` are fully optional\n * `.loose()` shapes \u2014 there is no required discriminator field on\n * either. These narrowers accept any plain object and reject the\n * shapes that would crash the renderer (non-array `navigation` /\n * `linkCards`). Dashboards / dev consumers use `safeParse` via\n * `@helpai/elements/schema` for full validation.\n *\n * Renderers consume:\n * - `site.{title, logo, logoDark}` \u2014 sidebar brand identity\n * - `blocks.{navigation, linkCards}` \u2014 each Link has\n * `id?, label, href?, icon?, description?`.\n * Page URL slug lives on `deployment.slug`, not `site.slug`.\n */\n\nimport type { BlocksConfig, SiteConfig } from \"@/schema\";\n\nfunction isPlainObject(raw: unknown): raw is Record<string, unknown> {\n return !!raw && typeof raw === \"object\" && !Array.isArray(raw);\n}\n\nexport function isSiteConfigShape(raw: unknown): raw is SiteConfig {\n return isPlainObject(raw);\n}\n\nexport function isBlocksConfigShape(raw: unknown): raw is BlocksConfig {\n if (!isPlainObject(raw)) return false;\n // All fields are optional; reject only when one of the consumed\n // arrays is the wrong type. Renderer falls back to `[]` when missing.\n if (raw.navigation !== undefined && !Array.isArray(raw.navigation)) return false;\n if (raw.linkCards !== undefined && !Array.isArray(raw.linkCards)) return false;\n return true;\n}\n", "/**\n * Host-element command bus.\n *\n * The programmatic API (`chat.open()`, `.close()`, `.expand()`, `.popOut()`)\n * dispatches `CustomEvent`s on the shadow host element. The App listens\n * for those same events and routes them to its state handlers. That\n * keeps the two modules decoupled \u2014 `index.ts` doesn't hold a ref to App\n * state, App doesn't know about the public instance handle.\n *\n * This module is the single source of truth for the event names + the\n * tiny helpers on both sides of the bridge.\n */\n\nimport { BRAND } from \"./brand\";\n\n/** Names of commands exchanged between `index.ts` and `App`. */\nexport type HostCommand = \"open\" | \"close\" | \"expand\" | \"fullscreen\" | \"popout\";\n\n/** `${BRAND.cssPrefix}:${command}` \u2014 the full event name on the host. */\nfunction commandEventName(cmd: HostCommand): string {\n return `${BRAND.cssPrefix}:${cmd}`;\n}\n\n/** Dispatch a command to the App via the shadow host element. */\nexport function dispatchHostCommand(host: HTMLElement, cmd: HostCommand): void {\n host.dispatchEvent(new CustomEvent(commandEventName(cmd)));\n}\n\n/**\n * Bind every command name to its handler on the host element.\n * Returns a cleanup function that removes all listeners.\n */\nexport function bindHostCommands(host: HTMLElement, handlers: Record<HostCommand, () => void>): () => void {\n const entries = Object.entries(handlers) as [HostCommand, () => void][];\n const wrapped = entries.map(([cmd, fn]) => {\n const event = commandEventName(cmd);\n const listener = () => fn();\n host.addEventListener(event, listener);\n return [event, listener] as const;\n });\n return () => {\n for (const [event, listener] of wrapped) host.removeEventListener(event, listener);\n };\n}\n", ":host{--__P__-accent: __BRAND_ACCENT__;--__P__-accent-text: __BRAND_ACCENT_TEXT__;--__P__-radius: __BRAND_RADIUS__;--__P__-radius-sm: 8px;--__P__-radius-md: 10px;--__P__-radius-lg: 16px;--__P__-font: __BRAND_FONT__;--__P__-font-display: var(--__P__-font);--__P__-font-mono: ui-monospace, \"SF Mono\", Menlo, Consolas, monospace;--__P__-bg: oklch(1 0 0);--__P__-bg-elevated: oklch(.9789 .0029 278.3);--__P__-surface: var(--__P__-bg-elevated);--__P__-muted: var(--__P__-fg-muted);--__P__-hover: oklch(.1686 .011 278.3 / .06);--__P__-fg: oklch(.1686 .011 278.3);--__P__-fg-muted: oklch(.4932 .0301 278.3);--__P__-border: oklch(.9312 .0084 278.3);--__P__-border-strong: oklch(.8757 .0131 278.3);--__P__-bubble-user: var(--__P__-accent);--__P__-bubble-user-text: var(--__P__-accent-text);--__P__-bubble-assistant: oklch(.9638 .0058 278.3);--__P__-bubble-assistant-text: var(--__P__-fg);--__P__-on-accent: var(--__P__-accent-text);--__P__-success: oklch(.6456 .1654 151.53);--__P__-warning: oklch(.7135 .1494 74.05);--__P__-danger: oklch(.5966 .1818 24.51);--__P__-danger-text: oklch(.5054 .1905 27.52);--__P__-danger-bg: oklch(.5966 .1818 24.51 / .1);--__P__-neutral: oklch(.7137 .0192 278.3);--__P__-focus: var(--__P__-accent);--__P__-shadow-panel: 0 8px 24px -8px rgba(13, 15, 20, .18), 0 24px 64px -24px rgba(13, 15, 20, .24);--__P__-shadow-card: 0 1px 2px rgba(13, 15, 20, .06), 0 4px 12px -6px rgba(13, 15, 20, .12);--__P__-backdrop: oklch(.1686 .011 278.3 / .55);--__P__-shadow-fab: 0 6px 16px -4px color-mix(in oklch, var(--__P__-accent) 45%, transparent), 0 12px 32px -12px rgba(13, 15, 20, .25);--__P__-z-fab: 2147483600;--__P__-z-panel: 2147483601;--__P__-panel-w: 380px;--__P__-panel-h: 600px;--__P__-fab-size: 56px;--__P__-space-1: 4px;--__P__-space-2: 8px;--__P__-space-3: 12px;--__P__-space-4: 16px;--__P__-space-5: 20px;--__P__-space-6: 24px;--__P__-space-8: 32px;--__P__-text-xs: 12px;--__P__-text-sm: 13px;--__P__-text-base: 15px;--__P__-text-md: 16px;--__P__-text-lg: 18px;--__P__-text-xl: 22px;--__P__-text-2xl: 30px;--__P__-ease: cubic-bezier(.2, .7, .2, 1);--__P__-ease-out: cubic-bezier(.2, .7, .2, 1);--__P__-ease-in: cubic-bezier(.4, 0, 1, 1);--__P__-ease-in-out: cubic-bezier(.65, 0, .35, 1);--__P__-dur-quick: .12s;--__P__-dur-base: .22s;--__P__-dur-slow: .32s;font-family:var(--__P__-font);color:var(--__P__-fg);color-scheme:light}:host([data-theme=\"dark\"]){--__P__-bg: oklch(.1783 .0128 278.3);--__P__-bg-elevated: oklch(.2232 .0182 278.3);--__P__-fg: oklch(.9368 .01 278.3);--__P__-fg-muted: oklch(.7048 .0296 278.3);--__P__-border: oklch(.2752 .0266 278.3);--__P__-border-strong: oklch(.335 .0291 278.3);--__P__-hover: oklch(.9368 .01 278.3 / .08);--__P__-bubble-assistant: oklch(.2474 .0311 278.3);--__P__-bubble-assistant-text: var(--__P__-fg);--__P__-danger-text: oklch(.8077 .1035 19.57);--__P__-danger-bg: oklch(.5966 .1818 24.51 / .18);--__P__-shadow-panel: 0 8px 24px -8px rgba(0, 0, 0, .6), 0 24px 64px -24px rgba(0, 0, 0, .7);--__P__-shadow-card: 0 1px 2px rgba(0, 0, 0, .4), 0 4px 12px -6px rgba(0, 0, 0, .5);--__P__-backdrop: rgba(0, 0, 0, .65);color-scheme:dark}@media(prefers-color-scheme:dark){:host([data-theme=\"auto\"]){--__P__-bg: oklch(.1783 .0128 278.3);--__P__-bg-elevated: oklch(.2232 .0182 278.3);--__P__-fg: oklch(.9368 .01 278.3);--__P__-fg-muted: oklch(.7048 .0296 278.3);--__P__-border: oklch(.2752 .0266 278.3);--__P__-border-strong: oklch(.335 .0291 278.3);--__P__-hover: oklch(.9368 .01 278.3 / .08);--__P__-bubble-assistant: oklch(.2474 .0311 278.3);--__P__-bubble-assistant-text: var(--__P__-fg);--__P__-shadow-panel: 0 8px 24px -8px rgba(0, 0, 0, .6), 0 24px 64px -24px rgba(0, 0, 0, .7);color-scheme:dark}}@media(prefers-reduced-motion:reduce){:host{--__P__-dur-quick: 1ms;--__P__-dur-base: 1ms;--__P__-dur-slow: 1ms}}\n", "*,*:before,*:after{box-sizing:border-box;margin:0;padding:0;border:0}button{font:inherit;color:inherit;background:none;cursor:pointer;-webkit-appearance:none;appearance:none;line-height:1}button:focus-visible,[tabindex]:focus-visible,textarea:focus-visible,input:focus-visible{outline:2px solid var(--__P__-accent);outline-offset:2px}textarea,input{font:inherit;color:inherit;background:none;border:0;outline:0;resize:none}a{color:var(--__P__-accent);text-decoration:underline;text-underline-offset:2px}img,svg{display:block;max-width:100%}ul,ol{list-style:none}.__P__-app{display:block;width:100%;height:100%;font-family:var(--__P__-font);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-feature-settings:\"cv11\",\"ss01\",\"ss03\"}.__P__-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n", ".__P__-anchor{right:16px;bottom:16px}:host([data-position=\"bottom-left\"]) .__P__-anchor,:host([data-position=\"top-left\"]) .__P__-anchor{right:auto;left:16px}:host([data-position=\"top-right\"]) .__P__-anchor,:host([data-position=\"top-left\"]) .__P__-anchor{bottom:auto;top:16px}.__P__-anchor{position:fixed;display:flex;flex-direction:column;align-items:flex-end;gap:var(--__P__-space-3);pointer-events:none}.__P__-anchor{--__P__-fab-size: 56px}.__P__-anchor[data-launcher-size=sm]{--__P__-fab-size: 44px}.__P__-anchor[data-launcher-size=md]{--__P__-fab-size: 56px}.__P__-anchor[data-launcher-size=lg]{--__P__-fab-size: 68px}.__P__-anchor>*{pointer-events:auto}.__P__-fab[data-size=sm]{--__P__-fab-size: 44px;font-size:var(--__P__-text-sm)}.__P__-fab[data-size=md]{--__P__-fab-size: 56px;font-size:14px}.__P__-fab[data-size=lg]{--__P__-fab-size: 68px;font-size:var(--__P__-text-base)}.__P__-fab{display:inline-flex;align-items:center;justify-content:center;gap:var(--__P__-space-2);color:var(--__P__-accent-text);background:var(--__P__-accent);box-shadow:var(--__P__-shadow-fab);font-weight:600;line-height:1;transform-origin:bottom right;animation:__P__-fab-in var(--__P__-dur-base) var(--__P__-ease) both;transition:transform var(--__P__-dur-base) var(--__P__-ease),opacity var(--__P__-dur-base) var(--__P__-ease),box-shadow var(--__P__-dur-base) var(--__P__-ease)}.__P__-fab:hover{transform:translateY(-2px)}.__P__-fab:active{transform:translateY(0)}.__P__-fab:focus-visible{outline:2px solid var(--__P__-on-accent);outline-offset:3px}.__P__-fab svg{width:24px;height:24px;flex-shrink:0}@keyframes __P__-fab-in{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:none}}.__P__-fab[data-leaving]{position:absolute;right:0;bottom:0;animation:__P__-fab-out var(--__P__-dur-quick) var(--__P__-ease) forwards;pointer-events:none}:host([data-position^=\"top-\"]) .__P__-fab[data-leaving]{bottom:auto;top:0}:host([data-position$=\"-left\"]) .__P__-fab[data-leaving]{right:auto;left:0}@keyframes __P__-fab-out{0%{opacity:1;transform:none}to{opacity:0;transform:scale(.85)}}.__P__-fab[data-variant=circle]{width:var(--__P__-fab-size);height:var(--__P__-fab-size);border-radius:999px}.__P__-fab[data-variant=circle] .__P__-fab-label{display:none}.__P__-fab[data-variant=pill]{height:var(--__P__-fab-size);padding:0 18px 0 16px;border-radius:999px}.__P__-fab[data-variant=bar]{height:var(--__P__-fab-size);padding:0 22px;border-radius:var(--__P__-radius)}.__P__-fab[data-variant=minimal]{height:var(--__P__-fab-size);padding:0 var(--__P__-space-4);border-radius:999px;background:transparent;color:var(--__P__-accent);box-shadow:none;border:1px solid currentColor}.__P__-fab[data-variant=minimal]:hover{background:color-mix(in srgb,var(--__P__-accent) 12%,transparent)}.__P__-callout{--__P__-callout-fab-h: var(--__P__-fab-size, 56px);--__P__-callout-gap: clamp(12px, calc(var(--__P__-callout-fab-h) * .25), 22px);--__P__-callout-nudge-direction: -1;position:absolute;display:inline-flex;align-items:center;gap:var(--__P__-space-2);padding:10px 14px;background:var(--__P__-fg);color:var(--__P__-bg);border-radius:999px;font-size:var(--__P__-text-sm);font-weight:600;line-height:1.2;box-shadow:0 10px 30px -8px #00000059;pointer-events:auto;animation:__P__-callout-in var(--__P__-dur-slow) var(--__P__-ease);z-index:1;max-width:240px;white-space:nowrap}.__P__-callout[data-position=left]{right:calc(100% + var(--__P__-callout-gap));bottom:calc(var(--__P__-callout-fab-h) / 2);transform:translateY(50%)}.__P__-callout[data-position=right]{left:calc(100% + var(--__P__-callout-gap));bottom:calc(var(--__P__-callout-fab-h) / 2);transform:translateY(50%);--__P__-callout-nudge-direction: 1}.__P__-callout[data-position=top]{bottom:calc(100% + var(--__P__-callout-gap));right:0;max-width:min(280px,calc(100vw - 32px))}.__P__-callout[data-position=bottom]{top:calc(100% + var(--__P__-callout-gap));right:0;max-width:min(280px,calc(100vw - 32px))}:host([data-position$=\"-left\"]) .__P__-callout[data-position=top],:host([data-position$=\"-left\"]) .__P__-callout[data-position=bottom]{right:auto;left:0}.__P__-callout[data-shape=bubble]{border-radius:var(--__P__-radius-md);white-space:normal;width:max-content;max-width:min(280px,calc(100vw - 32px))}.__P__-callout[data-shape=callout]{padding:12px 18px;font-size:14px;background:var(--__P__-accent);color:var(--__P__-accent-text)}.__P__-callout:after{content:\"\";position:absolute;width:12px;height:12px;background:inherit;border-radius:2px;transform:rotate(45deg)}.__P__-callout[data-position=left]:after{right:-5px;top:50%;margin-top:-6px}.__P__-callout[data-position=right]:after{left:-5px;top:50%;margin-top:-6px}.__P__-callout[data-position=top]:after{bottom:-5px;right:calc(var(--__P__-fab-size, 56px) / 2 - 6px)}.__P__-callout[data-position=bottom]:after{top:-5px;right:calc(var(--__P__-fab-size, 56px) / 2 - 6px)}:host([data-position$=\"-left\"]) .__P__-callout[data-position=top]:after,:host([data-position$=\"-left\"]) .__P__-callout[data-position=bottom]:after{right:auto;left:calc(var(--__P__-fab-size, 56px) / 2 - 6px)}.__P__-callout[data-animated]{animation:__P__-callout-in var(--__P__-dur-slow) var(--__P__-ease),__P__-callout-nudge 1.6s var(--__P__-ease-in-out) var(--__P__-dur-slow) infinite}@keyframes __P__-callout-in{0%{opacity:0}to{opacity:1}}@keyframes __P__-callout-nudge{0%,to{margin-left:0;margin-right:0}50%{margin-left:calc(6px * var(--__P__-callout-nudge-direction));margin-right:calc(-6px * var(--__P__-callout-nudge-direction))}}@media(prefers-reduced-motion:reduce){.__P__-callout[data-animated]{animation:__P__-callout-in 1ms var(--__P__-ease)}.__P__-icon-btn[data-recording=true],.__P__-typing span{animation:none}}.__P__-callout-close{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:inherit;opacity:.7;cursor:pointer}.__P__-callout-close:hover{opacity:1;background:#ffffff26}.__P__-callout-close svg{width:12px;height:12px}.__P__-panel{width:var(--__P__-panel-w);height:var(--__P__-panel-h);max-width:calc(100vw - 32px);max-height:calc(100dvh - 32px);background:var(--__P__-bg);color:var(--__P__-fg);border-radius:var(--__P__-radius-lg);box-shadow:var(--__P__-shadow-panel);display:flex;flex-direction:column;overflow:hidden;transform-origin:bottom right;animation:__P__-panel-in var(--__P__-dur-slow) var(--__P__-ease);border:1px solid var(--__P__-border)}:host([data-mode=\"open\"]) .__P__-panel{width:var(--__P__-widget-w, var(--__P__-panel-w));height:var(--__P__-widget-h, var(--__P__-panel-h));min-width:var(--__P__-resize-min-w, auto);min-height:var(--__P__-resize-min-h, auto);max-width:var(--__P__-resize-max-w, calc(100vw - 32px) );max-height:var(--__P__-resize-max-h, calc(100dvh - 32px) )}@keyframes __P__-panel-in{0%{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:none}}:host([data-mode=\"expanded\"]) .__P__-panel{width:var(--__P__-expanded-w, 640px);height:var(--__P__-expanded-h, 820px);max-width:calc(100vw - 32px);max-height:calc(100dvh - 32px)}:host([data-mode=\"fullscreen\"]){z-index:var(--__P__-z-panel)!important}:host([data-mode=\"fullscreen\"]) .__P__-anchor{inset:0;align-items:stretch;padding:0}:host([data-mode=\"fullscreen\"]) .__P__-panel{width:100vw;height:100dvh;max-width:none;max-height:none;border-radius:0;border:0}:host([data-mode=\"fullscreen\"]) .__P__-fab{display:none}:host([data-mode=\"inline\"]) .__P__-anchor,:host([data-mode=\"standalone\"]) .__P__-anchor{position:static;inset:auto;padding:0;align-items:stretch;width:100%;height:100%;min-height:0}:host([data-mode=\"inline\"]) .__P__-panel,:host([data-mode=\"standalone\"]) .__P__-panel{width:100%;height:100%;min-width:0;min-height:0;max-width:none;max-height:none;animation:none}:host([data-mode=\"inline\"]) .__P__-fab,:host([data-mode=\"standalone\"]) .__P__-fab{display:none}:host([data-mode=\"inline\"]){min-height:320px}:host([data-mode=\"inline\"]) .__P__-panel{border-radius:var(--__P__-radius)}:host([data-mode=\"standalone\"]) .__P__-panel{border-radius:0;border:0;box-shadow:none}:host([data-mode=\"modal\"]){position:fixed!important;inset:0!important;z-index:var(--__P__-z-panel)!important;display:block!important;width:100vw;height:100dvh;background:var(--__P__-modal-backdrop, var(--__P__-backdrop));animation:__P__-backdrop-in var(--__P__-dur-base) var(--__P__-ease) both;--__P__-panel-w: min(960px, 92vw);--__P__-panel-h: min(720px, 88dvh)}:host([data-mode=\"modal\"]) .__P__-anchor{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:var(--__P__-space-6);pointer-events:none}:host([data-mode=\"modal\"]) .__P__-panel{pointer-events:auto;width:var(--__P__-widget-w, var(--__P__-panel-w));height:var(--__P__-widget-h, var(--__P__-panel-h));max-width:calc(100vw - 48px);max-height:calc(100dvh - 48px);min-width:0;min-height:0;border-radius:var(--__P__-radius);box-shadow:var(--__P__-shadow-panel);animation:__P__-modal-in var(--__P__-dur-base) var(--__P__-ease);translate:var(--__P__-modal-dx, 0px) var(--__P__-modal-dy, 0px)}:host([data-mode=\"modal\"]) .__P__-header,:host([data-mode=\"modal\"]) .__P__-back-header,:host([data-mode=\"modal\"]) .__P__-home-hero{cursor:grab;touch-action:none}:host([data-mode=\"modal\"]) .__P__-panel[data-dragging]{cursor:grabbing;user-select:none}:host([data-mode=\"modal\"]) .__P__-panel[data-dragging] *{cursor:grabbing}:host([data-mode=\"modal\"]) .__P__-fab{display:none}@keyframes __P__-backdrop-in{0%{background:#0000}to{background:var(--__P__-modal-backdrop, var(--__P__-backdrop))}}@keyframes __P__-modal-in{0%{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}:host([data-mode=\"drawer\"]){--__P__-panel-w: min(440px, calc(100vw - 32px) );--__P__-panel-h: 100dvh}:host([data-mode=\"drawer\"]) .__P__-anchor{position:fixed;--__P__-inset-x: var(--__P__-panel-inset, 12px);--__P__-inset-y: var(--__P__-panel-inset, 3dvh);top:var(--__P__-inset-y);bottom:auto;right:var(--__P__-inset-x);left:auto;width:min(var(--__P__-widget-w, var(--__P__-panel-w)),calc(100vw - var(--__P__-inset-x) * 2));height:min(var(--__P__-widget-h, var(--__P__-panel-h)),calc(100dvh - var(--__P__-inset-y) * 2));padding:0;display:block;pointer-events:auto}:host([data-mode=\"drawer\"][data-position$=\"-left\"]) .__P__-anchor{right:auto;left:var(--__P__-inset-x)}:host([data-mode=\"drawer\"][data-position^=\"bottom-\"]) .__P__-anchor{top:auto;bottom:var(--__P__-inset-y)}:host([data-mode=\"drawer\"]) .__P__-panel{width:100%;height:100%;max-width:none;max-height:none;min-width:0;min-height:0;border-radius:var(--__P__-radius, 12px);border:1px solid var(--__P__-border);box-shadow:-8px 16px 32px -12px #00000038;animation:__P__-drawer-in var(--__P__-dur-base) var(--__P__-ease)}:host([data-mode=\"drawer\"][data-position$=\"-left\"]) .__P__-panel{box-shadow:8px 16px 32px -12px #00000038;animation:__P__-drawer-in-left var(--__P__-dur-base) var(--__P__-ease)}.__P__-fab[data-edge-tab]{display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--__P__-space-2);position:fixed;top:50%;height:140px;width:auto;min-width:0;padding:10px 8px;writing-mode:vertical-rl;text-orientation:mixed;transform:translateY(-50%);transform-origin:center;animation:__P__-edge-tab-in var(--__P__-dur-base) var(--__P__-ease) both}.__P__-fab[data-edge-tab] svg{writing-mode:horizontal-tb}.__P__-fab[data-edge-tab]:hover{transform:translateY(-50%) scale(1.03)}.__P__-fab[data-edge-tab]:active{transform:translateY(-50%) scale(.97)}:host([data-position$=\"-right\"]) .__P__-fab[data-edge-tab]{right:0;left:auto;border-radius:12px 0 0 12px;box-shadow:-6px 0 18px -8px #0000004d}:host([data-position$=\"-left\"]) .__P__-fab[data-edge-tab]{left:0;right:auto;border-radius:0 12px 12px 0;box-shadow:6px 0 18px -8px #0000004d}.__P__-fab[data-edge-tab][data-leaving]{position:fixed;top:50%;bottom:auto;animation:__P__-edge-tab-out var(--__P__-dur-quick) var(--__P__-ease) forwards}@keyframes __P__-edge-tab-in{0%{opacity:0;transform:translateY(-50%) scale(.9)}to{opacity:1;transform:translateY(-50%) scale(1)}}@keyframes __P__-edge-tab-out{0%{opacity:1;transform:translateY(-50%) scale(1)}to{opacity:0;transform:translateY(-50%) scale(.85)}}@keyframes __P__-drawer-in{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes __P__-drawer-in-left{0%{transform:translate(-100%)}to{transform:translate(0)}}@media(prefers-reduced-motion:reduce){:host([data-mode=\"drawer\"]) .__P__-panel,.__P__-fab[data-edge-tab]{animation:none}}.__P__-header{display:flex;align-items:center;gap:var(--__P__-space-2);padding:12px 14px;border-bottom:1px solid var(--__P__-border);background:var(--__P__-bg)}.__P__-header h1{font-size:14px;font-weight:600;flex:1;letter-spacing:-.01em}.__P__-agent{flex:1;display:inline-flex;align-items:center;gap:10px;min-width:0}.__P__-agent-avatar{position:relative;width:32px;height:32px;border-radius:999px;background:var(--__P__-bg-elevated);color:var(--__P__-fg-muted);display:inline-flex;align-items:center;justify-content:center;font-weight:700;font-size:var(--__P__-text-xs);overflow:hidden;flex-shrink:0}.__P__-agent-avatar img{width:100%;height:100%;object-fit:cover}.__P__-agent-avatar:after{content:\"\";position:absolute;right:-1px;bottom:-1px;width:10px;height:10px;border-radius:999px;border:2px solid var(--__P__-bg);background:var(--__P__-neutral)}.__P__-agent-avatar[data-status=online]:after{background:var(--__P__-success)}.__P__-agent-avatar[data-status=away]:after{background:var(--__P__-warning)}.__P__-agent-avatar[data-status=offline]:after{background:var(--__P__-neutral)}.__P__-agent-meta{display:flex;flex-direction:column;line-height:1.15;min-width:0}.__P__-agent-meta strong{font-size:14px;font-weight:600;letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-agent-meta span{font-size:var(--__P__-text-xs);color:var(--__P__-fg-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-suggestions{display:flex;flex-wrap:nowrap;gap:var(--__P__-space-2);padding:6px 14px 10px;overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;scroll-snap-type:x proximity;scrollbar-width:none;-ms-overflow-style:none;min-width:0}.__P__-suggestions::-webkit-scrollbar{display:none}.__P__-suggestions:before,.__P__-suggestions:after{content:\"\";flex:1 1 0;min-width:0}.__P__-suggestion{flex:0 0 auto;scroll-snap-align:center;padding:7px 14px;border-radius:999px;background:var(--__P__-bg-elevated);border:1px solid var(--__P__-border);color:var(--__P__-fg);font-size:var(--__P__-text-sm);font-weight:500;white-space:nowrap;transition:background var(--__P__-dur-quick) var(--__P__-ease),border-color var(--__P__-dur-quick) var(--__P__-ease),transform var(--__P__-dur-quick) var(--__P__-ease)}.__P__-suggestion:hover{border-color:var(--__P__-accent);color:var(--__P__-accent);transform:translateY(-1px)}.__P__-suggestion:focus-visible{outline:2px solid var(--__P__-accent);outline-offset:2px;border-color:var(--__P__-accent)}.__P__-suggestion:active{transform:translateY(0)}.__P__-icon-btn{width:32px;height:32px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;color:var(--__P__-fg-muted);transition:background var(--__P__-dur-quick) var(--__P__-ease),color var(--__P__-dur-quick) var(--__P__-ease)}.__P__-icon-btn:hover{background:var(--__P__-bg-elevated);color:var(--__P__-fg)}.__P__-icon-btn:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-icon-btn:active{background:var(--__P__-border)}.__P__-icon-btn:disabled{opacity:.45;cursor:not-allowed}.__P__-icon-btn:before{content:\"\";position:absolute;inset:-6px}.__P__-icon-btn{position:relative}.__P__-icon-btn svg{width:18px;height:18px}.__P__-icon-btn[data-recording=true]{color:var(--__P__-accent);background:color-mix(in srgb,var(--__P__-accent) 12%,transparent);animation:__P__-pulse 1.2s var(--__P__-ease) infinite}@keyframes __P__-pulse{0%,to{box-shadow:0 0 color-mix(in srgb,var(--__P__-accent) 40%,transparent)}50%{box-shadow:0 0 0 6px color-mix(in srgb,var(--__P__-accent) 0%,transparent)}}.__P__-list{flex:1;min-height:0;overflow-y:auto;padding:var(--__P__-space-4);display:flex;flex-direction:column;gap:14px;scroll-behavior:smooth;scrollbar-width:thin;scrollbar-color:var(--__P__-border-strong) transparent;scrollbar-gutter:stable}.__P__-list::-webkit-scrollbar{width:8px}.__P__-list::-webkit-scrollbar-thumb{background:var(--__P__-border-strong);border-radius:8px}.__P__-bubble-row{display:flex}.__P__-bubble-row[data-role=user]{justify-content:flex-end}.__P__-bubble-row[data-role=assistant]{justify-content:flex-start}.__P__-bubble{max-width:85%;padding:var(--__P__-space-3) var(--__P__-space-4);border-radius:var(--__P__-radius);line-height:1.6;font-size:14px;word-wrap:break-word;overflow-wrap:anywhere;box-shadow:0 1px 2px #0000000a,0 1px 8px -4px #0000000f;animation:__P__-bubble-in var(--__P__-dur-base) var(--__P__-ease)}.__P__-bubble ::selection{background:color-mix(in srgb,var(--__P__-accent) 30%,transparent)}@keyframes __P__-bubble-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.__P__-bubble-row[data-role=user] .__P__-bubble{background:var(--__P__-bubble-user);color:var(--__P__-bubble-user-text);border-bottom-right-radius:var(--__P__-radius-sm)}.__P__-bubble-row[data-role=assistant] .__P__-bubble{background:var(--__P__-bubble-assistant);color:var(--__P__-bubble-assistant-text);border-bottom-left-radius:var(--__P__-radius-sm)}.__P__-md>*:first-child{margin-top:0}.__P__-md>*:last-child{margin-bottom:0}.__P__-md p{margin:10px 0}.__P__-md h1,.__P__-md h2,.__P__-md h3,.__P__-md h4,.__P__-md h5,.__P__-md h6{margin:18px 0 8px;line-height:1.3;letter-spacing:-.01em;font-weight:700}.__P__-md h1{font-size:1.4em}.__P__-md h2{font-size:1.22em}.__P__-md h3{font-size:1.08em}.__P__-md h4,.__P__-md h5,.__P__-md h6{font-size:1em}.__P__-md>h1:first-child,.__P__-md>h2:first-child,.__P__-md>h3:first-child,.__P__-md>h4:first-child,.__P__-md>h5:first-child,.__P__-md>h6:first-child{margin-top:0}.__P__-md ul,.__P__-md ol{padding-left:1.5em;margin:10px 0}.__P__-md ul{list-style:disc}.__P__-md ol{list-style:decimal}.__P__-md li{margin:6px 0;padding-left:2px}.__P__-md li::marker{color:var(--__P__-fg-muted)}.__P__-md li>p{margin:6px 0}.__P__-md li>ul,.__P__-md li>ol{margin:6px 0}.__P__-md strong,.__P__-md b{font-weight:650;letter-spacing:-.005em}.__P__-md em,.__P__-md i{font-style:italic}.__P__-md code{font-family:var(--__P__-font-mono);font-size:.86em;padding:1px 6px;border-radius:5px;background:color-mix(in srgb,var(--__P__-accent) 10%,transparent);color:var(--__P__-fg);border:1px solid color-mix(in srgb,var(--__P__-accent) 16%,transparent)}.__P__-md pre{font-family:var(--__P__-font-mono);font-size:12.5px;padding:12px 14px;border-radius:var(--__P__-radius-sm);background:#7f7f7f1f;overflow-x:auto;margin:var(--__P__-space-3) 0;line-height:1.5}.__P__-md pre code{padding:0;background:none}.__P__-md a{color:inherit;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px}.__P__-md a:hover{text-decoration-thickness:2px}.__P__-md blockquote{margin:14px 0;padding:6px 14px;border-left:3px solid color-mix(in srgb,var(--__P__-accent) 50%,transparent);background:color-mix(in srgb,var(--__P__-accent) 5%,transparent);border-radius:0 var(--__P__-radius-sm) var(--__P__-radius-sm) 0;color:var(--__P__-fg-muted)}.__P__-md blockquote>:first-child{margin-top:0}.__P__-md blockquote>:last-child{margin-bottom:0}.__P__-md hr{border:0;height:1px;background:color-mix(in srgb,currentColor 18%,transparent);margin:var(--__P__-space-4) 0}.__P__-md table{border-collapse:collapse;margin:var(--__P__-space-3) 0;font-size:.95em;display:block;overflow-x:auto;max-width:100%;font-variant-numeric:tabular-nums}.__P__-md th,.__P__-md td{padding:6px 10px;border-bottom:1px solid color-mix(in srgb,currentColor 12%,transparent);text-align:left}.__P__-md th{font-weight:700;background:color-mix(in srgb,currentColor 6%,transparent)}.__P__-md h1+ul,.__P__-md h1+ol,.__P__-md h2+ul,.__P__-md h2+ol,.__P__-md h3+ul,.__P__-md h3+ol,.__P__-md h4+ul,.__P__-md h4+ol{margin-top:var(--__P__-space-1)}.__P__-loading{display:inline-flex;align-items:center;gap:var(--__P__-space-2);color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm)}.__P__-loading-spinner{width:14px;height:14px;border-radius:999px;border:2px solid currentColor;border-top-color:transparent;animation:__P__-spin .8s linear infinite}@keyframes __P__-spin{to{transform:rotate(1turn)}}.__P__-typing{display:inline-flex;gap:var(--__P__-space-1);padding:var(--__P__-space-1) 0}.__P__-typing span{width:6px;height:6px;border-radius:999px;background:currentColor;opacity:.4;animation:__P__-blink 1.2s var(--__P__-ease) infinite}.__P__-typing span:nth-child(2){animation-delay:.2s}.__P__-typing span:nth-child(3){animation-delay:.4s}@keyframes __P__-blink{0%,80%,to{opacity:.3;transform:translateY(0)}40%{opacity:1;transform:translateY(-2px)}}.__P__-reasoning{margin:var(--__P__-space-1) 0 var(--__P__-space-2);padding-left:var(--__P__-space-3);border-left:2px solid var(--__P__-border-strong)}.__P__-reasoning-summary{display:inline-flex;align-items:center;gap:var(--__P__-space-1);cursor:pointer;list-style:none;user-select:none;color:var(--__P__-fg-muted);font-size:var(--__P__-text-xs);font-weight:600}.__P__-reasoning-summary::-webkit-details-marker{display:none}.__P__-reasoning-summary:before{content:\"\";width:5px;height:5px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);opacity:.7;transition:transform var(--__P__-dur-quick) var(--__P__-ease)}.__P__-reasoning[open] .__P__-reasoning-summary:before{transform:rotate(45deg)}.__P__-reasoning[data-active=true] .__P__-reasoning-label{animation:__P__-reasoning-pulse 1.4s var(--__P__-ease) infinite}@keyframes __P__-reasoning-pulse{0%,to{opacity:.5}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.__P__-reasoning[data-active=true] .__P__-reasoning-label{animation:none}}.__P__-reasoning-body{margin-top:var(--__P__-space-1);color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm)}.__P__-reasoning-body>:first-child{margin-top:0}.__P__-reasoning-body>:last-child{margin-bottom:0}.__P__-tool-chip{display:inline-flex;align-items:center;gap:6px;padding:var(--__P__-space-1) var(--__P__-space-2);margin-top:6px;border-radius:999px;background:var(--__P__-bg-elevated);border:1px solid var(--__P__-border);color:var(--__P__-fg-muted);font-size:var(--__P__-text-xs)}.__P__-composer{border-top:1px solid var(--__P__-border);padding:10px 12px;background:var(--__P__-bg);display:flex;flex-direction:column;gap:var(--__P__-space-2)}.__P__-composer-row{display:flex;align-items:flex-end;gap:6px}.__P__-textarea{flex:1;height:40px;max-height:160px;padding:10px 12px;border-radius:var(--__P__-radius);background:var(--__P__-bg-elevated);border:1px solid transparent;font-size:14px;line-height:1.4;transition:height var(--__P__-dur-quick) var(--__P__-ease),border-color var(--__P__-dur-quick) var(--__P__-ease),box-shadow var(--__P__-dur-quick) var(--__P__-ease)}.__P__-textarea:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:1px;border-color:var(--__P__-focus)}.__P__-send{width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;transition:opacity var(--__P__-dur-quick) var(--__P__-ease),transform var(--__P__-dur-quick) var(--__P__-ease),background var(--__P__-dur-quick) var(--__P__-ease)}.__P__-send[data-shape=circle]{border-radius:999px}.__P__-send[data-shape=square]{border-radius:var(--__P__-radius-sm)}.__P__-send[data-shape=pill]{width:auto;padding:0 18px;border-radius:999px}.__P__-send[data-variant=filled]{background:var(--__P__-accent);color:var(--__P__-accent-text)}.__P__-send[data-variant=outline]{background:transparent;color:var(--__P__-accent);border-color:var(--__P__-accent)}.__P__-send[data-variant=outline]:not(:disabled):hover{background:color-mix(in srgb,var(--__P__-accent) 10%,transparent)}.__P__-send[data-variant=ghost]{background:transparent;color:var(--__P__-accent)}.__P__-send[data-variant=ghost]:not(:disabled):hover{background:color-mix(in srgb,var(--__P__-accent) 8%,transparent)}.__P__-send:disabled{opacity:.4;cursor:not-allowed}.__P__-send:not(:disabled):hover{transform:translateY(-1px)}.__P__-send:not(:disabled):active{transform:translateY(0)}.__P__-send:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-send svg{width:18px;height:18px}.__P__-composer-actions{display:flex;gap:var(--__P__-space-1);flex-wrap:wrap}.__P__-attachments{display:flex;flex-wrap:wrap;gap:6px}.__P__-attachment-chip{display:inline-flex;align-items:center;gap:6px;padding:var(--__P__-space-1) var(--__P__-space-2) var(--__P__-space-1) var(--__P__-space-1);border-radius:999px;background:var(--__P__-bg-elevated);border:1px solid var(--__P__-border);font-size:var(--__P__-text-xs);max-width:200px}.__P__-attachment-thumb{width:24px;height:24px;border-radius:999px;object-fit:cover;background:var(--__P__-border)}.__P__-attachment-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.__P__-attachment-remove{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;color:var(--__P__-fg-muted)}.__P__-attachment-remove:hover{background:var(--__P__-border);color:var(--__P__-fg)}.__P__-dropzone{position:absolute;inset:8px;border:2px dashed var(--__P__-accent);border-radius:var(--__P__-radius);background:color-mix(in srgb,var(--__P__-accent) 8%,transparent);display:flex;align-items:center;justify-content:center;font-weight:600;color:var(--__P__-accent);pointer-events:none;z-index:10;animation:__P__-fade-in var(--__P__-dur-quick) var(--__P__-ease)}@keyframes __P__-fade-in{0%{opacity:0}to{opacity:1}}.__P__-error{margin-top:var(--__P__-space-1);padding:8px 10px;border-radius:var(--__P__-radius-sm);background:var(--__P__-danger-bg);color:var(--__P__-danger-text);font-size:var(--__P__-text-sm);display:flex;align-items:center;gap:var(--__P__-space-2)}.__P__-error button{color:inherit;text-decoration:underline;font-size:var(--__P__-text-sm)}.__P__-history{flex:1;overflow-y:auto;padding:var(--__P__-space-2) var(--__P__-space-1) var(--__P__-space-3)}.__P__-history-footer{flex:none;padding:var(--__P__-space-2) var(--__P__-space-3) var(--__P__-space-3);border-top:1px solid var(--__P__-border);background:var(--__P__-surface)}.__P__-history-new{width:100%;display:flex;align-items:center;justify-content:center;gap:var(--__P__-space-2);padding:var(--__P__-space-2) var(--__P__-space-3);border:none;border-radius:var(--__P__-radius-md);background:var(--__P__-accent);color:var(--__P__-on-accent);cursor:pointer;font-size:var(--__P__-text-sm);font-weight:600;transition:filter var(--__P__-dur-quick) var(--__P__-ease-out),transform var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-history-new svg{width:16px;height:16px}.__P__-history-new:hover{filter:brightness(1.08)}.__P__-history-new:active{transform:translateY(1px)}.__P__-history-new:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-history-empty{flex:1;display:flex;align-items:center;justify-content:center;padding:40px 16px;color:var(--__P__-fg-muted);font-size:14px;text-align:center}.__P__-history-group{display:flex;flex-direction:column;padding:0 var(--__P__-space-2)}.__P__-history-heading{font-size:var(--__P__-text-xs);font-weight:600;color:var(--__P__-fg-muted);padding:12px 8px 6px;text-transform:uppercase;letter-spacing:.04em}.__P__-history-item{all:unset;display:flex;flex-direction:column;gap:3px;padding:10px 12px;border-radius:var(--__P__-radius-md);cursor:pointer;transition:background var(--__P__-dur-base) var(--__P__-ease)}.__P__-history-item:hover{background:var(--__P__-bg-elevated)}.__P__-history-item:focus-visible{background:var(--__P__-bg-elevated);outline:2px solid var(--__P__-focus);outline-offset:-2px}.__P__-history-title{font-size:14px;font-weight:500;color:var(--__P__-fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-history-preview{font-size:var(--__P__-text-xs);color:var(--__P__-fg-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-history-item[data-closed=true] .__P__-history-title:after{content:\"\\2022\";margin-left:var(--__P__-space-2);opacity:.5}.__P__-list-loading{margin:auto;padding:var(--__P__-space-6) var(--__P__-space-4);color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm);text-align:center}.__P__-readonly-banner{display:flex;flex-direction:column;align-items:center;gap:10px;padding:14px 12px;margin:0 var(--__P__-space-3) var(--__P__-space-3);border-radius:var(--__P__-radius-md);background:var(--__P__-bg-elevated);color:var(--__P__-fg-muted);text-align:center;font-size:var(--__P__-text-sm)}.__P__-readonly-label{line-height:1.4}.__P__-readonly-cta{appearance:none;border:0;cursor:pointer;padding:var(--__P__-space-2) var(--__P__-space-4);border-radius:999px;background:var(--__P__-accent);color:var(--__P__-accent-text, #fff);font:inherit;font-weight:600;font-size:var(--__P__-text-sm);transition:filter var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-readonly-cta:hover,.__P__-readonly-cta:focus-visible{filter:brightness(1.1)}.__P__-readonly-cta:focus-visible{outline:2px solid var(--__P__-accent);outline-offset:2px}.__P__-composer-footer{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:10px 16px;margin:0 var(--__P__-space-3);border-top:1px solid var(--__P__-border);text-align:center;font-size:11px;line-height:1.4;color:var(--__P__-fg-muted)}.__P__-disclaimer{max-width:320px;margin:0 auto;opacity:.9;letter-spacing:.01em}.__P__-poweredby{display:inline-flex;align-items:center;justify-content:center;gap:6px;color:inherit;text-decoration:none;opacity:.7;transition:opacity var(--__P__-dur-base) var(--__P__-ease);font-size:11px;letter-spacing:.02em}.__P__-poweredby:hover{opacity:1}.__P__-poweredby-logo{height:12px;width:auto;display:inline-block;vertical-align:middle}.__P__-poweredby-bar{flex:none;display:flex;align-items:center;justify-content:center;padding:6px 16px;border-top:1px solid var(--__P__-border);background:var(--__P__-bg-elevated)}.__P__-menu-wrap{position:relative;display:inline-flex}.__P__-menu{position:absolute;top:100%;right:0;margin-top:6px;min-width:200px;padding:6px;background:var(--__P__-bg);border:1px solid var(--__P__-border);border-radius:var(--__P__-radius-md);box-shadow:var(--__P__-shadow-panel);z-index:10;display:flex;flex-direction:column;gap:2px;animation:__P__-menu-in var(--__P__-dur-base) var(--__P__-ease)}@media(prefers-reduced-motion:reduce){.__P__-menu{animation:none}}@keyframes __P__-menu-in{0%{opacity:0;transform:translateY(-4px) scale(.98);transform-origin:top right}to{opacity:1;transform:none}}.__P__-menu-item{all:unset;display:flex;align-items:center;gap:10px;padding:8px 10px;font-size:var(--__P__-text-sm);color:var(--__P__-fg);border-radius:var(--__P__-radius-sm);cursor:pointer;user-select:none}.__P__-menu-item:hover{background:var(--__P__-bg-elevated)}.__P__-menu-item:focus-visible{background:var(--__P__-bg-elevated);outline:2px solid var(--__P__-focus);outline-offset:-2px}.__P__-menu-item[disabled]{opacity:.45;cursor:not-allowed}.__P__-menu-icon{display:inline-flex;width:16px;height:16px;color:var(--__P__-fg-muted)}.__P__-menu-icon svg{width:16px;height:16px}.__P__-menu-label{flex:1}.__P__-menu-check{display:inline-flex;color:var(--__P__-accent)}.__P__-menu-check svg{width:14px;height:14px}.__P__-resize-grip{position:absolute;width:18px;height:18px;display:flex;align-items:center;justify-content:center;color:var(--__P__-fg-muted);opacity:.45;transition:opacity var(--__P__-dur-base) var(--__P__-ease);z-index:2;touch-action:none;user-select:none}.__P__-resize-grip:hover,.__P__-resize-grip:focus-visible{opacity:1}.__P__-resize-grip svg{width:10px;height:10px}.__P__-resize-grip--bottom-left{bottom:2px;left:2px;cursor:nesw-resize;transform:scaleX(-1)}.__P__-resize-grip--bottom-right{bottom:2px;right:2px;cursor:nwse-resize}.__P__-resize-grip--top-left{top:2px;left:2px;cursor:nwse-resize;transform:rotate(180deg)}.__P__-resize-grip--top-right{top:2px;right:2px;cursor:nesw-resize;transform:scaleY(-1)}:host(:not([data-mode=\"open\"])) .__P__-resize-grip{display:none}.__P__-messenger{display:flex;flex-direction:column;overflow:hidden}.__P__-messenger-body{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.__P__-module{display:flex;flex-direction:column;height:100%;min-height:0}.__P__-module-scroll{flex:1;min-height:0;overflow-y:auto;padding:var(--__P__-space-3);display:flex;flex-direction:column;gap:var(--__P__-space-3)}.__P__-home{background:radial-gradient(125% 65% at 88% 0%,color-mix(in srgb,#fff 16%,transparent),transparent 55%),linear-gradient(180deg,var(--__P__-accent) 0%,var(--__P__-accent) 22%,color-mix(in srgb,var(--__P__-accent) 28%,var(--__P__-surface)) 44%,var(--__P__-surface) 70%);border-radius:var(--__P__-radius-lg) var(--__P__-radius-lg) 0 0}.__P__-home-scroll{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;background:transparent}.__P__-home-cards{display:flex;flex-direction:column;gap:var(--__P__-space-3);padding:4px 14px 16px}.__P__-module-pad{padding:var(--__P__-space-3) var(--__P__-space-3) 0}.__P__-module-empty{display:flex;flex-direction:column;align-items:center;gap:var(--__P__-space-3);padding:var(--__P__-space-8) var(--__P__-space-4);text-align:center;color:var(--__P__-fg-muted);font-size:var(--__P__-text-base)}.__P__-module-retry{padding:var(--__P__-space-2) var(--__P__-space-4);border:1px solid var(--__P__-border-strong);border-radius:var(--__P__-radius-sm);color:var(--__P__-fg);font-weight:600;cursor:pointer;transition:background var(--__P__-dur-quick) var(--__P__-ease-out),border-color var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-module-retry:hover{background:var(--__P__-hover);border-color:var(--__P__-accent)}.__P__-module-retry:active{background:var(--__P__-border)}.__P__-module-retry:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-help-list{flex:1;min-height:0;overflow-y:auto;padding-bottom:var(--__P__-space-3)}.__P__-help-group{display:flex;flex-direction:column}.__P__-help-section-title{position:sticky;top:0;z-index:1;margin:0;padding:var(--__P__-space-3) var(--__P__-space-5) var(--__P__-space-2);font-size:var(--__P__-text-xs);font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--__P__-fg-muted);background:var(--__P__-bg)}.__P__-help-card{margin:0 var(--__P__-space-3) var(--__P__-space-3);background:var(--__P__-surface);border:1px solid var(--__P__-border);border-radius:var(--__P__-radius);overflow:hidden}.__P__-help-card .__P__-list-row{border-bottom:0;border-radius:0}.__P__-help-card .__P__-list-row+.__P__-list-row{border-top:1px solid var(--__P__-border)}.__P__-help-card .__P__-list-row:hover{background:var(--__P__-hover)}.__P__-module-cta{padding:var(--__P__-space-3);border-top:1px solid var(--__P__-border)}.__P__-tabbar{display:flex;border-top:1px solid var(--__P__-border);background:var(--__P__-bg-elevated);flex-shrink:0}.__P__-tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--__P__-space-1);min-height:48px;padding:var(--__P__-space-2) var(--__P__-space-1);color:var(--__P__-fg-muted);font-size:var(--__P__-text-xs);font-weight:600;transition:color var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-tab:hover{color:var(--__P__-fg)}.__P__-tab[aria-selected=true]{color:var(--__P__-accent)}.__P__-tab:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:-2px}.__P__-tab:active{background:var(--__P__-hover)}.__P__-tab-icon{position:relative;display:inline-flex}.__P__-tab-icon svg{width:22px;height:22px}.__P__-tab-badge{position:absolute;top:-4px;left:calc(50% + 6px);min-width:16px;height:16px;padding:0 4px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--__P__-accent);color:var(--__P__-on-accent);font-size:10px;font-weight:600;line-height:1}.__P__-home-hero{flex-shrink:0;padding:var(--__P__-space-5) var(--__P__-space-5) var(--__P__-space-3);color:var(--__P__-on-accent);--__P__-focus: var(--__P__-on-accent)}.__P__-home-hero-top{display:flex;align-items:center;justify-content:space-between;gap:var(--__P__-space-3);min-height:32px}.__P__-home-brand{font-size:14px;font-weight:600;letter-spacing:.01em;color:color-mix(in srgb,#fff 82%,transparent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.__P__-home-brand-spacer{flex:1}.__P__-home-hero-actions{display:flex;align-items:center;gap:var(--__P__-space-2);flex-shrink:0}.__P__-home-close{color:#fff}.__P__-home-greeting{margin:28px 0 0;font-family:var(--__P__-font-display);font-size:var(--__P__-text-2xl);font-weight:800;line-height:1.18;letter-spacing:-.01em;overflow-wrap:anywhere}.__P__-home-lead{margin:2px 0 0;font-family:var(--__P__-font-display);font-size:var(--__P__-text-2xl);font-weight:800;line-height:1.18;letter-spacing:-.01em;color:color-mix(in srgb,var(--__P__-on-accent) 88%,transparent);overflow-wrap:anywhere}.__P__-home-avatars{display:flex}.__P__-home-avatar{width:30px;height:30px;border-radius:999px;overflow:hidden;margin-left:-10px;border:2px solid color-mix(in srgb,#fff 92%,transparent);box-shadow:0 1px 3px #0000002e;background:var(--__P__-surface);display:inline-flex;align-items:center;justify-content:center;font-size:var(--__P__-text-xs);font-weight:700;color:var(--__P__-fg)}.__P__-home-avatar:first-child{margin-left:0}.__P__-home-avatar img{width:100%;height:100%;object-fit:cover}.__P__-home-content{background:var(--__P__-surface);border:1px solid var(--__P__-border);border-radius:var(--__P__-radius);box-shadow:var(--__P__-shadow-card);overflow:hidden}.__P__-home-content-title{padding:var(--__P__-space-4) var(--__P__-space-4) var(--__P__-space-3);font-size:var(--__P__-text-sm);font-weight:700}.__P__-home-content-list{display:flex;flex-direction:column;padding:0 var(--__P__-space-2) var(--__P__-space-2)}.__P__-home-content-list .__P__-list-row+.__P__-list-row{border-top:1px solid var(--__P__-border)}.__P__-home-card{display:block;width:100%;text-align:left;background:var(--__P__-surface);border:1px solid var(--__P__-border);border-radius:var(--__P__-radius);padding:var(--__P__-space-4);box-shadow:var(--__P__-shadow-card)}.__P__-home-card[data-interactive=true]{cursor:pointer;transition:background var(--__P__-dur-quick) var(--__P__-ease-out),transform var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-home-card[data-interactive=true]:hover{background:var(--__P__-bg-elevated)}.__P__-home-card[data-interactive=true]:active{transform:translateY(1px)}.__P__-home-card[data-interactive=true]:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-home-recent-row{display:flex;align-items:center;gap:var(--__P__-space-3)}.__P__-home-recent-avatar{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border-radius:999px;background:color-mix(in oklch,var(--__P__-accent) 14%,transparent);color:var(--__P__-accent)}.__P__-home-recent-avatar svg{width:20px;height:20px}.__P__-home-recent-body{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}.__P__-home-recent-title{font-weight:600;font-size:14px}.__P__-home-recent-preview{color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.__P__-home-recent-at{color:var(--__P__-fg-muted);font-size:var(--__P__-text-xs);flex-shrink:0}.__P__-home-recent-dot{width:8px;height:8px;border-radius:999px;background:var(--__P__-accent);flex-shrink:0}.__P__-home-recent-row[data-unread=true] .__P__-home-recent-title{font-weight:700}.__P__-home-status{display:flex;align-items:center;gap:var(--__P__-space-3)}.__P__-home-status-icon svg{width:22px;height:22px;color:var(--__P__-success)}.__P__-home-status[data-level=degraded] .__P__-home-status-icon svg{color:var(--__P__-warning)}.__P__-home-status[data-level=down] .__P__-home-status-icon svg{color:var(--__P__-danger)}.__P__-home-status-text{font-weight:600;font-size:14px}.__P__-home-search{display:flex;align-items:center;gap:var(--__P__-space-2);width:100%;padding:var(--__P__-space-3) var(--__P__-space-4);border-radius:var(--__P__-radius);border:1px solid var(--__P__-border);background:var(--__P__-surface);text-align:left}.__P__-home-search[data-input=true]{background:var(--__P__-bg-elevated)}.__P__-home-search:hover{border-color:var(--__P__-border-strong)}.__P__-home-search:focus-visible,.__P__-home-search:focus-within{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-home-search-text{flex:1;color:var(--__P__-fg-muted);font-size:14px}.__P__-home-search-input{flex:1;border:0;background:transparent;font-size:14px;color:var(--__P__-fg);outline:none}.__P__-home-search-icon svg{width:18px;height:18px;color:var(--__P__-accent)}.__P__-list-row{display:flex;align-items:center;gap:var(--__P__-space-3);width:100%;min-height:44px;text-align:left;padding:var(--__P__-space-3) var(--__P__-space-2);border-bottom:1px solid var(--__P__-border);border-radius:var(--__P__-radius-sm)}.__P__-list-row:hover{background:var(--__P__-bg-elevated)}.__P__-list-row:active{background:var(--__P__-border)}.__P__-list-row:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:-2px}.__P__-list-row-body{display:flex;flex-direction:column;min-width:0;flex:1}.__P__-list-row-title{font-weight:600;font-size:var(--__P__-text-md);line-height:1.35}.__P__-list-row-sub{color:var(--__P__-fg-muted);font-size:var(--__P__-text-sm);margin-top:var(--__P__-space-1);line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.__P__-list-row-chevron svg{width:18px;height:18px;color:var(--__P__-accent);flex-shrink:0}.__P__-back-header{display:flex;align-items:center;gap:var(--__P__-space-2);padding:10px 12px;border-bottom:1px solid var(--__P__-border);flex-shrink:0}.__P__-back-title{flex:1;text-align:center;font-size:var(--__P__-text-md);font-weight:700;margin:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__P__-back-spacer{width:32px;height:1px}.__P__-content{display:flex;flex-direction:column;gap:14px;width:100%;max-width:68ch;margin:0 auto}.__P__-content-hero{width:100%;border-radius:var(--__P__-radius)}.__P__-content-subtitle{color:var(--__P__-fg-muted);margin:0}.__P__-content-frame{flex:1;min-height:0;width:100%;border:0;background:#fff}.__P__-news-list{display:flex;flex-direction:column;gap:var(--__P__-space-3)}.__P__-news-card{display:block;width:100%;text-align:left;border:1px solid var(--__P__-border);border-radius:var(--__P__-radius);overflow:hidden;background:var(--__P__-surface)}.__P__-news-card{transition:background var(--__P__-dur-quick) var(--__P__-ease-out),transform var(--__P__-dur-quick) var(--__P__-ease-out)}.__P__-news-card:hover{background:var(--__P__-bg-elevated)}.__P__-news-card:active{transform:translateY(1px)}.__P__-news-card:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}.__P__-news-hero{width:100%;display:block}.__P__-news-body{display:flex;flex-direction:column;gap:6px;padding:14px 16px}.__P__-news-tags{display:flex;gap:6px;flex-wrap:wrap}.__P__-news-tag{font-size:var(--__P__-text-xs);font-weight:600;color:var(--__P__-accent);background:color-mix(in srgb,var(--__P__-accent) 12%,transparent);padding:2px 8px;border-radius:999px}.__P__-news-title{font-weight:700;font-size:var(--__P__-text-md)}.__P__-news-summary{color:var(--__P__-fg-muted);font-size:14px;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.__P__-modules-empty{display:flex;align-items:center;justify-content:center;position:relative}.__P__-modules-empty-close{position:absolute;top:12px;right:12px}.__P__-modules-empty-text{color:var(--__P__-fg-muted);font-size:var(--__P__-text-base)}\n", ".__P__-standalone-page{margin:0;height:100vh;background:var(--__P__-bg, #fff);display:grid;place-items:stretch}\n", ":host([data-mode=\"page\"]){min-height:0}:host([data-mode=\"page\"]) .__P__-page-shell{display:grid;grid-template-columns:280px 1fr;width:100%;height:100%;min-height:0;background:var(--__P__-bg);color:var(--__P__-fg);transition:grid-template-columns var(--__P__-dur-base) var(--__P__-ease-in-out)}:host([data-mode=\"page\"]) .__P__-page-shell[data-sidebar-collapsed=true]{grid-template-columns:48px 1fr}:host([data-mode=\"page\"]) .__P__-page-chat{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}:host([data-mode=\"page\"]) .__P__-anchor{position:static;inset:auto;padding:0;align-items:stretch;width:100%;height:100%;min-width:0;min-height:0;flex:1 1 0}:host([data-mode=\"page\"]) .__P__-panel{width:100%;height:100%;min-width:0;min-height:0;max-width:none;max-height:none;border-radius:0;border:0;box-shadow:none;animation:none}:host([data-mode=\"page\"]) .__P__-sidebar{border-right:1px solid var(--__P__-border);display:flex;flex-direction:column;overflow:hidden;background:var(--__P__-surface)}:host([data-mode=\"page\"]) .__P__-sidebar-header{padding:var(--__P__-space-4) var(--__P__-space-5);border-bottom:1px solid var(--__P__-border);display:flex;align-items:center;justify-content:space-between;gap:var(--__P__-space-2);min-height:56px;box-sizing:border-box}:host([data-mode=\"page\"]) .__P__-sidebar[data-collapsed=true] .__P__-sidebar-header{padding:var(--__P__-space-4) var(--__P__-space-2);justify-content:center}:host([data-mode=\"page\"]) .__P__-sidebar[data-collapsed=true] .__P__-sidebar-logo,:host([data-mode=\"page\"]) .__P__-sidebar[data-collapsed=true] .__P__-sidebar-title{display:none}:host([data-mode=\"page\"]) .__P__-sidebar-toggle{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid var(--__P__-border);border-radius:6px;background:transparent;color:var(--__P__-fg);cursor:pointer;transition:background .12s,border-color .12s}:host([data-mode=\"page\"]) .__P__-sidebar-toggle:hover{background:var(--__P__-hover);border-color:var(--__P__-accent)}:host([data-mode=\"page\"]) .__P__-sidebar-toggle:focus-visible{outline:2px solid var(--__P__-accent);outline-offset:2px}:host([data-mode=\"page\"]) .__P__-sidebar-logo{height:32px;display:block}:host([data-mode=\"page\"]) .__P__-sidebar-title{font-size:var(--__P__-text-md);font-weight:600}:host([data-mode=\"page\"]) .__P__-sidebar-section{padding:var(--__P__-space-3) var(--__P__-space-4);border-bottom:1px solid var(--__P__-border)}:host([data-mode=\"page\"]) .__P__-sidebar-nav{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}:host([data-mode=\"page\"]) .__P__-sidebar-nav-item{display:flex;align-items:center;gap:10px;padding:var(--__P__-space-2) var(--__P__-space-3);border-radius:var(--__P__-radius-sm);color:var(--__P__-fg);text-decoration:none;font-size:14px;transition:background var(--__P__-dur-quick) var(--__P__-ease-out)}:host([data-mode=\"page\"]) .__P__-sidebar-nav-item:hover{background:var(--__P__-hover)}:host([data-mode=\"page\"]) .__P__-sidebar-nav-item:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:-2px}:host([data-mode=\"page\"]) .__P__-sidebar-cards{display:grid;grid-template-columns:1fr;gap:var(--__P__-space-2)}:host([data-mode=\"page\"]) .__P__-sidebar-card{display:block;padding:10px 12px;border:1px solid var(--__P__-border);border-radius:var(--__P__-radius-sm);text-decoration:none;color:var(--__P__-fg);transition:border-color var(--__P__-dur-quick) var(--__P__-ease-out)}:host([data-mode=\"page\"]) .__P__-sidebar-card:hover{border-color:var(--__P__-accent)}:host([data-mode=\"page\"]) .__P__-sidebar-card:focus-visible{outline:2px solid var(--__P__-focus);outline-offset:2px}:host([data-mode=\"page\"]) .__P__-sidebar-card-label{font-weight:600;font-size:14px}:host([data-mode=\"page\"]) .__P__-sidebar-card-desc{font-size:var(--__P__-text-xs);color:var(--__P__-muted);margin-top:2px}:host([data-mode=\"page\"]) .__P__-sidebar-conversations{flex:1 1 auto;overflow-y:auto;padding:var(--__P__-space-3) var(--__P__-space-4)}:host([data-mode=\"page\"]) .__P__-page-chat{display:flex;flex-direction:column;overflow:hidden}@media(max-width:768px){:host([data-mode=\"page\"]) .__P__-page-shell{grid-template-columns:1fr}:host([data-mode=\"page\"]) .__P__-sidebar{display:none}}\n", "/**\n * Concatenate every `.css` file in this folder into a single string that gets\n * adopted by the shadow root at mount time.\n *\n * Import convention: `./name.css?raw`.\n * - Vite serves `?raw` natively (default string export).\n * - esbuild (prod) strips the `?raw` suffix and loads the file with the\n * `text` loader (see `scripts/build-brand.mjs`).\n * One import style works for both pipelines.\n */\n\nimport tokens from \"./tokens.css?raw\";\nimport reset from \"./reset.css?raw\";\nimport panel from \"./panel.css?raw\";\nimport standalone from \"./standalone.css?raw\";\nimport page from \"./page.css?raw\";\n\n/**\n * Brand constants substituted at build time from `brands/<id>.json`\n * (`__CSS_PREFIX__` + `defaultTheme.*`). `brands/*.json` is the single source\n * of truth for the brand prefix and default theme; these placeholders are\n * injected into the CSS text below. Fallbacks mirror the shipped `help-ai`\n * brand for tooling contexts where defines aren't present.\n */\ndeclare const __CSS_PREFIX__: string;\ndeclare const __BRAND_ACCENT__: string;\ndeclare const __BRAND_ACCENT_TEXT__: string;\ndeclare const __BRAND_RADIUS__: string;\ndeclare const __BRAND_FONT__: string;\nconst PREFIX = typeof __CSS_PREFIX__ !== \"undefined\" ? __CSS_PREFIX__ : \"helpai\";\nconst ACCENT = typeof __BRAND_ACCENT__ !== \"undefined\" ? __BRAND_ACCENT__ : \"#5B5BD6\";\nconst ACCENT_TEXT = typeof __BRAND_ACCENT_TEXT__ !== \"undefined\" ? __BRAND_ACCENT_TEXT__ : \"#FFFFFF\";\nconst RADIUS = typeof __BRAND_RADIUS__ !== \"undefined\" ? __BRAND_RADIUS__ : \"12px\";\nconst FONT =\n typeof __BRAND_FONT__ !== \"undefined\" ? __BRAND_FONT__ : \"Inter, system-ui, -apple-system, 'Segoe UI', sans-serif\";\n\n/**\n * Full shadow-root stylesheet: every `__P__` swapped for the brand prefix and\n * each `__BRAND_*__` theme placeholder swapped for the brand JSON value.\n */\nexport const CSS_TEXT = [tokens, reset, panel, standalone, page]\n .join(\"\\n\")\n .replace(/__P__/g, PREFIX)\n .replaceAll(\"__BRAND_ACCENT_TEXT__\", ACCENT_TEXT)\n .replaceAll(\"__BRAND_ACCENT__\", ACCENT)\n .replaceAll(\"__BRAND_RADIUS__\", RADIUS)\n .replaceAll(\"__BRAND_FONT__\", FONT);\n", "// Create the shadow host, attach an open shadow root, and adopt our single\n// Constructable stylesheet. CSS_TEXT is injected by the build pipeline as a\n// String export from src/styles/index.ts (esbuild .css text loader).\n\nimport { BRAND } from \"@/core/brand\";\nimport { logger } from \"@/core/logger\";\nimport { CSS_TEXT } from \"@/styles\";\n\nconst log = logger.scope(\"mount\");\n\n/**\n * One constructed stylesheet per document, cached. Two widgets in the\n * *same* document share a single sheet (cheap). Two widgets in *different*\n * documents \u2014 i.e., iframe mode inside a page that also has a top-level\n * widget \u2014 each get their own, because a `CSSStyleSheet` constructed in\n * one document can't be `adoptedStyleSheets`-attached in another\n * (browsers throw `NotAllowedError`).\n */\nconst sheetsByDocument = new WeakMap<Document, CSSStyleSheet>();\n\nexport interface ShadowMountResult {\n hostElement: HTMLElement;\n shadowRoot: ShadowRoot;\n /** Mount point for the Preact tree. */\n appRoot: HTMLDivElement;\n destroy: () => void;\n}\n\nexport interface MountOptions {\n /** Where to insert the host element. Defaults to `document.body`. */\n target?: HTMLElement | null;\n /**\n * Inline-style variant for the host element. See `HOST_CSS` below\n * for the three options. Defaults to `\"flow\"`.\n * \u2022 `\"floating\"` \u2014 overlay (floating FAB, modal, drawer)\n * \u2022 `\"flow\"` \u2014 fills parent (inline mode)\n * \u2022 `\"viewport\"` \u2014 claims viewport (standalone / page modes)\n */\n position?: HostPosition;\n /** When provided, attach to the existing element instead of creating a new host. */\n existingHost?: HTMLElement;\n}\n\n/** Per-mode host-element styles. Kept as inline CSS so host-page rules can't override.\n *\n * Three variants:\n *\n * - **`floating`** \u2014 `position: fixed` overlay. Floating FAB,\n * modal, drawer. Per-mode CSS in `panel.css` positions the anchor +\n * panel inside the fixed host (e.g. bottom-right for the FAB).\n *\n * - **`flow`** \u2014 `position: relative; width/height: 100%` so the host\n * fills its parent box. Used by `inline` mode. Adds `contain: size`\n * so shadow-DOM intrinsic sizes (agent badge, suggestions, textarea)\n * don't bubble out into the embedding page's grid/flex tracks.\n *\n * - **`viewport`** \u2014 `position: absolute; inset: 0; width: 100%;\n * height: 100%`. Used by `standalone` and `page` modes. The widget\n * fills its nearest positioned ancestor, falling back to the\n * initial containing block (the viewport) when none exists.\n * Defensive against host pages that drop `<web-ai-chat>` into a\n * small or flow-only container \u2014 when the host page doesn't\n * position anything, the widget claims the viewport. Host pages\n * that DO want to bound the widget (e.g. a preview frame in a\n * builder UI) wrap it in `position: relative` and the absolute\n * positioning resolves against that ancestor instead. Lives in the\n * INLINE style (not shadow CSS) because the `all: initial` reset\n * would otherwise clobber any external `:host` rule attempting to\n * set width/height/inset.\n *\n * Why every variant starts with `all: initial`: the host element\n * inherits the embedding page's font / color / line-height / etc.\n * Resetting at the host boundary keeps the shadow stylesheet\n * deterministic across host pages. The cascade caveat above (no\n * external `:host` rule for size-related properties) is the cost.\n */\nconst HOST_CSS = {\n floating: \"all: initial; position: fixed; z-index: 2147483600;\",\n flow: \"all: initial; display: block; position: relative; width: 100%; height: 100%; z-index: 1; contain: size;\",\n viewport: \"all: initial; display: block; position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1;\",\n} as const;\n\nexport type HostPosition = keyof typeof HOST_CSS;\n\n/**\n * Re-apply the host element's positioning style.\n *\n * `createShadowMount` sets this once on first mount; this helper lets\n * the entry points (`index.ts`, `web-component.ts`) refresh it after\n * a server-pushed mode change merges new options. Without it, the\n * mount-time choice (typically `\"floating\"` from the resolver default)\n * sticks even when the server later pushes `mode: \"standalone\"`.\n *\n * Idempotent \u2014 writing the same string twice is a no-op.\n */\nexport function applyHostPosition(host: HTMLElement, position: HostPosition): void {\n host.style.cssText = HOST_CSS[position];\n}\n\n/**\n * Map a {@link Mode} to its host-positioning strategy. Centralised so\n * the `mount()` and web-component entry points pick the same variant.\n */\nexport function hostPositionForMode(mode: import(\"@/core/config\").Mode): HostPosition {\n if (mode === \"floating\" || mode === \"modal\" || mode === \"drawer\") return \"floating\";\n if (mode === \"standalone\" || mode === \"page\") return \"viewport\";\n return \"flow\";\n}\n\nexport function createShadowMount(opts: MountOptions = {}): ShadowMountResult {\n const position: HostPosition = opts.position ?? \"flow\";\n log.debug(\"createShadowMount\", { position, hasTarget: !!opts.target, hasHost: !!opts.existingHost });\n const tagName = `${BRAND.cssPrefix}-chat-host`;\n // Define a no-op custom element so the tag participates in the CE registry,\n // Letting frameworks treat it like any HTML element. Idempotent.\n if (typeof customElements !== \"undefined\" && !customElements.get(tagName)) {\n customElements.define(tagName, class extends HTMLElement {});\n }\n\n let host: HTMLElement;\n if (opts.existingHost) {\n host = opts.existingHost;\n } else {\n host = document.createElement(tagName) as HTMLElement;\n (opts.target ?? document.body).appendChild(host);\n }\n host.setAttribute(\"data-brand\", BRAND.id);\n host.style.cssText = HOST_CSS[position];\n\n // Use existing shadow root if already attached (re-init scenario).\n const shadow = host.shadowRoot ?? host.attachShadow({ mode: \"open\" });\n\n // Style the shadow root. Use `adoptedStyleSheets` when available (cheap,\n // shared across instances in the same document). Construct the sheet\n // using the *host's* own document \u2014 in iframe mode that's the iframe's\n // document, not the top-level page's. Cross-document adoption throws\n // `NotAllowedError`, which is why this must be per-document rather than\n // a single module-scope singleton.\n const hostDoc = host.ownerDocument;\n if (!attachAdoptedSheet(shadow, hostDoc)) {\n // Either the browser lacks constructable stylesheets or the adoption\n // itself failed \u2014 fall back to an inline `<style>` inside the shadow.\n const styleEl = hostDoc.createElement(\"style\");\n styleEl.textContent = CSS_TEXT;\n shadow.appendChild(styleEl);\n }\n\n const appRoot = document.createElement(\"div\");\n appRoot.className = `${BRAND.cssPrefix}-app`;\n shadow.appendChild(appRoot);\n\n return {\n appRoot,\n destroy: () => host.remove(),\n hostElement: host,\n shadowRoot: shadow,\n };\n}\n\n/**\n * Attach a constructed, cached stylesheet to a shadow root. Returns `true`\n * when adopted successfully; `false` when the runtime lacks constructable\n * stylesheets or the adoption threw \u2014 caller should fall back to `<style>`.\n *\n * Per-document caching guarantees the sheet and the shadow root share a\n * realm (iframe mode requires this \u2014 sheets from one document can't be\n * adopted by shadow roots in another).\n */\nfunction attachAdoptedSheet(shadow: ShadowRoot, doc: Document): boolean {\n if (!(\"adoptedStyleSheets\" in shadow)) return false;\n const win = doc.defaultView;\n if (!win || typeof win.CSSStyleSheet !== \"function\") return false;\n\n let sheet = sheetsByDocument.get(doc);\n if (!sheet) {\n try {\n sheet = new win.CSSStyleSheet();\n sheet.replaceSync(CSS_TEXT);\n sheetsByDocument.set(doc, sheet);\n } catch {\n return false;\n }\n }\n try {\n shadow.adoptedStyleSheets = [sheet];\n return true;\n } catch {\n return false;\n }\n}\n\n/** Apply theme overrides as inline CSS custom properties on the host. */\nfunction applyThemeOverrides(\n host: HTMLElement,\n overrides: { accent?: string; accentText?: string; radius?: string; fontFamily?: string },\n): void {\n const p = BRAND.cssPrefix;\n if (overrides.accent) host.style.setProperty(`--${p}-accent`, overrides.accent);\n if (overrides.accentText) host.style.setProperty(`--${p}-accent-text`, overrides.accentText);\n if (overrides.radius) host.style.setProperty(`--${p}-radius`, overrides.radius);\n if (overrides.fontFamily) host.style.setProperty(`--${p}-font`, overrides.fontFamily);\n}\n\n/** Set color scheme on the host. 'auto' lets prefers-color-scheme decide via CSS. */\nfunction applyThemeMode(host: HTMLElement, mode: \"auto\" | \"light\" | \"dark\"): void {\n host.dataset.theme = mode;\n}\n\n/**\n * Apply sizing overrides as CSS custom properties on the host.\n *\n * The properties cascade into the shadow root where the panel reads them\n * via `var(--__P__-panel-w)` etc. This lets inline + standalone modes\n * ignore them entirely (their higher-specificity CSS rules set `width:\n * 100%` / `height: 100%` outright) while floating + iframe modes honour\n * them. Previously these were written as *inline styles* on the host,\n * which clobbered the `width: 100%` rule in inline mode \u2014 sizing the\n * widget from config would shrink it below its container.\n *\n * Passing `undefined` for a field leaves any existing custom property\n * alone \u2014 useful for `update()` calls that only tweak one dimension.\n */\nfunction applySize(\n host: HTMLElement,\n size: {\n height?: string;\n width?: string;\n expanded?: { width?: string; height?: string };\n /** Drawer-mode edge inset (gap from viewport edges). */\n inset?: string;\n },\n): void {\n const p = BRAND.cssPrefix;\n // Normal-mode default panel dimensions \u2014 consumed by `.__P__-panel`'s\n // base `width/height: var(--__P__-panel-w/h)` rule in floating mode.\n if (size.width !== undefined) host.style.setProperty(`--${p}-panel-w`, size.width);\n if (size.height !== undefined) host.style.setProperty(`--${p}-panel-h`, size.height);\n // Expanded-mode dimensions \u2014 consumed by the `[data-mode=\"expanded\"]` rule.\n if (size.expanded?.width !== undefined) host.style.setProperty(`--${p}-expanded-w`, size.expanded.width);\n if (size.expanded?.height !== undefined) host.style.setProperty(`--${p}-expanded-h`, size.expanded.height);\n // Drawer-mode edge inset \u2014 turns the full-height dock into a flyout\n // with the given gap from each viewport edge.\n if (size.inset !== undefined) host.style.setProperty(`--${p}-panel-inset`, size.inset);\n}\n\n/** Set position class for the launcher / panel anchor. */\nfunction applyPosition(host: HTMLElement, pos: string): void {\n host.dataset.position = pos;\n}\n\n/** Set the panel display mode (collapsed / expanded / fullscreen / standalone). */\nexport function applyMode(host: HTMLElement, mode: string): void {\n host.dataset.mode = mode;\n}\n\n/**\n * Apply every host-level data attribute + inline CSS var derived from the\n * resolved options in one call. Callers (`index.ts`, `web-component.ts`)\n * used to repeat these five lines; keep the fan-out here.\n */\nexport function applyHostAttributes(\n host: HTMLElement,\n resolved: {\n themeMode: \"auto\" | \"light\" | \"dark\";\n themeOverrides: { accent?: string; accentText?: string; radius?: string; fontFamily?: string };\n position: string;\n size: {\n width?: string;\n height?: string;\n expanded?: { width?: string; height?: string };\n inset?: string;\n };\n },\n): void {\n applyThemeMode(host, resolved.themeMode);\n applyThemeOverrides(host, resolved.themeOverrides);\n applyPosition(host, resolved.position);\n applySize(host, resolved.size);\n // NOTE: `data-mode` is deliberately NOT set here. The App component owns\n // it via `computeHostMode(mode, panelSize, isOpen)` in its mount-time\n // effect \u2014 setting the raw mode string here would transiently land\n // something like `data-mode=\"iframe\"` (which `SIZE_BY_MODE` in\n // `iframe-mount` doesn't recognise, falling back to \"open\" dimensions)\n // and cause a visible flicker until App's effect corrected it. Callers\n // that need an initial value before App mounts (see `index.ts`) call\n // `applyMode` explicitly with the pre-computed host mode.\n}\n", "/**\n * Derives the `data-mode` attribute value from App state.\n *\n * Kept as a pure helper (no DOM, no React) so tests can pin the critical\n * invariants cheaply:\n *\n * - `isOpen === false` \u2192 `\"closed\"` regardless of `panelSize`.\n * This is the reason closing from fullscreen doesn't hide the launcher:\n * the `[data-mode=\"fullscreen\"] .__P__-fab { display: none }` rule never\n * matches when the widget is closed.\n * - `panelSize` is preserved across close \u2192 open, so reopening the widget\n * restores the user's last layout (normal / expanded / fullscreen).\n * - Inline + standalone are terminal modes: they override any panelSize\n * because the widget isn't a popover and can't be \"closed\".\n */\n\nimport type { Mode, SizeOptions } from \"@/core/config\";\n\nexport type PanelSize = \"normal\" | \"expanded\" | \"fullscreen\";\n\nexport type HostMode =\n | \"page\"\n | \"standalone\"\n | \"inline\"\n | \"modal\"\n | \"drawer\"\n | \"fullscreen\"\n | \"expanded\"\n | \"open\"\n | \"closed\";\n\nexport function computeHostMode(configured: Mode, panelSize: PanelSize, isOpen: boolean): HostMode {\n if (configured === \"page\") return \"page\";\n if (configured === \"standalone\") return \"standalone\";\n // `inline` normally sits in-flow inside its container, but the user\n // can still escape to full-viewport via the fullscreen header button.\n // `expanded` has no meaningful mid-tier in inline (the panel already\n // fills its container) \u2014 the state is retained but produces no visual\n // change until the user hits fullscreen or the widget is re-mounted.\n if (configured === \"inline\") return panelSize === \"fullscreen\" ? \"fullscreen\" : \"inline\";\n // Modal + drawer share the same closed / fullscreen / mode-name fallback.\n if (configured === \"modal\" || configured === \"drawer\") {\n if (!isOpen) return \"closed\";\n return panelSize === \"fullscreen\" ? \"fullscreen\" : configured;\n }\n if (!isOpen) return \"closed\";\n if (panelSize === \"fullscreen\") return \"fullscreen\";\n if (panelSize === \"expanded\") return \"expanded\";\n return \"open\";\n}\n\n/** Default breakpoint (px) for `initialSize: \"auto\"` promotion to `expanded`. */\nconst DEFAULT_AUTO_BREAKPOINT = 900;\n\n/**\n * Resolve the panel size for the very first render.\n *\n * Precedence (highest to lowest):\n * 1. `mode: \"standalone\"` \u2192 always `\"fullscreen\"` (the dedicated tab).\n * 2. `size.initialSize` literal (\"normal\" / \"expanded\" / \"fullscreen\").\n * 3. `size.initialSize: \"auto\"` \u2192 `\"expanded\"` when viewport width \u2265 the\n * configured breakpoint (default 900 px), else `\"normal\"`.\n * 4. Fallback \u2192 `\"normal\"`.\n *\n * Pure: pass `viewportWidth` in so tests don't need `window`.\n */\nexport function pickInitialPanelSize(mode: Mode, size: SizeOptions, viewportWidth: number): PanelSize {\n if (mode === \"standalone\" || mode === \"page\") return \"fullscreen\";\n const chosen = size.initialSize;\n if (chosen === \"expanded\" || chosen === \"fullscreen\" || chosen === \"normal\") return chosen;\n if (chosen === \"auto\") {\n const bp = size.autoSizeBreakpoint ?? DEFAULT_AUTO_BREAKPOINT;\n return viewportWidth >= bp ? \"expanded\" : \"normal\";\n }\n return \"normal\";\n}\n\n/**\n * The two pieces of first-render panel state the App + the mount-time\n * host-mode resolver both need:\n *\n * - `panelOpen` \u2014 whether the floating panel starts expanded. For inline /\n * standalone this is always `true` (the panel is part of the page flow\n * or owns the whole viewport). For floating / iframe, the user's\n * persisted pick (last open/close) wins over the page-owner's\n * `startMinimized` default, so closing the panel and refreshing doesn't\n * auto-reopen it.\n * - `panelSize` \u2014 the `normal`/`expanded`/`fullscreen` tier at first\n * render (see `pickInitialPanelSize`).\n *\n * Factored out of App + `resolveInitialHostMode` in `src/index.ts` to\n * prevent drift: these two callers MUST pick the same first frame or the\n * iframe physically resizes between mount and App's first effect (the\n * \"closed \u2192 open \u2192 closed\" flicker this pins against).\n */\nexport interface InitialPanelState {\n panelOpen: boolean;\n panelSize: PanelSize;\n}\n\nexport function resolveInitialPanelState(\n options: { mode: Mode; size: SizeOptions; startMinimized: boolean },\n viewportWidth: number,\n persistedPanelOpen: boolean | null,\n persistedPanelSize: PanelSize | null = null,\n): InitialPanelState {\n const isInlineLike = options.mode === \"inline\" || options.mode === \"standalone\" || options.mode === \"page\";\n const panelOpen = isInlineLike || (persistedPanelOpen ?? !options.startMinimized);\n // The user's last size (expand / fullscreen) wins over the page-owner's\n // `initialSize`, per-device \u2014 so making the panel bigger and refreshing keeps\n // it bigger. Skip the always-fullscreen / container-bound modes, where size\n // isn't a free user choice.\n const honorsPersisted = options.mode !== \"standalone\" && options.mode !== \"page\" && options.mode !== \"inline\";\n const panelSize =\n honorsPersisted && persistedPanelSize\n ? persistedPanelSize\n : pickInitialPanelSize(options.mode, options.size, viewportWidth);\n return { panelOpen, panelSize };\n}\n\n/**\n * Read the current viewport width, gracefully falling back to `0` outside\n * a browser (SSR, unit tests with no global `window`). Four callers used\n * to inline this \u2014 kept here so the `typeof window` guard lives in one\n * place and `pickInitialPanelSize` / `resolveInitialPanelState` stay\n * testable with arbitrary widths.\n */\nexport function currentViewportWidth(): number {\n return typeof window === \"undefined\" ? 0 : window.innerWidth;\n}\n", "/**\n * Auth header builder. Priority mirrors the backend (`module-help-ai-agent-api`):\n * public key (header) > guest (no header at all).\n *\n * The widget runs on third-party sites and does **not** authenticate end users\n * \u2014 there's no per-user token. Requests are authorised by the workspace\n * `publicKey`; the guest fallback is deliberate, since the backend supports\n * unauthenticated sessions for public-facing widgets (rate-limited per IP).\n */\n\nexport interface AuthOptions {\n /** Workspace-scoped public key. Sent as the public-key header when present. */\n publicKey?: string;\n /**\n * Deployment ID (UUID) of the AI agent deployment. When set, sent as\n * `x-ai-agent-deployment-id` header on every request so the backend can\n * disambiguate when one publicKey serves multiple deployments.\n */\n aiAgentDeploymentId?: string;\n /** Override the public-key header name (default: `x-public-key`). */\n publicKeyHeader?: string;\n}\n\n/**\n * Returned by {@link createAuth}. Keep an instance per widget session.\n */\nexport interface AuthHeaderProvider {\n /** Build a fresh `Record<string, string>` of headers for this request. */\n headers: () => Record<string, string>;\n}\n\nconst DEFAULT_PUBLIC_KEY_HEADER = \"x-public-key\";\nconst DEPLOYMENT_ID_HEADER = \"x-ai-agent-deployment-id\";\n\n/**\n * Build an {@link AuthHeaderProvider} from the widget's `AuthOptions`.\n *\n * @example public key\n * ```ts\n * createAuth({ publicKey: 'agent_pk_...' }).headers();\n * // { 'x-public-key': 'agent_pk_...' }\n * ```\n *\n * @example guest (no public key)\n * ```ts\n * createAuth({}).headers();\n * // {}\n * ```\n */\nexport function createAuth(opts: AuthOptions): AuthHeaderProvider {\n return {\n headers() {\n const headers: Record<string, string> = {};\n if (opts.aiAgentDeploymentId) headers[DEPLOYMENT_ID_HEADER] = opts.aiAgentDeploymentId;\n if (opts.publicKey) headers[opts.publicKeyHeader ?? DEFAULT_PUBLIC_KEY_HEADER] = opts.publicKey;\n return headers;\n },\n };\n}\n", "/**\n * UUIDv7 \u2014 RFC 9562 time-ordered identifier.\n *\n * Layout (128 bits / 16 bytes):\n * ```\n * bytes 0..5 \u2192 48-bit Unix-ms timestamp (big-endian)\n * byte 6 \u2192 high nibble = version (0x70), low nibble = rand_a high\n * byte 7 \u2192 rand_a low\n * byte 8 \u2192 high 2 bits = variant (0b10), low 6 bits = rand_b high\n * bytes 9..15 \u2192 rand_b low (56 bits)\n * ```\n *\n * Why v7 over v4: ids minted around the same moment sort lexicographically\n * by creation time, which makes range scans / index locality much better\n * for any backend store keyed by id (chat ids in our `dev-store.json`,\n * message ids on the wire, etc.).\n *\n * Falls back to `Math.random()` for runtimes without `crypto.getRandomValues`\n * \u2014 extremely rare in practice (every modern browser + Bun + Node).\n */\nexport function uuid7(): string {\n const ts = Date.now();\n const bytes = new Uint8Array(16);\n // High 48 bits \u2014 timestamp, big-endian. JS bitwise ops are 32-bit so we\n // split the high half via division.\n bytes[0] = Math.floor(ts / 2 ** 40) & 0xff;\n bytes[1] = Math.floor(ts / 2 ** 32) & 0xff;\n bytes[2] = (ts >>> 24) & 0xff;\n bytes[3] = (ts >>> 16) & 0xff;\n bytes[4] = (ts >>> 8) & 0xff;\n bytes[5] = ts & 0xff;\n // Bottom 80 bits \u2014 random.\n fillRandom(bytes.subarray(6));\n // Version 7 in high nibble of byte 6 (overwrite the random high nibble).\n bytes[6] = (bytes[6]! & 0x0f) | 0x70;\n // Variant 0b10 in high 2 bits of byte 8.\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Generate a short prefixed id, backed by {@link uuid7}. Drops the dashes\n * and truncates to 16 hex chars for the prefix-friendly form (`msg_\u2026`,\n * `visitor_\u2026`). Sort order within a prefix still tracks creation time\n * because the leading 12 hex digits are the timestamp.\n *\n * Used for client-minted chat / message / visitor / file ids \u2014 entities\n * that need to be unique-enough but not security tokens.\n */\nexport function cryptoId(prefix = \"id\"): string {\n return `${prefix}_${uuid7().replaceAll(\"-\", \"\").slice(0, 16)}`;\n}\n\n/**\n * Fill a Uint8Array with random bytes. Uses `crypto.getRandomValues` when\n * available (all modern runtimes); falls back to `Math.random()` only for\n * legacy environments where the Web Crypto API is missing.\n */\nfunction fillRandom(view: Uint8Array): void {\n if (typeof crypto !== \"undefined\" && typeof crypto.getRandomValues === \"function\") {\n // Cast: TS narrows getRandomValues to require an ArrayBuffer-backed view,\n // but Uint8Array<ArrayBufferLike> (the broader runtime type) is fine.\n crypto.getRandomValues(view as Uint8Array<ArrayBuffer>);\n return;\n }\n for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);\n}\n", "// Mirror of Vercel AI SDK v6 UIMessageChunk discriminated union.\n// We do NOT import `ai` \u2014 parsing the wire format ourselves keeps the bundle tiny.\n// Keep this file in lock-step with backend contract (module-help-ai-agent-api).\n\nexport type UIChunk =\n | { type: \"start\"; messageId?: string }\n | { type: \"start-step\" }\n | { type: \"finish-step\" }\n | {\n type: \"finish\";\n finishReason?: \"stop\" | \"length\" | \"tool-calls\" | \"content-filter\" | \"error\" | string;\n /**\n * Backend signal that the conversation is closed and cannot accept\n * further user messages (e.g. workspace hit its message cap, support\n * ticket was auto-closed). Absent or `true` \u2192 composer stays enabled;\n * explicit `false` \u2192 client flips into read-only mode.\n */\n canContinue?: boolean;\n }\n | { type: \"text-start\"; id: string }\n | { type: \"text-delta\"; id: string; delta: string }\n | { type: \"text-end\"; id: string }\n | { type: \"reasoning-start\"; id: string }\n | { type: \"reasoning-delta\"; id: string; delta: string }\n | { type: \"reasoning-end\"; id: string }\n | { type: \"tool-input-start\"; toolCallId: string; toolName: string }\n | { type: \"tool-input-delta\"; toolCallId: string; delta: string }\n | { type: \"tool-input-available\"; toolCallId: string; toolName: string; input: unknown }\n | { type: \"tool-output-available\"; toolCallId: string; output: unknown }\n | { type: \"tool-output-error\"; toolCallId: string; errorText: string }\n | { type: \"file\"; url: string; mediaType: string }\n | { type: \"source-url\"; sourceId: string; url: string; title?: string }\n | {\n type: \"data-notification\";\n data: { message: string; level?: \"info\" | \"warn\" | \"error\" };\n }\n | { type: \"error\"; errorText: string };\n\n// A Part is the UI-level projection of one or more chunks.\nexport type MessagePart =\n | { kind: \"text\"; id: string; text: string; done: boolean }\n | { kind: \"reasoning\"; id: string; text: string; done: boolean }\n | {\n kind: \"tool\";\n toolCallId: string;\n toolName: string;\n inputPartial: string;\n input?: unknown;\n output?: unknown;\n error?: string;\n state: \"input\" | \"awaiting\" | \"output\" | \"error\";\n }\n | { kind: \"file\"; url: string; mediaType: string }\n | { kind: \"source\"; sourceId: string; url: string; title?: string };\n\ninterface AttachmentRef {\n id: string;\n name: string;\n mediaType: string;\n size: number;\n url?: string;\n}\n\nexport interface Message {\n id: string;\n role: \"user\" | \"assistant\" | \"system\";\n createdAt: number;\n parts: MessagePart[];\n attachments?: AttachmentRef[];\n status: \"pending\" | \"streaming\" | \"complete\" | \"error\";\n errorText?: string;\n /** Backend's reason for ending the stream (`'stop'` | `'length'` | \u2026). */\n finishReason?: string;\n}\n\nexport class StreamError extends Error {\n constructor(\n message: string,\n public code: \"no-body\" | \"server\" | \"network\",\n /** HTTP status when the failure came from a response (e.g. 429, 404). */\n public status?: number,\n public override cause?: unknown,\n ) {\n super(message);\n this.name = \"StreamError\";\n }\n}\n", "/**\n * SSE \u2192 typed `UIChunk` parser.\n *\n * The backend emits Vercel AI SDK v6 `UIMessageChunk` frames over\n * `text/event-stream` (see `module-help-ai-agent-api`). Each frame is one event:\n *\n * ```\n * id: 42\\n\n * data: {\"type\":\"text-delta\",\"id\":\"t0\",\"delta\":\"hi\"}\\n\n * \\n\n * ```\n *\n * The only SSE bits we support:\n * - `data:` line \u2014 the JSON payload\n * - `id:` line \u2014 optional per-event id used for resume dedupe\n * - `:` lines \u2014 comments (keep-alives)\n *\n * `event:` and `retry:` are ignored \u2014 the backend does not use them.\n *\n * Corrupt frames (bad JSON) are logged and dropped; the stream continues.\n * Aborts propagate via the provided {@link AbortSignal}.\n */\n\nimport { logger } from \"@/core/logger\";\nimport { StreamError, type UIChunk } from \"./types\";\n\nconst log = logger.scope(\"parser\");\n\n/**\n * Consume `response.body` as SSE and yield one `{chunk, eventId}` tuple per\n * parsed event.\n *\n * @throws {@link StreamError} on non-2xx responses, missing body, or a\n * protocol error.\n * @throws {@link DOMException} with `name === 'AbortError'` when `signal`\n * aborts mid-stream.\n */\nexport async function* parseChatStream(\n response: Response,\n signal: AbortSignal,\n): AsyncGenerator<{ chunk: UIChunk; eventId?: string }, void, void> {\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new StreamError(`server responded ${response.status}: ${text.slice(0, 200)}`, \"server\", response.status);\n }\n if (!response.body) throw new StreamError(\"response has no body\", \"no-body\");\n\n const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();\n let buffer = \"\";\n\n try {\n while (true) {\n if (signal.aborted) throw new DOMException(\"aborted\", \"AbortError\");\n\n const { done, value } = await reader.read();\n if (done) {\n // Flush a trailing frame that wasn't terminated with \\n\\n.\n const trailing = buffer.trim();\n if (trailing) {\n const parsed = parseFrame(trailing);\n if (parsed) yield parsed;\n }\n return;\n }\n\n buffer += value;\n\n // Emit every complete frame currently in the buffer.\n let frameEnd: number;\n while ((frameEnd = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, frameEnd);\n buffer = buffer.slice(frameEnd + 2);\n const parsed = parseFrame(frame);\n if (parsed) yield parsed;\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Parse a single SSE frame string into a typed chunk + optional id.\n * Returns `null` for comment-only frames, `[DONE]` sentinels, and invalid JSON.\n */\nfunction parseFrame(frame: string): { chunk: UIChunk; eventId?: string } | null {\n let dataPayload = \"\";\n let eventId: string | undefined;\n\n for (const line of frame.split(\"\\n\")) {\n if (line === \"\" || line.startsWith(\":\")) continue;\n\n if (line.startsWith(\"data:\")) {\n // SSE spec: a `data:` line may include a leading space that must be stripped.\n // Multi-line data is concatenated with `\\n`.\n const part = line.slice(5).replace(/^ /, \"\");\n dataPayload = dataPayload ? `${dataPayload}\\n${part}` : part;\n continue;\n }\n\n if (line.startsWith(\"id:\")) {\n eventId = line.slice(3).trim();\n }\n // `event:` and `retry:` are intentionally ignored.\n }\n\n if (!dataPayload || dataPayload === \"[DONE]\") return null;\n\n try {\n const chunk = JSON.parse(dataPayload) as UIChunk;\n log.trace(\"chunk\", { type: chunk.type, eventId });\n return { chunk, eventId };\n } catch (err) {\n // One corrupt frame shouldn't kill the whole stream. Trace it so a\n // `?<brand>-debug=trace` session captures the offender; never log\n // beyond trace because corrupted frames CAN be the norm (network\n // splits, mid-frame disconnect) \u2014 flooding the console isn't useful.\n log.trace(\"frame parse failed\", { err, payload: dataPayload.slice(0, 200) });\n return null;\n }\n}\n", "/**\n * Codec for the host-asserted context field (`context`) carried on every\n * `/ai/agent/*` request \u2014 see {@link CONTEXT_PARAM}.\n *\n * The widget bundles the current `userContext` / `pageContext` into one object\n * `{ user?, page? }` and base64url-encodes its JSON. **This is encoding, not\n * encryption** \u2014 the transport is already TLS; base64url only keeps the value\n * body/URL-safe (unicode names) and out of plaintext access logs / the devtools\n * network tab. The backend reverses it with {@link decodeContext}.\n */\n\nimport type { DecodedContext } from \"./protocol\";\n\n/** A sanitised host-asserted context object (`userContext` / `pageContext`). */\nexport type ContextObject = Record<string, string | number | boolean | undefined>;\n\n/** UTF-8-safe base64url (no padding) \u2014 handles emoji / non-Latin names. */\nfunction toBase64Url(json: string): string {\n const bytes = new TextEncoder().encode(json);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Inverse of {@link toBase64Url}. */\nfunction fromBase64Url(b64url: string): string {\n const b64 = b64url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n}\n\nconst nonEmpty = (ctx: ContextObject | undefined): ContextObject | undefined =>\n ctx && Object.keys(ctx).length > 0 ? ctx : undefined;\n\n/**\n * Encode the current `user` / `page` context into the `context` request value.\n * Returns `undefined` when both are empty so the field is simply omitted.\n */\nexport function encodeContext(user: ContextObject | undefined, page: ContextObject | undefined): string | undefined {\n const u = nonEmpty(user);\n const p = nonEmpty(page);\n if (!u && !p) return undefined;\n const envelope: DecodedContext = {};\n if (u) envelope.user = u as DecodedContext[\"user\"];\n if (p) envelope.page = p as DecodedContext[\"page\"];\n return toBase64Url(JSON.stringify(envelope));\n}\n\n/**\n * Decode the `context` value back to `{ user?, page? }`. Returns `undefined`\n * for a missing / malformed value (never throws) \u2014 mirrors the lenient,\n * untrusted-input handling the backend should apply.\n */\nexport function decodeContext(value: string | undefined): DecodedContext | undefined {\n if (!value) return undefined;\n try {\n const parsed = JSON.parse(fromBase64Url(value));\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed) ? (parsed as DecodedContext) : undefined;\n } catch {\n return undefined;\n }\n}\n", "import type { ResponseMode, ServerConfig } from \"@/core/config\";\nimport type { UserPrefs } from \"@/core/persistence\";\nimport type { Message } from \"./types\";\n\n/** A tool reference on the wire: `{ code, config }`. `config` is opaque. */\nexport interface ToolRef {\n code: string;\n config?: Record<string, unknown>;\n}\n\n/**\n * Body of `POST /ai/agent/stream-message`.\n *\n * The **message structure** (`messages`) is the authoritative payload \u2014\n * this is what every request must carry. Everything else is optional and\n * only sent when the developer explicitly configured it.\n *\n * The `data` block exists so advanced integrations can *override* what\n * the backend has configured (currently `tools` and `responseMode`).\n * Model selection (text / image) is owned server-side per deployment\n * and isn't on the wire.\n */\nexport interface SendMessageRequest {\n /** Anonymous browser-level identifier (see {@link Persistence}). */\n visitorId?: string;\n /**\n * Snapshot of the client's current preferences (locale, panel size from\n * drag, sound mute). Sent on every request so the server can reconcile \u2014\n * cheap (<1KB) and keeps the wire protocol stateless.\n */\n userPrefs?: UserPrefs;\n /** Public runtime session id (UUIDv7), persisted server-side. */\n sessionId: string;\n /** What the client is doing. `\"submit-message\"` for a normal user send. */\n trigger?: \"submit-message\" | string;\n messages: WireMessage[];\n /**\n * Per-request overrides. Omit entirely when you want the backend's\n * workspace defaults (the typical case).\n */\n data?: {\n /** Restricts/overrides the tools the assistant may invoke this request. */\n tools?: ToolRef[];\n /**\n * Hint only. The backend ALWAYS streams (SSE); `responseMode` is a pure\n * client rendering choice \u2014 `streaming` paints tokens live, `buffered`\n * shows a loading indicator and paints the whole reply at the finish\n * marker. The wire is identical either way.\n */\n responseMode?: ResponseMode;\n };\n}\n\nexport interface WireMessage {\n id: string;\n role: \"user\" | \"assistant\" | \"system\";\n parts: WirePart[];\n}\n\nexport type WirePart =\n | { type: \"text\"; text: string }\n | {\n type: \"file\";\n /** Canonical URL. May be a custom scheme (e.g. `library-id://<id>`). */\n url: string;\n mediaType: string;\n /** Original filename as uploaded. */\n filename?: string;\n /** Opaque id from the upload response \u2014 lets the backend dedupe + cache. */\n fileId?: string;\n /** Size in bytes. */\n fileSize?: number;\n };\n\n/**\n * Default API paths (relative to `baseUrl`). `upload` and `transcribe` can\n * be overridden per-instance via {@link Endpoints}.\n */\nexport const DEFAULT_PATHS = {\n /** Session bootstrap. `POST` \u2192 deployment config + per-visitor state. */\n startSession: \"/ai/agent/start-session\",\n /** Send a message + stream the reply (SSE). `POST`. */\n streamMessage: \"/ai/agent/stream-message\",\n /**\n * Resume an interrupted reply. `GET` (envelope rides as query params).\n * Stateless: the server re-streams the whole reply SSE again from the start \u2014\n * there is NO `Last-Event-ID`. The client reconciles by id (`seenIds`), so\n * replayed events are skipped and only the continuation surfaces.\n */\n streamResume: \"/ai/agent/stream-resume\",\n /** Abort the in-flight reply stream. `POST`. */\n cancelStream: \"/ai/agent/cancel-stream\",\n /** List the visitor's conversations. `GET`. */\n listSessions: \"/ai/agent/list-sessions\",\n /** Load one conversation's messages. `GET ?sessionId=\u2026`. */\n listMessages: \"/ai/agent/list-messages\",\n /**\n * Persist a user-driven settings change. POST `{ visitorId, userPrefs }` \u2192\n * server persists, returns the merged authoritative copy. Fire-and-forget on\n * the client \u2014 a failure leaves the local cache valid; the next\n * `startSession()` reconciles.\n */\n updateSettings: \"/ai/agent/update-settings\",\n /**\n * Mark a conversation read. POST `{ visitorId, sessionId }` \u2192 the server\n * records `lastReadAt = now` for that (visitor, session) and recomputes\n * `unreadCount` (returned on `/list-sessions`) accordingly. Fire-and-forget\n * on the client; a failure just leaves the badge until the next sync.\n */\n markRead: \"/ai/agent/mark-read\",\n // \u2500\u2500 CMS content (unified \u2014 Home / Help / News / docs) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** All widget content via one filtered endpoint. `GET /ai/agent/content?tags=\u2026`. */\n content: \"/ai/agent/content\",\n} as const;\n\n/**\n * Info the widget sends to the backend on the initial connect call.\n * Lightweight \u2014 just enough context for the backend to personalise the\n * welcome payload (locale-specific copy, user-aware suggestions, etc).\n */\nexport interface StartSessionRequest {\n /**\n * Anonymous browser-level identifier \u2014 minted client-side on first mount,\n * reused forever after. Server uses it to look up per-visitor settings,\n * conversation history, etc. Always sent.\n */\n visitorId: string;\n /** Public runtime session id (UUIDv7). Minted by the widget. */\n sessionId: string;\n /**\n * Cached client-side preferences. Server treats this as a hint \u2014 if the\n * server has a newer / different copy, the response's `settings` field\n * is authoritative.\n */\n userPrefs?: UserPrefs;\n /**\n * Host-asserted context envelope \u2014 see {@link CONTEXT_PARAM}. Injected by the\n * transport on every request (not just this one), so it's listed here only\n * for documentation; callers never set it by hand.\n */\n context?: string;\n /** BCP-47 locale for server-side string selection. */\n locale?: string;\n}\n\n/**\n * Name of the host-asserted context field carried on **every** `/ai/agent/*`\n * request, alongside the required `visitorId` / `sessionId` identity (the request\n * envelope). Placement follows the request shape: JSON body for POSTs, form\n * fields for multipart POSTs (upload / transcribe), query params for GETs.\n *\n * Value: `base64url(JSON.stringify({ user?, page? }))` \u2014 the current, sanitised\n * `userContext` / `page\u200BContext`. base64url keeps it header/URL-safe (unicode\n * names) and out of plaintext logs (obfuscation only \u2014 the transport is TLS).\n * Re-encoded per request so it always reflects the visitor's latest context as\n * they navigate. Unlike `visitorId` / `sessionId`, `context` is **optional**\n * (omitted when empty), informational, and untrusted \u2014 **never** an auth\n * credential; the widget does not authenticate users.\n *\n * Backend: base64url-decode \u2192 `JSON.parse` \u2192 `{ user?, page? }` (both\n * `Record<string, string | number | boolean>`). Treat as untrusted.\n */\nexport const CONTEXT_PARAM = \"context\" as const;\n\n/** Decoded shape of {@link CONTEXT_PARAM} once the backend base64url-decodes it. */\nexport interface DecodedContext {\n user?: Record<string, string | number | boolean>;\n page?: Record<string, string | number | boolean>;\n}\n\n/**\n * Body of `POST /ai/agent/update-settings`. Used to push a user-driven settings\n * change (locale picker, sound toggle, drag-resize) up to the server out\n * of band \u2014 the regular `userPrefs` field on every request also\n * carries the snapshot, but pushing on change is faster than waiting for\n * the next message round-trip.\n */\nexport interface SettingsRequestBody {\n visitorId: string;\n sessionId?: string;\n userPrefs: UserPrefs;\n}\n\n/**\n * Response from `POST /ai/agent/update-settings`. The `userPrefs` echo lets\n * the client confirm what the server actually persisted (it may apply\n * server-side validation / defaults).\n */\nexport interface SettingsResponse {\n ok: true;\n userPrefs: UserPrefs;\n}\n\n/** One-shot bot persona shown in the panel header. */\nexport interface SessionAgent {\n name: string;\n /** Image URL or data URI. When omitted, initials render from `name`. */\n avatar?: string;\n /** Subtitle (\"AI Assistant\", \"Support team\", \u2026). */\n title?: string;\n /** Presence indicator. Default: `'online'`. */\n status?: \"online\" | \"away\" | \"offline\";\n}\n\n/** A clickable quick-reply chip. */\nexport interface SessionSuggestion {\n id: string;\n /** What the chip displays. */\n label: string;\n /** What actually gets sent when clicked. Defaults to `label`. */\n text?: string;\n}\n\n/** Welcome payload pushed into the message list on first open. */\nexport interface SessionWelcome {\n messages?: Array<{ id: string; role: \"assistant\" | \"system\"; text: string }>;\n suggestions?: SessionSuggestion[];\n}\n\n/**\n * Response from `POST /ai/agent/start-session`. Every field is optional so the\n * backend can opt into surfaces incrementally.\n *\n * The `userPrefs` field (when present) is the server's authoritative\n * copy of per-visitor preferences \u2014 the client should adopt it and update\n * its local cache. Lets a user picking French on their phone see the\n * widget in French when they open it on their laptop (assuming the same\n * `visitorId`, e.g. via authenticated session \u2014 out of scope for v1).\n */\nexport interface StartSessionResponse {\n /**\n * Public runtime session id. The widget mints this UUIDv7 and the backend\n * validates that it belongs to the public deployment + visitor.\n */\n sessionId: string;\n visitorId?: string;\n canContinue?: boolean;\n /** Bot persona in the panel header (name, avatar, status). */\n agent?: SessionAgent;\n /** Initial assistant messages + quick-reply chips. */\n welcome?: SessionWelcome;\n /**\n * Deployment-wide widget configuration the dashboard admin saved\n * (mode / theme / launcher / actions / composer / features / \u2026).\n * Client adopts as defaults; customer's `init({...})` still wins\n * per-field. Same for every visitor of this deployment.\n */\n config?: ServerConfig;\n /** Page-mode site config (logo, title). Backend ships it whenever the deployment renders the page-shell sidebar. */\n site?: Record<string, unknown>;\n /** Page-mode sidebar blocks (navigation, link cards). Backend ships it whenever the deployment renders the page-shell sidebar. */\n blocks?: Record<string, unknown>;\n messages?: WireMessage[];\n /**\n * Server's authoritative copy of the visitor's preferences. When present,\n * client adopts it (overrides local cache) and re-applies it to the UI\n * (locale, panel size, sound mute). Absent \u2192 client keeps its cache.\n */\n userPrefs?: UserPrefs;\n /**\n * Optional rebind metadata. When the server returns a `visitorId` /\n * `sessionId` different from what the client sent, this block tells\n * the client WHY \u2014 useful for telemetry and the `visitorRebound` /\n * `sessionRebound` events fired into the host page. Server is free to\n * omit it: the client detects rebinding by comparing request vs\n * response ids regardless.\n *\n * Common reasons: `\"conflict\"` (the id collided with an existing\n * record), `\"expired\"` (the id is too old to be re-used), `\"invalid\"`\n * (malformed / not a UUIDv7). Custom strings are passed through to\n * listeners verbatim.\n */\n rebind?: { visitorId?: string; sessionId?: string };\n}\n\nexport function buildSendMessageRequest(params: {\n sessionId: string;\n messages: Message[];\n tools?: Array<string | ToolRef>;\n responseMode?: ResponseMode;\n /** Override the default `\"submit-message\"` trigger. */\n trigger?: string;\n /** Anonymous browser-level identifier; attached to the body when set. */\n visitorId?: string;\n /** Snapshot of client-side preferences; attached to the body when set. */\n userPrefs?: UserPrefs;\n}): SendMessageRequest {\n const wire: WireMessage[] = params.messages.map((m) => ({\n id: m.id,\n parts: messageToWireParts(m),\n role: m.role,\n }));\n\n const body: SendMessageRequest = {\n messages: wire,\n sessionId: params.sessionId,\n trigger: params.trigger ?? \"submit-message\",\n };\n if (params.visitorId) body.visitorId = params.visitorId;\n if (params.userPrefs) body.userPrefs = params.userPrefs;\n\n const data: NonNullable<SendMessageRequest[\"data\"]> = {};\n const tools = params.tools?.map(normalizeToolRef).filter((t): t is ToolRef => t !== null);\n if (tools?.length) data.tools = tools;\n if (params.responseMode) data.responseMode = params.responseMode;\n if (Object.keys(data).length > 0) body.data = data;\n\n return body;\n}\n\n/** `\"tool:search\"` or `{ code: \"tool:search\", config: {\u2026} }` \u2192 `ToolRef`. */\nexport function normalizeToolRef(ref: string | ToolRef): ToolRef | null {\n if (typeof ref === \"string\") return ref ? { code: ref } : null;\n return ref?.code ? ref : null;\n}\n\nfunction messageToWireParts(m: Message): WirePart[] {\n const out: WirePart[] = [];\n for (const p of m.parts) {\n if (p.kind === \"text\" && p.text) {\n out.push({ text: p.text, type: \"text\" });\n }\n if (p.kind === \"file\" && p.url) {\n out.push({ mediaType: p.mediaType, type: \"file\", url: p.url });\n }\n }\n // Attach uploaded file refs as additional `file` parts (after text).\n for (const a of m.attachments ?? []) {\n if (!a.url) continue;\n out.push({\n type: \"file\",\n url: a.url,\n mediaType: a.mediaType,\n filename: a.name,\n fileId: a.id,\n fileSize: a.size,\n });\n }\n return out;\n}\n\n// ---- Chat history ------------------------------------------------------\n\n/**\n * One row in the chat history list. Returned by `GET /ai/agent/list-sessions`.\n * The backend decides how to compute `title` + `preview` (typically the\n * first user message and the last assistant message respectively).\n */\nexport interface SessionSummary {\n /** UI alias for the backend `sessionId`. */\n sessionId: string;\n title: string;\n /** ISO-8601 timestamp of the most recent message. Used for date grouping. */\n lastMessageAt: string;\n /** One-line preview, shown below the title. */\n preview?: string;\n /**\n * Whether the user can continue posting in this chat. `false` \u2192 viewer\n * mode (composer disabled). `true` \u2192 fully resumable.\n */\n canContinue: boolean;\n /**\n * Unread assistant messages for the current visitor. Drives the\n * Messages-tab badge + the Home \"recent message\" dot. Optional \u2014 older\n * backends omit it (treated as `0`).\n */\n unreadCount?: number;\n}\n\nexport interface ListSessionsResponse {\n sessions: SessionSummary[];\n /** Opaque cursor for the next page. Absent = last page. */\n nextCursor?: string;\n}\n\n/** Full messages for a single chat, fetched when the user taps it in the list. */\nexport interface LoadSessionResponse {\n /** UI alias for the backend `sessionId`. */\n sessionId: string;\n canContinue: boolean;\n messages: WireMessage[];\n /**\n * Optional fresh settings snapshot for this chat (e.g. the agent it was\n * with, which may differ from the current workspace default). Merged with\n * the same `user-wins-per-field` policy as the connect response.\n */\n agent?: SessionAgent;\n /**\n * Suggestion chips to show for *this specific chat*. Distinct from the\n * ones in `StartSessionResponse.welcome.suggestions`, which are new-chat-only\n * (\"What can you do?\", \"Show me pricing\") and become irrelevant once a\n * conversation is in progress. The client always swaps in these values\n * on resume \u2014 `undefined` / `null` means \"clear any connect-time\n * suggestions\", an array (even empty) replaces them.\n */\n suggestions?: SessionSuggestion[] | null;\n}\n\n// ---- CMS content (unified) ---------------------------------------------\n//\n// All widget content is ONE flat collection of `ContentItem` rows, grouped\n// only by `tags` + `categories` (both multi-value) \u2014 no per-type model.\n// Served by a single filtered `GET /ai/agent/content`. The public read returns\n// only published rows assigned to the deployment. Wire contract: `docs/api/content.md`.\n\n/**\n * One CMS content row. Rows are scoped by `tags` and grouped by `categories`.\n * `url` opens as an in-widget iframe (help articles); otherwise `content`\n * (markdown) renders in the native reader. The Home dashboard is NOT a content\n * row \u2014 it's assembled from the tab's `home` config flags; its \"Popular\n * articles\" list is just content fetched by `tags`.\n */\nexport interface ContentItem {\n id: string;\n title: string;\n description?: string;\n /** Markdown body \u2014 rendered in the native reader when there's no `url`. */\n content?: string;\n /** Grouping terms (e.g. Help sections). */\n categories?: string[];\n /** Scope terms (the `tags` filter). */\n tags?: string[];\n /** Hero / thumbnail image URL. */\n image?: string;\n /** External page URL \u2014 opened in a sandboxed in-widget iframe. */\n url?: string;\n /** Sort weight for `sortBy: \"order\"` (lower first). */\n order?: number;\n /** ISO-8601 publish timestamp \u2014 news sort + display. */\n publishedAt?: string;\n /** Custom fields. */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Filters for `GET /ai/agent/content`. Content is scoped by `tags` +\n * `categories` (both multi-value, comma-separated, match ANY); `ids` fetches\n * specific rows (the reader); `search` is full-text; pagination is the standard\n * `page` + `limit`. Usage: a tab's list `{tags}`, grouped by each row's\n * `categories`; news `{tags, sortBy:\"publishedAt\", sortOrder:\"desc\"}`; one row\n * `{ids:[id]}`; search `{tags, search}`.\n */\nexport interface ContentQuery {\n tags?: string | string[];\n categories?: string | string[];\n ids?: string | string[];\n search?: string;\n sortBy?: \"order\" | \"createdAt\" | \"updatedAt\" | \"publishedAt\" | \"title\";\n sortOrder?: \"asc\" | \"desc\";\n /** 1-based page number (standard pagination). */\n page?: number;\n /** Page size (max 200). */\n limit?: number;\n}\n\n/** Standard pagination envelope returned by every list endpoint. */\nexport interface PaginationMeta {\n page: number;\n limit: number;\n total: number;\n totalPages: number;\n hasNext: boolean;\n hasPrev: boolean;\n}\n\n/** Response from `GET /ai/agent/content`. */\nexport interface ListContentResponse {\n items: ContentItem[];\n pagination: PaginationMeta;\n}\n", "/**\n * HTTP transport for the public agent API.\n *\n * The UI can keep using \"chat\" language, but the wire protocol is the\n * deployment-facing `/ai/agent/*` surface: public key in `x-public-key`,\n * deployment id in `x-ai-agent-deployment-id`, raw UUIDv7 `visitorId`,\n * and raw UUIDv7 `sessionId`.\n */\n\nimport type { AuthHeaderProvider } from \"@/core/auth\";\nimport { logger } from \"@/core/logger\";\n\nconst log = logger.scope(\"transport\");\nimport type { UserPrefs } from \"@/core/persistence\";\nimport { parseChatStream } from \"./parser\";\nimport { encodeContext, type ContextObject } from \"./context-codec\";\nimport {\n type SendMessageRequest,\n CONTEXT_PARAM,\n type StartSessionRequest,\n type StartSessionResponse,\n type ContentQuery,\n DEFAULT_PATHS,\n type ListContentResponse,\n type ListSessionsResponse,\n type LoadSessionResponse,\n type SettingsResponse,\n} from \"./protocol\";\nimport { StreamError, type UIChunk } from \"./types\";\n\n/**\n * The validation envelope carried on EVERY `/ai/agent/*` request so the backend\n * can validate the request against the visitor + session it claims to belong to.\n * `visitorId` / `sessionId` are **required** identity (always sent once the\n * session is started); `context` is **optional** host-asserted info (omitted when\n * empty, never auth). Auth (publicKey + deployment id) rides separately in headers.\n *\n * On the wire: JSON body for POSTs, form fields for multipart POSTs, query params\n * for GETs.\n */\ntype RequestEnvelope = { visitorId?: string; sessionId?: string; context?: string };\n\nexport interface TransportOptions {\n /** Backend origin, no trailing slash. */\n baseUrl: string;\n /** Auth header provider. */\n auth: AuthHeaderProvider;\n /** Upload + transcribe endpoint paths (relative to `baseUrl` or absolute URLs). */\n endpoints?: { upload?: string; transcribe?: string };\n /** Inject a custom fetch (used in tests). Defaults to `globalThis.fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface MessageStreamHandle {\n /** Async iterable of `(chunk, eventId)` tuples \u2014 parsed and deduped. */\n iter: AsyncGenerator<ParsedEvent, void, void>;\n /** Cancel the in-flight stream + notify the server. Safe to call multiple times. */\n cancel: () => void;\n}\n\ntype ParsedEvent = { chunk: UIChunk; eventId?: string };\n\n/** Reconnect budget when a reply stream drops before a terminal chunk. */\nconst MAX_RESUME_ATTEMPTS = 3;\n/** Linear backoff base between resume attempts (\u00D7attempt). */\nconst RESUME_BACKOFF_MS = 400;\n\n/** Retry budget for transient request failures (network / retryable status). */\nconst MAX_REQUEST_RETRIES = 3;\n/** Statuses worth retrying \u2014 rate limits, gateway/overload, request timeouts. */\nconst RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);\n\n/** Exponential backoff with jitter: ~0.4s, ~0.8s, ~1.6s (\u00B1 up to 250ms). */\nfunction backoffMs(attempt: number): number {\n return RESUME_BACKOFF_MS * 2 ** attempt + Math.floor(Math.random() * 250);\n}\n\n/**\n * Parse a `Retry-After` header \u2192 ms to wait, or `null` if absent/unparseable.\n * Accepts both forms: delta-seconds (`\"5\"`) and an HTTP-date. Clamped to \u226430s.\n */\nfunction retryAfterMs(headers: Headers): number | null {\n const raw = headers.get(\"retry-after\");\n if (!raw) return null;\n const secs = Number(raw);\n const ms = Number.isFinite(secs) ? secs * 1000 : Date.parse(raw) - Date.now();\n return Number.isFinite(ms) && ms >= 0 ? Math.min(ms, 30_000) : null;\n}\n\n/** Abortable delay \u2014 rejects-free; resolves early if the (optional) signal aborts. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) return resolve();\n const id = setTimeout(done, ms);\n const onAbort = () => done();\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n function done() {\n clearTimeout(id);\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }\n });\n}\n\ninterface AgentSessionsResponse {\n sessions: Array<{\n sessionId: string;\n title: string;\n lastMessageAt: string;\n preview?: string;\n canContinue: boolean;\n unreadCount?: number;\n }>;\n nextCursor?: string;\n}\n\ninterface AgentMessagesResponse {\n sessionId: string;\n canContinue: boolean;\n messages: LoadSessionResponse[\"messages\"];\n agent?: LoadSessionResponse[\"agent\"];\n suggestions?: LoadSessionResponse[\"suggestions\"];\n}\n\nexport class AgentTransport {\n constructor(private readonly opts: TransportOptions) {}\n\n // Identity adopted from the start-session response; rides the request envelope\n // (see `envelope()`) on every subsequent call so the backend can validate the\n // (visitor, session) pair.\n private visitorId: string | undefined;\n private sessionId: string | undefined;\n // Host-asserted context (sanitised `userContext` / `pageContext`), refreshed via\n // `setContext` and carried as the envelope's optional `context` so the backend\n // always sees the visitor's latest context as they navigate.\n private userContext: ContextObject | undefined;\n private pageContext: ContextObject | undefined;\n\n // ---- Public API -------------------------------------------------------\n\n /**\n * Update the host-asserted context carried on every subsequent request.\n * Cheap + synchronous \u2014 call it on mount and whenever `userContext` /\n * `pageContext` change (SPA navigation, runtime `update()`). No re-handshake.\n */\n setContext(userContext: ContextObject | undefined, pageContext: ContextObject | undefined): void {\n this.userContext = userContext;\n this.pageContext = pageContext;\n }\n\n /** Build the per-request envelope from the current transport state. */\n private envelope(): RequestEnvelope {\n const out: RequestEnvelope = {};\n if (this.visitorId) out.visitorId = this.visitorId;\n if (this.sessionId) out.sessionId = this.sessionId;\n const context = encodeContext(this.userContext, this.pageContext);\n if (context) out[CONTEXT_PARAM] = context;\n return out;\n }\n\n /** Merge the envelope into a JSON body (POSTs). Explicit body fields win \u2014 e.g.\n * start-session's client-proposed ids, or a mark-read target session that\n * differs from the active one. */\n private withEnvelope<T extends object>(body: T): T & RequestEnvelope {\n return { ...this.envelope(), ...body };\n }\n\n /** Append the envelope as form fields (multipart POSTs \u2014 upload / transcribe). */\n private appendEnvelope(form: FormData): FormData {\n for (const [key, value] of Object.entries(this.envelope())) form.append(key, value);\n return form;\n }\n\n /** Resolve a path to its full URL and append the envelope as query params (GETs).\n * Won't clobber params the caller already set (e.g. a `sessionId` resource\n * selector on load-session). */\n private withEnvelopeQuery(urlOrPath: string): string {\n const url = new URL(this.url(urlOrPath));\n for (const [key, value] of Object.entries(this.envelope())) {\n if (!url.searchParams.has(key)) url.searchParams.set(key, value);\n }\n return url.toString();\n }\n\n /** One-shot runtime bootstrap. Called once after the widget mounts. */\n async startSession(body: StartSessionRequest): Promise<StartSessionResponse> {\n log.debug(\"startSession \u2192\", { visitorId: body.visitorId, sessionId: body.sessionId, locale: body.locale });\n const res = await this.postJson<StartSessionResponse>(DEFAULT_PATHS.startSession, body, \"startSession\");\n log.debug(\"startSession \u2190\", {\n visitorId: res.visitorId,\n sessionId: res.sessionId,\n canContinue: res.canContinue,\n hasMessages: !!res.messages?.length,\n hasWelcome: !!res.welcome,\n hasConfig: !!res.config,\n rebind: res.rebind,\n });\n // Defensive narrowing: warn (don't throw) on shape drift so the widget\n // keeps working when the backend ships a new optional field. We\n // hand-roll the check (instead of importing the canonical Zod schema)\n // because pulling Zod into the auto-boot IIFE bundle adds ~67KB gz \u2014\n // a non-starter for a CDN script. Zod stays the source of truth for\n // the admin under the `./schema` package subpath.\n if (!isStartSessionResponseShape(res)) {\n log.warn(\"startSession response did not match expected shape; using raw response\", { response: res });\n }\n // Adopt the SERVER's `visitorId` / `sessionId`. When the server\n // returns the same values the client sent, this is a no-op\n // (steady-state case). When the server rebinds \u2014 id collision,\n // expiration, malformed input \u2014 `res.visitorId` / `res.sessionId`\n // are the fresh values the rest of the widget MUST use for every\n // subsequent request. The App detects rebinding (request vs\n // response id mismatch) and fires `visitorRebound` /\n // `sessionRebound` events + persists the new ids.\n this.visitorId = res.visitorId ?? body.visitorId;\n this.sessionId = res.sessionId;\n return res;\n }\n\n /** Upload a file attachment. Returns the `{id, url}` the backend assigns. */\n async uploadFile(file: File): Promise<{ id: string; url: string }> {\n log.debug(\"uploadFile \u2192\", { name: file.name, size: file.size, type: file.type });\n const fd = new FormData();\n fd.append(\"file\", file, file.name);\n const res = await this.postForm<{ id: string; url: string }>(this.uploadPath, fd, \"upload\");\n log.debug(\"uploadFile \u2190\", { id: res.id, url: res.url });\n return res;\n }\n\n /**\n * Upload a captured audio blob for server-side speech-to-text.\n * Only used when `features.voice === 'server'`.\n */\n async transcribe(audio: Blob, mimeType = \"audio/webm\"): Promise<{ text: string }> {\n log.debug(\"transcribe \u2192\", { size: audio.size, mimeType });\n const fd = new FormData();\n fd.append(\"audio\", audio, `voice.${extensionFor(mimeType)}`);\n fd.append(\"mimeType\", mimeType);\n const res = await this.postForm<{ text: string }>(this.transcribePath, fd, \"transcribe\");\n log.debug(\"transcribe \u2190\", { textLen: res.text.length });\n return res;\n }\n\n async listSessions(\n params: { visitorId?: string; limit?: number; cursor?: string } = {},\n ): Promise<ListSessionsResponse> {\n const url = new URL(this.url(DEFAULT_PATHS.listSessions));\n // `visitorId` is supplied by the request envelope; only set it here to\n // override the transport's current visitor (rare \u2014 used by some callers).\n if (params.visitorId) url.searchParams.set(\"visitorId\", params.visitorId);\n if (params.limit) url.searchParams.set(\"limit\", String(params.limit));\n if (params.cursor) url.searchParams.set(\"cursor\", params.cursor);\n\n log.debug(\"listSessions \u2192\", {\n visitorId: params.visitorId ?? this.visitorId,\n limit: params.limit,\n cursor: params.cursor,\n });\n const res = await this.getJson<AgentSessionsResponse>(url.toString(), \"listSessions\");\n log.debug(\"listSessions \u2190\", { count: res.sessions.length, nextCursor: res.nextCursor });\n return {\n sessions: res.sessions.map((session) => ({\n sessionId: session.sessionId,\n title: session.title,\n lastMessageAt: session.lastMessageAt,\n preview: session.preview,\n canContinue: session.canContinue,\n unreadCount: session.unreadCount,\n })),\n nextCursor: res.nextCursor,\n };\n }\n\n async loadSession(sessionId: string): Promise<LoadSessionResponse> {\n // `visitorId` rides the envelope; `sessionId` here is the resource selector\n // (the thread to load \u2014 may differ from the active session), so it's explicit.\n const url = new URL(this.url(DEFAULT_PATHS.listMessages));\n url.searchParams.set(\"sessionId\", sessionId);\n\n log.debug(\"loadSession \u2192\", { sessionId, visitorId: this.visitorId });\n const res = await this.getJson<AgentMessagesResponse>(url.toString(), \"loadSession\");\n log.debug(\"loadSession \u2190\", {\n sessionId: res.sessionId,\n canContinue: res.canContinue,\n messageCount: res.messages.length,\n hasSuggestions: !!res.suggestions,\n });\n return {\n sessionId: res.sessionId,\n canContinue: res.canContinue,\n messages: res.messages,\n agent: res.agent,\n suggestions: res.suggestions,\n };\n }\n\n /**\n * Mark a conversation read up to its latest message. Fire-and-forget \u2014 the\n * server records `lastReadAt` and clears that session's `unreadCount` on the\n * next `/sessions`. A failure is non-fatal (the badge just lingers until the\n * next sync), so callers don't await this.\n */\n async markRead(sessionId: string): Promise<void> {\n if (!this.visitorId || !sessionId) return;\n log.debug(\"markRead \u2192\", { sessionId, visitorId: this.visitorId });\n try {\n // `sessionId` here is the thread being marked (may differ from the active\n // session), so it's explicit; `visitorId` rides the envelope.\n await this.postJson<{ ok: boolean }>(DEFAULT_PATHS.markRead, { sessionId }, \"markRead\");\n } catch (err) {\n log.debug(\"markRead failed (non-fatal)\", { err });\n }\n }\n\n // ---- CMS content (unified) --------------------------------------------\n //\n // One endpoint for every content surface (Home / Help / News / docs).\n // Callers pass filters; a thrown StreamError (e.g. 404 on an older backend)\n // is treated as \"no content\" by the module and the tab degrades gracefully.\n\n /** Fetch content rows by filter. `GET /ai/agent/content`. */\n async listContent(query: ContentQuery = {}): Promise<ListContentResponse> {\n const url = new URL(this.url(DEFAULT_PATHS.content));\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) continue;\n url.searchParams.set(key, Array.isArray(value) ? value.join(\",\") : String(value));\n }\n log.debug(\"listContent \u2192\", query);\n const res = await this.getJson<ListContentResponse>(url.toString(), \"listContent\");\n log.debug(\"listContent \u2190\", { count: res.items.length, total: res.pagination?.total });\n return res;\n }\n\n async saveUserPrefs(userPrefs: UserPrefs): Promise<SettingsResponse> {\n // `visitorId` / `sessionId` ride the request envelope.\n log.debug(\"saveUserPrefs \u2192\", {\n visitorId: this.visitorId,\n sessionId: this.sessionId,\n settingsKeys: Object.keys(userPrefs),\n });\n const res = await this.postJson<SettingsResponse>(DEFAULT_PATHS.updateSettings, { userPrefs }, \"saveUserPrefs\");\n log.debug(\"saveUserPrefs \u2190\", { ok: res.ok });\n return res;\n }\n\n private get uploadPath(): string {\n return this.opts.endpoints?.upload ?? \"/ai/agent/upload-file\";\n }\n\n private get transcribePath(): string {\n return this.opts.endpoints?.transcribe ?? \"/ai/agent/transcribe-audio\";\n }\n\n sendMessage(body: SendMessageRequest): MessageStreamHandle {\n log.debug(\"message \u2192\", {\n sessionId: body.sessionId,\n visitorId: body.visitorId,\n messageCount: body.messages.length,\n trigger: body.trigger,\n responseMode: body.data?.responseMode,\n tools: body.data?.tools?.map((t) => t.code),\n });\n const ctrl = new AbortController();\n const seenIds = new Set<string>();\n\n const iter = this.runStream(body, ctrl, seenIds);\n const cancel = () => this.cancelStream(ctrl, body.sessionId);\n return { iter, cancel };\n }\n\n // ---- Stream orchestration ---------------------------------------------\n\n private async *runStream(\n body: SendMessageRequest,\n ctrl: AbortController,\n seenIds: Set<string>,\n ): AsyncGenerator<ParsedEvent, void, void> {\n // First attempt: POST /stream-message. If it ends before a terminal chunk\n // (`finish`/`error`) \u2014 a dropped connection \u2014 try to re-attach to the\n // in-flight reply via GET /stream-resume. The server answers 200 (SSE,\n // still generating) or 204 (nothing in-flight \u2192 the reply is already done).\n // Replayed events are deduped by id, so only the continuation surfaces.\n if (yield* this.drain(this.openMessageStream(body, ctrl.signal), seenIds, ctrl)) return;\n\n for (let attempt = 1; attempt <= MAX_RESUME_ATTEMPTS && !ctrl.signal.aborted; attempt++) {\n await sleep(RESUME_BACKOFF_MS * attempt, ctrl.signal);\n if (ctrl.signal.aborted) return;\n let res: Response;\n try {\n res = await this.fetchImpl(this.resumeUrl(seenIds.size), {\n method: \"GET\",\n credentials: \"omit\",\n headers: { accept: \"text/event-stream\", ...this.opts.auth.headers() },\n signal: ctrl.signal,\n });\n } catch (err) {\n if (ctrl.signal.aborted) return;\n log.debug(\"resume fetch failed \u2014 retrying\", { attempt, err });\n continue;\n }\n // 204 (or any non-OK): nothing in-flight to resume \u2014 stop with what we have.\n if (res.status === 204 || !res.ok) {\n log.debug(\"resume \u2192 no in-flight stream\", { status: res.status });\n return;\n }\n if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;\n }\n if (ctrl.signal.aborted) return;\n // Exhausted the resume budget without a terminal chunk \u2014 surface an error so\n // the UI shows a retry instead of a half-finished reply hanging forever.\n throw new StreamError(\"stream incomplete after resume attempts\", \"server\");\n }\n\n /**\n * Drain one stream segment: yield its (deduped) events. Returns `true` once a\n * terminal chunk (`finish`/`error`) is seen OR the run is aborted \u2014 i.e. the\n * caller should stop. Returns `false` if the segment ended without finishing\n * (a drop), so the caller can attempt a resume.\n */\n private async *drain(\n source: AsyncIterable<ParsedEvent>,\n seenIds: Set<string>,\n ctrl: AbortController,\n ): AsyncGenerator<ParsedEvent, boolean, void> {\n try {\n for await (const evt of source) {\n if (ctrl.signal.aborted) return true;\n if (evt.eventId) {\n if (seenIds.has(evt.eventId)) continue;\n seenIds.add(evt.eventId);\n }\n yield evt;\n if (evt.chunk.type === \"finish\" || evt.chunk.type === \"error\") return true;\n }\n } catch (err) {\n if (ctrl.signal.aborted) return true;\n // An HTTP error (non-2xx with a status) is terminal \u2014 the request was\n // rejected, not dropped mid-flight. Propagate it so the UI shows the\n // error (with the status) instead of silently attempting a resume.\n if (err instanceof StreamError && err.status !== undefined) throw err;\n log.debug(\"stream segment dropped\", { err });\n }\n return false;\n }\n\n /** Abort + fire-and-forget POST to `/ai/agent/cancel-stream`. Idempotent. */\n private cancelStream(ctrl: AbortController, sessionId: string | undefined): void {\n if (ctrl.signal.aborted) return;\n log.debug(\"cancel stream\", { sessionId, visitorId: this.visitorId });\n ctrl.abort();\n if (!this.visitorId || !sessionId) return;\n this.fetchImpl(this.url(DEFAULT_PATHS.cancelStream), {\n method: \"POST\",\n credentials: \"omit\",\n headers: { \"content-type\": \"application/json\", ...this.opts.auth.headers() },\n body: JSON.stringify(this.withEnvelope({})),\n keepalive: true,\n }).catch((err) => log.debug(\"cancel POST failed (non-fatal)\", { err }));\n }\n\n // ---- Low-level fetch helpers ------------------------------------------\n\n private async *openMessageStream(body: SendMessageRequest, signal: AbortSignal): AsyncGenerator<ParsedEvent> {\n const res = await this.fetchImpl(this.url(DEFAULT_PATHS.streamMessage), {\n method: \"POST\",\n credentials: \"omit\",\n headers: {\n accept: \"text/event-stream\",\n \"content-type\": \"application/json\",\n ...this.opts.auth.headers(),\n },\n body: JSON.stringify(this.withEnvelope(body)),\n signal,\n });\n yield* parseChatStream(res, signal);\n }\n\n /**\n * Resume URL: `GET /stream-resume` with the envelope (visitorId / sessionId /\n * context) as query params plus `resumeAt` \u2014 the count of events already\n * consumed, so the server knows where the client is. The server replies 200\n * (SSE, still in-flight) or 204 (nothing to resume).\n */\n private resumeUrl(resumeAt: number): string {\n const url = new URL(this.withEnvelopeQuery(DEFAULT_PATHS.streamResume));\n url.searchParams.set(\"resumeAt\", String(resumeAt));\n return url.toString();\n }\n\n // JSON requests are idempotent (reads, or full-snapshot writes like\n // update-settings / mark-read / start-session), so they auto-retry transient\n // failures. The message POST is NOT in here \u2014 re-sending could duplicate the\n // reply, so it surfaces an error for the user to retry (see `openMessageStream`).\n private async postJson<T>(path: string, body: object, label: string): Promise<T> {\n const res = await this.fetchWithRetry(\n () => [\n this.url(path),\n {\n method: \"POST\",\n credentials: \"omit\",\n headers: { \"content-type\": \"application/json\", ...this.opts.auth.headers() },\n body: JSON.stringify(this.withEnvelope(body)),\n },\n ],\n label,\n );\n return this.assertOk<T>(res, label);\n }\n\n private async postForm<T>(path: string, body: FormData, label: string): Promise<T> {\n // No auto-retry: large multipart bodies + non-idempotent uploads. The user\n // re-triggers (re-attach / re-record) on failure.\n const res = await this.fetchImpl(this.url(path), {\n method: \"POST\",\n credentials: \"omit\",\n headers: this.opts.auth.headers(),\n body: this.appendEnvelope(body),\n });\n return this.assertOk<T>(res, label);\n }\n\n private async getJson<T>(urlOrPath: string, label: string): Promise<T> {\n const res = await this.fetchWithRetry(\n () => [\n this.withEnvelopeQuery(urlOrPath),\n { method: \"GET\", credentials: \"omit\", headers: { accept: \"application/json\", ...this.opts.auth.headers() } },\n ],\n label,\n );\n return this.assertOk<T>(res, label);\n }\n\n /**\n * Fetch with bounded retries for *transient* failures: retryable statuses\n * (429 / 5xx / \u2026) and network errors, honoring `Retry-After` (else backoff +\n * jitter). Terminal statuses (4xx) return immediately for the caller to throw.\n * Use only for idempotent requests.\n */\n private async fetchWithRetry(build: () => readonly [string, RequestInit], label: string): Promise<Response> {\n for (let attempt = 0; ; attempt++) {\n const [reqUrl, init] = build();\n let res: Response;\n try {\n res = await this.fetchImpl(reqUrl, init);\n } catch (err) {\n if (attempt >= MAX_REQUEST_RETRIES) {\n throw new StreamError(`${label} failed: network error`, \"network\", undefined, err);\n }\n log.debug(\"request network error \u2014 retrying\", { label, attempt });\n await sleep(backoffMs(attempt));\n continue;\n }\n if (res.ok || !RETRYABLE_STATUS.has(res.status) || attempt >= MAX_REQUEST_RETRIES) return res;\n const wait = retryAfterMs(res.headers) ?? backoffMs(attempt);\n log.debug(\"request retryable status \u2014 retrying\", { label, status: res.status, attempt, wait });\n await sleep(wait);\n }\n }\n\n /** Throw on non-2xx (with the status attached), otherwise parse the JSON body. */\n private async assertOk<T>(res: Response, label: string): Promise<T> {\n if (!res.ok) throw new StreamError(`${label} failed: ${res.status}`, \"server\", res.status);\n return (await res.json()) as T;\n }\n\n private url(pathOrUrl: string): string {\n if (/^https?:\\/\\//i.test(pathOrUrl)) return pathOrUrl;\n return `${this.opts.baseUrl}${pathOrUrl}`;\n }\n\n private get fetchImpl(): typeof fetch {\n return this.opts.fetchImpl ?? globalThis.fetch.bind(globalThis);\n }\n}\n\nfunction extensionFor(mimeType: string): string {\n if (mimeType.includes(\"webm\")) return \"webm\";\n if (mimeType.includes(\"ogg\")) return \"ogg\";\n if (mimeType.includes(\"mp4\") || mimeType.includes(\"m4a\")) return \"m4a\";\n if (mimeType.includes(\"mpeg\")) return \"mp3\";\n if (mimeType.includes(\"wav\")) return \"wav\";\n return \"bin\";\n}\n\n/**\n * Lightweight runtime check that the start-session response carries the\n * always-present fields the widget actually consumes. Mirrors the strict\n * fields of `startSessionResponseSchema` (visitorId, sessionId, canContinue) \u2014\n * everything else is optional or `.loose()` so we don't validate it here.\n *\n * Hand-rolled instead of `startSessionResponseSchema.safeParse(raw)` because\n * pulling Zod into the IIFE / auto-boot path is ~67KB gz. Zod remains the\n * source of truth via the `./schema` package subpath.\n */\nfunction isStartSessionResponseShape(raw: unknown): raw is StartSessionResponse {\n if (!raw || typeof raw !== \"object\") return false;\n const r = raw as Record<string, unknown>;\n return typeof r.visitorId === \"string\" && typeof r.sessionId === \"string\" && typeof r.canContinue === \"boolean\";\n}\n", "/**\n * Reactive-message shape + factories.\n *\n * A {@link ReactiveMessage} wraps a {@link Message} where every text field\n * is a `Signal<string>` \u2014 that's what makes per-token streaming cheap. The\n * reducer writes into those signals; the UI binds directly to them.\n *\n * This module exposes the types, the factories for the four canonical\n * message shapes (user / assistant / typewriter-welcome / instant-welcome),\n * and the conversion helpers `toReactive` / `fromReactive` used to bridge\n * with the wire format (`fromWireMessage`) and the request body builder\n * (`buildSendMessageRequest` consumes the plain `Message` shape).\n */\n\nimport { signal, type Signal } from \"@preact/signals\";\nimport { uuid7 } from \"@/core/ids\";\nimport type { WireMessage } from \"./protocol\";\nimport type { Message, MessagePart } from \"./types\";\n\n// ---- Reactive types ----------------------------------------------------\n\n/** A message stored in the App's `messagesSig`. */\nexport interface ReactiveMessage extends Omit<Message, \"parts\"> {\n partsSig: Signal<ReactivePart[]>;\n}\n\nexport type ReactivePart =\n | { kind: \"text\"; id: string; textSig: Signal<string>; doneSig: Signal<boolean> }\n | { kind: \"reasoning\"; id: string; textSig: Signal<string>; doneSig: Signal<boolean> }\n | {\n kind: \"tool\";\n toolCallId: string;\n toolName: string;\n inputPartialSig: Signal<string>;\n inputSig: Signal<unknown | undefined>;\n outputSig: Signal<unknown | undefined>;\n errorSig: Signal<string | undefined>;\n stateSig: Signal<\"input\" | \"awaiting\" | \"output\" | \"error\">;\n }\n | { kind: \"file\"; url: string; mediaType: string }\n | { kind: \"source\"; sourceId: string; url: string; title?: string };\n\n// ---- Factories ---------------------------------------------------------\n\n/** A user message \u2014 always complete, optional text + attachments. */\nexport function makeUserMessage(text: string, attachments: Message[\"attachments\"]): ReactiveMessage {\n const parts: ReactivePart[] = text ? [{ kind: \"text\", id: \"t0\", textSig: signal(text), doneSig: signal(true) }] : [];\n return {\n id: uuid7(),\n role: \"user\",\n createdAt: Date.now(),\n status: \"complete\",\n attachments,\n partsSig: signal(parts),\n };\n}\n\n/** An empty assistant shell that the reducer fills as chunks arrive. */\nexport function makeAssistantMessage(): ReactiveMessage {\n return {\n id: uuid7(),\n role: \"assistant\",\n createdAt: Date.now(),\n status: \"streaming\",\n partsSig: signal([]),\n };\n}\n\n/** A pushed-from-server welcome message. The two factories below build the\n * reactive shape for it \u2014 pick based on the configured animation mode.\n */\nexport interface WelcomeMessage {\n id: string;\n role: \"assistant\" | \"system\";\n text: string;\n}\n\n/**\n * A welcome message whose text signal starts empty \u2014 fills by the\n * typewriter animator (see `ui/welcome-animator.ts`).\n */\nexport function makeTypewriterMessage(m: WelcomeMessage): ReactiveMessage {\n return {\n id: m.id,\n role: m.role,\n createdAt: Date.now(),\n status: \"streaming\",\n partsSig: signal([{ kind: \"text\", id: `${m.id}-t0`, textSig: signal(\"\"), doneSig: signal(false) }]),\n };\n}\n\n/**\n * A welcome message rendered instantly (no typewriter). Used when\n * `welcome.animation === \"instant\"`.\n */\nexport function makeInstantWelcomeMessage(m: WelcomeMessage): ReactiveMessage {\n return toReactive({\n id: m.id,\n role: m.role,\n createdAt: Date.now(),\n status: \"complete\",\n parts: [{ kind: \"text\", id: `${m.id}-t0`, text: m.text, done: true }],\n });\n}\n\n// ---- Conversions -------------------------------------------------------\n\n/** Plain-data `Message` \u2192 reactive signals. */\nexport function toReactive(m: Message): ReactiveMessage {\n return {\n id: m.id,\n role: m.role,\n createdAt: m.createdAt,\n status: m.status,\n errorText: m.errorText,\n finishReason: m.finishReason,\n attachments: m.attachments,\n partsSig: signal(m.parts.map(partToReactive)),\n };\n}\n\n/**\n * Wire-format `WireMessage` \u2192 reactive. Used when hydrating a past chat\n * from `/ai/agent/list-messages?sessionId=...`. Text parts become signal-backed; file\n * parts pass through as-is (the wire shape for `file` already matches\n * the reactive shape for `kind: \"file\"`).\n */\nexport function fromWireMessage(w: WireMessage): ReactiveMessage {\n const parts: ReactivePart[] = w.parts\n .map((part, i): ReactivePart | null => {\n if (part.type === \"text\") {\n return { kind: \"text\", id: `${w.id}-t${i}`, textSig: signal(part.text), doneSig: signal(true) };\n }\n if (part.type === \"file\") {\n return { kind: \"file\", url: part.url, mediaType: part.mediaType };\n }\n return null;\n })\n .filter((p): p is ReactivePart => p !== null);\n return {\n id: w.id,\n role: w.role,\n createdAt: Date.now(),\n status: \"complete\",\n partsSig: signal(parts),\n };\n}\n\n/** Reactive \u2192 plain-data. Used when serialising messages for the wire. */\nexport function fromReactive(m: ReactiveMessage): Message {\n return {\n id: m.id,\n role: m.role,\n createdAt: m.createdAt,\n status: m.status,\n errorText: m.errorText,\n finishReason: m.finishReason,\n attachments: m.attachments,\n parts: m.partsSig.value.map(partFromReactive),\n };\n}\n\n/** Concatenate every text part into a single string. */\nexport function assistantText(m: ReactiveMessage): string {\n let out = \"\";\n for (const part of m.partsSig.value) {\n if (part.kind === \"text\") out += part.textSig.value;\n }\n return out;\n}\n\n// ---- internals ---------------------------------------------------------\n\nfunction partToReactive(p: MessagePart): ReactivePart {\n if (p.kind === \"text\" || p.kind === \"reasoning\") {\n return { kind: p.kind, id: p.id, textSig: signal(p.text), doneSig: signal(p.done) };\n }\n if (p.kind === \"tool\") {\n return {\n kind: \"tool\",\n toolCallId: p.toolCallId,\n toolName: p.toolName,\n inputPartialSig: signal(p.inputPartial),\n inputSig: signal(p.input),\n outputSig: signal(p.output),\n errorSig: signal(p.error),\n stateSig: signal(p.state),\n };\n }\n // Plain data parts (file / source) carry no signals \u2014 pass through.\n return p;\n}\n\nfunction partFromReactive(p: ReactivePart): MessagePart {\n if (p.kind === \"text\" || p.kind === \"reasoning\") {\n return { kind: p.kind, id: p.id, text: p.textSig.value, done: p.doneSig.value };\n }\n if (p.kind === \"tool\") {\n return {\n kind: \"tool\",\n toolCallId: p.toolCallId,\n toolName: p.toolName,\n inputPartial: p.inputPartialSig.value,\n input: p.inputSig.value,\n output: p.outputSig.value,\n error: p.errorSig.value,\n state: p.stateSig.value,\n };\n }\n return p;\n}\n", "/**\n * Stream reducer \u2014 applies {@link UIChunk}s to a reactive message.\n *\n * Signal-aware by design: each text part holds a `Signal<string>` internally,\n * so a token delta is *one* signal write that patches one DOM text node.\n * The outer `messagesSig` array is only mutated on *structural* changes\n * (new tool part arriving, error, finish) \u2014 list reconciliation stays cheap.\n *\n * Public API: `attach(msg)` to start tracking, `apply(chunk)` per chunk,\n * `detach()` when done.\n */\n\nimport { logger } from \"@/core/logger\";\nimport { signal, type Signal } from \"@preact/signals\";\nimport type { ReactiveMessage, ReactivePart } from \"./messages\";\nimport type { UIChunk } from \"./types\";\n\nconst log = logger.scope(\"reducer\");\n\nexport class StreamReducer {\n /** Currently-streaming assistant message; null when no stream is active. */\n private active: ReactiveMessage | null = null;\n\n constructor(private messagesSig: Signal<ReactiveMessage[]>) {}\n\n attach(msg: ReactiveMessage): void {\n this.active = msg;\n }\n\n detach(): void {\n this.active = null;\n }\n\n apply(chunk: UIChunk): void {\n const m = this.active;\n if (!m) return;\n\n switch (chunk.type) {\n case \"start\":\n case \"start-step\":\n case \"finish-step\":\n return;\n\n case \"finish\":\n log.debug(\"finish\", { reason: chunk.finishReason, canContinue: chunk.canContinue });\n m.status = \"complete\";\n if (chunk.finishReason) m.finishReason = chunk.finishReason;\n this.bumpDone(m);\n return;\n\n case \"error\":\n log.warn(\"stream error chunk\", { errorText: chunk.errorText });\n m.status = \"error\";\n m.errorText = chunk.errorText;\n this.bumpDone(m);\n return;\n\n case \"text-start\":\n ensureTextPart(m, \"text\", chunk.id);\n return;\n case \"text-delta\": {\n const p = ensureTextPart(m, \"text\", chunk.id);\n p.textSig.value += chunk.delta;\n return;\n }\n case \"text-end\":\n ensureTextPart(m, \"text\", chunk.id).doneSig.value = true;\n return;\n\n case \"reasoning-start\":\n // Backends may reuse one reasoning id across multiple thinking phases\n // (think \u2192 tool \u2192 think). Re-opening must clear `done` so the appended\n // deltas keep rendering instead of being treated as a finished block.\n ensureTextPart(m, \"reasoning\", chunk.id).doneSig.value = false;\n return;\n case \"reasoning-delta\": {\n const p = ensureTextPart(m, \"reasoning\", chunk.id);\n p.textSig.value += chunk.delta;\n return;\n }\n case \"reasoning-end\":\n ensureTextPart(m, \"reasoning\", chunk.id).doneSig.value = true;\n return;\n\n case \"tool-input-start\":\n case \"tool-input-delta\":\n case \"tool-input-available\":\n case \"tool-output-available\":\n case \"tool-output-error\":\n applyTool(m, chunk);\n return;\n\n case \"file\":\n appendPart(m, { kind: \"file\", url: chunk.url, mediaType: chunk.mediaType });\n return;\n\n case \"source-url\": {\n // De-duplicate by sourceId. Some backends emit a `source-url`\n // chunk every time the model cites a source mid-stream, so the\n // same id arrives multiple times for one logical citation. The\n // user-visible UI shows one row per source either way; appending\n // duplicates only produces a Preact \"two children with the same\n // key\" warning + a redundant link. Keep the first occurrence's\n // metadata so the title doesn't flicker if a later chunk is\n // missing it.\n const parts = m.partsSig.value;\n if (parts.some((p) => p.kind === \"source\" && p.sourceId === chunk.sourceId)) return;\n appendPart(m, { kind: \"source\", sourceId: chunk.sourceId, url: chunk.url, title: chunk.title });\n return;\n }\n\n case \"data-notification\":\n // Notifications surface in the panel toast, not the message body.\n return;\n\n default: {\n const _exhaustive: never = chunk;\n void _exhaustive;\n }\n }\n }\n\n /**\n * Terminal-state bump (`finish`/`error`). Touches the message's own `partsSig`\n * AND the list so subscribers re-render: a buffered bubble (subscribed to its\n * parts) flips out of the loading indicator into the finished reply, and the\n * list reflects the new `status`.\n */\n private bumpDone(m: ReactiveMessage): void {\n m.partsSig.value = [...m.partsSig.value];\n this.messagesSig.value = [...this.messagesSig.value];\n }\n}\n\n// ---- helpers -----------------------------------------------------------\n\ntype TextKind = \"text\" | \"reasoning\";\ntype TextPart = Extract<ReactivePart, { kind: TextKind }>;\n\nfunction ensureTextPart(m: ReactiveMessage, kind: TextKind, id: string): TextPart {\n const existing = m.partsSig.value.find((p): p is TextPart => p.kind === kind && p.id === id);\n if (existing) return existing;\n const part: TextPart = { kind, id, textSig: signal(\"\"), doneSig: signal(false) };\n appendPart(m, part);\n return part;\n}\n\ntype ToolPart = Extract<ReactivePart, { kind: \"tool\" }>;\n\nfunction ensureToolPart(m: ReactiveMessage, toolCallId: string, toolName?: string): ToolPart {\n const existing = m.partsSig.value.find((p): p is ToolPart => p.kind === \"tool\" && p.toolCallId === toolCallId);\n if (existing) return existing;\n const part: ToolPart = {\n kind: \"tool\",\n toolCallId,\n toolName: toolName ?? \"tool\",\n inputPartialSig: signal(\"\"),\n inputSig: signal(undefined),\n outputSig: signal(undefined),\n errorSig: signal(undefined),\n stateSig: signal(\"input\"),\n };\n appendPart(m, part);\n return part;\n}\n\nfunction appendPart(m: ReactiveMessage, part: ReactivePart): void {\n m.partsSig.value = [...m.partsSig.value, part];\n}\n\nfunction applyTool(m: ReactiveMessage, chunk: UIChunk): void {\n switch (chunk.type) {\n case \"tool-input-start\":\n ensureToolPart(m, chunk.toolCallId, chunk.toolName);\n return;\n case \"tool-input-delta\": {\n const p = ensureToolPart(m, chunk.toolCallId);\n p.inputPartialSig.value += chunk.delta;\n return;\n }\n case \"tool-input-available\": {\n const p = ensureToolPart(m, chunk.toolCallId, chunk.toolName);\n p.inputSig.value = chunk.input;\n p.stateSig.value = \"awaiting\";\n return;\n }\n case \"tool-output-available\": {\n const p = ensureToolPart(m, chunk.toolCallId);\n p.outputSig.value = chunk.output;\n p.stateSig.value = \"output\";\n return;\n }\n case \"tool-output-error\": {\n const p = ensureToolPart(m, chunk.toolCallId);\n p.errorSig.value = chunk.errorText;\n p.stateSig.value = \"error\";\n return;\n }\n default:\n // applyTool only called for tool-* chunks; other types are unreachable.\n return;\n }\n}\n", "/**\n * Per-widget client-side state.\n *\n * Storage holds three things, each under its own key:\n *\n * 1. **`visitorId`** \u2014 anonymous browser-level identifier, minted once per\n * widget instance and reused forever. Sent on every backend request so\n * the server can attach per-visitor settings, history, etc.\n * 2. **`sessionId`** \u2014 the active conversation thread. The backend is the\n * source of truth for messages; we store only the id and re-fetch on\n * reload via `transport.loadSession()`.\n * 3. **`userPrefs`** \u2014 user preferences (locale, panel size from drag,\n * sound mute). Cached locally for instant render-before-network, then\n * reconciled with the server's copy on each `connect()`.\n *\n * Keys are namespaced as `${brand.cssPrefix}.${widgetId}.${field}` so two\n * widgets on the same brand keep their state cleanly separated.\n *\n * Storage is pluggable via {@link ClientStorage} \u2014 pass a custom adapter\n * to add encryption, logging, or use a non-localStorage backend.\n */\n\nimport { BRAND } from \"./brand\";\nimport { uuid7 } from \"./ids\";\nimport { logger } from \"./logger\";\n\nconst log = logger.scope(\"persistence\");\nimport { type ClientStorage, defaultStorage } from \"./storage\";\n\n/** Drag-resized panel dimensions. Persisted only when resize is enabled. */\nexport interface WidgetSize {\n width: number;\n height: number;\n}\n\n/**\n * Panel size tier. Mirrors `ui/host-mode.ts`'s `PanelSize` \u2014 duplicated as a\n * literal here to keep `core` free of a `ui` import (one-way layering).\n */\nexport type PanelSizeTier = \"normal\" | \"expanded\" | \"fullscreen\";\n\n/**\n * User-changeable preferences. Sent to the backend on every request as a\n * `userPrefs` field so the server can detect drift (e.g. user changed\n * locale on another tab) and reconcile.\n *\n * Every field is optional \u2014 an unset key inherits the server / page-owner\n * default. Empty object `{}` means \"no preference recorded yet.\"\n */\nexport interface UserPrefs {\n /** BCP-47 tag picked from the language switcher. */\n locale?: string;\n /** Drag-resized panel dimensions. */\n widgetSize?: WidgetSize;\n /** Sound on/off toggle (icon button). */\n soundMuted?: boolean;\n /** Theme picked from the theme switcher. */\n themeMode?: \"auto\" | \"light\" | \"dark\";\n}\n\n/** Per-widget storage bundle. Create one per App instance. */\nexport interface Persistence {\n /**\n * Stable anonymous browser id. Minted on first call, then reused.\n * Performs a write on the first call to persist the mint \u2014 subsequent\n * calls return the cached value without touching storage.\n */\n getVisitorId(): string;\n /**\n * Replace the stored visitor id \u2014 only called when the server's\n * `/start-session` response rebinds the client-minted one (id\n * conflict, expiration, etc.). Most embeds never trigger this:\n * the mint-once-on-load path covers the common case. After\n * saving, subsequent `getVisitorId()` calls return the new id.\n */\n setVisitorId(id: string): void;\n /** Active conversation id, or `undefined` if no thread is in progress. */\n loadSessionId(): string | undefined;\n saveSessionId(id: string | undefined): void;\n /** Cached user preferences. Empty object when nothing is recorded. */\n loadUserPrefs(): UserPrefs;\n saveUserPrefs(s: UserPrefs): void;\n /**\n * Patch one or more fields without overwriting siblings. Internally a\n * load \u2192 spread \u2192 save round-trip \u2014 safe for concurrent calls within\n * a single tab (storage I/O is synchronous).\n */\n patchUserPrefs(patch: Partial<UserPrefs>): void;\n /**\n * Last open/closed state of the floating panel. `null` means the user\n * has never explicitly opened or closed it \u2014 the App falls back to\n * `options.startMinimized`. Stored as a separate key (not in\n * `userPrefs`) so it stays *per-device* \u2014 opening on a phone\n * doesn't force-open every other device's widget.\n */\n loadPanelOpen(): boolean | null;\n savePanelOpen(open: boolean): void;\n /**\n * Last panel size tier the user picked (expand / fullscreen). `null` = never\n * changed \u2192 caller falls back to `size.initialSize`. Per-device, like\n * {@link loadPanelOpen}, so making the panel bigger and refreshing keeps it\n * bigger. Ignored by always-fullscreen modes (standalone / page).\n */\n loadPanelSize(): PanelSizeTier | null;\n savePanelSize(size: PanelSizeTier): void;\n /**\n * Whether the user has dismissed the CURRENT hint copy. We persist the\n * exact text that was dismissed so reconfiguring the callout (a\n * different message) bypasses the prior dismissal \u2014 a NEW message is\n * a new ask, not a duplicate of the dismissed one.\n *\n * `currentText` is the callout text the widget intends to show right now.\n * Returns `true` only when the persisted \"last dismissed\" text matches.\n * `null`/empty `currentText` returns `false` (nothing to suppress).\n */\n loadCalloutDismissed(currentText: string | undefined): boolean;\n /**\n * Mark `currentText` as dismissed. Subsequent loads with the SAME text\n * suppress; loads with a different text show the callout again.\n */\n saveCalloutDismissed(currentText: string): void;\n /**\n * Page-mode-only: sidebar collapsed state. `null` means the user has\n * never explicitly toggled it \u2014 caller falls back to a default\n * (expanded). Per-device, like {@link loadPanelOpen}.\n */\n loadSidebarCollapsed(): boolean | null;\n saveSidebarCollapsed(collapsed: boolean): void;\n /**\n * Messenger-modules: the last active tab (its module `id`). `null` means the\n * user has never switched tabs \u2014 caller falls back to the first module.\n * Per-device, like {@link loadPanelOpen}; the caller is responsible for\n * ignoring a stored id that is no longer present.\n */\n loadActiveModule(): string | null;\n saveActiveModule(id: string): void;\n /**\n * Forget the active conversation. visitorId + settings + panelOpen are\n * deliberately preserved \u2014 clearing chat thread state is not the same\n * as forgetting who the user is or what they prefer.\n */\n clearSession(): void;\n}\n\n/**\n * Build a persistence bundle scoped to a widgetId. Calling twice with the\n * same id yields independent closures aimed at the same storage slots\n * \u2014 safe, just redundant.\n *\n * Pass a custom {@link ClientStorage} to add encryption, change backing\n * store, or hook reads + writes for analytics. Defaults to\n * `LocalStorageAdapter` (wraps `localStorage`).\n */\nexport function createPersistence(widgetId: string, storage: ClientStorage = defaultStorage): Persistence {\n const prefix = `${BRAND.cssPrefix}.${widgetId}`;\n const KEY_VISITOR = `${prefix}.visitorId`;\n const KEY_CHAT = `${prefix}.sessionId`;\n const KEY_USER_PREFS = `${prefix}.userPrefs`;\n const KEY_PANEL_OPEN = `${prefix}.panelOpen`;\n const KEY_PANEL_SIZE = `${prefix}.panelSize`;\n const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;\n const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;\n const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;\n\n const persistence: Persistence = {\n getVisitorId(): string {\n const existing = storage.get(KEY_VISITOR);\n if (existing) return existing;\n const minted = uuid7();\n storage.set(KEY_VISITOR, minted);\n return minted;\n },\n setVisitorId(id: string): void {\n log.debug(\"setVisitorId (rebind)\", { id, widgetId });\n storage.set(KEY_VISITOR, id);\n },\n loadSessionId(): string | undefined {\n return storage.get(KEY_CHAT) ?? undefined;\n },\n saveSessionId(id: string | undefined): void {\n log.trace(\"saveSessionId\", { id, widgetId });\n if (id) storage.set(KEY_CHAT, id);\n else storage.remove(KEY_CHAT);\n },\n loadUserPrefs(): UserPrefs {\n const raw = storage.get(KEY_USER_PREFS);\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return isPlainObject(parsed) ? (parsed as UserPrefs) : {};\n } catch (error) {\n log.warn(\"loadUserPrefs parse failed\", { error });\n return {};\n }\n },\n saveUserPrefs(s: UserPrefs): void {\n try {\n storage.set(KEY_USER_PREFS, JSON.stringify(s));\n } catch (error) {\n log.warn(\"saveUserPrefs serialise failed\", { error });\n }\n },\n patchUserPrefs(patch: Partial<UserPrefs>): void {\n const current = persistence.loadUserPrefs();\n persistence.saveUserPrefs({ ...current, ...patch });\n },\n loadPanelOpen(): boolean | null {\n const raw = storage.get(KEY_PANEL_OPEN);\n if (raw === \"1\") return true;\n if (raw === \"0\") return false;\n return null;\n },\n savePanelOpen(open: boolean): void {\n storage.set(KEY_PANEL_OPEN, open ? \"1\" : \"0\");\n },\n loadPanelSize(): PanelSizeTier | null {\n const raw = storage.get(KEY_PANEL_SIZE);\n return raw === \"normal\" || raw === \"expanded\" || raw === \"fullscreen\" ? raw : null;\n },\n savePanelSize(size: PanelSizeTier): void {\n storage.set(KEY_PANEL_SIZE, size);\n },\n loadCalloutDismissed(currentText: string | undefined): boolean {\n if (!currentText) return false;\n const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);\n return !!dismissedText && dismissedText === currentText;\n },\n saveCalloutDismissed(currentText: string): void {\n if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);\n else storage.remove(KEY_CALLOUT_DISMISSED);\n },\n loadSidebarCollapsed(): boolean | null {\n const raw = storage.get(KEY_SIDEBAR_COLLAPSED);\n if (raw === \"1\") return true;\n if (raw === \"0\") return false;\n return null;\n },\n saveSidebarCollapsed(collapsed: boolean): void {\n storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? \"1\" : \"0\");\n },\n loadActiveModule(): string | null {\n return storage.get(KEY_ACTIVE_MODULE) || null;\n },\n saveActiveModule(id: string): void {\n storage.set(KEY_ACTIVE_MODULE, id);\n },\n clearSession(): void {\n storage.remove(KEY_CHAT);\n // visitorId + userPrefs preserved \u2014 they're identity / preference,\n // not conversation state. Users expect their language and panel size\n // to survive a \"new conversation\" click.\n },\n };\n\n return persistence;\n}\n\n/**\n * Defensive parse guard \u2014 `JSON.parse('\"oops\"')` would otherwise hand back\n * a string and the spread in `patchUserPrefs` would silently produce\n * something nonsense. We only accept object-shaped settings.\n */\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n", "/**\n * Tiny Web Audio wrapper for synthesised notification tones.\n *\n * The `AudioContext` is created lazily on the first user gesture (browser\n * autoplay policy). Each play creates a fresh `OscillatorNode` chained\n * through a gain envelope \u2014 nodes are cheap; reusing them risks\n * `InvalidStateError` from the double-start() guard.\n *\n * Also supports decoded URL audio: the first `play(url)` fetches + decodes\n * into a shared `AudioBuffer` cache, then plays via `BufferSourceNode`.\n */\n\nimport { logger } from \"@/core/logger\";\nimport type { ToneSpec } from \"./defaults\";\n\nconst log = logger.scope(\"feedback:audio\");\n\ninterface MaybeWindow {\n AudioContext?: typeof AudioContext;\n webkitAudioContext?: typeof AudioContext;\n}\n\nexport class AudioEngine {\n private ctx: AudioContext | null = null;\n private master: GainNode | null = null;\n private readonly buffers = new Map<string, AudioBuffer | Promise<AudioBuffer | null>>();\n\n /** Lazily create the AudioContext. Safe to call on every user gesture. */\n unlock(): void {\n if (this.ctx) {\n // Context may have been auto-suspended; resume on user gesture.\n if (this.ctx.state === \"suspended\") void this.ctx.resume();\n return;\n }\n const Ctor = (globalThis as MaybeWindow).AudioContext ?? (globalThis as MaybeWindow).webkitAudioContext;\n if (!Ctor) return;\n try {\n this.ctx = new Ctor();\n this.master = this.ctx.createGain();\n this.master.gain.value = 1;\n this.master.connect(this.ctx.destination);\n } catch (err) {\n log.warn(\"AudioContext init failed\", { err });\n }\n }\n\n /** Route all sounds through this multiplier. Call on volume change. */\n setVolume(volume: number): void {\n if (!this.master || !this.ctx) return;\n // Equal-power taper on the low end feels more natural than linear.\n const clamped = Math.max(0, Math.min(1, volume));\n this.master.gain.setTargetAtTime(clamped, this.ctx.currentTime, 0.01);\n }\n\n /** Play a synthesised tone spec. Silently noops if context isn't live. */\n playTone(spec: ToneSpec): void {\n const ctx = this.ctx;\n const master = this.master;\n if (!ctx || !master || ctx.state !== \"running\") return;\n const now = ctx.currentTime;\n const noteSec = spec.noteMs / 1000;\n spec.notes.forEach((freq, i) => {\n const osc = ctx.createOscillator();\n osc.type = spec.type;\n osc.frequency.value = freq;\n\n // Per-note envelope: 5ms attack, linear decay to silent over the note.\n const env = ctx.createGain();\n const start = now + i * noteSec;\n env.gain.setValueAtTime(0.0001, start);\n env.gain.exponentialRampToValueAtTime(spec.peakGain, start + 0.005);\n env.gain.exponentialRampToValueAtTime(0.0001, start + noteSec);\n\n osc.connect(env).connect(master);\n osc.start(start);\n osc.stop(start + noteSec + 0.01);\n });\n }\n\n /** Play a URL-backed sample. First call decodes; subsequent calls are cached. */\n async playUrl(url: string): Promise<void> {\n const ctx = this.ctx;\n const master = this.master;\n if (!ctx || !master || ctx.state !== \"running\") return;\n const buf = await this.getBuffer(url);\n if (!buf) return;\n const src = ctx.createBufferSource();\n src.buffer = buf;\n src.connect(master);\n src.start();\n }\n\n destroy(): void {\n if (this.ctx) void this.ctx.close().catch(() => undefined);\n this.ctx = null;\n this.master = null;\n this.buffers.clear();\n }\n\n private async getBuffer(url: string): Promise<AudioBuffer | null> {\n if (!this.ctx) return null;\n const cached = this.buffers.get(url);\n if (cached) return cached instanceof Promise ? cached : cached;\n const ctx = this.ctx;\n const promise = fetch(url)\n .then((r) => (r.ok ? r.arrayBuffer() : Promise.reject(new Error(`fetch ${r.status}`))))\n .then((ab) => ctx.decodeAudioData(ab))\n .then((buf) => {\n this.buffers.set(url, buf);\n return buf;\n })\n .catch((err) => {\n log.warn(\"audio fetch/decode failed\", { url, err });\n this.buffers.delete(url);\n return null;\n });\n this.buffers.set(url, promise);\n return promise;\n }\n}\n", "/**\n * Per-event default tones + vibration patterns.\n *\n * Tones are described declaratively (frequencies + envelope timing) and\n * synthesised on demand via OscillatorNode in `audio.ts`. Shipping no\n * audio asset keeps the bundle tiny and avoids CDN coupling.\n */\n\nimport type { FeedbackEvent } from \"@/core/config/types\";\n\nexport interface ToneSpec {\n /** One or more frequencies (Hz). Each plays as a note in sequence. */\n notes: readonly number[];\n /** Per-note duration (ms). Total tone length = notes.length * noteMs. */\n noteMs: number;\n /** Oscillator waveform. Sine is softest; triangle/square are brighter. */\n type: OscillatorType;\n /** Peak gain (0..1). Scaled further by the user's `volume` setting. */\n peakGain: number;\n}\n\n/** Vibration patterns per event (Android `navigator.vibrate` shape). */\nexport const DEFAULT_HAPTIC_PATTERNS: Record<FeedbackEvent, number | number[]> = {\n messageReceived: 10,\n messageSent: 0, // no haptic on send (the user initiated it)\n error: [20, 40, 20],\n voiceStart: 10,\n voiceStop: 0,\n};\n\n/** Tones keyed by event. Designed to be subtle + distinguishable. */\nexport const DEFAULT_TONES: Record<FeedbackEvent, ToneSpec | null> = {\n // Soft two-note ascending chime \u2014 \"new message\" pattern iMessage + Slack use.\n messageReceived: { notes: [880, 1318.51], noteMs: 90, type: \"sine\", peakGain: 0.45 },\n // Single high soft tick \u2014 confirms the send.\n messageSent: { notes: [1568], noteMs: 60, type: \"sine\", peakGain: 0.25 },\n // Descending two-note \u2014 warning feel.\n error: { notes: [523.25, 392], noteMs: 120, type: \"triangle\", peakGain: 0.55 },\n // Tick-up for mic on.\n voiceStart: { notes: [1318.51], noteMs: 50, type: \"sine\", peakGain: 0.3 },\n // Tick-down for mic off.\n voiceStop: { notes: [880], noteMs: 50, type: \"sine\", peakGain: 0.3 },\n};\n", "/**\n * Haptic feedback.\n *\n * Two paths:\n * - Android / any browser exposing `navigator.vibrate` \u2192 pass the pattern through.\n * - iOS Safari 17.4+ \u2192 fabricate an invisible `<input type=\"checkbox\" switch>`\n * + `<label>`, programmatically `click()` the label. WebKit emits a native\n * system haptic. It's one pattern (no customisation) but it's better than\n * nothing on iOS \u2014 the standard Vibration API still isn't shipped.\n *\n * Everything else (older Safari, desktop without vibrators) silently noops.\n */\n\nimport { BRAND } from \"@/core/brand\";\n\ntype SwitchEl = { label: HTMLLabelElement; input: HTMLInputElement };\n\n/**\n * Module-level cache of the hidden `<input type=\"checkbox\" switch>` +\n * `<label>` used for the iOS 17.4+ haptic hack. Ref-counted so destroying\n * one widget doesn't rip the DOM out from under its siblings:\n * `acquireHaptics()` bumps, `releaseHaptics()` decrements, and we only\n * actually remove the nodes when the count hits zero.\n */\nlet switchEl: SwitchEl | null = null;\nlet refCount = 0;\n\nfunction canVibrate(): boolean {\n return typeof navigator !== \"undefined\" && typeof navigator.vibrate === \"function\";\n}\n\nfunction createSwitch(): SwitchEl | null {\n if (typeof document === \"undefined\") return null;\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.setAttribute(\"switch\", \"\");\n input.setAttribute(\"aria-hidden\", \"true\");\n input.tabIndex = -1;\n // ID is brand-prefixed for grep-ability (multi-brand repo). Random suffix\n // keeps it unique across co-mounted widgets sharing a document.\n input.id = `__${BRAND.cssPrefix}_haptic_${Math.random().toString(36).slice(2)}`;\n const label = document.createElement(\"label\");\n label.htmlFor = input.id;\n label.setAttribute(\"aria-hidden\", \"true\");\n // Off-screen but still clickable.\n const hideCss =\n \"position:absolute!important;width:1px!important;height:1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;opacity:0!important;pointer-events:none!important;\";\n input.style.cssText = hideCss;\n label.style.cssText = hideCss;\n document.body.append(input, label);\n return { input, label };\n}\n\n/**\n * Fire a haptic. `pattern` is the `navigator.vibrate` shape \u2014 a single ms\n * duration or an alternating `[vibrate, pause, vibrate, \u2026]` array. On iOS\n * the pattern is ignored (one system tick is all you get).\n */\nexport function haptic(pattern: number | number[]): void {\n // Skip \"no haptic\" sentinel.\n if (pattern === 0 || (Array.isArray(pattern) && pattern.every((n) => n === 0))) return;\n\n if (canVibrate()) {\n try {\n navigator.vibrate(pattern);\n return;\n } catch {\n // Fall through to iOS hack on browsers that partially implement.\n }\n }\n\n // Lazy-create the iOS switch on first haptic demand.\n if (!switchEl) switchEl = createSwitch();\n if (!switchEl) return;\n try {\n switchEl.label.click();\n } catch {\n // No-op.\n }\n}\n\n/**\n * Register a user of the haptics module. Must be paired with\n * `releaseHaptics()` on destroy. Returns an idempotent release function \u2014\n * calling it twice only decrements once.\n */\nexport function acquireHaptics(): () => void {\n refCount++;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n refCount = Math.max(0, refCount - 1);\n if (refCount === 0 && switchEl) {\n switchEl.input.remove();\n switchEl.label.remove();\n switchEl = null;\n }\n };\n}\n\n/** Test-only: force-reset the cache between cases. Not part of the public API. */\nexport function disposeHaptics(): void {\n if (switchEl) {\n switchEl.input.remove();\n switchEl.label.remove();\n }\n switchEl = null;\n refCount = 0;\n}\n", "/**\n * Public entry for the feedback module \u2014 audio + haptics in one surface.\n *\n * ```ts\n * const fb = new FeedbackBus(resolvedOptions.sound, resolvedOptions.haptics);\n * hostElement.addEventListener(\"click\", () => fb.unlock(), { once: true });\n * fb.play(\"messageReceived\"); // plays tone + vibrates\n * fb.setMuted(true); // killswitch\n * ```\n *\n * The bus is **stateless about persistence** \u2014 `mutedSig` is a plain\n * reactive signal. The App layer subscribes to it and persists changes\n * via the unified `Persistence` layer (`userPrefs.soundMuted`),\n * pushing them to the backend at the same time.\n */\n\nimport type { FeedbackEvent, HapticsOptions, SoundOptions } from \"@/core/config/types\";\nimport { logger } from \"@/core/logger\";\nimport { signal, type Signal } from \"@preact/signals\";\nimport { AudioEngine } from \"./audio\";\nimport { DEFAULT_HAPTIC_PATTERNS, DEFAULT_TONES } from \"./defaults\";\n\nconst log = logger.scope(\"feedback\");\nimport { acquireHaptics, haptic } from \"./haptics\";\n\nconst DEDUP_WINDOW_MS = 100;\n\nexport class FeedbackBus {\n /**\n * User/session mute state. Reactive \u2014 UI subscribes to drive the toggle\n * icon, and the App layer subscribes to persist changes via the unified\n * `Persistence` layer.\n */\n readonly mutedSig: Signal<boolean> = signal(false);\n\n private readonly audio = new AudioEngine();\n private readonly releaseHaptics: () => void;\n private sound: SoundOptions;\n private haptics: HapticsOptions;\n private lastPlayed = new Map<FeedbackEvent, number>();\n private reducedMotionMq: MediaQueryList | null = null;\n\n constructor(sound: SoundOptions, haptics: HapticsOptions) {\n this.sound = sound;\n this.haptics = haptics;\n // Ref-count the iOS haptic switch so a sibling widget surviving past\n // this one's destroy() still has its switch DOM available.\n this.releaseHaptics = acquireHaptics();\n this.audio.setVolume(this.effectiveVolume());\n if (typeof matchMedia !== \"undefined\") {\n this.reducedMotionMq = matchMedia(\"(prefers-reduced-motion: reduce)\");\n }\n }\n\n /**\n * Unlock the AudioContext. Must be called from inside a user gesture\n * (click, keydown, touchstart) \u2014 browsers reject any earlier creation.\n * Safe to call repeatedly.\n */\n unlock(): void {\n this.audio.unlock();\n this.audio.setVolume(this.effectiveVolume());\n }\n\n /** Update config without re-constructing. Called on server-settings merge. */\n update(sound: SoundOptions, haptics: HapticsOptions): void {\n this.sound = sound;\n this.haptics = haptics;\n this.audio.setVolume(this.effectiveVolume());\n }\n\n /**\n * Set the mute state explicitly. Used by the App on mount (to apply the\n * cached `userPrefs.soundMuted`) and after the server pushes a\n * different value via `connect()`. No-op when value is unchanged so\n * subscribers don't double-fire.\n */\n setMuted(muted: boolean): void {\n if (this.mutedSig.value !== muted) this.mutedSig.value = muted;\n }\n\n /**\n * Toggle mute. Persistence is handled by the App's `mutedSig.subscribe`\n * callback \u2014 this method just flips the signal.\n */\n toggleMuted(): void {\n this.mutedSig.value = !this.mutedSig.value;\n }\n\n /** Fire sound + haptic for an event. Respects all guards. */\n play(event: FeedbackEvent): void {\n if (this.mutedSig.value) return;\n if (this.isDeduped(event)) return;\n this.lastPlayed.set(event, performance.now());\n\n if (this.sound.enabled) this.playSound(event);\n if (this.haptics.enabled && this.isTouchDevice()) this.playHaptic(event);\n }\n\n /** Release the audio context + iOS hack DOM. Call on widget unmount. */\n destroy(): void {\n this.audio.destroy();\n this.releaseHaptics();\n }\n\n // ---- internals --------------------------------------------------------\n\n private isDeduped(event: FeedbackEvent): boolean {\n const prev = this.lastPlayed.get(event);\n if (prev === undefined) return false;\n return performance.now() - prev < DEDUP_WINDOW_MS;\n }\n\n private playSound(event: FeedbackEvent): void {\n const override = this.sound.events?.[event];\n if (override === false) return;\n if (typeof override === \"string\") {\n this.audio.playUrl(override).catch((err) => log.warn(\"audio play failed\", { err, url: override }));\n return;\n }\n const tone = DEFAULT_TONES[event];\n if (!tone) return;\n this.audio.playTone(tone);\n }\n\n private playHaptic(event: FeedbackEvent): void {\n // prefers-reduced-motion \u2192 disable haptics entirely. They're a form of\n // motion feedback and can trigger vestibular discomfort for some users.\n if (this.reducedMotionMq?.matches) return;\n const override = this.haptics.events?.[event];\n if (override === false) return;\n const pattern = override === true || override === undefined ? DEFAULT_HAPTIC_PATTERNS[event] : override;\n haptic(pattern);\n }\n\n /** prefers-reduced-motion halves the volume as a \"quiet please\" signal. */\n private effectiveVolume(): number {\n const base = this.sound.volume ?? 0.3;\n return this.reducedMotionMq?.matches ? base * 0.5 : base;\n }\n\n private isTouchDevice(): boolean {\n if (typeof window === \"undefined\") return false;\n return matchMedia(\"(hover: none) and (pointer: coarse)\").matches;\n }\n}\n", "/**\n * User-facing message for a caught request error \u2014 friendly + status-aware.\n * Never leaks raw `\"server responded 429\u2026\"` text. Shared by the chat error\n * bubble and the content surfaces (help / news) so every \"request failed\" path\n * speaks the same language.\n */\n\nimport type { Strings } from \"@/core/config\";\nimport { StreamError } from \"@/stream/types\";\n\nexport function errorMessageFor(error: unknown, strings: Strings): string {\n if (error instanceof StreamError && error.status === 429) return strings.errorRateLimited;\n return strings.errorGeneric;\n}\n", "const ENCODED_ENTITIES = /[\"&<]/;\n\n/** @param {string} str */\nexport function encodeEntities(str) {\n\t// Skip all work for strings with no entities needing encoding:\n\tif (str.length === 0 || ENCODED_ENTITIES.test(str) === false) return str;\n\n\tlet last = 0,\n\t\ti = 0,\n\t\tout = '',\n\t\tch = '';\n\n\t// Seek forward in str until the next entity char:\n\tfor (; i < str.length; i++) {\n\t\tswitch (str.charCodeAt(i)) {\n\t\t\tcase 34:\n\t\t\t\tch = '"';\n\t\t\t\tbreak;\n\t\t\tcase 38:\n\t\t\t\tch = '&';\n\t\t\t\tbreak;\n\t\t\tcase 60:\n\t\t\t\tch = '<';\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tcontinue;\n\t\t}\n\t\t// Append skipped/buffered characters and the encoded entity:\n\t\tif (i !== last) out += str.slice(last, i);\n\t\tout += ch;\n\t\t// Start the next seek/buffer after the entity's offset:\n\t\tlast = i + 1;\n\t}\n\tif (i !== last) out += str.slice(last, i);\n\treturn out;\n}\n", "/** Normal hydration that attaches to a DOM tree but does not diff it. */\nexport const MODE_HYDRATE = 1 << 5;\n/** Signifies this VNode suspended on the previous render */\nexport const MODE_SUSPENDED = 1 << 7;\n/** Indicates that this node needs to be inserted while patching children */\nexport const INSERT_VNODE = 1 << 2;\n/** Indicates a VNode has been matched with another VNode in the diff */\nexport const MATCHED = 1 << 1;\n\n/** Reset all mode flags */\nexport const RESET_MODE = ~(MODE_HYDRATE | MODE_SUSPENDED);\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\nexport const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\nexport const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n\nexport const NULL = null;\nexport const UNDEFINED = undefined;\nexport const EMPTY_OBJ = /** @type {any} */ ({});\nexport const EMPTY_ARR = [];\nexport const IS_NON_DIMENSIONAL =\n\t/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;\n", "import { options, Fragment } from 'preact';\nimport { encodeEntities } from './utils';\nimport { IS_NON_DIMENSIONAL } from '../../src/constants';\n\nlet vnodeId = 0;\n\nconst isArray = Array.isArray;\n\n/**\n * @fileoverview\n * This file exports various methods that implement Babel's \"automatic\" JSX runtime API:\n * - jsx(type, props, key)\n * - jsxs(type, props, key)\n * - jsxDEV(type, props, key, __source, __self)\n *\n * The implementation of createVNode here is optimized for performance.\n * Benchmarks: https://esbench.com/bench/5f6b54a0b4632100a7dcd2b3\n */\n\n/**\n * JSX.Element factory used by Babel's {runtime:\"automatic\"} JSX transform\n * @param {VNode['type']} type\n * @param {VNode['props']} props\n * @param {VNode['key']} [key]\n * @param {unknown} [isStaticChildren]\n * @param {unknown} [__source]\n * @param {unknown} [__self]\n */\nfunction createVNode(type, props, key, isStaticChildren, __source, __self) {\n\tif (!props) props = {};\n\t// We'll want to preserve `ref` in props to get rid of the need for\n\t// forwardRef components in the future, but that should happen via\n\t// a separate PR.\n\tlet normalizedProps = props,\n\t\tref,\n\t\ti;\n\n\tif ('ref' in normalizedProps) {\n\t\tnormalizedProps = {};\n\t\tfor (i in props) {\n\t\t\tif (i == 'ref') {\n\t\t\t\tref = props[i];\n\t\t\t} else {\n\t\t\t\tnormalizedProps[i] = props[i];\n\t\t\t}\n\t\t}\n\t}\n\n\t/** @type {VNode & { __source: any; __self: any }} */\n\tconst vnode = {\n\t\ttype,\n\t\tprops: normalizedProps,\n\t\tkey,\n\t\tref,\n\t\t_children: null,\n\t\t_parent: null,\n\t\t_depth: 0,\n\t\t_dom: null,\n\t\t_component: null,\n\t\tconstructor: undefined,\n\t\t_original: --vnodeId,\n\t\t_index: -1,\n\t\t_flags: 0,\n\t\t__source,\n\t\t__self\n\t};\n\n\t// If a Component VNode, check for and apply defaultProps.\n\t// Note: `type` is often a String, and can be `undefined` in development.\n\tif (typeof type === 'function' && (ref = type.defaultProps)) {\n\t\tfor (i in ref)\n\t\t\tif (normalizedProps[i] === undefined) {\n\t\t\t\tnormalizedProps[i] = ref[i];\n\t\t\t}\n\t}\n\n\tif (options.vnode) options.vnode(vnode);\n\treturn vnode;\n}\n\n/**\n * Create a template vnode. This function is not expected to be\n * used directly, but rather through a precompile JSX transform\n * @param {string[]} templates\n * @param {Array<string | null | VNode>} exprs\n * @returns {VNode}\n */\nfunction jsxTemplate(templates, ...exprs) {\n\tconst vnode = createVNode(Fragment, { tpl: templates, exprs });\n\t// Bypass render to string top level Fragment optimization\n\tvnode.key = vnode._vnode;\n\treturn vnode;\n}\n\nconst JS_TO_CSS = {};\nconst CSS_REGEX = /[A-Z]/g;\n\n/**\n * Unwrap potential signals.\n * @param {*} value\n * @returns {*}\n */\nfunction normalizeAttrValue(value) {\n\treturn value !== null &&\n\t\ttypeof value === 'object' &&\n\t\ttypeof value.valueOf === 'function'\n\t\t? value.valueOf()\n\t\t: value;\n}\n\n/**\n * Serialize an HTML attribute to a string. This function is not\n * expected to be used directly, but rather through a precompile\n * JSX transform\n * @param {string} name The attribute name\n * @param {*} value The attribute value\n * @returns {string}\n */\nfunction jsxAttr(name, value) {\n\tif (options.attr) {\n\t\tconst result = options.attr(name, value);\n\t\tif (typeof result === 'string') return result;\n\t}\n\n\tvalue = normalizeAttrValue(value);\n\n\tif (name === 'ref' || name === 'key') return '';\n\tif (name === 'style' && typeof value === 'object') {\n\t\tlet str = '';\n\t\tfor (let prop in value) {\n\t\t\tlet val = value[prop];\n\t\t\tif (val != null && val !== '') {\n\t\t\t\tconst name =\n\t\t\t\t\tprop[0] == '-'\n\t\t\t\t\t\t? prop\n\t\t\t\t\t\t: JS_TO_CSS[prop] ||\n\t\t\t\t\t\t\t(JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());\n\n\t\t\t\tlet suffix = ';';\n\t\t\t\tif (\n\t\t\t\t\ttypeof val === 'number' &&\n\t\t\t\t\t// Exclude custom-attributes\n\t\t\t\t\t!name.startsWith('--') &&\n\t\t\t\t\t!IS_NON_DIMENSIONAL.test(name)\n\t\t\t\t) {\n\t\t\t\t\tsuffix = 'px;';\n\t\t\t\t}\n\t\t\t\tstr = str + name + ':' + val + suffix;\n\t\t\t}\n\t\t}\n\t\treturn name + '=\"' + encodeEntities(str) + '\"';\n\t}\n\n\tif (\n\t\tvalue == null ||\n\t\tvalue === false ||\n\t\ttypeof value === 'function' ||\n\t\ttypeof value === 'object'\n\t) {\n\t\treturn '';\n\t} else if (value === true) return name;\n\n\treturn name + '=\"' + encodeEntities('' + value) + '\"';\n}\n\n/**\n * Escape a dynamic child passed to `jsxTemplate`. This function\n * is not expected to be used directly, but rather through a\n * precompile JSX transform\n * @param {*} value\n * @returns {string | null | VNode | Array<string | null | VNode>}\n */\nfunction jsxEscape(value) {\n\tif (\n\t\tvalue == null ||\n\t\ttypeof value === 'boolean' ||\n\t\ttypeof value === 'function'\n\t) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === 'object') {\n\t\t// Check for VNode\n\t\tif (value.constructor === undefined) return value;\n\n\t\tif (isArray(value)) {\n\t\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\t\tvalue[i] = jsxEscape(value[i]);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t}\n\n\treturn encodeEntities('' + value);\n}\n\nexport {\n\tcreateVNode as jsx,\n\tcreateVNode as jsxs,\n\tcreateVNode as jsxDEV,\n\tFragment,\n\t// precompiled JSX transform\n\tjsxTemplate,\n\tjsxAttr,\n\tjsxEscape\n};\n", "// Inline SVG icons. Tiny, tree-shaken, no icon font load.\n// Each icon is a stateless functional component returning an <svg>.\n\nimport type { JSX } from \"preact\";\n\nconst props = {\n fill: \"none\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\" as const,\n strokeLinejoin: \"round\" as const,\n strokeWidth: 2,\n viewBox: \"0 0 24 24\",\n};\n\nexport const ChatIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M21 12a8 8 0 0 1-12 6.93L3 21l1.65-5.16A8 8 0 1 1 21 12Z\" />\n </svg>\n);\n\nexport const CloseIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n);\n\n/** Plus / \"new\" icon. */\nexport const PlusIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M12 5v14M5 12h14\" />\n </svg>\n);\n\n/** \"Paper-plane\" style send icon (triangular envelope). */\nexport const PaperPlaneIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m22 2-7 20-4-9-9-4Z\" />\n <path d=\"M22 2 11 13\" />\n </svg>\n);\n\n/** Vertical arrow pointing up \u2014 the iMessage / ChatGPT default. */\nexport const ArrowUpIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M12 19V5M5 12l7-7 7 7\" />\n </svg>\n);\n\n/** Chat-bubble send (right-pointing triangle inside a rounded square). */\nexport const SendIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 20V4l18 8-18 8Z\" />\n </svg>\n);\n\n/** Chevron-right \u2014 minimal, for \"continue\" / \"submit\" feel. */\nexport const ChevronRightIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n);\n\nexport const StopIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n </svg>\n);\n\nexport const PaperclipIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m21 11-9 9a5.5 5.5 0 0 1-7.78-7.78l8.49-8.49a3.67 3.67 0 0 1 5.19 5.19l-8.49 8.49a1.83 1.83 0 0 1-2.6-2.6l7.78-7.78\" />\n </svg>\n);\n\nexport const MicIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <rect x=\"9\" y=\"2\" width=\"6\" height=\"12\" rx=\"3\" />\n <path d=\"M5 10a7 7 0 0 0 14 0M12 19v3\" />\n </svg>\n);\n\nexport const ExpandIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 8V3h5M21 8V3h-5M3 16v5h5M21 16v5h-5\" />\n </svg>\n);\n\nexport const CollapseIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M8 3v5H3M16 3v5h5M8 21v-5H3M16 21v-5h5\" />\n </svg>\n);\n\nexport const FullscreenIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 3h7v2H5v5H3zM21 3h-7v2h5v5h2zM3 21h7v-2H5v-5H3zM21 21h-7v-2h5v-5h2z\" />\n </svg>\n);\n\nexport const FullscreenExitIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M10 3v5H3M14 3v5h7M10 21v-5H3M14 21v-5h7\" />\n </svg>\n);\n\nexport const ResizeHandleIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\" viewBox=\"0 0 10 10\">\n <path d=\"M9 1L1 9M9 5L5 9M9 9H9\" strokeWidth=\"1.5\" />\n </svg>\n);\n\nexport const MoreIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"5\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"12\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"12\" cy=\"19\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n);\n\nexport const SpeakerIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M11 5 6 9H2v6h4l5 4zM15 9a5 5 0 0 1 0 6M18 7a9 9 0 0 1 0 10\" />\n </svg>\n);\n\nexport const SpeakerMutedIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M11 5 6 9H2v6h4l5 4zM22 9l-6 6M16 9l6 6\" />\n </svg>\n);\n\nexport const CheckIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n);\n\nexport const HistoryIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 3v6h6\" />\n <path d=\"M3.05 13A9 9 0 1 0 6 5.3L3 9\" />\n <path d=\"M12 7v5l3 2\" />\n </svg>\n);\n\nexport const GlobeIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M2 12h20\" />\n <path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\" />\n </svg>\n);\n\nexport const BackIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M19 12H5M12 19l-7-7 7-7\" />\n </svg>\n);\n\nexport const PopOutIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M15 3h6v6M10 14 21 3M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5\" />\n </svg>\n);\n\nexport const TrashIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" />\n </svg>\n);\n\nexport const FileIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z\" />\n <path d=\"M14 2v6h6\" />\n </svg>\n);\n\nexport const SparkIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m12 3-1.5 4.5L6 9l4.5 1.5L12 15l1.5-4.5L18 9l-4.5-1.5Z\" />\n <path d=\"M19 15v4M17 17h4M5 3v2M4 4h2\" />\n </svg>\n);\n\nexport const MessageIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z\" />\n </svg>\n);\n\nexport const HelpIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\" />\n <path d=\"M12 17h.01\" />\n </svg>\n);\n\nexport const WandIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m15 4 3 3-9 9-3-3z\" />\n <path d=\"M18 2v2M20 4h2M3 20l3-3M3 16h2M5 14v2\" />\n </svg>\n);\n\n/** Friendly robot face \u2014 the \"AI assistant\" preset. */\nexport const BotIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <rect x=\"4\" y=\"8\" width=\"16\" height=\"12\" rx=\"2\" />\n <path d=\"M12 8V4\" />\n <circle cx=\"12\" cy=\"3\" r=\"1\" />\n <path d=\"M2 14h2M20 14h2M9 13h.01M15 13h.01M9.5 17h5\" />\n </svg>\n);\n\n/** Support headset with mic boom \u2014 the \"live support / talk to a human\" preset. */\nexport const HeadsetIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M4 14v-2a8 8 0 0 1 16 0v2\" />\n <rect x=\"2\" y=\"14\" width=\"4\" height=\"6\" rx=\"1.5\" />\n <rect x=\"18\" y=\"14\" width=\"4\" height=\"6\" rx=\"1.5\" />\n <path d=\"M20 20v1a3 3 0 0 1-3 3h-5\" />\n </svg>\n);\n\n/** Life-ring \u2014 the \"help / rescue\" preset. */\nexport const LifebuoyIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"m4.93 4.93 4.24 4.24M14.83 14.83l4.24 4.24M14.83 9.17l4.24-4.24M4.93 19.07l4.24-4.24\" />\n </svg>\n);\n\n/** Magnifier \u2014 search bars (Home + Help). */\nexport const SearchIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n);\n\n/** Green check in a circle \u2014 Home status card \"operational\". */\nexport const StatusOkIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"m8.5 12 2.5 2.5 4.5-5\" />\n </svg>\n);\n\n/** House \u2014 the Home tab in the messenger bottom nav. */\nexport const HomeIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M3 9.5 12 3l9 6.5\" />\n <path d=\"M5 9v11a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9\" />\n </svg>\n);\n\n/** Megaphone \u2014 the News tab in the messenger bottom nav. */\nexport const NewsIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"m3 11 15-5v12L3 13z\" />\n <path d=\"M3 11v2a2 2 0 0 0 2 2h1V9H5a2 2 0 0 0-2 2z\" />\n <path d=\"M7 15v3a2 2 0 0 0 4 0v-2\" />\n <path d=\"M21 10a3 3 0 0 1 0 4\" />\n </svg>\n);\n\n/** Two overlapping speech bubbles \u2014 the \"conversation / chat\" preset. */\nexport const BubblesIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M14 9a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z\" />\n <path d=\"M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1\" />\n </svg>\n);\n\nexport const SunIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41\" />\n </svg>\n);\n\nexport const MoonIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n);\n\n/** Trigger for the theme switcher \u2014 half-sun/half-moon hints at \"system\" (auto). */\nexport const ThemeAutoIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 3v18\" />\n <path d=\"M12 3a9 9 0 0 1 0 18z\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n);\n\n/** Magnifier with a smile \u2014 friendly \"search / ask\" launcher. */\nexport const SearchSmileIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\" />\n <path d=\"M8 11a2.5 2.5 0 0 0 5 0\" />\n <path d=\"m20 20-4.4-4.4\" />\n </svg>\n);\n\n/** Multi-point sparkles \u2014 an \"AI / magic\" launcher. */\nexport const SparklesIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M12 3.5 13.6 9 19 10.5 13.6 12 12 17.5 10.4 12 5 10.5 10.4 9z\" />\n <path d=\"M18.5 4v3M20 5.5h-3M5.5 16v2.5M6.75 17.25h-2.5\" />\n </svg>\n);\n\n/** Concierge bell \u2014 a \"support / front-desk\" launcher. */\nexport const ConciergeIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M4 18h16\" />\n <path d=\"M5 18a7 7 0 0 1 14 0\" />\n <path d=\"M12 8V6\" />\n <path d=\"M10 6h4\" />\n </svg>\n);\n\n/** Waving hand \u2014 a warm \"say hi\" launcher. */\nexport const WaveIcon = (): JSX.Element => (\n <svg {...props} aria-hidden=\"true\">\n <path d=\"M7 11V6.5a1.5 1.5 0 0 1 3 0V10\" />\n <path d=\"M10 10V5.5a1.5 1.5 0 0 1 3 0V10\" />\n <path d=\"M13 10V6.5a1.5 1.5 0 0 1 3 0V12\" />\n <path d=\"M16 9.5a1.5 1.5 0 0 1 3 0v2.5a7 7 0 0 1-7 7 7 7 0 0 1-6.4-4.2L4 12.5a1.5 1.5 0 0 1 2.6-1.5L7 11\" />\n </svg>\n);\n\n/**\n * Map a preset name to its icon component. Consumed by the launcher\n * (`launcher.icon`) \u2014 a name not listed here is treated as raw SVG.\n */\nexport const ICON_PRESETS: Record<string, () => JSX.Element> = {\n chat: ChatIcon,\n message: MessageIcon,\n bubbles: BubblesIcon,\n spark: SparkIcon,\n wand: WandIcon,\n bot: BotIcon,\n help: HelpIcon,\n lifebuoy: LifebuoyIcon,\n headset: HeadsetIcon,\n search: SearchSmileIcon,\n sparkles: SparklesIcon,\n concierge: ConciergeIcon,\n wave: WaveIcon,\n};\n", "/**\n * Test-id catalogue for every interactive element in the widget.\n *\n * Every entry is brand-prefixed at runtime via `BRAND.cssPrefix` so\n * `help-ai` and `go-ai` widgets co-hosted on the same page can be\n * targeted independently:\n *\n * document.querySelector(`[data-testid=\"${TID.composerSend}\"]`)\n *\n * For Playwright / RTL, pierce the shadow root:\n *\n * page.locator(`${chatHostSelector} >> [data-testid=\"${TID.composerSend}\"]`)\n *\n * Stable across releases \u2014 additions are append-only, renames are a\n * breaking change. Adding a new interactive element? Add an entry\n * here and attach `data-testid={TID.foo}` at the JSX site.\n */\n\nimport { BRAND } from \"@/core/brand\";\n\nconst p = BRAND.cssPrefix;\n\nexport const TID = {\n // \u2500\u2500 Launcher / callout \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\u2500\u2500\u2500\u2500\u2500\u2500\n /** Floating launcher FAB. */\n launcher: `${p}-launcher`,\n /** Pre-engagement callout bubble. */\n callout: `${p}-callout`,\n /** Callout close (\u00D7) button. */\n calloutDismiss: `${p}-callout-dismiss`,\n\n // \u2500\u2500 Panel chrome \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Panel root (the floating / drawer / modal card). */\n panel: `${p}-panel`,\n /** Panel header bar. */\n panelHeader: `${p}-panel-header`,\n /** Resize grip (corner handle). */\n resizeGrip: `${p}-resize-grip`,\n\n // \u2500\u2500 Header actions \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Header `clear` (new conversation) button. */\n actionClear: `${p}-action-clear`,\n /** Header `expand` (toggle 1.5\u00D7) button. */\n actionExpand: `${p}-action-expand`,\n /** Header `fullscreen` button. */\n actionFullscreen: `${p}-action-fullscreen`,\n /** Header `popOut` button. */\n actionPopOut: `${p}-action-popout`,\n /** Header `sound` (mute toggle) button. */\n actionSound: `${p}-action-sound`,\n /** Header `history` (show past conversations) button. */\n actionHistory: `${p}-action-history`,\n /** Header `language` (locale switcher) trigger. */\n actionLanguage: `${p}-action-language`,\n /** Header `theme` (auto/light/dark switcher) trigger. */\n actionTheme: `${p}-action-theme`,\n /** Header `close` (\u00D7) button. */\n actionClose: `${p}-action-close`,\n /** Header overflow (\u22EF) menu trigger. */\n actionOverflow: `${p}-action-overflow`,\n\n // \u2500\u2500 Language / theme switcher items \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 /** Language switcher: single locale option (suffix with locale tag at site). */\n languageOption: `${p}-language-option`,\n /** Theme switcher: single mode option (suffix with `-auto|-light|-dark`). */\n themeOption: `${p}-theme-option`,\n\n // \u2500\u2500 Composer \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Composer root container. */\n composer: `${p}-composer`,\n /** Composer textarea. */\n composerInput: `${p}-composer-input`,\n /** Composer send button. */\n composerSend: `${p}-composer-send`,\n /** Composer stop-streaming button (replaces send while streaming). */\n composerStop: `${p}-composer-stop`,\n /** Composer paperclip / attach button. */\n composerAttach: `${p}-composer-attach`,\n /** Composer hidden `<input type=file>` (the picker the paperclip opens). */\n composerFileInput: `${p}-composer-file-input`,\n /** Composer mic / voice-record button. */\n composerVoice: `${p}-composer-voice`,\n /** Composer attachment chip \u2014 suffix `-{id}` at the JSX site. */\n attachmentChip: `${p}-attachment-chip`,\n /** Attachment chip's remove (\u00D7) button. */\n attachmentRemove: `${p}-attachment-remove`,\n\n // \u2500\u2500 Suggestions \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Suggested-message chip \u2014 suffix `-{index}` at the JSX site. */\n suggestion: `${p}-suggestion`,\n\n // \u2500\u2500 Messages \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Message bubble \u2014 suffix `-{id}` at the JSX site. */\n messageBubble: `${p}-message`,\n /** \"Stop\" / retry inline button inside an error bubble. */\n messageRetry: `${p}-message-retry`,\n\n // \u2500\u2500 History pane \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** History pane root (list view inside the panel). */\n historyPane: `${p}-history-pane`,\n /** History pane back / close button. */\n historyBack: `${p}-history-back`,\n /** Single history list row \u2014 suffix `-{sessionId}` at the JSX site. */\n historyItem: `${p}-history-item`,\n\n // \u2500\u2500 Page-mode sidebar \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Page-mode sidebar (full-bleed left rail). */\n sidebar: `${p}-sidebar`,\n /** Sidebar collapse / expand toggle. */\n sidebarToggle: `${p}-sidebar-toggle`,\n /** Sidebar \"New chat\" button. */\n sidebarNewChat: `${p}-sidebar-new-chat`,\n /** Sidebar history row \u2014 suffix `-{sessionId}` at the JSX site. */\n sidebarItem: `${p}-sidebar-item`,\n\n // \u2500\u2500 Messenger modules \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Messenger-home surface root. */\n messengerHome: `${p}-messenger-home`,\n /** Empty state shown when no module is enabled. */\n modulesEmpty: `${p}-modules-empty`,\n /** Bottom-nav tab button \u2014 suffix `-{moduleId}` at the JSX site. */\n tab: `${p}-tab`,\n /** Unread badge on the Messages tab. */\n tabBadge: `${p}-tab-badge`,\n /** Home module root. */\n homeView: `${p}-home-view`,\n /** Home greeting headline. */\n homeGreeting: `${p}-home-greeting`,\n /** Home hero brand wordmark. */\n homeBrand: `${p}-home-brand`,\n /** Content reader: detail header, scroll container, article. */\n backHeader: `${p}-back-header`,\n contentView: `${p}-content-view`,\n contentScroll: `${p}-content-scroll`,\n /** Home / Help search bar. */\n homeSearch: `${p}-home-search`,\n helpSearch: `${p}-help-search`,\n /** Home recent-message card. */\n homeRecent: `${p}-home-recent`,\n /** Home content-list row (\"Popular articles\") \u2014 suffix `-{id}`. */\n homeContentRow: `${p}-home-content-row`,\n /** Help article row \u2014 suffix `-{id}`. */\n helpArticle: `${p}-help-article`,\n /** News list item \u2014 suffix `-{id}`. */\n newsItem: `${p}-news-item`,\n} as const;\n\nexport type TestId = (typeof TID)[keyof typeof TID];\n\n/** Suffix helper \u2014 keeps generated ids predictable across the codebase. */\nexport function tid(base: TestId, suffix: string | number): string {\n return `${base}-${suffix}`;\n}\n\n/**\n * Map a header `ActionName` to its test-id. The action-name strings\n * (`clear` / `expand` / \u2026) come from the configurator schema; this\n * table mirrors them so the JSX site can render `data-testid` with\n * a single lookup.\n */\nexport const ACTION_TID = {\n clear: TID.actionClear,\n expand: TID.actionExpand,\n fullscreen: TID.actionFullscreen,\n popOut: TID.actionPopOut,\n sound: TID.actionSound,\n history: TID.actionHistory,\n language: TID.actionLanguage,\n theme: TID.actionTheme,\n close: TID.actionClose,\n} as const;\n", "/**\n * Launcher (FAB) + the unified `<LauncherCallout>` bubble that sits\n * next to it (greeting / nudge / onboarding tip \u2014 one component,\n * configured per use case via `launcher.callout`).\n *\n * Both render only in floating modes when `options.launcher.hidden` is\n * false. Visuals are driven by data-attrs + CSS in `styles/panel.css`\n * \u2014 no inline styles here.\n */\n\nimport { useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { CalloutPosition, Position, ResolvedOptions } from \"@/core/config\";\nimport { CloseIcon, ICON_PRESETS } from \"./icons\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface LauncherProps {\n onToggle: () => void;\n launcher: ResolvedOptions[\"launcher\"];\n strings: ResolvedOptions[\"strings\"];\n /**\n * The panel just opened and the FAB is mid-exit. Triggers the\n * `__P__-fab-out` keyframe via the `data-leaving` attribute and disables\n * pointer events so a stray click during the fade can't double-fire\n * `onToggle`. The component itself unmounts shortly after.\n */\n leaving?: boolean;\n /**\n * Drawer mode: render the launcher as a vertical handle docked flush\n * to the screen edge (reusing the same launcher config) instead of a\n * corner FAB. Styling is driven by the `data-edge-tab` attribute in\n * `styles/panel.css`.\n */\n edgeTab?: boolean;\n}\n\n/**\n * Floating launcher FAB. Only rendered while the panel is *closed* \u2014 the\n * App swaps to `<Panel>` on open, which carries its own close button. In\n * drawer mode it docks to the edge as a vertical tab (`edgeTab`).\n */\nexport function Launcher({ onToggle, launcher, strings, leaving, edgeTab }: LauncherProps) {\n const showLabel = launcher.variant !== \"circle\" && !!launcher.label;\n\n return (\n <button\n type=\"button\"\n class={`${p}-fab`}\n data-variant={launcher.variant}\n data-size={launcher.size}\n data-edge-tab={edgeTab ? \"true\" : undefined}\n data-leaving={leaving ? \"true\" : undefined}\n data-testid={TID.launcher}\n onClick={onToggle}\n aria-label={strings.launcherOpen}\n >\n {renderIcon(launcher.icon)}\n {showLabel ? <span class={`${p}-fab-label`}>{launcher.label}</span> : null}\n </button>\n );\n}\n\nfunction renderIcon(icon: string) {\n const Preset = ICON_PRESETS[icon];\n if (Preset) return <Preset />;\n // Treat anything else as raw SVG source. Parsed via DOMParser so no\n // innerHTML is ever written to a live element.\n return <InlineSvg source={icon} />;\n}\n\nfunction InlineSvg({ source }: { source: string }) {\n return (\n <span\n ref={(el) => {\n if (el) mountRawSvg(el, source);\n }}\n aria-hidden=\"true\"\n />\n );\n}\n\nfunction mountRawSvg(host: HTMLElement, source: string): void {\n if (host.dataset.svgMounted === source) return;\n try {\n const doc = new DOMParser().parseFromString(source, \"image/svg+xml\");\n const root = doc.documentElement;\n if (root && root.tagName.toLowerCase() === \"svg\") {\n host.replaceChildren(document.importNode(root, true));\n host.dataset.svgMounted = source;\n }\n } catch {\n // Malformed SVG \u2014 leave the span empty.\n }\n}\n\n// ---- LauncherCallout ------------------------------------------------------\n\ninterface LauncherCalloutProps {\n callout: NonNullable<ResolvedOptions[\"launcher\"][\"callout\"]>;\n /** FAB corner \u2014 drives the auto-position fallback (callout anchors away from this corner). */\n launcherPosition: Position;\n closeLabel: string;\n /** Called when the user clicks X. App persists + sets dismissed state. */\n onDismiss: () => void;\n}\n\n/**\n * Pre-engagement message bubble next to the FAB. Used for greetings\n * (\"\uD83D\uDC4B Hi, need help?\"), nudges (\"Click here \u2192\"), and onboarding tips\n * \u2014 same component, configured for the use case.\n *\n * Rendering is gated by App: when `callout.persistent === false`,\n * App suppresses it after a prior dismissal of the same text;\n * `persistent === true` re-shows it on every visit.\n */\nexport function LauncherCallout({ callout, launcherPosition, closeLabel, onDismiss }: LauncherCalloutProps) {\n const [visible, setVisible] = useState(false);\n\n // Reveal after the configured delay (gives the page time to settle).\n useEffect(() => {\n const id = setTimeout(() => setVisible(true), callout.delayMs);\n return () => clearTimeout(id);\n }, [callout.delayMs]);\n\n // Auto-dismiss schedule (optional \u2014 `0` disables).\n useEffect(() => {\n if (!visible || callout.autoDismissMs <= 0) return;\n const id = setTimeout(onDismiss, callout.autoDismissMs);\n return () => clearTimeout(id);\n }, [visible, callout.autoDismissMs, onDismiss]);\n\n if (!visible) return null;\n\n return (\n <div\n class={`${p}-callout`}\n role=\"status\"\n data-shape={callout.shape}\n data-position={resolveCalloutPosition(callout.position, launcherPosition)}\n data-animated={callout.animated ? \"true\" : undefined}\n data-testid={TID.callout}\n >\n <span>{callout.text}</span>\n <button\n type=\"button\"\n class={`${p}-callout-close`}\n onClick={onDismiss}\n aria-label={closeLabel}\n title={closeLabel}\n data-testid={TID.calloutDismiss}\n >\n <CloseIcon />\n </button>\n </div>\n );\n}\n\n/**\n * Resolve a coarse `auto | horizontal | vertical` choice to a concrete\n * side (`left | right | top | bottom`) based on the FAB's corner.\n *\n * - `auto` \u2192 side facing INTO the page (bottom-right FAB \u2192 left).\n * - `horizontal` \u2192 opposite of the FAB's horizontal edge (FAB on the\n * right \u2192 callout on the left).\n * - `vertical` \u2192 opposite of the FAB's vertical edge (FAB at the\n * bottom \u2192 callout on top).\n *\n * The mapping always picks the on-screen side so the bubble can't be\n * pushed off the viewport regardless of the FAB's corner.\n */\nexport type ResolvedSide = \"left\" | \"right\" | \"top\" | \"bottom\";\n\nexport function resolveCalloutPosition(configured: CalloutPosition, launcherPosition: Position): ResolvedSide {\n const isRight = launcherPosition.endsWith(\"-right\");\n const isBottom = launcherPosition.startsWith(\"bottom-\");\n if (configured === \"vertical\") return isBottom ? \"top\" : \"bottom\";\n // `auto` and `horizontal` both anchor along the FAB's horizontal axis.\n return isRight ? \"left\" : \"right\";\n}\n", "/**\n * User-drag resize grip for the panel.\n *\n * Renders a small handle in the corner pointing *away from* the launcher\n * (so dragging always enlarges the panel outward). On drag, writes the new\n * dimensions as CSS custom properties on the panel \u2014 NOT inline width /\n * height \u2014 so higher-specificity mode selectors (`expanded`, `fullscreen`)\n * can still override cleanly without an `!important` ladder.\n *\n * Persistence is delegated:\n * - `initialSize` seeds the CSS vars on mount (typically from the\n * widget's cached `userPrefs.widgetSize`).\n * - `onSizeChange(size)` fires on pointer-up with the resolved (post-clamp)\n * dimensions \u2014 the App pipes that to `persistence.patchUserPrefs(...)` and\n * the backend.\n *\n * The grip is only meaningful in \"normal\" panel size; expanded / fullscreen\n * override via CSS, and the user-set vars are simply unused there.\n */\n\nimport { useEffect, useRef } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResizeOptions, Strings } from \"@/core/config\";\nimport type { WidgetSize } from \"@/core/persistence\";\nimport { ResizeHandleIcon } from \"./icons\";\nimport { TID } from \"./test-ids\";\n\ninterface Props {\n /** The panel element whose size we control. */\n panelEl: HTMLElement | null;\n /** Resize limits + enabled flag. */\n resize: ResizeOptions;\n /** Position string on host \u2014 `'bottom-right'` / `'top-left'` / etc. */\n position: string;\n /** Previously-persisted size to restore on mount. `null` \u2192 use defaults. */\n initialSize: WidgetSize | null;\n /** Called on pointer-up with the resolved (post-clamp) dimensions. */\n onSizeChange: (size: WidgetSize) => void;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n}\n\nexport function ResizeGrip({ panelEl, resize, position, initialSize, onSizeChange, strings }: Props) {\n const p = BRAND.cssPrefix;\n const dragRef = useRef<{\n pointerId: number;\n startX: number;\n startY: number;\n startW: number;\n startH: number;\n dirX: number; // 1 = grow right; -1 = grow left\n dirY: number; // 1 = grow down; -1 = grow up\n } | null>(null);\n\n // Apply limits + restore persisted size as CSS custom properties on the\n // panel. Crucially NOT inline `width`/`max-width` \u2014 those would beat the\n // selectors that drive `data-mode=\"fullscreen\"` / `\"expanded\"` and clamp\n // those modes visibly wrong.\n useEffect(() => {\n if (!panelEl) return;\n const style = panelEl.style;\n if (resize.minWidth) style.setProperty(`--${p}-resize-min-w`, resize.minWidth);\n if (resize.maxWidth) style.setProperty(`--${p}-resize-max-w`, resize.maxWidth);\n if (resize.minHeight) style.setProperty(`--${p}-resize-min-h`, resize.minHeight);\n if (resize.maxHeight) style.setProperty(`--${p}-resize-max-h`, resize.maxHeight);\n if (initialSize) {\n style.setProperty(`--${p}-widget-w`, `${initialSize.width}px`);\n style.setProperty(`--${p}-widget-h`, `${initialSize.height}px`);\n }\n }, [panelEl, resize.minWidth, resize.maxWidth, resize.minHeight, resize.maxHeight, p, initialSize]);\n\n if (!panelEl) return null;\n\n // Which corner gets the grip depends on the host's position. The grip\n // always sits on the corner pointing *away* from the launcher so dragging\n // enlarges the panel outward.\n const isTop = position.startsWith(\"top-\");\n const isRight = position.endsWith(\"-right\");\n const cornerClass = `${p}-resize-grip ${p}-resize-grip--${isTop ? \"bottom\" : \"top\"}-${isRight ? \"left\" : \"right\"}`;\n\n const onPointerDown = (e: PointerEvent) => {\n if (!panelEl) return;\n const target = e.currentTarget as HTMLElement;\n const rect = panelEl.getBoundingClientRect();\n dragRef.current = {\n pointerId: e.pointerId,\n startX: e.clientX,\n startY: e.clientY,\n startW: rect.width,\n startH: rect.height,\n dirX: isRight ? -1 : 1, // bottom-right launcher \u2192 grip on bottom-left \u2192 drag left grows width\n dirY: isTop ? 1 : -1, // top-* launcher \u2192 grip on bottom \u2192 drag down grows height\n };\n target.setPointerCapture(e.pointerId);\n e.preventDefault();\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const d = dragRef.current;\n if (!d || e.pointerId !== d.pointerId || !panelEl) return;\n const dx = (e.clientX - d.startX) * d.dirX;\n const dy = (e.clientY - d.startY) * d.dirY;\n // Apply via CSS custom properties \u2014 not inline `style.width` \u2014 so\n // expanded/fullscreen CSS can override without an !important ladder.\n panelEl.style.setProperty(`--${p}-widget-w`, `${d.startW + dx}px`);\n panelEl.style.setProperty(`--${p}-widget-h`, `${d.startH + dy}px`);\n };\n\n const onPointerUp = (e: PointerEvent) => {\n const d = dragRef.current;\n if (!d || e.pointerId !== d.pointerId || !panelEl) return;\n (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);\n // Read the resolved (post-clamp) size from the DOM and hand it to the\n // owner \u2014 App persists locally + pushes to the backend.\n const rect = panelEl.getBoundingClientRect();\n onSizeChange({ width: Math.round(rect.width), height: Math.round(rect.height) });\n dragRef.current = null;\n };\n\n return (\n <div\n class={cornerClass}\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label={strings.resizeHandle}\n title={strings.resizeHandle}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerUp}\n data-testid={TID.resizeGrip}\n >\n <ResizeHandleIcon />\n </div>\n );\n}\n", "/**\n * Make a modal panel draggable by its chrome bars.\n *\n * Modal mode centres the panel via flex; this hook lets the user grab a\n * \"chrome\" region \u2014 the chat header, a back-header, or the home hero \u2014 and\n * reposition the dialog. The offset is written as CSS custom properties\n * (`--<p>-modal-dx` / `--<p>-modal-dy`) consumed by a `transform: translate()`\n * on the modal panel, so it never fights the panel's width/height vars.\n *\n * Pointerdown is delegated on the panel element and only starts a drag over a\n * handle that isn't an interactive control (so header buttons, the composer,\n * inputs, and tabs keep working). Move/up listen on `window` so a fast drag\n * that leaves the panel still tracks. The offset lives on the panel element's\n * inline style, which unmounts when the modal closes \u2014 so each open re-centres.\n *\n * No-op unless `enabled` (i.e. `mode === \"modal\"`).\n */\n\nimport { useEffect } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\n\nconst p = BRAND.cssPrefix;\n/** Grabbable chrome \u2014 the top bars of every modal surface. */\nconst HANDLE = `.${p}-header, .${p}-back-header, .${p}-home-hero`;\n/** Interactive descendants that must keep their own pointer behaviour. */\nconst BLOCK = `button, a, input, textarea, select, [role=\"tab\"], [contenteditable]`;\n/** Keep at least this many px of the panel on-screen when dragged. */\nconst KEEP_VISIBLE = 56;\n\nexport function useDragMove(panelEl: HTMLElement | null, enabled: boolean): void {\n useEffect(() => {\n if (!enabled || !panelEl) return;\n const num = (v: string) => Number.parseFloat(v) || 0;\n const clamp = (v: number, max: number) => Math.min(max, Math.max(-max, v));\n let drag: { id: number; x: number; y: number; dx: number; dy: number; maxX: number; maxY: number } | null = null;\n\n const onDown = (e: PointerEvent) => {\n const t = e.target;\n if (!(t instanceof Element) || !t.closest(HANDLE) || t.closest(BLOCK)) return;\n const rect = panelEl.getBoundingClientRect();\n drag = {\n id: e.pointerId,\n x: e.clientX,\n y: e.clientY,\n dx: num(panelEl.style.getPropertyValue(`--${p}-modal-dx`)),\n dy: num(panelEl.style.getPropertyValue(`--${p}-modal-dy`)),\n // Centred origin: |offset| \u2264 (viewport + panel)/2 \u2212 keepVisible keeps a\n // strip of the dialog reachable so it can never be lost off-screen.\n maxX: (window.innerWidth + rect.width) / 2 - KEEP_VISIBLE,\n maxY: (window.innerHeight + rect.height) / 2 - KEEP_VISIBLE,\n };\n panelEl.dataset.dragging = \"true\";\n e.preventDefault();\n };\n\n const onMove = (e: PointerEvent) => {\n if (!drag || e.pointerId !== drag.id) return;\n const nx = clamp(drag.dx + e.clientX - drag.x, drag.maxX);\n const ny = clamp(drag.dy + e.clientY - drag.y, drag.maxY);\n panelEl.style.setProperty(`--${p}-modal-dx`, `${nx}px`);\n panelEl.style.setProperty(`--${p}-modal-dy`, `${ny}px`);\n };\n\n const onUp = (e: PointerEvent) => {\n if (!drag || e.pointerId !== drag.id) return;\n drag = null;\n delete panelEl.dataset.dragging;\n };\n\n panelEl.addEventListener(\"pointerdown\", onDown);\n window.addEventListener(\"pointermove\", onMove);\n window.addEventListener(\"pointerup\", onUp);\n window.addEventListener(\"pointercancel\", onUp);\n return () => {\n panelEl.removeEventListener(\"pointerdown\", onDown);\n window.removeEventListener(\"pointermove\", onMove);\n window.removeEventListener(\"pointerup\", onUp);\n window.removeEventListener(\"pointercancel\", onUp);\n };\n }, [panelEl, enabled]);\n}\n", "/**\n * Per-mode visibility + behaviour rules for header actions.\n *\n * Pure helpers \u2014 no DOM, no React. Deliberately split from `panel.tsx` so\n * the same rules drive (a) the production filter applied before rendering\n * the header buttons and (b) the test suite. Keeping a parallel copy in\n * the test would have been a trip-wire that doesn't actually exercise\n * production code.\n */\n\nimport type { ActionName, Mode } from \"@/core/config\";\nimport type { PanelSize } from \"./host-mode\";\n\n/**\n * Whether an action button makes sense in the current mode + panel state.\n * The page-owner / server lists actions in `main` / `overflow`; this filter\n * drops the ones that would render a no-op button and confuse the user.\n *\n * - `expand` \u2014 only floating modes (inline has no mid-tier between\n * container-fill and fullscreen; standalone IS fullscreen).\n * - `fullscreen` \u2014 everywhere except standalone (already fullscreen).\n * - `popOut` \u2014 everywhere except standalone (can't pop out of itself)\n * AND requires `popOutUrl` to be resolved (otherwise the button is a\n * no-op \u2014 hide it).\n * - `close` \u2014 floating modes always; inline only when in fullscreen (X\n * exits fullscreen back to the in-flow layout); standalone and page never\n * (the widget IS the page \u2014 there's nothing to close it back to).\n * - `clear`, `sound`, `history`, `language`, `theme` \u2014 all modes.\n */\nexport interface VisibilityContext {\n /** Resolved popOut URL \u2014 gate the `popOut` action when empty. */\n popOutUrl?: string;\n}\n\nexport function isActionVisible(name: ActionName, mode: Mode, panelSize: PanelSize, ctx?: VisibilityContext): boolean {\n switch (name) {\n case \"clear\":\n case \"sound\":\n case \"history\":\n case \"language\":\n case \"theme\":\n return true;\n case \"expand\":\n return mode === \"floating\";\n case \"fullscreen\":\n return mode !== \"standalone\";\n case \"popOut\":\n return mode !== \"standalone\" && !!ctx?.popOutUrl;\n case \"close\":\n if (mode === \"standalone\" || mode === \"page\") return false;\n if (mode === \"inline\") return panelSize === \"fullscreen\";\n return true;\n }\n}\n\n/** Filter an action list down to the ones visible in the current mode + state. */\nexport function visibleActions(\n actions: readonly ActionName[],\n mode: Mode,\n panelSize: PanelSize,\n ctx?: VisibilityContext,\n): readonly ActionName[] {\n return actions.filter((name) => isActionVisible(name, mode, panelSize, ctx));\n}\n\n/**\n * The history pane's X button mirrors the main header's close visibility.\n * Returns `true` when both (a) the close action is visible per the rules\n * above AND (b) the deployment actually listed `close` in `main` /\n * `overflow` \u2014 there's no point showing the history-pane X if the close\n * action is otherwise omitted.\n */\nexport function canShowClose(\n mode: Mode,\n panelSize: PanelSize,\n actions: { main: readonly ActionName[]; overflow: readonly ActionName[] },\n): boolean {\n if (!isActionVisible(\"close\", mode, panelSize)) return false;\n return actions.main.includes(\"close\") || actions.overflow.includes(\"close\");\n}\n\n/**\n * What clicking the X / Escape should *do* given the current mode + state:\n *\n * - `\"exit-fullscreen\"` \u2014 only inline + fullscreen. The widget can't be\n * \"closed\" in inline (it's part of the page flow), but it can return\n * to its in-flow layout from a viewport-fill state. The handler resets\n * `panelSize` to `\"normal\"` and emits `fullscreen: false` on the bus.\n * - `\"close\"` \u2014 every other case. Hides the panel (floating) or no-ops\n * (when the rules above already hide the button \u2014 defensive).\n */\nexport type CloseAction = \"close\" | \"exit-fullscreen\";\n\nexport function resolveCloseAction(mode: Mode, panelSize: PanelSize): CloseAction {\n if (mode === \"inline\" && panelSize === \"fullscreen\") return \"exit-fullscreen\";\n return \"close\";\n}\n", "// Focus trap for the panel + a small helper to dispatch announcements via aria-live.\n// All queries scoped to the shadow root \u2014 no leakage into host page focusables.\n\nconst FOCUSABLE_SELECTOR =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]):not([type=\"hidden\"]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nexport function trapFocus(container: HTMLElement): () => void {\n const onKeydown = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\") {\n return;\n }\n const focusables = [...container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)].filter(\n (el) => el.offsetParent !== null || el === document.activeElement,\n );\n if (focusables.length === 0) {\n e.preventDefault();\n return;\n }\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n if (!first || !last) {\n return;\n }\n\n const active = (container.getRootNode() as ShadowRoot).activeElement as HTMLElement | null;\n if (e.shiftKey && active === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && active === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n container.addEventListener(\"keydown\", onKeydown);\n return () => container.removeEventListener(\"keydown\", onKeydown);\n}\n", "/** First letters of the first two words of a name, upper-cased. \"Sam Carter\" \u2192 \"SC\". */\nexport function initials(name: string): string {\n return name\n .trim()\n .split(/\\s+/)\n .slice(0, 2)\n .map((part) => part.charAt(0).toUpperCase())\n .join(\"\");\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { SessionAgent } from \"@/stream/protocol\";\nimport { initials } from \"./initials\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n agent: SessionAgent;\n}\n\n/**\n * Bot persona shown in the panel header. Renders an avatar (image or\n * initials), name, and optional subtitle + presence dot.\n */\nexport function AgentBadge({ agent }: Props) {\n const status = agent.status ?? \"online\";\n return (\n <div class={`${p}-agent`}>\n <div class={`${p}-agent-avatar`} data-status={status}>\n {agent.avatar ? <img src={agent.avatar} alt=\"\" /> : <span>{initials(agent.name)}</span>}\n </div>\n <div class={`${p}-agent-meta`}>\n <strong>{agent.name}</strong>\n {agent.title ? <span>{agent.title}</span> : null}\n </div>\n </div>\n );\n}\n", "// Unified ingestion pipeline for files arriving via drop, paste, or picker.\n// Returns LocalAttachments with object-URL previews ready for the composer.\n\nimport type { AttachmentLimits } from \"./config\";\nimport { cryptoId } from \"./ids\";\nimport { logger } from \"./logger\";\n\nconst log = logger.scope(\"attachments\");\n\nexport interface LocalAttachment {\n id: string;\n file: File;\n name: string;\n size: number;\n mediaType: string;\n /** Object URL for preview thumbnails. Caller MUST revoke when removed. */\n previewUrl: string;\n}\n\nexport interface IngestResult {\n accepted: LocalAttachment[];\n rejected: { file: File; reason: \"count\" | \"bytes\" | \"mime\" }[];\n}\n\ntype Limits = Required<Pick<AttachmentLimits, \"maxBytes\" | \"maxCount\">> & { accept: string };\ntype Source = DataTransferItemList | FileList | File[] | null | undefined;\n\nexport function ingest(source: Source, existing: LocalAttachment[], limits: Limits): IngestResult {\n const incoming = collectFiles(source);\n const accepted: LocalAttachment[] = [];\n const rejected: IngestResult[\"rejected\"] = [];\n const acceptList = parseAcceptList(limits.accept);\n\n let totalBytes = existing.reduce((sum, a) => sum + a.size, 0);\n let totalCount = existing.length;\n\n for (const file of incoming) {\n const reason = reject(file, totalCount, totalBytes, limits, acceptList);\n if (reason) {\n rejected.push({ file, reason });\n continue;\n }\n accepted.push(toLocalAttachment(file));\n totalBytes += file.size;\n totalCount += 1;\n }\n\n if (accepted.length || rejected.length) {\n log.debug(\"ingest\", {\n accepted: accepted.length,\n rejected: rejected.map((r) => ({ name: r.file.name, reason: r.reason })),\n });\n }\n return { accepted, rejected };\n}\n\nexport function revoke(att: LocalAttachment): void {\n try {\n URL.revokeObjectURL(att.previewUrl);\n } catch (error) {\n log.debug(\"revokeObjectURL failed\", { error });\n }\n}\n\nfunction reject(\n file: File,\n count: number,\n bytes: number,\n limits: Limits,\n accept: AcceptRule[],\n): IngestResult[\"rejected\"][number][\"reason\"] | null {\n if (count >= limits.maxCount) return \"count\";\n if (bytes + file.size > limits.maxBytes) return \"bytes\";\n if (!matchesAccept(file, accept)) return \"mime\";\n return null;\n}\n\nfunction collectFiles(source: Source): File[] {\n if (!source) return [];\n if (Array.isArray(source)) return source.filter((f): f is File => f instanceof File);\n // `DataTransferItemList` and `FileList` are both iterable; spread to a\n // real array. `DataTransferItem` is not a File \u2014 unwrap with `getAsFile`.\n return [...(source as Iterable<unknown>)]\n .map((entry) => (entry instanceof File ? entry : toFile(entry)))\n .filter((f): f is File => f !== null);\n}\n\nfunction toFile(entry: unknown): File | null {\n const item = entry as { kind?: string; getAsFile?: () => File | null } | null;\n if (!item?.getAsFile || item.kind !== \"file\") return null;\n return item.getAsFile();\n}\n\nfunction toLocalAttachment(file: File): LocalAttachment {\n return {\n file,\n id: cryptoId(\"att\"),\n mediaType: file.type || \"application/octet-stream\",\n name: file.name || \"pasted-file\",\n previewUrl: URL.createObjectURL(file),\n size: file.size,\n };\n}\n\ntype AcceptRule = { kind: \"mime\"; type: string; subtype: string } | { kind: \"ext\"; ext: string };\n\n// Mirror the HTML `<input accept>` grammar: a comma-separated mix of MIME\n// patterns (`image/*`, `application/pdf`) and extensions (`.pdf`, `.docx`).\n// Extension entries match against the filename suffix; MIME entries match\n// against the file's `type`. Empty list = accept anything.\nfunction parseAcceptList(accept: string): AcceptRule[] {\n return accept\n .split(\",\")\n .map((s) => s.trim().toLowerCase())\n .filter(Boolean)\n .map((s): AcceptRule => {\n if (s.startsWith(\".\")) return { kind: \"ext\", ext: s };\n const [type = \"*\", subtype = \"*\"] = s.split(\"/\");\n return { kind: \"mime\", type, subtype };\n });\n}\n\nfunction matchesAccept(file: File, rules: AcceptRule[]): boolean {\n if (rules.length === 0) return true;\n const [type = \"*\", subtype = \"*\"] = (file.type || \"\").toLowerCase().split(\"/\");\n const name = (file.name || \"\").toLowerCase();\n return rules.some((r) => {\n if (r.kind === \"ext\") return name.endsWith(r.ext);\n return (r.type === \"*\" || r.type === type) && (r.subtype === \"*\" || r.subtype === subtype);\n });\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { LocalAttachment } from \"@/core/attachments\";\nimport { CloseIcon, FileIcon } from \"./icons\";\nimport { tid, TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n attachment: LocalAttachment;\n onRemove: () => void;\n}\n\nexport function AttachmentChip({ attachment, onRemove }: Props) {\n const isImage = attachment.mediaType.startsWith(\"image/\");\n return (\n <span class={`${p}-attachment-chip`} title={attachment.name} data-testid={tid(TID.attachmentChip, attachment.id)}>\n {isImage ? (\n <img class={`${p}-attachment-thumb`} src={attachment.previewUrl} alt=\"\" />\n ) : (\n <span class={`${p}-attachment-thumb`} aria-hidden=\"true\">\n <FileIcon />\n </span>\n )}\n <span class={`${p}-attachment-name`}>{attachment.name}</span>\n <button\n type=\"button\"\n class={`${p}-attachment-remove`}\n onClick={onRemove}\n aria-label={`Remove ${attachment.name}`}\n data-testid={tid(TID.attachmentRemove, attachment.id)}\n >\n <CloseIcon />\n </button>\n </span>\n );\n}\n", "/**\n * Voice-input controllers.\n *\n * Two backends, one interface:\n *\n * - `'local'` \u2014 Web Speech API (`SpeechRecognition`). Free, on-device,\n * streams interim results. Browser support varies (Chrome/Edge/Safari\n * yes, Firefox no).\n * - `'server'` \u2014 `MediaRecorder` captures audio, we POST the blob to the\n * configured transcription endpoint when the user clicks stop. More\n * consistent quality + works anywhere MediaRecorder does.\n *\n * `'disabled'` collapses to an unsupported controller \u2014 the mic button\n * is hidden by the composer.\n */\n\nimport type { VoiceMode } from \"@/core/config\";\n\nexport interface VoiceController {\n /** Whether the mic button should render at all. */\n isSupported: boolean;\n /** Begin capture. `onText` fires for interim (local) and final transcripts. */\n start(onText: (text: string, isFinal: boolean) => void, onEnd: () => void): void;\n /** Stop capture \u2014 triggers transcription upload in `server` mode. */\n stop(): void;\n}\n\ntype Transcribe = (audio: Blob, mimeType: string) => Promise<{ text: string }>;\n\nconst UNSUPPORTED: VoiceController = {\n isSupported: false,\n start: () => undefined,\n stop: () => undefined,\n};\n\nexport function createVoice(mode: VoiceMode, locale: string, transcribe: Transcribe): VoiceController {\n if (mode === \"disabled\") return UNSUPPORTED;\n if (mode === \"server\") return createServerVoice(transcribe);\n return createLocalVoice(locale);\n}\n\n// ---- local: Web Speech API ---------------------------------------------\n\ninterface SpeechRecognitionLike extends EventTarget {\n continuous: boolean;\n interimResults: boolean;\n lang: string;\n start: () => void;\n stop: () => void;\n abort: () => void;\n}\n\ninterface RecognitionResultEvent extends Event {\n results: { isFinal: boolean; 0: { transcript: string } }[];\n resultIndex: number;\n}\n\ntype RecognitionCtor = new () => SpeechRecognitionLike;\n\nfunction createLocalVoice(locale: string): VoiceController {\n const Ctor = getSpeechRecognitionCtor();\n if (!Ctor) return UNSUPPORTED;\n\n let rec: SpeechRecognitionLike | null = null;\n\n return {\n isSupported: true,\n start(onText, onEnd) {\n rec = new Ctor();\n rec.continuous = true;\n rec.interimResults = true;\n rec.lang = locale;\n\n rec.addEventListener(\"result\", (e) => {\n const evt = e as RecognitionResultEvent;\n let text = \"\";\n let isFinal = false;\n for (let i = evt.resultIndex; i < evt.results.length; i++) {\n const r = evt.results[i];\n if (!r) continue;\n text += r[0].transcript;\n if (r.isFinal) isFinal = true;\n }\n onText(text, isFinal);\n });\n const end = () => {\n rec = null;\n onEnd();\n };\n rec.addEventListener(\"end\", end);\n rec.addEventListener(\"error\", end);\n rec.start();\n },\n stop() {\n rec?.stop();\n },\n };\n}\n\nfunction getSpeechRecognitionCtor(): RecognitionCtor | undefined {\n const g = globalThis as unknown as {\n SpeechRecognition?: RecognitionCtor;\n webkitSpeechRecognition?: RecognitionCtor;\n };\n return g.SpeechRecognition ?? g.webkitSpeechRecognition;\n}\n\n// ---- server: MediaRecorder \u2192 /transcribe -------------------------------\n\nconst CANDIDATE_MIME_TYPES = [\"audio/webm;codecs=opus\", \"audio/webm\", \"audio/ogg;codecs=opus\", \"audio/mp4\"];\n\nfunction createServerVoice(transcribe: Transcribe): VoiceController {\n if (typeof MediaRecorder === \"undefined\" || !navigator?.mediaDevices?.getUserMedia) {\n return UNSUPPORTED;\n }\n\n let recorder: MediaRecorder | null = null;\n let stream: MediaStream | null = null;\n let chunks: Blob[] = [];\n\n const releaseStream = () => {\n for (const track of stream?.getTracks() ?? []) track.stop();\n stream = null;\n };\n\n return {\n isSupported: true,\n async start(onText, onEnd) {\n try {\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const mimeType = pickSupportedMimeType();\n recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);\n chunks = [];\n\n recorder.addEventListener(\"dataavailable\", (e) => {\n if (e.data.size > 0) chunks.push(e.data);\n });\n recorder.addEventListener(\"stop\", async () => {\n const type = recorder?.mimeType ?? \"audio/webm\";\n releaseStream();\n const blob = new Blob(chunks, { type });\n chunks = [];\n try {\n const { text } = await transcribe(blob, type);\n if (text) onText(text, true);\n } catch {\n // Swallow transcription errors \u2014 the composer's error state\n // would need plumbing. Refine once we have a UX spec for this.\n } finally {\n onEnd();\n }\n });\n recorder.start();\n } catch {\n releaseStream();\n onEnd();\n }\n },\n stop() {\n recorder?.stop();\n },\n };\n}\n\nfunction pickSupportedMimeType(): string | undefined {\n for (const type of CANDIDATE_MIME_TYPES) {\n if (MediaRecorder.isTypeSupported(type)) return type;\n }\n return undefined;\n}\n", "import { useEffect, useRef, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResolvedOptions } from \"@/core/config\";\nimport type { EventBus } from \"@/core/events\";\nimport type { FeedbackBus } from \"@/core/feedback\";\nimport { ingest, type LocalAttachment, revoke } from \"@/core/attachments\";\nimport { logger } from \"@/core/logger\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport { AttachmentChip } from \"./attachment-chip\";\nimport { ArrowUpIcon, ChevronRightIcon, MicIcon, PaperPlaneIcon, PaperclipIcon, SendIcon, StopIcon } from \"./icons\";\nimport { TID } from \"./test-ids\";\nimport { createVoice } from \"./voice\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"composer\");\n\n/** Sum the resolved sizes \u2014 used in attach event payloads. */\nfunction totalBytes(items: LocalAttachment[]): number {\n return items.reduce((acc, a) => acc + a.size, 0);\n}\n\nconst SEND_ICONS = {\n arrowUp: ArrowUpIcon,\n paperPlane: PaperPlaneIcon,\n send: SendIcon,\n chevron: ChevronRightIcon,\n} as const;\n\n/**\n * Imperative API the Composer exposes via `attachApiRef` so the\n * Panel (which owns the drag-drop target \u2014 the whole panel area, not\n * just the composer footer) can route dropped files into the\n * composer's attachment state instead of sending them as an empty\n * message. Composer keeps ownership of the `atts` state + the\n * `ingest()`-driven dedupe/clamp policy; Panel just hands over the\n * raw `DataTransferItemList`.\n */\nexport interface ComposerAttachAPI {\n /** Run dropped items through `ingest()` and append accepted ones to the composer's pending attachments. */\n attachFromDrop: (items: DataTransferItemList) => void;\n}\n\ninterface Props {\n options: ResolvedOptions;\n transport: AgentTransport;\n feedback: FeedbackBus;\n bus: EventBus;\n isStreaming: boolean;\n onSend: (text: string, attachments: LocalAttachment[]) => void;\n onStop: () => void;\n /**\n * Optional. Composer assigns an imperative API into this ref on\n * mount so callers (Panel's drop handler) can append attachments\n * without re-implementing the composer's ingest pipeline.\n */\n attachApiRef?: { current: ComposerAttachAPI | null };\n}\n\nexport function Composer({ options, transport, feedback, bus, isStreaming, onSend, onStop, attachApiRef }: Props) {\n const s = options.strings;\n const [text, setText] = useState(\"\");\n const [atts, setAtts] = useState<LocalAttachment[]>([]);\n const [voiceOn, setVoiceOn] = useState(false);\n const taRef = useRef<HTMLTextAreaElement>(null);\n const fileRef = useRef<HTMLInputElement>(null);\n\n const filesEnabled = options.features.files;\n const voiceMode = options.features.voice;\n\n // Voice controller \u2014 rebuilt when locale / mode / transport change.\n // `useState` (not `useRef`) so changes flow into the next render and\n // the mic-button branch re-evaluates `voice.isSupported`. Setting\n // `features.voice = \"disabled\"` returns an UNSUPPORTED controller \u2014\n // without state, the ref-mutation in a useEffect did NOT trigger a\n // re-render, leaving the mic visible after the user disabled voice.\n const [voice, setVoice] = useState(() =>\n createVoice(voiceMode, options.locale, (blob, type) => transport.transcribe(blob, type)),\n );\n // Hold off on swapping the controller mid-recording \u2014 destroying it\n // would drop the in-flight transcription. `voiceOn` is in the deps\n // so the swap fires the instant the user releases the mic, picking\n // up whatever locale / mode / transport changes accumulated.\n // Disabled mode returns the frozen UNSUPPORTED constant \u2014 short-circuit\n // so a locale switch doesn't churn through `createVoice` for no effect.\n useEffect(() => {\n if (voiceOn || voiceMode === \"disabled\") return;\n setVoice(createVoice(voiceMode, options.locale, (blob, type) => transport.transcribe(blob, type)));\n }, [options.locale, voiceMode, transport, voiceOn]);\n\n // Autofocus the textarea when the composer mounts. Composer remounts at\n // every \"user is now ready to type\" moment \u2014 panel opens, history pane\n // closes, read-only banner flips back to sendable \u2014 so a single\n // mount-only effect covers all of them without an explicit `isOpen` dep.\n // Only autofocus when no other element in the panel already has focus,\n // so keyboard users mid-tab-cycle aren't yanked back to the input.\n useEffect(() => {\n const el = taRef.current;\n if (!el) return;\n const root = el.getRootNode() as ShadowRoot | Document;\n const active = \"activeElement\" in root ? root.activeElement : null;\n const focusedInsidePanel = active && el.closest(`.${p}-panel`)?.contains(active as Node);\n if (!focusedInsidePanel) el.focus({ preventScroll: true });\n // Mount-only.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Autosize textarea on input. When empty, clear the inline height so\n // CSS `min-height: 40px` drives the single-line appearance \u2014 reading\n // `scrollHeight` before layout has settled (iframe mode, first mount)\n // can return values that then get clamped to `max-height: 160px` and\n // stick as inline, leaving the input looking weirdly tall.\n useEffect(() => {\n const el = taRef.current;\n if (!el) return;\n if (text.length === 0) {\n el.style.height = \"\";\n return;\n }\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, 160)}px`;\n }, [text]);\n\n // Cleanup attachment object URLs on unmount. Use a ref so the cleanup\n // sees the LATEST attachments, not the empty list at mount time\n // (naive `[]` deps would capture the initial value and leak blob URLs).\n const attsRef = useRef(atts);\n attsRef.current = atts;\n useEffect(() => () => attsRef.current.forEach(revoke), []);\n\n // Expose an imperative API to the parent Panel so its drop handler\n // (the panel-wide drop target is wider than the composer footer)\n // can route accepted files INTO the composer's pending attachments\n // \u2014 not auto-send them. Without this hand-off the drop fell back to\n // calling `onSend(\"\", [...])`, which submitted an empty-text message\n // the user never typed.\n useEffect(() => {\n if (!attachApiRef) return;\n attachApiRef.current = {\n attachFromDrop: (items) => {\n const result = ingest(items, attsRef.current, options.attachments);\n if (result.accepted.length === 0) return;\n log.info(\"attach (drop)\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n bus.emit(\"attach\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n setAtts((prev) => [...prev, ...result.accepted]);\n },\n };\n return () => {\n if (attachApiRef.current) attachApiRef.current = null;\n };\n }, [attachApiRef, options.attachments]);\n\n // Panel-wide paste: text \u2192 append to textarea, files \u2192 attachment chips \u2014\n // regardless of which element inside the panel is focused. The focus-trap\n // in `Panel` keeps focus inside the panel, so this listener catches every\n // paste the user can produce while interacting with the widget. Skips\n // text paste when the textarea is itself the target so native paste keeps\n // cursor/selection semantics.\n useEffect(() => {\n const ta = taRef.current;\n if (!ta) return;\n const panel = ta.closest(`.${p}-panel`);\n if (!panel) return;\n\n const onPaste = (e: ClipboardEvent) => {\n const data = e.clipboardData;\n if (!data) return;\n\n if (filesEnabled) {\n const result = ingest(data.items, attsRef.current, options.attachments);\n if (result.accepted.length > 0) {\n e.preventDefault();\n log.info(\"attach (paste)\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n bus.emit(\"attach\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n setAtts((prev) => [...prev, ...result.accepted]);\n return;\n }\n }\n\n if (e.target === ta) return;\n const pastedText = data.getData(\"text/plain\");\n if (!pastedText) return;\n e.preventDefault();\n setText((prev) => prev + pastedText);\n ta.focus();\n };\n\n panel.addEventListener(\"paste\", onPaste as EventListener);\n return () => panel.removeEventListener(\"paste\", onPaste as EventListener);\n }, [filesEnabled, options.attachments, p]);\n\n const send = () => {\n const trimmed = text.trim();\n if (!trimmed && atts.length === 0) return;\n if (isStreaming) return;\n onSend(trimmed, atts);\n setText(\"\");\n setAtts([]);\n };\n\n const onKeydown = (e: KeyboardEvent) => {\n if (e.key !== \"Enter\" || e.shiftKey || e.isComposing) return;\n e.preventDefault();\n if (isStreaming) onStop();\n else send();\n };\n\n const onFilePick = (e: Event) => {\n const input = e.target as HTMLInputElement;\n const result = ingest(input.files, atts, options.attachments);\n if (result.accepted.length > 0) {\n log.info(\"attach (file picker)\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n bus.emit(\"attach\", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });\n setAtts((prev) => [...prev, ...result.accepted]);\n }\n input.value = \"\";\n };\n\n const removeAtt = (id: string) => {\n log.info(\"removeAttachment\", { id });\n bus.emit(\"removeAttachment\", { id });\n setAtts((prev) => {\n const target = prev.find((a) => a.id === id);\n if (target) revoke(target);\n return prev.filter((a) => a.id !== id);\n });\n };\n\n const voiceStartedAtRef = useRef<number | null>(null);\n const toggleVoice = () => {\n if (!voice.isSupported) return;\n if (voiceOn) {\n const durationMs = voiceStartedAtRef.current ? Date.now() - voiceStartedAtRef.current : 0;\n log.info(\"voiceStop\", { durationMs });\n bus.emit(\"voiceStop\", { durationMs });\n voice.stop();\n setVoiceOn(false);\n feedback.play(\"voiceStop\");\n voiceStartedAtRef.current = null;\n return;\n }\n voiceStartedAtRef.current = Date.now();\n log.info(\"voiceStart\");\n bus.emit(\"voiceStart\", undefined);\n setVoiceOn(true);\n feedback.play(\"voiceStart\");\n voice.start(\n (transcript, isFinal) => setText((prev) => (isFinal ? `${prev}${transcript} ` : prev)),\n () => {\n setVoiceOn(false);\n // Auto-stop fired by the recognizer (e.g. silence detection) \u2014\n // treat as a normal stop; cancel paths use a separate UX flow.\n if (voiceStartedAtRef.current) {\n const durationMs = Date.now() - voiceStartedAtRef.current;\n log.debug(\"voiceStop (auto)\", { durationMs });\n bus.emit(\"voiceStop\", { durationMs });\n voiceStartedAtRef.current = null;\n }\n },\n );\n };\n\n // Pre-resolve the send-button icon component so the JSX below stays\n // declarative. `SEND_ICONS` is a small lookup over the four icon variants.\n const SendGlyph = SEND_ICONS[options.composer.sendButton.icon];\n\n return (\n <div class={`${p}-composer`} data-testid={TID.composer}>\n {atts.length > 0 ? (\n <div class={`${p}-attachments`}>\n {atts.map((a) => (\n <AttachmentChip key={a.id} attachment={a} onRemove={() => removeAtt(a.id)} />\n ))}\n </div>\n ) : null}\n <div class={`${p}-composer-row`}>\n <div class={`${p}-composer-actions`}>\n {filesEnabled ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={() => fileRef.current?.click()}\n aria-label={s.attach}\n data-testid={TID.composerAttach}\n >\n <PaperclipIcon />\n </button>\n ) : null}\n {voice.isSupported ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={toggleVoice}\n aria-label={voiceOn ? s.micStop : s.micStart}\n aria-pressed={voiceOn}\n data-recording={voiceOn ? \"true\" : undefined}\n data-testid={TID.composerVoice}\n >\n <MicIcon />\n </button>\n ) : null}\n </div>\n <textarea\n ref={taRef}\n class={`${p}-textarea`}\n rows={1}\n value={text}\n placeholder={s.composerPlaceholder}\n onInput={(e) => setText((e.target as HTMLTextAreaElement).value)}\n onKeyDown={onKeydown}\n aria-label={s.composerPlaceholder}\n data-testid={TID.composerInput}\n />\n {isStreaming ? (\n <button type=\"button\" class={`${p}-send`} onClick={onStop} aria-label={s.stop} data-testid={TID.composerStop}>\n <StopIcon />\n </button>\n ) : (\n <button\n type=\"button\"\n class={`${p}-send`}\n data-shape={options.composer.sendButton.shape}\n data-variant={options.composer.sendButton.variant}\n onClick={send}\n disabled={!text.trim() && atts.length === 0}\n aria-label={s.send}\n data-testid={TID.composerSend}\n >\n <SendGlyph />\n </button>\n )}\n </div>\n <input\n ref={fileRef}\n type=\"file\"\n multiple\n accept={options.attachments.accept}\n onChange={onFilePick}\n class={`${p}-sr-only`}\n data-testid={TID.composerFileInput}\n />\n </div>\n );\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n visible: boolean;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n}\n\nexport function DropZone({ visible, strings }: Props) {\n if (!visible) return null;\n return (\n <div class={`${p}-dropzone`} role=\"status\" aria-live=\"polite\">\n {strings.dropZone}\n </div>\n );\n}\n", "/**\n * Wire HTML5 file-drop on a container ref. Returns a `visible` flag\n * for the drop-zone overlay; the consumer renders `<DropZone\n * visible={visible} />` and reads dropped files through the `onDrop`\n * callback.\n *\n * Pulled out of `Panel` because the file-drop choreography (depth\n * counter to ignore child-element dragenter/leave noise; `Files`\n * type-list gate; preventDefault on dragover for the OS to allow the\n * drop) is mechanical plumbing that has nothing to do with panel\n * chrome. Keeping it inline made `Panel` re-render reasoning harder\n * than it needs to be.\n */\n\nimport type { RefObject } from \"preact\";\nimport { useEffect, useState } from \"preact/hooks\";\n\ninterface UseFileDropOptions {\n /** Container the file-drop is wired on. Drop anywhere inside fires `onDrop`. */\n containerRef: RefObject<HTMLElement | null>;\n /**\n * Fires with the raw `DataTransferItemList` from the drop event.\n * The caller decides how to parse / filter / ingest the items\n * (mime / extension checks, count limits, etc.).\n */\n onDrop: (items: DataTransferItemList) => void;\n}\n\nexport function useFileDrop({ containerRef, onDrop }: UseFileDropOptions): { visible: boolean } {\n const [visible, setVisible] = useState(false);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n // Depth counter \u2014 `dragenter`/`dragleave` fire for every child\n // element the cursor crosses. Without counting, the overlay\n // flickers as the cursor moves between children. Track NESTING\n // depth and only hide when we've left every level.\n let depth = 0;\n const onEnter = (e: DragEvent) => {\n depth += 1;\n if (!e.dataTransfer?.types.includes(\"Files\")) return;\n e.preventDefault();\n setVisible(true);\n };\n const onLeave = () => {\n depth -= 1;\n if (depth > 0) return;\n depth = 0;\n setVisible(false);\n };\n const onOver = (e: DragEvent) => {\n // `preventDefault` is what tells the OS \"yes, drop here is OK\"\n // \u2014 without it the cursor stays on the \"no drop\" indicator.\n if (e.dataTransfer?.types.includes(\"Files\")) e.preventDefault();\n };\n const onDropEvent = (e: DragEvent) => {\n depth = 0;\n setVisible(false);\n if (!e.dataTransfer) return;\n e.preventDefault();\n onDrop(e.dataTransfer.items);\n };\n el.addEventListener(\"dragenter\", onEnter);\n el.addEventListener(\"dragleave\", onLeave);\n el.addEventListener(\"dragover\", onOver);\n el.addEventListener(\"drop\", onDropEvent);\n return () => {\n el.removeEventListener(\"dragenter\", onEnter);\n el.removeEventListener(\"dragleave\", onLeave);\n el.removeEventListener(\"dragover\", onOver);\n el.removeEventListener(\"drop\", onDropEvent);\n };\n }, [containerRef, onDrop]);\n\n return { visible };\n}\n", "/*\nStreaming Markdown Parser and Renderer\nMIT License\nCopyright 2024 Damian Tarnawski\nhttps://github.com/thetarnav/streaming-markdown\n*/\n\nexport const\n DOCUMENT = 1,\n PARAGRAPH = 2,\n HEADING_1 = 3,\n HEADING_2 = 4,\n HEADING_3 = 5,\n HEADING_4 = 6,\n HEADING_5 = 7,\n HEADING_6 = 8,\n CODE_BLOCK = 9,\n CODE_FENCE = 10,\n CODE_INLINE = 11,\n ITALIC_AST = 12,\n ITALIC_UND = 13,\n STRONG_AST = 14,\n STRONG_UND = 15,\n STRIKE = 16,\n LINK = 17,\n RAW_URL = 18,\n IMAGE = 19,\n BLOCKQUOTE = 20,\n LINE_BREAK = 21,\n RULE = 22,\n LIST_UNORDERED = 23,\n LIST_ORDERED = 24,\n LIST_ITEM = 25,\n CHECKBOX = 26,\n TABLE = 27,\n TABLE_ROW = 28,\n TABLE_CELL = 29,\n EQUATION_BLOCK = 30,\n EQUATION_INLINE = 31,\n NEWLINE = 101,\n MAYBE_URL = 102,\n MAYBE_TASK = 103,\n MAYBE_BR = 104,\n MAYBE_EQ_BLOCK = 105\n\n/** @enum {(typeof Token)[keyof typeof Token]} */\nexport const Token = /** @type {const} */({\n Document: DOCUMENT,\n Blockquote: BLOCKQUOTE,\n Paragraph: PARAGRAPH,\n Heading_1: HEADING_1,\n Heading_2: HEADING_2,\n Heading_3: HEADING_3,\n Heading_4: HEADING_4,\n Heading_5: HEADING_5,\n Heading_6: HEADING_6,\n Code_Block: CODE_BLOCK,\n Code_Fence: CODE_FENCE,\n Code_Inline: CODE_INLINE,\n Italic_Ast: ITALIC_AST,\n Italic_Und: ITALIC_UND,\n Strong_Ast: STRONG_AST,\n Strong_Und: STRONG_UND,\n Strike: STRIKE,\n Link: LINK,\n Raw_URL: RAW_URL,\n Image: IMAGE,\n Line_Break: LINE_BREAK,\n Rule: RULE,\n List_Unordered: LIST_UNORDERED,\n List_Ordered: LIST_ORDERED,\n List_Item: LIST_ITEM,\n Checkbox: CHECKBOX,\n Table: TABLE,\n Table_Row: TABLE_ROW,\n Table_Cell: TABLE_CELL,\n Equation_Block: EQUATION_BLOCK,\n Equation_Inline:EQUATION_INLINE,\n})\n\n/**\n * @param {Token} type\n * @returns {string } */\nexport function token_to_string(type) {\n switch (type) {\n case DOCUMENT: return \"Document\"\n case BLOCKQUOTE: return \"Blockquote\"\n case PARAGRAPH: return \"Paragraph\"\n case HEADING_1: return \"Heading_1\"\n case HEADING_2: return \"Heading_2\"\n case HEADING_3: return \"Heading_3\"\n case HEADING_4: return \"Heading_4\"\n case HEADING_5: return \"Heading_5\"\n case HEADING_6: return \"Heading_6\"\n case CODE_BLOCK: return \"Code_Block\"\n case CODE_FENCE: return \"Code_Fence\"\n case CODE_INLINE: return \"Code_Inline\"\n case ITALIC_AST: return \"Italic_Ast\"\n case ITALIC_UND: return \"Italic_Und\"\n case STRONG_AST: return \"Strong_Ast\"\n case STRONG_UND: return \"Strong_Und\"\n case STRIKE: return \"Strike\"\n case LINK: return \"Link\"\n case RAW_URL: return \"Raw URL\"\n case IMAGE: return \"Image\"\n case LINE_BREAK: return \"Line_Break\"\n case RULE: return \"Rule\"\n case LIST_UNORDERED: return \"List_Unordered\"\n case LIST_ORDERED: return \"List_Ordered\"\n case LIST_ITEM: return \"List_Item\"\n case CHECKBOX: return \"Checkbox\"\n case TABLE: return \"Table\"\n case TABLE_ROW: return \"Table_Row\"\n case TABLE_CELL: return \"Table_Cell\"\n case EQUATION_BLOCK: return \"Equation_Block\"\n case EQUATION_INLINE:return \"Equation_Inline\"\n }\n}\n\nexport const\n HREF = 1,\n SRC = 2,\n LANG = 4,\n CHECKED = 8,\n START = 16\n\n/** @enum {(typeof Attr)[keyof typeof Attr]} */\nexport const Attr = /** @type {const} */({\n Href : HREF,\n Src : SRC,\n Lang : LANG,\n Checked: CHECKED,\n Start : START,\n})\n\n/**\n * @param {Attr} type\n * @returns {string } */\nexport function attr_to_html_attr(type) {\n switch (type) {\n case HREF: return \"href\"\n case SRC : return \"src\"\n case LANG: return \"class\"\n case CHECKED: return \"checked\"\n case START: return \"start\"\n }\n}\n\n/**\n * @param {number} level\n * @returns {Token } */\nexport const level_to_heading = (level) => {\n switch (level) {\n case 1: return HEADING_1\n case 2: return HEADING_2\n case 3: return HEADING_3\n case 4: return HEADING_4\n case 5: return HEADING_5\n default: return HEADING_6\n }\n}\nexport const heading_from_level = level_to_heading\n\n/**\n * @param {Token} token\n * @returns {number} */\nexport const heading_to_level = (token) => {\n switch (token) {\n case HEADING_1: return 1\n case HEADING_2: return 2\n case HEADING_3: return 3\n case HEADING_4: return 4\n case HEADING_5: return 5\n case HEADING_6: return 6\n default: return 0\n }\n}\n\n/**\n * @typedef {object } Parser\n * @property {Any_Renderer} renderer - {@link Renderer} interface\n * @property {string } text - Text to be added to the last token in the next flush\n * @property {string } pending - Characters for identifying tokens\n * @property {Uint32Array } tokens - Current token and it's parents (a slice of a tree)\n * @property {number } len - Number of tokens in types without root\n * @property {number } token - Last token in the tree\n * @property {Uint8Array } spaces\n * @property {string } indent\n * @property {number } indent_len\n * @property {number } fence_end - For {@link Token.Code_Fence} parsing\n * @property {number } fence_start\n * @property {number } blockquote_idx - For Blockquote parsing\n * @property {string } hr_char - For horizontal rule parsing\n * @property {number } hr_chars - For horizontal rule parsing\n * @property {number } table_state\n */\n\nconst TOKEN_ARRAY_CAP = 24\n\n/**\n * Makes a new Parser object.\n * @param {Any_Renderer} renderer\n * @returns {Parser } */\nexport function parser(renderer) {\n const tokens = new Uint32Array(TOKEN_ARRAY_CAP)\n tokens[0] = DOCUMENT\n return {\n renderer : renderer,\n text : \"\",\n pending : \"\",\n tokens : tokens,\n len : 0,\n token : DOCUMENT,\n fence_end: 0,\n blockquote_idx: 0,\n hr_char : '',\n hr_chars : 0,\n fence_start: 0,\n spaces : new Uint8Array(TOKEN_ARRAY_CAP),\n indent : \"\",\n indent_len : 0,\n table_state: 0,\n }\n}\n\n/**\n * Finish rendering the markdown - flushes any remaining text.\n * @param {Parser} p\n * @returns {void } */\nexport function parser_end(p) {\n if (p.pending.length > 0) {\n parser_write(p, \"\\n\")\n }\n}\n\n/**\n * @param {Parser} p\n * @returns {void } */\nfunction add_text(p) {\n if (p.text.length === 0) return\n console.assert(p.len > 0, \"Never adding text to root\")\n p.renderer.add_text(p.renderer.data, p.text)\n p.text = \"\"\n}\n\n/**\n * @param {Parser} p\n * @returns {void } */\nfunction ensure_paragraph(p) {\n switch (p.token) {\n case LINE_BREAK:\n case DOCUMENT:\n case BLOCKQUOTE:\n case LIST_ORDERED:\n case LIST_UNORDERED:\n add_token(p, PARAGRAPH)\n }\n}\n\n/**\n * @param {Parser} p\n * @param {string} text\n * @returns {void } */\nfunction push_text(p, text) {\n ensure_paragraph(p)\n p.text += text\n}\n\n/**\n * @param {Parser} p\n * @returns {void } */\nfunction end_token(p) {\n console.assert(p.len > 0, \"No nodes to end\")\n p.len -= 1\n p.token = /** @type {Token} */ (p.tokens[p.len])\n p.renderer.end_token(p.renderer.data)\n}\n\n/**\n * @param {Parser} p\n * @param {Token } token\n * @returns {void } */\nfunction add_token(p, token) {\n /*\n If a list doesn't start with a list item\n it means that there was a newline after the list:\n\n 1. foo\n 2. bar\n <empty line>\n <not_a_list_item> <- new token\n */\n if ((p.tokens[p.len] === LIST_ORDERED || p.tokens[p.len] === LIST_UNORDERED) &&\n token !== LIST_ITEM\n ) {\n end_token(p)\n }\n\n p.len += 1\n p.tokens[p.len] = token\n p.token = token\n p.renderer.add_token(p.renderer.data, token)\n}\n\n/**\n * @param {Parser} p\n * @param {number} token\n * @param {number} start_idx\n * @returns {number} */\nfunction idx_of_token(p, token, start_idx) {\n while (start_idx <= p.len) {\n if (p.tokens[start_idx] === token) {\n return start_idx\n }\n start_idx += 1\n }\n return -1\n}\n\n/**\n * End tokens until the parser has the given length.\n * @param {Parser} p\n * @param {number} len\n * @returns {void } */\nfunction end_tokens_to_len(p, len) {\n\n // TODO: specific token state should be reset only when the token ends\n p.fence_start = 0\n\n while (p.len > len) {\n end_token(p)\n }\n}\n\n/**\n * @param {Parser} p\n * @param {number} indent\n * @returns {number} */\nfunction end_tokens_to_indent(p, indent) {\n\n let idx = 0\n for (let i = 0; i <= p.len; i += 1) {\n indent -= p.spaces[i]\n if (indent < 0) {\n break \n }\n switch (p.tokens[i]) {\n case CODE_BLOCK:\n case CODE_FENCE:\n case BLOCKQUOTE:\n case LIST_ITEM:\n idx = i\n break\n }\n }\n\n while (p.len > idx) {end_token(p)}\n \n return indent\n}\n\n/**\n * @param {Parser } p\n * @param {Token } list_token\n * @returns {boolean} added a new list */\nfunction continue_or_add_list(p, list_token) {\n /* will create a new list inside the last item\n if the amount of spaces is greater than the last one (with prefix)\n 1. foo\n - bar <- new nested ul\n - baz <- new nested ul\n 12. qux <- cannot be nested in \"baz\" or \"bar\",\n so it's a new list in \"foo\"\n */\n let list_idx = -1\n let item_idx = -1\n\n for (let i = p.blockquote_idx+1; i <= p.len; i += 1) {\n if (p.tokens[i] === LIST_ITEM) {\n if (p.indent_len < p.spaces[i]) {\n item_idx = -1\n break\n }\n item_idx = i\n } else if (p.tokens[i] === list_token) {\n list_idx = i\n }\n }\n\n if (item_idx === -1) {\n if (list_idx === -1) {\n end_tokens_to_len(p, p.blockquote_idx)\n add_token(p, list_token)\n return true\n }\n end_tokens_to_len(p, list_idx)\n return false\n }\n end_tokens_to_len(p, item_idx)\n add_token(p, list_token)\n return true\n}\n\n/**\n * Create a new list\n * or continue the last one\n * @param {Parser } p\n * @param {number } prefix_length\n * @returns {void } */\nfunction add_list_item(p, prefix_length) {\n add_token(p, LIST_ITEM)\n p.spaces[p.len] = p.indent_len + prefix_length\n clear_root_pending(p)\n p.token = MAYBE_TASK\n}\n\n/**\n * @param {Parser} p\n * @returns {void } */\nfunction clear_root_pending(p) {\n p.indent = \"\"\n p.indent_len = 0\n p.pending = \"\"\n}\n\n/**\n * @param {number} charcode\n * @returns {boolean} */\nfunction is_digit(charcode) {\n switch (charcode) {\n case 48: case 49: case 50: case 51: case 52:\n case 53: case 54: case 55: case 56: case 57:\n return true\n default:\n return false\n }\n}\n\n/**\n * @param {number} charcode\n * @returns {boolean} */\nfunction is_delimeter(charcode) {\n switch (charcode) {\n // \" \" \":\" \";\" \")\" \",\" \"!\" \".\" \"?\" \"]\" \"\\n\"\n case 32: case 58: case 59: case 41: case 44: case 33: case 46: case 63: case 93: case 10: \n return true\n default:\n return false\n }\n}\n\n/**\n * @param {number} charcode\n * @returns {boolean} */\nfunction is_delimeter_or_number(charcode) {\n return is_digit(charcode) || is_delimeter(charcode)\n}\n\n/**\n * @param {number} charcode\n * @returns {boolean} */\nfunction is_alnum(charcode) {\n return is_digit(charcode) || // 0-9\n (charcode >= 65 && charcode <= 90) || // A-Z\n (charcode >= 97 && charcode <= 122) // a-z\n}\n\n/**\n * Parse and render another chunk of markdown.\n * @param {Parser} p\n * @param {string} chunk\n * @returns {void } */\nexport function parser_write(p, chunk) {\n for (const char of chunk) {\n\n /*\n Handle newlines\n */\n if (p.token === NEWLINE) {\n switch (char) {\n case ' ':\n p.indent_len += 1\n continue\n case '\\t':\n p.indent_len += 4\n continue\n }\n \n let indent = end_tokens_to_indent(p, p.indent_len)\n \n p.indent_len = 0\n p.token = p.tokens[p.len]\n\n if (indent > 0) {\n parser_write(p, \" \".repeat(indent))\n }\n }\n\n const pending_with_char = p.pending + char\n\n /*\n Token specific checks\n */\n switch (p.token) {\n case LINE_BREAK:\n case DOCUMENT:\n case BLOCKQUOTE:\n case LIST_ORDERED:\n case LIST_UNORDERED:\n console.assert(p.text.length === 0, \"Root should not have any text\")\n\n switch (p.pending[0]) {\n case undefined:\n p.pending = char\n continue\n case ' ':\n console.assert(p.pending.length === 1)\n p.pending = char\n p.indent += ' '\n p.indent_len += 1\n continue\n case '\\t':\n console.assert(p.pending.length === 1)\n p.pending = char\n p.indent += '\\t'\n p.indent_len += 4\n continue\n case '\\n':\n console.assert(p.pending.length === 1)\n /*\n Lists can have an empty line in between items:\n 1. foo\n <empty>\n 2. bar\n */\n if (p.tokens[p.len] === LIST_ITEM && p.token === LINE_BREAK) {\n end_token(p)\n clear_root_pending(p)\n p.pending = char\n continue\n }\n /*\n Exit out of tokens\n And ignore newlines in root\n */\n end_tokens_to_len(p, p.blockquote_idx)\n clear_root_pending(p)\n p.blockquote_idx = 0\n p.fence_start = 0\n p.pending = char\n continue\n /* Heading */\n case '#':\n switch (char) {\n case '#':\n if (p.pending.length < 6) {\n p.pending = pending_with_char\n continue\n }\n break // fail\n case ' ':\n end_tokens_to_indent(p, p.indent_len)\n add_token(p, heading_from_level(p.pending.length))\n clear_root_pending(p)\n continue\n }\n break // fail\n /* Blockquote */\n case '>': {\n const next_blockquote_idx = idx_of_token(p, BLOCKQUOTE, p.blockquote_idx+1)\n\n /*\n Only when there is no blockquote to the right of blockquote_idx\n a new blockquote can be created\n */\n if (next_blockquote_idx === -1) {\n end_tokens_to_len(p, p.blockquote_idx)\n p.blockquote_idx += 1\n p.fence_start = 0\n add_token(p, BLOCKQUOTE)\n } else {\n p.blockquote_idx = next_blockquote_idx\n }\n\n clear_root_pending(p)\n p.pending = char\n continue\n }\n /* Horizontal Rule\n \"-- - --- - --\"\n */\n case '-':\n case '*':\n case '_':\n if (p.hr_chars === 0) {\n console.assert(p.pending.length === 1, \"Pending should be one character\")\n p.hr_chars = 1\n p.hr_char = p.pending\n }\n\n if (p.hr_chars > 0) {\n switch (char) {\n case p.hr_char:\n p.hr_chars += 1\n p.pending = pending_with_char\n continue\n case ' ':\n p.pending = pending_with_char\n continue\n case '\\n':\n if (p.hr_chars < 3) break\n end_tokens_to_indent(p, p.indent_len)\n p.renderer.add_token(p.renderer.data, RULE)\n p.renderer.end_token(p.renderer.data)\n clear_root_pending(p)\n p.hr_chars = 0\n continue\n }\n\n p.hr_chars = 0\n }\n\n /* Unordered list\n / * foo\n / * *bar*\n / * **baz**\n /*/\n if ('_' !== p.pending[0] &&\n ' ' === p.pending[1]\n ) {\n continue_or_add_list(p, LIST_UNORDERED)\n add_list_item(p, 2)\n parser_write(p, pending_with_char.slice(2))\n continue\n }\n\n break // fail\n /* Code Fence */\n case '`':\n /* ``?\n ^\n */\n if (p.pending.length < 3) {\n if ('`' === char) {\n p.pending = pending_with_char\n p.fence_start = pending_with_char.length\n continue\n }\n p.fence_start = 0\n break // fail\n }\n\n switch (char) {\n case '`':\n /* ````?\n ^\n */\n if (p.pending.length === p.fence_start) {\n p.pending = pending_with_char\n p.fence_start = pending_with_char.length\n }\n /* ```code`\n ^\n */\n else {\n add_token(p, PARAGRAPH)\n clear_root_pending(p)\n p.fence_start = 0\n parser_write(p, pending_with_char)\n }\n continue\n case '\\n': {\n /* ```lang\\n\n ^\n */\n end_tokens_to_indent(p, p.indent_len)\n \n add_token(p, CODE_FENCE)\n if (p.pending.length > p.fence_start) {\n p.renderer.set_attr(p.renderer.data, LANG, p.pending.slice(p.fence_start))\n }\n clear_root_pending(p)\n p.token = NEWLINE\n continue\n }\n default:\n /* ```lang\\n\n ^\n */\n p.pending = pending_with_char\n continue\n }\n /*\n List Unordered for '+'\n The other list types are handled with HORIZONTAL_RULE\n */\n case '+':\n if (' ' !== char) break // fail\n\n continue_or_add_list(p, LIST_UNORDERED)\n add_list_item(p, 2)\n continue\n /* List Ordered */\n case '0': case '1': case '2': case '3': case '4':\n case '5': case '6': case '7': case '8': case '9':\n /*\n 12. foo\n ^\n */\n if ('.' === p.pending[p.pending.length-1]) {\n if (' ' !== char) break // fail\n\n if (continue_or_add_list(p, LIST_ORDERED) && p.pending !== \"1.\") {\n p.renderer.set_attr(p.renderer.data, START, p.pending.slice(0, -1))\n }\n add_list_item(p, p.pending.length+1)\n continue\n } else {\n const char_code = char.charCodeAt(0)\n if (46 === char_code || // '.'\n is_digit(char_code) // 0-9\n ) {\n p.pending = pending_with_char\n continue\n }\n }\n break // fail\n /* Table */\n case '|':\n end_tokens_to_len(p, p.blockquote_idx)\n\n add_token(p, TABLE)\n add_token(p, TABLE_ROW)\n\n p.pending = \"\"\n parser_write(p, char)\n\n continue\n }\n\n let to_write = pending_with_char\n\n /* Add a line break and continue in previous token */\n if (p.token === LINE_BREAK) {\n p.token = p.tokens[p.len]\n p.renderer.add_token(p.renderer.data, LINE_BREAK)\n p.renderer.end_token(p.renderer.data)\n }\n /* Code Block */\n else if (p.indent_len >= 4) {\n /*\n Case where there are additional spaces\n after the indent that makes the code block\n _________________________\n code\n ^^^^----indent\n ^^^-part of code\n _________________________\n \\t code\n ^^-----indent\n ^^^-part of code\n */\n let code_start = 0\n for (; code_start < 4; code_start += 1) {\n if (p.indent[code_start] === '\\t') {\n code_start = code_start+1\n break\n }\n }\n to_write = p.indent.slice(code_start) + pending_with_char\n add_token(p, CODE_BLOCK)\n }\n /* Paragraph */\n else {\n add_token(p, PARAGRAPH)\n }\n\n clear_root_pending(p)\n parser_write(p, to_write)\n continue\n case TABLE:\n if (p.table_state === 1) {\n switch (char) {\n case '-':\n case ' ':\n case '|':\n case ':':\n p.pending = pending_with_char\n continue\n case '\\n':\n p.table_state = 2\n p.pending = \"\"\n continue\n default:\n end_token(p)\n p.table_state = 0\n break\n }\n } else {\n switch (p.pending) {\n case \"|\":\n add_token(p, TABLE_ROW)\n p.pending = \"\"\n parser_write(p, char)\n continue\n case \"\\n\":\n end_token(p)\n p.pending = \"\"\n p.table_state = 0\n parser_write(p, char)\n continue\n }\n }\n break\n case TABLE_ROW:\n switch (p.pending) {\n case \"\":\n break\n case \"|\":\n add_token(p, TABLE_CELL)\n end_token(p)\n p.pending = \"\"\n parser_write(p, char)\n continue\n case \"\\n\":\n end_token(p)\n p.table_state = Math.min(p.table_state+1, 2)\n p.pending = \"\"\n parser_write(p, char)\n continue\n default:\n add_token(p, TABLE_CELL)\n parser_write(p, char)\n continue\n }\n break\n case TABLE_CELL:\n if (p.pending === \"|\") {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n parser_write(p, char)\n continue\n }\n break\n case CODE_BLOCK:\n switch (pending_with_char) {\n case \"\\n \":\n case \"\\n \\t\":\n case \"\\n \\t\":\n case \"\\n \\t\":\n case \"\\n\\t\":\n p.text += \"\\n\"\n p.pending = \"\"\n continue\n case \"\\n\":\n case \"\\n \":\n case \"\\n \":\n case \"\\n \":\n p.pending = pending_with_char\n continue\n default:\n if (p.pending.length !== 0) {\n add_text(p)\n end_token(p)\n p.pending = char\n } else {\n p.text += char\n }\n continue\n }\n case CODE_FENCE:\n switch (char) {\n case '`':\n /* ```\\n<code>\\n``??\n | ^\n */\n p.pending = pending_with_char\n continue\n case '\\n':\n /* ```\\n<code>\\n```\\n\n | ^\n */\n if (pending_with_char.length === p.fence_start + p.fence_end + 1) {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n p.fence_start = 0\n p.fence_end = 0\n p.token = NEWLINE\n continue\n }\n p.token = NEWLINE\n break\n case ' ':\n /* ```\\n<code>\\n ??\n | ^ (space after newline is allowed)\n */\n if (p.pending[0] === '\\n') {\n p.pending = pending_with_char\n p.fence_end += 1\n continue\n }\n break\n }\n // any other char\n p.text += p.pending\n p.pending = char\n p.fence_end = 1\n continue\n case CODE_INLINE:\n switch (char) {\n case '`':\n if (pending_with_char.length ===\n p.fence_start + Number(p.pending[0] === ' ') // 0 or 1 for space\n ) {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n p.fence_start = 0\n } else {\n p.pending = pending_with_char\n }\n continue\n case '\\n':\n p.text += p.pending\n p.pending = \"\"\n p.token = LINE_BREAK\n p.blockquote_idx = 0\n add_text(p)\n continue\n /* Trim space before ` */\n case ' ':\n p.text += p.pending\n p.pending = char\n continue\n default:\n p.text += pending_with_char\n p.pending = \"\"\n continue\n }\n /* Checkboxes */\n case MAYBE_TASK:\n switch (p.pending.length) {\n case 0:\n if ('[' !== char) break // fail\n p.pending = pending_with_char\n continue\n case 1:\n if (' ' !== char && 'x' !== char) break // fail\n p.pending = pending_with_char\n continue\n case 2:\n if (']' !== char) break // fail\n p.pending = pending_with_char\n continue\n case 3:\n if (' ' !== char) break // fail\n p.renderer.add_token(p.renderer.data, CHECKBOX)\n if ('x' === p.pending[1]) {\n p.renderer.set_attr(p.renderer.data, CHECKED, \"\")\n }\n p.renderer.end_token(p.renderer.data)\n p.pending = \" \"\n continue\n }\n\n p.token = p.tokens[p.len]\n p.pending = \"\"\n parser_write(p, pending_with_char)\n continue\n case STRONG_AST:\n case STRONG_UND: {\n /** @type {string} */ let symbol = '*'\n /** @type {Token } */ let italic = ITALIC_AST\n if (p.token === STRONG_UND) {\n symbol = '_'\n italic = ITALIC_UND\n }\n\n if (symbol === p.pending) {\n add_text(p)\n /* **Bold**\n ^\n */\n if (symbol === char) {\n end_token(p)\n p.pending = \"\"\n continue\n }\n /* **Bold*Bold->Em*\n ^\n */\n add_token(p, italic)\n p.pending = char\n continue\n }\n\n break\n }\n case ITALIC_AST:\n case ITALIC_UND: {\n /** @type {string} */ let symbol = '*'\n /** @type {Token } */ let strong = STRONG_AST\n if (p.token === ITALIC_UND) {\n symbol = '_'\n strong = STRONG_UND\n }\n\n switch (p.pending) {\n case symbol:\n if (symbol === char) {\n /* Decide between ***bold>em**em* and **bold*bold>em***\n ^ ^\n With the help of the next character\n */\n if (p.tokens[p.len-1] === strong) {\n p.pending = pending_with_char\n }\n /* *em**bold\n ^\n */\n else {\n add_text(p)\n add_token(p, strong)\n p.pending = \"\"\n }\n }\n /* *em*foo\n ^\n */\n else {\n add_text(p)\n end_token(p)\n p.pending = char\n }\n continue\n case symbol+symbol:\n const italic = p.token\n add_text(p)\n end_token(p)\n end_token(p)\n /* ***bold>em**em* or **bold*bold>em***\n ^ ^\n */\n if (symbol !== char) {\n add_token(p, italic)\n p.pending = char\n } else {\n p.pending = \"\"\n }\n continue\n }\n break\n }\n case STRIKE:\n if (\"~~\" === pending_with_char) {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n continue\n }\n break\n case MAYBE_EQ_BLOCK:\n /*\n \\[? or $$?\n ^ ^\n */\n if (char === '\\n') {\n add_text(p)\n add_token(p, EQUATION_BLOCK)\n p.pending = \"\"\n } else {\n p.token = p.tokens[p.len]\n if (p.pending[0] === '\\\\') {\n p.text += '['\n } else {\n p.text += '$$'\n }\n p.pending = \"\"\n parser_write(p, char)\n }\n continue\n case EQUATION_BLOCK:\n if (\"\\\\]\" === pending_with_char || \"$$\" === pending_with_char) {\n add_text(p)\n end_token(p)\n p.pending = \"\"\n continue\n }\n break\n case EQUATION_INLINE:\n if (\"\\\\)\" === pending_with_char || \"$\" === p.pending[0]) {\n add_text(p)\n end_token(p)\n\n if(char === ')'){\n p.pending = \"\"\n } else {\n p.pending = char\n }\n continue\n }\n break\n /* Raw URLs */\n case MAYBE_URL:\n if (\"http://\" === pending_with_char ||\n \"https://\" === pending_with_char\n ) {\n add_text(p)\n add_token(p, RAW_URL)\n p.pending = pending_with_char\n p.text = pending_with_char\n }\n else\n if (\"http:/\" [p.pending.length] === char ||\n \"https:/\"[p.pending.length] === char\n ) {\n p.pending = pending_with_char\n }\n else {\n p.token = p.tokens[p.len]\n parser_write(p, char)\n }\n continue\n case LINK:\n case IMAGE:\n if (\"]\" === p.pending) {\n /*\n [Link](url)\n ^\n */\n add_text(p)\n if ('(' === char) {\n p.pending = pending_with_char\n } else {\n end_token(p)\n p.pending = char\n }\n continue\n }\n if (']' === p.pending[0] &&\n '(' === p.pending[1]\n ) {\n /*\n [Link](url)\n ^\n */\n if (')' === char) {\n const type = p.token === LINK ? HREF : SRC\n const url = p.pending.slice(2)\n p.renderer.set_attr(p.renderer.data, type, url)\n end_token(p)\n p.pending = \"\"\n } else {\n p.pending += char\n }\n continue\n }\n break\n case RAW_URL:\n /* http://example.com?\n ^\n */\n if (' ' === char ||\n '\\n'=== char ||\n '\\\\'=== char\n ) {\n p.renderer.set_attr(p.renderer.data, HREF, p.pending)\n add_text(p)\n end_token(p)\n p.pending = char\n } else {\n p.text += char\n p.pending = pending_with_char\n }\n continue\n case MAYBE_BR:\n if (pending_with_char.startsWith(\"<br\")) {\n if (/* \"<br\" */\n pending_with_char.length === 3 ||\n /* \"<br \" */\n char === ' ' ||\n /* \"<br/\" | \"<br /\" */\n char === '/' && (pending_with_char.length === 4 ||\n p.pending[p.pending.length-1] === ' ')\n ) {\n p.pending = pending_with_char\n continue\n }\n\n /* \"<br>\" | \"<br/>\" */\n if (char === '>') {\n add_text(p)\n p.token = p.tokens[p.len]\n p.renderer.add_token(p.renderer.data, LINE_BREAK)\n p.renderer.end_token(p.renderer.data)\n p.pending = \"\"\n continue\n }\n }\n // Fail\n p.token = p.tokens[p.len]\n p.text += '<'\n p.pending = p.pending.slice(1)\n parser_write(p, char)\n continue\n }\n\n /*\n Common checks\n */\n switch (p.pending[0]) {\n /* Escape character */\n case '\\\\':\n if (p.token === IMAGE ||\n p.token === EQUATION_BLOCK ||\n p.token === EQUATION_INLINE)\n break\n\n switch (char) {\n case '(':\n add_text(p)\n add_token(p, EQUATION_INLINE)\n p.pending = \"\"\n continue\n case '[':\n p.token = MAYBE_EQ_BLOCK\n p.pending = pending_with_char\n continue\n case '\\n':\n // Escaped newline has the same affect as unescaped one\n p.pending = char\n continue\n default:\n let charcode = char.charCodeAt(0)\n p.pending = \"\"\n p.text += is_digit(charcode) || // 0-9\n (charcode >= 65 && charcode <= 90) || // A-Z\n (charcode >= 97 && charcode <= 122) // a-z\n ? pending_with_char\n : char\n continue\n }\n /* Newline */\n case '\\n':\n switch (p.token) {\n case IMAGE:\n case EQUATION_BLOCK:\n case EQUATION_INLINE:\n break\n case HEADING_1:\n case HEADING_2:\n case HEADING_3:\n case HEADING_4:\n case HEADING_5:\n case HEADING_6:\n add_text(p)\n end_tokens_to_len(p, p.blockquote_idx)\n p.blockquote_idx = 0\n p.pending = char\n continue\n default:\n add_text(p)\n p.pending = char\n p.token = LINE_BREAK\n p.blockquote_idx = 0\n continue\n }\n break\n /* <br> */\n case '<':\n if (p.token !== IMAGE &&\n p.token !== EQUATION_BLOCK &&\n p.token !== EQUATION_INLINE\n ) {\n add_text(p)\n p.pending = pending_with_char\n p.token = MAYBE_BR\n continue\n }\n break\n /* `Code Inline` */\n case '`':\n if (p.token === IMAGE) break\n\n if ('`' === char) {\n p.fence_start += 1\n p.pending = pending_with_char\n } else {\n p.fence_start += 1 // started at 0, and first wasn't counted\n add_text(p)\n add_token(p, CODE_INLINE)\n p.text = ' ' === char || '\\n' === char ? \"\" : char // trim leading space\n p.pending = \"\"\n }\n continue\n case '_':\n case '*': {\n if (p.token === IMAGE ||\n p.token === EQUATION_BLOCK ||\n p.token === EQUATION_INLINE ||\n p.token === STRONG_AST)\n break\n\n /** @type {Token} */ let italic = ITALIC_AST\n /** @type {Token} */ let strong = STRONG_AST\n const symbol = p.pending[0]\n if ('_' === symbol) {\n italic = ITALIC_UND\n strong = STRONG_UND\n }\n\n if (p.pending.length === 1) {\n /* **Strong**\n ^\n */\n if (symbol === char) {\n p.pending = pending_with_char\n continue\n }\n /* *Em*\n ^\n */\n if (' ' !== char && '\\n' !== char) {\n add_text(p)\n add_token(p, italic)\n p.pending = char\n continue\n }\n } else {\n /* ***Strong->Em***\n ^\n */\n if (symbol === char) {\n add_text(p)\n add_token(p, strong)\n add_token(p, italic)\n p.pending = \"\"\n continue\n }\n /* **Strong**\n ^\n */\n if (' ' !== char && '\\n' !== char) {\n add_text(p)\n add_token(p, strong)\n p.pending = char\n continue\n }\n }\n\n break\n }\n case '~':\n if (p.token !== IMAGE &&\n p.token !== STRIKE\n ) {\n if (\"~\" === p.pending) {\n /* ~~Strike~~\n ^\n */\n if ('~' === char) {\n p.pending = pending_with_char\n continue\n }\n } else {\n /* ~~Strike~~\n | ^\n */\n if (' ' !== char && '\\n' !== char) {\n add_text(p)\n add_token(p, STRIKE)\n p.pending = char\n continue\n }\n }\n }\n break\n /* $eq$ | $$eq$$ */\n case '$':\n if (p.token !== IMAGE &&\n p.token !== STRIKE &&\n \"$\" === p.pending\n ) {\n /* $$EQUATION_BLOCK$$\n ^\n */\n if ('$' === char) {\n p.token = MAYBE_EQ_BLOCK\n p.pending = pending_with_char\n continue\n }\n /* $123\n ^\n */\n else if (is_delimeter_or_number(char.charCodeAt(0))) {\n break\n }\n /* $EQUATION_INLINE$\n ^\n */\n else {\n add_text(p)\n add_token(p, EQUATION_INLINE)\n p.pending = char\n continue\n }\n }\n break\n /* [Image](url) */\n case '[':\n if (p.token !== IMAGE &&\n p.token !== LINK &&\n p.token !== EQUATION_BLOCK &&\n p.token !== EQUATION_INLINE &&\n ']' !== char\n ) {\n add_text(p)\n add_token(p, LINK)\n p.pending = char\n continue\n }\n break\n /*  */\n case '!':\n if (!(p.token === IMAGE) &&\n '[' === char\n ) {\n add_text(p)\n add_token(p, IMAGE)\n p.pending = \"\"\n continue\n }\n break\n /* Trim spaces */\n case ' ':\n if (p.pending.length === 1 && ' ' === char) {\n continue\n }\n break\n }\n\n /* foo http://...\n | ^\n */\n if (p.token !== IMAGE &&\n p.token !== LINK &&\n p.token !== EQUATION_BLOCK &&\n p.token !== EQUATION_INLINE &&\n 'h' === char &&\n (\" \" === p.pending ||\n \"\" === p.pending)\n ) {\n p.text += p.pending\n p.pending = char\n\n p.token = MAYBE_URL\n continue\n }\n\n /*\n No check hit\n */\n p.text += p.pending\n p.pending = char\n }\n\n add_text(p)\n}\n\n\n/**\n * @template T\n * @callback Renderer_Add_Token\n * @param {T } data\n * @param {Token} type\n * @returns {void } */\n\n/**\n * @template T\n * @callback Renderer_End_Token\n * @param {T } data\n * @returns {void } */\n\n/**\n * @template T\n * @callback Renderer_Add_Text\n * @param {T } data\n * @param {string} text\n * @returns {void } */\n\n/**\n * @template T\n * @callback Renderer_Set_Attr\n * @param {T } data\n * @param {Attr } type\n * @param {string} value\n * @returns {void } */\n\n/**\n * The renderer interface.\n * @template T\n * @typedef {object } Renderer\n * @property {T } data User data object. Available as first param in callbacks.\n * @property {Renderer_Add_Token<T>} add_token When the tokens starts.\n * @property {Renderer_End_Token<T>} end_token When the token ends.\n * @property {Renderer_Add_Text <T>} add_text To append text to current token. Can be called multiple times or none.\n * @property {Renderer_Set_Attr <T>} set_attr Set additional attributes of current token eg. the link url.\n */\n\n/** @typedef {Renderer<any>} Any_Renderer */\n\n\n/**\n * @typedef {object} Default_Renderer_Data\n * @property {HTMLElement[]} nodes\n * @property {number } index\n *\n * @typedef {Renderer <Default_Renderer_Data>} Default_Renderer\n * @typedef {Renderer_Add_Token<Default_Renderer_Data>} Default_Renderer_Add_Token\n * @typedef {Renderer_End_Token<Default_Renderer_Data>} Default_Renderer_End_Token\n * @typedef {Renderer_Add_Text <Default_Renderer_Data>} Default_Renderer_Add_Text\n * @typedef {Renderer_Set_Attr <Default_Renderer_Data>} Default_Renderer_Set_Attr\n */\n\n/**\n * @param {HTMLElement } root\n * @returns {Default_Renderer} */\nexport function default_renderer(root) {\n return {\n add_token: default_add_token,\n end_token: default_end_token,\n add_text: default_add_text,\n set_attr: default_set_attr,\n data : {\n nodes: /**@type {*}*/([root,,,,,]),\n index: 0,\n },\n }\n}\n\n/** @type {Default_Renderer_Add_Token} */\nexport function default_add_token(data, type) {\n\n /**@type {Element}*/ let parent = data.nodes[data.index]\n\n /**@type {HTMLElement}*/ let slot\n\n switch (type) {\n case DOCUMENT: return // document is provided\n case BLOCKQUOTE: slot = document.createElement(\"blockquote\");break\n case PARAGRAPH: slot = document.createElement(\"p\") ;break\n case LINE_BREAK: slot = document.createElement(\"br\") ;break\n case RULE: slot = document.createElement(\"hr\") ;break\n case HEADING_1: slot = document.createElement(\"h1\") ;break\n case HEADING_2: slot = document.createElement(\"h2\") ;break\n case HEADING_3: slot = document.createElement(\"h3\") ;break\n case HEADING_4: slot = document.createElement(\"h4\") ;break\n case HEADING_5: slot = document.createElement(\"h5\") ;break\n case HEADING_6: slot = document.createElement(\"h6\") ;break\n case ITALIC_AST:\n case ITALIC_UND: slot = document.createElement(\"em\") ;break\n case STRONG_AST:\n case STRONG_UND: slot = document.createElement(\"strong\") ;break\n case STRIKE: slot = document.createElement(\"s\") ;break\n case CODE_INLINE: slot = document.createElement(\"code\") ;break\n case RAW_URL:\n case LINK: slot = document.createElement(\"a\") ;break\n case IMAGE: slot = document.createElement(\"img\") ;break\n case LIST_UNORDERED:slot = document.createElement(\"ul\") ;break\n case LIST_ORDERED: slot = document.createElement(\"ol\") ;break\n case LIST_ITEM: slot = document.createElement(\"li\") ;break\n case CHECKBOX:\n let checkbox = slot = document.createElement(\"input\")\n checkbox.type = \"checkbox\"\n checkbox.disabled = true\n break\n case CODE_BLOCK:\n case CODE_FENCE:\n parent = parent.appendChild(document.createElement(\"pre\"))\n slot = document.createElement(\"code\")\n break\n case TABLE:\n slot = document.createElement(\"table\")\n break\n case TABLE_ROW:\n switch (parent.children.length) {\n case 0:\n parent = parent.appendChild(document.createElement(\"thead\"))\n break\n case 1:\n parent = parent.appendChild(document.createElement(\"tbody\"))\n break\n default:\n parent = parent.children[1]\n }\n slot = document.createElement(\"tr\")\n break\n case TABLE_CELL:\n slot = document.createElement(parent.parentElement?.tagName === \"THEAD\" ? \"th\" : \"td\")\n break\n case EQUATION_BLOCK: slot = document.createElement(\"equation-block\"); break\n case EQUATION_INLINE: slot = document.createElement(\"equation-inline\"); break\n }\n\n data.nodes[++data.index] = parent.appendChild(slot)\n}\n\n/** @type {Default_Renderer_End_Token} */\nexport function default_end_token(data) {\n data.index -= 1\n}\n\n/** @type {Default_Renderer_Add_Text} */\nexport function default_add_text(data, text) {\n data.nodes[data.index].appendChild(document.createTextNode(text))\n}\n\n/** @type {Default_Renderer_Set_Attr} */\nexport function default_set_attr(data, type, value) {\n data.nodes[data.index].setAttribute(attr_to_html_attr(type), value)\n}\n\n\n/**\n * @typedef {undefined} Logger_Renderer_Data\n *\n * @typedef {Renderer <Logger_Renderer_Data>} Logger_Renderer\n * @typedef {Renderer_Add_Token<Logger_Renderer_Data>} Logger_Renderer_Add_Token\n * @typedef {Renderer_End_Token<Logger_Renderer_Data>} Logger_Renderer_End_Token\n * @typedef {Renderer_Add_Text <Logger_Renderer_Data>} Logger_Renderer_Add_Text\n * @typedef {Renderer_Set_Attr <Logger_Renderer_Data>} Logger_Renderer_Set_Attr\n */\n\n/** @returns {Logger_Renderer} */\nexport function logger_renderer() {\n return {\n data: undefined,\n add_token: logger_add_token,\n end_token: logger_end_token,\n add_text: logger_add_text,\n set_attr: logger_set_attr,\n }\n}\n\n/** @type {Logger_Renderer_Add_Token} */\nexport function logger_add_token(data, type) {\n console.log(\"add_token:\", token_to_string(type))\n}\n\n/** @type {Logger_Renderer_End_Token} */\nexport function logger_end_token(data) {\n console.log(\"end_token\")\n}\n\n/** @type {Logger_Renderer_Add_Text} */\nexport function logger_add_text(data, text) {\n console.log('add_text: \"%s\"', text)\n}\n\n/** @type {Logger_Renderer_Set_Attr} */\nexport function logger_set_attr(data, type, value) {\n console.log('set_attr: %s=\"%s\"', attr_to_html_attr(type), value)\n}\n", "/**\n * Streaming markdown renderer.\n *\n * Wraps `streaming-markdown` \u2014 a tiny (~3 KB) parser purpose-built for\n * incremental token rendering. Unlike `marked`, which re-parses the full\n * string on every delta (O(n\u00B2) for streaming), `streaming-markdown` feeds\n * only the new bytes into its state machine and emits DOM nodes directly.\n *\n * Strategy: each `MarkdownView` instance owns one parser bound to a DOM\n * container. A signal `effect` watches `textSig` and feeds the *delta*\n * (computed from the previous length) into the parser on each change.\n * If the signal text shrinks (e.g. stream reset), the container is\n * cleared and a fresh parser created.\n */\n\nimport { useEffect, useMemo, useRef } from \"preact/hooks\";\nimport { effect, signal, type Signal } from \"@preact/signals\";\nimport * as smd from \"streaming-markdown\";\nimport { BRAND } from \"@/core/brand\";\n\ninterface MarkdownViewProps {\n /** Live markdown source; updates on every stream delta. */\n textSig: Signal<string>;\n /** `true` once the text part is final \u2014 triggers `parser_end()`. */\n doneSig: Signal<boolean>;\n}\n\nexport function MarkdownView({ textSig, doneSig }: MarkdownViewProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n el.classList.add(`${BRAND.cssPrefix}-md`);\n\n let renderer = smd.default_renderer(el);\n let parser = smd.parser(renderer);\n let lastLen = 0;\n\n const dispose = effect(() => {\n const current = textSig.value;\n const isDone = doneSig.value;\n\n // Reset on shrink (message restart / new conversation).\n if (current.length < lastLen) {\n el.replaceChildren();\n renderer = smd.default_renderer(el);\n parser = smd.parser(renderer);\n lastLen = 0;\n }\n\n const delta = current.slice(lastLen);\n if (delta) smd.parser_write(parser, delta);\n lastLen = current.length;\n\n if (isDone) smd.parser_end(parser);\n });\n\n // Harden links emitted by streaming-markdown.\n //\n // 1. Force `target=\"_blank\"` so clicking a citation never navigates\n // the host page away from the chat.\n // 2. `rel=\"noopener noreferrer\"` strips the back-channel + referrer\n // leak. Required for any user-supplied URL.\n // 3. Click delegation as a defense-in-depth: if anything strips the\n // attributes (host-page rewrites, Preact reconcile reset), the\n // listener still opens the link in a new window safely.\n //\n // streaming-markdown emits anchors directly into `el` \u2014 we don't\n // own the renderer, so a MutationObserver is the cleanest hook\n // that catches every anchor as it streams in.\n const observer = new MutationObserver((records) => {\n for (const r of records) {\n for (const node of r.addedNodes) {\n if (!(node instanceof HTMLElement)) continue;\n const anchors = node.matches?.(\"a\") ? [node] : [...(node.querySelectorAll?.(\"a\") ?? [])];\n for (const a of anchors) hardenLink(a);\n }\n }\n });\n observer.observe(el, { childList: true, subtree: true });\n // Cover the (rare) case where anchors already exist before this\n // observer attaches.\n for (const a of el.querySelectorAll(\"a\")) hardenLink(a);\n\n const onClick = (event: Event) => {\n const target = (event.target as Element | null)?.closest?.(\"a\");\n if (!target || !(target instanceof HTMLAnchorElement)) return;\n // Internal in-page anchors (`#section`) stay on the page.\n const href = target.getAttribute(\"href\") ?? \"\";\n if (!href || href.startsWith(\"#\")) return;\n event.preventDefault();\n window.open(href, \"_blank\", \"noopener,noreferrer\");\n };\n el.addEventListener(\"click\", onClick);\n\n return () => {\n dispose();\n observer.disconnect();\n el.removeEventListener(\"click\", onClick);\n };\n }, [textSig, doneSig]);\n\n return <div ref={ref} />;\n}\n\n/**\n * Render a fixed (non-streaming) markdown string \u2014 help articles, news\n * bodies. Wraps {@link MarkdownView} with one-shot signals so the same\n * battle-tested renderer (and link hardening) is reused without pulling\n * in a second markdown dependency.\n */\nexport function StaticMarkdown({ text }: { text: string }) {\n const textSig = useMemo(() => signal(text), [text]);\n const doneSig = useMemo(() => signal(true), []);\n return <MarkdownView textSig={textSig} doneSig={doneSig} />;\n}\n\n/** Apply safe defaults to a link emitted by the markdown renderer. */\nfunction hardenLink(a: HTMLAnchorElement): void {\n const href = a.getAttribute(\"href\") ?? \"\";\n // Skip in-page anchors \u2014 `target=\"_blank\"` on `#foo` would open a\n // useless empty new tab.\n if (!href || href.startsWith(\"#\")) return;\n if (a.target !== \"_blank\") a.target = \"_blank\";\n // Use `rel.split` to preserve any tokens streaming-markdown may\n // already have set.\n const tokens = new Set((a.rel || \"\").split(/\\s+/).filter(Boolean));\n tokens.add(\"noopener\");\n tokens.add(\"noreferrer\");\n a.rel = [...tokens].join(\" \");\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\nimport type { ReactivePart } from \"@/stream/messages\";\n\ninterface Props {\n part: Extract<ReactivePart, { kind: \"tool\" }>;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n}\n\nexport function ToolChip({ part, strings }: Props) {\n const p = BRAND.cssPrefix;\n return (\n <span class={`${p}-tool-chip`} role=\"status\">\n <span>{strings.usedTool}:</span>\n <strong>{part.toolName}</strong>\n </span>\n );\n}\n", "import { useComputed } from \"@preact/signals\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResponseMode, Strings } from \"@/core/config\";\nimport type { ReactiveMessage, ReactivePart } from \"@/stream/messages\";\nimport { MarkdownView } from \"./markdown\";\nimport { tid, TID } from \"./test-ids\";\nimport { ToolChip } from \"./tool-chip\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n message: ReactiveMessage;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n responseMode: ResponseMode;\n /** Render reasoning (\"thoughts\") disclosures. Default `false` (hidden). */\n showReasoning: boolean;\n /** Render tool-call chips. Default `false` (hidden). */\n showToolCalls: boolean;\n}\n\nexport function MessageBubble({ message, strings, responseMode, showReasoning, showToolCalls }: Props) {\n // Read the parts array unconditionally so the bubble always subscribes to its\n // identity. The reducer bumps it on `finish`, which is how a *buffered* bubble\n // learns the reply is complete and flips from \"loading\" to the full message.\n const parts = useComputed(() => message.partsSig.value);\n const partList = parts.value;\n // Reactive to text *content*, not just part identity: the streaming indicator\n // must stay up until the first visible answer token lands \u2014 reasoning doesn't count.\n const hasAnswerText = useComputed(() =>\n message.partsSig.value.some((part) => part.kind === \"text\" && part.textSig.value.length > 0),\n );\n // A *visible* reasoning block (present AND not hidden by config) carries the\n // live \"Thinking\u2026\" state itself, so the typing dots stand down for it.\n const reasoningVisible = useComputed(\n () => showReasoning && message.partsSig.value.some((part) => part.kind === \"reasoning\"),\n );\n const streaming = message.role === \"assistant\" && message.status === \"streaming\";\n // Buffered: hold the whole reply behind a neutral LOADING indicator and paint\n // it only once the stream finishes. Loading \u2260 thinking \u2014 a thinking block is\n // message *content* (shown inside the rendered reply when `showReasoning`).\n const bufferedHold = responseMode === \"buffered\" && streaming;\n // \"Working\" = streaming with no answer text yet. Drives the reasoning\n // disclosure's expanded \"Thinking\u2026\" pulse (collapses once the answer starts).\n const working = streaming && !hasAnswerText.value;\n // Streaming: typing dots until the first answer token (the reasoning\n // disclosure, when shown, carries its own live pulse instead).\n const showTypingDots = !bufferedHold && working && !reasoningVisible.value;\n\n return (\n <div class={`${p}-bubble-row`} data-role={message.role} data-testid={tid(TID.messageBubble, message.id)}>\n <div class={`${p}-bubble`}>\n {bufferedHold ? (\n <LoadingSpinner label={strings.loading} />\n ) : (\n partList.map((part) => (\n <PartView\n key={partKey(part)}\n part={part}\n active={working}\n strings={strings}\n showReasoning={showReasoning}\n showToolCalls={showToolCalls}\n />\n ))\n )}\n {showTypingDots && <TypingDots />}\n {message.status === \"error\" && message.errorText ? (\n <div class={`${p}-error`} role=\"alert\">\n <span>{message.errorText}</span>\n </div>\n ) : null}\n </div>\n </div>\n );\n}\n\nfunction PartView({\n part,\n active,\n strings,\n showReasoning,\n showToolCalls,\n}: {\n part: ReactivePart;\n active: boolean;\n strings: Strings;\n showReasoning: boolean;\n showToolCalls: boolean;\n}) {\n switch (part.kind) {\n case \"text\":\n return <MarkdownView textSig={part.textSig} doneSig={part.doneSig} />;\n case \"reasoning\":\n return showReasoning ? <ReasoningView part={part} active={active} strings={strings} /> : null;\n case \"tool\":\n return showToolCalls ? <ToolChip part={part} strings={strings} /> : null;\n case \"file\":\n if (part.mediaType.startsWith(\"image/\")) {\n return <img src={part.url} alt=\"\" loading=\"lazy\" />;\n }\n return (\n <a href={part.url} target=\"_blank\" rel=\"noreferrer noopener\">\n {part.url}\n </a>\n );\n case \"source\":\n // Citations are intentionally not rendered in the visible\n // transcript \u2014 we keep them out of the user-facing thread (the\n // reducer still tracks them for de-dup / future surfacing).\n return null;\n }\n}\n\n/**\n * Reasoning (\"thinking\") renders as a collapsible disclosure, visually distinct\n * from the answer \u2014 never inlined into the message body. It's expanded with a\n * live \"Thinking\u2026\" pulse while the model works, then auto-collapses to a quiet\n * \"Thoughts\" toggle once the answer starts streaming. Reasoning is ephemeral:\n * the backend doesn't replay it, so it simply won't be present after a reload \u2014\n * but the answer body now reads identically live and on refresh.\n */\nfunction ReasoningView({\n part,\n active,\n strings,\n}: {\n part: Extract<ReactivePart, { kind: \"reasoning\" }>;\n active: boolean;\n strings: Strings;\n}) {\n return (\n <details class={`${p}-reasoning`} open={active} data-active={active ? \"true\" : undefined}>\n <summary class={`${p}-reasoning-summary`}>\n <span class={`${p}-reasoning-label`}>{active ? strings.thinking : strings.thoughts}</span>\n </summary>\n <div class={`${p}-reasoning-body`}>\n <MarkdownView textSig={part.textSig} doneSig={part.doneSig} />\n </div>\n </details>\n );\n}\n\nfunction partKey(part: ReactivePart): string {\n switch (part.kind) {\n case \"text\":\n case \"reasoning\":\n return `${part.kind}:${part.id}`;\n case \"tool\":\n return `tool:${part.toolCallId}`;\n case \"file\":\n return `file:${part.url}`;\n case \"source\":\n return `source:${part.sourceId}`;\n }\n}\n\nfunction TypingDots() {\n return (\n <span class={`${p}-typing`} aria-hidden=\"true\">\n <span />\n <span />\n <span />\n </span>\n );\n}\n\n/** Neutral \"loading\" spinner for buffered replies \u2014 distinct from the reasoning\n * (\"Thinking\u2026\") disclosure, which is message content, not a wait state. */\nfunction LoadingSpinner({ label }: { label: string }) {\n return (\n <span class={`${p}-loading`} role=\"status\">\n <span class={`${p}-loading-spinner`} aria-hidden=\"true\" />\n <span class={`${p}-loading-label`}>{label}</span>\n </span>\n );\n}\n", "import { useEffect, useLayoutEffect, useRef } from \"preact/hooks\";\nimport { type Signal, useComputed } from \"@preact/signals\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResponseMode, Strings } from \"@/core/config\";\nimport type { ReactiveMessage } from \"@/stream/messages\";\nimport { MessageBubble } from \"./message-bubble\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n messagesSig: Signal<ReactiveMessage[]>;\n /** Pre-resolved string map (`options.strings`). */\n strings: Strings;\n responseMode: ResponseMode;\n /** Show reasoning (\"thoughts\") blocks. Default `false` (resolved upstream). */\n showReasoning: boolean;\n /** Show tool-call chips. Default `false` (resolved upstream). */\n showToolCalls: boolean;\n /**\n * True while the App is fetching message history from the backend\n * (resume path). Surfaces a small placeholder so reload of a long thread\n * doesn't flash empty before the network settles.\n */\n loading?: boolean;\n}\n\n/** Distance-from-bottom (px) below which \"stick to bottom\" still applies. */\nconst STICK_THRESHOLD = 120;\n\nexport function MessageList({ messagesSig, strings, responseMode, showReasoning, showToolCalls, loading }: Props) {\n const ref = useRef<HTMLDivElement>(null);\n const messages = useComputed(() => messagesSig.value);\n\n // Tracks whether the list has ever held a message. Flips from `false` to\n // `true` exactly once \u2014 when the resume-load (or a fresh send) seeds the\n // first message into an empty list. Used to distinguish \"initial\n // hydration\" (always pin-to-bottom) from \"new-message-while-scrolled-up\"\n // (only pin if near bottom \u2014 respect the user's read position).\n const hasHydratedRef = useRef(false);\n\n // ---- 1. Mount + hydration: pin to the bottom as content settles --------\n // Runs on mount AND when the first message arrives post-mount. Inline\n // mode's worst case: panel stays mounted across reloads, then `loadSession`\n // drops the full thread into `messagesSig` ~300-1000 ms later \u2014 well\n // after any fixed observer window would have torn itself down.\n //\n // The subtle problem: each <MessageBubble> contains a <MarkdownView> whose\n // `useEffect` writes the historical text into its ref AFTER this effect\n // runs. Parent `useLayoutEffect` + `requestAnimationFrame` both fire\n // BEFORE the children's useEffects flush \u2014 so reading `scrollHeight`\n // immediately gives us the height of *empty* bubble divs, not the real\n // post-markdown height. Worse: when that markdown expands, it grows the\n // list's *scrollHeight* but not its own clientHeight (list is flex-sized),\n // so a `ResizeObserver` on the list alone misses the expansion entirely.\n //\n // Fix: combine two observers during a 300 ms hydration window:\n // (a) `MutationObserver` \u2014 fires whenever a descendant's DOM changes\n // (markdown injecting text nodes, code blocks highlighting, etc.).\n // This is what actually catches the async markdown renders.\n // (b) `ResizeObserver` \u2014 fires when sibling rails below the list\n // (suggestions row appearing, composer growing) shrink the list's\n // clientHeight; without it, pinBottom would read a stale clientHeight\n // and leave the last line clipped behind the suggestions row.\n // Both trigger the same `pinBottom`, which is safe to call repeatedly.\n const firstMessageId = messages.value[0]?.id;\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n if (messages.value.length === 0) return; // wait for content\n hasHydratedRef.current = true;\n const pinBottom = () => {\n el.scrollTop = el.scrollHeight;\n };\n pinBottom();\n const resizeObs = new ResizeObserver(pinBottom);\n resizeObs.observe(el);\n const mutationObs = new MutationObserver(pinBottom);\n mutationObs.observe(el, { childList: true, subtree: true, characterData: true });\n const timerId = setTimeout(() => {\n resizeObs.disconnect();\n mutationObs.disconnect();\n }, 300);\n return () => {\n resizeObs.disconnect();\n mutationObs.disconnect();\n clearTimeout(timerId);\n };\n // Key on `firstMessageId`: re-run when we go from empty \u2192 populated, or\n // when the chat swaps (history pane \u2192 different chat loaded). Length\n // alone would miss the chat-swap case when both chats have the same\n // count. Intentionally not depending on `messages.value` (every token\n // would retrigger).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [firstMessageId]);\n\n // ---- 2. Length change: stick to the bottom if the user is near it -----\n // Skips the hydration moment (effect #1 handles that unconditionally).\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n if (!hasHydratedRef.current) return;\n const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;\n if (distanceFromBottom < STICK_THRESHOLD) el.scrollTop = el.scrollHeight;\n }, [messages.value.length]);\n\n // ---- 3. Streaming + finish ---------------------------------------------\n // While the last message is streaming, poll-pin the view to the bottom so\n // tokens arriving mid-paragraph don't drift the reader off-screen. When\n // the stream finishes we do ONE more rAF-deferred scroll \u2014 by that time\n // streaming-markdown has flushed its final block (closing list items,\n // code-block highlighting, etc.), and `scrollHeight` reflects the\n // post-paint layout. Without this rAF the previous polling tick can fire\n // *before* the final block paints, leaving the bubble's last line clipped.\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const last = messages.value.at(-1);\n if (!last) return;\n\n if (last.status !== \"streaming\") {\n const rafId = requestAnimationFrame(() => {\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n if (distance < STICK_THRESHOLD * 2) el.scrollTop = el.scrollHeight;\n });\n return () => cancelAnimationFrame(rafId);\n }\n\n const id = setInterval(() => {\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n if (distance < STICK_THRESHOLD * 2) el.scrollTop = el.scrollHeight;\n }, 250);\n return () => clearInterval(id);\n }, [messages.value]);\n\n return (\n <div ref={ref} class={`${p}-list`} role=\"log\" aria-live=\"polite\" aria-relevant=\"additions text\">\n {loading && messages.value.length === 0 ? (\n <div class={`${p}-list-loading`} role=\"status\">\n {strings.messagesLoading}\n </div>\n ) : null}\n {messages.value.map((m) => (\n <MessageBubble\n key={m.id}\n message={m}\n strings={strings}\n responseMode={responseMode}\n showReasoning={showReasoning}\n showToolCalls={showToolCalls}\n />\n ))}\n </div>\n );\n}\n", "/**\n * Shared state machine for an accessible popover menu.\n *\n * Three header components (`OverflowMenu`, `LanguageSwitcher`,\n * `ThemeSwitcher`) all render the same pattern: an icon trigger, a\n * dropdown of items, arrow-key nav, `Escape` to close, click-outside to\n * close, and focus return to the trigger on dismiss. Before this hook\n * each maintained its own copy of the state + effects \u2014 ~60 LOC\n * triplicated.\n *\n * The hook owns only the state machine and the keyboard / click handlers.\n * Callers render their own trigger + menu JSX around it, so shape-specific\n * concerns (icon, items heterogeneity, option labels) stay in the caller.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"preact/hooks\";\nimport type { RefObject } from \"preact\";\n\nexport interface UsePopoverMenuOptions {\n /** Total number of selectable items \u2014 drives arrow-key bounds. */\n itemCount: number;\n /**\n * Which item to focus when the menu opens. Runs once per open\n * transition \u2014 the typical use is \"focus the currently-active option\"\n * so arrow-nav starts from the user's current pick. Returning `-1`\n * falls back to item 0.\n */\n initialFocusIndex?: () => number;\n}\n\nexport interface PopoverMenu {\n open: boolean;\n focusIndex: number;\n triggerRef: RefObject<HTMLButtonElement>;\n menuRef: RefObject<HTMLDivElement>;\n /** Toggle open state. Typical binding: `onClick` on the trigger. */\n toggle: () => void;\n /** Explicitly close + return focus to the trigger. */\n close: () => void;\n /** Keyboard handler for the trigger button \u2014 opens on Down/Enter/Space. */\n onTriggerKey: (e: KeyboardEvent) => void;\n /** Keyboard handler for the menu \u2014 arrows, Home/End, Escape. */\n onMenuKey: (e: KeyboardEvent) => void;\n}\n\nexport function usePopoverMenu({ itemCount, initialFocusIndex }: UsePopoverMenuOptions): PopoverMenu {\n const [open, setOpen] = useState(false);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const [focusIndex, setFocusIndex] = useState(-1);\n\n const close = useCallback(() => {\n setOpen(false);\n setFocusIndex(-1);\n triggerRef.current?.focus();\n }, []);\n\n const toggle = useCallback(() => setOpen((v) => !v), []);\n\n // Click-outside closes. Uses composed path so it works inside shadow DOM.\n useEffect(() => {\n if (!open) return;\n const onDocClick = (e: Event) => {\n const menu = menuRef.current;\n const trigger = triggerRef.current;\n if (!menu || !trigger) return;\n const path = e.composedPath();\n if (path.includes(menu) || path.includes(trigger)) return;\n setOpen(false);\n setFocusIndex(-1);\n };\n document.addEventListener(\"click\", onDocClick, true);\n return () => document.removeEventListener(\"click\", onDocClick, true);\n }, [open]);\n\n // Pick the initial focus target on open. Clamped to valid bounds so a\n // bad `initialFocusIndex` can't leave focusIndex pointing past the last\n // item (the subsequent effect would silently fail to focus anything).\n useEffect(() => {\n if (!open) return;\n if (itemCount === 0) return;\n const desired = initialFocusIndex?.() ?? 0;\n const clamped = desired < 0 || desired >= itemCount ? 0 : desired;\n setFocusIndex(clamped);\n }, [open, itemCount, initialFocusIndex]);\n\n // Apply focus to the current item.\n useEffect(() => {\n if (!open || focusIndex < 0) return;\n const items = menuRef.current?.querySelectorAll<HTMLElement>(\"[role='menuitem']\");\n items?.[focusIndex]?.focus();\n }, [open, focusIndex]);\n\n const onTriggerKey = useCallback((e: KeyboardEvent) => {\n if (e.key === \"ArrowDown\" || e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setOpen(true);\n }\n }, []);\n\n const onMenuKey = useCallback(\n (e: KeyboardEvent) => {\n if (itemCount === 0) return;\n switch (e.key) {\n case \"Escape\":\n e.preventDefault();\n close();\n return;\n case \"ArrowDown\":\n e.preventDefault();\n setFocusIndex((i) => (i + 1) % itemCount);\n return;\n case \"ArrowUp\":\n e.preventDefault();\n setFocusIndex((i) => (i - 1 + itemCount) % itemCount);\n return;\n case \"Home\":\n e.preventDefault();\n setFocusIndex(0);\n return;\n case \"End\":\n e.preventDefault();\n setFocusIndex(itemCount - 1);\n }\n },\n [itemCount, close],\n );\n\n return { open, focusIndex, triggerRef, menuRef, toggle, close, onTriggerKey, onMenuKey };\n}\n", "/**\n * 3-dot overflow menu for the panel header.\n *\n * Minimal accessible dropdown \u2014 `role=\"menu\"` + `role=\"menuitem\"`, arrow\n * nav, `Home` / `End`, `Enter` / `Space` / click to select, `Escape` +\n * click-outside to close. State + keyboard wiring is shared with\n * `LanguageSwitcher` and `ThemeSwitcher` via {@link usePopoverMenu}; this\n * file only owns the item layout + checkmark-on-switch rendering.\n *\n * Items are either `\"button\"` (click \u2192 `onSelect`) or `\"switch\"`\n * (toggleable, shows a right-aligned checkmark when `on=true`). Either\n * way, selecting an item closes the menu immediately.\n */\n\nimport type { JSX } from \"preact\";\nimport { BRAND } from \"@/core/brand\";\nimport { CheckIcon, MoreIcon } from \"./icons\";\nimport { usePopoverMenu } from \"./use-popover-menu\";\n\nexport interface MenuItem {\n id: string;\n label: string;\n icon?: JSX.Element;\n onSelect: () => void;\n /** Switch renders a right-aligned checkmark when `on=true`. */\n type?: \"button\" | \"switch\";\n on?: boolean;\n disabled?: boolean;\n /**\n * BCP-47 tag applied to the rendered item \u2014 used by the expanded\n * language picker so each locale's name renders in its own script.\n */\n lang?: string;\n}\n\ninterface Props {\n items: MenuItem[];\n triggerLabel: string;\n}\n\nexport function OverflowMenu({ items, triggerLabel }: Props) {\n const p = BRAND.cssPrefix;\n const menu = usePopoverMenu({ itemCount: items.length });\n\n const handleSelect = (item: MenuItem) => {\n if (item.disabled) return;\n item.onSelect();\n // Every item closes the menu immediately on select \u2014 including\n // switch-style toggles (sound / language). Picking an action and\n // seeing the menu dismiss is the expected behaviour.\n menu.close();\n };\n\n return (\n <div class={`${p}-menu-wrap`}>\n <button\n ref={menu.triggerRef}\n type=\"button\"\n class={`${p}-icon-btn`}\n aria-label={triggerLabel}\n aria-haspopup=\"menu\"\n aria-expanded={menu.open}\n title={triggerLabel}\n onClick={menu.toggle}\n onKeyDown={menu.onTriggerKey}\n >\n <MoreIcon />\n </button>\n {menu.open ? (\n <div\n ref={menu.menuRef}\n class={`${p}-menu`}\n role=\"menu\"\n aria-label={triggerLabel}\n onKeyDown={menu.onMenuKey}\n // Prevents the panel's focus-trap from stealing focus on open.\n tabIndex={-1}\n >\n {items.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"menuitem\"\n class={`${p}-menu-item`}\n aria-pressed={item.type === \"switch\" ? item.on : undefined}\n disabled={item.disabled}\n lang={item.lang}\n onClick={() => handleSelect(item)}\n >\n {item.icon ? <span class={`${p}-menu-icon`}>{item.icon}</span> : null}\n <span class={`${p}-menu-label`}>{item.label}</span>\n {item.type === \"switch\" && item.on ? (\n <span class={`${p}-menu-check`} aria-hidden=\"true\">\n <CheckIcon />\n </span>\n ) : null}\n </button>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n", "/**\n * Group chat-history rows by friendly date buckets \u2014 Today / Yesterday /\n * Last 7 days / Older. Pure function, no i18n lookups; UI layer does the\n * label translation so this stays cheap to test.\n *\n * Input timestamps are ISO-8601 strings. Unparseable rows fall into \"Older\".\n */\n\nimport type { SessionSummary } from \"@/stream/protocol\";\n\nexport type HistoryBucket = \"today\" | \"yesterday\" | \"lastWeek\" | \"older\";\n\nexport interface HistoryGroup {\n bucket: HistoryBucket;\n sessions: SessionSummary[];\n}\n\n/** Visible group order, in the order the UI should render them. */\nexport const HISTORY_BUCKETS: readonly HistoryBucket[] = [\"today\", \"yesterday\", \"lastWeek\", \"older\"];\n\n/** Given a reference \"now\" and a row timestamp, return the bucket. */\nexport function bucketFor(nowMs: number, iso: string): HistoryBucket {\n const t = Date.parse(iso);\n if (!Number.isFinite(t)) return \"older\";\n const diffDays = Math.floor((startOfDay(nowMs) - startOfDay(t)) / (24 * 60 * 60 * 1000));\n if (diffDays <= 0) return \"today\";\n if (diffDays === 1) return \"yesterday\";\n if (diffDays < 7) return \"lastWeek\";\n return \"older\";\n}\n\n/**\n * Partition `sessions` into ordered buckets. Each bucket's `sessions` stays in\n * the server-provided order (assumed: newest first within the bucket).\n */\nexport function groupByBucket(nowMs: number, sessions: readonly SessionSummary[]): HistoryGroup[] {\n const buckets = new Map<HistoryBucket, SessionSummary[]>();\n for (const chat of sessions) {\n const b = bucketFor(nowMs, chat.lastMessageAt);\n const list = buckets.get(b);\n if (list) list.push(chat);\n else buckets.set(b, [chat]);\n }\n return HISTORY_BUCKETS.filter((b) => buckets.has(b)).map((b) => ({ bucket: b, sessions: buckets.get(b)! }));\n}\n\n/**\n * UTC-aligned day boundary. UTC keeps the function pure + deterministic\n * across timezones; the worst-case UX skew is ~1 day boundary drift for\n * users far from UTC, which is acceptable for history grouping.\n */\nfunction startOfDay(ms: number): number {\n const d = new Date(ms);\n d.setUTCHours(0, 0, 0, 0);\n return d.getTime();\n}\n", "/**\n * Persistent conversation list \u2014 rendered by the chat-history-pane\n * (slide-over view in widget/inline modes) and the page-mode sidebar.\n *\n * Pure data + presentation: fetches via transport.listSessions() on mount,\n * groups by date bucket, calls `onSelect` when a row is tapped. No\n * container styling \u2014 wrapper supplies the panel chrome.\n */\n\nimport { useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\nimport { logger } from \"@/core/logger\";\nimport type { StringKey } from \"@/i18n/strings\";\nimport { PlusIcon } from \"./icons\";\nimport { tid, TID } from \"./test-ids\";\n\nconst log = logger.scope(\"history\");\nimport type { SessionSummary } from \"@/stream/protocol\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport { type HistoryBucket, groupByBucket } from \"./history-groups\";\n\nexport interface ConversationListProps {\n transport: AgentTransport;\n strings: Strings;\n visitorId: string;\n onSelect: (chat: SessionSummary) => void;\n /** Optional: surfaces a \"+ New chat\" affordance above the list. */\n onNewChat?: () => void;\n}\n\nconst BUCKET_TO_STRING: Record<HistoryBucket, StringKey> = {\n today: \"dateToday\",\n yesterday: \"dateYesterday\",\n lastWeek: \"dateLastWeek\",\n older: \"dateOlder\",\n};\n\nexport function ConversationList({ transport, strings, visitorId, onSelect, onNewChat }: ConversationListProps) {\n const p = BRAND.cssPrefix;\n const [state, setState] = useState<\"loading\" | \"loaded\" | \"error\">(\"loading\");\n const [sessions, setChats] = useState<SessionSummary[]>([]);\n\n useEffect(() => {\n let cancelled = false;\n transport\n .listSessions({ visitorId })\n .then((res) => {\n if (cancelled) return;\n setChats(res.sessions);\n setState(\"loaded\");\n })\n .catch((err) => {\n if (cancelled) return;\n log.warn(\"listSessions failed\", { err });\n setState(\"error\");\n });\n return () => {\n cancelled = true;\n };\n }, [transport, visitorId]);\n\n // Pinned footer action \u2014 sits BELOW the scrolling list, not on top of it.\n const newChatButton = onNewChat ? (\n <div class={`${p}-history-footer`}>\n <button type=\"button\" class={`${p}-history-new`} onClick={onNewChat} data-testid={TID.sidebarNewChat}>\n <PlusIcon />\n {strings.historyNewChat}\n </button>\n </div>\n ) : null;\n\n if (state === \"loading\") {\n return (\n <>\n <div class={`${p}-history-empty`}>{strings.historyLoading}</div>\n {newChatButton}\n </>\n );\n }\n if (state === \"error\" || sessions.length === 0) {\n return (\n <>\n <div class={`${p}-history-empty`}>{strings.historyEmpty}</div>\n {newChatButton}\n </>\n );\n }\n\n const groups = groupByBucket(Date.now(), sessions);\n return (\n <>\n <div class={`${p}-history`} role=\"list\">\n {groups.map((group) => (\n <div key={group.bucket} class={`${p}-history-group`}>\n <div class={`${p}-history-heading`}>{strings[BUCKET_TO_STRING[group.bucket]]}</div>\n {group.sessions.map((chat) => (\n <button\n key={chat.sessionId}\n type=\"button\"\n role=\"listitem\"\n class={`${p}-history-item`}\n onClick={() => onSelect(chat)}\n data-closed={chat.canContinue ? undefined : \"true\"}\n data-testid={tid(TID.historyItem, chat.sessionId)}\n >\n <span class={`${p}-history-title`}>{chat.title}</span>\n {chat.preview ? <span class={`${p}-history-preview`}>{chat.preview}</span> : null}\n </button>\n ))}\n </div>\n ))}\n </div>\n {newChatButton}\n </>\n );\n}\n", "/**\n * Slide-over chat history view (widget + inline modes).\n *\n * Thin wrapper around {@link ConversationList} so the persistent\n * page-mode sidebar can share the same data + UI.\n */\n\nimport { ConversationList, type ConversationListProps } from \"./conversation-list\";\n\nexport function ChatHistoryPane(props: ConversationListProps) {\n return <ConversationList {...props} />;\n}\n", "/**\n * Language picker \u2014 globe icon button + dropdown of `availableLocales`.\n *\n * State + keyboard wiring is shared with `OverflowMenu` and\n * `ThemeSwitcher` via {@link usePopoverMenu}; this file only owns the\n * locale-specific rendering (display name, active checkmark, lang\n * attribute so each row renders in its own script).\n *\n * Display names come from {@link Intl.DisplayNames} expressed in the\n * **active** UI locale \u2014 so a French user sees \"fran\u00E7ais\", \"anglais\",\n * \"espagnol\" instead of \"French\", \"English\", \"Spanish\".\n */\n\nimport { useCallback } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport { localeDisplayName } from \"@/i18n/strings\";\nimport { CheckIcon, GlobeIcon } from \"./icons\";\nimport { usePopoverMenu } from \"./use-popover-menu\";\n\ninterface Props {\n available: readonly string[];\n /** Currently active locale \u2014 gets the checkmark in the menu. */\n active: string;\n onSelect: (locale: string) => void;\n triggerLabel: string;\n}\n\nexport function LanguageSwitcher({ available, active, onSelect, triggerLabel }: Props) {\n const p = BRAND.cssPrefix;\n // Open the menu with focus on the active locale \u2014 so arrow-nav starts\n // from the user's current pick rather than the top of the list.\n const initialFocusIndex = useCallback(() => available.indexOf(active), [available, active]);\n const menu = usePopoverMenu({ itemCount: available.length, initialFocusIndex });\n\n const handleSelect = (locale: string) => {\n onSelect(locale);\n menu.close();\n };\n\n return (\n <div class={`${p}-menu-wrap`}>\n <button\n ref={menu.triggerRef}\n type=\"button\"\n class={`${p}-icon-btn`}\n aria-label={triggerLabel}\n aria-haspopup=\"menu\"\n aria-expanded={menu.open}\n title={triggerLabel}\n onClick={menu.toggle}\n onKeyDown={menu.onTriggerKey}\n >\n <GlobeIcon />\n </button>\n {menu.open ? (\n <div\n ref={menu.menuRef}\n class={`${p}-menu`}\n role=\"menu\"\n aria-label={triggerLabel}\n onKeyDown={menu.onMenuKey}\n tabIndex={-1}\n >\n {available.map((locale) => {\n const isActive = locale === active;\n return (\n <button\n key={locale}\n type=\"button\"\n role=\"menuitem\"\n class={`${p}-menu-item`}\n lang={locale}\n aria-current={isActive ? \"true\" : undefined}\n onClick={() => handleSelect(locale)}\n >\n <span class={`${p}-menu-label`}>{localeDisplayName(locale, active)}</span>\n {isActive ? (\n <span class={`${p}-menu-check`} aria-hidden=\"true\">\n <CheckIcon />\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n ) : null}\n </div>\n );\n}\n", "/**\n * Theme picker \u2014 sun/moon/auto icon button + dropdown with three options.\n *\n * State + keyboard wiring is shared with `OverflowMenu` and\n * `LanguageSwitcher` via {@link usePopoverMenu}; this file only owns the\n * theme-specific rendering (trigger icon reflects the active pick,\n * checkmark on the selected option).\n *\n * The component is controlled \u2014 state for the actual theme lives in\n * `App` (which writes `data-theme` on the host + persists the choice).\n * This component just surfaces the dropdown UI.\n */\n\nimport { useCallback } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ThemePreference } from \"@/core/config\";\nimport { CheckIcon, MoonIcon, SunIcon, ThemeAutoIcon } from \"./icons\";\nimport { usePopoverMenu } from \"./use-popover-menu\";\n\ninterface Props {\n active: ThemePreference;\n onSelect: (mode: ThemePreference) => void;\n triggerLabel: string;\n /** Per-option labels (resolved from i18n strings by the caller). */\n optionLabels: Record<ThemePreference, string>;\n}\n\nconst OPTIONS: readonly ThemePreference[] = [\"auto\", \"light\", \"dark\"];\n\nfunction iconFor(mode: ThemePreference) {\n switch (mode) {\n case \"light\":\n return <SunIcon />;\n case \"dark\":\n return <MoonIcon />;\n case \"auto\":\n return <ThemeAutoIcon />;\n }\n}\n\nexport function ThemeSwitcher({ active, onSelect, triggerLabel, optionLabels }: Props) {\n const p = BRAND.cssPrefix;\n const initialFocusIndex = useCallback(() => OPTIONS.indexOf(active), [active]);\n const menu = usePopoverMenu({ itemCount: OPTIONS.length, initialFocusIndex });\n\n const handleSelect = (mode: ThemePreference) => {\n onSelect(mode);\n menu.close();\n };\n\n return (\n <div class={`${p}-menu-wrap`}>\n <button\n ref={menu.triggerRef}\n type=\"button\"\n class={`${p}-icon-btn`}\n aria-label={triggerLabel}\n aria-haspopup=\"menu\"\n aria-expanded={menu.open}\n title={triggerLabel}\n data-theme-active={active}\n onClick={menu.toggle}\n onKeyDown={menu.onTriggerKey}\n >\n {iconFor(active)}\n </button>\n {menu.open ? (\n <div\n ref={menu.menuRef}\n class={`${p}-menu`}\n role=\"menu\"\n aria-label={triggerLabel}\n onKeyDown={menu.onMenuKey}\n tabIndex={-1}\n >\n {OPTIONS.map((mode) => {\n const isActive = mode === active;\n return (\n <button\n key={mode}\n type=\"button\"\n role=\"menuitem\"\n class={`${p}-menu-item`}\n aria-current={isActive ? \"true\" : undefined}\n onClick={() => handleSelect(mode)}\n >\n <span class={`${p}-menu-icon`}>{iconFor(mode)}</span>\n <span class={`${p}-menu-label`}>{optionLabels[mode]}</span>\n {isActive ? (\n <span class={`${p}-menu-check`} aria-hidden=\"true\">\n <CheckIcon />\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n ) : null}\n </div>\n );\n}\n", "import { BRAND } from \"@/core/brand\";\nimport type { SessionSuggestion } from \"@/stream/protocol\";\nimport { tid, TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n suggestions: SessionSuggestion[];\n onPick: (s: SessionSuggestion) => void;\n}\n\n/**\n * Quick-reply chips rendered below the welcome message list. One-shot \u2014\n * dismissed as soon as the user sends anything (handled by the App).\n */\nexport function Suggestions({ suggestions, onPick }: Props) {\n if (suggestions.length === 0) return null;\n return (\n <div class={`${p}-suggestions`} role=\"group\" aria-label=\"Suggested replies\">\n {suggestions.map((s, i) => (\n <button\n type=\"button\"\n class={`${p}-suggestion`}\n onClick={() => onPick(s)}\n key={s.id}\n data-testid={tid(TID.suggestion, i)}\n >\n {s.label}\n </button>\n ))}\n </div>\n );\n}\n", "import { useCallback, useEffect, useRef } from \"preact/hooks\";\nimport { ResizeGrip } from \"./resize-grip\";\nimport { useDragMove } from \"./use-drag-move\";\nimport { type Signal, useComputed } from \"@preact/signals\";\nimport type { LocalAttachment } from \"@/core/attachments\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ActionName, ResolvedOptions, Strings } from \"@/core/config\";\nimport { canShowClose, visibleActions } from \"./action-visibility\";\nimport type { EventBus } from \"@/core/events\";\nimport type { FeedbackBus } from \"@/core/feedback\";\nimport type { WidgetSize } from \"@/core/persistence\";\nimport { trapFocus } from \"@/shadow/a11y\";\nimport type { ReactiveMessage } from \"@/stream/messages\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport type { SessionAgent, SessionSuggestion } from \"@/stream/protocol\";\nimport { AgentBadge } from \"./agent-badge\";\nimport { Composer, type ComposerAttachAPI } from \"./composer\";\nimport { DropZone } from \"./drop-zone\";\nimport { useFileDrop } from \"./use-file-drop\";\nimport type { JSX } from \"preact\";\nimport {\n BackIcon,\n CloseIcon,\n CollapseIcon,\n ExpandIcon,\n FullscreenExitIcon,\n FullscreenIcon,\n HistoryIcon,\n PopOutIcon,\n SpeakerIcon,\n SpeakerMutedIcon,\n TrashIcon,\n} from \"./icons\";\nimport { MessageList } from \"./message-list\";\nimport { OverflowMenu, type MenuItem } from \"./overflow-menu\";\nimport { ChatHistoryPane } from \"./chat-history-pane\";\nimport { LanguageSwitcher } from \"./language-switcher\";\nimport { ACTION_TID, TID } from \"./test-ids\";\nimport { ThemeSwitcher } from \"./theme-switcher\";\nimport { Suggestions } from \"./suggestions\";\n\nimport type { PanelSize } from \"./host-mode\";\n\nconst p = BRAND.cssPrefix;\n\nexport type PanelView = \"chat\" | \"history\";\n\ninterface Props {\n options: ResolvedOptions;\n transport: AgentTransport;\n feedback: FeedbackBus;\n bus: EventBus;\n panelSize: PanelSize;\n view: PanelView;\n /** `false` disables the composer (read-only chat). */\n canSend: boolean;\n isStreaming: boolean;\n /**\n * True while the App is fetching message history for a resumed chat.\n * Drives a placeholder in MessageList so reload of a long thread doesn't\n * flash empty before the network settles.\n */\n loadingMessages: boolean;\n messagesSig: Signal<ReactiveMessage[]>;\n agent: SessionAgent | null;\n suggestions: SessionSuggestion[];\n /** Cached drag-resized panel dimensions. `null` = use CSS defaults. */\n initialWidgetSize: WidgetSize | null;\n /** Anonymous browser id \u2014 needed for visitor-scoped history queries. */\n visitorId: string;\n /** Close the widget. Absent in non-closeable modes (standalone / page). */\n onClose?: () => void;\n /**\n * When set, the chat-view header renders a leading Back chevron (used\n * when the Panel is mounted as a drilled-in thread inside the messenger\n * home). Undefined \u2192 no Back button (floating / page / inline panels).\n */\n onBack?: () => void;\n /**\n * Start a fresh conversation. Bound to both the `clear` header action\n * and the read-only banner's \"Start new conversation\" CTA \u2014 same effect.\n */\n onClear: () => void;\n /**\n * Toggle the expanded / fullscreen tier. The buttons that call these\n * are gated per-mode by `visibleActions` (expand only shows in\n * floating; fullscreen everywhere except standalone), so passing\n * non-applicable modes is harmless \u2014 the user can't click what isn't\n * rendered.\n */\n onExpand: () => void;\n onFullscreen: () => void;\n onPopOut: () => void;\n onSoundToggle: () => void;\n onToggleHistory: () => void;\n onSelectHistoryChat: (sessionId: string) => void;\n /** Start a fresh conversation and return to the chat view (also used by the\n * history pane's empty-state CTA). */\n onNewChat: () => void;\n /** End-user picked a different language from the picker. */\n onLocaleChange: (locale: string) => void;\n /** End-user picked a different theme from the theme switcher. */\n onThemeChange: (mode: \"auto\" | \"light\" | \"dark\") => void;\n /** End-user dragged the resize grip \u2014 fired with resolved dimensions. */\n onWidgetSizeChange: (size: WidgetSize) => void;\n onSend: (text: string, attachments: LocalAttachment[]) => void;\n onStop: () => void;\n onSuggestion: (s: SessionSuggestion) => void;\n}\n\n/**\n * Action names that render via a plain icon button (or as an overflow\n * menu row). `language` and `theme` are excluded \u2014 the Header renders\n * them as dedicated popovers via `LanguageSwitcher` / `ThemeSwitcher`,\n * so they don't need a descriptor entry.\n */\ntype DescribedAction = Exclude<ActionName, \"language\" | \"theme\">;\ntype DescriptorTable = Record<DescribedAction, ActionDescriptor>;\n\ninterface ActionDescriptor {\n icon: JSX.Element;\n label: string;\n onSelect: () => void;\n /** Only used by the `sound` action \u2014 drives the switch UI. */\n switchOn?: boolean;\n /** Render a switch item in the overflow menu (vs. a plain button). */\n switchable?: boolean;\n}\n\nexport function Panel(props: Props) {\n const { options, onClose } = props;\n const s = options.strings;\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Trap focus inside panel + Escape to close.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const dispose = trapFocus(el);\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose?.();\n };\n el.addEventListener(\"keydown\", onKey);\n return () => {\n dispose();\n el.removeEventListener(\"keydown\", onKey);\n };\n }, [onClose]);\n\n // File-drop on the panel container \u2014 overlay visibility + drop\n // handler wired by `useFileDrop`. The dropped items are routed\n // into the Composer's `atts` state via the imperative API ref it\n // populates on mount (see `ComposerAttachAPI`). NOT auto-sent \u2014\n // a file drop means \"stage this file for my next message\", not\n // \"submit a message containing this file with no text\". The user\n // still has to type and press send.\n const composerAttachApiRef = useRef<ComposerAttachAPI | null>(null);\n const onDropItems = useCallback((items: DataTransferItemList) => {\n composerAttachApiRef.current?.attachFromDrop(items);\n }, []);\n const { visible: dragOver } = useFileDrop({ containerRef, onDrop: onDropItems });\n\n // Modal mode: let the user grab the header to reposition the centred dialog.\n useDragMove(containerRef.current, options.mode === \"modal\");\n\n return (\n <div\n ref={containerRef}\n class={`${p}-panel`}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-label={s.panelTitle}\n style={{ position: \"relative\" }}\n data-testid={TID.panel}\n >\n <PanelContent\n {...props}\n containerEl={containerRef.current}\n dragOver={dragOver}\n composerAttachApiRef={composerAttachApiRef}\n />\n {/* Global attribution \u2014 single-chat surface has no tab bar, so it pins\n to the bottom of the panel. */}\n <PoweredByBar poweredBy={props.options.poweredBy} />\n </div>\n );\n}\n\ninterface PanelContentProps extends Props {\n /** The panel card element (for the resize grip). */\n containerEl: HTMLElement | null;\n /** File-drag overlay visibility (owned by the card container). */\n dragOver: boolean;\n /** Imperative attach API the Composer populates on mount. */\n composerAttachApiRef: { current: ComposerAttachAPI | null };\n}\n\n/**\n * Panel inner content \u2014 header + (history | chat body) + resize grip.\n * Split out from {@link Panel}'s card wrapper so the messenger-home\n * surface can render a chat thread inside its OWN card (one dialog, one\n * focus trap, with the bottom tab bar reachable) instead of nesting a\n * second `.__P__-panel`.\n */\nexport function PanelContent({\n options,\n transport,\n feedback,\n bus,\n panelSize,\n view,\n canSend,\n isStreaming,\n loadingMessages,\n messagesSig,\n agent,\n suggestions,\n initialWidgetSize,\n visitorId,\n onClose,\n onBack,\n onClear,\n onExpand,\n onFullscreen,\n onPopOut,\n onSoundToggle,\n onToggleHistory,\n onSelectHistoryChat,\n onNewChat,\n onLocaleChange,\n onThemeChange,\n onWidgetSizeChange,\n onSend,\n onStop,\n onSuggestion,\n containerEl,\n dragOver,\n composerAttachApiRef,\n}: PanelContentProps) {\n const s = options.strings;\n return (\n <>\n {view === \"history\" ? (\n <HistoryHeader\n strings={s}\n onBack={onToggleHistory}\n onClose={onClose}\n showClose={canShowClose(options.mode, panelSize, options.actions)}\n />\n ) : (\n <Header\n agent={agent}\n onBack={onBack}\n backLabel={s.moduleBack}\n moreActionsLabel={s.moreActions}\n languageLabel={s.language}\n themeLabel={s.theme}\n themeOptionLabels={{ auto: s.themeAuto, light: s.themeLight, dark: s.themeDark }}\n activeThemeMode={options.themeMode}\n onThemeChange={onThemeChange}\n panelTitle={s.panelTitle}\n activeLocale={options.locale}\n availableLocales={options.availableLocales}\n onLocaleChange={onLocaleChange}\n main={visibleActions(options.actions.main, options.mode, panelSize, { popOutUrl: options.popOutUrl })}\n overflow={visibleActions(options.actions.overflow, options.mode, panelSize, { popOutUrl: options.popOutUrl })}\n descriptors={buildDescriptors({\n strings: s,\n panelSize,\n feedback,\n onClear,\n onExpand,\n onFullscreen,\n onPopOut,\n onSoundToggle,\n onToggleHistory,\n onClose,\n })}\n />\n )}\n {view === \"history\" ? (\n <ChatHistoryPane\n transport={transport}\n strings={s}\n visitorId={visitorId}\n onSelect={(chat) => onSelectHistoryChat(chat.sessionId)}\n onNewChat={onNewChat}\n />\n ) : (\n <>\n <DropZone visible={dragOver} strings={s} />\n <MessageList\n messagesSig={messagesSig}\n strings={s}\n responseMode={options.responseMode}\n showReasoning={options.showReasoning}\n showToolCalls={options.showToolCalls}\n loading={loadingMessages}\n />\n {canSend ? (\n <>\n <Suggestions suggestions={suggestions} onPick={onSuggestion} />\n <Composer\n options={options}\n transport={transport}\n feedback={feedback}\n bus={bus}\n isStreaming={isStreaming}\n onSend={onSend}\n onStop={onStop}\n attachApiRef={composerAttachApiRef}\n />\n </>\n ) : (\n <ReadOnlyBanner label={s.chatClosed} ctaLabel={s.startNewConversation} onNewChat={onClear} />\n )}\n <ComposerFooter disclaimer={options.composerDisclaimer} />\n </>\n )}\n {options.size.resize?.enabled ? (\n <ResizeGrip\n panelEl={containerEl}\n resize={options.size.resize}\n position={options.position}\n initialSize={initialWidgetSize}\n onSizeChange={onWidgetSizeChange}\n strings={s}\n />\n ) : null}\n </>\n );\n}\n\n// ---- chat history header -------------------------------------------------\n\nfunction HistoryHeader({\n strings,\n onBack,\n onClose,\n showClose,\n}: {\n strings: Strings;\n onBack: () => void;\n onClose?: () => void;\n showClose: boolean;\n}) {\n return (\n <header class={`${p}-header`}>\n <button\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={onBack}\n aria-label={strings.historyBack}\n title={strings.historyBack}\n >\n <BackIcon />\n </button>\n <h1>{strings.historyTitle}</h1>\n {showClose ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={onClose}\n aria-label={strings.close}\n title={strings.close}\n >\n <CloseIcon />\n </button>\n ) : null}\n </header>\n );\n}\n\n/**\n * Banner shown in place of the composer when the active chat is read-only\n * (server returned `canContinue: false`). Carries a CTA so the user can\n * start fresh without hunting for the trash icon \u2014 especially important\n * in mobile/floating layouts where the header overflow menu is hidden\n * behind a tap.\n */\nfunction ReadOnlyBanner({ label, ctaLabel, onNewChat }: { label: string; ctaLabel: string; onNewChat: () => void }) {\n return (\n <div class={`${p}-readonly-banner`} role=\"note\">\n <span class={`${p}-readonly-label`}>{label}</span>\n <button type=\"button\" class={`${p}-readonly-cta`} onClick={onNewChat}>\n {ctaLabel}\n </button>\n </div>\n );\n}\n\n// ---- composer footer ----------------------------------------------------\n\n/**\n * Chat-only disclaimer line (\"AI can make mistakes\") under the composer.\n * \"Powered by \u2026\" is NOT here \u2014 it's a global widget footer (see {@link PoweredByBar}).\n */\nfunction ComposerFooter({ disclaimer }: { disclaimer: string }) {\n if (!disclaimer) return null;\n return (\n <div class={`${p}-composer-footer`}>\n <div class={`${p}-disclaimer`}>{disclaimer}</div>\n </div>\n );\n}\n\n/**\n * Global \"Powered by \u2026\" strip \u2014 pinned at the bottom of the whole widget\n * surface (below the tab bar in the messenger; below the composer in single-chat),\n * so it shows on every tab, not just the chat. Renders nothing when unset.\n */\nexport function PoweredByBar({ poweredBy }: { poweredBy: ResolvedOptions[\"poweredBy\"] }) {\n if (!poweredBy) return null;\n return (\n <div class={`${p}-poweredby-bar`}>\n <PoweredBy logoUrl={poweredBy.logoUrl} text={poweredBy.text} href={poweredBy.href} />\n </div>\n );\n}\n\nfunction PoweredBy({ logoUrl, text, href }: { logoUrl?: string; text?: string; href?: string }) {\n const inner = (\n <>\n {logoUrl ? <img class={`${p}-poweredby-logo`} src={logoUrl} alt=\"\" loading=\"lazy\" /> : null}\n {text ? <span>{text}</span> : null}\n </>\n );\n if (href) {\n return (\n <a class={`${p}-poweredby`} href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n {inner}\n </a>\n );\n }\n return <span class={`${p}-poweredby`}>{inner}</span>;\n}\n\n// ---- header internals ---------------------------------------------------\n\ninterface HeaderProps {\n agent: SessionAgent | null;\n /** When set, render a leading Back chevron (drilled-in thread). */\n onBack?: () => void;\n /** Pre-resolved label for the Back chevron (`strings.moduleBack`). */\n backLabel: string;\n /** Pre-resolved label for the overflow trigger (`strings.moreActions`). */\n moreActionsLabel: string;\n /** Pre-resolved label for the language switcher trigger (`strings.language`). */\n languageLabel: string;\n /** Pre-resolved label for the theme switcher trigger (`strings.theme`). */\n themeLabel: string;\n /** Pre-resolved labels for each theme option \u2014 shown in the dropdown. */\n themeOptionLabels: Record<\"auto\" | \"light\" | \"dark\", string>;\n activeThemeMode: \"auto\" | \"light\" | \"dark\";\n onThemeChange: (mode: \"auto\" | \"light\" | \"dark\") => void;\n panelTitle: string;\n /** Currently active UI locale \u2014 drives the language switcher's checkmark. */\n activeLocale: string;\n /** Locales the user can pick from. Empty hides the language switcher. */\n availableLocales: readonly string[];\n onLocaleChange: (locale: string) => void;\n main: readonly ActionName[];\n overflow: readonly ActionName[];\n descriptors: DescriptorTable;\n}\n\nfunction Header({\n agent,\n onBack,\n backLabel,\n moreActionsLabel,\n languageLabel,\n themeLabel,\n themeOptionLabels,\n activeThemeMode,\n onThemeChange,\n panelTitle,\n activeLocale,\n availableLocales,\n onLocaleChange,\n main,\n overflow,\n descriptors,\n}: HeaderProps) {\n // Hide the language switcher if there's nothing meaningful to switch to\n // (zero or one available locale). This lets a deployment leave `language`\n // in `actions.main` permanently \u2014 it self-hides when not configured.\n const showLanguage = availableLocales.length > 1;\n\n // Build overflow items. `theme` is always filtered (rendered as its own\n // popover alongside the overflow trigger when placed in overflow, since\n // a dropdown-inside-a-dropdown would be poor UX). `language` is expanded\n // inline: each available locale becomes a menu item with a checkmark on\n // the active one \u2014 simpler than a submenu and still accessible.\n const overflowItems: MenuItem[] = [];\n for (const name of overflow) {\n if (name === \"theme\") continue; // rendered as a dedicated popover below\n if (name === \"language\") {\n if (!showLanguage) continue;\n for (const locale of availableLocales) {\n overflowItems.push({\n id: `language:${locale}`,\n label: languageLabel + ` \u2014 ${locale}`,\n lang: locale,\n onSelect: () => onLocaleChange(locale),\n type: \"switch\",\n on: locale === activeLocale,\n });\n }\n continue;\n }\n const d = descriptors[name];\n overflowItems.push({\n id: name,\n icon: d.icon,\n label: d.label,\n onSelect: d.onSelect,\n type: d.switchable ? \"switch\" : \"button\",\n on: d.switchOn,\n });\n }\n\n // Theme switcher placed in overflow still renders as its own popover\n // (right next to the 3-dot trigger) \u2014 a dropdown inside a dropdown is\n // too fiddly for a v1 picker, so we surface it as a sibling control\n // that accepts placement in either bucket without changing its UX.\n const themeInOverflow = overflow.includes(\"theme\");\n\n return (\n <header class={`${p}-header`} data-testid={TID.panelHeader}>\n {onBack ? (\n <button type=\"button\" class={`${p}-icon-btn`} onClick={onBack} aria-label={backLabel} title={backLabel}>\n <BackIcon />\n </button>\n ) : null}\n {agent ? <AgentBadge agent={agent} /> : <h1>{panelTitle}</h1>}\n {main.map((name) => {\n if (name === \"language\") {\n if (!showLanguage) return null;\n return (\n <LanguageSwitcher\n key=\"language\"\n available={availableLocales}\n active={activeLocale}\n onSelect={onLocaleChange}\n triggerLabel={languageLabel}\n />\n );\n }\n if (name === \"theme\") {\n return (\n <ThemeSwitcher\n key=\"theme\"\n active={activeThemeMode}\n onSelect={onThemeChange}\n triggerLabel={themeLabel}\n optionLabels={themeOptionLabels}\n />\n );\n }\n const d = descriptors[name];\n return (\n <button\n key={name}\n type=\"button\"\n class={`${p}-icon-btn`}\n onClick={d.onSelect}\n aria-label={d.label}\n aria-pressed={d.switchable ? d.switchOn : undefined}\n title={d.label}\n data-testid={ACTION_TID[name as keyof typeof ACTION_TID]}\n >\n {d.icon}\n </button>\n );\n })}\n {themeInOverflow ? (\n <ThemeSwitcher\n active={activeThemeMode}\n onSelect={onThemeChange}\n triggerLabel={themeLabel}\n optionLabels={themeOptionLabels}\n />\n ) : null}\n {overflowItems.length > 0 ? <OverflowMenu items={overflowItems} triggerLabel={moreActionsLabel} /> : null}\n </header>\n );\n}\n\ninterface BuildDescriptorsArgs {\n strings: Strings;\n panelSize: PanelSize;\n feedback: FeedbackBus;\n onClear: () => void;\n onExpand: () => void;\n onFullscreen: () => void;\n onPopOut: () => void;\n onSoundToggle: () => void;\n onToggleHistory: () => void;\n onClose?: () => void;\n}\n\n/**\n * Build the full table of `ActionName \u2192 ActionDescriptor`. This is a plain\n * function so the Header can pluck any action by name regardless of whether\n * it's placed in the main bar or the overflow menu. Keeping the logic in\n * one place means new actions only need one row added.\n */\nfunction buildDescriptors(args: BuildDescriptorsArgs): DescriptorTable {\n const {\n strings,\n panelSize,\n feedback,\n onClear,\n onExpand,\n onFullscreen,\n onPopOut,\n onSoundToggle,\n onToggleHistory,\n onClose,\n } = args;\n const expandedNow = panelSize === \"expanded\";\n const fullscreenNow = panelSize === \"fullscreen\";\n // Subscribe to the mute signal so the switch label/icon stay live.\n const muted = useComputed(() => feedback.mutedSig.value).value;\n return {\n clear: { icon: <TrashIcon />, label: strings.newConversation, onSelect: onClear },\n expand: {\n icon: expandedNow ? <CollapseIcon /> : <ExpandIcon />,\n label: expandedNow ? strings.collapse : strings.expand,\n onSelect: onExpand,\n switchable: true,\n switchOn: expandedNow,\n },\n fullscreen: {\n icon: fullscreenNow ? <FullscreenExitIcon /> : <FullscreenIcon />,\n label: fullscreenNow ? strings.exitFullscreen : strings.fullscreen,\n onSelect: onFullscreen,\n switchable: true,\n switchOn: fullscreenNow,\n },\n popOut: { icon: <PopOutIcon />, label: strings.popOut, onSelect: onPopOut },\n sound: {\n icon: muted ? <SpeakerMutedIcon /> : <SpeakerIcon />,\n label: muted ? strings.soundOff : strings.soundOn,\n onSelect: onSoundToggle,\n switchable: true,\n // The switch is \"on\" when sound is ENABLED (not muted).\n switchOn: !muted,\n },\n history: { icon: <HistoryIcon />, label: strings.history, onSelect: onToggleHistory },\n close: { icon: <CloseIcon />, label: strings.close, onSelect: onClose ?? (() => {}) },\n };\n}\n", "/**\n * Page-mode sidebar \u2014 logo, navigation, link cards, conversation list,\n * and a collapse toggle in the header. Reads `site` and `blocks` parsed\n * from the start-session response.\n *\n * Persistent \u2014 not a slide-over. Always rendered when mode === 'page'.\n * The collapsed state is owned by App (persisted via `Persistence`); the\n * sidebar just renders the `data-collapsed` attribute and the toggle button\n * \u2014 CSS in `styles/page.css` handles the visual collapse to a thin rail.\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\nimport type { BlocksConfig, Link, SiteConfig } from \"@/schema\";\nimport type { SessionSummary } from \"@/stream/protocol\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport { TID } from \"./test-ids\";\nimport { ConversationList } from \"./conversation-list\";\n\nexport interface SidebarProps {\n site: SiteConfig | undefined;\n blocks: BlocksConfig | undefined;\n transport: AgentTransport;\n strings: Strings;\n visitorId: string;\n /** Render the conversation list + new-chat (only when `messages` is enabled). */\n showConversations: boolean;\n onSelectChat: (chat: SessionSummary) => void;\n onNewChat: () => void;\n collapsed: boolean;\n onToggleCollapsed: () => void;\n}\n\nexport function Sidebar(props: SidebarProps) {\n const p = BRAND.cssPrefix;\n const { site, blocks, strings, collapsed } = props;\n const navigation = blocks?.navigation ?? [];\n const linkCards = blocks?.linkCards ?? [];\n const toggleLabel = collapsed ? strings.expandSidebar : strings.collapseSidebar;\n\n return (\n <aside class={`${p}-sidebar`} data-collapsed={collapsed ? \"true\" : \"false\"} data-testid={TID.sidebar}>\n <div class={`${p}-sidebar-header`}>\n <SidebarBrand site={site} />\n <button\n type=\"button\"\n class={`${p}-sidebar-toggle`}\n aria-label={toggleLabel}\n aria-expanded={collapsed ? \"false\" : \"true\"}\n title={toggleLabel}\n onClick={props.onToggleCollapsed}\n data-testid={TID.sidebarToggle}\n >\n <SidebarToggleIcon collapsed={collapsed} />\n </button>\n </div>\n {collapsed ? null : (\n <>\n {navigation.length > 0 ? (\n <nav class={`${p}-sidebar-section`} data-section=\"navigation\">\n <SidebarNav items={navigation} />\n </nav>\n ) : null}\n {linkCards.length > 0 ? (\n <div class={`${p}-sidebar-section`} data-section=\"link-cards\">\n <SidebarCards items={linkCards} />\n </div>\n ) : null}\n {props.showConversations ? (\n <div class={`${p}-sidebar-conversations`}>\n <ConversationList\n transport={props.transport}\n strings={props.strings}\n visitorId={props.visitorId}\n onSelect={props.onSelectChat}\n onNewChat={props.onNewChat}\n />\n </div>\n ) : null}\n </>\n )}\n </aside>\n );\n}\n\nfunction SidebarBrand({ site }: { site: SiteConfig | undefined }) {\n const p = BRAND.cssPrefix;\n if (site?.logo?.url) {\n const alt = site.logo.alt ?? site.title ?? \"Logo\";\n // <picture> swaps to `logoDark` automatically when the user's OS /\n // host preference is dark. Brands that ship only one logo just\n // render the fallback <img>.\n return (\n <picture>\n {site.logoDark?.url ? <source srcset={site.logoDark.url} media=\"(prefers-color-scheme: dark)\" /> : null}\n <img class={`${p}-sidebar-logo`} src={site.logo.url} alt={alt} />\n </picture>\n );\n }\n return <div class={`${p}-sidebar-title`}>{site?.title ?? BRAND.name}</div>;\n}\n\nfunction SidebarToggleIcon({ collapsed }: { collapsed: boolean }) {\n // Chevron-left when expanded (click \u2192 collapses leftward); chevron-right\n // when collapsed (click \u2192 expands rightward).\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n {collapsed ? <polyline points=\"9 6 15 12 9 18\" /> : <polyline points=\"15 6 9 12 15 18\" />}\n </svg>\n );\n}\n\nfunction SidebarNav({ items }: { items: Link[] }) {\n const p = BRAND.cssPrefix;\n return (\n <ul class={`${p}-sidebar-nav`}>\n {items.map((item) => (\n <li key={item.id ?? item.label}>\n <a\n class={`${p}-sidebar-nav-item`}\n href={item.href}\n target={item.href ? \"_blank\" : undefined}\n rel={item.href ? \"noreferrer\" : undefined}\n >\n {item.icon ? <span class={`${p}-sidebar-nav-icon`} data-icon={item.icon} /> : null}\n <span class={`${p}-sidebar-nav-label`}>{item.label}</span>\n </a>\n </li>\n ))}\n </ul>\n );\n}\n\nfunction SidebarCards({ items }: { items: Link[] }) {\n const p = BRAND.cssPrefix;\n return (\n <div class={`${p}-sidebar-cards`}>\n {items.map((item) => (\n <a\n key={item.id ?? item.label}\n class={`${p}-sidebar-card`}\n href={item.href}\n target={item.href ? \"_blank\" : undefined}\n rel={item.href ? \"noreferrer\" : undefined}\n >\n <div class={`${p}-sidebar-card-label`}>{item.label}</div>\n {item.description ? <div class={`${p}-sidebar-card-desc`}>{item.description}</div> : null}\n </a>\n ))}\n </div>\n );\n}\n", "/**\n * Page-mode root layout \u2014 full-viewport sidebar + chat split.\n *\n * Sidebar renders site/blocks config + persistent conversation list.\n * Chat area reuses the existing Panel component verbatim. Layout grid\n * lives in src/styles/page.css scoped to [data-mode=\"page\"].\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/core/config\";\nimport type { BlocksConfig, SiteConfig } from \"@/schema\";\nimport type { SessionSummary } from \"@/stream/protocol\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport type { ComponentChildren, ComponentProps } from \"preact\";\nimport { Panel } from \"./panel\";\nimport { Sidebar } from \"./sidebar\";\n\nconst p = BRAND.cssPrefix;\n\nexport type PanelProps = ComponentProps<typeof Panel>;\n\nexport interface PageShellProps {\n site: SiteConfig | undefined;\n blocks: BlocksConfig | undefined;\n transport: AgentTransport;\n strings: Strings;\n visitorId: string;\n /** Show the conversation list + new-chat in the sidebar (i.e. `messages` is enabled). */\n showConversations: boolean;\n onSelectChat: (chat: SessionSummary) => void;\n onNewChat: () => void;\n sidebarCollapsed: boolean;\n onToggleSidebarCollapsed: () => void;\n /** The main surface \u2014 the same messenger module surface used in every mode. */\n children: ComponentChildren;\n}\n\nexport function PageShell(props: PageShellProps) {\n // Semantic outer <main> wraps the page surface; sidebar is <aside>\n // (rendered inside the Sidebar component) and the chat column is a\n // <section>. The host page is expected to provide its own <header>\n // ABOVE this widget \u2014 page mode does not render its own page header.\n return (\n <main class={`${p}-page-shell`} data-sidebar-collapsed={props.sidebarCollapsed ? \"true\" : \"false\"}>\n <Sidebar\n site={props.site}\n blocks={props.blocks}\n transport={props.transport}\n strings={props.strings}\n visitorId={props.visitorId}\n showConversations={props.showConversations}\n onSelectChat={props.onSelectChat}\n onNewChat={props.onNewChat}\n collapsed={props.sidebarCollapsed}\n onToggleCollapsed={props.onToggleSidebarCollapsed}\n />\n <section class={`${p}-page-chat`} aria-label=\"Chat\">\n {props.children}\n </section>\n </main>\n );\n}\n", "/**\n * Messenger-home navigation state.\n *\n * `activeTab` (a module `id`) + a per-tab drill-in **stack** so Back returns\n * to the tab root and switching tabs preserves each tab's depth. Created\n * per App instance (not a module singleton) so two widgets on one page\n * keep independent navigation \u2014 mirrors how `messagesSig` is per-instance.\n *\n * Tabs are data-driven (the resolved `modules` list), so stacks are keyed by\n * the dynamic module ids passed in at construction.\n *\n * Transitions reassign the signal by value (never mutate in place) so\n * `useComputed` subscribers re-render reliably.\n */\n\nimport { signal, type Signal } from \"@preact/signals\";\n\n/** A tab key \u2014 a resolved module `id`. */\nexport type HomeTab = string;\n\n/** A drill-in screen pushed on top of a tab root. */\nexport type HomeScreen =\n /** A single content row rendered natively in the reader (markdown). */\n | { kind: \"content\"; id: string; title: string }\n /** External page in a sandboxed in-widget iframe (help article, status, \u2026). */\n | { kind: \"iframe\"; url: string; title: string };\n\nexport interface HomeNavState {\n activeTab: HomeTab;\n /** Per-tab drill-in stack, keyed by module id. Empty array = tab root (tab bar visible). */\n stacks: Record<HomeTab, HomeScreen[]>;\n}\n\n/** Navigation API handed to module screens. */\nexport interface HomeNav {\n sig: Signal<HomeNavState>;\n switchTab(tab: HomeTab): void;\n push(screen: HomeScreen): void;\n pop(): void;\n reset(tab: HomeTab): void;\n}\n\nconst emptyStacks = (tabs: HomeTab[]): Record<HomeTab, HomeScreen[]> => Object.fromEntries(tabs.map((t) => [t, []]));\n\n/**\n * Build a navigation controller over the given tab ids. `onSwitch` fires\n * whenever the active tab changes (used to persist the last tab) \u2014 not called\n * by `reset`, which is the programmatic open-time restore.\n */\nexport function createHomeNav(initialTab: HomeTab, tabs: HomeTab[], onSwitch?: (tab: HomeTab) => void): HomeNav {\n const sig = signal<HomeNavState>({ activeTab: initialTab, stacks: emptyStacks(tabs) });\n return {\n sig,\n switchTab(tab) {\n if (sig.value.activeTab === tab) return;\n sig.value = { ...sig.value, activeTab: tab };\n onSwitch?.(tab);\n },\n push(screen) {\n const s = sig.value;\n const stack = s.stacks[s.activeTab] ?? [];\n sig.value = { ...s, stacks: { ...s.stacks, [s.activeTab]: [...stack, screen] } };\n },\n pop() {\n const s = sig.value;\n const stack = s.stacks[s.activeTab] ?? [];\n if (stack.length === 0) return;\n sig.value = { ...s, stacks: { ...s.stacks, [s.activeTab]: stack.slice(0, -1) } };\n },\n reset(tab) {\n sig.value = { activeTab: tab, stacks: emptyStacks(tabs) };\n },\n };\n}\n\nexport const activeStack = (s: HomeNavState): HomeScreen[] => s.stacks[s.activeTab] ?? [];\nexport const topScreen = (s: HomeNavState): HomeScreen | undefined => activeStack(s).at(-1);\nexport const atTabRoot = (s: HomeNavState): boolean => activeStack(s).length === 0;\n", "/**\n * Chat layout \u2014 the conversation (the widget's core experience): resumes the\n * last conversation, shows the composer, and browses history via the panel\n * header. The actual surface is the existing `Panel`, which `MessengerHome`\n * renders for a `chat`-layout tab; this layout only supplies the bottom-nav icon.\n */\n\nimport { BubblesIcon } from \"../icons\";\nimport type { WidgetLayout } from \"./types\";\n\nexport const chatLayout: WidgetLayout = {\n Icon: BubblesIcon,\n // No `Root`: MessengerHome renders the existing chat panel for this tab.\n};\n", "/** Detail-screen header: Back chevron + centered title + optional close. */\n\nimport { BRAND } from \"@/core/brand\";\nimport { BackIcon, CloseIcon } from \"./icons\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n title: string;\n backLabel: string;\n onBack: () => void;\n onClose?: () => void;\n closeLabel?: string;\n testid?: string;\n}\n\n/** Tab-root header: centered title + optional close (no Back). */\nexport function TitleBar({ title, onClose, closeLabel }: { title: string; onClose?: () => void; closeLabel?: string }) {\n return (\n <header class={`${p}-back-header`} data-variant=\"title\">\n <span class={`${p}-back-spacer`} aria-hidden=\"true\" />\n <h1 class={`${p}-back-title`}>{title}</h1>\n {onClose ? (\n <button type=\"button\" class={`${p}-icon-btn`} onClick={onClose} aria-label={closeLabel} title={closeLabel}>\n <CloseIcon />\n </button>\n ) : (\n <span class={`${p}-back-spacer`} aria-hidden=\"true\" />\n )}\n </header>\n );\n}\n\nexport function BackHeader({ title, backLabel, onBack, onClose, closeLabel, testid }: Props) {\n return (\n <header class={`${p}-back-header`} data-testid={testid}>\n <button type=\"button\" class={`${p}-icon-btn`} onClick={onBack} aria-label={backLabel} title={backLabel}>\n <BackIcon />\n </button>\n <h1 class={`${p}-back-title`}>{title}</h1>\n {onClose ? (\n <button type=\"button\" class={`${p}-icon-btn`} onClick={onClose} aria-label={closeLabel} title={closeLabel}>\n <CloseIcon />\n </button>\n ) : (\n <span class={`${p}-back-spacer`} aria-hidden=\"true\" />\n )}\n </header>\n );\n}\n", "/**\n * \"Search for help\" bar. On Home it's a button that jumps to the Help tab\n * (`onActivate`); on Help it's a live input (`onInput` + `value`).\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport { SearchIcon } from \"./icons\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface ButtonProps {\n placeholder: string;\n onActivate: () => void;\n}\n\n/** Tappable placeholder bar (Home). */\nexport function HomeSearchButton({ placeholder, onActivate }: ButtonProps) {\n return (\n <button type=\"button\" class={`${p}-home-search`} onClick={onActivate} data-testid={TID.homeSearch}>\n <span class={`${p}-home-search-text`}>{placeholder}</span>\n <span class={`${p}-home-search-icon`} aria-hidden=\"true\">\n <SearchIcon />\n </span>\n </button>\n );\n}\n\ninterface InputProps {\n placeholder: string;\n value: string;\n onInput: (value: string) => void;\n}\n\n/** Live search input (Help tab). */\nexport function HelpSearchInput({ placeholder, value, onInput }: InputProps) {\n return (\n <div class={`${p}-home-search`} data-input=\"true\">\n <input\n type=\"search\"\n class={`${p}-home-search-input`}\n placeholder={placeholder}\n value={value}\n onInput={(e) => onInput((e.currentTarget as HTMLInputElement).value)}\n data-testid={TID.helpSearch}\n />\n <span class={`${p}-home-search-icon`} aria-hidden=\"true\">\n <SearchIcon />\n </span>\n </div>\n );\n}\n", "/** Generic tappable list row: title + subtitle, trailing chevron. */\n\nimport { BRAND } from \"@/core/brand\";\nimport { ChevronRightIcon } from \"./icons\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n title: string;\n subtitle?: string;\n onClick: () => void;\n testid?: string;\n}\n\nexport function ListRow({ title, subtitle, onClick, testid }: Props) {\n return (\n <button type=\"button\" class={`${p}-list-row`} onClick={onClick} data-testid={testid}>\n <span class={`${p}-list-row-body`}>\n <span class={`${p}-list-row-title`}>{title}</span>\n {subtitle ? <span class={`${p}-list-row-sub`}>{subtitle}</span> : null}\n </span>\n <span class={`${p}-list-row-chevron`} aria-hidden=\"true\">\n <ChevronRightIcon />\n </span>\n </button>\n );\n}\n", "/**\n * Shared loading / empty / error placeholder for module screens.\n *\n * Gives every state the right a11y semantics (`role=\"status\"` + `aria-live`\n * for loading/empty, `role=\"alert\"` for errors) instead of a bare muted\n * `<div>`, and offers an optional retry affordance for recoverable errors.\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/i18n/strings\";\n\nconst p = BRAND.cssPrefix;\n\nexport function ModuleState({\n tone = \"info\",\n message,\n onRetry,\n strings,\n}: {\n tone?: \"info\" | \"error\";\n message: string;\n /** When present (error tone), renders a \"Retry\" button. */\n onRetry?: () => void;\n strings: Strings;\n}) {\n return (\n <div class={`${p}-module-empty`} role={tone === \"error\" ? \"alert\" : \"status\"} aria-live=\"polite\">\n <span>{message}</span>\n {onRetry ? (\n <button type=\"button\" class={`${p}-module-retry`} onClick={onRetry}>\n {strings.errorRetry}\n </button>\n ) : null}\n </div>\n );\n}\n", "/**\n * Help layout \u2014 content rows scoped by the tab's `contentTags`, fetched once\n * (one large page) and grouped client-side into iOS-style category sections\n * with sticky headers. Search is a small client-side fuzzy filter over the\n * loaded rows (no server round-trip). An article with a `url` opens as an\n * in-widget sandboxed iframe; otherwise its `content` renders in the reader.\n */\n\nimport { useEffect, useMemo, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport { logger } from \"@/core/logger\";\nimport type { ContentItem } from \"@/stream/protocol\";\nimport { TitleBar } from \"../back-header\";\nimport { HelpIcon } from \"../icons\";\nimport { errorMessageFor } from \"../error-message\";\nimport { HelpSearchInput } from \"../home-search\";\nimport { ListRow } from \"../list-row\";\nimport { ModuleState } from \"../module-state\";\nimport { tid, TID } from \"../test-ids\";\nimport type { ModuleNav, ModuleScreenProps, WidgetLayout } from \"./types\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"help\");\n\n/** Open a content row: external `url` \u2192 in-widget iframe; inline `content` \u2192 reader. */\nconst openArticle = (nav: ModuleNav, a: ContentItem) =>\n a.url ? nav.push({ kind: \"iframe\", url: a.url, title: a.title }) : nav.push({ kind: \"content\", id: a.id, title: a.title }); // prettier-ignore\n\n/** Group rows into ordered `[category, items]` sections by each row's first category. */\nfunction groupByCategory(items: ContentItem[]): Array<[string, ContentItem[]]> {\n const groups = new Map<string, ContentItem[]>();\n for (const item of items) {\n const key = item.categories?.[0] ?? \"\";\n (groups.get(key) ?? groups.set(key, []).get(key)!).push(item);\n }\n return [...groups.entries()];\n}\n\n/**\n * Tiny fuzzy match: case-insensitive substring (strong) or in-order subsequence\n * (weak). Returns 0 for no match; higher is a better match. Deliberately simple\n * \u2014 no tokenizing or weighting beyond \"substring beats scattered letters\".\n */\nfunction fuzzyScore(query: string, text: string): number {\n const t = text.toLowerCase();\n const idx = t.indexOf(query);\n if (idx !== -1) return 100 - Math.min(idx, 99); // substring; earlier = better\n let from = 0;\n for (const ch of query) {\n from = t.indexOf(ch, from);\n if (from === -1) return 0;\n from += 1;\n }\n return 1; // scattered subsequence\n}\n\n/** Rank rows by best fuzzy score across title (weighted) + description. */\nfunction fuzzySearch(items: ContentItem[], query: string): ContentItem[] {\n const q = query.toLowerCase().trim();\n if (!q) return [];\n return items\n .map((item) => ({ item, score: Math.max(fuzzyScore(q, item.title) * 2, fuzzyScore(q, item.description ?? \"\")) }))\n .filter((r) => r.score > 0)\n .toSorted((a, b) => b.score - a.score)\n .map((r) => r.item);\n}\n\nfunction ArticleRow({ article, nav }: { article: ContentItem; nav: ModuleNav }) {\n return (\n <ListRow\n title={article.title}\n subtitle={article.description}\n onClick={() => openArticle(nav, article)}\n testid={tid(TID.helpArticle, article.id)}\n />\n );\n}\n\nfunction HelpRoot({ transport, strings, config, nav, panelProps }: ModuleScreenProps) {\n const tags = config.contentTags;\n const [state, setState] = useState<\"loading\" | \"loaded\" | \"error\">(\"loading\");\n const [errorMsg, setErrorMsg] = useState(strings.errorGeneric);\n const [items, setItems] = useState<ContentItem[]>([]);\n const [query, setQuery] = useState(\"\");\n const [reloadKey, setReloadKey] = useState(0);\n\n useEffect(() => {\n let cancelled = false;\n setState(\"loading\");\n transport\n // One large page, grouped into category sections client-side \u2014 help\n // catalogues are bounded (dozens of articles), so a single fetch beats\n // paging through a list the visitor scrolls as one. Search filters this\n // same set locally, so typing never hits the network.\n .listContent({ tags, sortBy: \"order\", limit: 100 })\n .then((res) => {\n if (cancelled) return;\n setItems(res.items);\n setState(\"loaded\");\n })\n .catch((err) => {\n if (cancelled) return;\n log.warn(\"listContent (help) failed\", { err });\n setErrorMsg(errorMessageFor(err, strings));\n setState(\"error\");\n });\n return () => {\n cancelled = true;\n };\n }, [transport, tags, reloadKey]);\n\n const results = useMemo(() => fuzzySearch(items, query), [items, query]);\n\n function renderBody() {\n if (query.trim().length > 0) {\n if (results.length === 0) return <ModuleState message={strings.helpSearchEmpty} strings={strings} />;\n return (\n <div class={`${p}-help-card`}>\n {results.map((a) => (\n <ArticleRow key={a.id} article={a} nav={nav} />\n ))}\n </div>\n );\n }\n if (state === \"loading\") return <ModuleState message={strings.helpLoading} strings={strings} />;\n if (state === \"error\") {\n return (\n <ModuleState tone=\"error\" message={errorMsg} onRetry={() => setReloadKey((k) => k + 1)} strings={strings} />\n );\n }\n if (items.length === 0) return <ModuleState message={strings.helpEmpty} strings={strings} />;\n return groupByCategory(items).map(([category, rows]) => (\n <section key={category} class={`${p}-help-group`}>\n {category ? <h2 class={`${p}-help-section-title`}>{category}</h2> : null}\n <div class={`${p}-help-card`}>\n {rows.map((a) => (\n <ArticleRow key={a.id} article={a} nav={nav} />\n ))}\n </div>\n </section>\n ));\n }\n\n return (\n <div class={`${p}-module`}>\n <TitleBar title={strings.helpTitle} onClose={panelProps.onClose} closeLabel={strings.close} />\n <div class={`${p}-module-pad`}>\n <HelpSearchInput placeholder={strings.helpSearchPlaceholder} value={query} onInput={setQuery} />\n </div>\n <div class={`${p}-help-list`}>{renderBody()}</div>\n </div>\n );\n}\n\nexport const helpLayout: WidgetLayout = {\n Icon: HelpIcon,\n Root: (props) => <HelpRoot {...props} />,\n};\n", "/** Rounded surface card used on the Home tab. Clickable when `onClick` is set. */\n\nimport type { ComponentChildren } from \"preact\";\nimport { BRAND } from \"@/core/brand\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n onClick?: () => void;\n children?: ComponentChildren;\n testid?: string;\n}\n\nexport function HomeCard({ onClick, children, testid }: Props) {\n if (onClick) {\n return (\n <button type=\"button\" class={`${p}-home-card`} data-interactive=\"true\" onClick={onClick} data-testid={testid}>\n {children}\n </button>\n );\n }\n return (\n <div class={`${p}-home-card`} data-testid={testid}>\n {children}\n </div>\n );\n}\n", "/**\n * Home layout \u2014 a fully config-driven dashboard (no block authoring). The tab's\n * `home` config flags assemble a fixed set of sections, in order:\n * greeting \u2192 search bar \u2192 recent conversation \u2192 status \u2192 content list.\n * The content list is built from the tab's `contentTags` (titled\n * `contentBlockTitle`, default \"Popular articles\").\n */\n\nimport { useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport { logger } from \"@/core/logger\";\nimport type { ContentItem } from \"@/stream/protocol\";\nimport type { SessionSummary } from \"@/stream/protocol\";\nimport { HomeCard } from \"../home-card\";\nimport { HomeSearchButton } from \"../home-search\";\nimport { BubblesIcon, CloseIcon, HomeIcon, StatusOkIcon } from \"../icons\";\nimport { initials } from \"../initials\";\nimport { ListRow } from \"../list-row\";\nimport { moduleLabel } from \"@/i18n/strings\";\nimport { tid, TID } from \"../test-ids\";\nimport type { ModuleNav, ModuleScreenProps, WidgetLayout } from \"./types\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"home\");\n\nfunction resolveGreeting(props: ModuleScreenProps): { title: string; subtitle?: string } {\n const name = props.options.userContext?.name;\n const fromConfig = props.config.greetingText;\n if (fromConfig) {\n const title = name ? fromConfig.replace(\"{name}\", name) : fromConfig.replace(/\\s*\\{name\\}\\s*/, \" \").trim();\n return { title, subtitle: props.strings.homeGreetingLead };\n }\n return name\n ? { title: props.strings.homeGreetingNamed.replace(\"{name}\", name), subtitle: props.strings.homeGreetingLead }\n : { title: props.strings.homeGreeting };\n}\n\n/** Open a content row: external `url` \u2192 in-widget iframe; otherwise the native reader. */\nconst openContent = (nav: ModuleNav, item: ContentItem) =>\n item.url ? nav.push({ kind: \"iframe\", url: item.url, title: item.title }) : nav.push({ kind: \"content\", id: item.id, title: item.title }); // prettier-ignore\n\nfunction HomeRoot(props: ModuleScreenProps) {\n const { transport, strings, config, nav, panelProps } = props;\n const [recent, setRecent] = useState<SessionSummary | null>(null);\n const [content, setContent] = useState<ContentItem[]>([]);\n const tagsKey = config.contentTags?.join(\",\");\n\n // Recent conversation \u2014 the visitor's latest session (fetched, not authored).\n useEffect(() => {\n if (!config.showRecentMessages) return;\n let cancelled = false;\n transport\n .listSessions({ limit: 1 })\n .then((res) => !cancelled && setRecent(res.sessions[0] ?? null))\n .catch((err) => !cancelled && log.warn(\"listSessions (home) failed\", { err }));\n return () => {\n cancelled = true;\n };\n }, [transport, config.showRecentMessages]);\n\n // Content list (\"Popular articles\") \u2014 rows matching the tab's contentTags.\n useEffect(() => {\n if (!config.contentTags?.length) return;\n let cancelled = false;\n transport\n .listContent({ tags: config.contentTags, limit: 6 })\n .then((res) => !cancelled && setContent(res.items))\n .catch((err) => !cancelled && log.warn(\"listContent (home) failed\", { err }));\n return () => {\n cancelled = true;\n };\n }, [transport, tagsKey]);\n\n const greeting = resolveGreeting(props);\n const avatars = config.userAvatars ?? [];\n const status = config.status;\n const contentTitle = config.contentBlockTitle\n ? moduleLabel(strings, config.contentBlockTitle)\n : strings.homeContentTitle;\n\n return (\n <div class={`${p}-module ${p}-home`} data-testid={TID.homeView}>\n <div class={`${p}-home-scroll`}>\n <div class={`${p}-home-hero`}>\n <div class={`${p}-home-hero-top`}>\n {config.brandName ? (\n <span class={`${p}-home-brand`} data-testid={TID.homeBrand}>\n {config.brandName}\n </span>\n ) : (\n <span class={`${p}-home-brand-spacer`} aria-hidden=\"true\" />\n )}\n <div class={`${p}-home-hero-actions`}>\n {avatars.length > 0 ? (\n <div class={`${p}-home-avatars`}>\n {avatars.map((a) => (\n <span key={a.name} class={`${p}-home-avatar`} title={a.role ? `${a.name} \u00B7 ${a.role}` : a.name}>\n {a.avatar ? <img src={a.avatar} alt=\"\" loading=\"lazy\" /> : <span>{initials(a.name)}</span>}\n </span>\n ))}\n </div>\n ) : null}\n {panelProps.onClose ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn ${p}-home-close`}\n onClick={panelProps.onClose}\n aria-label={strings.close}\n title={strings.close}\n >\n <CloseIcon />\n </button>\n ) : null}\n </div>\n </div>\n {config.showGreeting !== false ? (\n <>\n <h1 class={`${p}-home-greeting`} data-testid={TID.homeGreeting}>\n {greeting.title}\n </h1>\n {greeting.subtitle ? <p class={`${p}-home-lead`}>{greeting.subtitle}</p> : null}\n </>\n ) : null}\n </div>\n\n <div class={`${p}-home-cards`}>\n {config.showSearchBar !== false ? (\n <HomeSearchButton\n placeholder={strings.homeSearchPlaceholder}\n onActivate={() => nav.switchToLayout(\"help\")}\n />\n ) : null}\n\n {recent ? (\n <HomeCard onClick={() => nav.selectSession(recent.sessionId)} testid={TID.homeRecent}>\n <div class={`${p}-home-recent-row`} data-unread={(recent.unreadCount ?? 0) > 0 ? \"true\" : undefined}>\n <span class={`${p}-home-recent-avatar`} aria-hidden=\"true\">\n <BubblesIcon />\n </span>\n <span class={`${p}-home-recent-body`}>\n <span class={`${p}-home-recent-title`}>{recent.title}</span>\n {recent.preview ? <span class={`${p}-home-recent-preview`}>{recent.preview}</span> : null}\n </span>\n {(recent.unreadCount ?? 0) > 0 ? <span class={`${p}-home-recent-dot`} aria-label=\"Unread\" /> : null}\n </div>\n </HomeCard>\n ) : null}\n\n {status ? (\n <HomeCard\n onClick={\n status.url ? () => nav.push({ kind: \"iframe\", url: status.url!, title: status.text }) : undefined\n }\n >\n <div class={`${p}-home-status`} data-level={status.level ?? \"operational\"}>\n <span class={`${p}-home-status-icon`} aria-hidden=\"true\">\n <StatusOkIcon />\n </span>\n <span class={`${p}-home-status-text`}>{status.text}</span>\n </div>\n </HomeCard>\n ) : null}\n\n {content.length > 0 ? (\n <section class={`${p}-home-content`}>\n <div class={`${p}-home-content-title`}>{contentTitle}</div>\n <div class={`${p}-home-content-list`}>\n {content.map((item) => (\n <ListRow\n key={item.id}\n title={item.title}\n subtitle={item.description}\n onClick={() => openContent(nav, item)}\n testid={tid(TID.homeContentRow, item.id)}\n />\n ))}\n </div>\n </section>\n ) : null}\n </div>\n </div>\n </div>\n );\n}\n\nexport const homeLayout: WidgetLayout = {\n Icon: HomeIcon,\n Root: (props) => <HomeRoot {...props} />,\n};\n", "/** News module \u2014 announcement cards (`type=news`) \u2192 the content reader. */\n\nimport { useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport { logger } from \"@/core/logger\";\nimport type { ContentItem } from \"@/stream/protocol\";\nimport { TitleBar } from \"../back-header\";\nimport { errorMessageFor } from \"../error-message\";\nimport { NewsIcon } from \"../icons\";\nimport { ModuleState } from \"../module-state\";\nimport { tid, TID } from \"../test-ids\";\nimport type { ModuleScreenProps, WidgetLayout } from \"./types\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"news\");\n\nfunction NewsRoot({ transport, strings, config, nav, panelProps }: ModuleScreenProps) {\n const tags = config.contentTags;\n const [state, setState] = useState<\"loading\" | \"loaded\" | \"error\">(\"loading\");\n const [errorMsg, setErrorMsg] = useState(strings.errorGeneric);\n const [items, setItems] = useState<ContentItem[]>([]);\n const [reloadKey, setReloadKey] = useState(0);\n\n useEffect(() => {\n let cancelled = false;\n setState(\"loading\");\n transport\n .listContent({ tags, sortBy: \"publishedAt\", sortOrder: \"desc\" })\n .then((res) => {\n if (cancelled) return;\n setItems(res.items);\n setState(\"loaded\");\n })\n .catch((err) => {\n if (cancelled) return;\n log.warn(\"listContent (news) failed\", { err });\n setErrorMsg(errorMessageFor(err, strings));\n setState(\"error\");\n });\n return () => {\n cancelled = true;\n };\n }, [transport, tags, reloadKey]);\n\n function renderBody() {\n if (state === \"loading\") return <ModuleState message={strings.newsLoading} strings={strings} />;\n if (state === \"error\") {\n return (\n <ModuleState tone=\"error\" message={errorMsg} onRetry={() => setReloadKey((k) => k + 1)} strings={strings} />\n );\n }\n if (items.length === 0) return <ModuleState message={strings.newsEmpty} strings={strings} />;\n return (\n <div class={`${p}-news-list`}>\n {items.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n class={`${p}-news-card`}\n onClick={() => nav.push({ kind: \"content\", id: item.id, title: item.title })}\n data-testid={tid(TID.newsItem, item.id)}\n >\n {item.image ? <img class={`${p}-news-hero`} src={item.image} alt=\"\" loading=\"lazy\" /> : null}\n <span class={`${p}-news-body`}>\n {item.tags && item.tags.length > 0 ? (\n <span class={`${p}-news-tags`}>\n {item.tags.map((t) => (\n <span key={t} class={`${p}-news-tag`}>\n {t}\n </span>\n ))}\n </span>\n ) : null}\n <span class={`${p}-news-title`}>{item.title}</span>\n {item.description ? <span class={`${p}-news-summary`}>{item.description}</span> : null}\n </span>\n </button>\n ))}\n </div>\n );\n }\n\n return (\n <div class={`${p}-module`}>\n <TitleBar title={strings.newsTitle} onClose={panelProps.onClose} closeLabel={strings.close} />\n <div class={`${p}-module-scroll`}>{renderBody()}</div>\n </div>\n );\n}\n\nexport const newsLayout: WidgetLayout = {\n Icon: NewsIcon,\n Root: (props) => <NewsRoot {...props} />,\n};\n", "/** Layout registry \u2014 `ModuleLayout \u2192 WidgetLayout`. Add a layout here. */\n\nimport type { ModuleLayout } from \"@/core/config\";\nimport { chatLayout } from \"./messages\";\nimport { helpLayout } from \"./help\";\nimport { homeLayout } from \"./home\";\nimport { newsLayout } from \"./news\";\nimport type { WidgetLayout } from \"./types\";\n\nexport const LAYOUTS: Record<ModuleLayout, WidgetLayout> = {\n chat: chatLayout,\n home: homeLayout,\n help: helpLayout,\n news: newsLayout,\n};\n", "/** Bottom navigation for the messenger home. One button per tab (resolved module). */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { ResolvedModule } from \"@/core/config\";\nimport { moduleLabel, type Strings } from \"@/i18n/strings\";\nimport { LAYOUTS } from \"./modules/registry\";\nimport type { HomeTab } from \"./home-nav\";\nimport { tid, TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n modules: ResolvedModule[];\n activeTab: HomeTab;\n strings: Strings;\n /** Total unread across conversations \u2014 shows a badge on the chat tab. */\n unreadCount: number;\n onSelect: (tab: HomeTab) => void;\n}\n\nexport function HomeTabBar({ modules, activeTab, strings, unreadCount, onSelect }: Props) {\n return (\n <nav class={`${p}-tabbar`} role=\"tablist\" aria-label={strings.panelTitle}>\n {modules.map((m) => {\n const Icon = LAYOUTS[m.layout].Icon;\n const selected = m.id === activeTab;\n // Badge the chat tab when there are unread replies. Cap the display at\n // 9+ so a long backlog never blows out the tab width.\n const badge = m.layout === \"chat\" && unreadCount > 0 ? (unreadCount > 9 ? \"9+\" : String(unreadCount)) : null;\n return (\n <button\n key={m.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n class={`${p}-tab`}\n onClick={() => onSelect(m.id)}\n data-testid={tid(TID.tab, m.id)}\n >\n <span class={`${p}-tab-icon`} aria-hidden=\"true\">\n <Icon />\n {badge ? (\n <span class={`${p}-tab-badge`} data-testid={TID.tabBadge}>\n {badge}\n </span>\n ) : null}\n </span>\n <span class={`${p}-tab-label`}>{moduleLabel(strings, m.label)}</span>\n </button>\n );\n })}\n </nav>\n );\n}\n", "/**\n * In-widget sandboxed iframe viewer \u2014 help articles, status pages, and any\n * `{kind:\"iframe\"}` drill-in. The host expands the panel for reading (see\n * MessengerHome) and restores the prior size on Back.\n *\n * Sandbox intentionally omits `allow-same-origin` so a cross-origin page\n * can't script the embedder. `referrerpolicy` strips the referrer.\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/i18n/strings\";\nimport { BackHeader } from \"./back-header\";\n\nconst p = BRAND.cssPrefix;\nconst SANDBOX = \"allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads\";\n\ninterface Props {\n url: string;\n title: string;\n strings: Strings;\n onBack: () => void;\n onClose?: () => void;\n}\n\nexport function IframeView({ url, title, strings, onBack, onClose }: Props) {\n return (\n <div class={`${p}-module`}>\n <BackHeader\n title={title || strings.moduleBack}\n backLabel={strings.moduleBack}\n onBack={onBack}\n onClose={onClose}\n closeLabel={strings.close}\n />\n <iframe\n class={`${p}-content-frame`}\n src={url}\n title={title || \"content\"}\n sandbox={SANDBOX}\n referrerpolicy=\"no-referrer\"\n loading=\"lazy\"\n allow=\"fullscreen\"\n />\n </div>\n );\n}\n", "/**\n * Content reader \u2014 fetches one {@link ContentItem} by id and renders it\n * natively: hero image, title, then its markdown `content`. Backs every\n * `{kind:\"content\"}` screen (news items, inline articles, docs).\n */\n\nimport { useCallback, useEffect, useState } from \"preact/hooks\";\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/i18n/strings\";\nimport { logger } from \"@/core/logger\";\nimport type { ContentItem } from \"@/stream/protocol\";\nimport type { AgentTransport } from \"@/stream/transport\";\nimport { BackHeader } from \"./back-header\";\nimport { StaticMarkdown } from \"./markdown\";\nimport { ModuleState } from \"./module-state\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\nconst log = logger.scope(\"content\");\n\ninterface Props {\n id: string;\n title: string;\n transport: AgentTransport;\n strings: Strings;\n onBack: () => void;\n onClose?: () => void;\n}\n\nexport function ContentView({ id, title, transport, strings, onBack, onClose }: Props) {\n const [item, setItem] = useState<ContentItem | null>(null);\n const [failed, setFailed] = useState(false);\n const [reloadKey, setReloadKey] = useState(0);\n const retry = useCallback(() => {\n setFailed(false);\n setItem(null);\n setReloadKey((k) => k + 1);\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n transport\n .listContent({ ids: [id], limit: 1 })\n .then((res) => {\n if (cancelled) return;\n const found = res.items[0];\n if (found) setItem(found);\n else setFailed(true);\n })\n .catch((err) => {\n if (cancelled) return;\n log.warn(\"listContent (reader) failed\", { err });\n setFailed(true);\n });\n return () => {\n cancelled = true;\n };\n }, [transport, id, reloadKey]);\n\n function renderBody() {\n if (failed) return <ModuleState tone=\"error\" message={strings.errorGeneric} onRetry={retry} strings={strings} />;\n if (item === null) return <ModuleState message={strings.contentLoading} strings={strings} />;\n return (\n <article class={`${p}-content`} data-testid={TID.contentView}>\n {item.image ? <img class={`${p}-content-hero`} src={item.image} alt=\"\" loading=\"lazy\" /> : null}\n {item.description ? <p class={`${p}-content-subtitle`}>{item.description}</p> : null}\n {item.tags && item.tags.length > 0 ? (\n <span class={`${p}-news-tags`}>\n {item.tags.map((t) => (\n <span key={t} class={`${p}-news-tag`}>\n {t}\n </span>\n ))}\n </span>\n ) : null}\n <StaticMarkdown text={item.content ?? \"\"} />\n </article>\n );\n }\n\n return (\n <div class={`${p}-module`}>\n <BackHeader\n title={item?.title || title || strings.moduleBack}\n backLabel={strings.moduleBack}\n onBack={onBack}\n onClose={onClose}\n closeLabel={strings.close}\n testid={TID.backHeader}\n />\n <div class={`${p}-module-scroll`} data-testid={TID.contentScroll}>\n {renderBody()}\n </div>\n </div>\n );\n}\n", "/**\n * Messenger-home surface \u2014 the tabbed container shown when \u22652 modules are\n * enabled. Owns ONE panel card (dialog + focus trap + file-drop) so the\n * bottom tab bar stays keyboard-reachable. Routes the active tab root and\n * cross-cutting drill-ins (thread / iframe / content) itself; module-owned\n * drill-ins (help-collection / news-item) go through the module's `Detail`.\n * Opening an iframe/content screen grows the panel to `expanded` and Back\n * restores the prior size.\n */\n\nimport { useCallback, useEffect, useRef } from \"preact/hooks\";\nimport { useComputed } from \"@preact/signals\";\nimport { BRAND } from \"@/core/brand\";\nimport type { ResolvedModule } from \"@/core/config\";\nimport { trapFocus } from \"@/shadow/a11y\";\nimport type { ComposerAttachAPI } from \"./composer\";\nimport { atTabRoot, type HomeNav, topScreen } from \"./home-nav\";\nimport type { PanelSize } from \"./host-mode\";\nimport { HomeTabBar } from \"./home-tab-bar\";\nimport { IframeView } from \"./iframe-view\";\nimport { ContentView } from \"./content-view\";\nimport { LAYOUTS } from \"./modules/registry\";\nimport type { ModuleNav, ModuleScreenProps } from \"./modules/types\";\nimport { PanelContent, PoweredByBar } from \"./panel\";\nimport { ResizeGrip } from \"./resize-grip\";\nimport type { PanelProps } from \"./page-shell\";\nimport { useFileDrop } from \"./use-file-drop\";\nimport { useDragMove } from \"./use-drag-move\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\ninterface Props {\n panelProps: PanelProps;\n enabledModules: ResolvedModule[];\n nav: HomeNav;\n /** Total unread across conversations \u2014 drives the Messages-tab badge. */\n unreadCount: number;\n onSelectSession: (sessionId: string) => void;\n onStartNewSession: () => void;\n /** Set panel size (used to grow to `expanded` while reading an iframe/content). */\n onSetPanelSize: (size: PanelSize) => void;\n}\n\nexport function MessengerHome({\n panelProps,\n enabledModules,\n nav,\n unreadCount,\n onSelectSession,\n onStartNewSession,\n onSetPanelSize,\n}: Props) {\n const options = panelProps.options;\n const strings = options.strings;\n const containerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const dispose = trapFocus(el);\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") panelProps.onClose?.();\n };\n el.addEventListener(\"keydown\", onKey);\n return () => {\n dispose();\n el.removeEventListener(\"keydown\", onKey);\n };\n }, [panelProps.onClose]);\n\n const composerAttachApiRef = useRef<ComposerAttachAPI | null>(null);\n const onDropItems = useCallback((items: DataTransferItemList) => {\n composerAttachApiRef.current?.attachFromDrop(items);\n }, []);\n const { visible: dragOver } = useFileDrop({ containerRef, onDrop: onDropItems });\n\n // Modal mode: grab a chrome bar (header / back-header / home hero) to move\n // the centred dialog. One controller for the whole messenger card.\n useDragMove(containerRef.current, options.mode === \"modal\");\n\n const navState = useComputed(() => nav.sig.value).value;\n const top = topScreen(navState);\n const activeModule = options.modules.byId[navState.activeTab];\n\n // A \"reader\" is the final, immersive content view (article iframe, structured\n // content doc, news item) \u2014 it expands the panel and hides the bottom tab bar.\n // List/browsing drill-ins (help collections \u2192 sub-collections \u2192 sections)\n // KEEP the tab bar, so the visitor stays oriented while navigating the\n // hierarchy. Tabs show at a tab root OR on a non-reader drill-in.\n const isReader = top?.kind === \"iframe\" || top?.kind === \"content\";\n const showTabBar = enabledModules.length > 1 && (atTabRoot(navState) || !isReader);\n const savedSize = useRef<PanelSize | null>(null);\n useEffect(() => {\n if (isReader && savedSize.current === null) {\n savedSize.current = panelProps.panelSize;\n if (panelProps.panelSize === \"normal\") onSetPanelSize(\"expanded\");\n } else if (!isReader && savedSize.current !== null) {\n const prev = savedSize.current;\n savedSize.current = null;\n onSetPanelSize(prev);\n }\n }, [isReader]);\n\n const moduleNav: ModuleNav = {\n push: nav.push,\n pop: nav.pop,\n switchTab: nav.switchTab,\n switchToLayout: (layout) => {\n const target = enabledModules.find((m) => m.layout === layout);\n if (target) nav.switchTab(target.id);\n },\n selectSession: onSelectSession,\n startNewSession: onStartNewSession,\n };\n\n const screenProps = (module: ResolvedModule): ModuleScreenProps => ({\n options,\n transport: panelProps.transport,\n strings,\n config: module,\n visitorId: panelProps.visitorId,\n nav: moduleNav,\n panelProps,\n });\n\n let body: preact.JSX.Element | null;\n if (top?.kind === \"iframe\") {\n body = (\n <IframeView url={top.url} title={top.title} strings={strings} onBack={nav.pop} onClose={panelProps.onClose} />\n );\n } else if (top?.kind === \"content\") {\n body = (\n <ContentView\n id={top.id}\n title={top.title}\n transport={panelProps.transport}\n strings={strings}\n onBack={nav.pop}\n onClose={panelProps.onClose}\n />\n );\n } else if (activeModule?.layout === \"chat\") {\n // A `chat`-layout tab IS the chat \u2014 the existing panel (header history +\n // composer), driven by the App's chat/history view.\n body = (\n <PanelContent\n {...panelProps}\n containerEl={containerRef.current}\n dragOver={dragOver}\n composerAttachApiRef={composerAttachApiRef}\n />\n );\n } else if (activeModule) {\n const Root = LAYOUTS[activeModule.layout].Root;\n body = Root ? <Root key={activeModule.id} {...screenProps(activeModule)} /> : null;\n } else {\n body = null;\n }\n\n return (\n <div\n ref={containerRef}\n class={`${p}-panel ${p}-messenger`}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-label={strings.panelTitle}\n style={{ position: \"relative\" }}\n data-testid={TID.messengerHome}\n >\n <div class={`${p}-messenger-body`}>{body}</div>\n {showTabBar ? (\n <HomeTabBar\n modules={enabledModules}\n activeTab={navState.activeTab}\n strings={strings}\n unreadCount={unreadCount}\n onSelect={nav.switchTab}\n />\n ) : null}\n {/* Global \"Powered by\" \u2014 below the tab bar, shown on every tab. */}\n <PoweredByBar poweredBy={options.poweredBy} />\n {/* Drag-resize the whole floating panel \u2014 available on every tab. The\n Messages chat renders PanelContent, which carries its own grip, so we\n skip ours there to avoid a duplicate. */}\n {options.size.resize?.enabled && !(activeModule?.layout === \"chat\" && !top) ? (\n <ResizeGrip\n panelEl={containerRef.current}\n resize={options.size.resize}\n position={options.position}\n initialSize={panelProps.initialWidgetSize}\n onSizeChange={panelProps.onWidgetSizeChange}\n strings={strings}\n />\n ) : null}\n </div>\n );\n}\n", "/** Shown in the panel when no module is enabled (every tab toggled off). */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Strings } from \"@/i18n/strings\";\nimport { CloseIcon } from \"./icons\";\nimport { TID } from \"./test-ids\";\n\nconst p = BRAND.cssPrefix;\n\nexport function ModulesEmpty({ strings, onClose }: { strings: Strings; onClose?: () => void }) {\n return (\n <div\n class={`${p}-panel ${p}-modules-empty`}\n role=\"dialog\"\n aria-label={strings.panelTitle}\n data-testid={TID.modulesEmpty}\n >\n {onClose ? (\n <button\n type=\"button\"\n class={`${p}-icon-btn ${p}-modules-empty-close`}\n onClick={onClose}\n aria-label={strings.close}\n title={strings.close}\n >\n <CloseIcon />\n </button>\n ) : null}\n <p class={`${p}-modules-empty-text`}>{strings.modulesEmpty}</p>\n </div>\n );\n}\n", "/**\n * Typewriter animator for server-pushed welcome messages.\n *\n * Each message's text signal starts empty; this module fills it at a target\n * chars-per-second rate using `requestAnimationFrame` for smooth playback.\n * Multiple welcome messages animate sequentially so they feel like a real\n * greeting, not a wall of text.\n *\n * Cancellation is driven by an `AbortSignal` \u2014 the App aborts on clear /\n * unmount and the in-flight rAF is cancelled immediately.\n */\n\nimport type { ReactiveMessage } from \"@/stream/messages\";\n\n/** Type each welcome message in sequence. Aborts cleanly on `abort`. */\nexport async function animateWelcomeSequence(\n reactives: ReactiveMessage[],\n payloads: Array<{ text: string }>,\n charsPerSecond: number,\n abort: AbortSignal,\n): Promise<void> {\n for (let i = 0; i < reactives.length; i++) {\n if (abort.aborted) return;\n const msg = reactives[i];\n const payload = payloads[i];\n if (!msg || !payload) continue;\n await animateTypewriter(msg, payload.text, charsPerSecond, abort);\n }\n}\n\n/**\n * Animate `text` into the message's first text part at `charsPerSecond`.\n *\n * Resolves after the full text is rendered (or immediately on abort /\n * when rAF is unavailable, falling back to an instant paint).\n */\nexport function animateTypewriter(\n msg: ReactiveMessage,\n text: string,\n charsPerSecond: number,\n abort: AbortSignal,\n): Promise<void> {\n return new Promise((resolve) => {\n const part = msg.partsSig.value[0];\n if (!part || part.kind !== \"text\") {\n resolve();\n return;\n }\n\n const finish = () => {\n part.textSig.value = text;\n part.doneSig.value = true;\n msg.status = \"complete\";\n resolve();\n };\n\n // Fallback for non-browser runtimes (SSR-ish, tests without rAF polyfill).\n if (typeof requestAnimationFrame === \"undefined\") {\n finish();\n return;\n }\n\n const totalMs = (text.length / Math.max(1, charsPerSecond)) * 1000;\n const start = performance.now();\n\n const tick = (now: number) => {\n if (abort.aborted) return finish();\n const progress = totalMs === 0 ? 1 : Math.min(1, (now - start) / totalMs);\n part.textSig.value = text.slice(0, Math.floor(progress * text.length));\n if (progress >= 1) return finish();\n rafId = requestAnimationFrame(tick);\n };\n let rafId = requestAnimationFrame(tick);\n\n abort.addEventListener(\n \"abort\",\n () => {\n cancelAnimationFrame(rafId);\n finish();\n },\n { once: true },\n );\n });\n}\n", "/**\n * Launcher-callout dismissal state.\n *\n * Tracks whether the current callout copy is dismissed for THIS\n * session, with a configurable \"persistent\" mode:\n *\n * \u2022 `persistent: true` (default) \u2014 the bubble re-shows on every\n * visit regardless of prior dismissal. The \"recurring greeting\"\n * behaviour. `dismissCallout()` only flips the in-session flag;\n * reloading the page surfaces the bubble again.\n *\n * \u2022 `persistent: false` \u2014 `dismissCallout()` writes the dismissed\n * TEXT to persistence. Subsequent loads of the SAME text stay\n * hidden; changing the text bypasses the dismissal (a new\n * message is a new ask, not a duplicate of the dismissed one).\n *\n * Callers read `dismissed` + call `dismissCallout()` from the\n * launcher / panel-open code paths.\n */\n\nimport { useCallback, useEffect, useState } from \"preact/hooks\";\nimport type { LauncherCalloutOptions } from \"@/core/config\";\nimport type { Persistence } from \"@/core/persistence\";\n\ninterface UseLauncherCalloutOptions {\n callout: LauncherCalloutOptions | null;\n persistence: Persistence;\n}\n\ninterface UseLauncherCalloutResult {\n dismissed: boolean;\n /** Mark the current callout copy as dismissed (in-session + persistent variants). */\n dismissCallout: () => void;\n}\n\nexport function useLauncherCallout({ callout, persistence }: UseLauncherCalloutOptions): UseLauncherCalloutResult {\n const textKey = callout?.text ?? \"\";\n // `null` callout means \"no bubble configured\"; treat as non-persistent\n // for the type narrowing. Default to `true` when callout exists but\n // doesn't explicitly opt out (matches the schema `.default(true)`).\n const persistent = callout?.persistent ?? true;\n\n const [dismissed, setDismissed] = useState(() => (persistent ? false : persistence.loadCalloutDismissed(textKey)));\n\n // Re-read dismissal on text / persistence change \u2014 a different\n // configured message bypasses the previous dismissal.\n useEffect(() => {\n setDismissed(persistent ? false : persistence.loadCalloutDismissed(textKey));\n }, [textKey, persistent, persistence]);\n\n const dismissCallout = useCallback(() => {\n setDismissed(true);\n if (!persistent) persistence.saveCalloutDismissed(textKey);\n }, [persistence, textKey, persistent]);\n\n return { dismissed, dismissCallout };\n}\n", "import { useCallback, useEffect, useMemo, useRef, useState } from \"preact/hooks\";\nimport { useSignal } from \"@preact/signals\";\nimport { BRAND } from \"@/core/brand\";\nimport { applyOptionOverrides, type PageContext, type ResolvedOptions, type ThemePreference } from \"@/core/config\";\nimport { isBlocksConfigShape, isSiteConfigShape } from \"@/core/start-session-shape\";\nimport type { BlocksConfig, SiteConfig } from \"@/schema\";\nimport { bindHostCommands } from \"@/core/host-commands\";\nimport { applyMode } from \"@/shadow/mount\";\nimport {\n computeHostMode,\n currentViewportWidth,\n type PanelSize,\n pickInitialPanelSize,\n resolveInitialPanelState,\n} from \"./host-mode\";\nimport { createAuth } from \"@/core/auth\";\nimport { uuid7 } from \"@/core/ids\";\nimport { AgentTransport } from \"@/stream/transport\";\nimport {\n buildSendMessageRequest,\n type SessionSummary,\n type SessionAgent,\n type StartSessionResponse,\n type SessionSuggestion,\n} from \"@/stream/protocol\";\nimport {\n assistantText,\n fromReactive,\n fromWireMessage,\n makeAssistantMessage,\n makeInstantWelcomeMessage,\n makeTypewriterMessage,\n makeUserMessage,\n type ReactiveMessage,\n} from \"@/stream/messages\";\nimport { StreamReducer } from \"@/stream/reducer\";\nimport { createPersistence, type UserPrefs, type WidgetSize } from \"@/core/persistence\";\nimport { logger } from \"@/core/logger\";\n\nconst log = logger.scope(\"app\");\nimport type { EventBus } from \"@/core/events\";\nimport type { LocalAttachment } from \"@/core/attachments\";\nimport { FeedbackBus } from \"@/core/feedback\";\nimport type { Message } from \"@/stream/types\";\nimport { errorMessageFor } from \"./error-message\";\nimport { Launcher, LauncherCallout } from \"./launcher\";\nimport { Panel, type PanelView } from \"./panel\";\nimport { PageShell } from \"./page-shell\";\nimport { createHomeNav, type HomeNav } from \"./home-nav\";\nimport { MessengerHome } from \"./messenger-home\";\nimport { ModulesEmpty } from \"./modules-empty\";\nimport { isActionVisible, resolveCloseAction } from \"./action-visibility\";\nimport { animateWelcomeSequence } from \"./welcome-animator\";\nimport { useLauncherCallout } from \"./use-launcher-callout\";\n\nconst p = BRAND.cssPrefix;\n\ninterface AppProps {\n options: ResolvedOptions;\n hostElement: HTMLElement;\n bus: EventBus;\n}\n\nexport function App({ options, hostElement, bus }: AppProps) {\n // Each widgetId gets its own storage namespace \u2014 two widgets on the\n // same brand (different deployments on one page) stay isolated.\n const [persistence] = useState(() => createPersistence(options.widgetId, options.storage));\n // Stable anonymous browser id. Minted on first ever load, reused forever.\n // Sent on every backend request so the server can attach per-visitor\n // settings, conversation history, etc.\n // `setVisitorId` is only called when the server rebinds the id via\n // `/start-session` (collision / expiration). Steady state \u2014 every common\n // mount \u2014 this stays the same value `useState`'s lazy initializer\n // read on mount.\n const [visitorId, setVisitorId] = useState(() => persistence.getVisitorId());\n // Cached preferences. Empty `{}` means \"no client-side cache yet\" \u2014 the\n // server's reply on `connect()` becomes the seed.\n const initialSettings = persistence.loadUserPrefs();\n // The end-user's last picked locale (from the language switcher) wins\n // over anything the page-owner / server set \u2014 it's their explicit\n // personal choice. Falls through to `options.locale` (which itself\n // already accounts for `defaultLocale: \"auto\"` detection).\n const [activeLocale, setActiveLocale] = useState<string>(() => initialSettings.locale ?? options.locale);\n // End-user's last picked theme mode. Same precedence as locale \u2014 the\n // picker writes both to persisted `userPrefs` and to the host's\n // `data-theme` attribute so the panel styling reflects the choice\n // immediately without waiting for `update()` to round-trip.\n const [activeThemeMode, setActiveThemeMode] = useState<ThemePreference>(\n () => initialSettings.themeMode ?? options.themeMode,\n );\n // Conversation thread id \u2014 persisted across reloads. The backend is the\n // source of truth for the actual messages; we re-fetch them via\n // `transport.loadSession()` after `connect()` resolves.\n const sessionIdSig = useSignal<string | undefined>(persistence.loadSessionId());\n // Messages start empty \u2014 historical content arrives async via loadSession().\n const messagesSig = useSignal<ReactiveMessage[]>([]);\n // Total unread across the visitor's conversations \u2014 drives the Messages-tab\n // badge. Server-owned: summed from `/sessions` `unreadCount` (set by the\n // backend's per-(visitor,session) `lastReadAt`), refreshed on open + after a\n // thread is marked read.\n const unreadCountSig = useSignal(0);\n // True while the resume `loadSession()` is in flight \u2014 drives a placeholder\n // in MessageList so reload of a long thread doesn't flash empty.\n const [loadingMessages, setLoadingMessages] = useState(false);\n // Messenger-home navigation (active tab + per-tab drill-in stacks).\n // Created once and kept across open/close so the last tab/screen is\n // preserved; on a fresh load it starts on the persisted (or default)\n // tab. `switchTab` persists the choice.\n function landingTab(): string {\n const list = options.modules.list;\n const ids = new Set(list.map((m) => m.id));\n const persisted = persistence.loadActiveModule();\n if (persisted && ids.has(persisted)) return persisted;\n return list[0]?.id ?? \"\";\n }\n /** The id of the chat-layout tab (where conversations open), if any. */\n function chatTabId(): string | undefined {\n return options.modules.list.find((m) => m.layout === \"chat\")?.id;\n }\n const homeNavRef = useRef<HomeNav>();\n if (!homeNavRef.current) {\n const ids = options.modules.list.map((m) => m.id);\n homeNavRef.current = createHomeNav(landingTab(), ids, (tab) => persistence.saveActiveModule(tab));\n }\n const homeNav = homeNavRef.current;\n // Gates the FIRST render. Stays `false` until the initial start-session\n // resolves (success OR error) so the widget never paints with stale\n // mount-time defaults that the server immediately overrides \u2014 e.g.\n // a `mode: \"modal\"` deployment briefly showing a floating launcher\n // before the merged options kick in. After the first start-session the\n // flag stays `true` for the lifetime of the instance; subsequent\n // re-connects (`handleClear`) don't reset it.\n const [sessionReady, setSessionReady] = useState(false);\n\n const isInlineLike = options.mode === \"standalone\" || options.mode === \"inline\";\n // Initial open + panel-size state. Shared helper with `index.ts`'s\n // `resolveInitialHostMode` so the first App render and the pre-mount\n // iframe sizing agree \u2014 otherwise the iframe paints at the wrong size\n // and snaps to the right one on the next tick (the flicker bug).\n //\n // Inline / standalone are always \"open\" (panel in flow / full viewport).\n // Floating modes: user's persisted pick wins over `startMinimized` \u2014\n // closing then reloading doesn't force an auto-reopen.\n //\n // `useRef` (not `useState`) to compute exactly once on mount \u2014 we'd\n // otherwise call `resolveInitialPanelState` twice, once per useState\n // initializer, even though every field comes from the same snapshot.\n //\n // The values are recomputed below in a one-shot effect once the\n // start-session resolves \u2014 see `initialPanelApplied`. This ref's value\n // seeds the FIRST render (which is gated by `sessionReady`, so\n // it never paints to the user), then the effect overlays the\n // server-merged options.\n const initialPanelRef = useRef(\n resolveInitialPanelState(options, currentViewportWidth(), persistence.loadPanelOpen(), persistence.loadPanelSize()),\n );\n const [isOpen, setIsOpen] = useState(initialPanelRef.current.panelOpen);\n // Flips to `true` after the first post-start-session `isOpen` / `panelSize`\n // sync below runs. Stays `true` for the lifetime of the App so subsequent\n // server-settings merges don't fight the user's manual open/close +\n // expand/fullscreen choices.\n const initialPanelApplied = useRef(false);\n // True for the brief window between user-clicking-launcher and the\n // launcher actually unmounting \u2014 lets the FAB run a fade-out animation\n // in parallel with the panel's fade-in, avoiding a visible \"FAB \u2192 blank\n // \u2192 panel\" flicker. Cleared by a timer below.\n const [launcherLeaving, setLauncherLeaving] = useState(false);\n // Callout dismissal state \u2014 see `useLauncherCallout` for the\n // persistent vs in-session behaviour. The destructured names below\n // (`calloutDismissed` / `dismissCallout`) keep the call sites\n // unchanged from when this lived inline.\n const { dismissed: calloutDismissed, dismissCallout: dismissCalloutRaw } = useLauncherCallout({\n callout: options.launcher.callout,\n persistence,\n });\n const calloutPersistent = options.launcher.callout?.persistent ?? true;\n // Wrap the hook's dismiss so the action surfaces as both a log line\n // and a bus event \u2014 keeps the hook free of cross-cutting concerns.\n const dismissCallout = useCallback(() => {\n log.info(\"calloutDismiss\");\n bus.emit(\"calloutDismiss\", undefined);\n dismissCalloutRaw();\n }, [bus, dismissCalloutRaw]);\n const [panelSize, setPanelSize] = useState<PanelSize>(initialPanelRef.current.panelSize);\n // One-shot flag so the server-push effect below doesn't override the user\n // after they've manually expanded / fullscreen-ed.\n const initialSizeApplied = useRef(false);\n const [view, setView] = useState<PanelView>(\"chat\");\n // `canSend = false` switches the composer to a read-only banner when a\n // past, closed chat is being viewed (`canContinue = false` from the API).\n const [canSend, setCanSend] = useState(true);\n const [streaming, setStreaming] = useState(false);\n const [agent, setAgent] = useState<SessionAgent | null>(null);\n const [suggestions, setSuggestions] = useState<SessionSuggestion[]>([]);\n const [activeCancel, setActiveCancel] = useState<(() => void) | null>(null);\n const welcomeAbortRef = useRef<AbortController | null>(null);\n // Page-mode-only payloads parsed from the start-session. Both stay\n // `undefined` for widget/inline/standalone \u2014 the sidebar simply renders\n // empty sections when blocks are missing, so the parse-failure fallback\n // is a no-op rather than a hard error.\n const [parsedSite, setParsedSite] = useState<SiteConfig | undefined>(undefined);\n const [parsedBlocks, setParsedBlocks] = useState<BlocksConfig | undefined>(undefined);\n // Page-mode-only: sidebar collapse state, persisted per-device. `null`\n // from `loadSidebarCollapsed()` means user has never toggled \u2014 default\n // to expanded so first-visit users see the conversation list.\n const [sidebarCollapsed, setSidebarCollapsed] = useState(() => persistence.loadSidebarCollapsed() ?? false);\n\n const [transport] = useState(\n () =>\n new AgentTransport({\n baseUrl: options.baseUrl,\n auth: createAuth({\n publicKey: options.publicKey,\n aiAgentDeploymentId: options.aiAgentDeploymentId,\n publicKeyHeader: \"x-public-key\",\n }),\n endpoints: options.endpoints,\n }),\n );\n const [reducer] = useState(() => new StreamReducer(messagesSig));\n const [feedback] = useState(() => new FeedbackBus(options.sound, options.haptics));\n\n /**\n * Apply a settings patch locally + push to the backend (fire-and-forget).\n * Server failure leaves the local cache valid; the next `connect()` re-syncs.\n */\n const patchAndSync = useCallback(\n (patch: Partial<UserPrefs>) => {\n persistence.patchUserPrefs(patch);\n transport.saveUserPrefs(persistence.loadUserPrefs()).catch((err) => {\n log.warn(\"saveUserPrefs failed\", { err });\n });\n },\n [persistence, transport],\n );\n\n // Apply the cached `soundMuted` preference once on mount.\n useEffect(() => {\n if (initialSettings.soundMuted !== undefined) feedback.setMuted(initialSettings.soundMuted);\n // Mount-only init.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Keep the host's `data-theme` attribute in lockstep with `activeThemeMode`.\n // `applyHostAttributes` (called from index.ts on mount AND on every\n // server-settings merge) writes `options.themeMode` to the host \u2014 which\n // would stomp a user's picked/persisted theme unless we reclaim it here.\n // Depending on `options` makes this effect re-fire after the connect\n // merge too, so the user's pick survives the server round-trip.\n useEffect(() => {\n hostElement.dataset.theme = activeThemeMode;\n }, [hostElement, activeThemeMode, options]);\n\n // Config can change after a server-settings merge \u2014 keep the bus in sync.\n useEffect(() => {\n feedback.update(options.sound, options.haptics);\n }, [feedback, options.sound, options.haptics]);\n\n // Release audio resources on unmount.\n useEffect(() => () => feedback.destroy(), [feedback]);\n\n // Subscribe to mute toggles \u2192 persist + push to backend. Skip the very\n // first emission (signal subscription always fires the current value\n // synchronously on subscribe), since the *initial* state isn't a user\n // action and we don't want to round-trip it.\n useEffect(() => {\n let firstRun = true;\n return feedback.mutedSig.subscribe((muted) => {\n if (firstRun) {\n firstRun = false;\n return;\n }\n patchAndSync({ soundMuted: muted });\n });\n }, [feedback, patchAndSync]);\n\n // `options.size.initialSize` often arrives via server settings AFTER the\n // first render. The ref guard keeps this idempotent across the (cheap)\n // re-evaluations triggered when `options.size` is re-spread by the merge.\n useEffect(() => {\n if (initialSizeApplied.current || !options.size.initialSize) return;\n initialSizeApplied.current = true;\n if (persistence.loadPanelSize()) return; // the user's persisted size wins over `initialSize`\n setPanelSize(pickInitialPanelSize(options.mode, options.size, currentViewportWidth()));\n }, [options.mode, options.size, persistence]);\n\n // Re-derive `isOpen` + `panelSize` from the SERVER-MERGED options once\n // the start-session resolves. The mount-time `useState` initializer ran\n // with pre-start-session options (no `mode`, default `startMinimized:\n // true`), so a deployment with `startMinimized: false` + `mode:\n // \"modal\"` would otherwise paint as closed because the initial state\n // was captured before the server's overrides arrived.\n //\n // One-shot via `initialPanelApplied` \u2014 subsequent merges (e.g. a\n // server-pushed locale change) must NOT reopen a panel the user just\n // closed, or reset their drag-resize, etc.\n useEffect(() => {\n if (!sessionReady || initialPanelApplied.current) return;\n initialPanelApplied.current = true;\n const state = resolveInitialPanelState(\n options,\n currentViewportWidth(),\n persistence.loadPanelOpen(),\n persistence.loadPanelSize(),\n );\n setIsOpen(state.panelOpen);\n setPanelSize(state.panelSize);\n }, [sessionReady, options, persistence]);\n\n // Seed the messenger landing tab once the start-session's module config has\n // merged (the nav controller was created from pre-start-session options). The\n // panel is gated on `sessionReady`, so the user can't have navigated yet.\n const homeNavSeeded = useRef(false);\n useEffect(() => {\n if (!sessionReady || homeNavSeeded.current) return;\n homeNavSeeded.current = true;\n homeNav.reset(landingTab());\n }, [sessionReady]);\n\n /** Render the welcome payload \u2014 typewriter or instant per options. */\n const playWelcome = useCallback(\n (welcomeMessages: NonNullable<StartSessionResponse[\"welcome\"]>[\"messages\"]) => {\n if (!welcomeMessages || welcomeMessages.length === 0) return;\n const { animation, speed } = options.welcome;\n if (animation === \"instant\") {\n messagesSig.value = welcomeMessages.map(makeInstantWelcomeMessage);\n return;\n }\n welcomeAbortRef.current?.abort();\n const ctrl = new AbortController();\n welcomeAbortRef.current = ctrl;\n const reactives = welcomeMessages.map(makeTypewriterMessage);\n messagesSig.value = reactives;\n void animateWelcomeSequence(reactives, welcomeMessages, speed, ctrl.signal);\n },\n [messagesSig, options.welcome],\n );\n\n // ---- connect / re-connect ------------------------------------------------\n // `runStartSession` is the one place that talks to `/ai/agent/start-session`. Called:\n // 1. Once on mount (resume or first-visit welcome).\n // 2. Again whenever the user clicks \"New conversation\" \u2014 `newConversation`\n // flag tells the backend to mint a fresh sessionId + return fresh welcome.\n // Race safety: a generation counter discards any in-flight response if a\n // newer call has started or the component has unmounted.\n const connectGenRef = useRef(0);\n\n const runStartSession = useCallback(\n async ({ newConversation }: { newConversation: boolean }) => {\n const myGen = ++connectGenRef.current;\n const isStale = () => myGen !== connectGenRef.current;\n const persistedChatId = sessionIdSig.value;\n const sessionId = newConversation || !persistedChatId ? uuid7() : persistedChatId;\n if (sessionId !== persistedChatId) {\n sessionIdSig.value = sessionId;\n persistence.saveSessionId(sessionId);\n }\n\n let res: StartSessionResponse;\n try {\n res = await transport.startSession({\n visitorId,\n sessionId,\n userPrefs: persistence.loadUserPrefs(),\n locale: options.locale,\n });\n } catch (err) {\n if (isStale()) return;\n bus.emit(\"error\", err);\n options.onError?.(err);\n // Unblock the first render anyway \u2014 leaving the widget invisible\n // forever on a network failure is worse than rendering with the\n // local mount-time options. The error event already surfaced the\n // problem to the host page.\n if (!newConversation) setSessionReady(true);\n return;\n }\n if (isStale()) return;\n\n // Visitor / session rebind \u2014 the server returned a fresh id\n // (collision, expiration, server policy). Persist the new id\n // and notify host listeners so analytics / external systems\n // pinned to the previous value can re-link. Idempotent: if the\n // server echoed back what we sent, this is a no-op. The\n // session-rebind side also flows through `sessionIdSig` updates\n // a few lines below in the !isResume branch \u2014 keep them\n // consistent.\n if (res.visitorId && res.visitorId !== visitorId) {\n log.info(\"visitor rebound\", { previous: visitorId, current: res.visitorId, reason: res.rebind?.visitorId });\n persistence.setVisitorId(res.visitorId);\n setVisitorId(res.visitorId);\n bus.emit(\"visitorRebound\", {\n previous: visitorId,\n current: res.visitorId,\n reason: res.rebind?.visitorId,\n });\n }\n if (res.sessionId && res.sessionId !== sessionId) {\n log.info(\"session rebound\", { previous: sessionId, current: res.sessionId, reason: res.rebind?.sessionId });\n bus.emit(\"sessionRebound\", {\n previous: sessionId,\n current: res.sessionId,\n reason: res.rebind?.sessionId,\n });\n }\n\n if (res.agent) setAgent(res.agent);\n if (res.welcome?.suggestions) setSuggestions(res.welcome.suggestions);\n // Page-mode site/blocks. Permissive-narrow so an unknown future\n // field doesn't blank the sidebar; on shape mismatch we keep\n // whatever was last set (typically `undefined` \u2192 sidebar shows\n // empty sections). Hand-rolled narrowers (not zod) so the IIFE\n // auto-boot path stays free of the ~70 KB gz zod runtime \u2014\n // canonical Zod schemas live under `@/schema`. Skip entirely\n // when not in page mode: `parsedSite` / `parsedBlocks` are only\n // ever read by `<PageShell>` (rendered only when `mode === \"page\"`),\n // so validating + storing them outside that mode is dead work.\n if (options.mode === \"page\") {\n if (res.site && isSiteConfigShape(res.site)) setParsedSite(res.site);\n else if (res.site) log.warn(\"invalid site config, using fallback\", { site: res.site });\n if (res.blocks && isBlocksConfigShape(res.blocks)) setParsedBlocks(res.blocks);\n else if (res.blocks) log.warn(\"invalid blocks config, using fallback\", { blocks: res.blocks });\n }\n // Server's authoritative copy of preferences wins on the *initial*\n // bootstrap (cross-device sync) \u2014 but NOT on a \"New conversation\"\n // re-connect. The user's per-device UI state (locale picker, mute\n // toggle, dragged panel size) is local-authoritative once the widget\n // is running; reapplying server's snapshot would visibly reset\n // anything the user changed since mount.\n if (res.userPrefs && !newConversation) {\n persistence.saveUserPrefs(res.userPrefs);\n if (res.userPrefs.locale) setActiveLocale(res.userPrefs.locale);\n if (res.userPrefs.soundMuted !== undefined) feedback.setMuted(res.userPrefs.soundMuted);\n if (res.userPrefs.themeMode) setActiveThemeMode(res.userPrefs.themeMode);\n }\n\n // Resume an existing chat OR start fresh. Resume is suppressed for\n // newConversation so we always render welcome on a clear.\n const isResume = !newConversation && persistedChatId && res.sessionId === persistedChatId;\n if (isResume) {\n setLoadingMessages(true);\n try {\n const chat = res.messages\n ? { sessionId: persistedChatId, canContinue: res.canContinue ?? true, messages: res.messages }\n : await transport.loadSession(persistedChatId);\n if (isStale()) return;\n messagesSig.value = chat.messages.map(fromWireMessage);\n setCanSend(chat.canContinue);\n // Connect returned new-chat suggestions (\"Show me pricing\", etc.);\n // those don't belong on a resumed conversation. Swap them out for\n // whatever loadSession says (including clearing them entirely when\n // the backend returns `undefined` / `null` \u2014 a resumed chat with\n // no suggestions is a valid state).\n setSuggestions(chat.suggestions ?? []);\n } catch (err) {\n if (isStale()) return;\n log.warn(\"loadSession failed; resetting sessionId\", { err, sessionId: persistedChatId });\n sessionIdSig.value = undefined;\n persistence.saveSessionId(undefined);\n } finally {\n if (!isStale()) setLoadingMessages(false);\n }\n } else {\n sessionIdSig.value = res.sessionId;\n persistence.saveSessionId(res.sessionId);\n playWelcome(res.welcome?.messages);\n }\n\n // Only the initial bootstrap fires \"sessionStarted\". Page-owner listeners\n // treat that event as \"widget is ready\" \u2014 firing it again on every\n // \"New conversation\" would mislead them. It also prevents index.ts\n // from re-merging `res.config` + re-resolving options, which would\n // hand the App a fresh `options.size.*` object and cause ResizeGrip\n // to re-fire its useEffect \u2014 visibly resetting the user's drag-size.\n if (!newConversation) {\n bus.emit(\"sessionStarted\", res);\n // Unblock the first render. Sequenced AFTER `bus.emit(\"sessionStarted\")`\n // so the merged options from index.ts are applied before the gate\n // lifts \u2014 guarantees no flash of stale mount-time defaults.\n setSessionReady(true);\n }\n },\n [transport, visitorId, persistence, options, bus, messagesSig, sessionIdSig, feedback, playWelcome],\n );\n\n // Keep the transport's context fresh as `userContext` / `pageContext` change \u2014\n // the host calls `instance.update(...)` after login or on SPA navigation. The\n // transport carries it as the `context` field on EVERY request, so the new\n // context rides the NEXT request with no extra round-trip (the non-obtrusive\n // path). Gated by value signatures, not object identity. Declared BEFORE the\n // mount bootstrap so the very first start-session already carries context.\n const userSig = JSON.stringify(options.userContext ?? null);\n const pageSig = JSON.stringify(options.pageContext ?? null);\n useEffect(() => {\n transport.setContext(options.userContext, toWirePageContext(options.pageContext));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [transport, userSig, pageSig]);\n\n // Mount-only bootstrap. Subsequent re-connects come from `handleClear`.\n // Other options changes that need a full re-mount go through index.ts.\n useEffect(() => {\n void runStartSession({ newConversation: false });\n return () => {\n // Invalidate any pending in-flight connect on unmount so its response\n // doesn't try to set state on a torn-down tree.\n connectGenRef.current++;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Only a USER-identity change re-runs start-session \u2014 to re-personalise the\n // greeting / welcome. A `pageContext` navigation must NOT reconnect (it just\n // refreshes the transport context above), so the session and thread are never\n // disturbed.\n const lastUserSig = useRef(userSig);\n useEffect(() => {\n if (!sessionReady || lastUserSig.current === userSig) return;\n lastUserSig.current = userSig;\n void runStartSession({ newConversation: false });\n }, [userSig, sessionReady, runStartSession]);\n\n // ---- host data-mode attribute -------------------------------------------\n useEffect(() => {\n applyMode(hostElement, computeHostMode(options.mode, panelSize, isOpen));\n }, [hostElement, isOpen, panelSize, options.mode]);\n\n // ---- modal: backdrop click closes ---------------------------------------\n // The host element IS the backdrop in modal mode (full-viewport fixed\n // overlay). Clicks on the host but outside the panel close it. Clicks\n // inside the panel propagate up via `e.target` so we filter by hit.\n useEffect(() => {\n if (options.mode !== \"modal\" || !isOpen) return;\n const onClick = (e: MouseEvent) => {\n // `composedPath` pierces the shadow DOM so we can tell whether the\n // click landed on the panel or on the bare host backdrop.\n const path = e.composedPath();\n const clickedPanel = path.some(\n (n) => n instanceof HTMLElement && n.classList.contains(`${BRAND.cssPrefix}-panel`),\n );\n if (!clickedPanel) setIsOpen(false);\n };\n hostElement.addEventListener(\"click\", onClick);\n return () => hostElement.removeEventListener(\"click\", onClick);\n }, [hostElement, isOpen, options.mode]);\n\n // ---- drawer: Escape closes from anywhere on the page --------------------\n // Drawer is non-modal \u2014 page stays interactive \u2014 so focus may sit\n // outside the panel when the user presses Escape. The Panel's own\n // keydown handler only fires on focus inside the panel; we add a\n // document-level listener to catch the rest.\n useEffect(() => {\n if (options.mode !== \"drawer\" || !isOpen) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setIsOpen(false);\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [isOpen, options.mode]);\n\n // ---- send / stop / clear ------------------------------------------------\n const handleSend = useCallback(\n async (text: string, attachments: LocalAttachment[]) => {\n log.info(\"send\", { textLen: text.length, attachments: attachments.length, streaming });\n if (streaming) return;\n bus.emit(\"send\", { text, attachmentCount: attachments.length });\n if (suggestions.length > 0) setSuggestions([]);\n\n const uploaded = await uploadAttachments(transport, attachments, (err) => {\n log.warn(\"upload failed\", { err });\n bus.emit(\"error\", err);\n options.onError?.(err);\n });\n\n // The send click is itself a user gesture \u2014 unlock the AudioContext\n // here so the very first inbound reply can ping.\n feedback.unlock();\n feedback.play(\"messageSent\");\n\n const userMsg = makeUserMessage(text, uploaded);\n const assistantMsg = makeAssistantMessage();\n messagesSig.value = [...messagesSig.value, userMsg, assistantMsg];\n reducer.attach(assistantMsg);\n emitMessage(bus, options, \"user\", text);\n const activeSessionId = sessionIdSig.value ?? uuid7();\n if (!sessionIdSig.value) {\n sessionIdSig.value = activeSessionId;\n persistence.saveSessionId(activeSessionId);\n }\n\n const body = buildSendMessageRequest({\n messages: messagesSig.value.slice(0, -1).map(fromReactive),\n sessionId: activeSessionId,\n // textModel + imageModel are backend-only \u2014 selected per\n // deployment in the dashboard, never passed up by the widget.\n tools: options.features.tools,\n responseMode: options.responseMode,\n // Identity + preferences ride on every message \u2014 server may need\n // them to route, persist, or reconcile in-flight.\n visitorId,\n userPrefs: persistence.loadUserPrefs(),\n });\n\n setStreaming(true);\n const handle = transport.sendMessage(body);\n setActiveCancel(() => handle.cancel);\n\n try {\n // Drain the reducer. We deliberately do NOT adopt `chunk.messageId`\n // from the backend's `start` frame \u2014 our client-minted `assistantMsg.id`\n // is already globally unique and serves as the React key for the\n // bubble. Swapping the id mid-stream would force Preact to remount\n // the bubble on the next list re-render (on `finish`), which would\n // tear down + rebuild the streaming-markdown DOM \u2192 a visible flicker\n // the moment the assistant finishes talking.\n for await (const evt of handle.iter) {\n reducer.apply(evt.chunk);\n // Backend can close a conversation mid-stream (e.g. message cap).\n // Explicit `false` on the finish chunk flips us into read-only.\n if (evt.chunk.type === \"finish\" && evt.chunk.canContinue === false) {\n setCanSend(false);\n }\n }\n assistantMsg.status = \"complete\";\n feedback.play(\"messageReceived\");\n emitMessage(bus, options, \"assistant\", assistantText(assistantMsg));\n } catch (error) {\n assistantMsg.status = \"error\";\n // Friendly, status-aware text \u2014 never leak raw \"server responded 429\u2026\".\n assistantMsg.errorText = errorMessageFor(error, options.strings);\n feedback.play(\"error\");\n bus.emit(\"error\", error);\n options.onError?.(error);\n } finally {\n reducer.detach();\n setStreaming(false);\n setActiveCancel(null);\n // Trigger a re-render so the bubble's terminal status (complete /\n // error) is reflected. No persistence call \u2014 backend is the source\n // of truth for messages now.\n messagesSig.value = [...messagesSig.value];\n }\n },\n [\n streaming,\n transport,\n reducer,\n messagesSig,\n sessionIdSig,\n options,\n bus,\n suggestions.length,\n feedback,\n persistence,\n visitorId,\n ],\n );\n\n const handleStop = useCallback(() => {\n log.info(\"stop\");\n bus.emit(\"stop\", undefined);\n activeCancel?.();\n }, [activeCancel, bus]);\n\n const handleSoundToggle = useCallback(() => {\n feedback.toggleMuted();\n const muted = feedback.mutedSig.value;\n log.info(\"soundToggle\", { muted });\n bus.emit(\"soundToggle\", { muted });\n }, [feedback, bus]);\n\n const handleClose = useCallback(() => {\n // `resolveCloseAction` decides what X means in this context:\n // exit-fullscreen for inline+fullscreen (the only way to \"close\" an\n // in-flow widget is to leave fullscreen back into its container),\n // a real close for everything else.\n if (resolveCloseAction(options.mode, panelSize) === \"exit-fullscreen\") {\n log.info(\"close \u2192 exit-fullscreen\", { mode: options.mode });\n setPanelSize(\"normal\");\n bus.emit(\"fullscreen\", false);\n return;\n }\n log.info(\"close \u2192 panel closed\", { mode: options.mode });\n setIsOpen(false);\n persistence.savePanelOpen(false);\n // `panelSize` is intentionally preserved \u2014 the user's last layout\n // (expanded / fullscreen / normal) is restored on reopen. The launcher\n // still shows while closed because `computeHostMode` returns \"closed\"\n // before it ever looks at panelSize.\n bus.emit(\"close\", undefined);\n options.onClose?.();\n }, [bus, options, persistence, panelSize]);\n\n // Recompute the unread badge from the server's per-session `unreadCount`.\n // No-op when there's no chat tab (no badge to show). Fire-and-forget.\n const refreshUnread = useCallback(() => {\n if (!options.modules.list.some((m) => m.layout === \"chat\")) return;\n transport\n .listSessions({ limit: 50 })\n .then((res) => {\n unreadCountSig.value = res.sessions.reduce((sum, c) => sum + (c.unreadCount ?? 0), 0);\n })\n .catch((err) => log.debug(\"refreshUnread failed (non-fatal)\", { err }));\n }, [transport, options.modules, unreadCountSig]);\n\n // Seed the badge once the start-session resolves \u2014 covers always-open modes\n // (inline / standalone / page) where `handleOpen` never fires.\n const unreadSeeded = useRef(false);\n useEffect(() => {\n if (!sessionReady || unreadSeeded.current) return;\n unreadSeeded.current = true;\n refreshUnread();\n }, [sessionReady, refreshUnread]);\n\n const handleOpen = useCallback(() => {\n log.info(\"open\", { mode: options.mode });\n // Open click is a user gesture \u2014 take the chance to unlock audio so the\n // first reply after open can ping without needing a send.\n feedback.unlock();\n // Cross-fade: keep the FAB rendered for one animation tick (with a\n // `leaving` flag \u2192 CSS exit animation) while the panel slides in.\n // After the same duration the FAB unmounts cleanly.\n setLauncherLeaving(true);\n setIsOpen(true);\n persistence.savePanelOpen(true);\n // Engaging with the widget at all dismisses any non-persistent\n // callout \u2014 no need to keep telling the user \"click here\" once\n // they've clicked. Callouts with `persistent: true` are immune;\n // they re-appear on every visit by design.\n if (!calloutDismissed && !calloutPersistent) dismissCallout();\n setTimeout(() => setLauncherLeaving(false), LAUNCHER_EXIT_MS);\n // Reopening is the moment to pick up replies that arrived while closed.\n refreshUnread();\n bus.emit(\"open\", undefined);\n options.onOpen?.();\n }, [bus, options, feedback, persistence, calloutDismissed, calloutPersistent, dismissCallout, refreshUnread]);\n\n const handleClear = useCallback(() => {\n log.info(\"clear \u2192 new conversation\", { wasStreaming: streaming });\n bus.emit(\"clear\", undefined);\n if (streaming) activeCancel?.();\n welcomeAbortRef.current?.abort();\n messagesSig.value = [];\n sessionIdSig.value = undefined;\n persistence.clearSession();\n // A brand-new conversation is always sendable.\n setCanSend(true);\n // Re-connect to the backend with `newConversation: true`. This:\n // - Lets the backend mint + persist the new chat upfront (so the\n // `userPrefs`/history/etc. layer sees it immediately, not on\n // the next message send).\n // - Returns a fresh agent intro / welcome / suggestions that the\n // UI renders right away \u2014 without this the panel sits empty until\n // the user types the first message.\n // - Re-applies any server-side per-visitor settings (locale, mute,\n // etc.) that may have changed since mount.\n void runStartSession({ newConversation: true });\n }, [streaming, activeCancel, messagesSig, sessionIdSig, persistence, runStartSession, bus]);\n\n /**\n * Page-mode \"New conversation\" entry-point. Shares the implementation\n * with the in-panel clear button \u2014 same backend round-trip, same local\n * reset \u2014 so there's only one path that mints a fresh session. Also\n * forces the panel back to the chat view in case some other surface\n * had toggled it (page mode mostly doesn't surface history-overlay,\n * but keep the invariant cheap).\n */\n const handleNewChat = useCallback(() => {\n handleClear();\n setView(\"chat\");\n // New chat always lands in a fresh chat-tab conversation, whatever surface\n // triggered it (panel header, page sidebar, \"send us a message\"). No-op when\n // chat is the only/active surface; switches the tab when a multi-tab\n // messenger had Home/Help/News focused.\n const chatId = options.modules.list.find((m) => m.layout === \"chat\")?.id;\n if (chatId) homeNav.switchTab(chatId);\n }, [handleClear, homeNav, options]);\n\n const handleExpand = useCallback(() => {\n const nextSize = panelSize === \"expanded\" ? \"normal\" : \"expanded\";\n log.info(\"expand\", { from: panelSize, expanded: nextSize === \"expanded\" });\n setPanelSize(nextSize);\n persistence.savePanelSize(nextSize); // remembered per-device across refresh\n bus.emit(\"expand\", nextSize === \"expanded\");\n }, [bus, panelSize, persistence]);\n\n const handleFullscreen = useCallback(() => {\n const nextSize = panelSize === \"fullscreen\" ? \"normal\" : \"fullscreen\";\n log.info(\"fullscreen\", { from: panelSize, fullscreen: nextSize === \"fullscreen\" });\n setPanelSize(nextSize);\n persistence.savePanelSize(nextSize);\n bus.emit(\"fullscreen\", nextSize === \"fullscreen\");\n }, [bus, panelSize, persistence]);\n\n const handlePopOut = useCallback(() => {\n if (!options.popOutUrl) return;\n const url = new URL(options.popOutUrl);\n if (sessionIdSig.value) url.searchParams.set(\"chat\", sessionIdSig.value);\n // Carry the widgetId across so the standalone tab reads the same\n // storage namespace (otherwise a non-default widget pops out into\n // an empty thread).\n if (options.widgetId !== \"default\") url.searchParams.set(\"widgetId\", options.widgetId);\n log.info(\"popOut\", { url: url.toString() });\n window.open(url.toString(), \"_blank\", \"noopener,noreferrer\");\n bus.emit(\"popOut\", undefined);\n }, [bus, sessionIdSig, options.popOutUrl, options.widgetId]);\n\n /** Flip between the active chat view and the history list. */\n const handleToggleHistory = useCallback(() => {\n setView((v) => {\n const next = v === \"history\" ? \"chat\" : \"history\";\n log.info(\"toggleHistory\", { view: next });\n bus.emit(\"toggleHistory\", { view: next });\n return next;\n });\n }, [bus]);\n\n /**\n * User picked a locale from the language switcher. Persist it locally\n * (so the next reload renders in their language even before the network\n * resolves) and push to the backend so other devices / future visits\n * carrying the same `visitorId` see the same choice.\n */\n const handleLocaleChange = useCallback(\n (locale: string) => {\n log.info(\"localeChange\", { locale });\n setActiveLocale(locale);\n patchAndSync({ locale });\n bus.emit(\"localeChange\", locale);\n },\n [patchAndSync, bus],\n );\n\n /**\n * User picked a theme from the theme switcher. Write through to the\n * host's `data-theme` attribute so the CSS flip is instant (before\n * React re-renders with the new `themeMode` option), persist locally,\n * and sync to the backend alongside locale + sound preferences.\n */\n const handleThemeChange = useCallback(\n (mode: ThemePreference) => {\n log.info(\"themeChange\", { mode });\n setActiveThemeMode(mode);\n hostElement.dataset.theme = mode;\n patchAndSync({ themeMode: mode });\n bus.emit(\"themeChange\", mode);\n },\n [hostElement, patchAndSync, bus],\n );\n\n /** Page-mode sidebar toggle \u2014 persist so refresh keeps the same layout. */\n const handleToggleSidebarCollapsed = useCallback(() => {\n setSidebarCollapsed((prev) => {\n const next = !prev;\n log.info(\"sidebarToggle\", { collapsed: next });\n persistence.saveSidebarCollapsed(next);\n bus.emit(\"sidebarToggle\", { collapsed: next });\n return next;\n });\n }, [persistence, bus]);\n\n /** User dragged the resize grip \u2014 persist + push the resolved size. */\n const handleWidgetSizeChange = useCallback(\n (size: WidgetSize) => {\n log.info(\"resize\", size);\n patchAndSync({ widgetSize: size });\n bus.emit(\"resize\", size);\n },\n [patchAndSync, bus],\n );\n\n /**\n * Tap a row in the history pane: fetch the full chat from the backend,\n * swap our `messagesSig` + `sessionIdSig` into it, flip view back to chat,\n * and set `canSend` based on the backend's `canContinue` flag.\n */\n const handleSelectHistoryChat = useCallback(\n async (targetChatId: string) => {\n log.info(\"selectChat\", { sessionId: targetChatId });\n bus.emit(\"selectChat\", { sessionId: targetChatId });\n try {\n const res = await transport.loadSession(targetChatId);\n const hydrated = res.messages.map(fromWireMessage);\n messagesSig.value = hydrated;\n sessionIdSig.value = res.sessionId;\n persistence.saveSessionId(res.sessionId);\n if (res.agent) setAgent(res.agent);\n setCanSend(res.canContinue);\n // Same contract as runStartSession's resume path: loadSession owns the\n // suggestion set for existing sessions. Absent/null clears whatever\n // connect seeded (typically new-chat chips that are stale here).\n setSuggestions(res.suggestions ?? []);\n setView(\"chat\");\n // Opening a thread reads it: tell the server, then refresh the badge.\n await transport.markRead(targetChatId);\n refreshUnread();\n } catch (err) {\n bus.emit(\"error\", err);\n options.onError?.(err);\n }\n },\n [transport, messagesSig, sessionIdSig, bus, options, persistence, refreshUnread],\n );\n\n // ---- bridge programmatic CustomEvents \u2192 handlers -------------------------\n useEffect(() => {\n const unsub = bindHostCommands(hostElement, {\n open: handleOpen,\n close: handleClose,\n expand: handleExpand,\n fullscreen: handleFullscreen,\n popout: handlePopOut,\n });\n return unsub;\n }, [hostElement, handleOpen, handleClose, handleExpand, handleFullscreen, handlePopOut]);\n\n // ---- render --------------------------------------------------------------\n // Apply user-driven runtime overrides (language picker, theme picker)\n // on top of the resolved baseline. Identity-preserving when neither\n // override differs from the resolved value \u2014 common case.\n const effectiveOptions = useMemo<ResolvedOptions>(\n () => applyOptionOverrides(options, activeLocale, activeThemeMode),\n [options, activeLocale, activeThemeMode],\n );\n\n const panelProps = {\n options: effectiveOptions,\n transport,\n feedback,\n bus,\n view,\n canSend,\n isStreaming: streaming,\n loadingMessages,\n messagesSig,\n agent,\n suggestions,\n initialWidgetSize: initialSettings.widgetSize ?? null,\n visitorId,\n onClose: handleClose,\n onClear: handleClear,\n onExpand: handleExpand,\n onFullscreen: handleFullscreen,\n onPopOut: handlePopOut,\n onSoundToggle: handleSoundToggle,\n onToggleHistory: handleToggleHistory,\n onSelectHistoryChat: handleSelectHistoryChat,\n onNewChat: handleNewChat,\n onLocaleChange: handleLocaleChange,\n onThemeChange: handleThemeChange,\n onWidgetSizeChange: handleWidgetSizeChange,\n onSend: handleSend,\n onStop: handleStop,\n onSuggestion: (s: SessionSuggestion) => {\n const text = s.text ?? s.label;\n log.info(\"suggestion\", { text });\n bus.emit(\"suggestion\", { text });\n void handleSend(text, []);\n },\n };\n\n // Messenger-home: open a past conversation (load it + switch to the\n // Messages tab, which is the chat), or start a fresh one. Both reuse the\n // existing chat plumbing \u2014 no separate \"thread\" surface.\n const onSelectSession = (sessionId: string) => {\n void handleSelectHistoryChat(sessionId);\n const chatId = chatTabId();\n if (chatId) homeNav.switchTab(chatId);\n };\n\n // First-paint gate. Render nothing until the initial start-session resolves\n // so server-pushed overrides (e.g. `mode: \"modal\"`) are applied before\n // the user sees anything. On error the flag is still flipped so the\n // widget renders with local defaults rather than staying invisible.\n if (!sessionReady) return null;\n\n // The single open surface, shared by EVERY mode (floating / drawer / modal /\n // inline / standalone / page): 0 enabled modules \u2192 empty state; only the\n // `messages` module \u2192 the plain chat panel; otherwise the tabbed messenger\n // home. Page mode wraps this in its sidebar; the rest render it directly.\n const enabledModules = effectiveOptions.modules.list;\n const renderSurface = (size: PanelSize) => {\n // Non-closeable modes (standalone / page, and inline below fullscreen) have\n // nothing to close back to \u2014 drop the X from the messenger chrome. The chat\n // Panel header self-gates the close action via `isActionVisible`.\n const closeable = isActionVisible(\"close\", effectiveOptions.mode, size);\n if (enabledModules.length === 0) {\n return <ModulesEmpty strings={effectiveOptions.strings} onClose={closeable ? handleClose : undefined} />;\n }\n if (enabledModules.length === 1 && enabledModules[0]?.layout === \"chat\") {\n return <Panel {...panelProps} panelSize={size} />;\n }\n return (\n <MessengerHome\n panelProps={{ ...panelProps, panelSize: size, onClose: closeable ? handleClose : undefined }}\n enabledModules={enabledModules}\n nav={homeNav}\n unreadCount={unreadCountSig.value}\n onSelectSession={onSelectSession}\n onStartNewSession={handleNewChat}\n onSetPanelSize={setPanelSize}\n />\n );\n };\n\n if (options.mode === \"page\") {\n // Page mode is always full-viewport: the sidebar owns conversation history,\n // and the main area renders the SAME messenger module surface as every\n // other mode (pinned to \"fullscreen\" so size-conditional rendering agrees).\n // Sidebar's onSelect signature differs from the in-panel history\n // pane (it forwards a SessionSummary instead of a sessionId string). Bridge\n // it back to the same handler so both surfaces share one resume path.\n const onSelectChatFromSidebar = (chat: SessionSummary) => {\n void handleSelectHistoryChat(chat.sessionId);\n };\n // The sidebar's conversation list + new-chat ARE the chat, so they only\n // make sense when the `messages` module is enabled. With messages off\n // (e.g. a help-center page of home/help/news), the sidebar shows just\n // site/nav/cards and the main area renders the tabbed module surface.\n const messagesEnabled = enabledModules.some((m) => m.layout === \"chat\");\n return (\n <div class={`${p}-anchor`}>\n <PageShell\n site={parsedSite}\n blocks={parsedBlocks}\n transport={transport}\n strings={effectiveOptions.strings}\n visitorId={visitorId}\n showConversations={messagesEnabled}\n onSelectChat={onSelectChatFromSidebar}\n onNewChat={handleNewChat}\n sidebarCollapsed={sidebarCollapsed}\n onToggleSidebarCollapsed={handleToggleSidebarCollapsed}\n >\n {renderSurface(\"fullscreen\")}\n </PageShell>\n </div>\n );\n }\n\n if (isInlineLike) {\n // Standalone mode is always fullscreen by design (dedicated tab).\n // Inline mode respects the panel-size state so the fullscreen button\n // can escape the container and take over the viewport. Expand in\n // inline is collapsed to fullscreen semantics in `computeHostMode`\n // (no sensible mid-tier when the panel already fills its container).\n const inlineSize: PanelSize = options.mode === \"standalone\" ? \"fullscreen\" : panelSize;\n return <div class={`${p}-anchor`}>{renderSurface(inlineSize)}</div>;\n }\n\n // Floating (floating / iframe). When the panel is open it takes the anchor\n // slot entirely \u2014 the FAB is hidden. Panel header carries its own close.\n // During the open transition both render briefly (`launcherLeaving`) so\n // the FAB can fade out while the panel fades in, instead of a hard swap.\n // Drawer mode renders the launcher too, but as a vertical edge tab\n // (`drawerEdgeTab`) docked to the screen border. Modal mode never renders\n // a launcher \u2014 the host page brings its own trigger and calls `open()`.\n // Either mode can opt out with `launcher.hidden: true`.\n const drawerEdgeTab = options.mode === \"drawer\";\n const triggerOwnedByPage = options.mode === \"modal\";\n const launcherVisible = !triggerOwnedByPage && !options.launcher.hidden && (!isOpen || launcherLeaving);\n // Callout: only when the launcher is visible (the panel covers it\n // otherwise), configured (`callout` resolved to non-null), and the\n // user hasn't dismissed (or `persistent: true` ignores dismissal).\n // Suppressed for the drawer edge tab \u2014 a floating bubble next to a\n // vertical edge handle doesn't read well. Combining the gate with the\n // resolved value into a single nullable lets the JSX render with one\n // truthy check + narrowed type.\n const calloutToRender =\n launcherVisible && !launcherLeaving && !calloutDismissed && !drawerEdgeTab\n ? effectiveOptions.launcher.callout\n : null;\n // Open surface (floating / drawer / modal) \u2014 the same module surface used by\n // every other mode, gated by `isOpen` and laid over the launcher.\n return (\n <div class={`${p}-anchor`} data-launcher-size={effectiveOptions.launcher.size}>\n {isOpen ? renderSurface(panelSize) : null}\n {launcherVisible ? (\n <Launcher\n onToggle={handleOpen}\n launcher={effectiveOptions.launcher}\n strings={effectiveOptions.strings}\n leaving={launcherLeaving}\n edgeTab={drawerEdgeTab}\n />\n ) : null}\n {calloutToRender ? (\n <LauncherCallout\n callout={calloutToRender}\n launcherPosition={effectiveOptions.position}\n closeLabel={effectiveOptions.strings.close}\n onDismiss={dismissCallout}\n />\n ) : null}\n </div>\n );\n}\n\n// ---- internals ------------------------------------------------------------\n\n/**\n * Auto-fill `pageContext.url` from the host page's `location.href` when the\n * host didn't set it, so the agent always knows the current page. The rest is\n * already sanitised by `resolveOptions`. Returns `undefined` when there's no\n * context at all (and no URL to capture).\n */\nfunction toWirePageContext(pageContext: ResolvedOptions[\"pageContext\"]): PageContext | undefined {\n const url = pageContext?.url ?? (typeof location !== \"undefined\" ? location.href : undefined);\n if (!pageContext && !url) return undefined;\n return { ...pageContext, ...(url ? { url } : {}) };\n}\n\n/**\n * Duration the FAB stays mounted with the `leaving` class after the user\n * clicks it. Must match the `__P__-fab-out` keyframe + `--__P__-dur-quick`\n * in panel.css. Keep them in lockstep \u2014 too short and the exit animation\n * gets cut; too long and the FAB visibly hangs over the panel.\n */\nconst LAUNCHER_EXIT_MS = 120;\n\n/** Upload each attachment; capture its `{id, url}`. Errors are reported but don't block. */\nasync function uploadAttachments(\n transport: AgentTransport,\n attachments: LocalAttachment[],\n onError: (err: unknown) => void,\n): Promise<Message[\"attachments\"]> {\n const out: NonNullable<Message[\"attachments\"]> = [];\n for (const a of attachments) {\n try {\n const { id, url } = await transport.uploadFile(a.file);\n out.push({ id, name: a.name, mediaType: a.mediaType, size: a.size, url });\n } catch (err) {\n onError(err);\n }\n }\n return out;\n}\n\n/** Emit both the bus event and the `onMessage` callback in one shot. */\nfunction emitMessage(bus: EventBus, options: ResolvedOptions, role: string, text: string): void {\n const payload = { role, text };\n bus.emit(\"message\", payload);\n options.onMessage?.(payload);\n}\n", "/**\n * Render-error boundary. Catches a throw anywhere in the widget's Preact tree\n * so a render crash degrades to a quiet fallback instead of white-screening the\n * host page \u2014 and routes the error to the widget's observability funnel (the\n * `error` event \u2192 hooks / `on(\"error\")` / `options.onError`, plus the logger).\n *\n * Error boundaries only catch *render* errors. Async errors (event handlers,\n * promises, the stream) are caught where they happen and reported through the\n * same `error` event \u2014 see `app.tsx` `handleSend`.\n */\n\nimport { Component, type ComponentChildren } from \"preact\";\nimport { logger } from \"@/core/logger\";\n\nconst log = logger.scope(\"error-boundary\");\n\ninterface Props {\n /** Report the caught error (\u2192 `error` event + `options.onError`). */\n onError: (error: unknown, componentStack?: string) => void;\n /** Shown after a crash. Defaults to nothing \u2014 the widget simply unmounts. */\n fallback?: ComponentChildren;\n children?: ComponentChildren;\n}\n\nexport class ErrorBoundary extends Component<Props, { crashed: boolean }> {\n override state = { crashed: false };\n\n override componentDidCatch(error: unknown, info: { componentStack?: string }): void {\n log.error(\"widget render crashed\", { error, componentStack: info?.componentStack });\n this.setState({ crashed: true });\n this.props.onError(error, info?.componentStack);\n }\n\n override render() {\n return this.state.crashed ? this.props.fallback : this.props.children;\n }\n}\n", "/**\n * Iframe-wrapped mount.\n *\n * Hosts the widget inside a same-origin iframe for strict CSS + JS isolation\n * beyond shadow DOM. Orthogonal to the `mode` option \u2014 any layout can opt\n * in via `mount({ mode, iframe: true })`:\n *\n * - **Floating** (`mode: \"floating\"`): iframe is `position: fixed` in the\n * host page, precisely sized to the current state (`data-mode` on the\n * inner host) so pointer events fall through to the page everywhere else.\n * `closed` = 88\u00D788 (FAB), `open` = normal panel dims, `expanded` = larger,\n * `fullscreen` = 100vw \u00D7 100vh.\n *\n * - **Inline** (`mode: \"inline\"`): iframe is `display: block` in the target\n * container, `width/height: 100%` \u2014 fills its parent like any other inline\n * element. Escapes to `position: fixed; inset: 0` when the user clicks\n * the fullscreen header button (data-mode flips to \"fullscreen\").\n *\n * - **Standalone** (`mode: \"standalone\"`): iframe is the full viewport \u2014\n * same as floating + fullscreen, never resizes.\n *\n * A MutationObserver watches `data-mode` on the inner host (written by\n * App's render-mode effect) and re-sizes the iframe on every transition.\n */\n\nimport { BRAND } from \"@/core/brand\";\nimport type { Mode } from \"@/core/config\";\nimport { logger } from \"@/core/logger\";\nimport { createShadowMount, type MountOptions, type ShadowMountResult } from \"./mount\";\n\nconst log = logger.scope(\"iframe\");\n\nconst IFRAME_SRCDOC =\n '<!doctype html><html><head><meta charset=\"utf-8\"><style>html,body{margin:0;padding:0;background:transparent;overflow:hidden}</style></head><body></body></html>';\n\n// Internal return shape \u2014 extends `ShadowMountResult` with the outer\n// iframe element so `index.ts` can reach it for cleanup. Not exported:\n// no caller outside this module consumes the type.\ninterface IframeMountResult extends ShadowMountResult {\n iframeElement: HTMLIFrameElement;\n}\n\nexport function createIframeMount(\n opts: Omit<MountOptions, \"target\">,\n position: string,\n initialMode = \"closed\",\n layoutMode: Mode = \"floating\",\n target: HTMLElement | null = null,\n): IframeMountResult {\n log.debug(\"createIframeMount\", { position, initialMode, layoutMode, hasTarget: !!target });\n const iframe = document.createElement(\"iframe\");\n iframe.title = `${BRAND.name} chat`;\n iframe.setAttribute(\"allow\", \"microphone; clipboard-write\");\n // Size the iframe at its *eventual* first-render dimensions, not a\n // generic \"closed\" fallback. A user reloading a page with the widget\n // previously open would otherwise see a 88\u00D788 iframe flash before\n // App's first effect corrects the size to the full panel.\n sizeIframe(iframe, position, initialMode, layoutMode);\n // Inline iframes go into the host-page target container (flow layout);\n // floating + standalone attach to `document.body` (position: fixed).\n const parent = layoutMode === \"inline\" && target ? target : document.body;\n parent.appendChild(iframe);\n\n const doc = iframe.contentDocument;\n if (!doc) throw new Error(\"iframe contentDocument unavailable (cross-origin?)\");\n doc.open();\n doc.write(IFRAME_SRCDOC);\n doc.close();\n\n // `createShadowMount` attaches the widget's stylesheet to the shadow\n // root using the host's `ownerDocument` \u2014 that's the iframe's document\n // here, so the sheet is constructed against the right realm. We\n // deliberately don't inject widget CSS into the iframe's `<head>`:\n // shadow DOM doesn't cascade from host-document styles, so a duplicate\n // up there would just be dead weight.\n const mount = createShadowMount({ ...opts, target: doc.body });\n\n const mo = new MutationObserver(() => {\n sizeIframe(iframe, position, mount.hostElement.dataset.mode ?? \"closed\", layoutMode);\n });\n mo.observe(mount.hostElement, { attributes: true, attributeFilter: [\"data-mode\"] });\n\n return {\n ...mount,\n iframeElement: iframe,\n destroy() {\n mo.disconnect();\n mount.destroy();\n iframe.remove();\n },\n };\n}\n\nconst BASE_CSS = \"border:0;background:transparent;z-index:2147483647;pointer-events:auto;\";\n\n/** Dimensions per `data-mode` in floating layouts. Fullscreen is special-cased. */\nconst FLOATING_SIZE_BY_MODE: Record<string, { w: string; h: string }> = {\n closed: { w: \"88px\", h: \"88px\" },\n expanded: { w: \"min(calc(100vw - 32px), 680px)\", h: \"min(calc(100vh - 32px), 860px)\" },\n // `open` (normal) \u2014 also the fallback for any other non-fullscreen state.\n open: { w: \"min(calc(100vw - 32px), 440px)\", h: \"min(calc(100vh - 32px), 700px)\" },\n};\n\n/**\n * Apply position + size inline CSS to the outer iframe.\n *\n * - Fullscreen (any layout) \u2192 `position: fixed; inset: 0; 100vw \u00D7 100vh`.\n * - Floating (`floating`) \u2192 `position: fixed` in a corner, size per data-mode.\n * - Inline (`inline`) \u2192 `display: block; width/height: 100%`, flows with target.\n * - Standalone \u2192 always fullscreen.\n */\nfunction sizeIframe(iframe: HTMLIFrameElement, position: string, mode: string, layoutMode: Mode): void {\n if (mode === \"fullscreen\" || layoutMode === \"standalone\") {\n iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;\n return;\n }\n if (layoutMode === \"inline\") {\n // In-flow iframe: fills its target container. No corner offsets;\n // the container's own layout (margin/padding/grid cell) positions it.\n iframe.style.cssText = `${BASE_CSS}position:static;display:block;width:100%;height:100%;`;\n return;\n }\n // Floating layout: fixed-position corner dock, size by data-mode.\n const vKey = position.startsWith(\"top-\") ? \"top\" : \"bottom\";\n const hKey = position.endsWith(\"-left\") ? \"left\" : \"right\";\n const { w, h } = FLOATING_SIZE_BY_MODE[mode] ?? FLOATING_SIZE_BY_MODE.open!;\n iframe.style.cssText = `${BASE_CSS}position:fixed;${vKey}:16px;${hKey}:16px;width:${w};height:${h};`;\n}\n", "/**\n * Tiny typed pub/sub for widget lifecycle events.\n *\n * Insulates UI components from the public callback shape, and gives us room\n * to add listeners (analytics, logging) without touching the UI.\n *\n * All listeners are isolated \u2014 a throw in one never breaks the widget or\n * prevents other listeners from firing.\n */\n\nimport type { ThemePreference } from \"@/core/config\";\nimport type { StartSessionResponse } from \"@/stream/protocol\";\n\n/**\n * Payload shape per event. Keyed by event name; use `void` for events that\n * carry no data.\n *\n * **Every user-visible action fires here.** Subscribe via\n * `chat.on(\"eventName\", fn)` to add analytics, side effects, or audit\n * trails without forking the widget. The same names also stream to the\n * structured logger at `info` level when `debug` is enabled.\n */\nexport interface EventMap {\n // \u2500\u2500 Panel lifecycle \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Panel opened (floating / modal / drawer). */\n open: void;\n /** Panel closed. */\n close: void;\n /** Expanded mode toggled (1.5\u00D7 size). Payload is the new state. */\n expand: boolean;\n /** Fullscreen mode toggled. Payload is the new state. */\n fullscreen: boolean;\n /** Widget was popped out into a standalone tab. */\n popOut: void;\n /** End-user dragged the resize grip \u2014 payload is the resolved size. */\n resize: { width: number; height: number };\n\n // \u2500\u2500 Conversation \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** A message was exchanged. Fires for both user and assistant messages. */\n message: { role: string; text: string };\n /** User submitted a message (fires before the request is sent). */\n send: { text: string; attachmentCount: number };\n /** User cancelled an in-flight stream. */\n stop: void;\n /** Clear / new-conversation requested. */\n clear: void;\n /** User picked a chat from the history pane / sidebar. */\n selectChat: { sessionId: string };\n /** User clicked a suggested-message chip. */\n suggestion: { text: string };\n /** History pane toggled. Payload is the resulting view. */\n toggleHistory: { view: \"chat\" | \"history\" };\n\n // \u2500\u2500 Composer / attachments / voice \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 /** Files attached (drag-drop, file picker, paste). */\n attach: { count: number; totalBytes: number };\n /** An attachment was removed before send. */\n removeAttachment: { id: string };\n /** Voice recording started. */\n voiceStart: void;\n /** Voice recording stopped and was sent for transcription. */\n voiceStop: { durationMs: number };\n /** Voice recording cancelled (e.g. user released outside the button). */\n voiceCancel: void;\n\n // \u2500\u2500 Preferences \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** End-user picked a different UI locale. Payload is the new BCP-47 tag. */\n localeChange: string;\n /** End-user picked a different theme mode (`auto` / `light` / `dark`). */\n themeChange: ThemePreference;\n /** End-user toggled the global sound mute. */\n soundToggle: { muted: boolean };\n /** Page-mode sidebar collapsed / expanded. */\n sidebarToggle: { collapsed: boolean };\n /** Pre-engagement launcher callout dismissed. */\n calloutDismiss: void;\n\n // \u2500\u2500 Lifecycle / errors \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** An unhandled error occurred somewhere in the transport or UI. */\n error: unknown;\n /** Connect bootstrap succeeded. Payload is the typed server response. */\n sessionStarted: StartSessionResponse;\n /**\n * Server rebound the `visitorId` \u2014 e.g. the client-minted id\n * conflicted with an existing record, was expired, or otherwise\n * rejected, and the server returned a fresh one in the\n * `/start-session` response. The widget has already adopted the new\n * id and persisted it; this event is informational so host pages\n * can re-link analytics / external systems that pinned the\n * previous value.\n *\n * `reason` is the server's explanation, when provided\n * (`rebind.visitorId` in the start-session response). Common values:\n * `\"conflict\"` / `\"expired\"` / `\"invalid\"`. May be `undefined` if\n * the server didn't include a `rebind` block.\n */\n visitorRebound: { previous: string; current: string; reason?: string };\n /**\n * Server rebound the `sessionId` \u2014 same shape and semantics as\n * `visitorRebound` but for the conversation-thread id. Fires when\n * the server's response carries a different `sessionId` than the\n * client sent.\n */\n sessionRebound: { previous: string; current: string; reason?: string };\n}\n\nexport type EventName = keyof EventMap;\ntype Handler<E extends EventName> = (payload: EventMap[E]) => void;\n\n/**\n * Minimal event bus used by `ChatWidgetInstance`.\n *\n * Memory model: listeners are held in a `Set` keyed by event name. `on()`\n * returns an `off` function; callers must retain it to unsubscribe. A single\n * `clear()` drops all listeners (called on instance `destroy()`).\n */\nexport class EventBus {\n private handlers = new Map<EventName, Set<Handler<EventName>>>();\n\n /**\n * Register a listener for `name`.\n * @returns an `off` function that removes this listener.\n */\n on<E extends EventName>(name: E, fn: Handler<E>): () => void {\n let set = this.handlers.get(name);\n if (!set) {\n set = new Set();\n this.handlers.set(name, set);\n }\n set.add(fn as Handler<EventName>);\n // Look up the set on unsubscribe so post-`clear()` `off()` calls\n // don't mutate a detached set (the invariant the bus advertises:\n // \"safe to call after destroy\").\n return () => this.handlers.get(name)?.delete(fn as Handler<EventName>);\n }\n\n /**\n * Dispatch an event synchronously to all listeners.\n * Listener errors are swallowed \u2014 the widget must not crash on user callbacks.\n */\n emit<E extends EventName>(name: E, payload: EventMap[E]): void {\n const set = this.handlers.get(name);\n if (!set) return;\n for (const fn of set) {\n try {\n (fn as Handler<E>)(payload);\n } catch {\n // Listener errors must never break the widget.\n }\n }\n }\n\n /** Remove every listener. Called by the instance `destroy()`. */\n clear(): void {\n this.handlers.clear();\n }\n}\n", "/**\n * Global lifecycle-hook registry.\n *\n * Per-instance events (`open`, `close`, `message`, \u2026) are already delivered\n * to subscribers via each widget's {@link EventBus}. That works well for\n * callers who hold a direct handle to the instance \u2014 but not for the\n * zero-JS `<script data-*>` embed path, and not for analytics wiring that\n * wants to hear from *every* widget without knowing their ids in advance.\n *\n * This module plugs that gap. Hooks are registered against a widgetId\n * (exact or `\"*\"`) and an event name (exact or `\"*\"`), and fire with a\n * unified signature \u2014 `(instance, event, data)` \u2014 so a single callback can\n * handle any event on any widget:\n *\n * ```ts\n * HelpAI.hook(\"*\", \"*\", (inst, event, data) => analytics.track(event, { widget: inst.widgetId }));\n * HelpAI.hook(\"support\", \"message\", (inst, _event, data) => log(`[${inst.widgetId}] ${data.role}: ${data.text}`));\n * ```\n *\n * Registration order is irrelevant \u2014 hooks may be added before the bundle\n * loads (via the pre-boot queue) or after any number of instances are\n * already mounted. Live instances are subscribed retroactively.\n *\n * Listeners are isolated: a throw in one never breaks the widget or\n * prevents other listeners from firing. Same contract as the per-instance\n * bus.\n */\n\nimport type { ChatWidgetInstance } from \"..\";\nimport { EventBus, type EventMap, type EventName } from \"./events\";\nimport { logger } from \"./logger\";\n\nconst log = logger.scope(\"hooks\");\n\n/** Wildcard that matches every widget id / every event name. */\nexport const HOOK_WILDCARD = \"*\";\n\n/**\n * Unified lifecycle callback signature. `data` is `unknown` \u2014 narrow it\n * via the `event` discriminator using {@link EventMap} if needed:\n *\n * ```ts\n * HelpAI.hook(\"*\", \"*\", (inst, event, data) => {\n * if (event === \"message\") {\n * const msg = data as EventMap[\"message\"]; // { role, text }\n * // ...\n * }\n * });\n * ```\n */\nexport type HookListener = (instance: ChatWidgetInstance, event: EventName, data: unknown) => void;\n\ninterface HookEntry {\n widgetId: string;\n event: EventName | typeof HOOK_WILDCARD;\n listener: HookListener;\n}\n\n// Module-scope because there is one global registry per page \u2014 the whole\n// point is to wire listeners registered before any particular instance\n// exists. Tests reset via `__resetHooksForTest`.\nconst entries = new Set<HookEntry>();\n\n/**\n * Register a listener that fires for every matching (widgetId, event) pair.\n * Pass `\"*\"` for either to match all.\n *\n * Matching live instances are subscribed immediately \u2014 so listeners added\n * after a widget has already emitted its boot events still receive the\n * next round. Returns an `off` function; call it to unsubscribe.\n */\nexport function registerHook(\n widgetId: string,\n event: EventName | typeof HOOK_WILDCARD,\n listener: HookListener,\n): () => void {\n const entry: HookEntry = { widgetId, event, listener };\n entries.add(entry);\n return () => {\n entries.delete(entry);\n };\n}\n\n/**\n * Called from `mount()` for each event a new instance fires. Walks the\n * entries set and invokes matching hooks with the unified signature.\n *\n * Kept internal: `mount()` forwards every event through `dispatchHook` by\n * wiring one bus subscription per known event name (see `subscribeInstance`).\n */\nexport function dispatchHook(instance: ChatWidgetInstance, event: EventName, data: unknown): void {\n // Iterate a snapshot \u2014 a listener that registers a new hook during\n // dispatch must not re-enter this dispatch and receive its own event.\n const snapshot = [...entries];\n for (const entry of snapshot) {\n if (!matchesId(entry.widgetId, instance.widgetId)) continue;\n if (entry.event !== HOOK_WILDCARD && entry.event !== event) continue;\n try {\n entry.listener(instance, event, data);\n } catch (error) {\n // Listener errors must never break the widget. Same contract as EventBus.\n log.warn(\"hook listener threw\", { event, error });\n }\n }\n}\n\n/**\n * Wire a newly-mounted instance into the global hook bus.\n *\n * Subscribes every known event name on the instance's bus \u2014 each emit\n * delegates to {@link dispatchHook} so registered hooks receive it with\n * the unified `(instance, event, data)` signature. Returns a cleanup\n * function that the instance's `destroy()` must invoke.\n */\nexport function subscribeInstance(instance: ChatWidgetInstance, bus: EventBus): () => void {\n const unsubs: Array<() => void> = [];\n for (const name of EVENT_NAMES) {\n unsubs.push(\n bus.on(name, (payload) => {\n dispatchHook(instance, name, payload);\n }),\n );\n }\n return () => {\n for (const off of unsubs) off();\n };\n}\n\n/**\n * Replay pre-boot hook registrations pushed onto `window.<Brand>Hooks` by\n * the loader snippet before the bundle arrived. Each entry is a\n * `[widgetId, event, listener]` tuple or a rich `{ widgetId, event,\n * listener }` object \u2014 both forms are accepted to match the loader\n * pattern's existing shape.\n */\nexport function drainPreBootQueue(queue: unknown): void {\n if (!Array.isArray(queue)) return;\n for (const entry of queue) {\n const parsed = parsePreBootEntry(entry);\n if (!parsed) continue;\n registerHook(parsed.widgetId, parsed.event, parsed.listener);\n }\n}\n\n/** @internal \u2014 test hook. Clear every registered listener. */\nexport function __resetHooksForTest(): void {\n entries.clear();\n}\n\n/** @internal \u2014 test hook. How many listeners are live right now. */\nexport function __hookCountForTest(): number {\n return entries.size;\n}\n\n/** Exact match OR wildcard match for the widget-id side. */\nfunction matchesId(registered: string, actual: string): boolean {\n return registered === HOOK_WILDCARD || registered === actual;\n}\n\n/**\n * Build-time-enumerable list of every event name so we can wire one\n * bus-subscription per event in `subscribeInstance`. Kept here (not on\n * `EventMap`) because TS types are erased at runtime \u2014 the widget needs\n * a concrete array to iterate.\n *\n * The trailing exhaustiveness check fails compilation if a new event is\n * added to {@link EventMap} but forgotten here \u2014 `satisfies readonly\n * EventName[]` alone permits omissions, which previously silently\n * dropped `visitorRebound` / `sessionRebound` from `*`-hook delivery.\n */\nexport const EVENT_NAMES = [\n // Panel lifecycle\n \"open\",\n \"close\",\n \"expand\",\n \"fullscreen\",\n \"popOut\",\n \"resize\",\n // Conversation\n \"message\",\n \"send\",\n \"stop\",\n \"clear\",\n \"selectChat\",\n \"suggestion\",\n \"toggleHistory\",\n // Composer / attachments / voice\n \"attach\",\n \"removeAttachment\",\n \"voiceStart\",\n \"voiceStop\",\n \"voiceCancel\",\n // Preferences\n \"localeChange\",\n \"themeChange\",\n \"soundToggle\",\n \"sidebarToggle\",\n \"calloutDismiss\",\n // Lifecycle / errors\n \"error\",\n \"sessionStarted\",\n \"visitorRebound\",\n \"sessionRebound\",\n] as const satisfies readonly EventName[];\n\ntype MissingEventNames = Exclude<EventName, (typeof EVENT_NAMES)[number]>;\n// If this fails to typecheck, an `EventMap` member is missing from `EVENT_NAMES` above.\nconst eventNamesExhaustive: MissingEventNames extends never ? true : never = true;\nvoid eventNamesExhaustive;\n\n/** Accept either a `[widgetId, event, listener]` tuple or a rich object. */\nfunction parsePreBootEntry(entry: unknown): HookEntry | null {\n let widgetId: unknown, event: unknown, listener: unknown;\n if (Array.isArray(entry)) {\n [widgetId, event, listener] = entry;\n } else if (entry && typeof entry === \"object\") {\n ({ widgetId, event, listener } = entry as Record<string, unknown>);\n } else {\n return null;\n }\n if (typeof widgetId !== \"string\" || typeof event !== \"string\" || typeof listener !== \"function\") return null;\n return { widgetId, event: event as EventName | typeof HOOK_WILDCARD, listener: listener as HookListener };\n}\n\n// Re-export for convenience \u2014 consumers importing from `core/hooks` get\n// the event-map types without needing a second import.\nexport type { EventMap, EventName };\n", "/**\n * Public ESM entry for `@<brand>/elements`.\n *\n * This module is side-effect free \u2014 importing it does not mount anything or\n * touch the DOM. Call {@link mount} or {@link init} to render the widget.\n *\n * The Web Component (`<{brand}-chat>`) lives in a separate sub-entry\n * (`./web-component`) so tree-shakers can drop the custom-element machinery\n * when it isn't used.\n *\n * @example\n * ```ts\n * import { mount } from '@helpai/elements'; // or '@goai/elements' for go.ai\n *\n * const chat = mount({\n * publicKey: 'agent_pk_...',\n * mode: 'inline',\n * target: document.getElementById('chat'),\n * });\n *\n * chat.on('message', (m) => console.log(m));\n * ```\n *\n * @packageDocumentation\n */\n\nimport { h, render } from \"preact\";\nimport { App } from \"./ui/app\";\nimport { ErrorBoundary } from \"./ui/error-boundary\";\nimport { BRAND } from \"./core/brand\";\nimport {\n type ChatWidgetOptions,\n mergeServerConfig,\n type PageContext,\n type ResolvedOptions,\n resolveOptions,\n type UserContext,\n} from \"./core/config\";\nimport { dispatchHostCommand } from \"./core/host-commands\";\nimport { createPersistence } from \"./core/persistence\";\nimport { getLogLevel, type LogEntry, type LogLevel, logger, setLogLevel, setLogSink } from \"./core/logger\";\nimport {\n applyHostAttributes,\n applyHostPosition,\n applyMode,\n createShadowMount,\n hostPositionForMode,\n} from \"./shadow/mount\";\nimport { createIframeMount } from \"./shadow/iframe-mount\";\nimport { EventBus, type EventMap, type EventName } from \"./core/events\";\nimport {\n HOOK_WILDCARD,\n type HookListener,\n registerHook,\n subscribeInstance as subscribeInstanceHooks,\n} from \"./core/hooks\";\nimport { computeHostMode, currentViewportWidth, type HostMode, resolveInitialPanelState } from \"./ui/host-mode\";\n\nexport type { ChatWidgetOptions, ResolvedOptions } from \"./core/config\";\nexport type { EventMap, EventName } from \"./core/events\";\nexport type { LogEntry, LogLevel } from \"./core/logger\";\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Canonical schema types (runtime values live on \"./schema\" subpath) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// We export ONLY types from the package main. Runtime Zod schema values\n// (e.g. `widgetConfigSchema`, `startSessionResponseSchema`) are exported\n// from the dedicated `./schema` subpath so the IIFE / auto-boot bundle\n// doesn't pay for the ~67KB Zod runtime. Admin / dashboard / 3rd-party\n// tooling that needs runtime validation:\n// `import { widgetConfigSchema } from \"@your-org/web-help-ai-elements/schema\"`.\nexport type {\n Asset,\n BlocksConfig,\n ConnectionConfig,\n ConnectionConfigPartial,\n StartSessionResponse,\n Link,\n ServerConfig,\n SiteConfig,\n WidgetConfig,\n WidgetConfigPartial,\n WidgetSettings,\n WidgetSettingsPartial,\n} from \"./schema\";\n\n/**\n * Handle returned by {@link mount} / {@link init}. All methods are safe to call\n * after `destroy()` \u2014 they become no-ops rather than throwing.\n */\nexport interface ChatWidgetInstance {\n /** Stable identifier \u2014 matches `options.widgetId ?? \"default\"`. */\n readonly widgetId: string;\n /** Open the floating panel (no-op in `inline` / `standalone` mode). */\n open: () => void;\n /** Close the floating panel (no-op in `inline` / `standalone` mode). */\n close: () => void;\n /** Unmount the widget and release all subscriptions. */\n destroy: () => void;\n /** Toggle fullscreen mode. In `standalone` mode this is a no-op. */\n expand: () => void;\n /**\n * Open the widget in a new browser tab at `behavior.popOutUrl`\n * (mount option / start-session-pushable), or the brand's\n * `BRAND.standaloneUrl` build-time default. Carries the active\n * `chat` + `widgetId` as query params. No-ops when the resolved\n * URL is empty.\n */\n popOut: () => void;\n /**\n * Subscribe to a lifecycle event. Returns an `off` function. See\n * {@link EventMap} for the full set + payload shapes \u2014 currently\n * `open | close | message | error | expand | fullscreen | popOut |\n * sessionStarted | localeChange`.\n */\n on: <E extends EventName>(name: E, fn: (payload: EventMap[E]) => void) => () => void;\n /**\n * Merge new options into the running instance without a full remount.\n * Useful for theme toggles or other live config tweaks.\n */\n update: (patch: Partial<ChatWidgetOptions>) => void;\n /**\n * Set / merge the host-asserted end-user context (`id`, `email`, `name`, plus\n * any custom scalar fields). Informational only \u2014 not auth. Merges into the\n * current context, refreshes the Home greeting, and re-handshakes so the\n * agent gets it. Call this after the visitor logs in. Untrusted input is\n * sanitised; bad fields are dropped with a `warn`.\n *\n * @example\n * ```ts\n * getInstance().setUserContext({ id: \"u_1\", name: \"Mohamed\", email: \"mo@acme.com\", plan: \"pro\" });\n * ```\n */\n setUserContext: (userContext: UserContext) => void;\n /** Clear the end-user context (e.g. on logout) and re-start-session anonymously. */\n clearUserContext: () => void;\n /**\n * Set / merge the host-asserted page/visit context (`area`, `url`, `path`,\n * `title`, plus custom scalar fields). Use it to tell the agent which surface\n * the visitor is on (e.g. `area: \"pricing\"`) and update it on SPA navigation.\n * Merges into the current context and re-handshakes.\n *\n * @example\n * ```ts\n * getInstance().setPageContext({ area: \"pricing\", path: \"/pricing/enterprise\" });\n * ```\n */\n setPageContext: (pageContext: PageContext) => void;\n /** Clear the page/visit context and re-start-session. */\n clearPageContext: () => void;\n /**\n * Set the runtime log level. Accepts `\"off\"` / `\"error\"` / `\"warn\"` /\n * `\"info\"` / `\"debug\"` / `\"trace\"`, or `true` (\u2192 `\"debug\"`) /\n * `false` (\u2192 `\"off\"`), or a number 0..5. Persists to localStorage so\n * a page refresh keeps the same verbosity \u2014 useful for asking a\n * customer to reproduce a bug and capture the console transcript.\n *\n * Affects every widget on the page (logger is module-global; setting\n * it from one instance applies everywhere). Equivalent to calling\n * `HelpAI.setDebug(level)` on the brand global.\n */\n setDebug: (level: LogLevel | boolean | number | string) => void;\n /** Current log level. Read-only. */\n readonly debugLevel: () => LogLevel;\n /**\n * Forward every log line (that passes the active level) to an external sink \u2014\n * pipe errors/warnings/debug into Sentry, Datadog, your own logger, etc. Pass\n * `null` to clear. One global sink per bundle (logger is module-global); use\n * `setDebug(\"trace\")` to capture everything, not just warnings + errors.\n */\n onLog: (sink: ((entry: LogEntry) => void) | null) => void;\n}\n\n/**\n * Registry of live widget instances, keyed by `widgetId`. Calling `mount()`\n * with a `widgetId` that's already in the registry destroys the old instance\n * first. Different `widgetId`s coexist on the same page \u2014 each with its own\n * shadow host, its own client-storage namespace (visitorId + active sessionId\n * + cached userPrefs), and its own `FeedbackBus` / `AgentTransport`.\n *\n * The `\"default\"` id is used when no `widgetId` is supplied \u2014 keeps the\n * zero-JS `<script data-public-key>` embed working as before.\n */\nconst instances = new Map<string, ChatWidgetInstance>();\n\n/**\n * Mount a new widget. If an instance with the same `widgetId` already\n * exists, that one is destroyed first (a reload of the same \"slot\").\n *\n * @example Single widget (default):\n * ```ts\n * mount({ publicKey, mode: 'inline', target: document.getElementById('chat') });\n * ```\n * @example Two widgets on one page:\n * ```ts\n * mount({ widgetId: 'support', publicKey: KEY_A, target: supportEl });\n * mount({ widgetId: 'sales', publicKey: KEY_B, target: salesEl });\n * ```\n */\nexport function mount(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance {\n // Set the log level FIRST, before anything else logs. Customer\n // `debug` passes through `parseLevel`: `true` \u2192 \"debug\", a level\n // string (\"trace\" / \"info\" / \u2026) used verbatim, `false` / \"off\"\n // silences. URL query + persisted localStorage already took effect\n // at module load (see `resolveInitialLevel` in `core/logger.ts`);\n // explicit `options.debug` overrides them so a `mount({ debug:\n // \"trace\" })` call wins for this mount even on machines that had\n // a lower level persisted.\n if (opts.debug !== undefined) setLogLevel(opts.debug);\n\n const log = logger.scope(\"mount\");\n log.info(\"mount\", { widgetId: opts.widgetId, publicKey: opts.publicKey, mode: opts.presentation?.mode });\n\n let rawOptions: ChatWidgetOptions = opts;\n let currentOptions = resolveOptions(rawOptions);\n const widgetId = currentOptions.widgetId;\n\n // Replace any prior instance at the same slot.\n const existing = instances.get(widgetId);\n if (existing) {\n log.debug(\"destroy existing instance at slot\", { widgetId });\n existing.destroy();\n }\n\n // Compute the host mode the App's first render will produce *before*\n // we create the iframe / host \u2014 so `createIframeMount` can size the\n // iframe correctly and `applyMode` below sets `data-mode` to a value\n // that matches the first paint, avoiding a visible closed\u2192open\u2192closed\n // flicker on refresh (especially in iframe mode where the iframe itself\n // has to physically resize when data-mode changes).\n const initialHostMode = resolveInitialHostMode(currentOptions);\n\n // The iframe wrapper is orthogonal to the layout mode \u2014 any mode can\n // opt into it for strict JS + CSS isolation. `floating` + iframe is the\n // classic floating-FAB-in-iframe. `inline` + iframe is an in-flow chat\n // block whose JS runs in the iframe's realm (can't have its `window.*`\n // stomped by the host page).\n // Pick the host-element positioning strategy from the resolved mode.\n // See `hostPositionForMode` in `shadow/mount.ts` for the table.\n // Re-applied in the `sessionStarted` handler below so server-pushed mode\n // changes refresh the host's inline style.\n const mountResult = currentOptions.iframe\n ? createIframeMount(\n { position: hostPositionForMode(currentOptions.mode) },\n currentOptions.position,\n initialHostMode,\n currentOptions.mode,\n opts.target ?? null,\n )\n : createShadowMount({\n position: hostPositionForMode(currentOptions.mode),\n target: opts.target ?? null,\n });\n // App's useState resolves activeThemeMode as `cachedSettings.themeMode ??\n // options.themeMode`. Mirror that precedence on every host-attribute\n // write so the user's persisted pick survives both the initial paint\n // AND the `sessionStarted` server-settings merge \u2014 otherwise there's a\n // window between the synchronous `applyHostAttributes` write\n // (`options.themeMode`, e.g. \"auto\") and App's effect (cached pick,\n // e.g. \"light\") where the attribute is wrong. WebKit's effect\n // scheduling exposes that race to Playwright reads.\n const persistence = createPersistence(currentOptions.widgetId, currentOptions.storage);\n const applyResolvedHostAttributes = () => {\n const cachedThemeMode = persistence.loadUserPrefs().themeMode;\n applyHostAttributes(\n mountResult.hostElement,\n cachedThemeMode ? { ...currentOptions, themeMode: cachedThemeMode } : currentOptions,\n );\n };\n applyResolvedHostAttributes();\n applyMode(mountResult.hostElement, initialHostMode);\n mountResult.hostElement.dataset.widgetId = widgetId;\n\n const bus = new EventBus();\n\n // Funnel for any caught error \u2014 render crashes (ErrorBoundary), the stream,\n // and async handlers all converge here: the `error` event (\u2192 hooks /\n // `on(\"error\")`) plus the per-instance `onError` callback.\n const reportError = (error: unknown) => {\n bus.emit(\"error\", error);\n currentOptions.onError?.(error);\n };\n\n const renderApp = () => {\n render(\n h(\n ErrorBoundary,\n { onError: reportError },\n h(App, { bus, hostElement: mountResult.hostElement, options: currentOptions }),\n ),\n mountResult.appRoot,\n );\n };\n renderApp();\n\n // Server-driven settings arrive via `sessionStarted` event from the App's\n // connect() handler. User options still win per-field.\n bus.on(\"sessionStarted\", (response) => {\n if (!response.config) return;\n rawOptions = mergeServerConfig(rawOptions, response.config);\n currentOptions = resolveOptions(rawOptions);\n // Re-apply the host positioning style so a server-pushed mode\n // change (e.g. `floating` \u2192 `standalone`) re-styles the host's\n // inline `position` / `inset` / `width` / `height`. Without this\n // refresh the mount-time choice (typically the resolver default\n // `\"floating\"`) sticks and the widget never claims the viewport.\n applyHostPosition(mountResult.hostElement, hostPositionForMode(currentOptions.mode));\n applyResolvedHostAttributes();\n renderApp();\n });\n\n const host = mountResult.hostElement;\n // `unsubscribeHooks` is set after the instance handle is constructed so\n // hook listeners can reference the fully-populated handle. Declared here\n // to be in scope for `destroy()`.\n let unsubscribeHooks: (() => void) | null = null;\n\n // Merge a patch into the running options and re-render. `debug` is a\n // runtime-only knob applied eagerly. App re-renders with the new options;\n // a host-`user` change additionally triggers a re-start-session inside App.\n const applyUpdate = (patch: Partial<ChatWidgetOptions>) => {\n if (patch.debug !== undefined) setLogLevel(patch.debug);\n rawOptions = { ...rawOptions, ...patch };\n currentOptions = resolveOptions(rawOptions);\n applyResolvedHostAttributes();\n renderApp();\n };\n\n const instance: ChatWidgetInstance = {\n widgetId,\n open: () => dispatchHostCommand(host, \"open\"),\n close: () => dispatchHostCommand(host, \"close\"),\n expand: () => dispatchHostCommand(host, \"expand\"),\n popOut: () => dispatchHostCommand(host, \"popout\"),\n on: (name, fn) => bus.on(name, fn),\n update: (patch) => {\n log.debug(\"update\", { patch });\n applyUpdate(patch);\n },\n // Host-asserted context (NOT our visitor/session \u2014 those are minted +\n // persisted by the widget and untouched here). Merge into the current\n // context; App re-handshakes with the SAME visitor/session.\n setUserContext: (userContext) => {\n log.debug(\"setUserContext\", { keys: Object.keys(userContext) });\n applyUpdate({ userContext: { ...rawOptions.userContext, ...userContext } });\n },\n clearUserContext: () => {\n log.debug(\"clearUserContext\");\n applyUpdate({ userContext: undefined });\n },\n setPageContext: (pageContext) => {\n log.debug(\"setPageContext\", { keys: Object.keys(pageContext) });\n applyUpdate({ pageContext: { ...rawOptions.pageContext, ...pageContext } });\n },\n clearPageContext: () => {\n log.debug(\"clearPageContext\");\n applyUpdate({ pageContext: undefined });\n },\n setDebug: (level) => {\n setLogLevel(level);\n log.info(\"debug level set\", { level: getLogLevel() });\n },\n debugLevel: () => getLogLevel(),\n onLog: (logSink) => setLogSink(logSink),\n destroy: () => {\n log.info(\"destroy\", { widgetId });\n unsubscribeHooks?.();\n bus.clear();\n render(null, mountResult.appRoot);\n mountResult.destroy();\n if (instances.get(widgetId) === instance) instances.delete(widgetId);\n },\n };\n\n // Subscribe *after* building the handle so hook listeners can receive\n // the fully-populated `ChatWidgetInstance` (e.g. `instance.open()` must\n // be callable from within a `sessionStarted` hook).\n unsubscribeHooks = subscribeInstanceHooks(instance, bus);\n\n instances.set(widgetId, instance);\n return instance;\n}\n\n/**\n * Idempotent init per `widgetId`. If an instance exists at that slot, its\n * options are merged (via {@link ChatWidgetInstance.update}). Otherwise it\n * behaves like {@link mount}. Safe to call on every route change in an SPA.\n */\nexport function init(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance {\n const widgetId = opts.widgetId ?? \"default\";\n const existing = instances.get(widgetId);\n if (existing) {\n existing.update(opts);\n return existing;\n }\n return mount(opts);\n}\n\n/**\n * Destroy a specific instance by `widgetId`, or \u2014 when no id is supplied \u2014\n * every live widget on the page.\n */\nexport function destroy(widgetId?: string): void {\n if (widgetId !== undefined) {\n instances.get(widgetId)?.destroy();\n return;\n }\n // Snapshot first \u2014 `destroy()` mutates the registry via its own cleanup.\n const live = [...instances.values()];\n for (const inst of live) inst.destroy();\n}\n\n/**\n * Look up a live instance by `widgetId` (default: `\"default\"`). Returns\n * `null` if nothing is mounted at that slot.\n */\nexport function getInstance(widgetId = \"default\"): ChatWidgetInstance | null {\n return instances.get(widgetId) ?? null;\n}\n\n/** All live widget instances on the page. */\nexport function getAllInstances(): ChatWidgetInstance[] {\n return [...instances.values()];\n}\n\n// ---- Global lifecycle hooks ---------------------------------------------\n\nexport type { HookListener };\nexport { HOOK_WILDCARD };\n\n/**\n * Register a lifecycle hook that fires for every matching\n * `(widgetId, event)` pair across all live widgets.\n *\n * Use `\"*\"` for either argument (or the exported `HOOK_WILDCARD`) to match\n * everything. Listeners receive a unified signature \u2014\n * `(instance, event, data)` \u2014 so one callback can react to any event on\n * any widget without branching by type.\n *\n * @example\n * ```ts\n * // Audit every message exchanged in any widget.\n * HelpAI.hook(\"*\", \"message\", (inst, _event, data) => analytics.track({\n * widget: inst.widgetId,\n * role: (data as EventMap[\"message\"]).role,\n * }));\n *\n * // Fire once per open on the \"support\" widget only.\n * HelpAI.hook(\"support\", \"open\", (inst) => mixpanel.track(\"chat_opened\", { id: inst.widgetId }));\n * ```\n *\n * Returns an `off` function \u2014 call it to unsubscribe. Hooks are held in a\n * module-scope registry, so they persist across `mount()` / `destroy()`\n * cycles until explicitly unregistered.\n */\nexport function hook(widgetId: string, event: string, listener: HookListener): () => void {\n return registerHook(widgetId, event as Parameters<typeof registerHook>[1], listener);\n}\n\n/** Semver of the published package. */\nexport const version = \"0.1.0\";\n\n/** Compile-time brand constants inlined at build time (name, CDN URL, theme). */\nexport const brand = BRAND;\n\n/**\n * Determine what `data-mode` the host should start at \u2014 matching what\n * App's mount-time `useEffect` will produce on its first render. Exposed\n * only for the initial paint path in `mount()` (and the test suite);\n * after that, App owns `data-mode` for the lifetime of the instance.\n */\nexport function resolveInitialHostMode(options: ResolvedOptions): HostMode {\n if (options.mode === \"standalone\") return \"standalone\";\n if (options.mode === \"inline\") return \"inline\";\n // Floating modes (`floating` / `iframe`). `resolveInitialPanelState` is\n // the one source of truth for \"what does the first render look like\" \u2014\n // App.tsx uses it too. If these two paths ever pick different initial\n // states, the iframe physically resizes between mount and App's first\n // effect (the flicker bug).\n const persistence = createPersistence(options.widgetId, options.storage);\n const { panelOpen, panelSize } = resolveInitialPanelState(\n options,\n currentViewportWidth(),\n persistence.loadPanelOpen(),\n persistence.loadPanelSize(),\n );\n return computeHostMode(options.mode, panelSize, panelOpen);\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["slice", "options", "vnodeId", "isValidElement", "rerenderQueue", "prevDebounce", "defer", "depthSort", "_id", "EVENT_DISPATCHED", "EVENT_ATTACHED", "CAPTURE_REGEX", "eventClock", "eventProxy", "eventProxyCapture", "i", "EMPTY_OBJ", "EMPTY_ARR", "IS_NON_DIMENSIONAL", "isArray", "Array", "assign", "obj", "props", "removeNode", "node", "parentNode", "removeChild", "createElement", "type", "children", "key", "ref", "normalizedProps", "arguments", "length", "call", "defaultProps", "createVNode", "original", "vnode", "__k", "__", "__b", "__e", "__c", "constructor", "__v", "__i", "__u", "Fragment", "props", "children", "BaseComponent", "context", "this", "getDomSibling", "vnode", "childIndex", "__", "__i", "sibling", "__k", "length", "__e", "type", "renderComponent", "component", "__P", "__d", "oldVNode", "__v", "oldDom", "commitQueue", "refQueue", "newVNode", "assign", "options", "diff", "__n", "namespaceURI", "__u", "commitRoot", "updateParentDomPointers", "__c", "base", "some", "child", "enqueueRender", "c", "rerenderQueue", "push", "process", "__r", "prevDebounce", "debounceRendering", "defer", "l", "sort", "depthSort", "shift", "diffChildren", "parentDom", "renderResult", "newParentVNode", "oldParentVNode", "globalContext", "namespace", "excessDomChildren", "isHydrating", "i", "childVNode", "newDom", "firstChildDom", "result", "shouldPlace", "oldChildren", "EMPTY_ARR", "newChildrenLength", "constructNewChildrenArray", "EMPTY_OBJ", "ref", "applyRef", "insert", "nextSibling", "skewedIndex", "matchingIndex", "oldChildrenLength", "remainingOldChildren", "skew", "Array", "constructor", "String", "createVNode", "isArray", "__b", "key", "findMatchingIndex", "unmount", "parentVNode", "parentNode", "insertBefore", "nodeType", "findMatchingIndex", "childVNode", "oldChildren", "skewedIndex", "remainingOldChildren", "x", "y", "childIndex", "key", "type", "oldVNode", "matched", "__u", "length", "setStyle", "style", "value", "setProperty", "IS_NON_DIMENSIONAL", "test", "dom", "name", "oldValue", "namespace", "useCapture", "lowerCaseName", "o", "cssText", "replace", "CAPTURE_REGEX", "toLowerCase", "slice", "l", "EVENT_ATTACHED", "eventClock", "addEventListener", "eventProxyCapture", "eventProxy", "removeEventListener", "e", "removeAttribute", "setAttribute", "createEventProxy", "this", "eventHandler", "EVENT_DISPATCHED", "options", "event", "diff", "parentDom", "newVNode", "globalContext", "excessDomChildren", "commitQueue", "oldDom", "isHydrating", "refQueue", "tmp", "c", "isNew", "oldProps", "oldState", "snapshot", "clearProcessingException", "newProps", "isClassComponent", "provider", "componentContext", "renderHook", "count", "renderResult", "i", "newType", "constructor", "__e", "__b", "outer", "props", "prototype", "render", "contextType", "__c", "__", "__E", "BaseComponent", "doRender", "sub", "state", "__n", "__d", "__h", "_sb", "__s", "getDerivedStateFromProps", "assign", "__v", "componentWillMount", "componentDidMount", "push", "componentWillReceiveProps", "shouldComponentUpdate", "__k", "some", "vnode", "EMPTY_ARR", "apply", "componentWillUpdate", "componentDidUpdate", "context", "__P", "__r", "getChildContext", "getSnapshotBeforeUpdate", "Fragment", "cloneNode", "children", "diffChildren", "isArray", "base", "then", "MODE_HYDRATE", "nodeType", "nextSibling", "indexOf", "removeNode", "markAsForce", "diffElementNodes", "diffed", "commitRoot", "root", "applyRef", "cb", "call", "node", "map", "newHtml", "oldHtml", "newChildren", "inputValue", "checked", "EMPTY_OBJ", "localName", "document", "createTextNode", "createElementNS", "is", "__m", "data", "defaultValue", "childNodes", "attributes", "__html", "innerHTML", "content", "getDomSibling", "undefined", "ref", "hasRefUnmount", "current", "unmount", "parentVNode", "skipRemove", "r", "componentWillUnmount", "replaceNode", "documentElement", "createElement", "namespaceURI", "firstChild", "slice", "EMPTY_ARR", "options", "__e", "error", "vnode", "oldVNode", "errorInfo", "component", "ctor", "handled", "__", "__c", "constructor", "getDerivedStateFromError", "setState", "__d", "componentDidCatch", "__E", "e", "vnodeId", "isValidElement", "BaseComponent", "prototype", "update", "callback", "s", "this", "__s", "state", "assign", "props", "__v", "_sb", "push", "enqueueRender", "forceUpdate", "__h", "render", "Fragment", "rerenderQueue", "defer", "Promise", "then", "bind", "resolve", "setTimeout", "depthSort", "a", "b", "__b", "process", "__r", "_id", "Math", "random", "toString", "EVENT_DISPATCHED", "EVENT_ATTACHED", "CAPTURE_REGEX", "eventClock", "eventProxy", "createEventProxy", "eventProxyCapture", "i", "currentIndex", "currentComponent", "previousComponent", "prevRaf", "currentHook", "afterPaintEffects", "options", "_options", "oldBeforeDiff", "__b", "oldBeforeRender", "__r", "oldAfterDiff", "diffed", "oldCommit", "__c", "oldBeforeUnmount", "unmount", "oldRoot", "__", "getHookState", "index", "type", "__h", "hooks", "__H", "length", "push", "useState", "initialState", "useReducer", "invokeOrReturn", "reducer", "init", "hookState", "_reducer", "action", "currentValue", "__N", "nextValue", "setState", "__f", "updateHookState", "p", "s", "c", "stateHooks", "filter", "x", "every", "prevScu", "call", "this", "shouldUpdate", "props", "some", "hookItem", "shouldComponentUpdate", "prevCWU", "componentWillUpdate", "__e", "tmp", "useEffect", "callback", "args", "state", "__s", "argsChanged", "_pendingArgs", "useLayoutEffect", "useRef", "initialValue", "useMemo", "current", "useMemo", "factory", "args", "state", "getHookState", "currentIndex", "argsChanged", "__H", "__", "__h", "useCallback", "callback", "currentHook", "flushAfterPaintEffects", "component", "afterPaintEffects", "shift", "hooks", "__H", "__P", "__h", "some", "invokeCleanup", "invokeEffect", "e", "options", "__e", "__v", "__b", "vnode", "currentComponent", "oldBeforeDiff", "__", "parentDom", "__k", "__m", "oldRoot", "__r", "oldBeforeRender", "currentIndex", "__c", "previousComponent", "hookItem", "__N", "_pendingArgs", "diffed", "oldAfterDiff", "c", "length", "push", "prevRaf", "requestAnimationFrame", "afterNextFrame", "commitQueue", "filter", "cb", "oldCommit", "unmount", "oldBeforeUnmount", "hasErrored", "s", "HAS_RAF", "callback", "raf", "done", "clearTimeout", "timeout", "cancelAnimationFrame", "setTimeout", "hook", "comp", "cleanup", "argsChanged", "oldArgs", "newArgs", "arg", "index", "invokeOrReturn", "f", "BRAND_SYMBOL", "Symbol", "endBatch", "batchDepth", "error", "hasError", "snapshots", "batchSnapshots", "_source", "_value", "_version", "_next", "reconcileBatchSnapshots", "batchedEffect", "effect", "batchIteration", "next", "_nextBatchedEffect", "_flags", "needsToRecompute", "_callback", "err", "batch", "fn", "currentBatchSnapshotVersion", "batchSnapshotVersion", "evalContext", "untracked", "prevContext", "capturedEffects", "globalVersion", "addDependency", "signal", "node", "_node", "_target", "_prevSource", "_sources", "_nextSource", "_prevTarget", "_nextTarget", "_rollbackNode", "_subscribe", "Signal", "value", "options", "this", "_targets", "_batchSnapshotVersion", "_watched", "watched", "_unwatched", "unwatched", "name", "prototype", "brand", "_refresh", "_this", "targets", "_this$_watched", "call", "_unsubscribe", "_this2", "prev", "_this2$_unwatched", "subscribe", "_this3", "valueOf", "toString", "toJSON", "peek", "_this4", "Object", "defineProperty", "get", "set", "Error", "source", "_notify", "target", "prepareSources", "rollbackNode", "cleanupSources", "head", "Computed", "undefined", "_fn", "_globalVersion", "OUTDATED", "computed", "cleanupEffect", "cleanup", "_cleanup", "disposeEffect", "endEffect", "Effect", "push", "finish", "_start", "bind", "_dispose", "dispose", "oldNotify", "currentComponent", "finishUpdate", "DEVTOOLS_ENABLED", "window", "__PREACT_SIGNALS_DEVTOOLS__", "domQueue", "effect", "oldNotify", "this", "_notify", "hook", "hookName", "hookFn", "options", "bind", "setCurrentUpdater", "updater", "finishUpdate", "finish", "_start", "SignalValue", "_ref", "_this", "data", "currentSignal", "useSignal", "value", "_useMemo", "useMemo", "self", "v", "__v", "__", "__c", "_updateFlags", "wrappedSignal", "computed", "s", "isText", "Array", "isArray", "isValidElement", "dispose", "notifyDomUpdates", "__e", "nodeType", "oldDispose", "_updater", "_dispose", "call", "peek", "displayName", "Object", "defineProperties", "Signal", "prototype", "constructor", "configurable", "undefined", "type", "props", "get", "__b", "old", "vnode", "signalProps", "i", "__np", "Fragment", "component", "update", "name", "_callback", "_updater$_debugCallba", "DEVTOOLS_ENABLED", "_debugCallback", "setState", "currentComponent", "error", "oldVNode", "dom", "renderedProps", "updaters", "_updaters", "prop", "signal", "createPropUpdater", "_update", "propSignal", "setAsProperty", "ownerSVGElement", "changeSignal", "lastRenderedValue", "newSignal", "newProps", "setAttribute", "removeAttribute", "index", "Component", "shouldComponentUpdate", "state", "__R", "hasSignals", "_sources", "__f", "u", "hasHooksState", "useComputed", "compute", "$compute", "useRef", "current", "deferDomUpdates", "cb", "queueMicrotask", "flushDomUpdates", "batch", "inst", "domQueue", "shift", "oldNotify", "call", "notifyDomUpdates", "push", "this", "options", "requestAnimationFrame", "deferDomUpdates", "BRAND", "STRINGS_EN", "resolveStrings", "locale", "override", "tag", "primaryTag", "isNested", "isNestedShape", "localeOverride", "enOverride", "moduleLabel", "strings", "label", "v", "localeDisplayName", "target", "displayInLocale", "LocalStorageAdapter", "key", "value", "defaultStorage", "LEVELS", "p", "BRAND", "STORAGE_KEY", "GLOBAL_FLAG_KEY", "URL_QUERY_KEY", "levelIndex", "sink", "resolveInitial", "qs", "i", "parseToIndex", "persisted", "input", "setLogLevel", "input", "i", "parseToIndex", "levelIndex", "STORAGE_KEY", "LEVELS", "getLogLevel", "resolveInitial", "makeLogger", "scope", "tag", "p", "emit", "threshold", "key", "level", "message", "payload", "sink", "childScope", "logger", "setLogSink", "fn", "log", "logger", "RESERVED_KEYS", "MAX_KEYS", "MAX_STRING", "EMAIL_RE", "sanitizeUserContext", "raw", "sanitizeContext", "sanitizePageContext", "label", "validateEmail", "out", "kept", "key", "value", "clean", "sanitizeValue", "trimmed", "DEFAULT_ATTACHMENTS", "DEFAULT_WELCOME", "DEFAULT_FEATURES", "DEFAULT_ENDPOINTS", "DEFAULT_LAUNCHER", "DEFAULT_ACTIONS_BY_MODE", "DEFAULT_SOUND", "DEFAULT_HAPTICS", "DEFAULT_MODULES", "DEFAULT_HOME_MODULE", "resolveOptions", "rawOpts", "opts", "presentation", "behavior", "i18n", "feedback", "footer", "header", "themeMode", "themeOverrides", "parseTheme", "mode", "locale", "resolveDefaultLocale", "availableLocales", "BRAND", "sanitizeUserContext", "sanitizePageContext", "DEFAULT_WELCOME", "DEFAULT_FEATURES", "DEFAULT_ENDPOINTS", "DEFAULT_ATTACHMENTS", "resolveActions", "resolveLauncher", "resolveSound", "resolveHaptics", "resolveStrings", "resolvePoweredBy", "resolveComposer", "resolveModules", "defaultStorage", "input", "overrides", "defaults", "DEFAULT_ACTIONS_BY_MODE", "DEFAULT_SOUND", "DEFAULT_HAPTICS", "merged", "sb", "raw", "DEFAULT_MODULES", "seen", "list", "m", "tags", "id", "resolved", "DEFAULT_HOME_MODULE", "byId", "callout", "DEFAULT_LAUNCHER", "applyOptionOverrides", "options", "activeLocale", "activeThemeMode", "next", "resolveStrings", "SECTION_KEYS", "mergeServerConfig", "user", "server", "out", "key", "sv", "uv", "mergeLeaves", "userI18n", "serverI18n", "merged", "userStrings", "serverStrings", "mergeStringsOverride", "k", "v", "serverNested", "isNestedShape", "userNested", "serverNestedMap", "userNestedMap", "loc", "map", "ACTION_NAME_LITERALS", "boolAttr", "v", "csv", "s", "EMBED_SCALAR_ATTRS", "PRESENTATION_ATTRS", "PRESENTATION_SIZE_ATTRS", "BEHAVIOR_ATTRS", "I18N_ATTRS", "ACTION_NAMES", "LAUNCHER_ATTRS", "v", "boolAttr", "FEATURE_ATTRS", "csv", "ENDPOINT_ATTRS", "USER_CONTEXT_ATTRS", "PAGE_CONTEXT_ATTRS", "SPECIAL_REACTIVE_ATTRS", "ATTR_TABLES", "EMBED_SCALAR_ATTRS", "PRESENTATION_ATTRS", "PRESENTATION_SIZE_ATTRS", "BEHAVIOR_ATTRS", "I18N_ATTRS", "NON_REACTIVE_SCALARS", "REACTIVE_ATTRS", "table", "attr", "isPlainObject", "raw", "isSiteConfigShape", "isBlocksConfigShape", "commandEventName", "cmd", "BRAND", "dispatchHostCommand", "host", "bindHostCommands", "handlers", "wrapped", "fn", "event", "listener", "tokens_default", "reset_default", "panel_default", "standalone_default", "page_default", "PREFIX", "ACCENT", "ACCENT_TEXT", "RADIUS", "FONT", "CSS_TEXT", "tokens_default", "reset_default", "panel_default", "standalone_default", "page_default", "log", "logger", "sheetsByDocument", "HOST_CSS", "applyHostPosition", "host", "position", "hostPositionForMode", "mode", "createShadowMount", "opts", "tagName", "BRAND", "shadow", "hostDoc", "attachAdoptedSheet", "styleEl", "CSS_TEXT", "appRoot", "doc", "win", "sheet", "applyThemeOverrides", "overrides", "p", "applyThemeMode", "applySize", "size", "applyPosition", "pos", "applyMode", "applyHostAttributes", "resolved", "computeHostMode", "configured", "panelSize", "isOpen", "DEFAULT_AUTO_BREAKPOINT", "pickInitialPanelSize", "mode", "size", "viewportWidth", "chosen", "bp", "resolveInitialPanelState", "options", "persistedPanelOpen", "persistedPanelSize", "panelOpen", "currentViewportWidth", "DEFAULT_PUBLIC_KEY_HEADER", "DEPLOYMENT_ID_HEADER", "createAuth", "opts", "headers", "uuid7", "ts", "bytes", "fillRandom", "hex", "b", "cryptoId", "prefix", "view", "i", "StreamError", "message", "code", "status", "cause", "__publicField", "log", "logger", "parseChatStream", "response", "signal", "text", "StreamError", "reader", "buffer", "done", "value", "trailing", "parsed", "parseFrame", "frameEnd", "frame", "dataPayload", "eventId", "line", "part", "chunk", "err", "toBase64Url", "json", "bytes", "binary", "byte", "nonEmpty", "ctx", "encodeContext", "user", "page", "u", "p", "envelope", "toBase64Url", "DEFAULT_PATHS", "CONTEXT_PARAM", "buildSendMessageRequest", "params", "body", "m", "messageToWireParts", "data", "tools", "normalizeToolRef", "t", "ref", "out", "p", "a", "log", "logger", "MAX_RESUME_ATTEMPTS", "RESUME_BACKOFF_MS", "MAX_REQUEST_RETRIES", "RETRYABLE_STATUS", "backoffMs", "attempt", "retryAfterMs", "headers", "raw", "secs", "ms", "sleep", "signal", "resolve", "id", "done", "onAbort", "AgentTransport", "opts", "__publicField", "userContext", "pageContext", "out", "context", "encodeContext", "CONTEXT_PARAM", "body", "form", "key", "value", "urlOrPath", "url", "res", "DEFAULT_PATHS", "isStartSessionResponseShape", "file", "fd", "audio", "mimeType", "extensionFor", "params", "session", "sessionId", "err", "query", "userPrefs", "t", "ctrl", "seenIds", "parseChatStream", "StreamError", "source", "evt", "resumeAt", "path", "label", "build", "reqUrl", "init", "wait", "pathOrUrl", "r", "makeUserMessage", "text", "attachments", "parts", "y", "uuid7", "makeAssistantMessage", "makeTypewriterMessage", "m", "makeInstantWelcomeMessage", "toReactive", "partToReactive", "fromWireMessage", "w", "part", "i", "p", "fromReactive", "partFromReactive", "assistantText", "out", "log", "logger", "StreamReducer", "messagesSig", "__publicField", "msg", "chunk", "m", "ensureTextPart", "p", "applyTool", "appendPart", "_exhaustive", "kind", "id", "existing", "part", "y", "ensureToolPart", "toolCallId", "toolName", "log", "logger", "createPersistence", "widgetId", "storage", "defaultStorage", "prefix", "BRAND", "KEY_VISITOR", "KEY_CHAT", "KEY_USER_PREFS", "KEY_PANEL_OPEN", "KEY_PANEL_SIZE", "KEY_CALLOUT_DISMISSED", "KEY_SIDEBAR_COLLAPSED", "KEY_ACTIVE_MODULE", "persistence", "existing", "minted", "uuid7", "id", "raw", "parsed", "isPlainObject", "error", "s", "patch", "current", "open", "size", "currentText", "dismissedText", "collapsed", "v", "log", "logger", "AudioEngine", "__publicField", "Ctor", "err", "volume", "clamped", "spec", "ctx", "master", "now", "noteSec", "freq", "i", "osc", "env", "start", "url", "buf", "src", "cached", "promise", "r", "ab", "DEFAULT_HAPTIC_PATTERNS", "DEFAULT_TONES", "switchEl", "refCount", "canVibrate", "createSwitch", "input", "BRAND", "label", "hideCss", "haptic", "pattern", "n", "acquireHaptics", "released", "log", "logger", "DEDUP_WINDOW_MS", "FeedbackBus", "sound", "haptics", "__publicField", "y", "AudioEngine", "acquireHaptics", "muted", "event", "prev", "override", "err", "tone", "DEFAULT_TONES", "pattern", "DEFAULT_HAPTIC_PATTERNS", "haptic", "base", "errorMessageFor", "error", "strings", "StreamError", "vnodeId", "createVNode", "type", "props", "key", "isStaticChildren", "__source", "__self", "ref", "i", "normalizedProps", "vnode", "__k", "__", "__b", "__e", "__c", "constructor", "__v", "vnodeId", "__i", "__u", "defaultProps", "options", "props", "ChatIcon", "u", "CloseIcon", "PlusIcon", "PaperPlaneIcon", "ArrowUpIcon", "SendIcon", "ChevronRightIcon", "StopIcon", "PaperclipIcon", "MicIcon", "ExpandIcon", "CollapseIcon", "FullscreenIcon", "FullscreenExitIcon", "ResizeHandleIcon", "MoreIcon", "SpeakerIcon", "SpeakerMutedIcon", "CheckIcon", "HistoryIcon", "GlobeIcon", "BackIcon", "PopOutIcon", "TrashIcon", "FileIcon", "SparkIcon", "MessageIcon", "HelpIcon", "WandIcon", "BotIcon", "HeadsetIcon", "LifebuoyIcon", "SearchIcon", "StatusOkIcon", "HomeIcon", "NewsIcon", "BubblesIcon", "SunIcon", "MoonIcon", "ThemeAutoIcon", "SearchSmileIcon", "SparklesIcon", "ConciergeIcon", "WaveIcon", "ICON_PRESETS", "p", "BRAND", "TID", "tid", "base", "suffix", "ACTION_TID", "p", "BRAND", "Launcher", "onToggle", "launcher", "strings", "leaving", "edgeTab", "showLabel", "u", "TID", "renderIcon", "icon", "Preset", "ICON_PRESETS", "InlineSvg", "source", "el", "mountRawSvg", "host", "root", "LauncherCallout", "callout", "launcherPosition", "closeLabel", "onDismiss", "visible", "setVisible", "d", "y", "id", "resolveCalloutPosition", "CloseIcon", "configured", "isRight", "isBottom", "ResizeGrip", "panelEl", "resize", "position", "initialSize", "onSizeChange", "strings", "p", "BRAND", "dragRef", "A", "y", "style", "isTop", "isRight", "cornerClass", "onPointerDown", "e", "target", "rect", "onPointerMove", "d", "dx", "dy", "onPointerUp", "u", "TID", "ResizeHandleIcon", "p", "BRAND", "HANDLE", "BLOCK", "KEEP_VISIBLE", "useDragMove", "panelEl", "enabled", "y", "num", "v", "clamp", "max", "drag", "onDown", "e", "t", "rect", "onMove", "nx", "ny", "onUp", "isActionVisible", "name", "mode", "panelSize", "ctx", "visibleActions", "actions", "canShowClose", "resolveCloseAction", "FOCUSABLE_SELECTOR", "trapFocus", "container", "onKeydown", "e", "focusables", "el", "first", "last", "active", "initials", "name", "part", "p", "BRAND", "AgentBadge", "agent", "status", "u", "initials", "log", "logger", "ingest", "source", "existing", "limits", "incoming", "collectFiles", "accepted", "rejected", "acceptList", "parseAcceptList", "totalBytes", "sum", "a", "totalCount", "file", "reason", "reject", "toLocalAttachment", "r", "revoke", "att", "error", "count", "bytes", "accept", "matchesAccept", "f", "entry", "toFile", "item", "cryptoId", "s", "type", "subtype", "rules", "name", "p", "BRAND", "AttachmentChip", "attachment", "onRemove", "isImage", "u", "tid", "TID", "FileIcon", "CloseIcon", "UNSUPPORTED", "createVoice", "mode", "locale", "transcribe", "createServerVoice", "createLocalVoice", "Ctor", "getSpeechRecognitionCtor", "rec", "onText", "onEnd", "e", "evt", "text", "isFinal", "i", "r", "end", "g", "CANDIDATE_MIME_TYPES", "recorder", "stream", "chunks", "releaseStream", "track", "mimeType", "pickSupportedMimeType", "type", "blob", "p", "BRAND", "log", "logger", "totalBytes", "items", "acc", "a", "SEND_ICONS", "ArrowUpIcon", "PaperPlaneIcon", "SendIcon", "ChevronRightIcon", "Composer", "options", "transport", "feedback", "bus", "isStreaming", "onSend", "onStop", "attachApiRef", "s", "text", "setText", "d", "atts", "setAtts", "voiceOn", "setVoiceOn", "taRef", "A", "fileRef", "filesEnabled", "voiceMode", "voice", "setVoice", "createVoice", "blob", "type", "y", "el", "root", "active", "attsRef", "revoke", "result", "ingest", "prev", "ta", "panel", "onPaste", "e", "data", "pastedText", "send", "trimmed", "onKeydown", "onFilePick", "input", "removeAtt", "id", "target", "voiceStartedAtRef", "toggleVoice", "durationMs", "transcript", "isFinal", "SendGlyph", "u", "TID", "AttachmentChip", "PaperclipIcon", "MicIcon", "StopIcon", "p", "BRAND", "DropZone", "visible", "strings", "u", "useFileDrop", "containerRef", "onDrop", "visible", "setVisible", "d", "y", "el", "depth", "onEnter", "e", "onLeave", "onOver", "onDropEvent", "HREF", "SRC", "LANG", "CHECKED", "START", "attr_to_html_attr", "type", "HREF", "SRC", "LANG", "CHECKED", "START", "level_to_heading", "level", "heading_from_level", "TOKEN_ARRAY_CAP", "parser", "renderer", "tokens", "parser_end", "p", "parser_write", "add_text", "end_token", "p", "add_token", "token", "idx_of_token", "start_idx", "end_tokens_to_len", "len", "end_tokens_to_indent", "indent", "idx", "i", "continue_or_add_list", "list_token", "list_idx", "item_idx", "add_list_item", "prefix_length", "clear_root_pending", "is_digit", "charcode", "is_delimeter", "is_delimeter_or_number", "parser_write", "p", "chunk", "char", "indent", "end_tokens_to_indent", "pending_with_char", "end_token", "clear_root_pending", "end_tokens_to_len", "add_token", "heading_from_level", "next_blockquote_idx", "idx_of_token", "continue_or_add_list", "add_list_item", "LANG", "START", "char_code", "is_digit", "to_write", "code_start", "add_text", "CHECKED", "symbol", "italic", "strong", "type", "HREF", "SRC", "url", "charcode", "is_delimeter_or_number", "default_renderer", "root", "default_add_token", "default_end_token", "default_add_text", "default_set_attr", "data", "parent", "slot", "checkbox", "text", "value", "attr_to_html_attr", "MarkdownView", "textSig", "doneSig", "ref", "A", "y", "el", "BRAND", "renderer", "default_renderer", "parser", "lastLen", "dispose", "j", "current", "isDone", "delta", "parser_write", "parser_end", "observer", "records", "r", "node", "anchors", "a", "hardenLink", "onClick", "event", "target", "href", "u", "StaticMarkdown", "text", "T", "tokens", "ToolChip", "part", "strings", "p", "BRAND", "u", "p", "BRAND", "MessageBubble", "message", "strings", "responseMode", "showReasoning", "showToolCalls", "partList", "useComputed", "hasAnswerText", "part", "reasoningVisible", "streaming", "bufferedHold", "working", "showTypingDots", "u", "tid", "TID", "LoadingSpinner", "PartView", "partKey", "TypingDots", "active", "MarkdownView", "ReasoningView", "ToolChip", "label", "p", "BRAND", "STICK_THRESHOLD", "MessageList", "messagesSig", "strings", "responseMode", "showReasoning", "showToolCalls", "loading", "ref", "A", "messages", "useComputed", "hasHydratedRef", "firstMessageId", "_", "el", "pinBottom", "resizeObs", "mutationObs", "timerId", "y", "last", "rafId", "id", "u", "m", "MessageBubble", "usePopoverMenu", "itemCount", "initialFocusIndex", "open", "setOpen", "d", "triggerRef", "A", "menuRef", "focusIndex", "setFocusIndex", "close", "q", "toggle", "v", "y", "onDocClick", "e", "menu", "trigger", "path", "desired", "clamped", "onTriggerKey", "onMenuKey", "i", "OverflowMenu", "items", "triggerLabel", "p", "BRAND", "menu", "usePopoverMenu", "handleSelect", "item", "u", "MoreIcon", "CheckIcon", "HISTORY_BUCKETS", "bucketFor", "nowMs", "iso", "t", "diffDays", "startOfDay", "groupByBucket", "sessions", "buckets", "chat", "b", "list", "ms", "d", "log", "logger", "BUCKET_TO_STRING", "ConversationList", "transport", "strings", "visitorId", "onSelect", "onNewChat", "p", "BRAND", "state", "setState", "d", "sessions", "setChats", "y", "cancelled", "res", "err", "newChatButton", "u", "TID", "PlusIcon", "S", "groups", "groupByBucket", "group", "chat", "tid", "ChatHistoryPane", "props", "u", "ConversationList", "LanguageSwitcher", "available", "active", "onSelect", "triggerLabel", "p", "BRAND", "initialFocusIndex", "q", "menu", "usePopoverMenu", "handleSelect", "locale", "u", "GlobeIcon", "isActive", "localeDisplayName", "CheckIcon", "OPTIONS", "iconFor", "mode", "u", "SunIcon", "MoonIcon", "ThemeAutoIcon", "ThemeSwitcher", "active", "onSelect", "triggerLabel", "optionLabels", "p", "BRAND", "initialFocusIndex", "q", "menu", "usePopoverMenu", "handleSelect", "isActive", "CheckIcon", "p", "BRAND", "Suggestions", "suggestions", "onPick", "u", "s", "i", "tid", "TID", "p", "BRAND", "Panel", "props", "options", "onClose", "s", "containerRef", "A", "y", "el", "dispose", "trapFocus", "onKey", "e", "composerAttachApiRef", "onDropItems", "q", "items", "dragOver", "useFileDrop", "useDragMove", "u", "TID", "PanelContent", "PoweredByBar", "transport", "feedback", "bus", "panelSize", "view", "canSend", "isStreaming", "loadingMessages", "messagesSig", "agent", "suggestions", "initialWidgetSize", "visitorId", "onBack", "onClear", "onExpand", "onFullscreen", "onPopOut", "onSoundToggle", "onToggleHistory", "onSelectHistoryChat", "onNewChat", "onLocaleChange", "onThemeChange", "onWidgetSizeChange", "onSend", "onStop", "onSuggestion", "containerEl", "S", "HistoryHeader", "canShowClose", "Header", "visibleActions", "buildDescriptors", "ChatHistoryPane", "chat", "DropZone", "MessageList", "Suggestions", "Composer", "ReadOnlyBanner", "ComposerFooter", "ResizeGrip", "strings", "showClose", "BackIcon", "CloseIcon", "label", "ctaLabel", "disclaimer", "poweredBy", "PoweredBy", "logoUrl", "text", "href", "inner", "backLabel", "moreActionsLabel", "languageLabel", "themeLabel", "themeOptionLabels", "activeThemeMode", "panelTitle", "activeLocale", "availableLocales", "main", "overflow", "descriptors", "showLanguage", "overflowItems", "name", "locale", "d", "themeInOverflow", "AgentBadge", "LanguageSwitcher", "ThemeSwitcher", "ACTION_TID", "OverflowMenu", "args", "expandedNow", "fullscreenNow", "muted", "useComputed", "TrashIcon", "CollapseIcon", "ExpandIcon", "FullscreenExitIcon", "FullscreenIcon", "PopOutIcon", "SpeakerMutedIcon", "SpeakerIcon", "HistoryIcon", "Sidebar", "props", "p", "BRAND", "site", "blocks", "strings", "collapsed", "navigation", "linkCards", "toggleLabel", "u", "TID", "SidebarBrand", "SidebarToggleIcon", "S", "SidebarNav", "SidebarCards", "ConversationList", "alt", "items", "item", "p", "BRAND", "PageShell", "props", "u", "Sidebar", "emptyStacks", "tabs", "createHomeNav", "initialTab", "onSwitch", "sig", "y", "tab", "screen", "s", "stack", "activeStack", "topScreen", "atTabRoot", "chatLayout", "BubblesIcon", "p", "BRAND", "TitleBar", "title", "onClose", "closeLabel", "u", "CloseIcon", "BackHeader", "backLabel", "onBack", "testid", "BackIcon", "p", "BRAND", "HomeSearchButton", "placeholder", "onActivate", "u", "TID", "SearchIcon", "HelpSearchInput", "value", "onInput", "e", "p", "BRAND", "ListRow", "title", "subtitle", "onClick", "testid", "u", "ChevronRightIcon", "p", "BRAND", "ModuleState", "tone", "message", "onRetry", "strings", "u", "p", "BRAND", "log", "logger", "openArticle", "nav", "a", "groupByCategory", "items", "groups", "item", "key", "fuzzyScore", "query", "text", "t", "idx", "from", "ch", "fuzzySearch", "q", "r", "b", "ArticleRow", "article", "u", "ListRow", "tid", "TID", "HelpRoot", "transport", "strings", "config", "panelProps", "tags", "state", "setState", "d", "errorMsg", "setErrorMsg", "setItems", "setQuery", "reloadKey", "setReloadKey", "y", "cancelled", "res", "err", "errorMessageFor", "results", "T", "renderBody", "ModuleState", "k", "category", "rows", "TitleBar", "HelpSearchInput", "helpLayout", "HelpIcon", "props", "p", "BRAND", "HomeCard", "onClick", "children", "testid", "u", "p", "BRAND", "log", "logger", "resolveGreeting", "props", "name", "fromConfig", "openContent", "nav", "item", "HomeRoot", "transport", "strings", "config", "panelProps", "recent", "setRecent", "d", "content", "setContent", "tagsKey", "y", "cancelled", "res", "err", "greeting", "avatars", "status", "contentTitle", "moduleLabel", "u", "TID", "a", "initials", "CloseIcon", "S", "HomeSearchButton", "HomeCard", "BubblesIcon", "StatusOkIcon", "ListRow", "tid", "homeLayout", "HomeIcon", "p", "BRAND", "log", "logger", "NewsRoot", "transport", "strings", "config", "nav", "panelProps", "tags", "state", "setState", "d", "errorMsg", "setErrorMsg", "items", "setItems", "reloadKey", "setReloadKey", "y", "cancelled", "res", "err", "errorMessageFor", "renderBody", "u", "ModuleState", "k", "item", "tid", "TID", "t", "TitleBar", "newsLayout", "NewsIcon", "props", "LAYOUTS", "chatLayout", "homeLayout", "helpLayout", "newsLayout", "p", "BRAND", "HomeTabBar", "modules", "activeTab", "strings", "unreadCount", "onSelect", "u", "m", "Icon", "LAYOUTS", "selected", "badge", "tid", "TID", "moduleLabel", "p", "BRAND", "SANDBOX", "IframeView", "url", "title", "strings", "onBack", "onClose", "u", "BackHeader", "p", "BRAND", "log", "logger", "ContentView", "id", "title", "transport", "strings", "onBack", "onClose", "item", "setItem", "d", "failed", "setFailed", "reloadKey", "setReloadKey", "retry", "q", "k", "y", "cancelled", "res", "found", "err", "renderBody", "u", "ModuleState", "TID", "t", "StaticMarkdown", "BackHeader", "p", "BRAND", "MessengerHome", "panelProps", "enabledModules", "nav", "unreadCount", "onSelectSession", "onStartNewSession", "onSetPanelSize", "options", "strings", "containerRef", "A", "y", "el", "dispose", "trapFocus", "onKey", "e", "composerAttachApiRef", "onDropItems", "q", "items", "dragOver", "useFileDrop", "useDragMove", "navState", "useComputed", "top", "topScreen", "activeModule", "isReader", "showTabBar", "atTabRoot", "savedSize", "prev", "moduleNav", "layout", "target", "m", "screenProps", "module", "body", "u", "IframeView", "ContentView", "PanelContent", "Root", "LAYOUTS", "TID", "HomeTabBar", "PoweredByBar", "ResizeGrip", "p", "BRAND", "ModulesEmpty", "strings", "onClose", "u", "TID", "CloseIcon", "animateWelcomeSequence", "reactives", "payloads", "charsPerSecond", "abort", "msg", "payload", "animateTypewriter", "text", "resolve", "part", "finish", "totalMs", "start", "tick", "now", "progress", "rafId", "useLauncherCallout", "callout", "persistence", "textKey", "persistent", "dismissed", "setDismissed", "d", "y", "dismissCallout", "q", "log", "logger", "p", "BRAND", "App", "options", "hostElement", "bus", "persistence", "d", "createPersistence", "visitorId", "setVisitorId", "initialSettings", "activeLocale", "setActiveLocale", "activeThemeMode", "setActiveThemeMode", "sessionIdSig", "useSignal", "messagesSig", "unreadCountSig", "loadingMessages", "setLoadingMessages", "landingTab", "list", "ids", "m", "persisted", "chatTabId", "homeNavRef", "A", "createHomeNav", "tab", "homeNav", "sessionReady", "setSessionReady", "isInlineLike", "initialPanelRef", "resolveInitialPanelState", "currentViewportWidth", "isOpen", "setIsOpen", "initialPanelApplied", "launcherLeaving", "setLauncherLeaving", "calloutDismissed", "dismissCalloutRaw", "useLauncherCallout", "calloutPersistent", "dismissCallout", "q", "panelSize", "setPanelSize", "initialSizeApplied", "view", "setView", "canSend", "setCanSend", "streaming", "setStreaming", "agent", "setAgent", "suggestions", "setSuggestions", "activeCancel", "setActiveCancel", "welcomeAbortRef", "parsedSite", "setParsedSite", "parsedBlocks", "setParsedBlocks", "sidebarCollapsed", "setSidebarCollapsed", "transport", "AgentTransport", "createAuth", "reducer", "StreamReducer", "feedback", "FeedbackBus", "patchAndSync", "patch", "err", "y", "firstRun", "muted", "pickInitialPanelSize", "state", "homeNavSeeded", "playWelcome", "welcomeMessages", "animation", "speed", "makeInstantWelcomeMessage", "ctrl", "reactives", "makeTypewriterMessage", "animateWelcomeSequence", "connectGenRef", "runStartSession", "newConversation", "myGen", "isStale", "persistedChatId", "sessionId", "uuid7", "res", "isSiteConfigShape", "isBlocksConfigShape", "chat", "fromWireMessage", "userSig", "pageSig", "toWirePageContext", "lastUserSig", "applyMode", "computeHostMode", "onClick", "e", "n", "onKey", "handleSend", "text", "attachments", "uploaded", "uploadAttachments", "userMsg", "makeUserMessage", "assistantMsg", "makeAssistantMessage", "emitMessage", "activeSessionId", "body", "buildSendMessageRequest", "fromReactive", "handle", "evt", "assistantText", "error", "errorMessageFor", "handleStop", "handleSoundToggle", "handleClose", "resolveCloseAction", "refreshUnread", "sum", "c", "unreadSeeded", "handleOpen", "LAUNCHER_EXIT_MS", "handleClear", "handleNewChat", "chatId", "handleExpand", "nextSize", "handleFullscreen", "handlePopOut", "url", "handleToggleHistory", "v", "next", "handleLocaleChange", "locale", "handleThemeChange", "mode", "handleToggleSidebarCollapsed", "prev", "handleWidgetSizeChange", "size", "handleSelectHistoryChat", "targetChatId", "hydrated", "bindHostCommands", "effectiveOptions", "T", "applyOptionOverrides", "panelProps", "s", "onSelectSession", "enabledModules", "renderSurface", "closeable", "isActionVisible", "u", "ModulesEmpty", "Panel", "MessengerHome", "onSelectChatFromSidebar", "messagesEnabled", "PageShell", "inlineSize", "drawerEdgeTab", "launcherVisible", "calloutToRender", "Launcher", "LauncherCallout", "pageContext", "onError", "out", "a", "id", "role", "payload", "log", "logger", "ErrorBoundary", "C", "__publicField", "error", "info", "log", "logger", "IFRAME_SRCDOC", "createIframeMount", "opts", "position", "initialMode", "layoutMode", "target", "iframe", "BRAND", "sizeIframe", "doc", "mount", "createShadowMount", "mo", "BASE_CSS", "FLOATING_SIZE_BY_MODE", "mode", "vKey", "hKey", "w", "h", "EventBus", "__publicField", "name", "fn", "set", "payload", "log", "logger", "HOOK_WILDCARD", "entries", "registerHook", "widgetId", "event", "listener", "entry", "dispatchHook", "instance", "data", "snapshot", "matchesId", "error", "subscribeInstance", "bus", "unsubs", "name", "EVENT_NAMES", "payload", "off", "matchesId", "registered", "actual", "HOOK_WILDCARD", "EVENT_NAMES", "instances", "mount", "opts", "setLogLevel", "log", "logger", "rawOptions", "currentOptions", "resolveOptions", "widgetId", "existing", "initialHostMode", "resolveInitialHostMode", "mountResult", "createIframeMount", "hostPositionForMode", "createShadowMount", "persistence", "createPersistence", "applyResolvedHostAttributes", "cachedThemeMode", "applyHostAttributes", "applyMode", "bus", "EventBus", "reportError", "error", "renderApp", "R", "k", "ErrorBoundary", "App", "response", "mergeServerConfig", "applyHostPosition", "host", "unsubscribeHooks", "applyUpdate", "patch", "instance", "dispatchHostCommand", "name", "fn", "userContext", "pageContext", "level", "getLogLevel", "logSink", "setLogSink", "subscribeInstance", "init", "destroy", "live", "inst", "getInstance", "getAllInstances", "hook", "widgetId", "event", "listener", "registerHook", "version", "brand", "BRAND", "resolveInitialHostMode", "options", "persistence", "createPersistence", "panelOpen", "panelSize", "resolveInitialPanelState", "currentViewportWidth", "computeHostMode"]
|
|
7
7
|
}
|