@aparte/core 0.13.0 → 0.13.1
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/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +2 -0
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/custom-elements.json +6634 -6592
- package/dist/index.css +27 -2
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/index.node.js +1 -1
- package/dist/{is-awaiting-reply-DrO_oR32.js → is-awaiting-reply-CWp1ffHh.js} +15 -25
- package/dist/is-awaiting-reply-CWp1ffHh.js.map +1 -0
- package/dist/renderers/segment-renderers.d.ts +18 -0
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/is-awaiting-reply-DrO_oR32.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/primitives/select/aparte-option.ts","../src/primitives/select/aparte-optgroup.ts","../src/primitives/select/aparte-select.ts","../src/primitives/progress-spinner/aparte-progress-spinner.ts","../src/primitives/icon/aparte-icon.ts","../src/components/elicitation/aparte-elicitation.ts","../src/components/chat/aparte-chat.ts","../src/components/bubble/aparte-chat-bubble.ts","../src/components/status/aparte-chat-status.ts","../src/components/viewport/aparte-chat-viewport.ts","../src/components/composer/aparte-composer.ts","../src/components/composer/aparte-composer-input.ts","../src/components/composer/aparte-composer-send.ts","../src/components/composer/aparte-composer-cancel.ts","../src/components/composer/aparte-composer-attachments.ts","../src/components/composer/aparte-composer-add-attachment.ts","../src/components/composer/aparte-composer-action.ts","../src/components/composer/aparte-composer-toolbar.ts","../src/components/conversation-list/aparte-conversation-list.ts","../src/index.ts"],"sourcesContent":["/**\n * AparteOption\n * \n * Option element for aparte-select dropdown.\n *\n * One selectable row. Only meaningful inside `<aparte-select>` (directly, or nested in an\n * `<aparte-optgroup>`): the parent owns `selected` outright — on its first render, and on\n * every value change after it, it sets that attribute on the option whose `value` matches\n * its own and strips it from all the others. So `selected` written by hand does not\n * survive; set `value` on the select instead. Outside a select nothing selects it: it only\n * styles the row and sets `role=\"option\"` / `aria-selected` from its own attributes.\n *\n * It is not an `<option>`. It carries no form value, `disabled` blocks the click and the\n * keyboard walk but is not a form-disabled state, and when the `value` attribute is\n * absent the trimmed text content is used as the value instead.\n *\n * Keep the label one text node: the `label` property reads only the FIRST text node — that\n * is what keeps the injected status dot out of it — so wrapping the label in an element\n * makes `label` fall back to `value`. The select's own trigger label and its search filter\n * read the full `textContent`, so a wrapped label still displays and still matches.\n *\n * `data-status` is a rendering hook, not state core interprets: any non-empty value\n * appends an `aria-hidden` `.aparte-status-dot` span as the last child, and only\n * `ready`, `cached` and `not-downloaded` have a colour in the stylesheet — anything else\n * renders an uncoloured dot until you style it.\n *\n * @element aparte-option\n * @attr {string} value - Option value\n * @attr {boolean} disabled - Disabled state\n * @attr {boolean} selected - Selected state\n * @attr {string} data-status - Free-form status the host sets; styled, never read by core.\n *\n * @cssprop [--aparte-select-text=var(--aparte-text, #1e293b)] - Option text colour.\n * @cssprop [--aparte-select-option-hover=var(--aparte-surface-2, #f1f5f9)] - Background on hover, and for the keyboard-active row (`[data-active]`), which adds an inset `--aparte-primary` ring on top so the two are distinguishable.\n * @cssprop [--aparte-select-option-selected=color-mix(in srgb, var(--aparte-primary, #3b82f6) 18%, transparent)] - Background of the selected row. A tint by default: a solid accent fill with white text failed WCAG AA in both themes.\n * @cssprop [--aparte-select-option-selected-text=var(--aparte-select-text, var(--aparte-text, #1e293b))] - Text colour of the selected row. Set both this and the background to go back to a solid fill.\n *\n * @example\n * <aparte-select placeholder=\"Pick a model\" value=\"gpt-4o-mini\">\n * <aparte-option value=\"gpt-4o-mini\">GPT-4o mini</aparte-option>\n * <aparte-option value=\"o3\" disabled>o3 (no access)</aparte-option>\n * </aparte-select>\n */\n\nexport class AparteOption extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['value', 'disabled', 'selected', 'data-status'];\n }\n\n connectedCallback(): void {\n this.setAttribute('role', 'option');\n this._updateAriaSelected();\n this._updateStatusDot();\n }\n\n attributeChangedCallback(name: string): void {\n if (name === 'selected') {\n this._updateAriaSelected();\n }\n if (name === 'disabled') {\n this.setAttribute('aria-disabled', this.hasAttribute('disabled') ? 'true' : 'false');\n }\n if (name === 'data-status') {\n this._updateStatusDot();\n }\n }\n\n get value(): string {\n return this.getAttribute('value') || this.textContent?.trim() || '';\n }\n\n set value(val: string) {\n this.setAttribute('value', val);\n }\n\n get label(): string {\n // Use only the first text node, ignoring injected spans (e.g. status dot)\n const textNode = Array.from(this.childNodes).find(n => n.nodeType === Node.TEXT_NODE);\n return textNode?.textContent?.trim() || this.value;\n }\n\n get disabled(): boolean {\n return this.hasAttribute('disabled');\n }\n\n set disabled(val: boolean) {\n if (val) {\n this.setAttribute('disabled', '');\n } else {\n this.removeAttribute('disabled');\n }\n }\n\n get selected(): boolean {\n return this.hasAttribute('selected');\n }\n\n set selected(val: boolean) {\n if (val) {\n this.setAttribute('selected', '');\n } else {\n this.removeAttribute('selected');\n }\n }\n\n private _updateAriaSelected(): void {\n this.setAttribute('aria-selected', this.selected ? 'true' : 'false');\n }\n\n private _updateStatusDot(): void {\n const status = this.getAttribute('data-status');\n let dot = this.querySelector<HTMLSpanElement>('.aparte-status-dot');\n\n if (!status) {\n dot?.remove();\n return;\n }\n\n if (!dot) {\n dot = document.createElement('span');\n dot.className = 'aparte-status-dot';\n dot.setAttribute('aria-hidden', 'true');\n this.appendChild(dot);\n }\n\n dot.setAttribute('data-status', status);\n }\n}\n\n// Register\nif (!customElements.get('aparte-option')) {\n customElements.define('aparte-option', AparteOption);\n}\n","import { resolveConfig } from '../../config/config-context.js';\n\n/**\n * AparteOptgroup\n * \n * Option group element for aparte-select dropdown.\n *\n * A labelled band of options inside `<aparte-select>`. Presentational only: the group\n * holds no value, and collapsing it just sets `display: none` on its `<aparte-option>`\n * descendants — they stay in the DOM, which is what keeps the select's keyboard walk\n * skipping them, since it works off `display`. The label header is inserted before the\n * children and the loading row appended after them, so both live inside the group, and\n * neither is hidden when it collapses: only the options are.\n *\n * Two consequences of that worth knowing before you reach for it. The select's search\n * writes that same `display` property on every option in the select, so filtering can\n * reveal matches inside a collapsed group. And the header is built once, on the first\n * render that finds a `label`: changing `label` afterwards does not rewrite it — set the\n * label before inserting the group, or replace the group.\n *\n * `loading` is a display state, not a fetch. It appends a spinner row to the group and\n * nothing else happens — the host still owns the request. \"Fetch on expand\" is driven by\n * `aparte-optgroup-toggle`, whose `detail.collapsed` says which way the group just went;\n * the attribute and the options' `display` are already updated when it fires, since\n * setting the attribute runs `attributeChangedCallback` synchronously.\n *\n * @element aparte-optgroup\n * @attr {string} label - Group label\n * @attr {boolean} collapsible - Adds the chevron and the click handler to the header — so it needs a `label`, and it is read only when that header is first built.\n * @attr {boolean} collapsed - Collapsed state\n * @attr {boolean} loading - Appends a spinner row to the group; the options stay visible.\n *\n * @fires {CustomEvent<AparteOptgroupToggleEventDetail>} aparte-optgroup-toggle - The group was collapsed or expanded.\n *\n * @cssprop --aparte-text-muted - Colour of the group header label and of the loading row.\n * The shared theme token: there is no optgroup-specific override, so restyling one group's\n * header means setting this on that element.\n *\n * @example\n * <!-- Collapsed groups keep a long list readable; the label is the group's header. -->\n * <aparte-select grouped placeholder=\"Pick a model\">\n * <aparte-optgroup label=\"Ollama\" collapsible collapsed>\n * <aparte-option value=\"ollama::llama3\">Llama 3</aparte-option>\n * </aparte-optgroup>\n * <aparte-optgroup label=\"OpenRouter\" collapsible>\n * <aparte-option value=\"openrouter::gpt-4o-mini\">GPT-4o mini</aparte-option>\n * </aparte-optgroup>\n * </aparte-select>\n */\n\nexport class AparteOptgroup extends HTMLElement {\n /** Ids for the label span the group points `aria-labelledby` at. */\n private static _labelIdSeq = 0;\n\n static get observedAttributes(): string[] {\n return ['label', 'collapsible', 'collapsed', 'loading'];\n }\n\n connectedCallback(): void {\n this.setAttribute('role', 'group');\n this._render();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n if (name === 'collapsed') {\n this._updateCollapsedState();\n }\n\n if (this.isConnected) {\n this._render();\n }\n }\n\n get label(): string {\n return this.getAttribute('label') || '';\n }\n\n set label(val: string) {\n this.setAttribute('label', val);\n }\n\n get collapsible(): boolean {\n return this.hasAttribute('collapsible');\n }\n\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(val: boolean) {\n if (val) {\n this.setAttribute('collapsed', '');\n } else {\n this.removeAttribute('collapsed');\n }\n }\n\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(val: boolean) {\n if (val) this.setAttribute('loading', '');\n else this.removeAttribute('loading');\n }\n\n private _render(): void {\n // Only render header if we have a label\n if (this.label) {\n const existingHeader = this.querySelector('.aparte-optgroup-header');\n if (!existingHeader) {\n const header = document.createElement('div');\n header.className = 'aparte-optgroup-header';\n // The header stays a GENERIC node on purpose: naming it (it used to\n // carry `aria-label`) makes it a real element inside a `listbox`,\n // where only options and groups may live (axe:\n // aria-required-children, critical). The name belongs on the group,\n // via aria-labelledby below.\n // `label` is an attribute value — with the model-selector it carries a\n // provider-supplied name — so it goes through textContent, never\n // innerHTML (a hostile name would otherwise inject here).\n const labelSpan = document.createElement('span');\n labelSpan.className = 'aparte-optgroup-label';\n labelSpan.id = `aparte-optgroup-label-${++AparteOptgroup._labelIdSeq}`;\n labelSpan.textContent = this.label;\n this.setAttribute('aria-labelledby', labelSpan.id);\n header.appendChild(labelSpan);\n\n if (this.collapsible) {\n const chevron = document.createElement('span');\n chevron.className = 'aparte-optgroup-chevron';\n // The library's own `expand`, not a shape drawn here. This span was\n // painted as a CSS border-triangle — the second hand-drawn chevron in\n // core, built from PHYSICAL border sides where the tool row's used\n // logical ones: two markers, two constructions, which is the divergence\n // the icons rule was written after.\n chevron.innerHTML = resolveConfig(this).getIcon('expand');\n header.appendChild(chevron);\n header.style.cursor = 'pointer';\n header.addEventListener('click', (e) => {\n e.stopPropagation();\n this._toggleCollapse();\n });\n }\n\n this.insertBefore(header, this.firstChild);\n }\n }\n\n // Update loading state\n this._updateLoadingState();\n\n // Update collapsed state\n this._updateCollapsedState();\n }\n\n private _updateLoadingState(): void {\n let loader = this.querySelector('.aparte-optgroup-loader');\n if (this.loading) {\n if (!loader) {\n loader = document.createElement('div');\n loader.className = 'aparte-optgroup-loader';\n loader.innerHTML = '<span class=\"aparte-spinner-small\"></span> Fetching models...';\n this.appendChild(loader);\n }\n } else if (loader) {\n loader.remove();\n }\n }\n\n private _toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n\n // Dispatch event before updating UI to allow parent to react (e.g. fetch data)\n this.dispatchEvent(new CustomEvent<AparteOptgroupToggleEventDetail>('aparte-optgroup-toggle', {\n bubbles: true,\n composed: true,\n detail: {\n label: this.label,\n collapsed: this.collapsed\n }\n }));\n\n this._updateCollapsedState();\n }\n\n private _updateCollapsedState(): void {\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n (opt as HTMLElement).style.display = this.collapsed ? 'none' : '';\n });\n }\n}\n\n// Register\nif (!customElements.get('aparte-optgroup')) {\n customElements.define('aparte-optgroup', AparteOptgroup);\n}\n\n/**\n * Detail payload for `aparte-optgroup-toggle`.\n *\n * `types/event-map.ts` types `aparte-optgroup-toggle` with this detail, and\n * `@aparte/plugin-model-selector` reads both of its fields, from another package,\n * through an untyped cast — while the event is published in the generated CEM event\n * table. It is a contract.\n *\n * @event aparte-optgroup-toggle\n */\nexport interface AparteOptgroupToggleEventDetail {\n /** The group's label. */\n label: string;\n /** `true` when the group just collapsed. */\n collapsed: boolean;\n}\n","import './aparte-option.js';\nimport './aparte-optgroup.js';\nimport { resolveConfig } from '../../config/config-context.js';\n\nexport interface AparteSelectChangeDetail {\n value: string;\n label: string;\n previousValue: string;\n}\n\n/**\n * Dropdown select for aparté — a vanilla web component with optional grouping, a\n * search filter and a keyboard-driven listbox.\n *\n * The element is light DOM and takes `<aparte-option>` / `<aparte-optgroup>` children, in\n * the order they should appear. On its first render it captures its children, keeps those\n * two kinds and moves them into the `role=\"listbox\"` container it builds, then rewrites\n * its own `innerHTML` — so any other child is dropped, and a wrapper element around your\n * options takes the options down with it: only DIRECT children are captured.\n *\n * Children written later are picked up by a `subtree` MutationObserver and moved into that\n * same container, and the keyboard highlight is re-asserted on the new elements. The move\n * EMPTIES the container first, so a later write replaces the list instead of adding to it:\n * write the whole list, not one option. Writing straight into `.aparte-select-options`\n * skips the move (the observer then only re-asserts the highlight), and that is the path\n * `@aparte/plugin-model-selector` takes to refresh a live list in place.\n *\n * It is not a form control: no `name`, no `multiple`, no participation in form submission.\n * It holds exactly one value and reports it through `aparte-select-change`.\n *\n * The dropdown is `position: fixed` and placed from script so it escapes an\n * `overflow: hidden` ancestor — which is why its stacking order is a variable\n * (`--aparte-select-z`) rather than a fixed rule, and why an `open` dropdown does not\n * scroll with the trigger.\n *\n * @element aparte-select\n * @attr {string} value - The selected option's value.\n * @attr {string} placeholder - Shown while nothing is selected.\n * @attr {boolean} disabled - Blocks opening the dropdown.\n * @attr {boolean} grouped - Observed, never read: `<aparte-optgroup>` children render as groups without it.\n * @attr {boolean} searchable - Adds a filter field above the options. Read on the first render only.\n * @attr {boolean} open - Reflects (and controls) whether the dropdown is open.\n *\n * @fires {CustomEvent<AparteSelectChangeDetail>} aparte-select-change - The selection changed; carries the new value, its label and the previous value.\n * @fires aparte-select-open - The dropdown opened. No detail.\n * @fires aparte-select-close - The dropdown closed. No detail.\n *\n * @cssprop [--aparte-select-bg=var(--aparte-surface-1, #fff)] - Trigger background — and, under `[data-aparte-theme=\"dark\"]`, the dropdown panel's too.\n * @cssprop [--aparte-select-border=var(--aparte-border, #e2e8f0)] - Border of the trigger and of the dropdown.\n * @cssprop [--aparte-select-border-hover=var(--aparte-primary, #3b82f6)] - Trigger border on hover.\n * @cssprop [--aparte-select-border-focus=var(--aparte-primary, #3b82f6)] - Trigger border while focused.\n * @cssprop [--aparte-select-ring=rgba(59, 130, 246, 0.2)] - Colour of the 2px focus ring around the trigger.\n * @cssprop [--aparte-select-radius=0.5rem] - Corner radius of the trigger and the dropdown.\n * @cssprop [--aparte-select-text=var(--aparte-text, #1e293b)] - Colour of the trigger label (and of the options).\n * @cssprop [--aparte-select-chevron=var(--aparte-text-muted, #94a3b8)] - Colour of the chevron, which rotates 180° while open.\n * @cssprop [--aparte-select-dropdown-bg=var(--aparte-surface-1, #fff)] - Dropdown panel background in the light theme only; the `[data-aparte-theme=\"dark\"]` rule is more specific and reads `--aparte-select-bg` instead.\n * @cssprop [--aparte-select-shadow=0 4px 12px rgba(0, 0, 0, 0.1)] - Dropdown panel shadow.\n * @cssprop [--aparte-select-z=1000] - `z-index` of the dropdown. It is `position: fixed`, so this is the one knob that decides whether it lands above the rest of your page.\n *\n * @example\n * <aparte-select placeholder=\"Pick a model\" searchable value=\"gpt-4o-mini\">\n * <aparte-option value=\"gpt-4o-mini\">GPT-4o mini</aparte-option>\n * <aparte-option value=\"llama-3.1-8b\">Llama 3.1 8B</aparte-option>\n * </aparte-select>\n *\n * <script>\n * document.querySelector('aparte-select').addEventListener('aparte-select-change', (e) => {\n * console.log(e.detail.value, e.detail.label, e.detail.previousValue);\n * });\n * </script>\n */\nexport class AparteSelect extends HTMLElement {\n private static _optIdSeq = 0;\n /** Fallback ids for the listbox `aria-controls` target when the host has no id. */\n private static _listboxSeq = 0;\n\n private _value = '';\n private _isOpen = false;\n private _activeIndex = -1;\n private _trigger: HTMLElement | null = null;\n private _dropdown: HTMLElement | null = null;\n private _searchInput: HTMLInputElement | null = null;\n private _observer: MutationObserver | null = null;\n\n // Bound handlers for cleanup\n /**\n * Bound, like its two neighbours below — because an inline arrow on `this` can\n * never be removed.\n *\n * `_setupEventListeners()` runs on EVERY `connectedCallback`, and `_render()`'s\n * idempotency guard means the host element survives a re-connect. So each move\n * of the element (a portal, a Vue teleport, any framework re-parent) added\n * another option-click listener: measured, one click fired the change handler\n * FIVE times after five re-connects. `disconnectedCallback` removed the two\n * document-level handlers and could not touch this one.\n *\n * This is verbatim the bug class `aparte-chat-viewport` documents having fixed\n * for its own listeners.\n */\n private _boundHandleOptionClick = this._handleOptionClick.bind(this);\n private _boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n private _boundHandleKeydown = this._handleKeydown.bind(this);\n\n static get observedAttributes(): string[] {\n return ['value', 'placeholder', 'disabled', 'grouped', 'searchable', 'open'];\n }\n\n connectedCallback(): void {\n this._value = this.getAttribute('value') || '';\n this._isOpen = this.hasAttribute('open');\n this._render();\n this._setupEventListeners();\n this._setupMutationObserver();\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('click', this._boundHandleOptionClick);\n document.removeEventListener('click', this._boundHandleDocumentClick);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n this._observer?.disconnect();\n }\n\n attributeChangedCallback(name: string, oldValue: string, newValue: string): void {\n if (!this.isConnected) return;\n\n if (name === 'value' && oldValue !== newValue && newValue !== this._value) {\n this._value = newValue || '';\n this._updateTriggerLabel();\n }\n if (name === 'open') {\n this._isOpen = this.hasAttribute('open');\n if (this._isOpen) {\n this._dropdown?.removeAttribute('hidden');\n this._searchInput?.focus();\n } else {\n this._dropdown?.setAttribute('hidden', '');\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public API\n // ─────────────────────────────────────────────────────────────────────────\n\n get value(): string {\n return this._value;\n }\n\n set value(val: string) {\n if (val === this._value) return;\n const previousValue = this._value;\n this._value = val;\n this.setAttribute('value', val);\n this._updateTriggerLabel();\n this._emitChange(previousValue);\n }\n\n get open(): boolean {\n return this._isOpen;\n }\n\n set open(val: boolean) {\n if (val) {\n this.setAttribute('open', '');\n } else {\n this.removeAttribute('open');\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Rendering\n // ─────────────────────────────────────────────────────────────────────────\n\n private _render(): void {\n const placeholder = this.getAttribute('placeholder') || 'Select...';\n const searchable = this.hasAttribute('searchable');\n\n // Check if already rendered (has dropdown structure)\n if (this.querySelector('.aparte-select-dropdown')) {\n this._updateTriggerLabel();\n return;\n }\n\n // First render: capture any slotted children before modifying DOM\n const slottedChildren = Array.from(this.children);\n\n // Create wrapper structure\n const trigger = document.createElement('div');\n trigger.className = 'aparte-select-trigger';\n trigger.setAttribute('tabindex', '0');\n trigger.setAttribute('role', 'combobox');\n trigger.setAttribute('aria-haspopup', 'listbox');\n trigger.setAttribute('aria-expanded', 'false');\n // Accessible name (axe aria-input-field-name): the visible label span is\n // the combobox VALUE, not its name — name it from the host's aria-label\n // when provided, else the placeholder (\"Select model…\" etc.).\n trigger.setAttribute('aria-label', this.getAttribute('aria-label') || placeholder);\n // The label is placeholder text (consumer/attribute-supplied) → textContent,\n // same path as _updateTriggerLabel(), never innerHTML. Only the static SVG\n // chevron uses innerHTML.\n const labelSpan = document.createElement('span');\n labelSpan.className = 'aparte-select-label';\n labelSpan.textContent = placeholder;\n const chevronSpan = document.createElement('span');\n chevronSpan.className = 'aparte-select-chevron';\n chevronSpan.innerHTML = resolveConfig(this).getIcon('expand');\n trigger.append(labelSpan, chevronSpan);\n\n // The dropdown is a plain shell: it also holds the search field, and a\n // `listbox` may only contain options/groups (axe: aria-required-children,\n // critical). The listbox lives on the options container below.\n const dropdown = document.createElement('div');\n dropdown.className = 'aparte-select-dropdown';\n dropdown.hidden = !this._isOpen;\n\n if (searchable) {\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.className = 'aparte-select-search';\n searchInput.placeholder = 'Search...';\n searchInput.setAttribute('aria-label', 'Search options');\n dropdown.appendChild(searchInput);\n }\n\n const optionsContainer = document.createElement('div');\n optionsContainer.className = 'aparte-select-options';\n optionsContainer.setAttribute('role', 'listbox');\n // A listbox is an ARIA input field, so it needs its own name; reuse the\n // combobox's (axe: aria-input-field-name).\n optionsContainer.setAttribute('aria-label', trigger.getAttribute('aria-label') ?? placeholder);\n // `role=\"combobox\"` REQUIRES aria-controls (axe: aria-required-attr).\n optionsContainer.id = this.id ? `${this.id}-listbox` : `aparte-listbox-${++AparteSelect._listboxSeq}`;\n trigger.setAttribute('aria-controls', optionsContainer.id);\n\n // Move slotted children (aparte-option, aparte-optgroup) into options container\n slottedChildren.forEach(child => {\n if (child.tagName === 'APARTE-OPTION' || child.tagName === 'APARTE-OPTGROUP') {\n optionsContainer.appendChild(child);\n }\n });\n\n dropdown.appendChild(optionsContainer);\n\n // Clear and rebuild DOM\n this._observer?.disconnect();\n this.innerHTML = '';\n this.appendChild(trigger);\n this.appendChild(dropdown);\n\n this._trigger = trigger;\n this._dropdown = dropdown;\n this._searchInput = dropdown.querySelector('.aparte-select-search');\n\n if (this.isConnected) {\n this._setupMutationObserver();\n }\n\n // Update label based on current value\n this._updateTriggerLabel();\n }\n\n private _setupMutationObserver(): void {\n this._observer = new MutationObserver(() => {\n this._updateDropdownContent();\n // The options may have just been replaced under an open dropdown —\n // re-assert the keyboard position on the NEW elements. See\n // {@link _restoreActive}.\n this._restoreActive();\n });\n\n // `subtree` matters: a consumer refreshing a live list writes into\n // `.aparte-select-options` (the model selector does exactly that when the\n // provider list settles), which is a DESCENDANT. Watching only our own\n // children missed it entirely — the highlight vanished with the removed\n // elements and nothing here noticed. Our own writes disconnect the observer\n // first, so this cannot loop.\n this._observer.observe(this, { childList: true, subtree: true });\n }\n\n /**\n * Put the roving highlight back after the options changed underneath it.\n *\n * `data-active` lives on an option ELEMENT, so replacing the list throws it\n * away while `_activeIndex` still claims a position: the highlight disappeared,\n * `aria-activedescendant` kept pointing at an id no longer in the document, and\n * the next ArrowDown moved from the stale index — skipping an option. Only when\n * open and only when a position was held, so a refresh never invents one.\n */\n private _restoreActive(): void {\n if (!this._isOpen || this._activeIndex < 0) return;\n if (this.querySelector('aparte-option[data-active]')) return;\n this._setActive(this._activeIndex);\n }\n\n private _updateDropdownContent(): void {\n // If trigger is gone, the component was likely wiped by innerHTML\n if (!this._trigger || !this.contains(this._trigger)) {\n this._render();\n return;\n }\n\n const optionsContainer = this.querySelector('.aparte-select-options');\n if (!optionsContainer) {\n // If internal UI is present but container is gone\n this._render();\n return;\n }\n\n // Collect all potential options from light DOM\n // (those aren't internal UI elements)\n const lightChildren = Array.from(this.children).filter(child =>\n child.className !== 'aparte-select-trigger' &&\n child.className !== 'aparte-select-dropdown'\n );\n\n if (lightChildren.length === 0) return;\n\n // Pause observer to prevent self-triggering loop\n this._observer?.disconnect();\n\n // Clear container (but keep internal stuff if any)\n optionsContainer.innerHTML = '';\n\n // Move/Append children to container\n lightChildren.forEach(child => {\n optionsContainer.appendChild(child);\n });\n\n // Resume observer\n if (this.isConnected) {\n this._observer?.observe(this, { childList: true, subtree: true });\n }\n\n this._updateTriggerLabel();\n this._restoreActive();\n }\n\n private _setupEventListeners(): void {\n // Trigger click\n this._trigger?.addEventListener('click', () => this._toggle());\n\n // Trigger keyboard\n this._trigger?.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n // When open, Enter/Space selects the active option — handled by\n // the document-level nav handler. Only toggle when closed, so we\n // don't close the dropdown on the very keystroke meant to select.\n if (this._isOpen) return;\n e.preventDefault();\n this._toggle();\n }\n if (e.key === 'ArrowDown' && !this._isOpen) {\n e.preventDefault();\n this._openDropdown();\n }\n });\n\n // Option selection. Removed first: `_setupEventListeners` runs on every\n // connect, and adding the same bound reference twice is a no-op per spec —\n // but being explicit costs nothing and survives a future refactor that\n // rebinds.\n this.removeEventListener('click', this._boundHandleOptionClick);\n this.addEventListener('click', this._boundHandleOptionClick);\n\n // Search filter\n this._searchInput?.addEventListener('input', (e) => {\n const query = (e.target as HTMLInputElement).value.toLowerCase();\n this._filterOptions(query);\n });\n\n // Close on outside click\n document.addEventListener('click', this._boundHandleDocumentClick);\n\n // Keyboard navigation\n document.addEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _handleOptionClick(e: Event): void {\n const option = (e.target as HTMLElement).closest('aparte-option');\n if (option && !option.hasAttribute('disabled')) {\n this._selectOption(option as HTMLElement);\n }\n }\n\n private _handleDocumentClick(e: Event): void {\n if (!this.contains(e.target as Node)) {\n this._closeDropdown();\n }\n }\n\n private _handleKeydown(e: KeyboardEvent): void {\n if (!this._isOpen) return;\n\n // Home/End move the caret when typing in the search box; only hijack\n // them for option navigation when focus is not in the search field.\n const inSearch = document.activeElement === this._searchInput;\n\n switch (e.key) {\n case 'Escape':\n e.preventDefault();\n this._closeDropdown();\n this._trigger?.focus();\n break;\n case 'ArrowDown':\n e.preventDefault();\n this._moveActive(1);\n break;\n case 'ArrowUp':\n e.preventDefault();\n this._moveActive(-1);\n break;\n case 'Home':\n if (inSearch) break;\n e.preventDefault();\n this._setActive(0);\n break;\n case 'End':\n if (inSearch) break;\n e.preventDefault();\n this._setActive(this._visibleOptions().length - 1);\n break;\n case 'Enter': {\n const active = this._visibleOptions()[this._activeIndex];\n if (active) {\n e.preventDefault();\n this._selectOption(active);\n }\n break;\n }\n }\n }\n\n /** Non-disabled, non-filtered options in DOM order. */\n private _visibleOptions(): HTMLElement[] {\n return Array.from(this.querySelectorAll<HTMLElement>('aparte-option')).filter(\n opt => !opt.hasAttribute('disabled') && opt.style.display !== 'none',\n );\n }\n\n /** Move the active (keyboard-highlighted) option by `delta`, clamped. */\n private _moveActive(delta: number): void {\n const opts = this._visibleOptions();\n if (opts.length === 0) return;\n const base = this._activeIndex < 0 ? (delta > 0 ? -1 : 0) : this._activeIndex;\n this._setActive(base + delta);\n }\n\n /** Highlight the option at `index` (clamped) and point aria-activedescendant at it. */\n private _setActive(index: number): void {\n const all = this.querySelectorAll<HTMLElement>('aparte-option');\n all.forEach(o => o.removeAttribute('data-active'));\n\n const opts = this._visibleOptions();\n if (opts.length === 0) {\n this._activeIndex = -1;\n this._trigger?.removeAttribute('aria-activedescendant');\n return;\n }\n const clamped = Math.max(0, Math.min(index, opts.length - 1));\n this._activeIndex = clamped;\n\n const active = opts[clamped]!;\n if (!active.id) active.id = `aparte-option-${++AparteSelect._optIdSeq}`;\n active.setAttribute('data-active', '');\n this._trigger?.setAttribute('aria-activedescendant', active.id);\n active.scrollIntoView?.({ block: 'nearest' });\n }\n\n /** Clear the keyboard highlight (on close). */\n private _clearActive(): void {\n this._activeIndex = -1;\n this._trigger?.removeAttribute('aria-activedescendant');\n this.querySelectorAll('aparte-option').forEach(o => o.removeAttribute('data-active'));\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Actions\n // ─────────────────────────────────────────────────────────────────────────\n\n private _toggle(): void {\n if (this._isOpen) {\n this._closeDropdown();\n } else {\n this._openDropdown();\n }\n }\n\n private _openDropdown(): void {\n if (this.hasAttribute('disabled')) return;\n\n this._isOpen = true;\n this._dropdown?.removeAttribute('hidden');\n this._trigger?.setAttribute('aria-expanded', 'true');\n this.setAttribute('open', '');\n\n // Smart Positioning\n this._updatePosition();\n\n // Focus search if available\n this._searchInput?.focus();\n\n // Seed the keyboard highlight on the current selection (or the first\n // option) so ArrowUp/Down have an anchor and screen readers announce it.\n const opts = this._visibleOptions();\n const selectedIdx = opts.findIndex(o => o.getAttribute('value') === this._value);\n this._setActive(selectedIdx >= 0 ? selectedIdx : 0);\n\n this.dispatchEvent(new CustomEvent('aparte-select-open', { bubbles: true }));\n }\n\n private _closeDropdown(): void {\n this._isOpen = false;\n this._clearActive();\n this._dropdown?.setAttribute('hidden', '');\n this._trigger?.setAttribute('aria-expanded', 'false');\n this.removeAttribute('open');\n this.removeAttribute('position'); // Reset position attribute\n\n // Clear position styles set by _updatePosition\n if (this._dropdown) {\n this._dropdown.style.top = '';\n this._dropdown.style.bottom = '';\n this._dropdown.style.left = '';\n this._dropdown.style.width = '';\n }\n\n // Clear search\n if (this._searchInput) {\n this._searchInput.value = '';\n this._filterOptions('');\n }\n\n this.dispatchEvent(new CustomEvent('aparte-select-close', { bubbles: true }));\n }\n\n private _updatePosition(): void {\n if (!this._dropdown || !this._trigger) return;\n\n const rect = this._trigger.getBoundingClientRect();\n const dropdownHeight = this._dropdown.offsetHeight || 300;\n const viewportHeight = window.innerHeight;\n const spaceBelow = viewportHeight - rect.bottom;\n const GAP = 4; // px gap between trigger and dropdown\n\n // Always size to trigger width\n this._dropdown.style.left = `${rect.left}px`;\n this._dropdown.style.width = `${rect.width}px`;\n\n // Decide whether to open upward or downward\n if (spaceBelow < dropdownHeight && rect.top > dropdownHeight) {\n // Open upward\n this._dropdown.style.top = '';\n this._dropdown.style.bottom = `${viewportHeight - rect.top + GAP}px`;\n this.setAttribute('position', 'top');\n } else {\n // Open downward\n this._dropdown.style.top = `${rect.bottom + GAP}px`;\n this._dropdown.style.bottom = '';\n this.removeAttribute('position');\n }\n }\n\n private _selectOption(option: HTMLElement): void {\n const value = option.getAttribute('value') || option.textContent?.trim() || '';\n const previousValue = this._value;\n\n this._value = value;\n this.setAttribute('value', value);\n this._updateTriggerLabel();\n this._closeDropdown();\n this._emitChange(previousValue);\n this._trigger?.focus();\n }\n\n private _updateTriggerLabel(): void {\n const labelEl = this._trigger?.querySelector('.aparte-select-label');\n if (labelEl) {\n const selectedLabel = this._getSelectedLabel();\n labelEl.textContent = selectedLabel || this.getAttribute('placeholder') || 'Select...';\n }\n\n // Update selected state on options\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n const isSelected = opt.getAttribute('value') === this._value;\n if (isSelected) {\n opt.setAttribute('selected', '');\n } else {\n opt.removeAttribute('selected');\n }\n });\n }\n\n private _getSelectedLabel(): string {\n // Match by property, not an interpolated attribute selector — a value with\n // `\"`/`]` (e.g. a remote model id) would make querySelector throw SyntaxError.\n for (const opt of this.querySelectorAll('aparte-option')) {\n if (opt.getAttribute('value') === this._value) return opt.textContent?.trim() || '';\n }\n return '';\n }\n\n private _filterOptions(query: string): void {\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n const label = opt.textContent?.toLowerCase() || '';\n const matches = label.includes(query);\n (opt as HTMLElement).style.display = matches ? '' : 'none';\n });\n // Re-anchor the keyboard highlight on the first still-visible option.\n if (this._isOpen) this._setActive(0);\n }\n\n private _emitChange(previousValue: string): void {\n const detail: AparteSelectChangeDetail = {\n value: this._value,\n label: this._getSelectedLabel(),\n previousValue\n };\n\n this.dispatchEvent(new CustomEvent<AparteSelectChangeDetail>('aparte-select-change', {\n bubbles: true,\n composed: true,\n detail\n }));\n }\n}\n\n// Register\nif (!customElements.get('aparte-select')) {\n customElements.define('aparte-select', AparteSelect);\n}\n","/**\n * AparteProgressSpinner\n *\n * Circular progress spinner web component.\n * - Indeterminate (no `value` attribute): continuous rotation animation\n * - Determinate (`value=\"0–100\"`): fills the arc proportionally\n *\n * The ABSENCE of the attribute is what selects indeterminate, so `value=\"\"` is not\n * \"unknown progress\" — it parses to 0, i.e. an empty determinate arc. `value` is clamped\n * to 0–100 and anything non-numeric reads as 0; nothing throws.\n *\n * It renders its own SVG into itself on connect and on every `value` change, so it takes\n * no children: whatever you put inside is overwritten. The SVG is `aria-hidden` and the\n * ARIA lives on the host (`role=\"progressbar\"`, `aria-valuemin`/`aria-valuemax`, plus\n * `aria-valuenow` only when determinate) — there is no accessible NAME, so give the\n * element an `aria-label` unless the surrounding text already says what is loading.\n *\n * It draws an arc; it does not manage a loading lifecycle — no delay before appearing, no\n * timeout, no label, no live announcement. Under `prefers-reduced-motion: reduce` the\n * rotation stops (aparte.css scopes that rule to the library's own elements), which is the\n * other reason the indeterminate arc must not be the only signal that work is in flight.\n *\n * @element aparte-progress-spinner\n * @attr {number} value - Progress percentage 0–100 (omit for indeterminate)\n *\n * @cssprop [--aparte-spinner-size=16px] - Width and height of the element; the SVG fills it.\n * @cssprop [--aparte-spinner-stroke=2.5] - Stroke width of both arcs, in the units of the 24×24 viewBox.\n * @cssprop [--aparte-spinner-color=currentColor] - Stroke of the filled (progress) arc.\n * @cssprop [--aparte-spinner-track=color-mix(in srgb, currentColor 15%, transparent)] - Stroke of the track arc behind it.\n *\n * @example\n * <!-- Omit `value` for the indeterminate spin; set it to show real progress. -->\n * <aparte-progress-spinner></aparte-progress-spinner>\n * <aparte-progress-spinner value=\"62\"></aparte-progress-spinner>\n */\nexport class AparteProgressSpinner extends HTMLElement {\n static get observedAttributes(): string[] { return ['value']; }\n\n /** Radius of the SVG circle (viewBox is 0 0 24 24, center at 12,12) */\n private readonly _r = 9;\n private get _circ(): number { return 2 * Math.PI * this._r; }\n\n connectedCallback(): void { this._render(); }\n attributeChangedCallback(): void { this._render(); }\n\n private _render(): void {\n const raw = this.getAttribute('value');\n const value = raw !== null\n ? Math.min(100, Math.max(0, parseFloat(raw) || 0))\n : null;\n\n this.setAttribute('role', 'progressbar');\n this.setAttribute('aria-valuemin', '0');\n this.setAttribute('aria-valuemax', '100');\n if (value !== null) {\n this.setAttribute('aria-valuenow', String(value));\n } else {\n this.removeAttribute('aria-valuenow');\n }\n\n // Determinate: dashoffset shrinks from circ→0 as value goes 0→100\n const dashoffset = value !== null ? this._circ * (1 - value / 100) : 0;\n // Indeterminate: fixed partial arc (~72% of circumference)\n const dasharray = value !== null\n ? `${this._circ.toFixed(2)}`\n : `${(this._circ * 0.72).toFixed(2)} ${(this._circ * 0.28).toFixed(2)}`;\n\n this.innerHTML = `<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><circle class=\"aparte-spinner-track\" cx=\"12\" cy=\"12\" r=\"${this._r}\"/><circle class=\"aparte-spinner-fill\" cx=\"12\" cy=\"12\" r=\"${this._r}\" stroke-dasharray=\"${dasharray}\" stroke-dashoffset=\"${dashoffset.toFixed(2)}\"/></svg>`;\n }\n}\n\nif (!customElements.get('aparte-progress-spinner')) {\n customElements.define('aparte-progress-spinner', AparteProgressSpinner);\n}\n","import { resolveConfig } from '../../config/config-context.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\nimport type { AparteIconName } from '../../config/icon-provider.js';\n\n/**\n * AparteIcon\n *\n * The library's icon set, reachable from MARKUP.\n *\n * It existed only in JavaScript. Core ships 25 glyphs and `setIconProvider` is sold as the\n * lever that swaps them, but the only door in was `getIcon(name)` — so a consumer writing\n * plain HTML could not place one, and the icon provider could not reach a single icon that\n * consumer wrote themselves. `<aparte-composer-action>`'s own documentation tells you to\n * put an `<svg>` inside it, which is the same gap stated as an instruction.\n *\n * That gap is why every example on the CSS-classes reference carried 265 characters of\n * path data to demonstrate a 60-character class: there was no shorter way to say \"an icon\n * goes here\" that actually drew one. `<aparte-icon name=\"copy\">` is that way.\n *\n * It routes through `getIcon`, so it is not a second icon mechanism — it is a markup\n * entrance to the one that exists. Register a provider and every `<aparte-icon>` on the\n * page follows, including the ones in your own templates.\n *\n * ONE CONSEQUENCE, stated because it is the real cost: the 25 glyph NAMES become public\n * API. `expand`, `copy`, `nextBranch` were internal identifiers; renaming one now breaks a\n * consumer's markup.\n *\n * It renders into itself and takes no children — whatever you put inside is replaced. The\n * SVG is `aria-hidden`, because an icon beside a label is decoration; when the icon IS the\n * button's only content, name the BUTTON (`aria-label`), not this.\n *\n * @element aparte-icon\n * @attr {string} name - Which glyph to draw. One of the names `setIconProvider` accepts;\n * an unknown name draws nothing rather than a broken-image box.\n *\n * @cssprop [--aparte-icon-size=16px] - Width and height. `--sm`/`--lg`/`--xl` set it.\n *\n * @example\n * <aparte-icon name=\"copy\"></aparte-icon>\n * <aparte-icon name=\"check\" class=\"aparte-icon--lg\"></aparte-icon>\n * <button class=\"aparte-btn aparte-btn--icon\" aria-label=\"Copy\">\n * <aparte-icon name=\"copy\"></aparte-icon>\n * </button>\n */\nexport class AparteIcon extends HTMLElement {\n static get observedAttributes(): string[] { return ['name']; }\n\n private _unsubscribe: (() => void) | null = null;\n\n connectedCallback(): void {\n this._render();\n /*\n * A provider registered AFTER this element mounted still reaches it. Without\n * this the icons already on the page kept the built-in glyph while everything\n * rendered later got the consumer's — the same split `<aparte-composer-send>`\n * subscribes to avoid.\n */\n this._unsubscribe = subscribeConfigChange(this, () => this._render());\n }\n\n disconnectedCallback(): void {\n this._unsubscribe?.();\n this._unsubscribe = null;\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this._render();\n }\n\n private _render(): void {\n const name = this.getAttribute('name');\n /*\n * `getIcon` is typed to the known names and falls back per name, so an unknown\n * one would land on `undefined` and print it. Drawing NOTHING is the honest\n * failure: a misspelled name leaves a gap the author can see, where the string\n * \"undefined\" in a button would read as a rendering bug in the library.\n */\n const glyph = name ? resolveConfig(this).getIconProvider()[name as AparteIconName]?.() : null;\n this.innerHTML = glyph ?? '';\n // Decoration by default — see the class note above for when to name what instead.\n this.firstElementChild?.setAttribute('aria-hidden', 'true');\n }\n}\n\nif (!customElements.get('aparte-icon')) {\n customElements.define('aparte-icon', AparteIcon);\n}\n","/**\n * <aparte-elicitation> — the default elicitation presenter.\n *\n * Registers itself as the presenter for the config governing its subtree\n * (`resolveConfig(this)`), so `requestUserInput()` from a tool handler is routed\n * here WITHOUT any window events — the typed presenter contract replaces the\n * stringly-typed `aparte-ask-user-*` events that drifted in Phase 1.\n *\n * On a request it builds the schema-appropriate panel (enum / boolean / string /\n * object) and mounts it inside the nearest `<aparte-composer>` via its\n * `showPanel` API, resolving:\n * - accept — the send button (panel submit), when all fields are complete\n * - decline — the inline \"Skip\" affordance\n * - cancel — the assistant turn was stopped/errored while pending\n *\n * Its CSS ships in `@aparte/core/styles.css` like every other component — it used\n * to inject its own <style> from here, which made it the one surface that could not\n * be themed and whose variables were missing from the generated CSS reference.\n *\n * Place anywhere inside the chat (it renders nothing itself):\n * <aparte-elicitation></aparte-elicitation>\n */\n\nimport { resolveConfig, runWithConfig, type AparteConfigAware } from '../../config/config-context.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\nimport type { AparteConfig } from '../../config/aparte-config.js';\nimport type { AparteComposer, AparteComposerPanelMode } from '../composer/aparte-composer.js';\nimport { buildElicitationPanel, type BuiltElicitationPanel } from '../../elicitation/panel.js';\nimport { buildApprovalPanel } from '../../elicitation/approval-panel.js';\nimport type { AparteElicitationRequest, AparteElicitationResult, AparteElicitationPresenter } from '../../elicitation/types.js';\nimport { AparteElicitationAbortError } from '../../elicitation/types.js';\n\n/**\n * The slice of the composer this presenter drives.\n *\n * DERIVED from the component rather than re-typed by hand. It used to be a literal\n * copy of the three signatures, which is a twin: adding a parameter to the real\n * `setPanelSubmitEnabled` left this one behind, and the compiler pointed at the\n * CALLER instead of the stale declaration. `import type` is erased, so nothing here\n * pulls the composer element into a runtime import — which is why the copy existed.\n */\ntype ComposerEl = HTMLElement & Pick<AparteComposer, 'showPanel' | 'hidePanel' | 'setPanelSubmitEnabled'>;\n\ninterface Pending {\n /** End the request without an answer — see `AparteElicitationAbortError`. */\n abort(): void;\n composer: ComposerEl;\n /**\n * Re-apply every string this request took from the locale, in place.\n *\n * ONE function, where this used to hold the panel and the skip button separately\n * so the component could relabel each. That only worked because there was one kind\n * of panel; an approval's strings live elsewhere, and a second field per kind is\n * how the two would drift.\n */\n relabel(): void;\n}\n\n/**\n * The default presenter for a request to the human. It renders nothing itself: it\n * registers as the presenter for the config governing its subtree, and mounts a panel\n * inside the nearest `<aparte-composer>` when something asks — a tool handler calling\n * `requestUserInput`, or core's own approval gate.\n *\n * It dispatches no events on purpose. A request is answered through the typed presenter\n * contract, not by listening for one; the `aparte-tool-decision` event this replaced\n * existed only because the buttons used to live in a segment renderer with no reference\n * to the client.\n *\n * It has no children to project: `connectedCallback` sets\n * `display: none`, and the composer it presents in is found by walking UP from\n * `this.parentElement` — anything placed inside this element is only hidden with it.\n * So its position matters but its content does not: mount it anywhere inside the\n * `<aparte-chat>` whose questions it should answer.\n *\n * Not the element to reach for when you want a question UI of your own shape. It is\n * one caller of `setElicitationPresenter`, and among presenters registered for the\n * same chat the most recent one wins — so an app with a framework-native presenter\n * registers that and does not mount this, and a second `<aparte-elicitation>` in one\n * chat is redundant rather than additive. It is also not usable outside a chat that\n * has an `<aparte-composer>`: with nowhere to mount a panel the request is REJECTED,\n * on purpose, rather than borrowed into another chat's composer.\n *\n * The panel it mounts is styled by `@aparte/core/styles.css` — the `--aparte-elic-*`\n * and `--aparte-approval-*` knobs below theme it. They are declared here rather than\n * on the composer because this presenter is what builds the panel; the composer only\n * lends it the slot.\n *\n * @element aparte-elicitation\n *\n * @cssprop [--aparte-elic-gap=6px] - Vertical gap between the panel's rows (message, body, tabs).\n * @cssprop [--aparte-elic-padding=6px 4px] - Padding inside the panel.\n * @cssprop [--aparte-elic-max-height=50vh] - Cap on the panel's height; its body scrolls, the panel does not.\n * @cssprop [--aparte-elic-field-gap=8px] - Space and separator padding between two fields of an object schema.\n * @cssprop [--aparte-elic-message-size=0.82rem] - Font size of the question text at the top of the panel.\n * @cssprop [--aparte-elic-title-size=0.8rem] - Font size of a field's title.\n * @cssprop [--aparte-elic-desc-size=0.76rem] - Font size of a field's description.\n * @cssprop [--aparte-elic-option-padding=7px 10px] - Padding of one enum/boolean option row.\n * @cssprop [--aparte-elic-option-radius=8px] - Corner radius of an option row.\n * @cssprop [--aparte-elic-option-title-size=0.875rem] - Font size of an option's label, and of the text inputs.\n * @cssprop [--aparte-elic-option-desc-size=0.78rem] - Font size of an option's secondary line.\n * @cssprop [--aparte-elic-control-size=15px] - Size of the radio/checkbox control in an option row.\n * @cssprop [--aparte-elic-input-radius=6px] - Corner radius of the text inputs and of the Skip button.\n * @cssprop [--aparte-elic-textarea-min-height=64px] - Minimum height of a multi-line string field.\n * @cssprop [--aparte-elic-input-size=0.85rem] - Font size of the approval panel's instruction field (the free-text note the user writes).\n * @cssprop [--aparte-elic-skip-size=0.8rem] - Font size of the corner \"Skip\" affordance (the decline).\n * @cssprop [--aparte-elic-step-size=0.78rem] - Font size of a step tab, when the schema is asked one field at a time.\n * @cssprop [--aparte-elic-step-padding=4px 2px] - Padding of a step tab.\n * @cssprop [--aparte-elic-step-gap=14px] - Gap between step tabs.\n * @cssprop [--aparte-elic-step-underline=2px] - Thickness of the current step's underline (a tab, not a pill).\n * @cssprop [--aparte-elic-dismiss-room=72px] - Space kept clear at the end of the tab rail for the corner escape.\n * @cssprop [--aparte-approval-gap=4px] - Gap between the stacked options of an approval request.\n * @cssprop [--aparte-approval-option-size=0.85rem] - Font size of an approval option button.\n * @cssprop [--aparte-approval-option-padding=8px 10px] - Padding of an approval option button.\n * @cssprop [--aparte-approval-option-radius=8px] - Corner radius of an approval option button.\n *\n * @example\n * <!-- Renders nothing by itself: it registers as the presenter for its subtree, so a\n * tool handler calling requestUserInput() gets its panel mounted in the composer. -->\n * <aparte-chat>\n * <aparte-chat-viewport></aparte-chat-viewport>\n * <aparte-elicitation></aparte-elicitation>\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n */\nexport class AparteElicitation extends HTMLElement implements AparteConfigAware {\n private _pending: Pending | null = null;\n /**\n * A turn ended — cancel the open question only if it was OUR turn.\n *\n * These two listeners sit on `window` and had no instance filter at all, so a\n * Stop (or an error) in one chat cancelled the question a DIFFERENT chat was\n * waiting on — and that chat's model was told the user had refused a question\n * the user was still looking at. Same defect as the `compact()` handler that\n * emptied both chats, which its four sibling handlers in `AparteClient` already\n * guarded against.\n *\n * The leniency rule is the composer's, deliberately: an event with no\n * `targetId` is for everyone (a single-chat app never sets one), and a chat we\n * cannot identify accepts everything rather than becoming deaf. Only a\n * MISMATCH is ignored.\n */\n private _onTurnEnd = (e: Event): void => {\n const evtTargetId = (e as CustomEvent).detail?.targetId as string | undefined;\n const own = this._pendingTargetId();\n if (evtTargetId && own && evtTargetId !== own) return;\n this._cancelPending();\n };\n\n private _unsubscribeConfig: (() => void) | null = null;\n\n connectedCallback(): void {\n this.style.display = 'none';\n // A language switch while a question is OPEN. Every other live-config\n // consumer in core got this seam; the panel could not use it because it kept\n // no reference to itself — see `Pending.panel`.\n this._unsubscribeConfig = subscribeConfigChange(this, () => this._relabelPending());\n // Become the presenter for this instance's config (or the global one).\n // `this` as the owner: it is what lets a request naming a `target` reach the\n // presenter in the SAME chat, instead of whichever one mounted last.\n resolveConfig(this).setElicitationPresenter(this._present, this);\n // Safety net: if the turn is stopped/errored while a request is open,\n // resolve it as cancelled so the client loop unblocks and the composer\n // input is restored.\n window.addEventListener('aparte-message-aborted', this._onTurnEnd);\n window.addEventListener('aparte-message-error', this._onTurnEnd);\n }\n\n /**\n * The boundary above us appeared, changed, or went away — move the\n * registration with it.\n *\n * `connectedCallback` alone is not enough and cannot be: registering is a\n * WRITE, and under all four wrappers it happens before `attachConfig` runs, so\n * it lands on the global singleton. `requestUserInput()` then resolves the\n * instance config, finds nothing, and rejects the request — the model\n * hears the user refuse a question the user never saw.\n *\n * See {@link AparteConfigAware}.\n */\n aparteConfigChanged(next: AparteConfig, previous: AparteConfig): void {\n // Withdraw OURS by name. `setElicitationPresenter(null)` cleared the whole\n // registry, so moving one chat's registration took every other mounted chat's\n // presenter down with it.\n previous.removeElicitationPresenter(this._present);\n next.setElicitationPresenter(this._present, this);\n }\n\n disconnectedCallback(): void {\n // Ours only. This used to clear the slot whenever it happened to hold our\n // presenter, which left a still-mounted sibling chat unable to ask anything for\n // the life of the page — silently, since the no-presenter warning fires once.\n resolveConfig(this).removeElicitationPresenter(this._present);\n window.removeEventListener('aparte-message-aborted', this._onTurnEnd);\n window.removeEventListener('aparte-message-error', this._onTurnEnd);\n this._unsubscribeConfig?.();\n this._unsubscribeConfig = null;\n this._cancelPending();\n }\n\n /**\n * Re-apply the open question's strings, in place.\n *\n * Two owners, and both have to move or the panel goes bilingual: the panel's own\n * defaults (`relabel`), and the Skip button, which this file builds and this file\n * therefore has to re-text. The composer's one button is a third, and it already\n * follows — `aparte-composer-send` remembers the panel state it was given.\n */\n private _relabelPending(): void {\n if (!this._pending) return;\n const cfg = resolveConfig(this);\n runWithConfig(cfg, () => this._pending!.relabel());\n }\n\n private _present: AparteElicitationPresenter = (request: AparteElicitationRequest) => {\n /*\n * No concurrency guard here any more — `AparteConfig.requestUserInput` queues,\n * so a second request arrives only once this one has settled.\n *\n * What was here answered the second request `cancel` immediately: a refusal\n * invented for a question nobody was shown. And it protected only requests\n * that came through THIS presenter, so a consumer's own presenter had nothing.\n * If two ever do overlap, `showPanel` now evicts and NOTIFIES the first, which\n * degrades to a settled request instead of a wedged one.\n */\n const composer = this._getComposer();\n // Mounted outside a chat that has a composer: there is nowhere to put the\n // panel, which is the same situation as no presenter at all.\n if (!composer) return Promise.reject(new AparteElicitationAbortError('no-presenter'));\n\n return new Promise<AparteElicitationResult>((resolve, reject) => {\n let done = false;\n /**\n * The slot this request owns, once `showPanel` has handed it over.\n *\n * `settle` can run BEFORE that — an already-aborted signal settles on the\n * spot — so it starts absent, and `hidePanel(undefined)` then closes whatever\n * is there, which is correct because nothing of ours is open yet.\n *\n * A holder rather than a `let`: `settle` reads it before `showPanel` assigns\n * it, which is exactly the shape `prefer-const` rejects, and the object says\n * \"not handed over yet\" more plainly than an unassigned binding.\n */\n const slot: { token?: symbol } = {};\n const close = (): boolean => {\n if (done) return false;\n done = true;\n this._pending = null;\n // Scoped to our own panel: finishing late must not tear down the panel\n // that replaced ours.\n composer.hidePanel(slot.token);\n return true;\n };\n const settle = (result: AparteElicitationResult): void => {\n if (close()) resolve(result);\n };\n /**\n * End it without an answer.\n *\n * A rejection rather than a third `action`, because a value is easy to\n * mistake for an answer and this one was: the approval gate read the old\n * `cancel` as a refusal and told the model the user had refused a tool they\n * had only stopped.\n */\n const fail = (reason: 'aborted' | 'no-presenter' = 'aborted'): void => {\n if (close()) reject(new AparteElicitationAbortError(reason));\n };\n\n // Caller-side cancellation (tool handler signal: timeout / turn abort).\n if (request.signal) {\n if (request.signal.aborted) { fail(); return; }\n request.signal.addEventListener('abort', () => fail(), { once: true });\n }\n\n // Built INSIDE this instance's config, so the panel's own strings come\n // from the locale of the chat that asked — `contextConfig()` reads the\n // ambient render config, and without this it would fall back to the\n // global one on a page where each chat has its own.\n const cfg = resolveConfig(this);\n\n /*\n * An APPROVAL: a decision, not a value.\n *\n * Same slot, same queue, same teardown — only what goes inside the panel\n * differs, which is the whole claim of one mechanism with two\n * presentations. The options come with the request because only the\n * requester can write them.\n *\n * There is always an exit here without touching the composer's own\n * controls: every option is a button. That matters because a panel takes\n * the send button over, so Stop is unreachable while one is open — for a\n * question the escape is the corner, for an approval it is a refusal.\n */\n if (request.kind === 'approval') {\n /*\n * The button exists only once an INSTRUCTION has been written.\n *\n * The options never route through it — a decision is its own click —\n * so with `mode: 'submit'` from the start it sat there permanently\n * disabled beside them, offering an act that did not exist. It is the\n * written text, and only that, which is the act this button already\n * means; until there is some, the panel has none for it.\n */\n const approvalMode = (): AparteComposerPanelMode => (panel.isComplete() ? 'submit' : 'none');\n const panel = runWithConfig(cfg, () =>\n buildApprovalPanel(request.message, request.options ?? [], () => {\n composer.setPanelSubmitEnabled(panel.isComplete(), approvalMode());\n }));\n panel.onSettle((answer) => settle({ action: 'accept', content: answer }));\n this._pending = { abort: () => fail(), composer, relabel: () => panel.relabel() };\n slot.token = composer.showPanel(panel.el, {\n submitEnabled: panel.isComplete(),\n mode: approvalMode(),\n onSubmit: () => { if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() }); },\n onEvict: () => fail(),\n });\n panel.focus();\n return;\n }\n // A question without a schema has nothing to collect; that is an approval,\n // and it was handled above. The fallback keeps this branch total rather\n // than throwing on a shape the type already forbids.\n const schema = request.schema ?? { type: 'string' as const };\n const panel: BuiltElicitationPanel = runWithConfig(cfg, () =>\n buildElicitationPanel(request.message, schema, () => {\n composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());\n }));\n\n // \"Skip\" → decline (MCP's decline: the user chose not to answer), in the\n // panel's CORNER. It sat beside the button that advances through the form,\n // and that adjacency read as \"skip this question\" while it declines the\n // whole request — see `dismiss` on BuiltElicitationPanel.\n const skip = document.createElement('button');\n skip.type = 'button';\n skip.className = 'aparte-btn aparte-elic-skip';\n skip.textContent = cfg.t('elicitationSkip');\n skip.addEventListener('click', () => settle({ action: 'decline' }));\n panel.dismiss.appendChild(skip);\n\n /*\n * The click that IS the answer — a single question whose options are\n * buttons. Same wiring as the approval panel's, because it is the same\n * act; the panel decides which of its shapes has it, and reports through\n * `mode()` that the composer's button has nothing to do.\n */\n panel.onSettle((content) => settle({ action: 'accept', content }));\n\n this._pending = {\n abort: () => fail(),\n composer,\n relabel: () => { panel.relabel(); skip.textContent = resolveConfig(this).t('elicitationSkip'); },\n };\n slot.token = composer.showPanel(panel.el, {\n submitEnabled: panel.canProceed(),\n mode: panel.mode(),\n /*\n * Something else took the slot — another request, a conversation switch,\n * or a turn ending. The composer tears the panel down either way; only\n * this callback can settle the promise, and without it the request hung\n * AND `_pending` stayed set, so every later question was short-circuited\n * for the life of the page. `cancel`, not `decline`: nobody declined\n * anything, the question was taken away.\n */\n onEvict: () => fail(),\n onSubmit: () => {\n // The same button advances through the form and submits at the end;\n // the panel is what knows which of the two this click is.\n if (panel.mode() === 'advance') {\n panel.proceed();\n composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());\n return;\n }\n if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() });\n },\n });\n panel.focus();\n });\n };\n\n private _cancelPending(): void {\n this._pending?.abort();\n }\n\n /**\n * The host id of the chat whose composer holds the open panel.\n *\n * Walks up from the composer rather than from `this`, because the panel lives in\n * the composer and that is what the turn belongs to. Matches the hosts\n * `aparte-chat-bubble._resolveTargetId()` matches, for the reason written there:\n * Angular's wrapper root IS the `<aparte-chat>` element, while the plain-root\n * wrappers render a `[data-aparte-chat]` div instead, so matching only the tag\n * resolves `undefined` on three wrappers out of four.\n */\n private _pendingTargetId(): string | undefined {\n let el: HTMLElement | null = this._pending?.composer ?? null;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || tag === 'aparte-chat-component' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n /**\n * The composer to present in: the nearest one in an ancestor subtree, and\n * nothing else.\n *\n * There used to be a `document.querySelector('aparte-composer')` fallback, which\n * is the \"first chat on the page\" bug this repo has now fixed in four other\n * places: on a page with two chats, an elicitation that could not find its own\n * composer mounted its panel in the OTHER chat's — so one conversation's question\n * appeared under the other conversation, and answering it resolved a tool call\n * belonging to a chat the user was not looking at.\n *\n * Returning `null` instead REJECTS the request, which is honest: nothing was\n * shown, so nothing was answered. The warning names the fix, because this is a\n * setup mistake and only the developer can correct it — the guide's own example\n * puts `<aparte-elicitation>` inside `<aparte-chat>`.\n */\n private _getComposer(): ComposerEl | null {\n let node: Element | null = this.parentElement;\n while (node) {\n const composer = node.querySelector('aparte-composer') as ComposerEl | null;\n if (composer && typeof composer.showPanel === 'function') return composer;\n // Stop AT the chat boundary. Removing the explicit\n // `document.querySelector` fallback was not enough on its own: this walk\n // reached `<body>`, and a `querySelector` from there searches the whole\n // document — so it found another chat's composer anyway, by a longer\n // route. The two-chat test caught exactly that.\n const tag = node.tagName?.toLowerCase();\n const isChatBoundary = tag === 'aparte-chat' || tag === 'aparte-chat-component' || node.hasAttribute?.('data-aparte-chat');\n if (isChatBoundary) break;\n node = node.parentElement;\n }\n console.warn(\n '[aparte-elicitation] No <aparte-composer> in this element\\'s subtree, so the request '\n + 'could not be shown, so it REJECTED and the turn halted. Nothing was told to the '\n + 'model — there is nothing true to tell it. Move <aparte-elicitation> '\n + 'inside the <aparte-chat> it belongs to. It is deliberately NOT borrowing another '\n + 'chat\\'s composer: on a page with two chats that put the question under the wrong one.',\n );\n return null;\n }\n}\n\nif (typeof customElements !== 'undefined' && !customElements.get('aparte-elicitation')) {\n customElements.define('aparte-elicitation', AparteElicitation);\n}\n","import type { AparteChatViewport } from '../viewport/aparte-chat-viewport.js';\nimport type { AparteComposer } from '../composer/aparte-composer.js';\n// Defines <aparte-elicitation>, which the default composition below writes. A tag\n// nothing has defined is an inert unknown element, so the import is the difference\n// between a presenter and a placeholder.\nimport '../elicitation/aparte-elicitation.js';\nimport { escapeAttr } from '../../utils/escape.js';\n\n/**\n * AparteChat - The Shell\n *\n * The container element for a chat. It lays out its Light DOM children as a flex\n * column: an `<aparte-chat-viewport>` takes the space left over (`flex: 1 1 auto`)\n * and scrolls, an `<aparte-composer>` keeps its own height below it. Light DOM on\n * purpose, so the page's own global CSS reaches inside.\n *\n * The presence of an `<aparte-chat-viewport>` child at connect is the exact test for\n * \"the author composed this\". Find one and the children are used as given — this\n * element moves none of them, so anything else you drop in (a header, a banner above\n * the composer) is simply another row of that column, in DOM order. Find none and\n * `innerHTML` is OVERWRITTEN with a default composition — a viewport, an\n * `<aparte-elicitation>` presenter, and a composer shell holding an input and a send\n * button, plus the two attachment primitives when `attachments` is set — so children\n * written without a viewport anywhere inside them are destroyed, that header included.\n * The test is a DESCENDANT query, so a viewport nested in a wrapper of your own still\n * counts — compose it yourself with the viewport somewhere in the tree, or leave the tag\n * empty. Angular's wrapper sets `framework-managed` instead of relying on that test,\n * because its children do not exist yet when this element upgrades; React, Vue and\n * Svelte never create this element at all, so the question does not arise for them.\n *\n * Being a component (not a bare `<div>`), it also owns behaviour a wrapper div\n * can't: with `center-empty`, it watches its own viewport and keeps the composer\n * centered as a welcome state until the first `<aparte-chat-bubble>` lands, then\n * slides to the normal layout — no external JavaScript. While centered it carries\n * `data-empty` on itself (set and cleared by that same watcher), which is the hook to\n * style the welcome state from an app's own CSS. The watcher needs a viewport somewhere\n * inside, and hand-written markup always has one because composing the default injects\n * it — so the only path where no watcher starts and `data-empty` is never set is\n * `framework-managed`, where the framework owns the subtree anyway. The stylesheet\n * centers through\n * `aparte-chat[center-empty][data-empty]` and its DIRECT viewport child, so a\n * framework-managed host that nests the viewport inside a container of its own gets\n * nothing from the attribute — the wrappers ship their own centered layout.\n *\n * It is also one of the anchors where core re-declares its derived CSS layer, so\n * overriding a master — `--aparte-primary`, a surface, a text colour — on a single\n * `<aparte-chat>` re-derives the values computed from it for that instance rather\n * than moving one button. That is per-instance theming. The literal palette is\n * deliberately not re-declared here, so a chat nested in a dark wrapper stays dark.\n *\n * Presentational only: it does NOT wire a transport/client. Attach an\n * `AparteClient`, or handle `aparte-send` yourself, as with the primitives.\n * Size the element via CSS (a height, or let it fill a sized parent).\n *\n * @element aparte-chat\n * @attr {string} placeholder - Placeholder for the composer input (default composition)\n * @attr {boolean} disabled - Disables the composer\n * @attr {boolean} center-empty - Center the composer as a welcome state until the first message, then slide to the normal layout\n * @attr {boolean} framework-managed - The wrapper's explicit hands-off signal: set it and this\n * element composes none of its own children, because the framework owns them. Read once at\n * connect (it is not observed), so it has to be in the initial markup. Angular's wrapper sets\n * it on this element — its component selector IS `aparte-chat`; React/Vue/Svelte render a\n * `[data-aparte-chat]` div and never create this element at all.\n * @attr {boolean} attachments - Add the file picker + chips strip to the default composition (opt-in: the host must consume the files — an `AparteClient` does, a hand-rolled loop must read `event.detail.files`)\n *\n * @cssprop [--aparte-chat-bottom-gap=var(--aparte-space-8, 16px)] - Space below the\n * composer, as `padding-block-end` on the shell (the same rule covers a wrapper's\n * `[data-aparte-chat]` root). The gap belongs to this element because padding applied\n * from outside would also shrink the scroll area, stopping the transcript short of the\n * edge instead of scrolling to it.\n *\n * Composing it yourself is the other form, and the container still lays it out and still\n * runs `center-empty`. It is written out here rather than as a second `@example` for a\n * mechanical reason: every element-own example is concatenated into ONE live frame on the\n * generated reference page, so a second `<aparte-chat>` there rendered as a second whole\n * chat — two empty composers with 600px of nothing between them.\n *\n * ```html\n * <aparte-chat center-empty attachments style=\"height: 320px\">\n * <aparte-chat-viewport></aparte-chat-viewport>\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n * ```\n *\n * @example\n * <!-- Left empty it fills in a viewport, an input and a send button. -->\n * <aparte-chat center-empty placeholder=\"Say something…\" style=\"height: 320px\"></aparte-chat>\n *\n * <script>\n * // Seeded so the frame shows a real exchange rather than an empty box: this\n * // example is RENDERED, not only read.\n * const chat = document.querySelector('aparte-chat');\n * chat.viewport.appendMessage({ id: 'u1', role: 'user', content: 'What is a transport?' });\n * chat.viewport.appendMessage({\n * id: 'a1',\n * role: 'assistant',\n * content: 'The object that talks to the model. Swap it and the UI does not change.',\n * });\n * </script>\n */\nexport class AparteChat extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['placeholder', 'disabled', 'center-empty', 'attachments'];\n }\n\n private _observer: MutationObserver | null = null;\n\n /**\n * True only for the composition THIS element injected. An author-provided\n * composer (or a `framework-managed` host) is never edited by the attachments\n * toggle below — those own their own markup.\n */\n private _ownsShell = false;\n\n connectedCallback(): void {\n this._render();\n this._forwardAttr('placeholder');\n this._forwardAttr('disabled');\n this._syncEmptyWatch();\n }\n\n disconnectedCallback(): void {\n this._observer?.disconnect();\n this._observer = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n if (name === 'center-empty') {\n this._syncEmptyWatch();\n return;\n }\n if (name === 'attachments') {\n this._syncAttachments();\n return;\n }\n // placeholder / disabled forward to the inner composer. An explicit removal\n // mirrors through; a never-set attribute is left alone (so a caller-provided\n // composer keeps its own).\n const composer = this.querySelector('aparte-composer');\n if (!composer) return;\n if (newValue !== null) composer.setAttribute(name, newValue);\n else composer.removeAttribute(name);\n }\n\n /** The message viewport (yours or the default), or `null` before connect. */\n get viewport(): AparteChatViewport | null {\n return this.querySelector('aparte-chat-viewport');\n }\n\n /** The composer (yours or the default), or `null` before connect. */\n get composer(): AparteComposer | null {\n return this.querySelector('aparte-composer');\n }\n\n private _render(): void {\n // A framework wrapper renders the composition itself — and its children do not\n // exist yet when this runs (the element is upgraded on insert, before the\n // framework's template renders), so the viewport check below can't see them.\n // `framework-managed` is the wrapper's explicit \"hands off\" signal: without it\n // the default composition below would be injected UNDER the framework's own.\n if (this.hasAttribute('framework-managed')) return;\n\n // Author-provided composition wins — if a viewport is already inside, use the\n // children as given and only lay them out (via CSS). Otherwise fill in a\n // default viewport + composer so the empty tag \"just works\".\n if (this.querySelector('aparte-chat-viewport')) return;\n\n // The composer's `placeholder` is read by its input via `closest()` at upgrade\n // time (no event), so it must be on the element in the initial markup.\n const placeholder = this.getAttribute('placeholder');\n const composerAttrs =\n (placeholder !== null ? ` placeholder=\"${escapeAttr(placeholder)}\"` : '') +\n (this.hasAttribute('disabled') ? ' disabled' : '');\n\n // Attachments are opt-in: the picker only makes sense when the host consumes\n // the files (an `AparteClient` inlines them per `rawFileInject`; a hand-rolled\n // loop must read `event.detail.files`). Offering it unconditionally would show\n // a button that silently drops what the user attached.\n const attachments = this.hasAttribute('attachments');\n\n /*\n * The presenter ships in the default composition, and that is a change of tier.\n *\n * It renders nothing by itself — it registers as the presenter for this subtree and\n * mounts a panel in the composer when something asks. It used to be opt-in, which\n * was right while asking the user was a plugin's business. It is not any more: the\n * BUILT-IN approval gate asks through it, so a chat without one cannot honour\n * `needsApproval` at all. An affordance core can honour end to end is on by default\n * (ratified decision #8, tier a); leaving this out would have made the gate depend\n * on a tag nobody was told to write.\n */\n this.innerHTML = `\n <aparte-chat-viewport></aparte-chat-viewport>\n <aparte-elicitation></aparte-elicitation>\n <aparte-composer${composerAttrs}>\n <div class=\"aparte-composer-shell\">\n ${attachments ? '<aparte-composer-attachments></aparte-composer-attachments>' : ''}\n <div class=\"aparte-composer-row\">\n ${attachments ? '<aparte-composer-add-attachment></aparte-composer-add-attachment>' : ''}\n <aparte-composer-input></aparte-composer-input>\n <aparte-composer-send></aparte-composer-send>\n </div>\n </div>\n </aparte-composer>\n `;\n this._ownsShell = true;\n }\n\n /**\n * Add/remove the two attachment primitives on the composition we injected, so\n * toggling the attribute after mount works like the wrappers' reactive prop\n * (there, a re-render does it). Author-provided markup is left alone.\n */\n private _syncAttachments(): void {\n if (!this._ownsShell) return;\n const composer = this.composer;\n const shell = composer?.querySelector('.aparte-composer-shell');\n const row = shell?.querySelector('.aparte-composer-row');\n if (!composer || !shell || !row) return;\n\n const strip = shell.querySelector('aparte-composer-attachments');\n const picker = row.querySelector('aparte-composer-add-attachment');\n\n if (this.hasAttribute('attachments')) {\n if (!strip) shell.insertBefore(document.createElement('aparte-composer-attachments'), row);\n if (!picker) row.insertBefore(document.createElement('aparte-composer-add-attachment'), row.firstChild);\n return;\n }\n\n strip?.remove();\n picker?.remove();\n // Files picked before the capability was withdrawn would otherwise ride on\n // the next send with nothing in the UI showing them.\n composer.clearAttachments();\n }\n\n /** Set an attribute on the inner composer only when the shell carries it. */\n private _forwardAttr(name: string): void {\n if (!this.hasAttribute(name)) return;\n this.querySelector('aparte-composer')?.setAttribute(name, this.getAttribute(name) ?? '');\n }\n\n /** Start/stop watching the viewport so `center-empty` toggles itself. */\n private _syncEmptyWatch(): void {\n this._observer?.disconnect();\n this._observer = null;\n\n if (!this.hasAttribute('center-empty')) {\n this.removeAttribute('data-empty');\n return;\n }\n\n const viewport = this.querySelector('aparte-chat-viewport');\n if (!viewport) return;\n\n this._updateEmpty();\n // A message is an <aparte-chat-bubble>; watch the viewport for the first one.\n this._observer = new MutationObserver(() => this._updateEmpty());\n this._observer.observe(viewport, { childList: true, subtree: true });\n }\n\n private _updateEmpty(): void {\n const viewport = this.querySelector('aparte-chat-viewport');\n const empty = !viewport || !viewport.querySelector('aparte-chat-bubble');\n this.toggleAttribute('data-empty', empty);\n }\n\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat')) {\n customElements.define('aparte-chat', AparteChat);\n}\n","import type {\n AparteBubbleRole,\n AparteSegment,\n AparteAttachment,\n AparteBranchNavigateEventDetail,\n AparteRetryEventDetail,\n AparteEditEventDetail,\n AparteFeedbackEventDetail,\n AparteActionEventDetail,\n AparteMessageInfoEventDetail,\n AparteUsage,\n AparteMessage,\n} from '../../types/index.js';\nimport { getSegmentRenderer, installDefaultRenderersOnce } from '../../renderers/index.js';\nimport { writeStreamedMarkdown, type AparteMarkdownStreamHost } from '../../renderers/markdown-stream.js';\nimport { AparteConfig } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig } from '../../config/config-context.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { mergeSegmentUpdate } from '../../utils/segments.js';\nimport type { AparteComposerInput } from '../composer/aparte-composer-input.js';\nimport { escapeAttr, escapeHtml } from '../../utils/escape.js';\n\n/**\n * Warn ONCE when a segment has no renderer — now only for types core has never\n * heard of, since the built-ins install themselves on first use.\n */\nlet _warnedNoRenderer = false;\nfunction warnMissingRenderer(type: string): void {\n if (_warnedNoRenderer) return;\n _warnedNoRenderer = true;\n console.warn(`[aparte] No renderer for segment \"${type}\". Register one with registerSegmentRenderer({ type: '${type}', render }) from @aparte/core — see https://apartejs.dev/guides/customization/#custom-segment-types`);\n}\n\n/**\n * What a segment renders as when no renderer claims its type.\n *\n * `AparteCustomSegment.fallback` is documented as \"Optional fallback text\n * representation\" and was read by NOTHING — the field existed, the type published it,\n * and a custom segment arriving where its renderer is not registered (a conversation\n * replayed in another app, a client that loads its views lazily, an export) showed\n * `[Unknown segment type: custom]` while carrying the sentence written for exactly that\n * moment. Found while writing the segment's own `@example`, which is the kind of dead\n * declaration documentation is good at surfacing.\n *\n * The developer warning is skipped when a fallback is present: an author who supplied\n * one has already said this can happen, and warning then is crying wolf. Without one it\n * still fires, because a missing renderer is otherwise silent.\n *\n * `textContent`, so a fallback is text and cannot carry markup — the same rule the rest\n * of the library follows for anything a model or a host can produce.\n */\nfunction unrenderedSegment(segment: { type: string; fallback?: unknown }): HTMLElement {\n const fallback = typeof segment.fallback === 'string' && segment.fallback.trim() ? segment.fallback : null;\n if (!fallback) warnMissingRenderer(segment.type);\n const el = document.createElement('div');\n el.className = fallback ? 'aparte-segment aparte-segment-fallback' : 'aparte-segment aparte-segment-unknown';\n el.textContent = fallback ?? `[Unknown segment type: ${segment.type}]`;\n return el;\n}\n\n/**\n * The renderer for `type`, installing core's built-ins the first time a segment\n * finds the registry empty of its type. `registerDefaultRenderers()` therefore\n * becomes optional rather than a call you discover by seeing\n * `[Unknown segment type: text]` on screen (it is still honoured, and\n * `AparteClient({ autoRegister: false })` still keeps the built-ins out).\n */\nfunction resolveSegmentRenderer(\n type: string,\n config: AparteConfig,\n): ReturnType<typeof getSegmentRenderer> {\n // The CONFIG is passed in, not read ambiently.\n //\n // `runWithConfig` wrapped only `render` / `setup` / `update`, so the renderer's\n // OWN work was per-instance while the question \"which renderer is this?\" was\n // answered from a module-level registry. Two chats on a page therefore shared\n // their segment renderers no matter what `config` prop the wrapper was given —\n // half of the promise those props make.\n const renderer = getSegmentRenderer(type, config);\n if (renderer) return renderer;\n installDefaultRenderersOnce(config);\n return getSegmentRenderer(type, config);\n}\n\n/**\n * Normalize a segment renderer's output to a single element. Renderers may return\n * an HTML **string** (parsed via innerHTML — the built-in renderers) or a ready\n * **HTMLElement** (used directly, so custom renderers can wire event listeners /\n * framework nodes with no innerHTML XSS surface). See {@link AparteSegmentRenderer}.\n */\nfunction segmentRenderResultToElement(result: string | HTMLElement): HTMLElement | null {\n if (result instanceof HTMLElement) return result;\n const wrapper = document.createElement('div');\n wrapper.innerHTML = result;\n return wrapper.firstElementChild as HTMLElement | null;\n}\n\n/**\n * One message: plain content or a list of rich segments, in light DOM.\n *\n * Normally created for you by `<aparte-chat-viewport>`, one per message in the store;\n * you write the tag by hand only when you drive the DOM yourself. It is ONE message\n * with one role — a transcript is the viewport's job, and a bubble is not a\n * general-purpose card.\n *\n * **Not a slot host.** `_render()` writes its own markup into the light DOM on\n * connect, so children placed inside the tag are replaced rather than projected.\n * Everything customizable is a registered hook instead of a child: the structural\n * shell (`setBubbleShellRenderer` — it must root at `.aparte-message` and carry the\n * region hooks, since every query here is null-guarded and a partial shell silently\n * loses that region), the avatar (`setAvatarProvider`), the attachment chips\n * (`setAttachmentRenderer`), the `‹1/2›` position indicator\n * (`setSiblingNavRenderer`) and the body itself (`registerSegmentRenderer`).\n *\n * Two content paths, mutually exclusive: the `content` attribute (plain text run\n * through the configured Markdown provider, then highlighted once — after streaming\n * ends, not per token) and `setSegments()` / `addSegment()`. Segments win:\n * `.aparte-content` stays hidden for as long as any exist. The painted\n * `.aparte-message-content` box hides itself when there is nothing in it, so a\n * message that is only attachments is not a coloured rectangle.\n *\n * The bubble owns no transport and no host behaviour. The action bar and the branch\n * picker only dispatch the events below; nothing here retries a turn, persists an\n * edit, opens a stats popover or switches a branch. Which buttons exist follows from\n * that: `copy` is on by default, `edit` / `retry` / `feedback` need\n * `setBubbleActions`, `info` needs both that flag and a prior `setUsage()` (a details\n * button over no numbers is a dead button), and an image attachment becomes a preview\n * button only once `setHostHandlers` declares a lightbox — undeclared it stays a\n * picture, with no role, tab stop or pointer.\n *\n * The error state is derived from the segments (an `error` segment sets `data-error`\n * on `.aparte-message`), never from a status attribute, so it behaves identically in\n * vanilla and in every wrapper.\n *\n * All seven events are declared by hand rather than left to the analyser, which\n * found six. `aparte-branch-navigate` is dispatched from the `_onBranchPickerClick`\n * arrow class field, and the auto-detection visits `ts.isMethodDeclaration` only —\n * so the one event belonging to the branch picker was the one missing from the\n * manifest, and from the generated reference, for as long as both existed.\n *\n * @element aparte-chat-bubble\n *\n * @attr {string} role - The message role. `data-role` is the styled mirror of it.\n * @attr {string} data-role - `user` / `assistant` / `system`; what the CSS keys off.\n * @attr {string} content - Plain text content, for a bubble with no segments.\n * @attr {number | string} timestamp - Epoch milliseconds OR a date string: `_updateTimestamp` accepts either and only coerces when the value is numeric.\n * @attr {string} message-id - How streaming and the action bar address this bubble.\n * @attr {boolean} streaming - Hides the action bar and shows the caret while a reply is in flight.\n * @attr {string} name - The display name in the header.\n *\n * @fires {CustomEvent<AparteActionEventDetail>} aparte-action - A custom action-bar button was pressed.\n * @fires {CustomEvent<AparteRetryEventDetail>} aparte-retry - Retry was pressed; the host forks the turn.\n * @fires {CustomEvent<AparteEditEventDetail>} aparte-edit - An edit was saved.\n * @fires {CustomEvent<AparteFeedbackEventDetail>} aparte-feedback - Thumbs up or down.\n * @fires {CustomEvent<AparteMessageInfoEventDetail>} aparte-message-info - The info affordance was pressed.\n * @fires {CustomEvent<AparteBranchNavigateEventDetail>} aparte-branch-navigate - The `‹1/2›` picker moved between sibling versions.\n * @fires {CustomEvent<AparteAttachmentPreviewEventDetail>} aparte-attachment-preview - An attached image was clicked, asking the app to open it full-size.\n *\n * @cssprop [--aparte-message-gap=12px] - Gap between the avatar column and the body (the viewport reuses it between messages).\n * @cssprop [--aparte-message-padding=16px 12px] - Padding around one message row.\n * @cssprop [--aparte-message-max-width=800px] - Width of the centred message row.\n *\n * @cssprop [--aparte-message-content-radius=14px] - Radius of the painted content box.\n * @cssprop [--aparte-message-content-padding=10px 14px] - Padding of the USER box only; the assistant's content is plain full-width prose.\n * @cssprop [--aparte-message-content-bg-user=#efe7f6] - Background of the user box.\n * @cssprop [--aparte-message-content-bg-assistant=transparent] - Background of the assistant box — transparent on purpose (AI-chat convention, not messaging).\n * @cssprop [--aparte-message-content-text-user=var(--aparte-text)] - Text colour inside the user box.\n * @cssprop [--aparte-message-content-text-assistant=var(--aparte-text)] - Text colour inside the assistant box.\n *\n * @cssprop [--aparte-avatar-size=32px] - Square size of the avatar slot.\n * @cssprop [--aparte-avatar-radius=var(--aparte-radius-avatar)] - Avatar corner radius.\n * @cssprop [--aparte-avatar-font-size=14px] - Size of the initial, for a shell that renders one (the default shell leaves the slot empty, and `.aparte-avatar:empty` hides it).\n * @cssprop [--aparte-avatar-bg-user=var(--aparte-primary)] - Avatar background, user role.\n * @cssprop [--aparte-avatar-text-user=var(--aparte-text-inverse)] - Avatar text colour, user role.\n * @cssprop [--aparte-avatar-bg-assistant=var(--aparte-surface-3)] - Avatar background, assistant role.\n * @cssprop [--aparte-avatar-text-assistant=var(--aparte-text-inverse)] - Avatar text colour, assistant role.\n * @cssprop [--aparte-avatar-image-user=none] - `background-image` for the user avatar — a logo with no AvatarProvider and no JS.\n * @cssprop [--aparte-avatar-image-assistant=none] - `background-image` for the assistant avatar.\n * @cssprop [--aparte-avatar-image-size=90%] - `background-size` for both avatar images.\n *\n * @cssprop [--aparte-name-font-size=14px] - Sender name in the header.\n * @cssprop [--aparte-name-color=var(--aparte-text)] - Sender name colour.\n * @cssprop [--aparte-timestamp-font-size=12px] - Timestamp in the header.\n * @cssprop [--aparte-timestamp-color=var(--aparte-text-muted)] - Timestamp colour.\n * @cssprop [--aparte-content-font-size=15px] - Body type size, applied to both the plain-content and the segments container.\n * @cssprop [--aparte-content-color=var(--aparte-text)] - Body text colour.\n * @cssprop [--aparte-content-line-height=var(--aparte-line-height-loose)] - Body line height.\n *\n * @cssprop [--aparte-attachments-max-height=140px] - Cap on the sent-attachment strip; past it the strip scrolls instead of growing.\n * @cssprop [--aparte-attachment-image-size=40px] - Tile size in the strip. The strip re-declares the global 72px down to 40px, since these are thumbnails inside a conversation.\n * @cssprop [--aparte-thumb-radius=var(--aparte-radius-lg)] - Attachment tile radius (shared with the composer's preview tiles).\n * @cssprop [--aparte-thumb-name-color=#ffffff] - Filename overlaid on a tile.\n * @cssprop --aparte-thumb-name-scrim - Gradient behind that filename, so it stays legible over any image.\n * @cssprop [--aparte-thumb-name-padding=14px 5px 4px] - Padding of the filename overlay.\n *\n * @cssprop [--aparte-action-bar-gap=4px] - Gap between action buttons (and between the footer's two regions).\n * @cssprop [--aparte-action-bar-btn-size=28px] - Square size of an action button; also the footer's reserved height.\n * @cssprop [--aparte-action-bar-btn-color=var(--aparte-text-muted)] - Action icon colour at rest.\n * @cssprop [--aparte-action-bar-btn-hover-bg=var(--aparte-surface-2)] - Action button hover background (the branch arrows reuse it).\n * @cssprop [--aparte-action-bar-btn-hover-color=var(--aparte-text)] - Action icon colour on hover.\n *\n * @cssprop [--aparte-branch-picker-gap=4px] - Gap between the arrows and the position label.\n * @cssprop [--aparte-branch-picker-btn-size=20px] - Square size of each arrow.\n * @cssprop [--aparte-branch-picker-btn-icon-size=16px] - Glyph size inside an arrow.\n * @cssprop [--aparte-branch-picker-btn-color=var(--aparte-text-muted)] - Arrow colour at rest.\n * @cssprop [--aparte-branch-picker-btn-hover-color=var(--aparte-text)] - Arrow colour on hover (a disabled arrow is dimmed instead).\n * @cssprop [--aparte-branch-picker-label-size=12px] - Type size of the position label.\n * @cssprop [--aparte-branch-picker-label-color=var(--aparte-text-muted)] - Colour of the position label.\n * @cssprop [--aparte-branch-picker-label-min-width=32px] - Reserved label width, so `9 / 9` growing to `10 / 12` does not shift the arrows.\n *\n * @cssprop [--aparte-waiting-height=1.5em] - Min height of the waiting region, so the first token does not jump the layout.\n * @cssprop [--aparte-waiting-dot-gap=4px] - Gap between the three waiting dots.\n * @cssprop [--aparte-status-dot-size=6px] - Diameter of a waiting dot (shared with the status indicator).\n * @cssprop [--aparte-status-color=var(--aparte-text-muted)] - Colour of the waiting dots (shared with the status indicator).\n *\n * @cssprop [--aparte-error-solid=#dc2626] - Ring drawn around the avatar while `data-error` is set. The error CARD itself belongs to the error segment renderer.\n *\n * @example\n * <!-- Rendered for you by the viewport. Written by hand only when you drive the DOM\n * yourself: `message-id` is what streaming and the action bar address it by. -->\n * <aparte-chat-bubble\n * message-id=\"a1\"\n * data-role=\"assistant\"\n * name=\"Assistant\"\n * content=\"Hello.\"\n * ></aparte-chat-bubble>\n *\n * <!-- While a reply is in flight: `streaming` hides the action bar and shows the caret. -->\n * <aparte-chat-bubble message-id=\"a2\" data-role=\"assistant\" streaming></aparte-chat-bubble>\n *\n * <!-- One reply among several. `setSiblings(count, index)` is what draws the picker, and\n * it is a METHOD, not an attribute — so a branch cannot be shown by markup alone.\n * Retry forks a sibling instead of overwriting the reply, and this is the control\n * that walks them; each press dispatches `aparte-branch-navigate` for a host to\n * answer. Kept in the example because a guide that describes branching has no other\n * way to SHOW it. -->\n * <aparte-chat-bubble\n * message-id=\"a3\"\n * data-role=\"assistant\"\n * name=\"Assistant\"\n * content=\"A second take on the same question.\"\n * ></aparte-chat-bubble>\n *\n * <script>\n * document.querySelector('aparte-chat-bubble[message-id=\"a3\"]').setSiblings(2, 0);\n * </script>\n */\nexport class AparteChatBubble extends HTMLElement {\n private _contentEl: HTMLDivElement | null = null;\n private _segmentsEl: HTMLDivElement | null = null;\n private _attachmentsEl: HTMLDivElement | null = null;\n private _actionBarEl: HTMLDivElement | null = null;\n private _branchPickerEl: HTMLDivElement | null = null;\n private _footerEl: HTMLDivElement | null = null;\n private _content = '';\n private _streaming = false;\n private _segments: AparteSegment[] = [];\n private _role: AparteBubbleRole = 'assistant';\n private _attachments: AparteAttachment[] = [];\n private _usage: AparteUsage | null = null;\n /** Cleanup returned by the avatar provider — called on disconnect/re-render. */\n private _avatarCleanup: (() => void) | null = null;\n /** Sibling count for tree-based branch navigation (set by setSiblings()) */\n private _siblingCount = 1;\n /** Sibling index for tree-based branch navigation (set by setSiblings()) */\n private _siblingIndex = 0;\n /** True while the user-message inline editor is open. */\n private _editing = false;\n /** The live inline editor (the composer's contenteditable primitive), present only while `_editing`. */\n private _editInput: AparteComposerInput | null = null;\n\n static get observedAttributes(): string[] {\n // Both `data-role` (preferred, set by Angular wrapper) and `role` (legacy\n // / direct usage) feed into the same _role state. The host element gets\n // its own `role=\"article\"` set in _render() for ARIA compliance — that\n // is filtered in attributeChangedCallback so it doesn't loop back as a\n // bubble role of \"article\".\n return ['role', 'data-role', 'content', 'timestamp', 'message-id', 'streaming', 'name'];\n }\n\n constructor() {\n super();\n }\n\n // Rebuild the action bar when the global config changes (e.g. a live skin\n // switch calling setBubbleActions / setIconProvider) so already-rendered\n // bubbles pick up the new per-role actions + icons without being re-created.\n private _onConfigChange = (e: Event): void => {\n // Only rebuild for OUR config. An instance-scoped change on another chat —\n // or a global change while we resolve to an instance — must not touch us.\n // A bare dispatch (no detail.config) always rebuilds (e.g. manual notify).\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n this._updateActionBar();\n // Everything else the locale writes. A language switch is documented as live\n // (\"mounted components re-render immediately\"), and rebuilding only the action\n // bar delivered half of it: the labels changed language while the NAME still\n // read \"You\" and the branch arrows kept their old `aria-label` — a bilingual\n // bubble, fixed only by a reload (which rebuilds the element).\n this._updateName();\n this._updateLocalizedLabels();\n this._updateWaiting();\n // An avatar provider is config too, and it was the one provider a live change\n // never reached: swap the set and every bubble already on screen kept the old\n // one. `_renderAvatar` tears down the previous mount before re-mounting, so\n // calling it again is safe, and it no-ops when no provider is registered.\n this._renderAvatar();\n this._relabelSegments();\n // The clock, too. A tag change is a formatting change, so the timestamp has to\n // be re-rendered or the language switches around a 12-hour time that stays.\n this._updateTimestamp(this.getAttribute('timestamp'));\n };\n\n /**\n * Ask every rendered segment to re-read its config-derived text.\n *\n * Not `_renderSegments()`, which wipes the container and rebuilds: that destroys a\n * mounted artifact preview, reverts a reasoning block the reader expanded by\n * clicking `<summary>` (the DOM's real state is never written back to `collapsed`),\n * resets scroll inside long terminal panes, drops focus from an Approve/Reject\n * gate, and throws away the incremental Markdown parser's buffered lookahead\n * mid-stream — for a change that added no content. It also fires container-wide\n * childList mutations, which is what the viewport's observer reads as \"scroll to\n * the bottom\".\n *\n * `relabel` is the narrow alternative, bound by the same no-child-node rule as\n * `update()`. A renderer that has no config-derived text does not implement it,\n * and this loop simply skips it.\n */\n private _relabelSegments(): void {\n if (!this._segmentsEl) return;\n for (const segment of this._segments) {\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (!renderer?.relabel) continue;\n const el = this._segmentsEl.querySelector(\n `:scope > [data-segment-id=\"${cssEscape(segment.id)}\"]`,\n ) as HTMLElement | null;\n if (!el) continue;\n runWithConfig(this._cfg, () => renderer.relabel!(el, segment));\n }\n }\n\n /**\n * Re-apply the locale strings written straight into the markup by `_render()` —\n * the accessible names a screen reader reads, which nothing else refreshes.\n */\n private _updateLocalizedLabels(): void {\n const locale = this._cfg.getLocale();\n const set = (selector: string, label: string): void => {\n this.querySelector(selector)?.setAttribute('aria-label', label);\n };\n set('.aparte-branch-prev', locale.previousResponse ?? 'Previous response');\n set('.aparte-branch-next', locale.nextResponse ?? 'Next response');\n set('.aparte-action-bar', locale.messageActions ?? 'Message actions');\n }\n\n /**\n * Config governing this bubble: the instance config of the nearest\n * `[data-aparte-host]` boundary, else the global singleton. Resolved live\n * (a single `closest()`) rather than cached — the boundary may be attached\n * AFTER this bubble mounts (AparteChatHost.bind() runs post-mount), so a\n * connect-time cache would freeze the wrong config.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n\n connectedCallback(): void {\n this._render();\n this._updateContent();\n // Populate the timestamp from the current attribute. Frameworks that set\n // attributes BEFORE the element is connected (e.g. the Svelte wrapper) fire\n // attributeChangedCallback while _render() hasn't created `.aparte-timestamp`\n // yet, so the initial time would otherwise stay blank. No-ops when the\n // attribute is absent (set later → attributeChangedCallback handles it).\n this._updateTimestamp(this.getAttribute('timestamp'));\n window.addEventListener('aparte-config-change', this._onConfigChange);\n // Delegated, so a re-render cannot lose a click on the branch arrows.\n this.addEventListener('click', this._onBranchPickerClick);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n this.removeEventListener('click', this._onBranchPickerClick);\n if (this._avatarCleanup) {\n try { this._avatarCleanup(); } catch { /* ignore */ }\n this._avatarCleanup = null;\n }\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'role':\n case 'data-role':\n // Skip the ARIA-compliance value we set ourselves in _render().\n // Real bubble roles are 'user' or 'assistant'; anything else is\n // either the 'article' we wrote for accessibility or stale.\n if (newValue === 'article') return;\n if (newValue === 'user' || newValue === 'assistant') {\n this._role = newValue as AparteBubbleRole;\n this._updateRole();\n }\n break;\n case 'content':\n this._content = newValue || '';\n // A replace, like setContent — see _resetMarkdownStream.\n this._resetMarkdownStream();\n this._updateContent();\n break;\n case 'timestamp':\n this._updateTimestamp(newValue);\n break;\n case 'streaming':\n this._updateStreaming(newValue !== null && newValue !== 'false');\n break;\n case 'name':\n this._updateName();\n break;\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public API\n // ─────────────────────────────────────────────────────────────────────────\n\n /** Append a token chunk (for streaming) */\n appendToken(chunk: string): void {\n this._content += chunk;\n this._updateContent();\n }\n\n /** Set content directly */\n setContent(content: string): void {\n this._content = content;\n this.setAttribute('content', content);\n // A REPLACE, not an append: the incremental parser tracks how many characters it has\n // already written, so leaving its state behind would make the next token's delta a\n // slice of the wrong string. A retry does exactly this — clear, then re-stream.\n this._resetMarkdownStream();\n this._updateContent();\n }\n\n /**\n * Drop the incremental Markdown parser's state.\n *\n * Only needed where `_content` is REPLACED rather than grown. `appendToken` grows it, so\n * the parser's cursor stays valid there — which is the whole point of the seam.\n */\n private _resetMarkdownStream(): void {\n const host = this as AparteMarkdownStreamHost;\n if (host._aparteSmd) host._aparteSmd.renderer.end();\n host._aparteSmd = undefined;\n }\n\n /** Get current content */\n getContent(): string {\n return this._content;\n }\n\n /** Set segments for rich content */\n setSegments(segments: AparteSegment[]): void {\n /*\n * Copy the array IN, the way `getSegments()` already copies it OUT.\n *\n * That asymmetry was the bug: the bubble defended its list on the way out and\n * adopted the caller's on the way in. `populateBubbleFromMessage` hands over\n * `message.segments` — the repository's own array — so the bubble and the model\n * ended up advancing ONE array. `appendToSegment` then wrote each chunk twice:\n * the viewport replaced the slot with `{...segment, content: old + chunk}`, the\n * bubble looked the segment up in what it thought was its own list, found that\n * replacement (chunk already in it) and appended the chunk again. Measured:\n * \"ThatThat deletesdeletes aa filefile\".\n *\n * This is the same failure 3b026bb fixed for `addSegment` — where it does not\n * happen, because the bubble pushes into a list it created itself, so the\n * viewport's replacement decouples the two immediately. A message arriving with\n * its segments already populated went around that fix, exactly as `AparteClient`\n * went around the one before it. One copy here closes the last shared array:\n * `setSegments` has a single production caller, so all three paths through\n * `populateBubbleFromMessage` are covered by this line.\n *\n * The objects stay shared, deliberately — that is the arrangement `addSegment`\n * produces and that `appendToSegment` is written for: the first write on either\n * side replaces its own slot and the two are independent from then on.\n */\n this._segments = [...segments];\n this._renderSegments();\n this._updateWaiting();\n }\n\n /** Add a segment */\n addSegment(segment: AparteSegment): void {\n this._segments.push(segment);\n this._appendSegmentEl(segment);\n this._updateWaiting();\n }\n\n /** Update a specific segment */\n updateSegment(segmentId: string, updates: Partial<AparteSegment>): void {\n const index = this._segments.findIndex(s => s.id === segmentId);\n if (index !== -1) {\n const updated = mergeSegmentUpdate(this._segments[index]!, updates);\n this._segments[index] = updated;\n this._applySegmentUpdate(segmentId, updated, updates);\n }\n }\n\n /** Append content to a segment */\n appendToSegment(segmentId: string, content: string): void {\n const segment = this._segments.find(s => s.id === segmentId);\n if (segment && 'content' in segment) {\n (segment as { content: string }).content += content;\n this._applySegmentUpdate(segmentId, segment, { content: (segment as AparteSegment & { content: string }).content });\n }\n }\n\n /** Get all segments */\n getSegments(): AparteSegment[] {\n return [...this._segments];\n }\n\n /** Remove a segment by id (e.g. to discard a transient waiting indicator) */\n /**\n * Scoped to DIRECT children on purpose.\n *\n * Segments are appended as direct children of the container, but a descendant\n * query returns the first match in document order — and sanitized model\n * markdown renders inside that same container, with `data-*` attributes\n * deliberately preserved (they are inert). So a decoy `data-segment-id` planted\n * in an earlier segment's prose used to win over the real segment element.\n *\n * Parser ids are unguessable UUIDs, but a tool segment is `tool-${toolCallId}`\n * and the MODEL chooses that id — so this was reachable, and pointing an update\n * at a decoy left a rejected tool rendering as still-running: a spoof against\n * the human-in-the-loop control.\n */\n removeSegment(segmentId: string): void {\n const index = this._segments.findIndex(s => s.id === segmentId);\n if (index !== -1) {\n this._segments.splice(index, 1);\n }\n const el = this._segmentsEl?.querySelector(`:scope > [data-segment-id=\"${cssEscape(segmentId)}\"]`);\n el?.remove();\n this._updateWaiting();\n }\n\n /** Set attachments (chips shown above message content, user role only) */\n setAttachments(attachments: AparteAttachment[]): void {\n this._attachments = attachments;\n this._updateAttachments();\n }\n\n /**\n * Set token usage + timing for this message (assistant only).\n *\n * This is the *precondition* for the info (\"i\") action, not the trigger: the\n * button appears only if the app also declared it wants it —\n * `aparteGlobalConfig.setBubbleActions({ info: true })` — because the stats popover it\n * opens (`aparte-message-info`) is the app's, and core has none. Without usage\n * there is nothing to show, so the button never renders either way.\n */\n setUsage(usage: AparteUsage | null | undefined): void {\n this._usage = usage ?? null;\n this._updateActionBar();\n }\n\n /**\n * Update the branch picker UI for tree-based navigation.\n * The viewport calls this after a branch switch or re-render.\n * Prev/Next clicks dispatch `aparte-branch-navigate` (bubbles: true) so\n * the viewport can handle the actual tree switch.\n */\n setSiblings(count: number, index: number): void {\n this._siblingCount = count;\n this._siblingIndex = index;\n this._updateBranchPicker();\n }\n\n /**\n * Atomic update for the message\n */\n updateMessage(updates: Partial<AparteMessage>): void {\n if ('role' in updates) {\n this._role = updates.role!;\n this._updateRole();\n }\n if ('content' in updates) {\n this._content = updates.content!;\n this._updateContent();\n }\n if ('segments' in updates) {\n this._segments = updates.segments!;\n this._renderSegments();\n }\n if ('timestamp' in updates) {\n this._updateTimestamp(updates.timestamp!);\n }\n if ('status' in updates) {\n const isStreaming = updates.status === 'streaming' || updates.status === 'pending';\n this._updateStreaming(isStreaming);\n }\n if ('attachments' in updates) {\n this._attachments = updates.attachments ?? [];\n this._updateAttachments();\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n private _appendSegmentEl(segment: AparteSegment): void {\n if (!this._segmentsEl) {\n console.warn(`[AparteChatBubble] _appendSegmentEl ABORT: _segmentsEl is null`);\n return;\n }\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (renderer) {\n // Renderers are plain functions with no element to resolve from — expose\n // this bubble's config as the ambient render config for the duration.\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));\n if (el) {\n this._segmentsEl.appendChild(el);\n runWithConfig(this._cfg, () => renderer.setup?.(el, segment));\n }\n } else {\n this._segmentsEl.appendChild(unrenderedSegment(segment));\n }\n if (this._contentEl) this._contentEl.style.display = 'none';\n this._reflectError();\n }\n\n private _applySegmentUpdate(segmentId: string, segment: AparteSegment, updates: Partial<AparteSegment>): void {\n const el = this._segmentsEl?.querySelector(`:scope > [data-segment-id=\"${cssEscape(segmentId)}\"]`) as HTMLElement | null;\n if (!el) {\n this._renderSegments();\n return;\n }\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (!renderer) return;\n\n if (renderer.update) {\n runWithConfig(this._cfg, () => renderer.update!(el, segment));\n } else {\n const newEl = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));\n if (newEl) {\n el.replaceWith(newEl);\n runWithConfig(this._cfg, () => renderer.setup?.(newEl, segment));\n }\n }\n\n // Handle collapsed state only when explicitly provided in the update —\n // never override a state the user set by clicking <summary>.\n if ('collapsed' in updates) {\n if ((updates as { collapsed?: boolean }).collapsed) {\n el.removeAttribute('open');\n } else {\n el.setAttribute('open', '');\n }\n }\n }\n\n private _getDisplayName(): string {\n const nameAttr = this.getAttribute('name');\n if (nameAttr) return nameAttr;\n const locale = this._cfg.getLocale();\n return this._role === 'user'\n ? (locale.roleNameUser ?? 'You')\n : (locale.roleNameAssistant ?? 'Assistant');\n }\n\n private _getAvatarInitial(): string {\n const name = this._getDisplayName();\n return name.length > 0 ? name[0]! : (this._role === 'user' ? 'U' : 'A');\n }\n\n private _render(): void {\n // Read the bubble's logical role from `data-role` (preferred — written\n // by the Angular wrapper) or the legacy `role` attribute, then set the\n // host's actual `role` attribute to a valid ARIA value. \"user\" and\n // \"assistant\" are NOT valid ARIA roles and would trigger accessibility\n // warnings in browsers / Lighthouse. The role-based styling lives on\n // inner `data-role` markers, so this swap is transparent to CSS.\n const dataRole = this.getAttribute('data-role');\n const legacyRole = this.getAttribute('role');\n const role = (dataRole && dataRole !== 'article') ? dataRole\n : (legacyRole && legacyRole !== 'article') ? legacyRole\n : 'assistant';\n this._role = role as AparteBubbleRole;\n if (this.getAttribute('role') !== 'article') {\n this.setAttribute('role', 'article');\n }\n if (!this.hasAttribute('data-role')) {\n this.setAttribute('data-role', role);\n }\n\n // Ensure we don't overwrite if already rendered (re-entrancy check)\n if (this.querySelector('.aparte-message')) return;\n\n const displayName = this._getDisplayName();\n const initial = this._getAvatarInitial();\n\n // Custom structural shell (aparteGlobalConfig.setBubbleShellRenderer). Must root at\n // .aparte-message + carry the region hooks; the queries below are null-guarded\n // so a partial shell degrades gracefully. See AparteBubbleShellRenderer.\n const shell = this._cfg.getBubbleShellRenderer?.();\n if (shell) {\n const out = runWithConfig(this._cfg, () => shell({ role: this._role, name: displayName, avatarInitial: initial }));\n if (out instanceof HTMLElement) this.replaceChildren(out);\n else this.innerHTML = out;\n } else {\n this.innerHTML = `\n <div class=\"aparte-message\" data-role=\"${escapeAttr(role)}\" role=\"article\" aria-label=\"${escapeAttr(this._getAriaLabel())}\">\n <div class=\"aparte-avatar\" data-role=\"${escapeAttr(role)}\"></div>\n <div class=\"aparte-body\">\n <div class=\"aparte-header\">\n <span class=\"aparte-name\">${escapeHtml(displayName)}</span>\n <span class=\"aparte-timestamp\"></span>\n </div>\n <div class=\"aparte-attachments\" hidden></div>\n <div class=\"aparte-message-content\">\n <div class=\"aparte-segments\"></div>\n <div class=\"aparte-content\"></div>\n <div class=\"aparte-waiting\" hidden>\n <span class=\"aparte-dots\" aria-hidden=\"true\"><span class=\"aparte-dot\"></span><span class=\"aparte-dot\"></span><span class=\"aparte-dot\"></span></span>\n <span class=\"aparte-sr-only\"></span>\n </div>\n </div>\n <div class=\"aparte-footer\">\n <div class=\"aparte-branch-picker\" hidden>\n <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev\" aria-label=\"${escapeAttr(this._cfg.getLocale().previousResponse ?? 'Previous response')}\">‹</button>\n <span class=\"aparte-branch-label\">1 / 1</span>\n <!-- The move has to be ANNOUNCED. Pressing the arrows deliberately does not\n take focus, so without a live region a screen-reader user gets the new\n branch and no indication anything changed. The visible label cannot be\n the region itself: a custom sibling-nav renderer may replace it with\n dots, which reads as nothing. No new locale key — the position is\n digits, and the buttons beside it already carry translated labels. -->\n <span class=\"aparte-sr-only aparte-branch-status\" aria-live=\"polite\"></span>\n <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next\" aria-label=\"${escapeAttr(this._cfg.getLocale().nextResponse ?? 'Next response')}\">›</button>\n </div>\n <div class=\"aparte-action-bar\" role=\"toolbar\" aria-label=\"${escapeAttr(this._cfg.getLocale().messageActions ?? 'Message actions')}\"></div>\n </div>\n </div>\n </div>\n `;\n }\n\n this._contentEl = this.querySelector('.aparte-content');\n this._segmentsEl = this.querySelector('.aparte-segments');\n this._attachmentsEl = this.querySelector('.aparte-attachments');\n this._actionBarEl = this.querySelector('.aparte-action-bar');\n this._branchPickerEl = this.querySelector('.aparte-branch-picker');\n this._footerEl = this.querySelector('.aparte-footer');\n\n this._updateActionBar();\n this._renderAvatar();\n // Re-apply the streaming state onto the freshly-built `.aparte-message`.\n // Framework wrappers create the element with its attributes already set, so\n // `streaming` arrives BEFORE this render and `_updateStreaming()` had nothing\n // to write to — leaving a pending assistant bubble without `aria-busy` and\n // with its action bar exposed (copy/retry on an empty, still-streaming reply).\n if (this._streaming) this._updateStreaming(true);\n this._updateWaiting();\n }\n\n /**\n * Show the built-in waiting indicator while this bubble is in flight and has\n * nothing to show yet — the gap between \"user sends\" and the first token, which\n * used to be a bubble with a name and an empty body.\n *\n * The dots are CSS (no per-token work, themable, honours reduced-motion); the\n * accessible name is `locale.typing`, next to the `aria-busy` the streaming state\n * already sets. A custom bubble shell without the region simply has no indicator\n * (same null-guarded degradation as the other region hooks).\n */\n private _updateWaiting(): void {\n const empty = this._segments.length === 0 && !this._content.trim();\n const waiting = this._streaming && this._role !== 'user' && empty;\n\n // The painted box is hidden when it has nothing to paint. It carries the user\n // bubble's background, padding and radius, so an empty one is a coloured\n // rectangle with nothing in it — which is exactly what a message that is ONLY\n // attachments produced: the chips render ABOVE this box, so the box had no\n // content, no segments and no dots, and still drew itself.\n //\n // `hidden` and not `style.display`, deliberately: nothing sets an explicit\n // `display` on this class, so the UA sheet's rule applies. Where a component\n // DOES set one, `[hidden]` loses — a trap this repo has already paid for.\n const box = this.querySelector('.aparte-message-content') as HTMLElement | null;\n if (box) box.hidden = empty && !waiting;\n\n const el = this.querySelector('.aparte-waiting') as HTMLElement | null;\n if (!el) return;\n el.hidden = !waiting;\n if (!waiting) return;\n const label = this._cfg.getLocale().typing;\n const sr = el.querySelector('.aparte-sr-only');\n if (sr && sr.textContent !== label) sr.textContent = label;\n }\n\n /**\n * Hand the avatar host element off to the registered AvatarProvider, if any.\n *\n * With no provider the slot is left exactly as the shell rendered it — which for\n * the default shell means EMPTY, and hidden by `.aparte-avatar:empty`. This used\n * to claim it \"falls back to the default initial rendered by `_render()`\"; there\n * is no such initial, and believing there was is what made `_updateRole` write\n * one.\n */\n private _renderAvatar(): void {\n const avatar = this.querySelector('.aparte-avatar') as HTMLElement | null;\n if (!avatar) return;\n\n // Tear down any previously-mounted live component before re-rendering.\n if (this._avatarCleanup) {\n try { this._avatarCleanup(); } catch { /* ignore */ }\n this._avatarCleanup = null;\n }\n\n const provider = this._cfg.getAvatarProvider();\n if (!provider) return; // leave the slot as the shell rendered it\n\n avatar.textContent = '';\n const cleanup = provider.render(this._role, avatar);\n if (typeof cleanup === 'function') this._avatarCleanup = cleanup;\n }\n\n private _updateRole(): void {\n const message = this.querySelector('.aparte-message');\n const avatar = this.querySelector('.aparte-avatar');\n const nameEl = this.querySelector('.aparte-name');\n\n if (message) {\n message.setAttribute('data-role', this._role);\n message.setAttribute('aria-label', this._getAriaLabel());\n }\n if (avatar) {\n avatar.setAttribute('data-role', this._role);\n // Refresh an initial that is ALREADY there; never create one — the default\n // shell renders this slot empty and the stylesheet hides it while it stays\n // empty. Same rule as `_updateName`, which is where it was actually costing\n // something; the reasoning is written out there.\n if (avatar.textContent) avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) {\n nameEl.textContent = this._getDisplayName();\n }\n // Re-render the action bar so buttons match the correct role\n // (critical when the role attribute is set after connectedCallback)\n this._updateActionBar();\n this._renderAvatar();\n }\n\n private _updateName(): void {\n const avatar = this.querySelector('.aparte-avatar') as HTMLElement | null;\n const nameEl = this.querySelector('.aparte-name');\n /*\n * Two conditions, and the second one is the fix.\n *\n * No provider: otherwise a name change would wipe a live avatar component.\n *\n * Already non-empty: the default shell renders this slot EMPTY and the\n * stylesheet hides it while it stays empty — `.aparte-avatar:empty { display:\n * none }`, with the comment \"No message avatar by default — the slot only shows\n * once an AvatarProvider (or a consumer) fills it\". Writing the initial\n * unconditionally contradicted that, and `_onConfigChange` calls this method, so\n * ANY notifying config change filled the slot: `setLocale` (a language switcher\n * is enough), `setBubbleActions`, `setIconProvider`. Avatars appeared across the\n * transcript on a click that had nothing to do with them, and undoing the click\n * did not remove them, because the text was already written.\n *\n * The guard is \"already non-empty\" rather than \"no provider\" on purpose:\n * `avatarInitial` is part of the shell contract, so a CUSTOM shell may render an\n * initial and must still see it refreshed. Empty stays empty; filled stays in\n * sync.\n */\n if (avatar && avatar.textContent && !this._cfg.getAvatarProvider()) {\n avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) nameEl.textContent = this._getDisplayName();\n }\n\n private _updateContent(): void {\n if (!this._contentEl) return;\n\n // If we have segments, don't render simple content\n if (this._segments.length > 0) {\n this._contentEl.style.display = 'none';\n this._updateWaiting();\n return;\n }\n\n this._contentEl.style.display = '';\n /*\n * The SAME incremental seam the text and thinking segment renderers use.\n *\n * This line used to be `innerHTML = renderMarkdown(this._content)` — the whole message\n * re-parsed, re-sanitised and re-inserted on every token. That is the hot path of the\n * first thing getting-started teaches (`appendMessage` / `appendToken` /\n * `completeMessage`), and it made a published promise false: `setStreamingMarkdownProvider`\n * says \"the chat bubble uses it to render the assistant message token-by-token\n * instead of re-parsing the whole string on every token\", and the plugin's own page\n * repeats it. Only the segment path honoured it. Found by a cold audit.\n *\n * With no streaming provider registered, `writeStreamedMarkdown` falls through to the\n * one-shot render — so a consumer who has not installed the plugin sees exactly what\n * they saw before.\n *\n * `runWithConfig`, because the seam reads its provider from the ambient config and this\n * bubble may be one of several with configs of their own.\n */\n runWithConfig(this._cfg, () =>\n writeStreamedMarkdown(this as AparteMarkdownStreamHost, this._contentEl!, this._content, this._streaming),\n );\n // The first token retires the waiting indicator (and a cleared content brings\n // it back, e.g. a retry that resets the bubble before re-streaming).\n this._updateWaiting();\n // The Markdown provider only emits plain <pre><code>; apply the registered\n // syntax highlighter (if any) to those blocks. Skipped while streaming —\n // re-run once on completion (see _updateStreaming) to avoid per-token churn.\n if (!this._streaming) this._highlightContentCode();\n }\n\n /**\n * Apply the registered syntax-highlight provider to the code blocks produced\n * by the Markdown provider in the simple-content path. Provider-agnostic: a\n * full-block provider (e.g. Shiki) returns `<pre>…</pre>` so we replace the\n * element; a token provider (e.g. Prism, highlight.js) returns inner HTML so\n * we fill the existing `<code>`. No-op when no highlighter is installed.\n */\n private _highlightContentCode(): void {\n if (!this._contentEl || !this._cfg.hasHighlightProvider()) return;\n this._contentEl.querySelectorAll('pre > code').forEach((codeEl) => {\n const code = codeEl.textContent ?? '';\n if (!code.trim()) return;\n const match = codeEl.className.match(/language-([\\w+#-]+)/i);\n const lang = match?.[1] ?? '';\n const pre = codeEl.parentElement;\n Promise.resolve(this._cfg.highlightCode(code, lang)).then((html) => {\n const out = (html ?? '').trim();\n if (!out || !pre || !pre.isConnected) return;\n if (/^<pre[\\s>]/i.test(out)) {\n pre.outerHTML = out; // full block (Shiki)\n } else {\n (codeEl as HTMLElement).innerHTML = out; // inner tokens (Prism, hljs)\n }\n }).catch(() => { /* keep the plain block on failure */ });\n });\n }\n\n private _renderSegments(): void {\n if (!this._segmentsEl) return;\n\n // Clear existing segments\n this._segmentsEl.innerHTML = '';\n\n for (const segment of this._segments) {\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (renderer) {\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));\n if (el) {\n this._segmentsEl.appendChild(el);\n runWithConfig(this._cfg, () => renderer.setup?.(el, segment));\n }\n } else {\n this._segmentsEl.appendChild(unrenderedSegment(segment));\n }\n }\n\n // Hide simple content when segments are present\n if (this._contentEl) {\n this._contentEl.style.display = this._segments.length > 0 ? 'none' : '';\n }\n this._reflectError();\n }\n\n /**\n * Reflect the error state on the bubble: `data-error` on `.aparte-message` while\n * an error segment is present. Derived from segments (not the message `status`\n * attribute) so it works identically in vanilla and in every wrapper — the\n * error segment flows through the reactive list in all of them. CSS themes\n * `.aparte-message[data-error]`; custom error content is via setErrorRenderer.\n */\n private _reflectError(): void {\n const message = this.querySelector('.aparte-message');\n if (!message) return;\n const hasError = this._segments.some(s => s.type === 'error');\n if (hasError) message.setAttribute('data-error', '');\n else message.removeAttribute('data-error');\n }\n\n private _updateTimestamp(value: string | number | null): void {\n const timestampEl = this.querySelector('.aparte-timestamp');\n if (!timestampEl || !value) return;\n\n try {\n const date = new Date(isNaN(Number(value)) ? value : Number(value));\n // The locale's own tag, not `undefined`. `undefined` means \"follow the\n // BROWSER\", which is why a French chat on an en-US browser still read\n // `7:32 PM` — the app had chosen a language and the clock had not heard.\n // Still `undefined` when no tag is declared: that is the documented default\n // and the behaviour every consumer has today.\n timestampEl.textContent = date.toLocaleTimeString(this._cfg.getLocale().tag || undefined, {\n hour: '2-digit',\n minute: '2-digit'\n });\n } catch {\n timestampEl.textContent = '';\n }\n }\n\n private _getAriaLabel(): string {\n const locale = this._cfg.getLocale();\n return this._role === 'user'\n ? (locale.yourMessage ?? 'Your message')\n : (locale.assistantResponse ?? 'Assistant response');\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Attachments\n // ─────────────────────────────────────────────────────────────────────────\n\n private _updateAttachments(): void {\n if (!this._attachmentsEl) return;\n\n if (this._role !== 'user' || this._attachments.length === 0) {\n this._attachmentsEl.hidden = true;\n this._attachmentsEl.innerHTML = '';\n return;\n }\n\n this._attachmentsEl.hidden = false;\n\n // Custom attachment chips (aparteGlobalConfig.setAttachmentRenderer) — one node per\n // attachment; the consumer owns markup + interactions (no default preview wiring).\n const customAttachment = this._cfg.getAttachmentRenderer?.();\n if (customAttachment) {\n this._attachmentsEl.replaceChildren();\n for (const a of this._attachments) {\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(a)));\n if (el) this._attachmentsEl.appendChild(el);\n }\n return;\n }\n\n this._attachmentsEl.innerHTML = this._attachments.map(a => {\n const name = escapeHtml(a.name);\n if (a.type.startsWith('image/')) {\n return `<div class=\"aparte-thumb aparte-thumb--image\" title=\"${name}\">`\n + `<img class=\"aparte-thumb__img\" src=\"${escapeHtml(a.url)}\" alt=\"${name}\" loading=\"lazy\" />`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }\n return `<div class=\"aparte-thumb aparte-thumb--file\" title=\"${name}\">`\n + `<span class=\"aparte-thumb__ext\">${escapeHtml(this._fileExt(a.name))}</span>`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }).join('');\n\n // Image tiles ask for a full-size preview — but the lightbox is the app's, so\n // the tile only becomes a button once the app declared it opens one. Otherwise\n // it stays a plain picture: no role, no tab stop, no pointer (see the CSS,\n // which keys the cursor off role=\"button\").\n if (!this._cfg.getHostHandlers().attachmentPreview) return;\n this._attachmentsEl.querySelectorAll('.aparte-thumb--image').forEach(tile => {\n tile.setAttribute('role', 'button');\n tile.setAttribute('tabindex', '0');\n const open = (): void => {\n const img = tile.querySelector('.aparte-thumb__img') as HTMLImageElement | null;\n if (!img) return;\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true, composed: true,\n detail: { url: img.src, name: tile.getAttribute('title') ?? '' },\n }));\n };\n tile.addEventListener('click', open);\n tile.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key !== 'Enter' && key !== ' ') return;\n e.preventDefault();\n open();\n });\n });\n }\n\n /** Uppercased file extension (≤4 chars), or 'FILE' when there is none. */\n private _fileExt(filename: string): string {\n const dot = filename.lastIndexOf('.');\n return dot > 0 ? filename.slice(dot + 1).toUpperCase().slice(0, 4) : 'FILE';\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Branch Picker\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * The branch arrows are handled by DELEGATION, on this element, bound once.\n *\n * They used to get a fresh listener each, attached by `_render()` to the buttons\n * `_render()` had just created. So a click that landed while a re-render was\n * swapping those nodes hit an element about to be discarded, and did nothing at\n * all — not late, nothing. Invisible on a fast machine; reproducible on\n * WebKit-Linux in CI, where `‹` left the picker on \"2 / 2\" and a 20-second\n * assertion watched it stay there.\n *\n * Delegation makes `_render()` irrelevant to it: the listener lives on the host,\n * which is never replaced, and `closest()` finds whichever button exists at the\n * moment of the click. It is also less work — one listener per bubble instead of\n * two per bubble per render.\n *\n * Bound in `connectedCallback` and removed in `disconnectedCallback` as a stable\n * field, because an inline arrow re-added on every re-connect is how this repo has\n * stacked listeners twice before (the viewport, and `aparte-select`).\n */\n private _onBranchPickerClick = (event: Event): void => {\n const target = event.target as HTMLElement | null;\n const button = target?.closest?.('.aparte-branch-prev, .aparte-branch-next');\n if (!button || !this.contains(button)) return;\n const direction = button.classList.contains('aparte-branch-prev') ? 'prev' : 'next';\n const messageId = this.getAttribute('message-id');\n if (!messageId) return;\n const detail: AparteBranchNavigateEventDetail = { messageId, direction };\n // Tree-based navigation: let the viewport handle the branch switch\n this.dispatchEvent(new CustomEvent<AparteBranchNavigateEventDetail>('aparte-branch-navigate', {\n bubbles: true,\n composed: true,\n detail,\n }));\n };\n\n private _updateBranchPicker(): void {\n if (!this._branchPickerEl) return;\n if (this._siblingCount <= 1 || this._role !== 'assistant') {\n this._branchPickerEl.hidden = true;\n this._syncFooterVisibility();\n return;\n }\n this._branchPickerEl.hidden = false;\n this._syncFooterVisibility();\n const label = this._branchPickerEl.querySelector('.aparte-branch-label');\n if (label) {\n // Custom position indicator (aparteGlobalConfig.setSiblingNavRenderer) — e.g. dots —\n // fills the label between the arrows; the arrows keep their behavior.\n const customNav = this._cfg.getSiblingNavRenderer?.();\n if (customNav) {\n const out = runWithConfig(this._cfg, () => customNav({ count: this._siblingCount, index: this._siblingIndex }));\n if (out instanceof HTMLElement) label.replaceChildren(out);\n else label.innerHTML = out;\n } else {\n label.textContent = `${this._siblingIndex + 1} / ${this._siblingCount}`;\n }\n }\n\n const status = this._branchPickerEl.querySelector('.aparte-branch-status');\n if (status) {\n const position = `${this._siblingIndex + 1} / ${this._siblingCount}`;\n if (status.textContent !== position) status.textContent = position;\n }\n\n const prevBtn = this._branchPickerEl.querySelector('.aparte-branch-prev') as HTMLButtonElement | null;\n const nextBtn = this._branchPickerEl.querySelector('.aparte-branch-next') as HTMLButtonElement | null;\n if (prevBtn) prevBtn.disabled = this._siblingIndex === 0;\n if (nextBtn) nextBtn.disabled = this._siblingIndex === this._siblingCount - 1;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Action Bar\n // ─────────────────────────────────────────────────────────────────────────\n\n private _updateActionBar(): void {\n if (!this._actionBarEl) return;\n // While the inline editor is open the bar shows save (✓) / cancel (✗).\n if (this._editing) {\n this._renderEditActions();\n return;\n }\n const config = this._cfg.getBubbleActions();\n const icons = this._cfg.getIconProvider();\n const locale = this._cfg.getLocale();\n const buttons: string[] = [];\n\n if (this._role === 'user') {\n if (config.user) {\n // Explicit ordered set replaces the flag defaults for user bubbles.\n for (const a of config.user) buttons.push(this._actionButtonHtml(a, icons, locale));\n } else {\n // Flag-driven set. Only `copy` is on by default — see\n // APARTE_DEFAULT_BUBBLE_ACTIONS: edit needs a host to keep the new text.\n if (config.copy) buttons.push(this._actionButtonHtml('copy', icons, locale));\n if (config.edit) buttons.push(this._actionButtonHtml('edit', icons, locale));\n }\n } else if (this._role === 'assistant') {\n if (config.assistant) {\n // Explicit ordered set replaces the flag defaults (incl. the info button).\n for (const a of config.assistant) buttons.push(this._actionButtonHtml(a, icons, locale));\n } else {\n // Flag-driven set. Only `copy` is on by default — retry, feedback and\n // info all need a host or a listener to mean anything.\n if (config.copy) buttons.push(this._actionButtonHtml('copy', icons, locale));\n if (config.retry) buttons.push(this._actionButtonHtml('retry', icons, locale));\n if (config.feedback) {\n buttons.push(this._actionButtonHtml('thumbUp', icons, locale));\n buttons.push(this._actionButtonHtml('thumbDown', icons, locale));\n }\n if (config.info) buttons.push(this._actionButtonHtml('info', icons, locale));\n }\n }\n\n this._actionBarEl.innerHTML = buttons.join('');\n\n // Custom actions registered via aparteGlobalConfig.registerAction — appended\n // after the built-ins, built as DOM (label goes to attributes, never\n // interpolated into innerHTML) so a consumer label can't inject markup.\n this._appendCustomActions(icons);\n\n // Wire up button handlers — messageId read dynamically at click time\n // so it's always correct even when Angular sets the attribute after connectedCallback\n this._actionBarEl.querySelectorAll('.aparte-action-btn').forEach(btn => {\n btn.addEventListener('click', (e) => this._handleActionClick(e as MouseEvent));\n });\n\n this._syncFooterVisibility();\n }\n\n /**\n * An empty action bar is not a bar: with every action off it was still a\n * `role=\"toolbar\"` with nothing in it (announced as such), and it still reserved\n * its fixed height plus the footer's under every bubble. So both follow their\n * contents — the footer stays as long as the branch picker or the bar has\n * something to show.\n */\n private _syncFooterVisibility(): void {\n if (this._actionBarEl) this._actionBarEl.hidden = this._actionBarEl.children.length === 0;\n if (!this._footerEl) return;\n const barEmpty = !this._actionBarEl || this._actionBarEl.hidden;\n const pickerHidden = !this._branchPickerEl || this._branchPickerEl.hidden;\n this._footerEl.hidden = barEmpty && pickerHidden;\n }\n\n /** Append the registered custom action buttons for this bubble's role. */\n private _appendCustomActions(icons: ReturnType<AparteConfig['getIconProvider']>): void {\n if (!this._actionBarEl) return;\n for (const a of this._cfg.getActions('bubble')) {\n const roles = a.bubble?.roles ?? ['user', 'assistant'];\n if (!roles.includes(this._role)) continue;\n const btn = document.createElement('button');\n btn.className = 'aparte-btn aparte-btn--icon aparte-action-btn aparte-action-custom';\n btn.dataset['action'] = `custom:${a.id}`;\n // aria-label/title via setAttribute — safe for consumer-provided strings.\n btn.setAttribute('aria-label', a.label);\n btn.setAttribute('title', a.label);\n // Icon: raw inline SVG/HTML, else an icon-provider key (trusted output).\n const fromProvider = (icons as unknown as Record<string, (() => string) | undefined>)[a.icon];\n btn.innerHTML = a.icon.startsWith('<')\n ? a.icon\n : (typeof fromProvider === 'function' ? fromProvider() : (a.iconFallback ?? ''));\n this._actionBarEl.appendChild(btn);\n }\n }\n\n /** Build the `<button>` HTML for a single named action (shared by flag + per-role rendering). */\n private _actionButtonHtml(\n action: string,\n icons: ReturnType<AparteConfig['getIconProvider']>,\n locale: ReturnType<AparteConfig['getLocale']>,\n ): string {\n switch (action) {\n case 'copy': {\n const l = locale.copy ?? 'Copy';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-copy\" data-action=\"copy\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.copy()}</button>`;\n }\n case 'edit': {\n const l = locale.edit ?? 'Edit message';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-edit\" data-action=\"edit\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.edit()}</button>`;\n }\n case 'retry': {\n const l = locale.retry ?? 'Retry';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-retry\" data-action=\"retry\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.retry()}</button>`;\n }\n case 'thumbUp': {\n const l = locale.feedbackPositive ?? 'Good response';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-pos\" data-action=\"feedback-positive\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.thumbUp()}</button>`;\n }\n case 'thumbDown': {\n const l = locale.feedbackNegative ?? 'Bad response';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-neg\" data-action=\"feedback-negative\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.thumbDown()}</button>`;\n }\n case 'info': {\n // Only when there are numbers to show: a details button over nothing is a\n // dead button. The popover itself is the app's (see `aparte-message-info`).\n if (!this._usage) return '';\n const l = locale.messageInfo ?? 'Details';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-info\" data-action=\"info\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${this._cfg.getIcon('info')}</button>`;\n }\n default:\n return '';\n }\n }\n\n /** Render the edit-mode action bar: ✓ save (green) + ✗ cancel (red). */\n private _renderEditActions(): void {\n if (!this._actionBarEl) return;\n const locale = this._cfg.getLocale();\n const saveLabel = locale.editConfirm ?? 'Save';\n const cancelLabel = locale.editCancel ?? 'Cancel';\n this._actionBarEl.innerHTML =\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--success aparte-action-btn aparte-action-edit-save\" data-action=\"edit-save\" ` +\n `aria-label=\"${escapeAttr(saveLabel)}\" title=\"${escapeAttr(saveLabel)}\">${this._cfg.getIcon('check')}</button>` +\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--danger aparte-action-btn aparte-action-edit-cancel\" data-action=\"edit-cancel\" ` +\n `aria-label=\"${escapeAttr(cancelLabel)}\" title=\"${escapeAttr(cancelLabel)}\">${this._cfg.getIcon('close')}</button>`;\n this._actionBarEl.querySelectorAll('.aparte-action-btn').forEach(btn => {\n btn.addEventListener('click', (e) => this._handleActionClick(e as MouseEvent));\n });\n // Save/cancel must show even when every action flag is off.\n this._syncFooterVisibility();\n }\n\n private _handleActionClick(e: MouseEvent): void {\n const btn = (e.currentTarget as HTMLElement);\n const action = btn.dataset['action'];\n // Read dynamically — attribute may not be set yet at render time\n const messageId = this.getAttribute('message-id');\n\n // Custom actions (aparteGlobalConfig.registerAction) emit a generic aparte-action\n // event carrying the action id — same DOM-event contract as retry/feedback.\n if (action?.startsWith('custom:') && messageId) {\n const actionId = action.slice('custom:'.length);\n const detail: AparteActionEventDetail = {\n actionId,\n zone: 'bubble',\n messageId,\n role: this._role,\n targetId: this._resolveTargetId(),\n };\n this.dispatchEvent(new CustomEvent<AparteActionEventDetail>('aparte-action', {\n bubbles: true, composed: true, detail,\n }));\n this._cfg.getActions('bubble').find(x => x.id === actionId)?.onClick?.(e);\n return;\n }\n\n switch (action) {\n case 'copy': {\n const text = this._content || this._segments.map(s => (s as { content?: string }).content ?? '').join('\\n');\n const icons = this._cfg.getIconProvider();\n const locale = this._cfg.getLocale();\n navigator.clipboard.writeText(text).then(() => {\n btn.innerHTML = icons.check();\n btn.setAttribute('data-copied', '');\n const copiedLabel = locale.copied ?? locale.copy ?? 'Copied';\n btn.setAttribute('title', copiedLabel);\n btn.setAttribute('aria-label', copiedLabel);\n setTimeout(() => {\n btn.removeAttribute('data-copied');\n btn.innerHTML = icons.copy();\n const copyLabel = locale.copy ?? 'Copy';\n btn.setAttribute('title', copyLabel);\n btn.setAttribute('aria-label', copyLabel);\n }, 2000);\n }).catch(() => {\n console.warn('[aparte] Clipboard write failed');\n });\n break;\n }\n case 'retry': {\n if (!messageId) break;\n const targetId = this._resolveTargetId();\n const detail: AparteRetryEventDetail = { messageId, targetId };\n this.dispatchEvent(new CustomEvent<AparteRetryEventDetail>('aparte-retry', {\n bubbles: true, composed: true,\n detail,\n }));\n break;\n }\n case 'edit': {\n this._enterEditMode();\n break;\n }\n case 'edit-save': {\n this._exitEditMode(true);\n break;\n }\n case 'edit-cancel': {\n this._exitEditMode(false);\n break;\n }\n case 'feedback-positive':\n case 'feedback-negative': {\n if (!messageId) break;\n const value: AparteFeedbackEventDetail['value'] = action === 'feedback-positive' ? 'positive' : 'negative';\n btn.setAttribute('data-submitted', '');\n const detail: AparteFeedbackEventDetail = { messageId, value };\n this.dispatchEvent(new CustomEvent<AparteFeedbackEventDetail>('aparte-feedback', {\n bubbles: true, composed: true,\n detail,\n }));\n break;\n }\n case 'info': {\n if (!messageId) break;\n const detail: AparteMessageInfoEventDetail = {\n messageId,\n usage: this._usage ?? undefined,\n };\n this.dispatchEvent(new CustomEvent<AparteMessageInfoEventDetail>('aparte-message-info', {\n bubbles: true, composed: true,\n detail,\n }));\n break;\n }\n }\n }\n\n /**\n * Open the inline editor for a user message. Idempotent — a second `edit`\n * click while already editing is a no-op (no stacked editors).\n *\n * The editor reuses the composer's contenteditable primitive\n * (`<aparte-composer-input>`) so editing is iso with composing: same autosize,\n * IME, paste and styling. With no `<aparte-composer>` root it runs standalone —\n * `Enter` (Shift+Enter = newline) surfaces as `aparte-composer-submit`, which we\n * treat as save; `Esc` cancels.\n */\n private _enterEditMode(): void {\n if (this._editing || !this._contentEl) return;\n this._editing = true;\n this.querySelector('.aparte-message')?.setAttribute('data-editing', '');\n\n const input = document.createElement('aparte-composer-input') as AparteComposerInput;\n input.setAttribute('placeholder', this._cfg.getLocale().edit ?? 'Edit message');\n this._editInput = input;\n\n this._contentEl.style.display = 'none';\n this._contentEl.insertAdjacentElement('afterend', input);\n // `insertAdjacentElement` upgrades + connects synchronously, so the editor is\n // ready — seed it with the current text (autosizes to fit).\n input.setValue(this._content);\n\n // Enter (via the primitive's standalone submit event) saves; Esc cancels.\n input.addEventListener('aparte-composer-submit', () => this._exitEditMode(true));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape' && !e.isComposing) {\n e.preventDefault();\n this._exitEditMode(false);\n }\n });\n\n // Swap the action bar over to ✓ / ✗.\n this._updateActionBar();\n\n input.focusEnd();\n }\n\n /**\n * Leave edit mode. When `save` is true and the text actually changed, emits\n * `aparte-edit`; otherwise restores the original message untouched. Always\n * restores the normal action bar and removes the inline editor.\n */\n private _exitEditMode(save: boolean): void {\n if (!this._editing) return;\n const newContent = this._editInput?.getValue() ?? '';\n const original = this._content;\n\n this._editInput?.remove();\n this._editInput = null;\n if (this._contentEl) this._contentEl.style.display = '';\n this.querySelector('.aparte-message')?.removeAttribute('data-editing');\n this._editing = false;\n this._updateActionBar();\n\n if (save && newContent && newContent !== original) {\n const messageId = this.getAttribute('message-id');\n if (messageId) {\n const detail: AparteEditEventDetail = {\n messageId,\n content: newContent,\n targetId: this._resolveTargetId(),\n };\n this.dispatchEvent(new CustomEvent<AparteEditEventDetail>('aparte-edit', {\n bubbles: true, composed: true,\n detail,\n }));\n }\n }\n }\n\n private _resolveTargetId(): string | undefined {\n // Walk up to the chat host element with an id. Angular's wrapper root IS the\n // `<aparte-chat>` element (its component selector); the plain-root wrappers\n // (React/Vue/Svelte) render a `<div class=\"aparte-chat-container\" data-aparte-chat\n // id=\"…\">` instead — so match `[data-aparte-chat]` too. Without this, retry/edit\n // resolved to `undefined` outside Angular and AparteClient's fallback hit the\n // bare `<aparte-chat-viewport>` (a different message store) → retry regenerated\n // into the void.\n let el: HTMLElement | null = this.parentElement;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || tag === 'aparte-chat-component' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Utilities\n // ─────────────────────────────────────────────────────────────────────────\n\n\n private _updateStreaming(streaming: boolean): void {\n const wasStreaming = this._streaming;\n this._streaming = streaming;\n const message = this.querySelector('.aparte-message');\n if (message) {\n message.setAttribute('data-streaming', String(streaming));\n if (streaming) {\n // Signal \"in progress\" to assistive tech; clearing it on completion\n // cues screen readers (via the viewport's aria-live region) to read\n // the finished response.\n message.setAttribute('aria-busy', 'true');\n message.classList.add('aparte-message-streaming');\n } else {\n message.removeAttribute('aria-busy');\n message.classList.remove('aparte-message-streaming');\n }\n }\n this._updateWaiting();\n // Streaming just finished: highlight the final content once (skipped during\n // streaming to avoid re-highlighting on every token).\n if (wasStreaming && !streaming) this._highlightContentCode();\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-bubble')) {\n customElements.define('aparte-chat-bubble', AparteChatBubble);\n}\n","import { AparteConfig } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig } from '../../config/config-context.js';\n\n/**\n * A standalone status line — a light-DOM indicator the APP owns. Nothing in core turns\n * it on: the framework host only ever flips it back OFF (on the first streamed token),\n * and the four wrappers render one inside the viewport driven by their own `isTyping`\n * prop.\n *\n * It dispatches nothing, deliberately: it reports, it does not ask.\n *\n * Use it for a state only the app knows about — \"Searching the docs…\", \"Uploading…\",\n * a queue position. It is NOT the indicator for the gap between a send and the first\n * token: that one is built into the bubble (`.aparte-waiting`, shown while `streaming`\n * is set on a non-user bubble that has nothing to display yet). Turning this element on\n * for that gap is how a page ends up showing two indicators for one wait.\n *\n * It does not project children: `_render()` writes the subtree — a\n * `.aparte-status-container` row holding an empty avatar div and an `.aparte-body` that\n * wraps `.aparte-status-content` — so markup authored between the tags does not\n * survive. That avatar div never gets contents here, and `.aparte-avatar:empty` is\n * `display: none`, so it is not a spacer: the line sits flush with the row padding\n * rather than indented under an assistant bubble's text column.\n *\n * The seam for custom contents is `setStatusRenderer`, scoped or global: the container\n * keeps owning show/hide (`data-visible`), the accessible name (`aria-label`) and its\n * `.aparte-message` row metrics whatever the renderer returns, but the pulsing dot and\n * the text node belong to the default path only.\n *\n * A `text` attribute renders the visible label and feeds the accessible name; with no\n * `text` the line is dots-only and the name falls back to the literal `Typing` — this\n * element does not read the locale. Hiding happens twice over: the host element is\n * `display: none` without `[visible]`, and `data-visible` drives the fade/translate on\n * the container.\n *\n * The config is resolved live rather than cached, so a `setStatusRenderer` call that\n * lands after this element has already upgraded still reaches it: the element\n * re-renders on `aparte-config-change`, filtered to its own config.\n *\n * The two borrowed row variables below have one scope caveat: inside a viewport\n * narrower than 520px core REASSIGNS `--aparte-message-padding` on `.aparte-message`\n * itself, so a declaration on this host element loses to it there.\n *\n * @element aparte-chat-status\n * @attr {boolean} visible - Shows or hides the indicator.\n * @attr {string} text - The line to show. Absent, the line is dots-only and the\n * accessible name falls back to the literal `Typing` (not the locale's string).\n *\n * @cssprop [--aparte-status-color=var(--aparte-text-muted)] - Colour of the label text and of the pulsing dot in the default line.\n * @cssprop [--aparte-status-font-size=13px] - Size of the visible label (italic by default) in the default line.\n * @cssprop [--aparte-status-dot-size=6px] - Diameter of the single pulsing dot in the default line.\n * @cssprop [--aparte-message-padding=16px 12px] - Padding of the row, read because the container also carries `.aparte-message` — the status line borrows a bubble's row metrics so it lines up with the transcript.\n * @cssprop [--aparte-message-max-width=800px] - Width cap of that same row.\n *\n * @example\n * <!-- The app owns this indicator: core turns it on for nobody, which is also why it\n * is the wrong tool for the wait before the first token — the bubble's built-in\n * waiting state already covers that one. -->\n * <aparte-chat-status visible text=\"Searching the docs…\"></aparte-chat-status>\n */\nexport class AparteChatStatus extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['visible', 'text'];\n }\n\n /**\n * Resolved LIVE, not cached. Caching it at connect made this element\n * permanently deaf to its own instance: `_onConfigChange` filters on\n * `detail.config !== this._cfg`, so once `_cfg` had latched the global config no\n * change for the real instance ever matched, and the filter meant to isolate\n * chats became the thing that silenced one.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n\n constructor() {\n super();\n }\n\n connectedCallback(): void {\n // Cache the resolved config (instance boundary or global fallback), like the\n // other Aparte elements — so a scoped setStatusRenderer applies here too.\n this._render();\n // Re-render on a live config change (e.g. setStatusRenderer called after this\n // element already upgraded — it self-registers on import, so a persistent\n // <aparte-chat-status> in the page mounts before any config runs). Mirrors the\n // bubble's config-change subscription.\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n private _onConfigChange = (e: Event): void => {\n // Only react to OUR config (an instance-scoped change elsewhere must not touch\n // us). A bare notify (no detail.config) always re-renders.\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n // Clear so _render's re-entrancy guard doesn't bail; visible/text are read\n // from attributes, so the shown state is preserved across the re-render.\n this.innerHTML = '';\n this._render();\n };\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'visible':\n this._updateVisibility(newValue !== null);\n break;\n case 'text':\n this._updateText(newValue);\n break;\n }\n }\n\n /**\n * Show the typing indicator\n */\n show(): void {\n this.setAttribute('visible', '');\n }\n\n /**\n * Hide the typing indicator\n */\n hide(): void {\n this.removeAttribute('visible');\n }\n\n /**\n * Toggle visibility\n */\n toggle(): void {\n if (this.hasAttribute('visible')) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n /**\n * Check if visible\n */\n isVisible(): boolean {\n return this.hasAttribute('visible');\n }\n\n private _render(): void {\n const text = this.getAttribute('text') || 'Typing';\n const visible = this.hasAttribute('visible');\n\n // Re-entrancy check\n if (this.querySelector('.aparte-status-container')) return;\n\n // Custom typing indicator (charter §6 render hook): replace the inner markup\n // while the container keeps owning show/hide (data-visible) + accessible name.\n const custom = this._cfg?.getStatusRenderer?.();\n if (custom) {\n this.innerHTML =\n `<div class=\"aparte-message aparte-status-container\" data-visible=\"${visible}\" role=\"status\" aria-live=\"polite\"></div>`;\n const container = this.querySelector('.aparte-status-container') as HTMLElement;\n // `text` set via setAttribute, never interpolated — a `\"` would break out.\n container.setAttribute('aria-label', text);\n const result = runWithConfig(this._cfg, () => custom(text));\n if (result instanceof HTMLElement) container.appendChild(result);\n else container.innerHTML = result;\n return;\n }\n\n this.innerHTML = `\n <div\n class=\"aparte-message aparte-status-container\"\n data-visible=\"${visible}\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <div class=\"aparte-avatar\" data-role=\"assistant\" style=\"visibility: hidden\"></div>\n <div class=\"aparte-body\">\n <div class=\"aparte-status-content\">\n <div class=\"aparte-dots\" aria-hidden=\"true\">\n <span class=\"aparte-dot\"></span>\n </div>\n <span class=\"aparte-status-text\"></span>\n </div>\n </div>\n </div>\n `;\n // Set the (public, attacker-controllable) `text` via setAttribute/textContent\n // rather than interpolating it into the innerHTML template — a `\"` in the\n // attribute would otherwise break out and inject arbitrary attributes.\n this.querySelector('.aparte-status-container')?.setAttribute('aria-label', text);\n // Visible text only when explicitly requested — the default stays dots-only\n // (the aria-label above always carries the accessible name).\n if (this.hasAttribute('text')) {\n const textEl = this.querySelector('.aparte-status-text');\n if (textEl) textEl.textContent = text;\n }\n }\n\n private _updateVisibility(visible: boolean): void {\n const container = this.querySelector('.aparte-status-container');\n if (container) {\n container.setAttribute('data-visible', String(visible));\n }\n }\n\n private _updateText(text: string | null): void {\n const textEl = this.querySelector('.aparte-status-text');\n const container = this.querySelector('.aparte-status-container');\n if (!container) return; // not rendered yet — _render() reads the attribute\n // Removing the attribute restores the dots-only default (empty visible\n // text); the aria-label always keeps an accessible name.\n if (textEl) textEl.textContent = text ?? '';\n container.setAttribute('aria-label', text || 'Typing');\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-status')) {\n customElements.define('aparte-chat-status', AparteChatStatus);\n}\n","import type {\n AparteMessage,\n AparteViewportConfig,\n AparteSegment,\n AparteSegmentUpdateEventDetail,\n ApartePathChangedEventDetail,\n AparteSiblingInfo,\n AparteUsage,\n} from '../../types/index.js';\nimport { resolveConfig } from '../../config/index.js';\nimport { AparteMessageRepository } from '../../runtime/message-repository.js';\nimport type { ExportedMessageRepository } from '../../runtime/message-repository.js';\nimport { populateBubbleFromMessage, type SyncableBubble } from '../bubble/bubble-sync.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { isAwaitingReply } from '../../utils/is-awaiting-reply.js';\nimport { revokeAttachmentUrls } from '../../utils/files-to-attachments.js';\nimport { uuid } from '../../utils/uuid.js';\nimport {\n stampSegmentOnInsert,\n adoptMessageSegments,\n stampSegmentOnUpdate,\n mergeSegmentUpdate,\n renumberSegments,\n openSegmentIds,\n stampSegmentActivity,\n isTerminalStatus,\n} from '../../utils/segments.js';\n\n/**\n * The transcript surface: a light-DOM container with sticky scrolling, token\n * streaming and segment-aware rendering.\n *\n * Features:\n * - Smart Scroll: Sticks to bottom when user is at bottom, stops on manual scroll up\n * - appendToken(): For simple content streaming\n * - appendToSegment(): For segment-aware streaming (thinking, code, etc.)\n *\n * Two DOM modes. By default the element builds its own scroll surface\n * (`.aparte-viewport-container`) around a `.aparte-messages-wrapper`, and creates the\n * `<aparte-chat-bubble>` elements itself (the last `max-rendered-bubbles` of the active\n * path). With `framework-managed` set it builds neither wrapper: the HOST is the scroll\n * surface, the framework owns the bubble elements, and the bottom spacer becomes additive\n * host padding instead of an element — a relocated or removed child is what desynchronises\n * a framework's view tree from the live DOM, so this mode touches neither. The one child\n * it appends in both modes is the scroll-to-bottom button, kept trailing.\n *\n * Children you write inside the element are just children: there is no shadow root and no\n * slot to target. In the default mode they are MOVED into the internal\n * `.aparte-messages-wrapper` at first render, ahead of the bottom spacer, so pre-rendered\n * `<aparte-chat-bubble>` elements land in the transcript flow. A custom element of your own\n * is relocated the same way, and if it carries `data-aparte-bubble` plus a matching\n * `message-id` it also receives the live token and segment pushes, not just a restyle.\n * Do not expect such a child to outlive the transcript, though: anything that re-renders the\n * active path (`addBranch`, `addSiblingOf`, `navigateBranch`, `importTree`) empties the\n * wrapper and rebuilds it from the repository, so only what the repository holds comes back —\n * and `clearAll()` removes `<aparte-chat-bubble>` nodes only, so a `[data-aparte-bubble]`\n * element of your own is left behind with nothing left to render. With `framework-managed`\n * set children are not relocated: they stay direct children of the host, which is itself the\n * scroll surface.\n *\n * Messages are held as a TREE (siblings, branches, an active path), which is what lets\n * a retry fork and a bubble's sibling picker navigate with no host object involved.\n *\n * What it is NOT is storage. `max-rendered-bubbles` is a DOM ceiling and never evicts\n * from the repository — the full tree and its snapshot stay complete, `exportTree()` /\n * `importTree()` hand that snapshot to whoever owns persistence, and real history\n * retention is configured on the conversation manager instead. It is not a chat either:\n * a bare viewport IS a valid `AparteClient` target, but the composer, the transport and\n * the shell layout are other elements.\n *\n * @element aparte-chat-viewport\n *\n * @attr {boolean} framework-managed - The wrapper's explicit hands-off signal: set it and this\n * element builds no wrapper of its own and relocates none of the nodes the FRAMEWORK renders\n * into it, because the framework owns them. Not \"none of its children\": core's own\n * scroll-to-bottom button is re-appended whenever it stops being last, and that path runs in\n * this mode only. All four wrappers set it.\n * @attr {number} scroll-threshold - How close to the bottom still counts as \"at the bottom\".\n * @attr {number} max-rendered-bubbles - Caps how many bubbles stay in the DOM; older ones are released.\n * @attr {number} max-messages - DEPRECATED. It used to evict messages from the model; it now\n * only caps rendered bubbles, which is what `max-rendered-bubbles` says. For real history\n * retention configure the conversation manager instead.\n *\n * @fires {CustomEvent<AparteSegmentUpdateEventDetail>} aparte-segment-update - A segment grew or settled during a stream.\n * @fires aparte-reset-done - `clearAll()` finished emptying the transcript. No detail.\n * @fires {CustomEvent<ApartePathChangedEventDetail>} aparte-path-changed - The active branch path changed, after a retry fork or a navigation.\n *\n * @cssprop [--aparte-viewport-padding=16px] - Padding around the transcript — on\n * `.aparte-messages-wrapper`, or on the host itself in framework-managed mode, where the\n * auto-scroll spacer is added on top of it. A container narrower than 520px tightens it in\n * the default mode only: that rule reassigns the variable on `.aparte-messages-wrapper`,\n * which framework-managed mode never builds.\n * @cssprop [--aparte-message-gap=12px] - Gap between consecutive bubbles in the transcript\n * column (both DOM modes). Shared: it is also the avatar-to-content gap inside a bubble.\n * @cssprop [--aparte-scrollbar-width=6px] - Width of the WebKit scrollbar on the scroll\n * surface. Firefox and the standard property use `scrollbar-width: thin` and ignore it.\n * @cssprop [--aparte-scroll-btn-size=36px] - Diameter of the scroll-to-bottom button. A\n * coarse pointer raises it to `--aparte-touch-target-size`.\n * @cssprop [--aparte-scroll-btn-shadow=0 2px 8px rgba(0, 0, 0, 0.12)] - Its shadow; the dark\n * theme sets a heavier one.\n *\n * @example\n * <!-- On its own, outside `<aparte-chat>`. Give it a height: it fills what it is given\n * and owns the scrolling inside that box, so a viewport in an auto-height parent\n * grows forever instead of scrolling. Messages are pushed in — it fetches nothing. -->\n * <aparte-chat-viewport style=\"height: 320px\"></aparte-chat-viewport>\n *\n * <script>\n * const viewport = document.querySelector('aparte-chat-viewport');\n * viewport.appendMessage({ id: 'u1', role: 'user', content: 'What is a transport?', timestamp: Date.now() });\n * viewport.appendMessage({\n * id: 'a1',\n * role: 'assistant',\n * content: 'The object that talks to the model. Swap it and the UI does not change.',\n * timestamp: Date.now(),\n * });\n * </script>\n *\n * @example\n * // Three calls are a whole streamed turn.\n * const viewport = document.querySelector('aparte-chat-viewport')!;\n *\n * viewport.appendMessage({ id: 'a1', role: 'assistant', content: '', timestamp: Date.now() });\n * for await (const chunk of tokens) viewport.appendToken('a1', chunk);\n * viewport.completeMessage('a1'); // stops the streaming caret\n */\nexport class AparteChatViewport extends HTMLElement {\n // The scroll surface: an internal `.aparte-viewport-container` div (core mode)\n // or the host element itself (framework-managed mode). HTMLElement covers both.\n private _container: HTMLElement | null = null;\n private _scrollBtn: HTMLButtonElement | null = null;\n private _bottomSpacer: HTMLDivElement | null = null;\n /**\n * In framework-managed mode there is no spacer ELEMENT (an extra child would\n * collide with the framework's own DOM reconciliation). The spacer is an\n * additive `padding-bottom` on the host, tracked here so `_recalculateSpacer`\n * can read the current value without measuring an element.\n */\n private _fwSpacerHeight = 0;\n private _spacerRafId: number | null = null;\n private _spacerFrozenUntil: number = 0;\n private _layoutTransitionMs: number = 0;\n private _repo = new AparteMessageRepository();\n private _isAutoScrollEnabled: boolean = true;\n private _scrollThreshold: number = 50;\n /** When true, the next _autoScroll() call uses smooth instead of instant, then resets. */\n private _smoothScrollOnce: boolean = false;\n /**\n * DOM render cap: the max number of `<aparte-chat-bubble>` elements kept in the\n * DOM at once (a perf ceiling for very long conversations). This NEVER evicts\n * messages from the repository — the full conversation tree and its persistence\n * snapshot stay intact; only the oldest rendered bubbles are dropped from view.\n */\n private _maxRenderedBubbles: number = 1000;\n /** One-time guard for the deprecated `maxMessages` warning. */\n private _warnedMaxMessagesDeprecation = false;\n private _resizeObserver: ResizeObserver | null = null;\n private _mutationObserver: MutationObserver | null = null;\n private _boundResetHandler: (() => void) | null = null;\n /**\n * When true, _reRenderActivePath() only dispatches aparte-path-changed without\n * touching the DOM. Set via setFrameworkManagedDOM(true) when a framework\n * (e.g. Angular) owns the bubble elements.\n */\n private _frameworkManagedDOM = false;\n\n static get observedAttributes(): string[] {\n return ['scroll-threshold', 'max-rendered-bubbles', 'max-messages'];\n }\n\n constructor() {\n super();\n this._handleScroll = this._handleScroll.bind(this);\n }\n\n connectedCallback(): void {\n // Framework wrappers set `framework-managed` DECLARATIVELY so the flag is\n // known BEFORE _render() builds the DOM. Otherwise _render()'s child\n // relocation runs at connect — before the host's setFrameworkManagedDOM()\n // call — moving the framework's bubbles into an internal wrapper and\n // breaking its reconciliation (insertBefore NotFoundError on the next\n // append). See _setupFrameworkDOM().\n if (this.hasAttribute('framework-managed')) this._frameworkManagedDOM = true;\n this._render();\n this._setupEventListeners();\n this._setupObservers();\n this._boundResetHandler = () => this.clearAll();\n window.addEventListener('aparte-reset', this._boundResetHandler);\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n /**\n * A locale switch changes the reading direction, and `dir` was applied once at\n * render — so a chat already on screen never flipped to RTL until a reload.\n * Only OUR config: an instance-scoped change elsewhere must not touch us.\n */\n private _onConfigChange = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== resolveConfig(this)) return;\n this._applyDirection();\n };\n\n /** Mirror `locale.direction` onto the scroll container. */\n private _applyDirection(): void {\n const container = this.querySelector('.aparte-viewport-container');\n if (!container) return;\n const direction = resolveConfig(this).getLocale().direction;\n if (direction) container.setAttribute('dir', direction);\n else container.removeAttribute('dir');\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n if (this._boundResetHandler) {\n window.removeEventListener('aparte-reset', this._boundResetHandler);\n this._boundResetHandler = null;\n }\n this._cleanup();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'scroll-threshold':\n this._scrollThreshold = parseInt(newValue || '50', 10);\n break;\n case 'max-rendered-bubbles':\n this._maxRenderedBubbles = parseInt(newValue || '1000', 10);\n this._pruneRenderedBubbles();\n break;\n case 'max-messages':\n // Deprecated alias. It used to evict messages from the tree\n // (destructive, silent data loss); it now only caps rendered\n // bubbles in the DOM. Use `max-rendered-bubbles` instead.\n this._warnMaxMessagesDeprecated();\n this._maxRenderedBubbles = parseInt(newValue || '1000', 10);\n this._pruneRenderedBubbles();\n break;\n }\n }\n\n /**\n * Configure viewport with options\n */\n configure(config: AparteViewportConfig): void {\n if (config.scrollThreshold !== undefined) {\n this._scrollThreshold = config.scrollThreshold;\n }\n if (config.maxRenderedBubbles !== undefined) {\n this._maxRenderedBubbles = config.maxRenderedBubbles;\n this._pruneRenderedBubbles();\n }\n if (config.maxMessages !== undefined) {\n // Deprecated alias (see attributeChangedCallback).\n this._warnMaxMessagesDeprecated();\n this._maxRenderedBubbles = config.maxMessages;\n this._pruneRenderedBubbles();\n }\n if (config.layoutTransitionMs !== undefined) {\n this._layoutTransitionMs = config.layoutTransitionMs;\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Simple Content Streaming\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Append a token chunk to a message's content (simple text streaming)\n * @param messageId - Unique identifier for the message\n * @param chunk - Token chunk to append\n */\n appendToken(messageId: string, chunk: string): void {\n const message = this._getOrCreateMessage(messageId);\n\n // Append to simple content\n message.content = (message.content || '') + chunk;\n\n // Notify bubble\n this._notifyBubble(messageId, 'appendToken', chunk);\n this._autoScroll();\n this._scheduleSpacerUpdate();\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Segment-Aware Streaming\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Append content to a specific segment within a message\n * @param messageId - Message containing the segment\n * @param segmentId - Target segment ID\n * @param chunk - Content to append\n */\n appendToSegment(messageId: string, segmentId: string, chunk: string): void {\n const message = this._getOrCreateMessage(messageId);\n\n // Find or create segment\n if (!message.segments) {\n message.segments = [];\n }\n\n // REPLACE the segment, never mutate it in place. The bubble holds the very\n // same object — `addSegment` handed one object to the repo and to the bubble —\n // and it appends this chunk itself (see `_notifyBubble` below). Mutating here\n // made the two writes land on one object, so every chunk appeared twice, in\n // the model AND on screen (\"BonjourBonjour le le monde\"). Each view now owns\n // the value it advances.\n const index = message.segments.findIndex(s => s.id === segmentId);\n const segment = index === -1 ? undefined : message.segments[index];\n if (segment && 'content' in segment) {\n message.segments[index] = {\n ...segment,\n content: (segment as { content: string }).content + chunk,\n // Content arriving IS the segment's activity, so this is what\n // `endedAt` measures. Without it a thinking block's end would be\n // whenever someone happened to notice it had stopped — the end of\n // the turn, or the start of the next segment — and both of those\n // silently fold the waiting that followed into the duration.\n ...stampSegmentActivity(segment),\n } as AparteSegment;\n }\n\n // Dispatch segment update event\n this.dispatchEvent(new CustomEvent<AparteSegmentUpdateEventDetail>('aparte-segment-update', {\n bubbles: true,\n composed: true,\n detail: { messageId, segmentId, content: chunk, append: true }\n }));\n\n // Notify bubble\n this._notifyBubble(messageId, 'appendToSegment', chunk, segmentId);\n this._autoScroll();\n }\n\n /**\n * The active (head) message id — the target of `AparteClient`'s 1-argument\n * streaming convention (`addSegment(segment)`, `updateSegment(segmentId,\n * updates)`, …) which operates on \"the current message\". Lets a bare\n * `<aparte-chat-viewport>` be a valid `AparteClient` target, exactly like a\n * framework wrapper's host element.\n */\n private _activeMessageId(): string | null {\n // The head, UNLESS a different message is the one actually streaming.\n //\n // The 1-argument convention means \"operate on the message being streamed\",\n // and this resolved it as \"the head\" — but `appendMessage` always moves the\n // head (the repository advances it to any new child). So any message appended\n // mid-stream re-pointed the rest of the reply: measured with the real element,\n // segment two of message A landed on message B, and `updateSegment` for a\n // segment that genuinely lives on A became a silent no-op.\n //\n // `AparteChatHost` has had `_isOrphan` for exactly this, which is why the\n // framework wrappers were protected and the raw viewport — the documented\n // vanilla quick start — was not. Refusing, like the host does, rather than\n // routing: losing the tail is visible, writing it onto someone else's message\n // is not.\n const head = this._repo.headId;\n const streaming = this._streamingMessageId();\n if (streaming !== null && streaming !== head) return null;\n return head;\n }\n\n /** The id of the message currently streaming, if any. */\n private _streamingMessageId(): string | null {\n for (const message of this._repo.getMessages()) {\n if ((message as { isStreaming?: boolean }).isStreaming) return message.id;\n }\n return null;\n }\n\n /**\n * Add a new segment. Two calling conventions are accepted:\n * - `addSegment(segment)` — AparteClient's 1-arg \"operate on the current\n * (head) message\" convention (also what a wrapper host installs);\n * - `addSegment(messageId, segment)` — explicit standalone form.\n * The first argument's type disambiguates (string = messageId, object =\n * segment), so a raw viewport driven by `AparteClient` no longer drops text\n * (the args used to bind one position short, creating a phantom message).\n */\n addSegment(segment: AparteSegment): void;\n addSegment(messageId: string, segment: AparteSegment): void;\n addSegment(messageIdOrSegment: string | AparteSegment, maybeSegment?: AparteSegment): void {\n const messageId = typeof messageIdOrSegment === 'string' ? messageIdOrSegment : this._activeMessageId();\n const segment = typeof messageIdOrSegment === 'string' ? maybeSegment : messageIdOrSegment;\n if (!messageId || !segment) return;\n\n const message = this._getOrCreateMessage(messageId);\n if (!message.segments) {\n message.segments = [];\n }\n // Identity and start time land BEFORE anyone sees the object: the repo and\n // the bubble are handed the same segment, so a later stamp would leave one\n // of the two holding an unstamped copy. This is one of exactly two places\n // that writes those fields (`aparte-chat-host` is the other) — see\n // `utils/segments.ts` for why it is not the parser.\n const stamped = stampSegmentOnInsert(\n message.segments, segment, messageId,\n // THIS chat's defaults, not the page's: two chats on one page can be\n // configured differently, and the config seam is per instance.\n resolveConfig(this).getSegmentDefaults(segment.type),\n );\n message.segments.push(stamped);\n\n // Notify bubble to render the new segment\n this._notifyBubble(messageId, 'addSegment', stamped);\n this._autoScroll();\n }\n\n /**\n * Update a segment. `updateSegment(segmentId, updates)` (1-arg client\n * convention → current message) or `updateSegment(messageId, segmentId,\n * updates)` (explicit). Disambiguated by arity: the 3rd arg is absent and\n * the 2nd is the `updates` object in the 1-arg form.\n */\n updateSegment(segmentId: string, updates: Partial<AparteSegment>): void;\n updateSegment(messageId: string, segmentId: string, updates: Partial<AparteSegment>): void;\n updateSegment(a: string, b: string | Partial<AparteSegment>, c?: Partial<AparteSegment>): void {\n const clientForm = c === undefined && typeof b === 'object';\n const messageId = clientForm ? this._activeMessageId() : a;\n const segmentId = clientForm ? a : (b as string);\n const updates = clientForm ? (b as Partial<AparteSegment>) : (c as Partial<AparteSegment>);\n if (!messageId) return;\n\n const message = this._repo.getMessageById(messageId);\n if (!message?.segments) return;\n\n const segmentIndex = message.segments.findIndex(s => s.id === segmentId);\n if (segmentIndex !== -1) {\n const current = message.segments[segmentIndex]!;\n // An update that settles the segment carries its `endedAt`. Stamped\n // here rather than at each call site, so `completeSegment`, a tool\n // resolution and an app's own `updateSegment` all measure alike.\n const stamped = stampSegmentOnUpdate(current, updates);\n message.segments[segmentIndex] = mergeSegmentUpdate(current, stamped);\n\n this._notifyBubble(messageId, 'updateSegment', { segmentId, updates: stamped });\n }\n }\n\n /**\n * Remove a segment. `removeSegment(segmentId)` (1-arg client convention →\n * current message) or `removeSegment(messageId, segmentId)` (explicit).\n */\n removeSegment(segmentId: string): void;\n removeSegment(messageId: string, segmentId: string): void;\n removeSegment(a: string, b?: string): void {\n const clientForm = b === undefined;\n const messageId = clientForm ? this._activeMessageId() : a;\n const segmentId = clientForm ? a : b;\n if (!messageId || !segmentId) return;\n\n const message = this._repo.getMessageById(messageId);\n if (message?.segments) {\n const idx = message.segments.findIndex(s => s.id === segmentId);\n if (idx !== -1) {\n message.segments.splice(idx, 1);\n // `index` is a position, so a removal has to close the gap it left.\n renumberSegments(message.segments);\n }\n }\n this._notifyBubble(messageId, 'removeSegment', segmentId);\n }\n\n /**\n * Start a new streaming segment (e.g., thinking or code block)\n * Creates the segment and marks it as streaming\n */\n startSegment(messageId: string, segment: AparteSegment): void {\n const streamingSegment = { ...segment, isStreaming: true };\n this.addSegment(messageId, streamingSegment);\n }\n\n /**\n * Complete a streaming segment\n */\n completeSegment(messageId: string, segmentId: string): void {\n this.updateSegment(messageId, segmentId, { isStreaming: false });\n }\n\n /**\n * Persist token usage on a message and propagate to the live bubble, which is\n * what allows the info (\"i\") action to render — provided the app declared it\n * with `aparteGlobalConfig.setBubbleActions({ info: true })`; it is off by default,\n * since the popover it opens belongs to the app.\n */\n setUsage(messageId: string, usage: AparteUsage): void {\n const message = this._repo.getMessageById(messageId);\n if (message) message.usage = usage;\n this._notifyBubble(messageId, 'setUsage', usage);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Message Management\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Mark a message as finished streaming\n */\n completeMessage(messageId: string): void {\n const message = this._repo.getMessageById(messageId);\n if (message) {\n message.isStreaming = false;\n message.status = 'completed';\n\n // The message's end IS its segments' end: nothing in the stream says a\n // thinking block is over. Routed through `updateSegment` rather than\n // written onto the objects, so the bubble is told as well — a silent\n // mutation stamped the model and left the renderer thinking it was still\n // streaming, which is exactly what a browser run showed.\n this._settleSegments(messageId, message);\n\n this._notifyBubble(messageId, 'complete', { status: 'completed' });\n this._recalculateSpacer();\n }\n }\n\n /**\n * Close a finished message's still-open segments, one `updateSegment` each.\n *\n * Deliberately NOT a loop that writes `isStreaming` onto the objects: that path\n * stamps the model and tells the bubble nothing, so a renderer never learns its\n * segment settled — no `endedAt` in the rendered label, and no final Markdown\n * flush either. Going through `updateSegment` reuses the one path that does\n * both.\n */\n private _settleSegments(messageId: string, message: AparteMessage): void {\n if (!message.segments) return;\n for (const id of openSegmentIds(message.segments)) {\n this.updateSegment(messageId, id, { isStreaming: false });\n }\n }\n\n /**\n * Atomic update for a message by ID\n * Supports updating content, status, segments, and other metadata\n */\n updateMessage(messageId: string, updates: Partial<AparteMessage>): void {\n const message = this._repo.getMessageById(messageId);\n if (!message) return;\n\n // Apply updates to internal state\n Object.assign(message, updates);\n\n // Map AparteStatus to isStreaming for legacy bubble support\n if (updates.status) {\n message.isStreaming = updates.status === 'streaming' || updates.status === 'pending';\n // …and close the segments, because THIS is the path a completed turn\n // takes: both agent loops report the end with\n // `updateMessage({ status: 'completed' })`, and `completeMessage()` is\n // called by nobody. Without this a thinking segment kept `isStreaming`\n // unset forever and never recorded an `endedAt` — the duration only\n // worked for tool calls, which settle by their own status.\n if (isTerminalStatus(updates.status)) this._settleSegments(messageId, message);\n }\n\n // Notify bubble\n this._notifyBubble(messageId, 'update', updates);\n this._autoScroll();\n }\n\n /**\n * Add a complete message to the message registry.\n *\n * @remarks\n * **Framework-managed DOM only.** Records the message in the tree but does NOT\n * paint a bubble on its own (a framework wrapper reconciles the DOM from the\n * list). For standalone / vanilla usage call {@link appendMessage} instead,\n * which both records the message and creates its bubble element.\n */\n addMessage(message: AparteMessage): void {\n // Adopted, not stamped: this writes straight to the repository, so it is the\n // caller handing over a message they already hold rather than a turn starting.\n this._repo.addOrUpdateMessage(this._repo.headId, adoptMessageSegments({ ...message }));\n this._pruneRenderedBubbles();\n this._autoScroll();\n }\n\n /**\n * Append a new message and create its bubble in the DOM.\n * Implements the same contract as the Angular wrapper's appendMessage(),\n * making aparte-chat-viewport a fully standalone target for aparte-client.\n * When `_frameworkManagedDOM` is true, only the internal repo is updated —\n * the framework owns the DOM and will create the bubble element itself.\n */\n appendMessage(message: AparteMessage, options?: { historical?: boolean }): void {\n /*\n * A message may arrive with its segments already populated, and the two reasons\n * are not the same act: an app injecting a prefix or the client's own error\n * fallback is producing something NOW, while `setMessages` is handing back\n * something that happened. Both used to take the live path, so reloading a\n * three-week-old conversation stamped every one of its segments with `Date.now()`.\n *\n * Provenance is a parameter and not a guess. \"Arrived with its segments\" cannot\n * mean \"historical\" — `AparteClient` appends a message with a ready-made error\n * segment live, and a consumer streaming into a seeded segment is doing the same\n * thing. Defaulting to live keeps every existing caller's behaviour.\n *\n * Either way the segments go through a seam and into a NEW array, so `index`\n * follows the position and the caller's array is not retained.\n */\n const stored: AparteMessage = options?.historical\n ? adoptMessageSegments(message)\n : message.segments?.length\n ? { ...message, segments: message.segments.reduce<AparteSegment[]>(\n (acc, segment) => {\n acc.push(stampSegmentOnInsert(\n acc, segment, message.id,\n resolveConfig(this).getSegmentDefaults(segment.type),\n ));\n return acc;\n },\n [],\n ) }\n : { ...message };\n this._repo.addOrUpdateMessage(this._repo.headId, stored);\n if (!this._frameworkManagedDOM) {\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (wrapper) {\n const bubble = document.createElement('aparte-chat-bubble') as HTMLElement;\n bubble.setAttribute('message-id', message.id);\n bubble.setAttribute('role', message.role);\n if (message.timestamp) bubble.setAttribute('timestamp', String(message.timestamp));\n if (message.content) bubble.setAttribute('content', message.content);\n // Also true for an empty assistant message with no status: an\n // imperative \"the reply is coming\" shell, which otherwise rendered\n // as a finished answer (action bar and all) before a single token.\n if (isAwaitingReply(message)) {\n bubble.setAttribute('streaming', '');\n }\n // Insert before spacer so spacer stays last\n if (this._bottomSpacer && this._bottomSpacer.parentNode === wrapper) {\n wrapper.insertBefore(bubble, this._bottomSpacer);\n } else {\n wrapper.appendChild(bubble);\n }\n // Attributes alone can't carry segments / attachments / usage —\n // push them through the same helper the full render path uses,\n // or an imperatively appended message renders text-only.\n //\n // `stored`, not `message`: the bubble has to see the STAMPED segments\n // the repository holds. Handed the caller's object it rendered ones\n // with no `index` or `startedAt`, so the same segment was stamped in\n // the model and bare on screen — and an app reading them back off the\n // bubble got the bare ones.\n populateBubbleFromMessage(bubble as unknown as SyncableBubble, stored);\n }\n }\n this._pruneRenderedBubbles();\n this._recalculateSpacer();\n // User sending always anchors to bottom regardless of scroll position.\n if (message.role === 'user') {\n this._isAutoScrollEnabled = true;\n // Smooth scroll for user-initiated sends. Streaming auto-scroll stays\n // instant (via _autoScroll) so it can keep up with rapid token bursts.\n requestAnimationFrame(() => this._smoothScrollToBottom());\n } else {\n this._autoScroll();\n }\n }\n\n /**\n * Update the last message content, optionally appending.\n * Implements the same contract as the Angular wrapper's updateLastMessage(),'\n * making aparte-chat-viewport a fully standalone streaming target for aparte-client.\n */\n updateLastMessage(content: string, options?: { append?: boolean }): void {\n const lastId = this._repo.headId;\n if (!lastId) return;\n if (options?.append) {\n this.appendToken(lastId, content);\n } else {\n const message = this._repo.getMessageById(lastId);\n if (message) message.content = content;\n this._notifyBubble(lastId, 'appendToken', content);\n }\n }\n\n /**\n * Add a new sibling branch to an assistant message (retry flow).\n * Creates a new empty assistant message as a sibling of `messageId`\n * under the same parent, switches the active branch to it, and\n * re-renders the active path.\n * @returns The index of the new branch in the siblings array, or 0 on failure.\n */\n addBranch(messageId: string): number {\n const meta = this._repo.getMessage(messageId);\n if (!meta) return 0;\n\n const newMsg: AparteMessage = {\n id: uuid(),\n role: 'assistant',\n content: '',\n status: 'pending',\n timestamp: Date.now(),\n };\n this._repo.addOrUpdateMessage(meta.parentId, newMsg);\n this._repo.switchToBranch(newMsg.id);\n this._reRenderActivePath();\n\n const siblings = this._repo.getBranches(newMsg.id);\n return siblings.indexOf(newMsg.id);\n }\n\n /**\n * Add a new message relative to `existingId`, switch to it, and re-render.\n *\n * Role-aware semantics:\n * - existingId is an **assistant** message → create a sibling (same parent),\n * so the active path replaces the old response with the new one.\n * - existingId is a **user** message → create a child of that message,\n * so the user message stays on the active path and the new response follows it.\n *\n * Returns the new message's ID, or null if `existingId` is not found.\n */\n addSiblingOf(existingId: string, newMessage: AparteMessage): string | null {\n const meta = this._repo.getMessage(existingId);\n if (!meta) return null;\n\n // User messages: new response is a child (keep user on active path).\n // Assistant messages: new response is a sibling (replace old response).\n const parentId = meta.message.role === 'user'\n ? existingId\n : meta.parentId;\n this._repo.addOrUpdateMessage(parentId, { ...newMessage });\n this._repo.switchToBranch(newMessage.id);\n this._reRenderActivePath();\n return newMessage.id;\n }\n\n /**\n * Navigate to the previous or next sibling branch of a message.\n * Triggers a full re-render of the active path.\n */\n navigateBranch(messageId: string, direction: 'prev' | 'next'): void {\n const siblings = this._repo.getBranches(messageId);\n const currentIdx = siblings.indexOf(messageId);\n if (currentIdx === -1) return;\n\n const targetIdx = direction === 'prev' ? currentIdx - 1 : currentIdx + 1;\n if (targetIdx < 0 || targetIdx >= siblings.length) return;\n\n // Branch navigation is a deliberate user action, so it must not yank a user\n // who is reading mid-transcript: auto-scroll goes off and neither the spacer\n // recalculation nor the MutationObserver callback will scroll them away.\n //\n // But if they were already AT the bottom, staying there IS the expected\n // behaviour — and switching auto-follow off there is what left the\n // scroll-to-bottom button offering to scroll nowhere (bonaparte, React). It\n // also protects the swap itself: a rebuild's height flickers (measured on\n // React: 1730 → 1934 → 1730px as the new bubble renders and settles), so a\n // reader pinned to the bottom would drift up by whatever the flicker was.\n this._isAutoScrollEnabled = this._isAtBottom();\n this._updateScrollButton();\n\n this._repo.switchToBranch(siblings[targetIdx]!);\n this._reRenderActivePath();\n }\n\n /**\n * Remove ALL responses to a user message (every child branch) and set head\n * back to `userMessageId`. Cleaner than `truncateFrom` for edit flows: it\n * discards stale sibling branches so the regenerated response starts alone.\n */\n truncateResponsesAfter(userMessageId: string): void {\n const prevMessages = this._repo.getMessages();\n this._repo.clearChildren(userMessageId);\n\n // In framework-managed mode the host (Angular @for, React, etc.) owns\n // the bubble DOM. Removing nodes from under it triggers\n // `NotFoundError: Failed to execute 'insertBefore'` on the next change\n // detection cycle because the framework's view tree no longer matches\n // the actual DOM. Skip the manual cleanup and let the framework\n // reconcile when the consumer updates its message array.\n if (!this._frameworkManagedDOM) {\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (wrapper) {\n const startIdx = prevMessages.findIndex(m => m.id === userMessageId);\n const toRemove = startIdx >= 0 ? prevMessages.slice(startIdx + 1) : [];\n for (const m of toRemove) {\n wrapper.querySelector(`aparte-chat-bubble[message-id=\"${cssEscape(m.id)}\"]`)?.remove();\n }\n }\n }\n }\n\n /**\n * Remove all messages from `messageId` onwards (inclusive) from state and DOM.\n * Used by edit to truncate history before re-generating.\n */\n truncateFrom(messageId: string): void {\n const allMsgs = this._repo.getMessages();\n const startIdx = allMsgs.findIndex(m => m.id === messageId);\n if (startIdx === -1) return;\n\n const toRemove = allMsgs.slice(startIdx).map(m => m.id);\n this._repo.resetHead(messageId);\n\n // See the note in truncateResponsesAfter: skip DOM ops when a framework\n // owns the bubble elements.\n if (!this._frameworkManagedDOM) {\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n for (const id of toRemove) {\n wrapper?.querySelector(`aparte-chat-bubble[message-id=\"${cssEscape(id)}\"]`)?.remove();\n }\n }\n }\n\n /**\n * Get a message by ID\n */\n getMessage(messageId: string): AparteMessage | undefined {\n return this._repo.getMessageById(messageId);\n }\n\n /**\n * The messages on the currently ACTIVE path, root → head — not the whole tree.\n * A message that was retried contributes only the branch currently selected;\n * `exportTree()` is what returns every sibling.\n */\n getMessages(): AparteMessage[] {\n return this._repo.getMessages();\n }\n\n /**\n * Export the full conversation tree (all branches, not just the active path).\n * The returned snapshot can be persisted and restored via `importTree()`.\n */\n exportTree(): ExportedMessageRepository {\n return this._repo.export();\n }\n\n /**\n * Import a previously-exported tree snapshot, restoring the full branch\n * topology and the active head. Replaces any existing repo content.\n *\n * Always calls `_reRenderActivePath()`:\n * - In native DOM mode: rebuilds bubble elements.\n * - In framework-managed mode: skips DOM manipulation but dispatches\n * `aparte-path-changed` with sibling metadata so the wrapper can update\n * branch arrows on already-rendered bubbles.\n */\n importTree(tree: ExportedMessageRepository): void {\n // Not `clearAll()`: an import re-populates from a snapshot that may hold the\n // very attachment objects currently in the repo — which is exactly what a\n // conversation load does. See the note in `clearAll`.\n this.clearAll({ revokeAttachments: false });\n // A snapshot is history by definition, and this is the path that used to write\n // it to the repository RAW — so `messageId`/`index` stayed whatever the storage\n // held, and a tree saved before those fields existed came back without them.\n // It also runs AFTER `setMessages` on a conversation load, so whatever that\n // stamped was being replaced by this anyway: two paths, one of them silent.\n this._repo.import({\n ...tree,\n messages: tree.messages.map((entry) => ({\n ...entry,\n message: adoptMessageSegments(entry.message),\n })),\n });\n this._reRenderActivePath();\n }\n\n /**\n * Clear all messages and remove all bubble elements from the DOM.\n * Also dispatches a aparte-reset-done event.\n *\n * In framework-managed mode the DOM is owned by the host framework\n * (Angular @for, React, etc.) and we must not clear `innerHTML` — doing\n * so desynchronises the framework's view tree from the live DOM and the\n * next change-detection pass throws `NotFoundError` on insertBefore.\n */\n clearAll(options?: { revokeAttachments?: boolean }): void {\n /*\n * Release the attachments' object URLs before dropping the messages: after\n * `_repo.clear()` there is no way left to reach them, and nothing else\n * revoked them — so every `File` a session had sent stayed reachable for\n * the life of the page.\n *\n * UNLESS the caller is about to put the same messages back. Two callers do:\n * `setMessages` and `importTree`, and `ConversationController._load` runs\n * BOTH in sequence over one conversation. `export()` stores live `node.current`\n * references, so `conv.messages` and `conv.tree` share the very same\n * attachment objects — meaning the second clear revoked the object URLs of\n * the conversation being opened. Every image and file chip was dead on load,\n * and re-opening revoked twice.\n *\n * A reset (`aparte-reset`, the public `clearAll()`) still revokes: there the\n * messages really are gone.\n */\n if (options?.revokeAttachments !== false) {\n for (const message of this._repo.getMessages()) {\n revokeAttachmentUrls(message.attachments);\n }\n }\n this._repo.clear();\n if (!this._frameworkManagedDOM) {\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (wrapper) {\n // Remove bubbles individually so the spacer div is preserved.\n Array.from(wrapper.querySelectorAll('aparte-chat-bubble')).forEach(b => b.remove());\n }\n }\n // Reset spacer and scroll button regardless of mode\n this._setSpacerHeight(0);\n this._isAutoScrollEnabled = true;\n this._updateScrollButton();\n this.dispatchEvent(new CustomEvent('aparte-reset-done', { bubbles: true, composed: true }));\n }\n\n /**\n * Clear all messages\n * @deprecated Use clearAll() to also remove DOM bubbles\n */\n clearMessages(): void {\n this._repo.clear();\n }\n\n /**\n * Replace the entire message list in one shot. Used when switching\n * conversations: clears existing repo + DOM, then appends each message.\n *\n * In framework-managed mode the framework re-renders the bubble DOM\n * itself; we only update the internal repo (used by aparte-client to\n * build chat history).\n */\n setMessages(messages: AparteMessage[]): void {\n // Same reason as `importTree`: the incoming messages may BE the outgoing\n // ones, and a conversation the user can switch back to still holds them.\n this.clearAll({ revokeAttachments: false });\n for (const m of messages) {\n // Historical by definition: this replaces the transcript with a list the\n // caller already had. Nothing here is starting now.\n this.appendMessage(m, { historical: true });\n }\n }\n\n /**\n * Scroll to bottom of viewport\n */\n scrollToBottom(): void {\n this._scrollToBottom();\n }\n\n /**\n * Reset the bottom spacer to 0 height immediately and freeze it for\n * 350 ms so the host-app layout transition (e.g. flex: 0→1 animation)\n * does not trigger a premature recalculation with mid-animation geometry.\n * Call before a full messages swap.\n */\n resetSpacer(): void {\n this._setSpacerHeight(0);\n // Freeze spacer recalculation for the duration of any host layout\n // transition (configured via `layoutTransitionMs`). Without this,\n // ResizeObserver fires on every animation frame while the container\n // is still growing, producing incorrect spacer values.\n if (this._layoutTransitionMs > 0) {\n this._spacerFrozenUntil = Date.now() + this._layoutTransitionMs;\n }\n }\n\n /**\n * Enable or disable auto-scroll\n */\n setAutoScroll(enabled: boolean): void {\n this._isAutoScrollEnabled = enabled;\n }\n\n /**\n * Signal that a framework (e.g. Angular) manages the bubble DOM.\n * When true, branch navigation dispatches `aparte-path-changed` without\n * clearing/rebuilding the messages wrapper — the framework re-renders instead.\n */\n setFrameworkManagedDOM(managed: boolean): void {\n this._frameworkManagedDOM = managed;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private _getOrCreateMessage(messageId: string): AparteMessage {\n const existing = this._repo.getMessageById(messageId);\n if (existing) return existing;\n\n const message: AparteMessage = {\n id: messageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now(),\n isStreaming: true,\n status: 'streaming'\n };\n this._repo.addOrUpdateMessage(this._repo.headId, message);\n return message;\n }\n\n private _notifyBubble(messageId: string, action: string, payload?: unknown, segmentId?: string): void {\n // Find the bubble element — the native `<aparte-chat-bubble>` OR a custom\n // element opting into live streaming via `data-aparte-bubble` (so a raw-core\n // consumer can replace the bubble tag and still receive token/segment\n // pushes, not just a CSS restyle).\n const bubble = this.querySelector(\n `aparte-chat-bubble[message-id=\"${cssEscape(messageId)}\"], [data-aparte-bubble][message-id=\"${cssEscape(messageId)}\"]`,\n ) as HTMLElement & {\n appendToken?: (chunk: string) => void;\n appendToSegment?: (segmentId: string, chunk: string) => void;\n addSegment?: (segment: AparteSegment) => void;\n updateSegment?: (segmentId: string, updates: Partial<AparteSegment>) => void;\n removeSegment?: (segmentId: string) => void;\n setUsage?: (usage: AparteUsage) => void;\n updateMessage?: (updates: Partial<AparteMessage>) => void;\n };\n\n if (!bubble) return;\n\n switch (action) {\n case 'appendToken':\n bubble.appendToken?.(payload as string);\n break;\n case 'appendToSegment':\n bubble.appendToSegment?.(segmentId!, payload as string);\n break;\n case 'addSegment':\n bubble.addSegment?.(payload as AparteSegment);\n break;\n case 'updateSegment': {\n const { segmentId: sid, updates: segUpdates } = payload as { segmentId: string; updates: Partial<AparteSegment> };\n bubble.updateSegment?.(sid, segUpdates);\n break;\n }\n case 'removeSegment':\n bubble.removeSegment?.(payload as string);\n break;\n case 'setUsage':\n bubble.setUsage?.(payload as AparteUsage);\n break;\n case 'update': {\n // Atomic update: forward it when it carries anything the bubble\n // renders. `content` and `attachments` used to be filtered out\n // here, so an edit (which sends `{ content }`) updated the repo —\n // and therefore the history sent to the model — while the bubble\n // kept displaying the old text.\n const updates = payload as Record<string, unknown>;\n const renderable = ['status', 'segments', 'content', 'attachments', 'usage'];\n if (renderable.some((key) => key in updates)) {\n bubble.updateMessage?.(payload as Partial<AparteMessage>);\n }\n break;\n }\n case 'complete':\n bubble.updateMessage?.(payload as Partial<AparteMessage>);\n break;\n }\n }\n\n /**\n * Re-render the active path: clears the messages wrapper and rebuilds bubbles\n * for every message on the current active branch path (root → head).\n * Calls `setSiblings(count, index)` on each bubble that has siblings, and\n * dispatches `aparte-path-changed` so Angular wrapper can sync its signal.\n *\n * When `_frameworkManagedDOM` is true (set via setFrameworkManagedDOM), the DOM\n * manipulation is skipped — only `aparte-path-changed` is dispatched so the\n * framework can re-render from updated signal state.\n */\n private _reRenderActivePath(): void {\n const activeMessages = this._repo.getMessages();\n\n // Compute sibling metadata once and reuse — keeps the event payload\n // identical between framework-managed and default DOM modes.\n const siblingsInfo: AparteSiblingInfo[] = activeMessages.map(m => {\n const sibs = this._repo.getBranches(m.id);\n return { id: m.id, count: sibs.length, index: sibs.indexOf(m.id) };\n });\n\n if (this._frameworkManagedDOM) {\n this._dispatchPathChanged(activeMessages, siblingsInfo);\n return;\n }\n\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (!wrapper) return;\n wrapper.innerHTML = '';\n\n // Only materialise the last N messages of the active path (DOM render cap).\n // The repository keeps the full path; this is a perf ceiling, not eviction.\n const startIdx = Math.max(0, activeMessages.length - this._maxRenderedBubbles);\n for (let i = startIdx; i < activeMessages.length; i++) {\n const message = activeMessages[i]!;\n const sibInfo = siblingsInfo[i];\n\n const bubble = document.createElement('aparte-chat-bubble');\n bubble.setAttribute('message-id', message.id);\n bubble.setAttribute('role', message.role);\n if (message.timestamp) bubble.setAttribute('timestamp', String(message.timestamp));\n if (isAwaitingReply(message)) {\n bubble.setAttribute('streaming', '');\n }\n wrapper.appendChild(bubble);\n\n // Reconcile content / segments / attachments / sibling-picker via\n // the shared helper — same code path the framework wrappers use,\n // so the contract stays in lockstep.\n populateBubbleFromMessage(bubble as unknown as SyncableBubble, message, sibInfo);\n }\n\n this._dispatchPathChanged(activeMessages, siblingsInfo);\n this._recalculateSpacer();\n\n // No post-swap re-measure of the auto-scroll INTENT here, deliberately: a\n // rebuild's height flickers, and a one-shot measurement that lands mid-flicker\n // can only get it wrong (it would disarm auto-follow for a reader who is\n // pinned to the bottom). The intent is decided once, in `navigateBranch`, from\n // the position the user was actually in; the button re-derives itself from\n // geometry on every scroll and on every post-mutation frame.\n }\n\n private _dispatchPathChanged(messages: AparteMessage[], siblings: AparteSiblingInfo[]): void {\n const detail: ApartePathChangedEventDetail = { messages, siblings };\n this.dispatchEvent(new CustomEvent<ApartePathChangedEventDetail>('aparte-path-changed', {\n bubbles: true,\n composed: true,\n detail,\n }));\n }\n\n private _autoScroll(): void {\n if (this._isAutoScrollEnabled) {\n if (this._smoothScrollOnce) {\n this._smoothScrollOnce = false;\n requestAnimationFrame(() => this._smoothScrollToBottom());\n } else {\n requestAnimationFrame(() => this._scrollToBottom());\n }\n }\n this._pruneRenderedBubbles();\n }\n\n /**\n * Request that the next auto-scroll triggered by a DOM mutation uses\n * smooth behaviour instead of instant. Call this just before adding a\n * user message bubble so the viewport animates down rather than jumping.\n * Resets automatically after the first auto-scroll fires.\n */\n requestSmoothScroll(): void {\n this._smoothScrollOnce = true;\n }\n\n private _render(): void {\n // Framework-managed: the framework owns the bubble children directly.\n // Do NOT build the internal container/wrapper or relocate children.\n if (this._frameworkManagedDOM) {\n this._setupFrameworkDOM();\n return;\n }\n // Light DOM rendering\n // Preserving existing children in render allows framework composition\n if (!this.querySelector('.aparte-viewport-container')) {\n const container = document.createElement('div');\n container.className = 'aparte-viewport-container';\n\n // Set direction based on current locale\n const locale = resolveConfig(this).getLocale();\n if (locale.direction) {\n container.setAttribute('dir', locale.direction);\n }\n\n container.setAttribute('role', 'log');\n container.setAttribute('aria-live', 'polite');\n container.setAttribute('aria-atomic', 'false');\n container.setAttribute('aria-relevant', 'additions');\n\n const wrapper = document.createElement('div');\n wrapper.className = 'aparte-messages-wrapper';\n\n // Move existing children (bubbles) into wrapper\n while (this.firstChild) {\n wrapper.appendChild(this.firstChild);\n }\n\n // Bottom spacer — always last in wrapper, height driven by _recalculateSpacer()\n this._bottomSpacer = document.createElement('div');\n this._bottomSpacer.className = 'aparte-bottom-spacer';\n this._bottomSpacer.setAttribute('aria-hidden', 'true');\n wrapper.appendChild(this._bottomSpacer);\n\n container.appendChild(wrapper);\n this.appendChild(container);\n\n this._container = container;\n\n // Scroll-to-bottom button — absolutely positioned over the viewport\n this._scrollBtn = document.createElement('button');\n this._scrollBtn.className = 'aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden';\n this._scrollBtn.setAttribute('type', 'button');\n this._scrollBtn.setAttribute('aria-label', 'Scroll to bottom');\n const scrollIcon = resolveConfig(this).getIcon('scrollDown');\n this._scrollBtn.innerHTML = scrollIcon;\n this.appendChild(this._scrollBtn);\n } else {\n this._container = this.querySelector('.aparte-viewport-container');\n this._scrollBtn = this.querySelector('.aparte-scroll-btn');\n this._bottomSpacer = this.querySelector('.aparte-bottom-spacer');\n }\n }\n\n /**\n * DOM setup for framework-managed mode. The framework (React/Vue/Svelte/\n * Angular) renders the bubble elements as DIRECT children of the host, so we\n * must NOT relocate them into an internal wrapper — that desyncs the\n * framework's virtual DOM from the real DOM and throws NotFoundError on the\n * next append. Instead the HOST itself is the scroll surface, the spacer is\n * additive `padding-bottom` (no element), and the scroll button is a\n * `position: sticky` TRAILING foreign child (kept last by the framework\n * MutationObserver). A present foreign node is still a valid `insertBefore`\n * reference for the framework — the crash came from a RELOCATED node, not a\n * foreign one.\n */\n private _setupFrameworkDOM(): void {\n this._container = this;\n this._bottomSpacer = null;\n if (this.classList.contains('aparte-viewport--framework')) {\n this._scrollBtn = this.querySelector(':scope > .aparte-scroll-btn') as HTMLButtonElement | null;\n return; // already set up (re-entrant _render)\n }\n this.classList.add('aparte-viewport--framework');\n\n const scrollBtn = document.createElement('button');\n scrollBtn.className = 'aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden';\n scrollBtn.setAttribute('type', 'button');\n scrollBtn.setAttribute('aria-label', 'Scroll to bottom');\n const scrollIcon = resolveConfig(this).getIcon('scrollDown');\n scrollBtn.innerHTML = scrollIcon;\n this.appendChild(scrollBtn);\n this._scrollBtn = scrollBtn;\n }\n\n /**\n * Keep the sticky scroll button as the last child in framework-managed mode.\n * The framework usually inserts bubbles before its own trailing nodes (so the\n * button stays last), but a plain `appendChild` at the very end (e.g. some\n * Angular @for paths) can land a bubble after it — move it back. Idempotent:\n * a no-op when already last, so it never loops the MutationObserver.\n */\n private _keepScrollButtonLast(): void {\n if (!this._scrollBtn) return;\n if (this.lastElementChild !== this._scrollBtn) {\n this.appendChild(this._scrollBtn);\n }\n }\n\n /** Current spacer height — a padding value (framework) or the element's height (core). */\n private _getSpacerHeight(): number {\n if (this._frameworkManagedDOM) return this._fwSpacerHeight;\n return this._bottomSpacer?.offsetHeight ?? 0;\n }\n\n /** Set the spacer — host padding (framework, additive to base padding) or element height (core). */\n private _setSpacerHeight(px: number): void {\n if (this._frameworkManagedDOM) {\n this._fwSpacerHeight = px;\n this.style.setProperty('--aparte-fw-spacer', `${px}px`);\n } else if (this._bottomSpacer) {\n this._bottomSpacer.style.height = `${px}px`;\n }\n }\n\n // Bound fields, not inline arrows: a custom element is re-connected every\n // time it is MOVED in the DOM (a portal, a dialog, a framework re-parenting),\n // so `_setupEventListeners` runs again each time. An inline arrow can never\n // be handed to `removeEventListener`, so it just accumulates — one branch\n // click then ran N handlers, N active-path re-renders and N storage writes\n // through the conversation controller. The window listeners next to these\n // were always removed properly; these two, attached to `this`, were not.\n private readonly _onScrollBtnClick = (): void => {\n this._isAutoScrollEnabled = true;\n this._smoothScrollToBottom();\n this._updateScrollButton();\n };\n\n private readonly _onBranchNavigate = (e: Event): void => {\n const evt = e as CustomEvent<{ messageId: string; direction: 'prev' | 'next' }>;\n evt.stopPropagation();\n this.navigateBranch(evt.detail.messageId, evt.detail.direction);\n };\n\n private _setupEventListeners(): void {\n this._container?.addEventListener('scroll', this._handleScroll, { passive: true });\n this._scrollBtn?.addEventListener('click', this._onScrollBtnClick);\n this.addEventListener('aparte-branch-navigate', this._onBranchNavigate);\n }\n\n private _setupObservers(): void {\n this._resizeObserver = new ResizeObserver(() => {\n if (this._isAutoScrollEnabled) {\n this._scrollToBottom();\n }\n this._recalculateSpacer();\n });\n\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n\n if (this._container) {\n // Fires on window/viewport resize and when the composer grows.\n // NOTE: we intentionally do NOT observe .aparte-messages-wrapper here.\n // The wrapper contains the spacer div — observing it would create a\n // feedback loop: spacer changes → wrapper resizes → ResizeObserver →\n // _recalculateSpacer → spacer changes → … → height grows unbounded.\n // Streaming content growth is handled by direct _scheduleSpacerUpdate()\n // calls from appendToken(). New bubbles are handled by MutationObserver.\n // Framework-managed: _container IS the host, whose `padding-bottom`\n // carries the spacer — observe the BORDER box (fixed host size) so a\n // spacer/padding change does NOT re-trigger _recalculateSpacer and\n // loop. Core mode observes the container (no dynamic padding).\n if (this._frameworkManagedDOM) {\n this._resizeObserver.observe(this._container, { box: 'border-box' });\n } else {\n this._resizeObserver.observe(this._container);\n }\n }\n\n this._mutationObserver = new MutationObserver(() => {\n // Keep the sticky scroll button trailing after framework appends.\n if (this._frameworkManagedDOM) this._keepScrollButtonLast();\n // The gate is tested HERE, when the frame is queued, and moving it\n // inside the callback is not the improvement it looks like.\n //\n // Queue-time looks like a race — the user could scroll up before the\n // frame runs and be dragged back. Testing it at run-time instead was\n // tried and reverted: a branch swap replaces bubbles, the resulting\n // scroll event makes `_isAtBottom()` briefly false, and the deferred\n // check then refuses to re-anchor, leaving a scroll-to-bottom button on\n // a transcript that IS at the bottom (caught by\n // `bubble-actions.spec.ts` on WebKit, 3 runs out of 3).\n //\n // So queue-time is deliberate: it captures the user's intent BEFORE the\n // DOM churn can confuse the \"am I at the bottom?\" heuristic. Reading it\n // correctly in both cases needs a way to tell our own programmatic\n // scroll from a real gesture — see the note in `_handleScroll`.\n if (this._isAutoScrollEnabled) {\n requestAnimationFrame(() => this._scrollToBottom());\n }\n // Recalculate spacer when DOM mutates (new bubble added, Angular re-render).\n this._scheduleSpacerUpdate();\n });\n\n // Framework-managed: bubbles are direct children of the host (no wrapper).\n const observeTarget = this._frameworkManagedDOM ? this : wrapper;\n if (observeTarget) {\n this._mutationObserver.observe(observeTarget, {\n childList: true,\n subtree: true\n });\n }\n }\n\n /** Is the scroll surface within `_scrollThreshold` of its bottom, right now? */\n private _isAtBottom(): boolean {\n if (!this._container) return true;\n const { scrollTop, scrollHeight, clientHeight } = this._container;\n return scrollHeight - scrollTop - clientHeight <= this._scrollThreshold;\n }\n\n private _handleScroll(): void {\n if (!this._container) return;\n // A scroll IS the user's intent: reaching the bottom re-arms auto-follow,\n // leaving it disarms it.\n //\n // `_isAtBottom()` is deliberately generous (`_scrollThreshold`, 50px): a few\n // pixels of drift must NOT read as \"the reader walked away\". That generosity\n // is right here and wrong as a definition of \"anchored\" — which is why\n // `_settleAtBottom()` closes a residual gap instead of this method being\n // tightened. Tightening it would disarm auto-follow on every stray pixel.\n //\n // This handler also cannot tell our own programmatic scroll from a real\n // gesture. Marking them with a counter was tried and reverted: engines\n // coalesce scroll events, so the counter over-counted and started swallowing\n // the USER's scrolls — the browser suite caught it stealing the scroll back.\n // Anything attempted here must identify the scroll by POSITION, not by\n // counting events.\n this._isAutoScrollEnabled = this._isAtBottom();\n this._updateScrollButton();\n }\n\n private _scrollToBottom(): void {\n if (!this._container) return;\n this._container.scrollTop = this._container.scrollHeight;\n this._settleAtBottom(4);\n }\n\n /**\n * Confirm over the next few frames that we actually reached the bottom.\n *\n * One assignment is not enough, and the reason is measured rather than guessed.\n * A timeline of a streamed turn on Safari (framework mode) recorded the content\n * settling in TWO layout passes — 1118 → 1121 → 1152 px. `scrollTop =\n * scrollHeight` ran against the middle one, clamped to that layout's max (603),\n * and nothing ran afterwards: the last 31px never closed. Auto-follow stayed\n * armed the whole time, so the component was not disarmed — it was SATISFIED.\n * `_isAtBottom()` answers \"yes\" for any gap under `_scrollThreshold` (50), which\n * is the right rule for keeping auto-follow armed and the wrong one as a\n * definition of \"anchored\".\n *\n * Ruled out on the way here, so nobody pays for it twice: not a WebKit\n * padding-accounting difference (a probe writing `scrollTop = 1e7` reached\n * exactly `scrollHeight - clientHeight`), not a missing `characterData`\n * mutation, and not a child resize a ResizeObserver could see.\n *\n * A BOUNDED retry, not one corrective frame: a single frame lands on the same\n * stale layout and was measured leaving a wider gap than doing nothing. Bounded\n * so it always terminates; re-reads `_isAutoScrollEnabled` every frame so a\n * reader who scrolls away mid-settle is left alone; stops as soon as the gap is\n * closed, so the common case costs one frame that does nothing.\n */\n private _settleAtBottom(framesLeft: number): void {\n if (framesLeft <= 0) return;\n requestAnimationFrame(() => {\n if (!this._container || !this._isAutoScrollEnabled) return;\n const max = this._container.scrollHeight - this._container.clientHeight;\n if (max - this._container.scrollTop <= 1) return;\n this._container.scrollTop = max;\n this._settleAtBottom(framesLeft - 1);\n });\n }\n\n private _smoothScrollToBottom(): void {\n if (!this._container) return;\n // scrollTo with behavior:'smooth' is not available in all environments (e.g. jsdom).\n // Fall back to instant scroll so tests and SSR environments stay safe.\n // Reduced-motion users get the instant path too — the CSS\n // prefers-reduced-motion block cannot reach a JS-driven smooth scroll.\n if (typeof this._container.scrollTo === 'function' && !this._prefersReducedMotion()) {\n this._container.scrollTo({ top: this._container.scrollHeight, behavior: 'smooth' });\n } else {\n this._container.scrollTop = this._container.scrollHeight;\n }\n }\n\n private _prefersReducedMotion(): boolean {\n return typeof matchMedia === 'function'\n && matchMedia('(prefers-reduced-motion: reduce)').matches;\n }\n\n /**\n * Show/hide the scroll-to-bottom button from the **current geometry**, not from\n * `_isAutoScrollEnabled`.\n *\n * The two answer different questions: the flag is intent (\"should new content\n * pull the view down\"), the button is a fact (\"is there anything below the\n * fold\"). Mirroring the flag made the button lie whenever the two diverged —\n * `navigateBranch` deliberately disarms auto-follow, so swapping a branch while\n * already at the bottom of a scrollable transcript left the button offering to\n * scroll nowhere (reported from bonaparte, React). Re-derived on scroll, on the\n * post-mutation frame and after a path swap, so it converges to the truth\n * whatever a framework's render timing does in between.\n */\n private _updateScrollButton(): void {\n this._scrollBtn?.classList.toggle('aparte-scroll-btn--hidden', this._isAtBottom());\n }\n\n /**\n * Recalculate the bottom spacer height so the last user message is always\n * pinned to the top of the scroll area when a response is being generated.\n *\n * spacer = max(0, viewportHeight - lastUserBubble.offsetHeight - lastAssistantBubble.offsetHeight)\n *\n * The spacer shrinks progressively as the assistant streams content, eventually\n * reaching 0 when the combined height fills the viewport.\n */\n private _recalculateSpacer(): void {\n // Core mode needs the spacer element; framework mode uses host padding\n // (no element) — both need the scroll container.\n if (!this._container) return;\n if (!this._frameworkManagedDOM && !this._bottomSpacer) return;\n // Skip while the host layout is still animating (e.g. the flex transition\n // that moves the composer from the center of the screen to the bottom).\n // Without this guard, every ResizeObserver tick during the transition\n // reads a partially-grown clientHeight and writes an incorrect spacer\n // height that may reach the clientHeight cap and lock the spacer there.\n if (Date.now() < this._spacerFrozenUntil) return;\n\n const allBubbles = Array.from(\n this.querySelectorAll('aparte-chat-bubble')\n ) as HTMLElement[];\n\n if (allBubbles.length === 0) {\n this._setSpacerHeight(0);\n return;\n }\n\n const lastUserBubble = [...allBubbles]\n .reverse()\n .find(b => b.getAttribute('role') === 'user');\n\n if (!lastUserBubble) {\n this._setSpacerHeight(0);\n return;\n }\n\n // Read the current spacer height (may be non-zero during a CSS transition\n // or a previous non-zero value). Subtract it from scrollHeight to get the\n // true content height WITHOUT the spacer — no need to zero-then-reflow,\n // which would both fight the CSS transition and force an extra synchronous\n // layout that could read a stale animated value.\n const currentSpacerH = this._getSpacerHeight();\n\n // Use getBoundingClientRect so gaps, padding, and all children are\n // automatically accounted for — no need to manually sum heights.\n const containerRect = this._container.getBoundingClientRect();\n const userRect = lastUserBubble.getBoundingClientRect();\n\n // Absolute Y position of the user bubble's top within the full scrollable content\n const userTopInContent = userRect.top - containerRect.top + this._container.scrollTop;\n\n // Height of content from user bubble top to end, excluding the spacer\n const scrollHeightWithoutSpacer = this._container.scrollHeight - currentSpacerH;\n\n // If all content already fits in the viewport, no spacer is needed.\n if (scrollHeightWithoutSpacer <= this._container.clientHeight) {\n this._setSpacerHeight(0);\n return;\n }\n\n const contentBelowUserTop = scrollHeightWithoutSpacer - userTopInContent;\n\n const needed = this._container.clientHeight - contentBelowUserTop;\n // Hard cap: the spacer can never exceed the visible viewport height.\n // This acts as a safety net against stale layout reads (e.g. mid-swap)\n // that could produce an astronomical value and push content off-screen.\n const maxSpacer = this._container.clientHeight;\n this._setSpacerHeight(Math.min(Math.max(0, needed), maxSpacer));\n\n // Re-scroll after the spacer height changes so scrollTop is always\n // consistent with the new scrollHeight. Without this, the MutationObserver\n // schedules _scrollToBottom() one RAF *before* _recalculateSpacer() runs,\n // leaving scrollTop based on the pre-spacer scrollHeight. On the next\n // recalculation (e.g. from syncMessagesWithBubbles or a resize) the formula\n // reads a stale scrollTop and may grow the spacer to the clientHeight cap.\n if (this._isAutoScrollEnabled) {\n this._scrollToBottom();\n }\n }\n\n /**\n * Schedule a spacer recalculation on the next animation frame.\n * Batches multiple rapid calls (e.g. during token streaming) into one.\n *\n * Single-RAF intentional: both the scroll-to-bottom queued by MutationObserver\n * and this spacer recalculation must land in the *same* frame so the browser\n * paints exactly once — with the correct scroll position *and* the correct\n * spacer height. A double-RAF would put the spacer shrink one frame after the\n * scroll, causing a 1-frame layout jump during streaming.\n */\n private _scheduleSpacerUpdate(): void {\n if (this._spacerRafId !== null) return;\n this._spacerRafId = requestAnimationFrame(() => {\n this._spacerRafId = null;\n this._recalculateSpacer();\n // The DOM just changed (new bubble, streamed token, framework re-render):\n // whether anything sits below the fold changed with it.\n this._updateScrollButton();\n });\n }\n\n /**\n * Cap the number of rendered bubbles in the DOM (perf ceiling only).\n *\n * Drops the oldest `<aparte-chat-bubble>` elements beyond `_maxRenderedBubbles`\n * from the DOM. It **never** touches the AparteMessageRepository — the conversation\n * model and its persistence snapshot stay complete (retention/eviction is a\n * consumer/persistence concern, not the viewport's). No-op when a framework\n * owns the DOM.\n */\n private _pruneRenderedBubbles(): void {\n if (this._frameworkManagedDOM) return;\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (!wrapper) return;\n const bubbles = wrapper.querySelectorAll('aparte-chat-bubble');\n const excess = bubbles.length - this._maxRenderedBubbles;\n for (let i = 0; i < excess; i++) {\n bubbles[i]?.remove();\n }\n }\n\n private _warnMaxMessagesDeprecated(): void {\n if (this._warnedMaxMessagesDeprecation) return;\n this._warnedMaxMessagesDeprecation = true;\n console.warn(\n '[Aparte] `maxMessages` / `max-messages` on aparte-chat-viewport is deprecated: ' +\n 'it used to silently evict messages from the conversation model. It now only ' +\n 'caps rendered bubbles in the DOM — use `maxRenderedBubbles` / `max-rendered-bubbles`. ' +\n 'For actual history retention, configure it on your AparteConversationManager instead.',\n );\n }\n\n private _cleanup(): void {\n this._container?.removeEventListener('scroll', this._handleScroll);\n this._scrollBtn?.removeEventListener('click', this._onScrollBtnClick);\n this.removeEventListener('aparte-branch-navigate', this._onBranchNavigate);\n this._resizeObserver?.disconnect();\n this._mutationObserver?.disconnect();\n this._resizeObserver = null;\n this._mutationObserver = null;\n if (this._spacerRafId !== null) {\n cancelAnimationFrame(this._spacerRafId);\n this._spacerRafId = null;\n }\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-viewport')) {\n customElements.define('aparte-chat-viewport', AparteChatViewport);\n}\n","import type { AparteSendEventDetail } from '../../types/index.js';\nimport { type AparteConfig } from '../../config/aparte-config.js';\nimport { resolveConfig } from '../../config/config-context.js';\n\n/**\n * What the composer's one button means while a panel is up — and whether it is\n * there at all.\n *\n * `'submit'` answers, `'advance'` moves to the next question of a form, and\n * `'none'` says this panel has NO act left for that button, so it is not drawn.\n *\n * `'none'` exists because a panel could not previously say it. The composer's panel\n * mode was one fixed policy — hide the input and the attachment picker, keep the\n * strip and the toolbar, and ALWAYS keep the send button — and the approval panel\n * showed what that costs: its options settle on the first click by design, so the\n * button beside them sat permanently disabled, meaning nothing, until the optional\n * instruction field was opened. A control that is never the way forward is not\n * disabled, it is absent (ratified decision #8).\n *\n * Named rather than inlined at each of its six readers: this union is exactly the\n * kind of list this repo has watched drift when every reader kept its own copy.\n */\nexport type AparteComposerPanelMode = 'advance' | 'submit' | 'none';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Event map for internal pub/sub between primitives\n// ─────────────────────────────────────────────────────────────────────────────\nexport interface AparteComposerEventMap {\n 'value-change': { value: string };\n 'streaming-change': { streaming: boolean };\n 'disabled-change': { disabled: boolean };\n 'attachments-change': { attachments: File[] };\n 'submit': { value: string; attachments: File[] };\n 'cancel': Record<string, never>;\n 'panel-change': { active: boolean; submitEnabled: boolean; mode: AparteComposerPanelMode };\n}\n\nexport type AparteComposerEventType = keyof AparteComposerEventMap;\n\n/**\n * Public snapshot of the composer's observable state. Delivered on every\n * `aparte-composer-change` DOM event and available synchronously via\n * {@link AparteComposer.getState}. Lets an element OUTSIDE the composer package\n * (a custom send button, a footer control) mirror the composer's live state\n * without the internal `_on`/`_emit` bus.\n */\nexport interface AparteComposerState {\n value: string;\n streaming: boolean;\n disabled: boolean;\n attachments: File[];\n /** A panel (e.g. an elicitation form) is showing in place of the input. */\n panelActive: boolean;\n /** Whether the send button should act as \"submit\" while a panel is active. */\n submitEnabled: boolean;\n}\n\n/** Detail of the public `aparte-composer-change` DOM event. */\nexport interface AparteComposerChangeEventDetail {\n state: AparteComposerState;\n composer: AparteComposer;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AparteComposer — root context provider\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The root context for every `aparte-composer-*` primitive. It imposes no visual\n * layout — the consumer owns the structure — and holds the shared state the parts\n * read: the value, the streaming flag, pending attachments, whether a panel is up.\n *\n * It renders nothing of its own — no shadow root, no markup, no default children — so\n * an `<aparte-composer>` with nothing inside is an empty block. The parts that need\n * that state locate it with `closest('aparte-composer')`, which is why they may sit at\n * any depth and why the opt-in `.aparte-composer-shell` / `.aparte-composer-row`\n * wrappers can exist without this element knowing about them.\n * Not every part looks it up, though: `<aparte-composer-toolbar>` is purely structural\n * — it lays its children out and never resolves this element at all.\n *\n * WHAT GOES INSIDE — ordinary light-DOM children. Core has no shadow root and no\n * `<slot>`, so there is no slot name to write: drop in `<aparte-composer-input>`,\n * `<aparte-composer-send>`, `<aparte-composer-cancel>`,\n * `<aparte-composer-attachments>`, `<aparte-composer-add-attachment>`,\n * `<aparte-composer-action>`, `<aparte-composer-toolbar>`, plus whatever markup you\n * wrap them in. Order and nesting are yours. Two behaviours read the tree rather than a\n * flag, so they depend on what you put in: `focus()` forwards to the first\n * `<aparte-composer-input>` descendant, and `showPanel()` inserts the panel right after\n * it (appending to the host when there is none).\n *\n * A PANEL is neither markup you write nor a named slot: `showPanel()` takes the element,\n * stamps it `data-aparte-panel` and inserts it, `hidePanel()` removes it. One at a time\n * — a second `showPanel()` evicts the first and calls its `onEvict`. While one is up the\n * host carries `[data-panel-active]`, which hides `<aparte-composer-input>` and\n * `<aparte-composer-add-attachment>` and leaves the attachments strip and the toolbar in\n * place.\n *\n * It is not a transport either. `submit()` trims, checks the gates (disabled, empty,\n * no model selected), dispatches `aparte-send` and clears — nothing here talks to a\n * model, so without `AparteClient` or a listener of your own a send is a dispatched\n * event and no answer. With no panel up it doubles as the stop button: while `streaming`\n * it routes to `cancel()`, which is why the `getState` example below keeps a custom send\n * button clickable rather than disabling it mid-stream. With a panel up it means \"answer\n * the question\" instead — it calls the panel's `onSubmit` and returns, so neither the\n * stop branch nor a send is reached.\n *\n * The streaming flag comes from WINDOW lifecycle events, filtered by target. On a page\n * with two chats, give the composer a `target` — or put it under a chat host that has\n * an `id` — otherwise it answers to every chat's events, and one chat's Stop resets the\n * other's composer and evicts its open panel.\n *\n * Prose first, on purpose: when `@element` opens a docblock there is no free text\n * left for the analyser to use, and this component's description came out empty in\n * the manifest and blank on the generated reference page.\n *\n * `aparte-abort` and `aparte-message-aborted` have to be declared by hand and always\n * will: they go out through `window.dispatchEvent` (they concern the whole page, not\n * this subtree), and the analyser's fallback only recognises `this.dispatchEvent`.\n *\n * @element aparte-composer\n *\n * @attr {string} placeholder - Fallback placeholder for `<aparte-composer-input>`, which\n * reads it off this element when it carries none of its own. Read when that input\n * renders, not pushed: changing it here leaves an input already on the page as it was.\n * @attr {boolean} disabled - Disables the composer's own controls — the input, send,\n * add-attachment and `<aparte-composer-action>` buttons each read it. What you put in\n * the toolbar is yours to disable.\n * @attr {string} target - The id of the `<aparte-chat>` this composer drives.\n * @attr {boolean} submit-on-enter - Enter sends and Shift+Enter breaks the line (the\n * default); set it to the string `\"false\"` to swap them. Read lazily by the\n * `submitOnEnter` getter rather than observed, which is why it was missing from the\n * manifest — and so from every typed surface — while all four wrappers wrote it.\n *\n * @fires {CustomEvent<AparteSendEventDetail>} aparte-send - A message was submitted: the text, its attachments and the target.\n * @fires aparte-cancel - The stop button was pressed. No detail; the two window events below carry the target.\n * @fires {CustomEvent<AparteAbortEventDetail>} aparte-abort - Dispatched on `window`: stop the run for this target.\n * @fires {CustomEvent<AparteMessageAbortedEventDetail>} aparte-message-aborted - The run for this target ended early — the user pressed Stop, or `abort()` was called. This element dispatches it on `window`; `AparteClient` also dispatches it on the chat host, so it is listenable on either.\n * @fires {CustomEvent<AparteComposerChangeEventDetail>} aparte-composer-change - Any of value / streaming / disabled / attachments / panel changed, folded into one event.\n *\n * @cssprop [--aparte-composer-control-size=44px] - Width and height of the composer's\n * own control buttons (each is an `.aparte-btn--icon`, so it needs the opt-in\n * `.aparte-composer-row` wrapper, which is what carries the size down) and the\n * minimum height of the input's editor, which needs no wrapper. One knob for the\n * whole control set, so buttons stay aligned with a single line of text and anchored\n * to the bottom once the input grows. It reaches the buttons by declaration, not by\n * out-specifying them, so a panel mounted in the row keeps its own content's sizing.\n * @cssprop [--aparte-input-bg=var(--aparte-surface-1)] - Background of the opt-in\n * `.aparte-composer-shell` wrapper.\n * @cssprop [--aparte-input-border=var(--aparte-border)] - Border colour of that shell.\n * Its `:focus-within` colour is `--aparte-primary`, a global token rather than a\n * composer one.\n * @cssprop [--aparte-radius-input=var(--aparte-radius-lg)] - Corner radius of the shell,\n * and of the dashed outline drawn while files are dragged over the composer.\n * @cssprop [--aparte-message-max-width=800px] - Max width of the shell, which is\n * `margin: 0 auto` at this width — the same width `.aparte-message` uses, so the\n * composer keeps the transcript's column. Set on THIS element it moves the shell only:\n * custom properties inherit downward and the transcript is a sibling subtree, so set\n * it on a shared ancestor (the chat host, `:root`) to move both.\n *\n * @example\n * <!-- It renders nothing of its own — no shadow root, no default children — so this\n * markup IS the component. The shell draws the border; the row keeps the controls\n * on the bottom edge of the text as it grows. Both are opt-in classes: drop them\n * and the parts still work, they just sit wherever your own layout puts them. -->\n * <aparte-composer placeholder=\"Ask anything…\">\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposer extends HTMLElement {\n private _value = '';\n private _streaming = false;\n private _attachments: File[] = [];\n private _listeners = new Map<string, Set<(payload: unknown) => void>>();\n private _panelActive = false;\n /** What the send button means while a panel is up — see `showPanel`. */\n private _panelMode: AparteComposerPanelMode = 'submit';\n private _panelSubmitEnabled = false;\n private _panelOnSubmit: (() => void) | null = null;\n /**\n * Who owns the one panel slot, and how to tell them they lost it.\n *\n * There is exactly one slot, `showPanel` empties it unconditionally, and nothing\n * used to say whose it was. Three paths therefore closed a panel whose owner was\n * still awaiting an answer, and none of them told the owner: a second `showPanel`,\n * the owner-of-record's own `hidePanel`, and — the one that actually bit —\n * `_handleMessageDone`, which fires on EVERY turn end. A question still open when a\n * turn finished lost its panel while `<aparte-elicitation>` kept `_pending` set,\n * so the request never settled AND every later request was short-circuited for the\n * life of the page.\n */\n private _panelToken: symbol | null = null;\n private _panelOnEvict: (() => void) | null = null;\n\n // Internal bus events that represent an observable STATE change — these are\n // mirrored to the public `aparte-composer-change` DOM event. `submit`/`cancel`\n // are actions, not state, and are covered by `aparte-send`/`aparte-cancel`.\n private static readonly _STATE_EVENTS: ReadonlySet<AparteComposerEventType> = new Set([\n 'value-change', 'streaming-change', 'disabled-change', 'attachments-change', 'panel-change',\n ]);\n\n // Window event bindings\n private _onMessageStart = this._handleMessageStart.bind(this);\n private _onMessageDone = this._handleMessageDone.bind(this);\n\n /** Config governing THIS composer (nearest instance boundary, else global). */\n /**\n * Resolved LIVE, not cached at connect.\n *\n * A wrapper runs `AparteChatHost.bind()` — which calls `attachConfig` — from its\n * POST-mount hook, so this element connects BEFORE the boundary exists. Caching\n * here latched the global config forever: an instance `config` carrying an RTL\n * locale flipped the transcript and not the composer, and the\n * `requireModelSelection` gate was read off the wrong object. `aparte-chat-bubble`\n * has always resolved live, and its JSDoc names this exact race.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n /** Only OUR config: a change on another chat's instance must not touch us. */\n private _onConfigChangeEvent = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n this._onConfigChange();\n };\n /** True while `requireModelSelection` is on AND no model is selected — blocks send. */\n private _modelGated = false;\n private _onConfigChange = (): void => { this._evaluateModelGate(); this._applyDirection(); };\n\n /**\n * Mirror `locale.direction` onto ourselves, so everything inside — input, buttons,\n * the toolbar row and whatever the consumer put in it — inherits it.\n *\n * The direction used to stop at the transcript: only the viewport applied `dir`, so\n * an RTL locale flipped the conversation and left the composer left-to-right. It is\n * also what makes the toolbar's placement idiom real: `margin-inline-start: auto` in\n * a subtree that inherits no direction is just `margin-left`.\n *\n * One attribute on the host rather than a stamp per child: inheritance is the\n * mechanism, so nothing needs to know about the consumer's markup.\n */\n private _applyDirection(): void {\n const direction = (this._cfg ?? resolveConfig(this)).getLocale().direction;\n if (direction) this.setAttribute('dir', direction);\n else this.removeAttribute('dir');\n }\n\n static get observedAttributes(): string[] {\n return ['placeholder', 'disabled', 'target'];\n }\n\n connectedCallback(): void {\n window.addEventListener('aparte-message-start', this._onMessageStart);\n window.addEventListener('aparte-message-done', this._onMessageDone);\n window.addEventListener('aparte-message-error', this._onMessageDone);\n window.addEventListener('aparte-message-aborted', this._onMessageDone);\n // Model-selection gate (opt-in via aparteGlobalConfig.setRequireModelSelection).\n // A window listener rather than `_cfg.subscribe(...)`: subscribing binds to\n // whichever config was resolvable AT CONNECT, which is the bug above wearing\n // a different hat. `_notify()` dispatches `aparte-config-change` with\n // `detail.config`, so the same information arrives without the early binding\n // — and the filter below compares against the LIVE config.\n window.addEventListener('aparte-config-change', this._onConfigChangeEvent);\n this._evaluateModelGate();\n this._applyDirection();\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-message-start', this._onMessageStart);\n window.removeEventListener('aparte-message-done', this._onMessageDone);\n window.removeEventListener('aparte-message-error', this._onMessageDone);\n window.removeEventListener('aparte-message-aborted', this._onMessageDone);\n window.removeEventListener('aparte-config-change', this._onConfigChangeEvent);\n this._listeners.clear();\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'disabled') {\n this._emit('disabled-change', { disabled: value !== null });\n }\n if (name === 'placeholder') {\n // Primitives read this directly via closest() — no event needed\n }\n }\n\n // ── Public API ─────────────────────────────────────────────────────────\n\n get value(): string { return this._value; }\n get streaming(): boolean { return this._streaming; }\n get disabled(): boolean { return this.hasAttribute('disabled'); }\n /**\n * When false, Shift+Enter submits and a bare Enter inserts a newline —\n * the inverse of the default. Driven by the `submit-on-enter` attribute.\n */\n get submitOnEnter(): boolean { return this.getAttribute('submit-on-enter') !== 'false'; }\n get attachments(): File[] { return this._attachments; }\n get placeholder(): string { return this.getAttribute('placeholder') ?? ''; }\n get targetId(): string | null { return this.getAttribute('target'); }\n\n /**\n * Snapshot of the composer's observable state. Pair with the\n * `aparte-composer-change` DOM event to drive a custom send button or footer\n * control that lives outside the composer package:\n *\n * @example\n * // A custom send button. Keep it CLICKABLE while streaming — submit()\n * // routes to cancel() when a response is in flight, so one button is\n * // Send/Stop. Disabling it on `streaming` would make \"stop\" unreachable.\n * composer.addEventListener('aparte-composer-change', (e) => {\n * const { streaming, disabled, value, attachments } = e.detail.state;\n * myButton.textContent = streaming ? 'Stop' : 'Send';\n * myButton.disabled = disabled || (!streaming && !value.trim() && attachments.length === 0);\n * });\n * myButton.addEventListener('click', () => composer.submit()); // send or stop\n */\n getState(): AparteComposerState {\n return {\n value: this._value,\n streaming: this._streaming,\n disabled: this.disabled,\n attachments: [...this._attachments],\n panelActive: this._panelActive,\n submitEnabled: this._panelSubmitEnabled,\n };\n }\n\n /**\n * Set the composer's value — both what a send will submit and what the editor\n * shows. `<aparte-composer-input>` writes through any value it does not already\n * hold, so this prefills the visible field (a template button, a restored draft)\n * as readily as it stages text for an immediate `submit()`.\n */\n setValue(value: string): void {\n this._value = value;\n this._emit('value-change', { value });\n }\n\n /** Append files to the pending attachments and notify. Does not de-duplicate. */\n addAttachments(files: FileList | File[]): void {\n this._attachments = [...this._attachments, ...Array.from(files)];\n this._emit('attachments-change', { attachments: this._attachments });\n }\n\n /**\n * Drop one pending attachment and notify. Matched by IDENTITY — pass the same\n * `File` object the composer handed you, not an equal one; two picks of the same\n * file on disk are two distinct objects.\n */\n removeAttachment(file: File): void {\n this._attachments = this._attachments.filter(f => f !== file);\n this._emit('attachments-change', { attachments: this._attachments });\n }\n\n /** Drop every pending attachment and notify. */\n clearAttachments(): void {\n this._attachments = [];\n this._emit('attachments-change', { attachments: [] });\n }\n\n /**\n * Inject a panel into the composer. The send button calls `onSubmit` when clicked.\n *\n * While a panel is up, the composer is answering a QUESTION, not composing a\n * message — so the affordances that lead nowhere go away with the text input:\n * the attachment picker above all, which stayed clickable while the user was\n * being asked something (\"on voyait encore l'icône de upload\", reported from a\n * real session). Ratified decision #8: an affordance nothing can honour is not\n * rendered.\n *\n * What STAYS, deliberately: the attachments strip, because pending attachments\n * are the user's state and not an action to offer — hiding them would look like\n * losing them; and the toolbar, because switching model still does something.\n *\n * The send button is the part the PANEL decides, through `mode`. `'submit'` and\n * `'advance'` keep it; `'none'` says this panel has no act for it and it is not\n * drawn. That third value is what a panel whose options settle on the first click\n * needs — a single-choice question, an approval — and until it existed such a\n * panel left a permanently disabled button beside options that never routed\n * through it. Flip between them at any time with {@link setPanelSubmitEnabled},\n * which is how a panel that grows an act (an \"Other…\" field, a written\n * instruction) turns the button back on.\n *\n * Declared with an attribute + CSS rather than the inline `style.display` this\n * used to set on a child: an attribute is themeable, is visible to a consumer's\n * own rules, and does not clobber a `display` the consumer had set (the restore\n * wrote `''`, not the previous value).\n *\n * Returns the TOKEN for this panel. Pass it back to `hidePanel` so a presenter\n * that has already lost the slot cannot close the panel that replaced it, and\n * supply `onEvict` to be told when that happens — an owner that is not told is an\n * owner whose promise nobody can settle.\n */\n showPanel(\n panel: HTMLElement,\n options?: {\n submitEnabled?: boolean;\n onSubmit?: () => void;\n /**\n * What the send button means for this panel — `'none'` if it has no act\n * for it, in which case the button is not drawn. See\n * {@link AparteComposerPanelMode}.\n */\n mode?: AparteComposerPanelMode;\n /** Called when something other than this owner closes the panel. */\n onEvict?: () => void;\n },\n ): symbol {\n // Evict rather than hide: the previous owner is awaiting an answer it will\n // never get, and it is the only thing that can settle its own promise.\n this._evictPanel();\n const inputEl = this.querySelector('aparte-composer-input') as HTMLElement | null;\n this.setAttribute('data-panel-active', '');\n panel.dataset['apartePanel'] = 'true';\n if (inputEl) {\n inputEl.insertAdjacentElement('afterend', panel);\n } else {\n this.appendChild(panel);\n }\n this._panelActive = true;\n this._panelSubmitEnabled = options?.submitEnabled ?? false;\n this._panelMode = options?.mode ?? 'submit';\n this._reflectPanelMode();\n this._panelOnSubmit = options?.onSubmit ?? null;\n this._panelOnEvict = options?.onEvict ?? null;\n const token = Symbol('aparte-composer-panel');\n this._panelToken = token;\n this._emit('panel-change', { active: true, submitEnabled: this._panelSubmitEnabled, mode: this._panelMode });\n return token;\n }\n\n /**\n * Remove the panel, tell its owner, and restore the composer's own controls.\n *\n * With a `token`, this closes the panel only if that token still owns the slot —\n * so a presenter settling late cannot tear down the panel that replaced it. With\n * no token it closes whatever is there, which is what a consumer driving the\n * composer directly means.\n *\n * BOTH forms notify. The no-token form used to call `_teardownPanel` directly,\n * which nulls `_panelOnEvict` without ever calling it — so the documented public\n * `hidePanel()` closed an open approval panel and left its request pending\n * forever. The turn hung on \"waiting for you\", and because\n * `AparteConfig.requestUserInput` chains each request on the previous one, NO\n * further question or approval on that config was ever presented again, for the\n * life of the page.\n *\n * The old JSDoc justified the silent branch as \"what `reset()` needs\". It was not:\n * `reset()` calls `_evictPanel()`, which notifies. The branch had no consumer and\n * one failure mode.\n *\n * The two forms differ on purpose, and the difference is who already knows:\n *\n * - **With a matching token** the OWNER is closing its own panel, which is what\n * `<aparte-elicitation>`'s `close()` does right after it resolves. It must NOT be\n * notified — telling it \"you were evicted\" for a request it just settled would\n * fire `onEvict` against a finished promise.\n * - **With no token** somebody else is closing a panel they do not own, so the owner\n * cannot know and has to be told. That includes the presenter's own defensive\n * `hidePanel(undefined)` when it settles before `showPanel` ran: whatever is open\n * then belongs to another request, and that request must not orphan.\n */\n hidePanel(token?: symbol): void {\n if (token !== undefined) {\n if (token !== this._panelToken) return;\n this._teardownPanel();\n return;\n }\n this._evictPanel();\n }\n\n /** Close the panel AND tell its owner, so a pending request never orphans. */\n private _evictPanel(): void {\n const onEvict = this._panelOnEvict;\n // State first, callback second: the owner's settle path calls `hidePanel` with\n // its own token, which must find the slot already empty rather than recurse.\n this._teardownPanel();\n onEvict?.();\n }\n\n private _teardownPanel(): void {\n const existing = this.querySelector('[data-aparte-panel]') as HTMLElement | null;\n if (existing) existing.remove();\n this.removeAttribute('data-panel-active');\n this._panelActive = false;\n this._panelSubmitEnabled = false;\n this._panelMode = 'submit';\n this._reflectPanelMode();\n this._panelOnSubmit = null;\n this._panelOnEvict = null;\n this._panelToken = null;\n this._emit('panel-change', { active: false, submitEnabled: false, mode: 'submit' });\n this.focus();\n }\n\n /**\n * Update the send button's state while a panel is active.\n *\n * `mode` moves with it because both change on the same event — answering the\n * question you are on can enable the button AND turn it from \"advance\" into\n * \"submit\" (when it was the last one), and two separate calls would flash a\n * wrong icon between them.\n */\n setPanelSubmitEnabled(enabled: boolean, mode?: AparteComposerPanelMode): void {\n if (!this._panelActive) return;\n this._panelSubmitEnabled = enabled;\n if (mode) this._panelMode = mode;\n this._reflectPanelMode();\n this._emit('panel-change', { active: true, submitEnabled: enabled, mode: this._panelMode });\n }\n\n /**\n * Publish the panel mode as an attribute, so CSS can act on it.\n *\n * The same reasoning `data-panel-active` is set for and the same one that took\n * the old `style.display` off a child: an attribute is themeable, is visible to\n * a consumer's own rules, and does not clobber what the consumer set. It is what\n * lets `'none'` remove the send button without this component reaching into it.\n */\n private _reflectPanelMode(): void {\n if (this._panelActive) this.setAttribute('data-panel-mode', this._panelMode);\n else this.removeAttribute('data-panel-mode');\n }\n\n get panelActive(): boolean { return this._panelActive; }\n\n /**\n * Recompute the model gate from the resolved config. When\n * `requireModelSelection` is on and no model is selected, block sending and\n * reflect `data-model-gated` so the shipped CSS greys the composer. Re-runs on\n * every config change (e.g. the model selector's auto-select firing).\n */\n private _evaluateModelGate(): void {\n const gated = this._cfg.getRequireModelSelection() && !this._cfg.hasSelectedModel();\n if (gated === this._modelGated) return;\n this._modelGated = gated;\n this.toggleAttribute('data-model-gated', gated);\n }\n\n /** Submit the current value. Called by aparte-composer-send or programmatically. */\n submit(): void {\n if (this._panelActive) {\n // `'none'` is authoritative over `submitEnabled`, and deliberately so: the\n // two are set by the same caller and can disagree, and the mode is the one\n // that says whether an act exists at all. Reached by Enter inside the panel\n // and by a consumer calling `submit()` directly — the button itself is not\n // drawn in this mode.\n if (this._panelMode !== 'none' && this._panelSubmitEnabled) this._panelOnSubmit?.();\n return;\n }\n if (this._streaming) {\n this.cancel();\n return;\n }\n const value = this._value.trim();\n if (!value && this._attachments.length === 0) return;\n if (this.disabled) return;\n if (this._modelGated) return; // no model selected yet (require-model gate)\n\n this._emit('submit', { value, attachments: this._attachments });\n\n const detail: AparteSendEventDetail = {\n content: value,\n timestamp: Date.now(),\n targetId: this.targetId ?? undefined,\n files: this._attachments.length > 0 ? [...this._attachments] : undefined,\n };\n\n this.dispatchEvent(new CustomEvent<AparteSendEventDetail>('aparte-send', {\n bubbles: true,\n composed: true,\n detail,\n }));\n\n // Clear after send\n this.setValue('');\n this.clearAttachments();\n }\n\n /** Cancel the current streaming response. */\n cancel(): void {\n this._emit('cancel', {});\n // Public, element-scoped signal — symmetric with `aparte-send` on submit,\n // for consumers that want to observe cancel on the composer itself.\n this.dispatchEvent(new CustomEvent('aparte-cancel', { bubbles: true, composed: true }));\n // aparte-abort → tells AparteClient to actually stop the stream\n // aparte-message-aborted → resets the composer's own streaming state\n // Scope the abort to this composer's host so cancelling one chat doesn't\n // abort every scoped client / reset every composer on the page.\n /*\n * `_ownTargetId()`, not the bare attribute.\n *\n * The receive side got this fix and the SEND side did not, which left the whole\n * scoping inert in raw core: nothing writes the `target` attribute in the\n * hand-written markup the quick start shows, so this detail carried\n * `targetId: undefined` — and `_isForThisComposer` treats a missing id as \"for\n * everyone\". So Stop in chat A still tore down chat B's open panel, which is\n * exactly the failure `_ownTargetId`'s own docblock describes as fixed.\n *\n * Both sides now resolve the same way: the attribute if the wrapper set one,\n * otherwise the id of the chat host above us.\n */\n const abortDetail = { targetId: this._ownTargetId() };\n window.dispatchEvent(new CustomEvent('aparte-abort', { bubbles: false, detail: abortDetail }));\n window.dispatchEvent(new CustomEvent('aparte-message-aborted', { bubbles: false, detail: abortDetail }));\n }\n\n /**\n * Reset the composer to its initial state.\n * Clears value, attachments, and hides any active panel.\n * Call this when switching conversations.\n */\n reset(): void {\n this.setValue('');\n this.clearAttachments();\n if (this._panelActive) this._evictPanel();\n }\n\n /** Focus the input primitive inside this composer. */\n override focus(): void {\n const input = this.querySelector('aparte-composer-input') as HTMLElement | null;\n input?.focus();\n }\n\n // ── Internal pub/sub ────────────────────────────────────────────────────\n\n _emit<K extends AparteComposerEventType>(event: K, payload: AparteComposerEventMap[K]): void {\n this._listeners.get(event)?.forEach(cb => cb(payload));\n // Mirror state changes to a public DOM event so elements outside the\n // composer package can observe them without the private bus.\n if (AparteComposer._STATE_EVENTS.has(event)) {\n this.dispatchEvent(new CustomEvent<AparteComposerChangeEventDetail>('aparte-composer-change', {\n bubbles: true,\n composed: true,\n detail: { state: this.getState(), composer: this },\n }));\n }\n }\n\n _on<K extends AparteComposerEventType>(event: K, cb: (payload: AparteComposerEventMap[K]) => void): () => void {\n if (!this._listeners.has(event)) this._listeners.set(event, new Set());\n this._listeners.get(event)!.add(cb as unknown as (payload: unknown) => void);\n return () => this._listeners.get(event)?.delete(cb as unknown as (payload: unknown) => void);\n }\n\n // ── Window events ───────────────────────────────────────────────────────\n\n /** A window lifecycle event is for THIS composer when neither side is scoped\n * (single-instance broadcast) or the target ids match (multi-chat page).\n * Without this filter, streaming in one chat flips every composer's state. */\n private _isForThisComposer(e: Event): boolean {\n const evtTargetId = (e as CustomEvent).detail?.targetId as string | undefined;\n return !evtTargetId || !this._ownTargetId() || evtTargetId === this._ownTargetId();\n }\n\n /**\n * Which chat this composer belongs to: its `target` attribute, or the id of the\n * chat host above it.\n *\n * All four wrappers set `target` themselves, so the attribute alone identified a\n * composer there. In RAW core — the documented quick start, where the markup is\n * hand-written — nothing sets it, so `!this.targetId` was true and this composer\n * accepted every chat's lifecycle events: on a two-chat page, one chat's Stop\n * tore down the other's open elicitation panel while its tool call kept waiting,\n * i.e. the question vanished and the turn hung.\n *\n * Found by a two-chat test written for the elicitation presenter, which is the\n * only reason it surfaced: raw core with two chats is a shape nothing exercised.\n * The hosts matched are the ones `aparte-chat-bubble._resolveTargetId()` matches,\n * for the reason written there — Angular's wrapper root IS `<aparte-chat>`, the\n * other three render a `[data-aparte-chat]` div.\n */\n private _ownTargetId(): string | undefined {\n const attr = this.targetId;\n if (attr) return attr;\n let el: HTMLElement | null = this.parentElement;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || tag === 'aparte-chat-component' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n private _handleMessageStart(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streaming = true;\n this._emit('streaming-change', { streaming: true });\n }\n\n private _handleMessageDone(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streaming = false;\n this._emit('streaming-change', { streaming: false });\n // Always hide any active panel when a message lifecycle ends\n if (this._panelActive) this._evictPanel();\n }\n}\n\nif (!customElements.get('aparte-composer')) {\n customElements.define('aparte-composer', AparteComposer);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Contenteditable text input primitive.\n *\n * The element owns its subtree: on connect it writes one `.aparte-ci-editor`\n * contenteditable and binds its listeners to that node, so children you place inside are\n * replaced. There is nothing to project here — style the generated editor through the CSS\n * variables below, or replace the whole primitive.\n *\n * Enter submits and Shift+Enter inserts a newline; `submit-on-enter=\"false\"` on the\n * composer inverts that mapping, and Enter never submits mid-IME-composition — the key\n * that confirms a CJK candidate must not send the message. The editor auto-expands with\n * its content up to `max-height`, then scrolls. Paste is intercepted: text lands as plain\n * text with its markup stripped, and a pasted image goes to the composer's attachments.\n *\n * Without an `<aparte-composer>` ancestor it still works, and that is deliberate: a\n * submitting Enter then dispatches `aparte-composer-submit` instead of calling\n * `root.submit()`, which is how the bubble's inline editor reuses this primitive.\n * Everything the root owns goes with it though — the mirrored value, the placeholder\n * fallback, the disabled/streaming sync and image paste all need the composer.\n *\n * Not a `<textarea>` and not a stand-in for one: being a contenteditable it has no form\n * value, no `name` and no native validation, and `getValue()` returns trimmed text with\n * `<br>` serialized back to newlines. Use it for the chat draft, not as a form control.\n *\n * @element aparte-composer-input\n *\n * @fires aparte-composer-submit - A submitting Enter was pressed with no\n * `<aparte-composer>` ancestor to submit to; with one it calls `root.submit()` and\n * dispatches nothing. No detail — the host that placed this primitive reads\n * `getValue()`.\n *\n * @attr {boolean} disabled - Makes the field non-editable; the composer's own `disabled` also reaches it.\n * @attr {string} placeholder - Placeholder text (fallback: reads from aparte-composer)\n * @attr {number} max-height - Max height in px before scroll (default: 200)\n * @attr {number} min-height - Min height in px. When omitted, the stylesheet's\n * min-height governs (44px in aparte.css) — so themes can\n * resize the editor in pure CSS without being fought by\n * an inline height.\n *\n * @cssprop [--aparte-composer-control-size=44px] - Single-line min-height of the editor.\n * Inside the `.aparte-composer-row` layout helper the composer's buttons read\n * the same token, so one value resizes that whole control set and the row stays\n * aligned.\n * @cssprop [--aparte-input-padding-y=10px] - Vertical padding inside the editor.\n * @cssprop [--aparte-input-padding-x=12px] - Horizontal padding inside the editor.\n * @cssprop [--aparte-input-font-size=14px] - Editor font size.\n * @cssprop [--aparte-input-line-height=1.5] - Editor line height — also what the\n * auto-expand measures, so changing it changes the height the editor settles at\n * (until `max-height` clamps it).\n * @cssprop --aparte-text - Text and caret colour of the editor.\n * @cssprop --aparte-input-placeholder - Colour of the placeholder drawn by\n * `:empty::before` (falls back to `--aparte-text-muted`).\n * @cssprop --aparte-input-bg - Field background, applied only when this input is the\n * bubble's inline editor (`.aparte-message[data-editing]`) — inside a composer\n * the shell paints the surface instead.\n * @cssprop --aparte-input-border - Border colour of that same edit-mode box.\n * @cssprop [--aparte-radius-input=8px] - Corner radius of the edit-mode box.\n * @cssprop --aparte-input-focus-border - Border colour of the edit-mode box while it\n * holds focus (`:focus-within`).\n *\n * @example\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input placeholder=\"Ask anything…\" max-height=\"320\" style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerInput extends HTMLElement {\n private _editor: HTMLDivElement | null = null;\n private _maxHeight = 200;\n private _minHeight = 44;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handlers\n private _onInput = this._handleInput.bind(this);\n private _onKeydown = this._handleKeydown.bind(this);\n private _onFocus = this._handleFocus.bind(this);\n private _onBlur = this._handleBlur.bind(this);\n private _onPaste = this._handlePaste.bind(this);\n\n static get observedAttributes(): string[] {\n return ['placeholder', 'max-height', 'min-height', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n this._scheduleInitialReflow();\n // The placeholder is the one string in this composer a sighted user can\n // actually read, and it was frozen at the language of the first render.\n // `_updatePlaceholder` already exists for the attribute path — a locale\n // change is the same refresh, from a different trigger.\n this._unsubscribes.push(subscribeConfigChange(this, () => this._updatePlaceholder()));\n }\n\n disconnectedCallback(): void {\n this._editor?.removeEventListener('input', this._onInput);\n this._editor?.removeEventListener('keydown', this._onKeydown);\n this._editor?.removeEventListener('focus', this._onFocus);\n this._editor?.removeEventListener('blur', this._onBlur);\n this._editor?.removeEventListener('paste', this._onPaste);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'placeholder') this._updatePlaceholder();\n if (name === 'max-height') this._maxHeight = parseInt(value || '200', 10);\n if (name === 'min-height') this._minHeight = parseInt(value || '44', 10);\n if (name === 'disabled') this._updateDisabled(value !== null);\n }\n\n // ── Public API ──────────────────────────────────────────────────────────\n\n /**\n * The editor's text, with `<br>` serialized back to newlines — `textContent`\n * alone would collapse a multi-line draft onto a single line.\n */\n getValue(): string {\n // `textContent` drops `<br>`, so multi-line content would collapse onto one\n // line. Serialize the editor ourselves: text nodes as-is, `<br>` → newline.\n // We keep the editor flat (text + <br>, see _handleKeydown), but descend into\n // any stray wrapper for safety.\n if (!this._editor) return '';\n let out = '';\n const walk = (node: Node): void => {\n node.childNodes.forEach(child => {\n if (child.nodeType === Node.TEXT_NODE) out += child.textContent ?? '';\n else if (child.nodeName === 'BR') out += '\\n';\n else walk(child);\n });\n };\n walk(this._editor);\n return out.trim();\n }\n\n /** Replace the editor's content and mirror the value onto the parent composer. */\n setValue(value: string): void {\n if (!this._editor) return;\n this._editor.textContent = value;\n this._updatePlaceholderVisibility();\n this._adjustHeight();\n this._getRoot()?.setValue(value);\n }\n\n /** Empty the editor and mirror the empty value onto the parent composer. */\n clear(): void {\n if (!this._editor) return;\n this._editor.innerHTML = '';\n this._updatePlaceholderVisibility();\n this._adjustHeight();\n this._getRoot()?.setValue('');\n }\n\n /** Focus the inner contenteditable rather than the host element. */\n override focus(): void { this._editor?.focus(); }\n /** Blur the inner contenteditable rather than the host element. */\n override blur(): void { this._editor?.blur(); }\n\n /** Focus the editor and place the caret at the very end of its content. */\n focusEnd(): void {\n if (!this._editor) return;\n this._editor.focus();\n const sel = this.ownerDocument?.getSelection();\n if (!sel) return;\n const range = this.ownerDocument.createRange();\n range.selectNodeContents(this._editor);\n range.collapse(false);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _getPlaceholder(): string {\n return this.getAttribute('placeholder')\n || this._getRoot()?.placeholder\n || resolveConfig(this).t('inputPlaceholder')\n || 'Type a message...';\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-ci-editor')) return;\n\n const disabled = this.hasAttribute('disabled') || this._getRoot()?.disabled || false;\n // `placeholder` may come straight from a host attribute (often bound to\n // dynamic/translated text) — escape before it lands in a double-quoted\n // attribute so a stray `\"` can't break out and inject markup.\n const placeholder = escapeAttr(this._getPlaceholder());\n\n this.innerHTML = `<div\n class=\"aparte-ci-editor\"\n contenteditable=\"${!disabled}\"\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-label=\"${placeholder}\"\n tabindex=\"0\"\n aria-disabled=\"${disabled}\"\n data-placeholder=\"${placeholder}\"\n ></div>`;\n\n this._editor = this.querySelector('.aparte-ci-editor');\n this._editor?.addEventListener('input', this._onInput);\n this._editor?.addEventListener('keydown', this._onKeydown);\n this._editor?.addEventListener('focus', this._onFocus);\n this._editor?.addEventListener('blur', this._onBlur);\n this._editor?.addEventListener('paste', this._onPaste);\n\n this._adjustHeight();\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n // Sync disabled state from root\n this._unsubscribes.push(\n root._on('disabled-change', ({ disabled }) => this._updateDisabled(disabled))\n );\n\n // The editor shows what the composer says.\n //\n // Compared, not special-cased. While the user types, `_handleInput` has just\n // pushed this very value up, so the two sides are equal and nothing is\n // rewritten — which is what keeps the caret where the user left it, and was the\n // entire reason the old form acted on `''` alone. Any other value arrived from\n // somewhere else: `setValue()` on the composer, or the `''` that `submit()`\n // writes on its way out. Both now land, where only the second one used to.\n //\n // Compared against `value.trim()` because `getValue()` trims. A padded value\n // would never look equal otherwise, and the mirror back through `setValue`\n // would re-enter this callback forever.\n this._unsubscribes.push(\n root._on('value-change', ({ value }) => {\n if (this.getValue() === value.trim()) return;\n if (value === '') this.clear();\n else this.setValue(value);\n })\n );\n\n // Sync streaming state — disable input while streaming\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n this._updateDisabled(streaming || root.disabled);\n })\n );\n }\n\n private _handleInput(): void {\n this._adjustHeight();\n // After a delete-all, contenteditable leaves residual `<br>` tags\n // (Chromium especially) so the `:empty` CSS pseudo-class no longer\n // matches and the placeholder stays hidden. Force-clear when text\n // content reduces to whitespace so the placeholder reappears.\n if (this._editor && !this._editor.textContent?.trim() && this._editor.innerHTML !== '') {\n this._editor.innerHTML = '';\n }\n this._updatePlaceholderVisibility();\n const value = this.getValue();\n this._getRoot()?.setValue(value);\n }\n\n private _handleKeydown(e: KeyboardEvent): void {\n // During IME composition (CJK/Japanese/Korean), Enter confirms the\n // candidate — it must never submit. `keyCode === 229` is the legacy\n // signal for engines that don't set `isComposing`.\n if (e.isComposing || e.keyCode === 229) return;\n if (e.key !== 'Enter') return;\n // `submit-on-enter` (default true): Enter submits, Shift+Enter inserts\n // a newline. When false the mapping inverts — Shift+Enter submits and\n // a bare Enter inserts a newline (lets the user author multi-line).\n const submitOnEnter = this._getRoot()?.submitOnEnter ?? true;\n const submits = submitOnEnter ? !e.shiftKey : e.shiftKey;\n if (submits) {\n e.preventDefault();\n const root = this._getRoot();\n if (root) {\n root.submit();\n } else {\n // Standalone (no <aparte-composer> parent, e.g. the bubble's inline\n // editor): there is no root to submit to, so surface the intent as a\n // DOM event the host can act on. Keeps this primitive reusable on its\n // own — the IME guard + submitOnEnter mapping above stay the single\n // source of truth for \"when to submit\".\n this.dispatchEvent(new CustomEvent('aparte-composer-submit', { bubbles: true }));\n }\n } else {\n // Newline branch. Take control instead of the browser's contenteditable\n // default, which inserts <div>/<br> wrappers that resist deletion.\n // `insertLineBreak` inserts a single <br> and manages the trailing bogus\n // <br> so backspace removes it cleanly (the \"can't delete the newline\" bug).\n e.preventDefault();\n // Nothing to break on an empty field — don't seed a leading blank line.\n if (!this._editor?.textContent) return;\n this.ownerDocument?.execCommand('insertLineBreak');\n }\n }\n\n private _handleFocus(): void {\n this.classList.add('aparte-is-focused');\n }\n\n private _handleBlur(): void {\n this.classList.remove('aparte-is-focused');\n }\n\n private _handlePaste(e: ClipboardEvent): void {\n e.preventDefault();\n const cd = e.clipboardData;\n if (!cd) return;\n\n // Image paste → push to root attachments\n const imageFile = Array.from(cd.items).find(i => i.type.startsWith('image/'))?.getAsFile();\n if (imageFile) {\n this._getRoot()?.addAttachments([imageFile]);\n return;\n }\n\n // Plain text paste\n const text = cd.getData('text/plain');\n if (text) {\n document.execCommand('insertText', false, text);\n }\n }\n\n private _adjustHeight(): void {\n if (!this._editor) return;\n // Measure with height:0 (not auto): an explicit height opts the editor\n // OUT of any parent flex `align-items: stretch`, so scrollHeight reflects\n // the real content — not the (taller) row it may be stretched into. With\n // `auto`, a stretching parent inflates scrollHeight and the editor gets\n // stuck tall until the next reflow.\n this._editor.style.height = '0px';\n // Floor: the `min-height` ATTRIBUTE when explicitly set; otherwise defer\n // to the stylesheet (CSS min-height caps an inline height anyway). A\n // hardcoded JS floor would override theme CSS with an inline style and\n // break editor/controls alignment in restyled composers.\n const floor = this.hasAttribute('min-height') ? this._minHeight : 0;\n const contentHeight = this._editor.scrollHeight;\n const h = Math.min(Math.max(contentHeight, floor), this._maxHeight);\n this._editor.style.height = `${h}px`;\n this._editor.style.overflowY = contentHeight > this._maxHeight ? 'auto' : 'hidden';\n }\n\n /**\n * The first `_adjustHeight()` runs synchronously in `_render()` on connect —\n * before the stylesheet, flex layout and web fonts have necessarily settled.\n * On an unstabilized layout `scrollHeight` can read inflated, leaving the\n * editor stuck tall (misaligned with the composer controls) until the first\n * keystroke re-measures it. Re-measure once the layout is ready so it's\n * correct from the first paint.\n */\n private _scheduleInitialReflow(): void {\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => this._adjustHeight());\n }\n const fonts = (document as Document & { fonts?: { ready?: Promise<unknown> } }).fonts;\n fonts?.ready?.then(() => this._adjustHeight()).catch(() => { /* fonts unavailable — rAF path covers it */ });\n }\n\n private _updatePlaceholder(): void {\n if (this._editor) {\n const p = this._getPlaceholder();\n this._editor.setAttribute('data-placeholder', p);\n this._editor.setAttribute('aria-label', p);\n }\n }\n\n private _updatePlaceholderVisibility(): void {\n // Handled by CSS :empty — nothing to do\n }\n\n private _updateDisabled(disabled: boolean): void {\n if (!this._editor) return;\n this._editor.setAttribute('contenteditable', String(!disabled));\n this._editor.setAttribute('aria-disabled', String(disabled));\n }\n\n /** Escape a value before it lands in a double-quoted HTML attribute. */\n}\n\nif (!customElements.get('aparte-composer-input')) {\n customElements.define('aparte-composer-input', AparteComposerInput);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer, AparteComposerPanelMode } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Submit button primitive for <aparte-composer>.\n *\n * One control, four meanings: **send**, **stop** while the root is streaming, and — when\n * an elicitation panel is open — **submit** this answer or **advance** to the next\n * question. The panel outranks streaming: while one is open the button stays the answer\n * control and a streaming change is ignored. The icon moves with the meaning (paper\n * plane, square, check, chevron), because a check on a form with three questions left is\n * as wrong as a paper plane that means \"answer\". All four are decided by the root's state\n * — its `value`, `attachments`, `disabled`, `streaming` and the panel payload it\n * broadcasts — not by anything this element owns, which is why it recomputes its chrome\n * rather than re-rendering: a rebuild would put a paper plane back mid-stream, drop out\n * of answer mode, and take the focus off the control most likely to be holding it.\n *\n * \"Empty\" counts attachments: a pending attachment with no text still enables the\n * button, because that is a message the composer can send.\n *\n * It owns its subtree — the button is generated on connect and children placed inside\n * are replaced, so there is nothing to project. The host element itself is\n * `display: contents`, so it adds no box: the layout comes from whatever flex row you put\n * it in, and the CSS variables below style the inner button.\n *\n * It needs an `<aparte-composer>` ancestor: without one the button renders disabled, no\n * root event ever reaches it, and a click has nothing to submit to.\n *\n * It is not the place to gate on model selection: the opt-in\n * `aparteGlobalConfig.setRequireModelSelection()` gate already blocks this element's\n * pointer events through `aparte-composer[data-model-gated]`.\n *\n * @element aparte-composer-send\n *\n * @cssprop [--aparte-composer-control-size=44px] - Width/height of the button inside the\n * `.aparte-composer-row` layout helper, shared with the input's single-line\n * height so the row stays aligned. It wins over `--aparte-send-btn-size` there.\n * @cssprop [--aparte-send-btn-size=36px] - Width/height of the button outside that row\n * helper. On coarse pointers it is raised to `--aparte-touch-target-size`.\n * @cssprop [--aparte-touch-target-size=44px] - Hit-area floor applied to the button\n * under `@media (pointer: coarse)`.\n * @cssprop [--aparte-radius-send-btn=6px] - Corner radius of the button.\n * @cssprop --aparte-primary - Button background.\n * @cssprop --aparte-primary-hover - Button background on hover, while enabled.\n * @cssprop --aparte-on-primary - The glyph's colour. Undeclared by default, which means\n * the recipe derives it from `--aparte-primary` itself, so a theme that changes\n * the fill gets a readable glyph with no second edit. Declare it to choose one\n * — it then applies to every primary control, which is the honest scope.\n * @cssprop [--aparte-ink-flip=0.57] - Fill lightness at which the derived ink flips from\n * dark to light, for every solid control.\n * @cssprop [--aparte-ink-dark=0.176] - How dark that derived ink goes. Not 0: at zero\n * lightness OKLCH drops the chroma, and the ink loses the fill's own hue.\n * @cssprop --aparte-send-disabled-bg - Background while disabled (falls back to\n * `--aparte-primary`, which is then dimmed by opacity).\n *\n * @example\n * <!-- One button for both halves of the turn: it submits, and while a reply streams it\n * becomes the stop button. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerSend extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n /**\n * The last `panel-change` payload.\n *\n * This button has four meanings — send, stop, submit an answer, advance to the\n * next question — and three of them are decided by state it does not own: the\n * root's `streaming`, and this payload. It was read straight out of the event's\n * arguments and thrown away, so nothing could recompute the button's chrome\n * afterwards; a config change had no way to know which of the four to write.\n */\n private _panel: { active: boolean; submitEnabled: boolean; mode: AparteComposerPanelMode } | null = null;\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-cs-button')) return;\n\n const label = resolveConfig(this).t('sendButton') || 'Send';\n const icon = this._getSendIcon();\n const root = this._getRoot();\n const disabled = !root || root.disabled || root.value.trim() === '';\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--primary aparte-btn--solid aparte-btn--icon aparte-cs-button aparte-send-button\"\n aria-label=\"${escapeAttr(label)}\"\n title=\"${escapeAttr(label)}\"\n ${disabled ? 'disabled' : ''}\n >${icon}</button>`; // safe-text: _getSendIcon() returns the provider's SVG markup — escaping it would print the source\n\n this._button = this.querySelector('.aparte-cs-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('value-change', () => this._syncState())\n );\n this._unsubscribes.push(\n root._on('disabled-change', () => this._syncState())\n );\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n // If panel is active, streaming state change doesn't affect the button —\n // the panel controls it (submit answer, not stop stream)\n if (this._getRoot()?.panelActive) return;\n this._syncStreamingState(streaming);\n })\n );\n this._unsubscribes.push(\n root._on('attachments-change', () => this._syncState())\n );\n this._unsubscribes.push(\n root._on('panel-change', (payload) => {\n this._panel = payload;\n this._refreshChrome();\n })\n );\n // A config change — a new icon set, another language — has to write the\n // chrome for whichever of the four meanings the button currently carries.\n this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));\n }\n\n /**\n * Write the chrome for the mode the button is IN, deciding before writing.\n *\n * Never `_render()`: it returns early once the button exists, and its own\n * disabled/icon computation consults neither `root.streaming` nor the panel — so\n * rebuilding mid-turn would put a paper plane back while a reply was still\n * streaming, and rebuilding with the question panel open would silently drop out\n * of answer mode. It would also take the focus off the one control in this\n * composer most likely to be holding it.\n */\n private _refreshChrome(): void {\n if (!this._button) return;\n if (this._panel?.active) { this._syncPanelState(); return; }\n if (this._getRoot()?.streaming) { this._syncStreamingState(true); return; }\n this._syncState();\n }\n\n /**\n * Panel open: this one button now means \"answer\", and WHICH answer depends on\n * where you are in the form.\n *\n * The icon has to move with the meaning: it drew a paper plane while the label\n * already said \"Submit\", so it read as \"send a message\" while it meant \"answer\n * this question\". And a check on a form with three questions left was just as\n * wrong — hence a chevron while there is more ahead. The visual is what a user\n * reads.\n */\n private _syncPanelState(): void {\n const panel = this._panel;\n if (!this._button || !panel?.active) return;\n const cfg = resolveConfig(this);\n // No act for this button on this panel: its options settle themselves. The\n // composer's `[data-panel-mode=\"none\"]` rule takes it out of the layout — and\n // `display: none` takes it out of the accessibility tree with it, so there is\n // no `aria-hidden` or `tabindex` to set here and none to restore when the mode\n // flips back. What this branch does is refuse to RELABEL it: leaving it\n // announced as a disabled \"Submit\" is the lie, not the button.\n if (panel.mode === 'none') {\n this._button.disabled = true;\n return;\n }\n const advancing = panel.mode === 'advance';\n this._button.disabled = !panel.submitEnabled;\n this._button.innerHTML = advancing ? cfg.getIcon('nextBranch') : this._getSubmitIcon();\n const label = advancing\n ? (cfg.t('elicitationNext') || 'Next')\n : (cfg.t('submitButton') || 'Submit');\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.classList.remove('aparte-is-streaming');\n }\n\n private _handleClick(e: MouseEvent): void {\n e.preventDefault();\n this._getRoot()?.submit();\n }\n\n private _syncState(): void {\n const root = this._getRoot();\n if (!root || !this._button) return;\n if (root.streaming) return; // streaming state managed separately\n\n const isEmpty = root.value.trim() === '' && root.attachments.length === 0;\n this._button.disabled = root.disabled || isEmpty;\n this._button.innerHTML = this._getSendIcon();\n const label = resolveConfig(this).t('sendButton') || 'Send';\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.classList.remove('aparte-is-streaming');\n }\n\n private _syncStreamingState(streaming: boolean): void {\n if (!this._button) return;\n if (streaming) {\n this._button.disabled = false;\n this._button.innerHTML = this._getStopIcon();\n // Was the bare literal 'Stop', so no locale could reach it — the same\n // gap `aparte-composer-cancel` had, on a second element. The key is\n // declared now.\n const label = resolveConfig(this).t('stopButton') || 'Stop';\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.classList.add('aparte-is-streaming');\n } else {\n this._syncState();\n }\n }\n\n /**\n * The icon for submitting an ANSWER, which is not the same act as sending a\n * message — one button, two meanings, and it has to say which one it is.\n */\n private _getSubmitIcon(): string {\n // No fallback chain: `getIcon` already returns a built-in when the consumer's\n // icon set has no entry, so `|| getIcon('send')` was dead code — written on the\n // assumption that it could come back empty, and a test proved it cannot.\n return resolveConfig(this).getIcon('check');\n }\n\n private _getSendIcon(): string {\n return resolveConfig(this).getIcon('send') || 'Send';\n }\n\n private _getStopIcon(): string {\n return resolveConfig(this).getIcon('stop');\n }\n}\n\nif (!customElements.get('aparte-composer-send')) {\n customElements.define('aparte-composer-send', AparteComposerSend);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Cancel/stop streaming button primitive for <aparte-composer>.\n *\n * Most composers should not use this element. `<aparte-composer-send>` already becomes\n * the stop button while a reply streams, so adding this one gives you a second, equally\n * working way to stop; reach for it only when you want stop to live somewhere the send\n * button is not.\n *\n * It renders hidden, and only the root reveals it — the `root.streaming` check on connect,\n * then each `streaming-change` — so it needs an `<aparte-composer>` ancestor to be\n * reachable at all: standalone, nothing flips `hidden` and the click has no `cancel()` to\n * call. A locale or icon-set change is re-read in place rather than re-rendered, for the\n * same reason: a rebuild renders it hidden again, making the stop button vanish mid-turn.\n *\n * It owns its subtree — the button is generated on connect and children placed inside\n * are replaced, so there is nothing to project. The host element is `display: contents`\n * and adds no box of its own; the row you put it in provides the layout, and the CSS\n * variables below style the inner button, which is deliberately a quiet action button\n * rather than a filled one.\n *\n * @element aparte-composer-cancel\n *\n * @cssprop [--aparte-composer-control-size=44px] - Width/height of the button inside the\n * `.aparte-composer-row` layout helper, shared with the composer's other\n * controls so the row stays aligned.\n * @cssprop [--aparte-radius-action-btn=4px] - Corner radius of the button.\n * @cssprop --aparte-neutral - Icon colour at rest (the button's background is\n * transparent).\n * @cssprop --aparte-text - Icon colour on hover.\n * @cssprop --aparte-surface-2 - Button background on hover.\n *\n * @example\n * <!-- Only needed when you want a SEPARATE stop button: <aparte-composer-send> already\n * turns into one while streaming. This stays hidden until then. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-cancel></aparte-composer-cancel>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerCancel extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-cc-button')) return;\n\n const label = resolveConfig(this).t('stopButton') || 'Stop';\n const icon = this._getStopIcon();\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--icon aparte-cc-button\"\n aria-label=\"${escapeAttr(label)}\"\n title=\"${escapeAttr(label)}\"\n hidden\n >${icon}</button>`; // safe-text: _getStopIcon() returns the provider's SVG markup — escaping it would print the source\n\n this._button = this.querySelector('.aparte-cc-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n if (this._button) this._button.hidden = !streaming;\n })\n );\n\n // Sync initial state\n if (root.streaming && this._button) this._button.hidden = false;\n }\n\n private _handleClick(e: MouseEvent): void {\n e.preventDefault();\n this._getRoot()?.cancel();\n }\n\n /**\n * Re-read the accessible name and the icon in place.\n *\n * `hidden` is NOT touched: `_render()` always renders this button hidden and only\n * the root's `streaming-change` listener ever un-hides it, so a rebuild would make\n * the stop button vanish in the middle of a turn.\n */\n private _refreshChrome(): void {\n if (!this._button) return;\n const label = resolveConfig(this).t('stopButton') || 'Stop';\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.innerHTML = this._getStopIcon();\n }\n\n private _getStopIcon(): string {\n return resolveConfig(this).getIcon('stop');\n }\n}\n\nif (!customElements.get('aparte-composer-cancel')) {\n customElements.define('aparte-composer-cancel', AparteComposerCancel);\n}\n","import type { AparteComposer } from './aparte-composer.js';\nimport { resolveConfig } from '../../config/config-context.js';\nimport { escapeAttr } from '../../utils/escape.js';\n\n\n/**\n * Renders a square thumbnail tile for each file attached to the root composer.\n *\n * Image files show the actual picture; other files show an extension badge.\n * The filename and a remove (✗) button surface on hover. Clicking an image asks\n * the app to open it full-size (`aparte-attachment-preview`) — only when the app\n * declared `attachmentPreview` via `aparteGlobalConfig.setHostHandlers()`.\n * Automatically hidden when there are no attachments. It reads the nearest\n * <aparte-composer> ancestor; without one it renders nothing and stays hidden.\n *\n * This is the PENDING strip: what the user has attached and not yet sent. It mirrors\n * `composer.attachments` and rewrites itself on every `attachments-change` — it is not the\n * strip under a sent message, which the bubble draws with the same `.aparte-thumb` tile\n * rules (minus the remove button), so a tile variable set at the theme root reaches both\n * strips, while one set on this element reaches only this one. It owns its `innerHTML` and\n * therefore projects nothing:\n * children written inside it are discarded on the first render. Removing a tile calls\n * `root.removeAttachment()` rather than mutating a list of its own, and the image previews\n * are blob URLs minted per render and revoked on the next one and on disconnect.\n *\n * @element aparte-composer-attachments\n *\n * @fires {CustomEvent<AparteAttachmentPreviewEventDetail>} aparte-attachment-preview - An attached image was clicked; the app opens it full-size, and only if it declared `attachmentPreview`.\n *\n * @cssprop [--aparte-attachments-max-height=140px] - Height cap on the strip; past it the\n * tiles scroll instead of pushing the composer up.\n * @cssprop [--aparte-attachment-image-size=56px] - Tile edge. The stylesheet sets 56px on\n * this element (the `:root` default is 72px, and the sent-message strip re-sets 40px on\n * itself), so a theme-level value reaches neither strip — target\n * `aparte-composer-attachments` to resize these tiles.\n * @cssprop [--aparte-thumb-radius=var(--aparte-radius-lg)] - Tile corner radius.\n * @cssprop [--aparte-attachment-chip-bg=var(--aparte-surface-2)] - Tile background, seen\n * behind a non-image file.\n * @cssprop [--aparte-attachment-chip-border=var(--aparte-border)] - Tile border colour.\n * @cssprop [--aparte-thumb-name-color=#ffffff] - Filename colour on the hover overlay.\n * @cssprop [--aparte-thumb-name-scrim=linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0))] - Background behind the filename; a bottom-up black\n * gradient by default, so the name stays legible over any picture.\n * @cssprop [--aparte-thumb-name-padding=14px 5px 4px] - Padding of that overlay.\n * @cssprop [--aparte-thumb-remove-size=18px] - Diameter of the ✗ button.\n * @cssprop [--aparte-thumb-remove-inset=3px] - Its inset from the tile's top and right\n * edges (physical `right`, so it does not flip in a right-to-left locale).\n * @cssprop [--aparte-thumb-remove-bg=rgba(0, 0, 0, 0.6)] - Its background.\n * @cssprop [--aparte-thumb-remove-bg-hover=rgba(0, 0, 0, 0.85)] - Its hover background.\n * @cssprop [--aparte-thumb-remove-color=#ffffff] - Its glyph colour.\n *\n * @example\n * <!-- The strip hides itself while nothing is attached. Pair it with the picker, and\n * only if your loop actually reads the files from the send event. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <aparte-composer-attachments></aparte-composer-attachments>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-add-attachment></aparte-composer-add-attachment>\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerAttachments extends HTMLElement {\n private _unsubscribes: (() => void)[] = [];\n /** Object URLs minted for image previews — revoked on re-render/disconnect. */\n private _objectUrls: string[] = [];\n\n connectedCallback(): void {\n this._render([]);\n this._connectToRoot();\n }\n\n disconnectedCallback(): void {\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n this._revokeUrls();\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('attachments-change', ({ attachments }) => this._render(attachments))\n );\n\n // Sync initial state\n this._render(root.attachments);\n }\n\n /** Release the previous render's blob URLs so they don't leak. */\n private _revokeUrls(): void {\n this._objectUrls.forEach(url => URL.revokeObjectURL(url));\n this._objectUrls = [];\n }\n\n private _render(files: File[]): void {\n this.hidden = files.length === 0;\n // Free the previous render's preview URLs before minting new ones.\n this._revokeUrls();\n\n this.innerHTML = files.map((file) => {\n const name = this._escape(file.name);\n const remove =\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-thumb__remove\" type=\"button\" ` +\n `aria-label=\"Remove ${name}\">${resolveConfig(this).getIcon('close')}</button>`;\n\n if (file.type.startsWith('image/')) {\n const url = URL.createObjectURL(file);\n this._objectUrls.push(url);\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--image\" title=\"${escapeAttr(name)}\">` +\n `<img class=\"aparte-thumb__img\" src=\"${escapeAttr(url)}\" alt=\"${escapeAttr(name)}\" />` +\n `<span class=\"aparte-thumb__name\">${name}</span>${remove}</div>`;\n }\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--file\" title=\"${escapeAttr(name)}\">` +\n `<span class=\"aparte-thumb__ext\">${this._escape(this._ext(file.name))}</span>` +\n `<span class=\"aparte-thumb__name\">${name}</span>${remove}</div>`;\n }).join('');\n\n // Remove buttons — every file has exactly one tile, so the button\n // index lines up with the attachments index.\n this.querySelectorAll('.aparte-thumb__remove').forEach((btn, i) => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n const root = this._getRoot();\n if (root) root.removeAttachment(root.attachments[i]!);\n });\n });\n\n // Image tiles ask for the full-size preview — only when the app declared it\n // opens one (same rule as the sent-message strip in the bubble).\n if (!resolveConfig(this).getHostHandlers().attachmentPreview) return;\n this.querySelectorAll('.aparte-thumb--image').forEach(tile => {\n tile.setAttribute('role', 'button');\n tile.setAttribute('tabindex', '0');\n const open = (): void => {\n const img = tile.querySelector('.aparte-thumb__img') as HTMLImageElement | null;\n if (!img) return;\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true,\n composed: true,\n detail: { url: img.src, name: tile.getAttribute('title') ?? '' },\n }));\n };\n tile.addEventListener('click', open);\n tile.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key !== 'Enter' && key !== ' ') return;\n e.preventDefault();\n open();\n });\n });\n }\n\n /** Uppercased file extension (≤4 chars), or 'FILE' when there is none. */\n private _ext(filename: string): string {\n const dot = filename.lastIndexOf('.');\n return dot > 0 ? filename.slice(dot + 1).toUpperCase().slice(0, 4) : 'FILE';\n }\n\n private _escape(str: string): string {\n return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"').replace(/'/g, ''');\n }\n}\n\nif (!customElements.get('aparte-composer-attachments')) {\n customElements.define('aparte-composer-attachments', AparteComposerAttachments);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * File picker button for <aparte-composer>.\n *\n * Opens a native file picker on click, then pushes picked files to root.addAttachments().\n * Also sets up drag & drop on the nearest <aparte-composer> root.\n *\n * It only COLLECTS files: it never reads, uploads or renders them.\n * `<aparte-composer-attachments>` draws the pending strip, and sending is the host's job\n * (`event.detail.files` on `aparte-send`) — which is why the default `<aparte-chat>` shell\n * only includes this button when the `attachments` attribute is set. With nothing reading\n * the files, an attach button is an affordance core cannot honour (ratified decision #8).\n *\n * Drag & drop is installed on the composer ROOT, not on this button, so a drop anywhere\n * over the composer attaches and the root carries `aparte-is-dragover` while a drag is\n * over it. The dashed outline is drawn on `.aparte-composer-shell` when the markup has one\n * and on the composer element itself when it does not — width from\n * `--aparte-focus-outline-width`, colour from `--aparte-primary`, radius from\n * `--aparte-radius-input`, none of them declared here. The drop handler always calls\n * `preventDefault()`, even while disabled, so the browser can never navigate away to the\n * dropped file. `disabled` on the ROOT removes the drop target; `streaming` does not — it\n * only greys the button out, so a drop mid-turn still attaches.\n *\n * The label and the icon are not attributes — they come from the config (`t('actionUpload')`\n * and the `paperclip` icon), so a locale or icon-provider change rewrites the existing\n * button in place instead of re-rendering it.\n *\n * A child already carrying `class=\"aparte-caa-button\"` suppresses core's own render — and\n * core then wires nothing to it: no click listener (so no picker opens), and no label,\n * icon or disabled/streaming writes. Drag & drop still works, since it is installed on the\n * root regardless. Any other child is replaced on the first render. The file input itself\n * is never a child: it is created on `document.body` per click and removed again.\n *\n * @element aparte-composer-add-attachment\n *\n * @attr {string} accept - MIME types / extensions passed to the file input (e.g. \"image/*,.pdf\")\n * @attr {boolean} multiple - Allow multiple file selection (default: true)\n * @attr {boolean} disabled - Greys out the picker. Drops are gated by the composer root's\n * `disabled`, not by this one.\n *\n * @cssprop [--aparte-input-action-btn-size=36px] - Square size of the button. On a coarse\n * pointer the stylesheet re-sets it to `--aparte-touch-target-size` (44px) on\n * `.aparte-action-button` itself, which wins over a value inherited from your theme.\n * @cssprop [--aparte-input-action-btn-icon-size=20px] - Size of the `<svg>` inside it.\n * @cssprop [--aparte-radius-action-btn=var(--aparte-radius-sm)] - Corner radius.\n *\n * @example\n * <!-- Opt-in: nothing consumes the files unless your host does (an AparteClient, or\n * your own listener reading `event.detail.files` off `aparte-send`). -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-add-attachment accept=\"image/*,.pdf\"></aparte-composer-add-attachment>\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerAddAttachment extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _dragCleanup: (() => void) | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n static get observedAttributes(): string[] {\n return ['accept', 'multiple', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n this._setupDragDrop();\n this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._dragCleanup?.();\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'disabled' && this._button) {\n this._button.disabled = value !== null;\n }\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n /**\n * Re-read the label and the icon on the button that already exists.\n *\n * Deliberately not a re-render: `_render()` returns early once the button is\n * there, and its `disabled` computation ignores `root.streaming` while the\n * streaming listener sets `disabled` directly — so rebuilding would silently\n * re-enable the attach button mid-turn, and drop focus if the user were on it.\n * The native file input and the drag listeners live outside this element\n * (on `document.body` and on the composer root), so they are untouched either way.\n */\n private _refreshChrome(): void {\n if (!this._button) return;\n const cfg = resolveConfig(this);\n const label = cfg.t('actionUpload') || 'Attach file';\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.innerHTML = cfg.getIcon('paperclip');\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-caa-button')) return;\n\n const label = resolveConfig(this).t('actionUpload') || 'Attach file';\n const icon = resolveConfig(this).getIcon('paperclip');\n const disabled = this.hasAttribute('disabled') || this._getRoot()?.disabled || false;\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--icon aparte-caa-button aparte-action-button\"\n aria-label=\"${escapeAttr(label)}\"\n title=\"${escapeAttr(label)}\"\n type=\"button\"\n ${disabled ? 'disabled' : ''}\n >${icon}</button>`;\n\n this._button = this.querySelector('.aparte-caa-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('disabled-change', ({ disabled }) => {\n if (this._button) this._button.disabled = disabled;\n })\n );\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n if (this._button) this._button.disabled = streaming || root.disabled;\n })\n );\n }\n\n private _handleClick(): void {\n const input = document.createElement('input');\n input.type = 'file';\n input.multiple = !this.hasAttribute('multiple') || this.getAttribute('multiple') !== 'false';\n const accept = this.getAttribute('accept');\n if (accept) input.accept = accept;\n input.style.display = 'none';\n\n document.body.appendChild(input);\n input.addEventListener('change', () => {\n if (input.files?.length) this._getRoot()?.addAttachments(input.files);\n document.body.removeChild(input);\n }, { once: true });\n input.click();\n }\n\n private _setupDragDrop(): void {\n const root = this._getRoot();\n if (!root) return;\n\n const prevent = (e: Event) => { e.preventDefault(); e.stopPropagation(); };\n const onDragOver = (e: Event) => {\n if (root.disabled) return; // no drop target while disabled (e.g. streaming)\n prevent(e);\n root.classList.add('aparte-is-dragover');\n };\n const onDragLeave = (e: Event) => { prevent(e); root.classList.remove('aparte-is-dragover'); };\n const onDrop = (e: DragEvent) => {\n prevent(e); // always block the browser from navigating to the dropped file\n root.classList.remove('aparte-is-dragover');\n if (root.disabled) return; // don't attach while disabled (the add button is blocked too)\n const files = e.dataTransfer?.files;\n if (files?.length) this._getRoot()?.addAttachments(files);\n };\n\n root.addEventListener('dragover', onDragOver);\n root.addEventListener('dragleave', onDragLeave);\n root.addEventListener('drop', onDrop);\n\n this._dragCleanup = () => {\n root.removeEventListener('dragover', onDragOver);\n root.removeEventListener('dragleave', onDragLeave);\n root.removeEventListener('drop', onDrop);\n };\n }\n\n}\n\nif (!customElements.get('aparte-composer-add-attachment')) {\n customElements.define('aparte-composer-add-attachment', AparteComposerAddAttachment);\n}\n","import { resolveConfig, type AparteIconName } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Generic action button primitive for <aparte-composer>.\n *\n * The consumer declares it directly in markup — no global registration needed.\n *\n * It is the escape hatch for a button core has no opinion about: it renders one icon\n * button wearing `.aparte-action-button` (the shared icon-button look — colour from\n * `--aparte-neutral`, hover tint derived from `--aparte-primary`) and emits\n * `aparte-action-click`. It carries no behaviour of its own and nothing in core listens\n * for that event, so the app is the only thing that can make it do something. Prefer the\n * dedicated element wherever one exists — `<aparte-composer-send>`,\n * `<aparte-composer-cancel>`, `<aparte-composer-add-attachment>` — since those already\n * talk to the composer.\n *\n * The host is `display: contents`, so the `<button>` rather than this element is the flex\n * child of the surrounding `.aparte-composer-row`. It subscribes to the nearest composer's\n * `disabled` and `streaming` changes, so it greys out while a turn is running without the\n * app tracking that. Used outside a composer it still mounts and still fires, with\n * `composer: null` in the detail.\n *\n * A child already carrying `class=\"aparte-cact-button\"` suppresses core's own render — and\n * core then wires nothing to it: no click listener (so no `aparte-action-click`), no\n * `label` → `aria-label`/`title` write, no `icon` write, no disabled/streaming sync. Take\n * that path only for a button your own code drives end to end. Any other child is replaced\n * on the first render.\n *\n * @element aparte-composer-action\n *\n * @attr {string} icon - Icon key for aparteGlobalConfig.getIcon(), or raw SVG/HTML starting with `<`\n * @attr {string} label - Accessible label (also used as tooltip)\n * @attr {boolean} disabled - Disables the button\n * @attr {string} action-id - Identifies WHICH button fired; carried as\n * `AparteActionClickEventDetail.actionId`. Read lazily at dispatch time rather than\n * observed, so changing it takes effect on the next click.\n *\n * @fires {CustomEvent<AparteActionClickEventDetail>} aparte-action-click - Bubbles up when\n * the button is clicked, carrying which button it was and the composer it belongs to.\n * The type argument is not decoration: a BARE `@fires` records `CustomEvent` with no\n * argument, and the bindings generator then emits `EventEmitter<void>` with a\n * listener that drops `$event` — so an Angular consumer with two custom buttons\n * could not tell which one fired.\n * detail: { actionId: string, composer: AparteComposer | null }\n *\n * @cssprop [--aparte-input-action-btn-size=36px] - Square size of the button. On a coarse\n * pointer the stylesheet re-sets it to `--aparte-touch-target-size` (44px) on\n * `.aparte-action-button` itself, which wins over a value inherited from your theme.\n * @cssprop [--aparte-input-action-btn-icon-size=20px] - Size of the `<svg>` inside it.\n * @cssprop [--aparte-radius-action-btn=var(--aparte-radius-sm)] - Corner radius.\n *\n * @example\n * <!-- Inside a composer, because that is what it resolves with `closest()`. `action-id`\n * is what tells two custom buttons apart: it comes back on the event's detail, and\n * a second button without one is indistinguishable from the first. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-action icon=\"star\" label=\"Favourite\" action-id=\"favourite\"></aparte-composer-action>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n *\n * <script>\n * // The event bubbles, so one listener above the composer serves every action.\n * document.addEventListener('aparte-action-click', (event) => {\n * if (event.detail.actionId === 'favourite') console.log('starred');\n * });\n * </script>\n */\nexport class AparteComposerAction extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n static get observedAttributes(): string[] {\n return ['icon', 'label', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n // Icon only, and no locale: this element's label is the consumer's `label`\n // ATTRIBUTE, so the app owns that string and a locale change is correctly a\n // no-op here. The write is the same one `attributeChangedCallback` does for\n // the `icon` attribute — `_resolveIcon` already decides between a provider\n // key and raw markup, so calling it again is idempotent.\n this._unsubscribes.push(subscribeConfigChange(this, () => {\n if (this._button) this._button.innerHTML = this._resolveIcon(this.getAttribute('icon') ?? '');\n }));\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (!this._button) return;\n if (name === 'disabled') {\n this._button.disabled = value !== null;\n }\n if (name === 'label') {\n this._button.setAttribute('aria-label', value ?? '');\n this._button.setAttribute('title', value ?? '');\n }\n if (name === 'icon') {\n this._button.innerHTML = this._resolveIcon(value ?? '');\n }\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-cact-button')) return;\n\n // `label` is a host-set attribute (often bound to dynamic/translated\n // text by the consumer) — escape before it lands in a double-quoted\n // attribute so a stray `\"` can't break out and inject markup.\n const label = escapeAttr(this.getAttribute('label') ?? '');\n const icon = this._resolveIcon(this.getAttribute('icon') ?? ''); // safe-text: _resolveIcon returns provider SVG, or the host-set icon attribute verbatim when it starts with < — documented as trusted markup, same contract as AparteIconProvider\n const disabled = this.hasAttribute('disabled') || this._getRoot()?.disabled || false;\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--icon aparte-cact-button aparte-action-button\"\n aria-label=\"${label}\"\n title=\"${label}\"\n type=\"button\"\n ${disabled ? 'disabled' : ''}\n >${icon}</button>`;\n\n this._button = this.querySelector('.aparte-cact-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('disabled-change', ({ disabled }) => {\n if (this._button) this._button.disabled = disabled || this.hasAttribute('disabled');\n })\n );\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n if (this._button) this._button.disabled = streaming || root.disabled || this.hasAttribute('disabled');\n })\n );\n }\n\n private _handleClick(_e: MouseEvent): void {\n this.dispatchEvent(new CustomEvent<AparteActionClickEventDetail>('aparte-action-click', {\n bubbles: true,\n composed: true,\n detail: { actionId: this.getAttribute('action-id') ?? '', composer: this._getRoot() },\n }));\n }\n\n private _resolveIcon(icon: string): string {\n if (!icon) return '';\n if (icon.trimStart().startsWith('<')) return icon;\n return resolveConfig(this).getIcon(icon as AparteIconName) ?? icon;\n }\n}\n\nif (!customElements.get('aparte-composer-action')) {\n customElements.define('aparte-composer-action', AparteComposerAction);\n}\n\n/**\n * Detail payload for `aparte-action-click`.\n *\n * `<aparte-composer-action>` is a publicly exported element whose only purpose is\n * to emit this event, and nothing in core listens for it — so the app IS the\n * consumer, and it had no type to read `e.detail` with. The shape was already\n * published in prose in the generated API reference; this makes it compile.\n *\n * @event aparte-action-click\n */\nexport interface AparteActionClickEventDetail {\n /** The `action-id` attribute of the button that was clicked, or `''`. */\n actionId: string;\n /** The owning composer, or `null` when the button is used outside one. */\n composer: AparteComposer | null;\n}\n","/**\n * The composer's bottom row — the strip a mode picker, a model selector or a token\n * counter belongs in, rather than a bar of your own floating below the chat. Purely\n * structural: it lays its children out in a row and gets out of the way.\n *\n * **Position is the DOM order.** `margin-inline-start: auto` on a child pushes it (and\n * everything after it) to the end of the row. That is the whole placement API on\n * purpose: there is no `left`/`right` to be wrong about, so the row reads correctly in a\n * right-to-left locale without the author thinking about it.\n *\n * The controls the row is made of can be any element, or plain text. Nothing is wrapped or\n * reordered — the children ARE the row, laid out by flex in DOM order, and they may arrive\n * after connection (a framework commits children in its own order). Non-whitespace TEXT\n * counts as content too, so a hand-written row holding a bare token count stays visible\n * instead of tripping the `data-empty` hide.\n *\n * The row is not part of the default `<aparte-chat>` shell — nothing is drawn until you\n * put something in it.\n *\n * It declares no custom property of its own: the gap, the padding and the top separator\n * come from the global `--aparte-space-*` and `--aparte-border*` tokens, so it inherits a\n * theme rather than exposing knobs to re-set.\n *\n * @element aparte-composer-toolbar\n *\n * @attr {boolean} data-empty - Reflected BY the element while it holds neither an element\n * child nor non-whitespace text; the stylesheet hides it then.\n * Read-only, do not set it yourself.\n *\n * @example\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n *\n * <!-- `aparte-model-selector` is NOT part of core: importing\n * `@aparte/plugin-model-selector` is what defines it. Until then the tag\n * renders empty and inert with no error, and upgrades by itself when the\n * definition arrives. Any element of your own works here too. -->\n * <aparte-composer-toolbar>\n * <my-mode-picker></my-mode-picker>\n * <aparte-model-selector style=\"margin-inline-start:auto\"></aparte-model-selector>\n * </aparte-composer-toolbar>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerToolbar extends HTMLElement {\n private _observer: MutationObserver | null = null;\n\n connectedCallback(): void {\n this._syncEmpty();\n // Children can arrive after connection — a framework commits the element and its\n // children in whichever order suits it, and a consumer may add a control later.\n this._observer ??= new MutationObserver(() => this._syncEmpty());\n this._observer.observe(this, { childList: true });\n }\n\n disconnectedCallback(): void {\n this._observer?.disconnect();\n this._observer = null;\n }\n\n /**\n * Reflect `data-empty` from the presence of an ELEMENT child.\n *\n * Not `:empty` in CSS: that selector does not match an element holding a whitespace\n * text node, so a template that indents its content keeps the row — separator,\n * padding and all — while it looks empty to the user. Every framework template\n * indents. An empty row must not draw its own separator (the same rule as an empty\n * bubble action bar).\n *\n * Non-whitespace TEXT counts as content, not just an element child: a hand-written\n * row holding a bare token count (`<aparte-composer-toolbar>1 240 tokens</…>`) is\n * not empty, and hiding it would be a twenty-minute mystery for whoever wrote it.\n */\n private _syncEmpty(): void {\n const hasContent = Boolean(this.firstElementChild) || this.textContent?.trim() !== '';\n if (hasContent) this.removeAttribute('data-empty');\n else this.setAttribute('data-empty', '');\n }\n}\n\nif (!customElements.get('aparte-composer-toolbar')) {\n customElements.define('aparte-composer-toolbar', AparteComposerToolbar);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { escapeAttr } from '../../utils/escape.js';\n\nexport interface AparteConversationListItem {\n id: string;\n title: string;\n updatedAt?: number;\n /** When set, the item renders the unarchive action instead of archive. */\n archivedAt?: number;\n}\n\nexport interface AparteConversationSelectDetail {\n id: string;\n}\n\nexport interface AparteConversationDeleteDetail {\n id: string;\n}\n\nexport interface AparteConversationArchiveDetail {\n id: string;\n}\n\n/**\n * Conversation-history sidebar — a framework-agnostic web component. The host sets\n * the `conversations` JS property and the `active-id` attribute; this renders the\n * list and fires the user's intent, never acting on it itself.\n *\n * Children are not a composition point: `_render()` assigns `innerHTML` from the\n * `conversations` array, so any light-DOM child a host writes inside the element is\n * discarded the next time the list renders — and switching this element's locale is\n * enough to trigger one. Compose around the element, not inside it: it renders rows\n * and nothing else, with no header, no new-conversation button and no search field.\n *\n * What it is not: a store. Clicking a row selects nothing, and the two row actions\n * delete and archive nothing — the four events carry an id and stop. A row's text\n * comes from the array, so it changes when the host assigns `conversations` again;\n * the exception is an empty title, which falls back to the locale's new-chat label\n * and therefore follows a locale switch. An archived item is still rendered (it gains\n * `aparte-conv-item--archived` and swaps its action's icon and event name); filtering\n * archived conversations out of the list is the host's decision, not this element's.\n * The asymmetry between the two inputs is deliberate: `active-id` is an attribute\n * because moving the selection patches the rendered rows in place, while\n * `conversations` is a JS property because it is structured data an attribute cannot\n * carry, and setting it re-renders the whole list.\n *\n * @element aparte-conversation-list\n * @attr {string} active-id - The id of the conversation to render as selected.\n *\n * @fires {CustomEvent<AparteConversationSelectDetail>} aparte-select-conversation - A row was activated; the host loads that conversation.\n * @fires {CustomEvent<AparteConversationDeleteDetail>} aparte-delete-conversation - The delete action was pressed. Nothing is removed here.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-archive-conversation - The archive action was pressed on a live conversation.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-unarchive-conversation - The same action on an already-archived one; same detail shape, opposite intent.\n *\n * @cssprop [--aparte-conv-list-gap=2px] - Vertical gap between rows. The element itself is the flex column, so this is its `gap`.\n * @cssprop [--aparte-conv-item-padding=7px 10px] - Padding of a row.\n * @cssprop [--aparte-conv-item-gap=6px] - Gap between a row's title and its two action buttons.\n * @cssprop [--aparte-conv-item-radius=var(--aparte-radius-md)] - Corner radius of a row.\n * @cssprop [--aparte-conv-item-font-size=0.8125rem] - Font size of a row's title.\n * @cssprop [--aparte-conv-item-color=var(--aparte-text-muted)] - Title colour of an inactive row.\n * @cssprop [--aparte-conv-item-bg-hover=var(--aparte-surface-3)] - Row background on hover.\n * @cssprop [--aparte-conv-item-bg-active=var(--aparte-surface-3)] - Background of the row matching `active-id`.\n * @cssprop [--aparte-conv-item-color-active=var(--aparte-text)] - Title colour of the active row.\n * @cssprop [--aparte-conv-item-font-weight-active=var(--aparte-font-weight-medium, 500)] - Title weight of the active row.\n * @cssprop [--aparte-conv-action-btn-size=20px] - Square size of both action buttons. Under `(pointer: coarse)` the stylesheet redeclares it as 28px on the buttons themselves, so a value set on the element does not reach them there; the buttons also stay visible instead of appearing on hover.\n * @cssprop [--aparte-conv-delete-color=var(--aparte-text-muted)] - Icon colour of the delete button.\n * @cssprop [--aparte-conv-delete-bg-hover=var(--aparte-error)] - Delete button background on hover.\n * @cssprop [--aparte-conv-delete-color-hover=var(--aparte-text-inverse)] - Delete button icon colour on hover.\n * @cssprop [--aparte-conv-delete-radius=var(--aparte-radius-sm)] - Corner radius of the delete button.\n * @cssprop [--aparte-conv-archive-color=var(--aparte-text-muted)] - Icon colour of the archive/unarchive button.\n * @cssprop [--aparte-conv-archive-bg-hover=var(--aparte-surface-4, var(--aparte-surface-3))] - Archive button background on hover. Core declares no `--aparte-surface-4`, so unset it resolves to `--aparte-surface-3`.\n * @cssprop [--aparte-conv-archive-color-hover=var(--aparte-text)] - Archive button icon colour on hover.\n * @cssprop [--aparte-conv-archive-radius=var(--aparte-radius-sm)] - Corner radius of the archive button.\n *\n * @example\n * <!-- It stores nothing and fetches nothing: an empty tag renders the empty state, and\n * the list appears when the host assigns `conversations`. -->\n * <aparte-conversation-list active-id=\"c1\" style=\"max-width: 20rem\"></aparte-conversation-list>\n *\n * <script>\n * document.querySelector('aparte-conversation-list').conversations = [\n * { id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },\n * { id: 'c2', title: 'Rename the segment types', updatedAt: Date.now() - 864e5 },\n * ];\n * </script>\n *\n * @example\n * // The host owns the data: set the `conversations` property, listen for the intent.\n * const list = document.querySelector('aparte-conversation-list')!;\n * list.conversations = [\n * { id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },\n * { id: 'c2', title: 'Old thread', updatedAt: 0, archivedAt: Date.now() },\n * ];\n * list.setAttribute('active-id', 'c1');\n *\n * list.addEventListener('aparte-select-conversation', (e) => load(e.detail.id));\n * list.addEventListener('aparte-delete-conversation', (e) => remove(e.detail.id));\n */\nexport class AparteConversationList extends HTMLElement {\n private _conversations: AparteConversationListItem[] = [];\n private _activeId: string | null = null;\n\n static get observedAttributes(): string[] {\n return ['active-id'];\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────\n\n connectedCallback(): void {\n if (!this.classList.contains('aparte-conv-list')) {\n this.classList.add('aparte-conv-list');\n }\n if (!this.getAttribute('role')) {\n this.setAttribute('role', 'navigation');\n }\n this._render();\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n if (name === 'active-id') {\n this._activeId = newValue;\n this._updateActiveState();\n }\n }\n\n /**\n * Re-render on a locale switch: every row's title fallback and both button\n * labels come from the locale, so without this the list stayed in the previous\n * language until something else happened to re-render it. Only OUR config.\n */\n private _onConfigChange = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== resolveConfig(this)) return;\n this._render();\n };\n\n // ─── Public API ───────────────────────────────────────────────────────\n\n /** Set the list of conversations to display. Triggers a re-render. */\n set conversations(items: AparteConversationListItem[]) {\n this._conversations = Array.isArray(items) ? items : [];\n this._render();\n }\n\n get conversations(): AparteConversationListItem[] {\n return this._conversations;\n }\n\n // ─── Rendering ────────────────────────────────────────────────────────\n\n private _render(): void {\n this.innerHTML = this._conversations\n .map(conv => this._renderItem(conv))\n .join('');\n this._bindEvents();\n }\n\n private _renderItem(conv: AparteConversationListItem): string {\n const locale = resolveConfig(this).getLocale();\n const isActive = conv.id === this._activeId;\n const isArchived = !!conv.archivedAt;\n const activeClass = isActive ? ' aparte-conv-item--active' : '';\n const archivedClass = isArchived ? ' aparte-conv-item--archived' : '';\n const escapedId = this._esc(conv.id);\n const escapedTitle = this._esc(conv.title || locale.newChat);\n const deleteLabel = this._esc(locale.deleteConversation);\n const archiveLabel = this._esc(locale['archiveConversation'] ?? 'Archive conversation');\n const unarchiveLabel = this._esc(locale['unarchiveConversation'] ?? 'Unarchive conversation');\n const archiveAction = isArchived ? 'unarchive' : 'archive';\n const archiveAriaLabel = isArchived ? unarchiveLabel : archiveLabel;\n // Distinct icons: a downward tray for archive, an upward tray for unarchive.\n // Marked at the declaration because the use site is inside a multi-line template\n // literal, where a `//` would render as text rather than exempt anything.\n const archiveGlyph = resolveConfig(this).getIcon(isArchived ? 'unarchive' : 'archive'); // safe-text: the icon provider's SVG — markup by contract, which is what getIcon returns everywhere in core.\n return `\n<div\n class=\"aparte-menu__item aparte-conv-item${activeClass}${archivedClass}\"\n role=\"button\"\n tabindex=\"0\"\n data-conv-id=\"${escapedId}\"\n aria-current=\"${isActive ? 'page' : 'false'}\"\n>\n <span class=\"aparte-conv-item__title\">${escapedTitle}</span>\n <button\n class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__archive\"\n type=\"button\"\n data-archive-id=\"${escapedId}\"\n data-archive-action=\"${escapeAttr(archiveAction)}\"\n aria-label=\"${escapeAttr(archiveAriaLabel)}\"\n tabindex=\"0\"\n >${archiveGlyph}</button>\n <button\n class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__delete\"\n type=\"button\"\n data-delete-id=\"${escapedId}\"\n aria-label=\"${deleteLabel}\"\n tabindex=\"0\"\n >\n ${resolveConfig(this).getIcon('close')}\n </button>\n</div>`;\n }\n\n private _bindEvents(): void {\n this.addEventListener('click', this._onClick);\n this.addEventListener('keydown', this._onKeydown);\n }\n\n private _onClick = (e: Event): void => {\n const target = e.target as HTMLElement;\n const archiveBtn = target.closest('[data-archive-id]') as HTMLElement | null;\n if (archiveBtn) {\n e.stopPropagation();\n const id = archiveBtn.dataset['archiveId']!;\n const action = archiveBtn.dataset['archiveAction'];\n const eventName = action === 'unarchive'\n ? 'aparte-unarchive-conversation'\n : 'aparte-archive-conversation';\n this.dispatchEvent(new CustomEvent<AparteConversationArchiveDetail>(\n eventName,\n { detail: { id }, bubbles: true, composed: true }\n ));\n return;\n }\n const deleteBtn = target.closest('[data-delete-id]') as HTMLElement | null;\n if (deleteBtn) {\n e.stopPropagation();\n const id = deleteBtn.dataset['deleteId']!;\n this.dispatchEvent(new CustomEvent<AparteConversationDeleteDetail>(\n 'aparte-delete-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n return;\n }\n const item = target.closest('[data-conv-id]') as HTMLElement | null;\n if (item) {\n const id = item.dataset['convId']!;\n this.dispatchEvent(new CustomEvent<AparteConversationSelectDetail>(\n 'aparte-select-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n }\n };\n\n private _onKeydown = (e: KeyboardEvent): void => {\n if (e.key !== 'Enter' && e.key !== ' ') return;\n const target = e.target as HTMLElement;\n /*\n * ONLY the row, and that word is the whole fix.\n *\n * The row is a `role=\"button\"` div, so Enter and Space do nothing on their own and\n * this handler supplies them. The archive and delete controls inside it are real\n * `<button>`s, which already activate on both keys — but this used to reach for\n * `closest('[data-conv-id]')` from whatever was focused, so pressing Enter on\n * Delete found the ROW, called preventDefault() (cancelling the button's own\n * activation) and clicked the row instead. Keyboard users could not archive or\n * delete a conversation at all: both keys selected it.\n *\n * Matching instead of climbing keeps the synthetic activation on the one element\n * that lacks a native one, and leaves every real control alone.\n */\n if (!target.matches('[data-conv-id]')) return;\n e.preventDefault();\n target.click();\n };\n\n /** Update active class without full re-render (perf optimisation). */\n private _updateActiveState(): void {\n const items = this.querySelectorAll<HTMLElement>('[data-conv-id]');\n items.forEach(el => {\n const isActive = el.dataset['convId'] === this._activeId;\n el.classList.toggle('aparte-conv-item--active', isActive);\n el.setAttribute('aria-current', isActive ? 'page' : 'false');\n });\n }\n\n // ─── Helpers ──────────────────────────────────────────────────────────\n\n private _esc(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/'/g, ''');\n }\n}\n\nif (!customElements.get('aparte-conversation-list')) customElements.define('aparte-conversation-list', AparteConversationList);\n","/**\n * Aparte\n * High-performance AI conversation engine in Vanilla TypeScript\n * Zero-dependency Web Components for LLM streaming\n *\n * ⚠️ This is the **browser** entry: it defines the custom elements and imports CSS\n * at module scope, so it needs a DOM. **Node resolves `index.node.ts` instead**\n * (via the `node` condition in package.json) — a DOM-free entry with the client,\n * host, transports, the chat handler and every type. This file sits first in the\n * exports map only because of the repo-local `@aparte-workspace/source` condition,\n * which is why reading it can look like \"this package can't run in Node\".\n * See the \"Node / SSR\" section of the README.\n *\n * @packageDocumentation\n */\nimport './styles/theme.css';\nimport './styles/base.css';\nimport './styles/button.css';\nimport './styles/field.css';\nimport './styles/display/avatar.css';\nimport './styles/display/icon.css';\nimport './styles/display/badge.css';\nimport './styles/display/tag.css';\nimport './styles/display/thumbnail.css';\nimport './styles/display/spinner.css';\nimport './styles/display/progress.css';\nimport './styles/display/skeleton.css';\nimport './styles/display/divider.css';\nimport './styles/display/alert.css';\nimport './styles/display/card.css';\nimport './styles/display/kbd.css';\nimport './styles/surface/tabs.css';\nimport './styles/surface/accordion.css';\nimport './styles/surface/menu.css';\nimport './styles/surface/popover.css';\nimport './styles/surface/tooltip.css';\nimport './styles/primitives/select.css';\nimport './styles/primitives/progress-spinner.css';\nimport './styles/components/shell.css';\nimport './styles/components/bubble.css';\nimport './styles/components/composer.css';\nimport './styles/segment/thinking.css';\nimport './styles/segment/code.css';\nimport './styles/segment/tool-call.css';\nimport './styles/segment/error.css';\nimport './styles/segment/pipeline.css';\nimport './styles/segment/text.css';\nimport './styles/segment/artifact.css';\nimport './styles/components/elicitation.css';\nimport './styles/components/conversation.css';\nimport './styles/prose.css';\nimport './styles/responsive.css';\n\n// Global HTMLElementEventMap augmentation — typed `e.detail` for aparté events.\nimport './types/event-map.js';\n// Global HTMLElementTagNameMap augmentation — `querySelector('aparte-…')` returns\n// the concrete element, not `Element`. Both are DOM-only, hence browser-entry only.\nimport './types/element-map.js';\n\n// Export primitives\nexport { AparteSelect, AparteOption, AparteOptgroup, type AparteSelectChangeDetail, type AparteOptgroupToggleEventDetail, AparteProgressSpinner, AparteIcon } from './primitives/index.js';\n\n// Export types\nexport type {\n AparteBubbleRole,\n AparteMessage,\n AparteContentParser,\n AparteSendEventDetail,\n AparteViewportConfig,\n AparteInputConfig,\n AparteThemeVariables,\n AparteStatus,\n AparteAttachment,\n AparteMessageBranch,\n AparteBubbleActionsConfig,\n AparteBubbleActionName,\n AparteSegment,\n AparteSegmentType,\n AparteTextSegment,\n AparteThinkingSegment,\n AparteCodeSegment,\n AparteSegmentRenderer,\n AparteCustomSegment,\n AparteToolCallSegment,\n AparteArtifactSegment,\n // Five shapes that were public in everything but name. `AparteSegment` is exported\n // and its union names all eight members, yet two of them — the error segment and the\n // pipeline indicator — could not be written down: narrowing on `type: 'error'` gave a\n // consumer the shape and no way to declare a variable of it. `AparteSegmentBase` is\n // worse than an omission: it is the CONSTRAINT on the exported\n // `AparteSegmentRenderer<T>`, so writing a renderer for a segment type of your own\n // required naming a type the package does not export. `AparteSegmentTiming` types\n // `meta.aparte`, and `AparteSegmentDefaults` types what `setSegmentDefaults` takes.\n AparteSegmentBase,\n AparteSegmentDefaults,\n AparteSegmentTiming,\n AparteErrorSegment,\n ApartePipelineWaitingSegment,\n // The detail of the `aparte-segment-update` event. It reached types/index.ts and\n // stopped there — and types/index.ts is not an entry point, so a consumer could\n // bind the event (it is in the published event table) and never name its detail.\n AparteSegmentUpdateEventDetail,\n // AI Provider types (BYORK)\n AparteAIProvider,\n AparteAIModel,\n AparteAIProviderConfigField,\n AparteAIProviderConfigSchema,\n AparteModelConfig,\n ModelStatus,\n ModelLoadProgress,\n AparteModelChangeEventDetail,\n AparteMessageDoneEventDetail,\n AparteMessageStartEventDetail,\n AparteMessageErrorEventDetail,\n AparteMessageAbortedEventDetail,\n AparteAbortEventDetail,\n AparteCompactEventDetail,\n AparteCompactDoneEventDetail,\n AparteCompactErrorEventDetail,\n AparteAttachmentPreviewEventDetail,\n AparteFileGenReadyEventDetail,\n AparteFileGenErrorEventDetail,\n AparteMessageInfoEventDetail,\n AparteSiblingInfo,\n AparteBranchNavigateEventDetail,\n ApartePathChangedEventDetail,\n AparteRetryEventDetail,\n AparteEditEventDetail,\n AparteFeedbackEventDetail,\n AparteActionEventDetail,\n AparteArtifactStartEventDetail,\n AparteArtifactDeltaEventDetail,\n AparteArtifactReadyEventDetail,\n AparteArtifactRedownloadEventDetail,\n // Chat types\n AparteChatRequest,\n AparteChatResponse,\n AparteChatMessage,\n AparteContentPart,\n AparteTextPart,\n AparteImagePart,\n AparteFilePart,\n AparteStreamEvent,\n AparteStreamEventMap,\n AparteUsage,\n // Tool types\n AparteTool,\n AparteToolCall,\n AparteToolResult,\n AparteToolHandler,\n AparteToolContext,\n AparteToolRenderer,\n AparteToolApprovalRequestDetail,\n // Canonical imperative surface (aliased by every wrapper's handle type).\n AparteChatImperativeApi,\n // The attribute surface of every element, for the wrappers to map over.\n AparteElementAttributes,\n AparteElementTagName,\n AparteAttrValue,\n AparteTemplateAttrs,\n AparteNoAttributes,\n AparteChatAttributes,\n AparteChatViewportAttributes,\n AparteChatBubbleAttributes,\n AparteChatStatusAttributes,\n AparteComposerAttributes,\n AparteComposerInputAttributes,\n AparteComposerActionAttributes,\n AparteComposerAddAttachmentAttributes,\n AparteComposerToolbarAttributes,\n AparteConversationListAttributes,\n AparteSelectAttributes,\n AparteOptionAttributes,\n AparteOptgroupAttributes,\n AparteProgressSpinnerAttributes,\n} from './types/index.js';\n\nexport { AparteErrorCode, AparteError, contentToText } from './types/index.js';\n\n// Export renderers\nexport {\n registerSegmentRenderer,\n unregisterSegmentRenderer,\n getSegmentRenderer,\n collectRendererStyles,\n registerDefaultRenderers,\n // The three the public barrel left behind. `renderers/index.ts` has always\n // exported all eight; this one published five, which made the registry\n // half-public: `declineDefaultRenderers` is the ONLY way to say \"do not install\n // the built-ins on this config\" without constructing an `AparteClient`\n // (`autoRegister: false`), and the bring-your-own-loop guide tells you not to\n // construct one. `installDefaultRenderersOnce` is what a hand-written bubble\n // needs, and `getAllRenderers` is the introspection half — the same reason\n // `hasHighlightProvider` and `renderMarkdown` are public.\n installDefaultRenderersOnce,\n declineDefaultRenderers,\n getAllRenderers\n} from './renderers/index.js';\n\n// Export components\nexport { AparteChat } from './components/index.js';\nexport { AparteChatBubble, populateBubbleFromMessage } from './components/index.js';\nexport type { SyncableBubble } from './components/index.js';\nexport { AparteChatStatus } from './components/index.js';\nexport { AparteChatViewport } from './components/index.js';\n\n// Export composer primitives\nexport { AparteComposer, AparteComposerInput, AparteComposerSend, AparteComposerCancel, AparteComposerAttachments, AparteComposerAddAttachment, AparteComposerAction, AparteComposerToolbar } from './components/index.js';\nexport type { AparteComposerEventMap, AparteComposerEventType, AparteComposerState, AparteComposerChangeEventDetail, AparteComposerPanelMode, AparteActionClickEventDetail } from './components/index.js';\n\n// Export conversation list primitive\nexport { AparteConversationList } from './components/index.js';\nexport type { AparteConversationListItem, AparteConversationSelectDetail, AparteConversationDeleteDetail, AparteConversationArchiveDetail } from './components/index.js';\n\n// Export conversations (types, adapter contract, manager)\nexport type {\n AparteConversation,\n AparteConversationMeta,\n AparteStorageAdapter,\n AparteMemoryFact,\n AparteArtifactRow,\n AparteAttachmentRow,\n} from './conversations/index.js';\nexport { APARTE_CONVERSATION_SCHEMA_VERSION } from './conversations/index.js';\nexport { AparteConversationManager, type ConversationManagerOptions } from './conversations/index.js';\nexport {\n AparteConversationController,\n type AparteChatBinding,\n type AparteConversationControllerOptions,\n} from './conversations/index.js';\n\n// Export the framework-agnostic chat-host orchestrator (streaming/branch/\n// host-method layer that every framework wrapper binds to).\nexport {\n AparteChatHost,\n type AparteChatHostBinding,\n type AparteChatHostOptions,\n} from './host/index.js';\n\n// Export parsers\nexport { AparteStreamParser, parseMarkdownToSegments, deriveArtifactKind } from './parsers/index.js';\nexport type { AparteStreamParserOptions, AparteThinkingDelimiterPair, AparteParserState, AparteParserResult } from './parsers/index.js';\nexport { parseAparteEventStream } from './parsers/index.js';\n\n// Export config\nexport { aparteGlobalConfig, AparteConfig, APARTE_DEFAULT_BUBBLE_ACTIONS, APARTE_DEFAULT_HOST_HANDLERS } from './config/index.js';\nexport type { AparteConfigChangeEventDetail } from './config/index.js';\nexport { resolveConfig, attachConfig, detachConfig, runWithConfig, contextConfig, APARTE_HOST_ATTR } from './config/index.js';\nexport { subscribeConfigChange, APARTE_CONFIG_CHANGE } from './config/index.js';\nexport type { AparteConfigAware } from './config/index.js';\nexport type { AparteMarkdownProvider, AparteStreamingMarkdownProvider, AparteStreamingMarkdownRenderer, AparteHighlightProvider, AparteSystemPromptVarsProvider, AparteSkeletonProvider, AparteSkeletonType, AparteLocale, AparteAction, AparteActionZone, AparteIconProvider, AparteIconName, AparteAvatarProvider, AparteStatusRenderer, AparteErrorRenderer, AparteAttachmentRenderer, AparteElicitationFieldRenderer, AparteElicitationFieldContext, AparteElicitationFieldControl, AparteSiblingNavRenderer, AparteBubbleShellRenderer, AparteModelPreference, AparteModelPreferenceProvider, AparteArtifactPreviewBuilder, AparteSanitizer } from './config/index.js';\nexport { APARTE_DEFAULT_ICON_FALLBACKS, APARTE_DEFAULT_SKELETON_FALLBACKS, APARTE_DEFAULT_LOCALE, defaultSanitizer, isSafeUrl } from './config/index.js';\n\n// Export Client\nexport { AparteClient } from './client/aparte-client.js';\n\n// Custom-element interop helpers shared by the framework wrappers' AparteUi.\nexport { applyElementProps, APARTE_DEFAULT_UI_EVENTS } from './interop/element-props.js';\nexport type { AparteUiEventName } from './interop/element-props.js';\n// Turns the `File[]` an `aparte-send` carries into renderable attachments — the\n// same conversion ConversationController does, for consumers driving the\n// imperative API themselves.\nexport { filesToAttachments, revokeAttachmentUrls } from './utils/files-to-attachments.js';\n// Is a message waiting for a reply? Shared by the viewport, the four wrappers and\n// any consumer rendering its own bubble — one rule, so they can't disagree.\nexport { isAwaitingReply } from './utils/is-awaiting-reply.js';\n\n// HTML escaping — one implementation for the whole scope. Exported because the\n// plugins render their own HTML (they cannot reach into core's internals) and\n// because a consumer writing a render hook needs it for exactly the same reason.\n// Nine private copies existed before this line; three of them had drifted to\n// escape only four of the five characters that matter.\nexport { escapeHtml, escapeAttr } from './utils/escape.js';\n// `cssEscape` belongs beside them: `pnpm check:attr-escaping` tells a renderer\n// author \"in a selector, use cssEscape()\", and the customization guide says the\n// same — while it was not exported at all, so the only way to follow that advice\n// was `CSS.escape`, which over-escapes inside a quoted attribute selector.\nexport { cssEscape } from './utils/css-escape.js';\n// Exported because the same wall is hit outside core: a wrapper naming its host\n// element, a provider tagging a request, or any bring-your-own-loop consumer\n// generating message ids all reach for `crypto.randomUUID`, which does not exist\n// on `http://` — the LAN deployment this library's own audience runs.\nexport { uuid } from './utils/uuid.js';\n// A segment's own completion rule, and the two readers of what core measured.\n// Exported because a consumer rendering \"thought for 8 s\" needs to know when the span\n// closed, and a rule kept private is a rule re-derived slightly differently outside —\n// the tool call is the trap: it settles by `status`, never by `isStreaming`.\n//\n// `segmentTiming` joins them because the measurements moved into `meta.aparte`, and\n// `segment.meta?.aparte` spelled at each call site is the same rule re-derived by hand\n// — exactly what the other two are exported to prevent.\n//\n// The WRITERS stay internal: only the two owners of a message's segment array may\n// stamp those fields, which `pnpm check:segment-stamp` enforces.\nexport { isSegmentSettled, segmentDuration, segmentTiming } from './utils/segments.js';\n// The PARAMETER types of two documented setters. They existed and were the declared\n// argument types, but were not exported — so anyone typing a settings layer over\n// `setHostHandlers` / `setKeyProvider` had to re-declare the shape by hand.\nexport type { AparteHostHandlersConfig } from './types/models.js';\nexport type { AparteKeyProvider } from './config/aparte-config.js';\nexport type { AparteClientOptions, AparteToolApprovalResolver, AparteCompactionSelector } from './client/aparte-client.js';\n// Structured-stream adapter — DOM half of the runStreamAgent loop (see stream-adapter.ts).\nexport { createStreamAdapter, readableToAsyncIterable } from './client/stream-adapter.js';\nexport type { AparteStreamRunEvent, AparteStreamRunEmitter, StreamAdapterTarget, CreateStreamAdapterOptions, AparteStreamRunner, AparteStreamRunOptions } from './client/stream-adapter.js';\n\n// Export transport seam (where chat requests go + how auth is handled)\nexport { AparteDirectTransport, AparteBackendTransport, createAparteChatHandler, isFormatAdapter } from './transport/index.js';\nexport type { AparteTransport, AparteTransportContext, AparteFormatAdapter, AparteVendorRequest, BackendTransportOptions, DirectTransportOptions, AparteChatHandlerOptions } from './transport/index.js';\n\n// Export runtime utilities\nexport { AparteMessageRepository } from './runtime/message-repository.js';\nexport type { ExportedMessageRepository } from './runtime/message-repository.js';\n\n// Export elicitation (human-in-the-loop typed input)\nexport { requestUserInput, buildElicitationPanel, buildApprovalPanel, AparteElicitationAbortError } from './elicitation/index.js';\nexport type {\n AparteElicitationSchema,\n AparteElicitationField,\n AparteElicitationEnumField,\n AparteElicitationBooleanField,\n AparteElicitationStringField,\n AparteElicitationObjectSchema,\n AparteElicitationRequest,\n AparteElicitationResult,\n AparteElicitationPresenter,\n AparteApprovalOption,\n AparteApprovalAnswer,\n BuiltApprovalPanel,\n BuiltElicitationPanel,\n} from './elicitation/index.js';\n\n// Export the default elicitation presenter Web Component\nexport { AparteElicitation } from './components/elicitation/aparte-elicitation.js';\n\n// Auto-register components when module is imported\n// Components register themselves in their files\nimport './components/chat/aparte-chat.js';\nimport './components/bubble/aparte-chat-bubble.js';\nimport './components/status/aparte-chat-status.js';\nimport './components/viewport/aparte-chat-viewport.js';\nimport './components/elicitation/aparte-elicitation.js';\n// Import primitives to auto-register\nimport './primitives/select/aparte-select.js';\nimport './primitives/select/aparte-option.js';\nimport './primitives/select/aparte-optgroup.js';\n\n/**\n * Utility to ensure all components are registered\n * Call this if using dynamic imports\n */\nexport function registerAllComponents(): void {\n // Components self-register, but this ensures imports are not tree-shaken\n const _chat = customElements.get('aparte-chat');\n const _viewport = customElements.get('aparte-chat-viewport');\n const _bubble = customElements.get('aparte-chat-bubble');\n const _status = customElements.get('aparte-chat-status');\n\n if (!_chat || !_viewport || !_bubble || !_status) {\n console.warn('[Aparte] Some components may not be registered. Ensure all component files are imported.');\n }\n}\n"],"names":["n","e","o","panel","a","l","b","c","m","i","f","h"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/primitives/select/aparte-option.ts","../src/primitives/select/aparte-optgroup.ts","../src/primitives/select/aparte-select.ts","../src/primitives/progress-spinner/aparte-progress-spinner.ts","../src/primitives/icon/aparte-icon.ts","../src/components/elicitation/aparte-elicitation.ts","../src/components/chat/aparte-chat.ts","../src/components/bubble/aparte-chat-bubble.ts","../src/components/status/aparte-chat-status.ts","../src/components/viewport/aparte-chat-viewport.ts","../src/components/composer/aparte-composer.ts","../src/components/composer/aparte-composer-input.ts","../src/components/composer/aparte-composer-send.ts","../src/components/composer/aparte-composer-cancel.ts","../src/components/composer/aparte-composer-attachments.ts","../src/components/composer/aparte-composer-add-attachment.ts","../src/components/composer/aparte-composer-action.ts","../src/components/composer/aparte-composer-toolbar.ts","../src/components/conversation-list/aparte-conversation-list.ts","../src/index.ts"],"sourcesContent":["/**\n * AparteOption\n * \n * Option element for aparte-select dropdown.\n *\n * One selectable row. Only meaningful inside `<aparte-select>` (directly, or nested in an\n * `<aparte-optgroup>`): the parent owns `selected` outright — on its first render, and on\n * every value change after it, it sets that attribute on the option whose `value` matches\n * its own and strips it from all the others. So `selected` written by hand does not\n * survive; set `value` on the select instead. Outside a select nothing selects it: it only\n * styles the row and sets `role=\"option\"` / `aria-selected` from its own attributes.\n *\n * It is not an `<option>`. It carries no form value, `disabled` blocks the click and the\n * keyboard walk but is not a form-disabled state, and when the `value` attribute is\n * absent the trimmed text content is used as the value instead.\n *\n * Keep the label one text node: the `label` property reads only the FIRST text node — that\n * is what keeps the injected status dot out of it — so wrapping the label in an element\n * makes `label` fall back to `value`. The select's own trigger label and its search filter\n * read the full `textContent`, so a wrapped label still displays and still matches.\n *\n * `data-status` is a rendering hook, not state core interprets: any non-empty value\n * appends an `aria-hidden` `.aparte-status-dot` span as the last child, and only\n * `ready`, `cached` and `not-downloaded` have a colour in the stylesheet — anything else\n * renders an uncoloured dot until you style it.\n *\n * @element aparte-option\n * @attr {string} value - Option value\n * @attr {boolean} disabled - Disabled state\n * @attr {boolean} selected - Selected state\n * @attr {string} data-status - Free-form status the host sets; styled, never read by core.\n *\n * @cssprop [--aparte-select-text=var(--aparte-text, #1e293b)] - Option text colour.\n * @cssprop [--aparte-select-option-hover=var(--aparte-surface-2, #f1f5f9)] - Background on hover, and for the keyboard-active row (`[data-active]`), which adds an inset `--aparte-primary` ring on top so the two are distinguishable.\n * @cssprop [--aparte-select-option-selected=color-mix(in srgb, var(--aparte-primary, #3b82f6) 18%, transparent)] - Background of the selected row. A tint by default: a solid accent fill with white text failed WCAG AA in both themes.\n * @cssprop [--aparte-select-option-selected-text=var(--aparte-select-text, var(--aparte-text, #1e293b))] - Text colour of the selected row. Set both this and the background to go back to a solid fill.\n *\n * @example\n * <aparte-select placeholder=\"Pick a model\" value=\"gpt-4o-mini\">\n * <aparte-option value=\"gpt-4o-mini\">GPT-4o mini</aparte-option>\n * <aparte-option value=\"o3\" disabled>o3 (no access)</aparte-option>\n * </aparte-select>\n */\n\nexport class AparteOption extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['value', 'disabled', 'selected', 'data-status'];\n }\n\n connectedCallback(): void {\n this.setAttribute('role', 'option');\n this._updateAriaSelected();\n this._updateStatusDot();\n }\n\n attributeChangedCallback(name: string): void {\n if (name === 'selected') {\n this._updateAriaSelected();\n }\n if (name === 'disabled') {\n this.setAttribute('aria-disabled', this.hasAttribute('disabled') ? 'true' : 'false');\n }\n if (name === 'data-status') {\n this._updateStatusDot();\n }\n }\n\n get value(): string {\n return this.getAttribute('value') || this.textContent?.trim() || '';\n }\n\n set value(val: string) {\n this.setAttribute('value', val);\n }\n\n get label(): string {\n // Use only the first text node, ignoring injected spans (e.g. status dot)\n const textNode = Array.from(this.childNodes).find(n => n.nodeType === Node.TEXT_NODE);\n return textNode?.textContent?.trim() || this.value;\n }\n\n get disabled(): boolean {\n return this.hasAttribute('disabled');\n }\n\n set disabled(val: boolean) {\n if (val) {\n this.setAttribute('disabled', '');\n } else {\n this.removeAttribute('disabled');\n }\n }\n\n get selected(): boolean {\n return this.hasAttribute('selected');\n }\n\n set selected(val: boolean) {\n if (val) {\n this.setAttribute('selected', '');\n } else {\n this.removeAttribute('selected');\n }\n }\n\n private _updateAriaSelected(): void {\n this.setAttribute('aria-selected', this.selected ? 'true' : 'false');\n }\n\n private _updateStatusDot(): void {\n const status = this.getAttribute('data-status');\n let dot = this.querySelector<HTMLSpanElement>('.aparte-status-dot');\n\n if (!status) {\n dot?.remove();\n return;\n }\n\n if (!dot) {\n dot = document.createElement('span');\n dot.className = 'aparte-status-dot';\n dot.setAttribute('aria-hidden', 'true');\n this.appendChild(dot);\n }\n\n dot.setAttribute('data-status', status);\n }\n}\n\n// Register\nif (!customElements.get('aparte-option')) {\n customElements.define('aparte-option', AparteOption);\n}\n","import { resolveConfig } from '../../config/config-context.js';\n\n/**\n * AparteOptgroup\n * \n * Option group element for aparte-select dropdown.\n *\n * A labelled band of options inside `<aparte-select>`. Presentational only: the group\n * holds no value, and collapsing it just sets `display: none` on its `<aparte-option>`\n * descendants — they stay in the DOM, which is what keeps the select's keyboard walk\n * skipping them, since it works off `display`. The label header is inserted before the\n * children and the loading row appended after them, so both live inside the group, and\n * neither is hidden when it collapses: only the options are.\n *\n * Two consequences of that worth knowing before you reach for it. The select's search\n * writes that same `display` property on every option in the select, so filtering can\n * reveal matches inside a collapsed group. And the header is built once, on the first\n * render that finds a `label`: changing `label` afterwards does not rewrite it — set the\n * label before inserting the group, or replace the group.\n *\n * `loading` is a display state, not a fetch. It appends a spinner row to the group and\n * nothing else happens — the host still owns the request. \"Fetch on expand\" is driven by\n * `aparte-optgroup-toggle`, whose `detail.collapsed` says which way the group just went;\n * the attribute and the options' `display` are already updated when it fires, since\n * setting the attribute runs `attributeChangedCallback` synchronously.\n *\n * @element aparte-optgroup\n * @attr {string} label - Group label\n * @attr {boolean} collapsible - Adds the chevron and the click handler to the header — so it needs a `label`, and it is read only when that header is first built.\n * @attr {boolean} collapsed - Collapsed state\n * @attr {boolean} loading - Appends a spinner row to the group; the options stay visible.\n *\n * @fires {CustomEvent<AparteOptgroupToggleEventDetail>} aparte-optgroup-toggle - The group was collapsed or expanded.\n *\n * @cssprop --aparte-text-muted - Colour of the group header label and of the loading row.\n * The shared theme token: there is no optgroup-specific override, so restyling one group's\n * header means setting this on that element.\n *\n * @example\n * <!-- Collapsed groups keep a long list readable; the label is the group's header. -->\n * <aparte-select grouped placeholder=\"Pick a model\">\n * <aparte-optgroup label=\"Ollama\" collapsible collapsed>\n * <aparte-option value=\"ollama::llama3\">Llama 3</aparte-option>\n * </aparte-optgroup>\n * <aparte-optgroup label=\"OpenRouter\" collapsible>\n * <aparte-option value=\"openrouter::gpt-4o-mini\">GPT-4o mini</aparte-option>\n * </aparte-optgroup>\n * </aparte-select>\n */\n\nexport class AparteOptgroup extends HTMLElement {\n /** Ids for the label span the group points `aria-labelledby` at. */\n private static _labelIdSeq = 0;\n\n static get observedAttributes(): string[] {\n return ['label', 'collapsible', 'collapsed', 'loading'];\n }\n\n connectedCallback(): void {\n this.setAttribute('role', 'group');\n this._render();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n if (name === 'collapsed') {\n this._updateCollapsedState();\n }\n\n if (this.isConnected) {\n this._render();\n }\n }\n\n get label(): string {\n return this.getAttribute('label') || '';\n }\n\n set label(val: string) {\n this.setAttribute('label', val);\n }\n\n get collapsible(): boolean {\n return this.hasAttribute('collapsible');\n }\n\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(val: boolean) {\n if (val) {\n this.setAttribute('collapsed', '');\n } else {\n this.removeAttribute('collapsed');\n }\n }\n\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(val: boolean) {\n if (val) this.setAttribute('loading', '');\n else this.removeAttribute('loading');\n }\n\n private _render(): void {\n // Only render header if we have a label\n if (this.label) {\n const existingHeader = this.querySelector('.aparte-optgroup-header');\n if (!existingHeader) {\n const header = document.createElement('div');\n header.className = 'aparte-optgroup-header';\n // The header stays a GENERIC node on purpose: naming it (it used to\n // carry `aria-label`) makes it a real element inside a `listbox`,\n // where only options and groups may live (axe:\n // aria-required-children, critical). The name belongs on the group,\n // via aria-labelledby below.\n // `label` is an attribute value — with the model-selector it carries a\n // provider-supplied name — so it goes through textContent, never\n // innerHTML (a hostile name would otherwise inject here).\n const labelSpan = document.createElement('span');\n labelSpan.className = 'aparte-optgroup-label';\n labelSpan.id = `aparte-optgroup-label-${++AparteOptgroup._labelIdSeq}`;\n labelSpan.textContent = this.label;\n this.setAttribute('aria-labelledby', labelSpan.id);\n header.appendChild(labelSpan);\n\n if (this.collapsible) {\n const chevron = document.createElement('span');\n chevron.className = 'aparte-optgroup-chevron';\n // The library's own `expand`, not a shape drawn here. This span was\n // painted as a CSS border-triangle — the second hand-drawn chevron in\n // core, built from PHYSICAL border sides where the tool row's used\n // logical ones: two markers, two constructions, which is the divergence\n // the icons rule was written after.\n chevron.innerHTML = resolveConfig(this).getIcon('expand');\n header.appendChild(chevron);\n header.style.cursor = 'pointer';\n header.addEventListener('click', (e) => {\n e.stopPropagation();\n this._toggleCollapse();\n });\n }\n\n this.insertBefore(header, this.firstChild);\n }\n }\n\n // Update loading state\n this._updateLoadingState();\n\n // Update collapsed state\n this._updateCollapsedState();\n }\n\n private _updateLoadingState(): void {\n let loader = this.querySelector('.aparte-optgroup-loader');\n if (this.loading) {\n if (!loader) {\n loader = document.createElement('div');\n loader.className = 'aparte-optgroup-loader';\n loader.innerHTML = '<span class=\"aparte-spinner-small\"></span> Fetching models...';\n this.appendChild(loader);\n }\n } else if (loader) {\n loader.remove();\n }\n }\n\n private _toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n\n // Dispatch event before updating UI to allow parent to react (e.g. fetch data)\n this.dispatchEvent(new CustomEvent<AparteOptgroupToggleEventDetail>('aparte-optgroup-toggle', {\n bubbles: true,\n composed: true,\n detail: {\n label: this.label,\n collapsed: this.collapsed\n }\n }));\n\n this._updateCollapsedState();\n }\n\n private _updateCollapsedState(): void {\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n (opt as HTMLElement).style.display = this.collapsed ? 'none' : '';\n });\n }\n}\n\n// Register\nif (!customElements.get('aparte-optgroup')) {\n customElements.define('aparte-optgroup', AparteOptgroup);\n}\n\n/**\n * Detail payload for `aparte-optgroup-toggle`.\n *\n * `types/event-map.ts` types `aparte-optgroup-toggle` with this detail, and\n * `@aparte/plugin-model-selector` reads both of its fields, from another package,\n * through an untyped cast — while the event is published in the generated CEM event\n * table. It is a contract.\n *\n * @event aparte-optgroup-toggle\n */\nexport interface AparteOptgroupToggleEventDetail {\n /** The group's label. */\n label: string;\n /** `true` when the group just collapsed. */\n collapsed: boolean;\n}\n","import './aparte-option.js';\nimport './aparte-optgroup.js';\nimport { resolveConfig } from '../../config/config-context.js';\n\nexport interface AparteSelectChangeDetail {\n value: string;\n label: string;\n previousValue: string;\n}\n\n/**\n * Dropdown select for aparté — a vanilla web component with optional grouping, a\n * search filter and a keyboard-driven listbox.\n *\n * The element is light DOM and takes `<aparte-option>` / `<aparte-optgroup>` children, in\n * the order they should appear. On its first render it captures its children, keeps those\n * two kinds and moves them into the `role=\"listbox\"` container it builds, then rewrites\n * its own `innerHTML` — so any other child is dropped, and a wrapper element around your\n * options takes the options down with it: only DIRECT children are captured.\n *\n * Children written later are picked up by a `subtree` MutationObserver and moved into that\n * same container, and the keyboard highlight is re-asserted on the new elements. The move\n * EMPTIES the container first, so a later write replaces the list instead of adding to it:\n * write the whole list, not one option. Writing straight into `.aparte-select-options`\n * skips the move (the observer then only re-asserts the highlight), and that is the path\n * `@aparte/plugin-model-selector` takes to refresh a live list in place.\n *\n * It is not a form control: no `name`, no `multiple`, no participation in form submission.\n * It holds exactly one value and reports it through `aparte-select-change`.\n *\n * The dropdown is `position: fixed` and placed from script so it escapes an\n * `overflow: hidden` ancestor — which is why its stacking order is a variable\n * (`--aparte-select-z`) rather than a fixed rule, and why an `open` dropdown does not\n * scroll with the trigger.\n *\n * @element aparte-select\n * @attr {string} value - The selected option's value.\n * @attr {string} placeholder - Shown while nothing is selected.\n * @attr {boolean} disabled - Blocks opening the dropdown.\n * @attr {boolean} grouped - Observed, never read: `<aparte-optgroup>` children render as groups without it.\n * @attr {boolean} searchable - Adds a filter field above the options. Read on the first render only.\n * @attr {boolean} open - Reflects (and controls) whether the dropdown is open.\n *\n * @fires {CustomEvent<AparteSelectChangeDetail>} aparte-select-change - The selection changed; carries the new value, its label and the previous value.\n * @fires aparte-select-open - The dropdown opened. No detail.\n * @fires aparte-select-close - The dropdown closed. No detail.\n *\n * @cssprop [--aparte-select-bg=var(--aparte-surface-1, #fff)] - Trigger background — and, under `[data-aparte-theme=\"dark\"]`, the dropdown panel's too.\n * @cssprop [--aparte-select-border=var(--aparte-border, #e2e8f0)] - Border of the trigger and of the dropdown.\n * @cssprop [--aparte-select-border-hover=var(--aparte-primary, #3b82f6)] - Trigger border on hover.\n * @cssprop [--aparte-select-border-focus=var(--aparte-primary, #3b82f6)] - Trigger border while focused.\n * @cssprop [--aparte-select-ring=rgba(59, 130, 246, 0.2)] - Colour of the 2px focus ring around the trigger.\n * @cssprop [--aparte-select-radius=0.5rem] - Corner radius of the trigger and the dropdown.\n * @cssprop [--aparte-select-text=var(--aparte-text, #1e293b)] - Colour of the trigger label (and of the options).\n * @cssprop [--aparte-select-chevron=var(--aparte-text-muted, #94a3b8)] - Colour of the chevron, which rotates 180° while open.\n * @cssprop [--aparte-select-dropdown-bg=var(--aparte-surface-1, #fff)] - Dropdown panel background in the light theme only; the `[data-aparte-theme=\"dark\"]` rule is more specific and reads `--aparte-select-bg` instead.\n * @cssprop [--aparte-select-shadow=0 4px 12px rgba(0, 0, 0, 0.1)] - Dropdown panel shadow.\n * @cssprop [--aparte-select-z=1000] - `z-index` of the dropdown. It is `position: fixed`, so this is the one knob that decides whether it lands above the rest of your page.\n *\n * @example\n * <aparte-select placeholder=\"Pick a model\" searchable value=\"gpt-4o-mini\">\n * <aparte-option value=\"gpt-4o-mini\">GPT-4o mini</aparte-option>\n * <aparte-option value=\"llama-3.1-8b\">Llama 3.1 8B</aparte-option>\n * </aparte-select>\n *\n * <script>\n * document.querySelector('aparte-select').addEventListener('aparte-select-change', (e) => {\n * console.log(e.detail.value, e.detail.label, e.detail.previousValue);\n * });\n * </script>\n */\nexport class AparteSelect extends HTMLElement {\n private static _optIdSeq = 0;\n /** Fallback ids for the listbox `aria-controls` target when the host has no id. */\n private static _listboxSeq = 0;\n\n private _value = '';\n private _isOpen = false;\n private _activeIndex = -1;\n private _trigger: HTMLElement | null = null;\n private _dropdown: HTMLElement | null = null;\n private _searchInput: HTMLInputElement | null = null;\n private _observer: MutationObserver | null = null;\n\n // Bound handlers for cleanup\n /**\n * Bound, like its two neighbours below — because an inline arrow on `this` can\n * never be removed.\n *\n * `_setupEventListeners()` runs on EVERY `connectedCallback`, and `_render()`'s\n * idempotency guard means the host element survives a re-connect. So each move\n * of the element (a portal, a Vue teleport, any framework re-parent) added\n * another option-click listener: measured, one click fired the change handler\n * FIVE times after five re-connects. `disconnectedCallback` removed the two\n * document-level handlers and could not touch this one.\n *\n * This is verbatim the bug class `aparte-chat-viewport` documents having fixed\n * for its own listeners.\n */\n private _boundHandleOptionClick = this._handleOptionClick.bind(this);\n private _boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n private _boundHandleKeydown = this._handleKeydown.bind(this);\n\n static get observedAttributes(): string[] {\n return ['value', 'placeholder', 'disabled', 'grouped', 'searchable', 'open'];\n }\n\n connectedCallback(): void {\n this._value = this.getAttribute('value') || '';\n this._isOpen = this.hasAttribute('open');\n this._render();\n this._setupEventListeners();\n this._setupMutationObserver();\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('click', this._boundHandleOptionClick);\n document.removeEventListener('click', this._boundHandleDocumentClick);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n this._observer?.disconnect();\n }\n\n attributeChangedCallback(name: string, oldValue: string, newValue: string): void {\n if (!this.isConnected) return;\n\n if (name === 'value' && oldValue !== newValue && newValue !== this._value) {\n this._value = newValue || '';\n this._updateTriggerLabel();\n }\n if (name === 'open') {\n this._isOpen = this.hasAttribute('open');\n if (this._isOpen) {\n this._dropdown?.removeAttribute('hidden');\n this._searchInput?.focus();\n } else {\n this._dropdown?.setAttribute('hidden', '');\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public API\n // ─────────────────────────────────────────────────────────────────────────\n\n get value(): string {\n return this._value;\n }\n\n set value(val: string) {\n if (val === this._value) return;\n const previousValue = this._value;\n this._value = val;\n this.setAttribute('value', val);\n this._updateTriggerLabel();\n this._emitChange(previousValue);\n }\n\n get open(): boolean {\n return this._isOpen;\n }\n\n set open(val: boolean) {\n if (val) {\n this.setAttribute('open', '');\n } else {\n this.removeAttribute('open');\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Rendering\n // ─────────────────────────────────────────────────────────────────────────\n\n private _render(): void {\n const placeholder = this.getAttribute('placeholder') || 'Select...';\n const searchable = this.hasAttribute('searchable');\n\n // Check if already rendered (has dropdown structure)\n if (this.querySelector('.aparte-select-dropdown')) {\n this._updateTriggerLabel();\n return;\n }\n\n // First render: capture any slotted children before modifying DOM\n const slottedChildren = Array.from(this.children);\n\n // Create wrapper structure\n const trigger = document.createElement('div');\n trigger.className = 'aparte-select-trigger';\n trigger.setAttribute('tabindex', '0');\n trigger.setAttribute('role', 'combobox');\n trigger.setAttribute('aria-haspopup', 'listbox');\n trigger.setAttribute('aria-expanded', 'false');\n // Accessible name (axe aria-input-field-name): the visible label span is\n // the combobox VALUE, not its name — name it from the host's aria-label\n // when provided, else the placeholder (\"Select model…\" etc.).\n trigger.setAttribute('aria-label', this.getAttribute('aria-label') || placeholder);\n // The label is placeholder text (consumer/attribute-supplied) → textContent,\n // same path as _updateTriggerLabel(), never innerHTML. Only the static SVG\n // chevron uses innerHTML.\n const labelSpan = document.createElement('span');\n labelSpan.className = 'aparte-select-label';\n labelSpan.textContent = placeholder;\n const chevronSpan = document.createElement('span');\n chevronSpan.className = 'aparte-select-chevron';\n chevronSpan.innerHTML = resolveConfig(this).getIcon('expand');\n trigger.append(labelSpan, chevronSpan);\n\n // The dropdown is a plain shell: it also holds the search field, and a\n // `listbox` may only contain options/groups (axe: aria-required-children,\n // critical). The listbox lives on the options container below.\n const dropdown = document.createElement('div');\n dropdown.className = 'aparte-select-dropdown';\n dropdown.hidden = !this._isOpen;\n\n if (searchable) {\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.className = 'aparte-select-search';\n searchInput.placeholder = 'Search...';\n searchInput.setAttribute('aria-label', 'Search options');\n dropdown.appendChild(searchInput);\n }\n\n const optionsContainer = document.createElement('div');\n optionsContainer.className = 'aparte-select-options';\n optionsContainer.setAttribute('role', 'listbox');\n // A listbox is an ARIA input field, so it needs its own name; reuse the\n // combobox's (axe: aria-input-field-name).\n optionsContainer.setAttribute('aria-label', trigger.getAttribute('aria-label') ?? placeholder);\n // `role=\"combobox\"` REQUIRES aria-controls (axe: aria-required-attr).\n optionsContainer.id = this.id ? `${this.id}-listbox` : `aparte-listbox-${++AparteSelect._listboxSeq}`;\n trigger.setAttribute('aria-controls', optionsContainer.id);\n\n // Move slotted children (aparte-option, aparte-optgroup) into options container\n slottedChildren.forEach(child => {\n if (child.tagName === 'APARTE-OPTION' || child.tagName === 'APARTE-OPTGROUP') {\n optionsContainer.appendChild(child);\n }\n });\n\n dropdown.appendChild(optionsContainer);\n\n // Clear and rebuild DOM\n this._observer?.disconnect();\n this.innerHTML = '';\n this.appendChild(trigger);\n this.appendChild(dropdown);\n\n this._trigger = trigger;\n this._dropdown = dropdown;\n this._searchInput = dropdown.querySelector('.aparte-select-search');\n\n if (this.isConnected) {\n this._setupMutationObserver();\n }\n\n // Update label based on current value\n this._updateTriggerLabel();\n }\n\n private _setupMutationObserver(): void {\n this._observer = new MutationObserver(() => {\n this._updateDropdownContent();\n // The options may have just been replaced under an open dropdown —\n // re-assert the keyboard position on the NEW elements. See\n // {@link _restoreActive}.\n this._restoreActive();\n });\n\n // `subtree` matters: a consumer refreshing a live list writes into\n // `.aparte-select-options` (the model selector does exactly that when the\n // provider list settles), which is a DESCENDANT. Watching only our own\n // children missed it entirely — the highlight vanished with the removed\n // elements and nothing here noticed. Our own writes disconnect the observer\n // first, so this cannot loop.\n this._observer.observe(this, { childList: true, subtree: true });\n }\n\n /**\n * Put the roving highlight back after the options changed underneath it.\n *\n * `data-active` lives on an option ELEMENT, so replacing the list throws it\n * away while `_activeIndex` still claims a position: the highlight disappeared,\n * `aria-activedescendant` kept pointing at an id no longer in the document, and\n * the next ArrowDown moved from the stale index — skipping an option. Only when\n * open and only when a position was held, so a refresh never invents one.\n */\n private _restoreActive(): void {\n if (!this._isOpen || this._activeIndex < 0) return;\n if (this.querySelector('aparte-option[data-active]')) return;\n this._setActive(this._activeIndex);\n }\n\n private _updateDropdownContent(): void {\n // If trigger is gone, the component was likely wiped by innerHTML\n if (!this._trigger || !this.contains(this._trigger)) {\n this._render();\n return;\n }\n\n const optionsContainer = this.querySelector('.aparte-select-options');\n if (!optionsContainer) {\n // If internal UI is present but container is gone\n this._render();\n return;\n }\n\n // Collect all potential options from light DOM\n // (those aren't internal UI elements)\n const lightChildren = Array.from(this.children).filter(child =>\n child.className !== 'aparte-select-trigger' &&\n child.className !== 'aparte-select-dropdown'\n );\n\n if (lightChildren.length === 0) return;\n\n // Pause observer to prevent self-triggering loop\n this._observer?.disconnect();\n\n // Clear container (but keep internal stuff if any)\n optionsContainer.innerHTML = '';\n\n // Move/Append children to container\n lightChildren.forEach(child => {\n optionsContainer.appendChild(child);\n });\n\n // Resume observer\n if (this.isConnected) {\n this._observer?.observe(this, { childList: true, subtree: true });\n }\n\n this._updateTriggerLabel();\n this._restoreActive();\n }\n\n private _setupEventListeners(): void {\n // Trigger click\n this._trigger?.addEventListener('click', () => this._toggle());\n\n // Trigger keyboard\n this._trigger?.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n // When open, Enter/Space selects the active option — handled by\n // the document-level nav handler. Only toggle when closed, so we\n // don't close the dropdown on the very keystroke meant to select.\n if (this._isOpen) return;\n e.preventDefault();\n this._toggle();\n }\n if (e.key === 'ArrowDown' && !this._isOpen) {\n e.preventDefault();\n this._openDropdown();\n }\n });\n\n // Option selection. Removed first: `_setupEventListeners` runs on every\n // connect, and adding the same bound reference twice is a no-op per spec —\n // but being explicit costs nothing and survives a future refactor that\n // rebinds.\n this.removeEventListener('click', this._boundHandleOptionClick);\n this.addEventListener('click', this._boundHandleOptionClick);\n\n // Search filter\n this._searchInput?.addEventListener('input', (e) => {\n const query = (e.target as HTMLInputElement).value.toLowerCase();\n this._filterOptions(query);\n });\n\n // Close on outside click\n document.addEventListener('click', this._boundHandleDocumentClick);\n\n // Keyboard navigation\n document.addEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _handleOptionClick(e: Event): void {\n const option = (e.target as HTMLElement).closest('aparte-option');\n if (option && !option.hasAttribute('disabled')) {\n this._selectOption(option as HTMLElement);\n }\n }\n\n private _handleDocumentClick(e: Event): void {\n if (!this.contains(e.target as Node)) {\n this._closeDropdown();\n }\n }\n\n private _handleKeydown(e: KeyboardEvent): void {\n if (!this._isOpen) return;\n\n // Home/End move the caret when typing in the search box; only hijack\n // them for option navigation when focus is not in the search field.\n const inSearch = document.activeElement === this._searchInput;\n\n switch (e.key) {\n case 'Escape':\n e.preventDefault();\n this._closeDropdown();\n this._trigger?.focus();\n break;\n case 'ArrowDown':\n e.preventDefault();\n this._moveActive(1);\n break;\n case 'ArrowUp':\n e.preventDefault();\n this._moveActive(-1);\n break;\n case 'Home':\n if (inSearch) break;\n e.preventDefault();\n this._setActive(0);\n break;\n case 'End':\n if (inSearch) break;\n e.preventDefault();\n this._setActive(this._visibleOptions().length - 1);\n break;\n case 'Enter': {\n const active = this._visibleOptions()[this._activeIndex];\n if (active) {\n e.preventDefault();\n this._selectOption(active);\n }\n break;\n }\n }\n }\n\n /** Non-disabled, non-filtered options in DOM order. */\n private _visibleOptions(): HTMLElement[] {\n return Array.from(this.querySelectorAll<HTMLElement>('aparte-option')).filter(\n opt => !opt.hasAttribute('disabled') && opt.style.display !== 'none',\n );\n }\n\n /** Move the active (keyboard-highlighted) option by `delta`, clamped. */\n private _moveActive(delta: number): void {\n const opts = this._visibleOptions();\n if (opts.length === 0) return;\n const base = this._activeIndex < 0 ? (delta > 0 ? -1 : 0) : this._activeIndex;\n this._setActive(base + delta);\n }\n\n /** Highlight the option at `index` (clamped) and point aria-activedescendant at it. */\n private _setActive(index: number): void {\n const all = this.querySelectorAll<HTMLElement>('aparte-option');\n all.forEach(o => o.removeAttribute('data-active'));\n\n const opts = this._visibleOptions();\n if (opts.length === 0) {\n this._activeIndex = -1;\n this._trigger?.removeAttribute('aria-activedescendant');\n return;\n }\n const clamped = Math.max(0, Math.min(index, opts.length - 1));\n this._activeIndex = clamped;\n\n const active = opts[clamped]!;\n if (!active.id) active.id = `aparte-option-${++AparteSelect._optIdSeq}`;\n active.setAttribute('data-active', '');\n this._trigger?.setAttribute('aria-activedescendant', active.id);\n active.scrollIntoView?.({ block: 'nearest' });\n }\n\n /** Clear the keyboard highlight (on close). */\n private _clearActive(): void {\n this._activeIndex = -1;\n this._trigger?.removeAttribute('aria-activedescendant');\n this.querySelectorAll('aparte-option').forEach(o => o.removeAttribute('data-active'));\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Actions\n // ─────────────────────────────────────────────────────────────────────────\n\n private _toggle(): void {\n if (this._isOpen) {\n this._closeDropdown();\n } else {\n this._openDropdown();\n }\n }\n\n private _openDropdown(): void {\n if (this.hasAttribute('disabled')) return;\n\n this._isOpen = true;\n this._dropdown?.removeAttribute('hidden');\n this._trigger?.setAttribute('aria-expanded', 'true');\n this.setAttribute('open', '');\n\n // Smart Positioning\n this._updatePosition();\n\n // Focus search if available\n this._searchInput?.focus();\n\n // Seed the keyboard highlight on the current selection (or the first\n // option) so ArrowUp/Down have an anchor and screen readers announce it.\n const opts = this._visibleOptions();\n const selectedIdx = opts.findIndex(o => o.getAttribute('value') === this._value);\n this._setActive(selectedIdx >= 0 ? selectedIdx : 0);\n\n this.dispatchEvent(new CustomEvent('aparte-select-open', { bubbles: true }));\n }\n\n private _closeDropdown(): void {\n this._isOpen = false;\n this._clearActive();\n this._dropdown?.setAttribute('hidden', '');\n this._trigger?.setAttribute('aria-expanded', 'false');\n this.removeAttribute('open');\n this.removeAttribute('position'); // Reset position attribute\n\n // Clear position styles set by _updatePosition\n if (this._dropdown) {\n this._dropdown.style.top = '';\n this._dropdown.style.bottom = '';\n this._dropdown.style.left = '';\n this._dropdown.style.width = '';\n }\n\n // Clear search\n if (this._searchInput) {\n this._searchInput.value = '';\n this._filterOptions('');\n }\n\n this.dispatchEvent(new CustomEvent('aparte-select-close', { bubbles: true }));\n }\n\n private _updatePosition(): void {\n if (!this._dropdown || !this._trigger) return;\n\n const rect = this._trigger.getBoundingClientRect();\n const dropdownHeight = this._dropdown.offsetHeight || 300;\n const viewportHeight = window.innerHeight;\n const spaceBelow = viewportHeight - rect.bottom;\n const GAP = 4; // px gap between trigger and dropdown\n\n // Always size to trigger width\n this._dropdown.style.left = `${rect.left}px`;\n this._dropdown.style.width = `${rect.width}px`;\n\n // Decide whether to open upward or downward\n if (spaceBelow < dropdownHeight && rect.top > dropdownHeight) {\n // Open upward\n this._dropdown.style.top = '';\n this._dropdown.style.bottom = `${viewportHeight - rect.top + GAP}px`;\n this.setAttribute('position', 'top');\n } else {\n // Open downward\n this._dropdown.style.top = `${rect.bottom + GAP}px`;\n this._dropdown.style.bottom = '';\n this.removeAttribute('position');\n }\n }\n\n private _selectOption(option: HTMLElement): void {\n const value = option.getAttribute('value') || option.textContent?.trim() || '';\n const previousValue = this._value;\n\n this._value = value;\n this.setAttribute('value', value);\n this._updateTriggerLabel();\n this._closeDropdown();\n this._emitChange(previousValue);\n this._trigger?.focus();\n }\n\n private _updateTriggerLabel(): void {\n const labelEl = this._trigger?.querySelector('.aparte-select-label');\n if (labelEl) {\n const selectedLabel = this._getSelectedLabel();\n labelEl.textContent = selectedLabel || this.getAttribute('placeholder') || 'Select...';\n }\n\n // Update selected state on options\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n const isSelected = opt.getAttribute('value') === this._value;\n if (isSelected) {\n opt.setAttribute('selected', '');\n } else {\n opt.removeAttribute('selected');\n }\n });\n }\n\n private _getSelectedLabel(): string {\n // Match by property, not an interpolated attribute selector — a value with\n // `\"`/`]` (e.g. a remote model id) would make querySelector throw SyntaxError.\n for (const opt of this.querySelectorAll('aparte-option')) {\n if (opt.getAttribute('value') === this._value) return opt.textContent?.trim() || '';\n }\n return '';\n }\n\n private _filterOptions(query: string): void {\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n const label = opt.textContent?.toLowerCase() || '';\n const matches = label.includes(query);\n (opt as HTMLElement).style.display = matches ? '' : 'none';\n });\n // Re-anchor the keyboard highlight on the first still-visible option.\n if (this._isOpen) this._setActive(0);\n }\n\n private _emitChange(previousValue: string): void {\n const detail: AparteSelectChangeDetail = {\n value: this._value,\n label: this._getSelectedLabel(),\n previousValue\n };\n\n this.dispatchEvent(new CustomEvent<AparteSelectChangeDetail>('aparte-select-change', {\n bubbles: true,\n composed: true,\n detail\n }));\n }\n}\n\n// Register\nif (!customElements.get('aparte-select')) {\n customElements.define('aparte-select', AparteSelect);\n}\n","/**\n * AparteProgressSpinner\n *\n * Circular progress spinner web component.\n * - Indeterminate (no `value` attribute): continuous rotation animation\n * - Determinate (`value=\"0–100\"`): fills the arc proportionally\n *\n * The ABSENCE of the attribute is what selects indeterminate, so `value=\"\"` is not\n * \"unknown progress\" — it parses to 0, i.e. an empty determinate arc. `value` is clamped\n * to 0–100 and anything non-numeric reads as 0; nothing throws.\n *\n * It renders its own SVG into itself on connect and on every `value` change, so it takes\n * no children: whatever you put inside is overwritten. The SVG is `aria-hidden` and the\n * ARIA lives on the host (`role=\"progressbar\"`, `aria-valuemin`/`aria-valuemax`, plus\n * `aria-valuenow` only when determinate) — there is no accessible NAME, so give the\n * element an `aria-label` unless the surrounding text already says what is loading.\n *\n * It draws an arc; it does not manage a loading lifecycle — no delay before appearing, no\n * timeout, no label, no live announcement. Under `prefers-reduced-motion: reduce` the\n * rotation stops (aparte.css scopes that rule to the library's own elements), which is the\n * other reason the indeterminate arc must not be the only signal that work is in flight.\n *\n * @element aparte-progress-spinner\n * @attr {number} value - Progress percentage 0–100 (omit for indeterminate)\n *\n * @cssprop [--aparte-spinner-size=16px] - Width and height of the element; the SVG fills it.\n * @cssprop [--aparte-spinner-stroke=2.5] - Stroke width of both arcs, in the units of the 24×24 viewBox.\n * @cssprop [--aparte-spinner-color=currentColor] - Stroke of the filled (progress) arc.\n * @cssprop [--aparte-spinner-track=color-mix(in srgb, currentColor 15%, transparent)] - Stroke of the track arc behind it.\n *\n * @example\n * <!-- Omit `value` for the indeterminate spin; set it to show real progress. -->\n * <aparte-progress-spinner></aparte-progress-spinner>\n * <aparte-progress-spinner value=\"62\"></aparte-progress-spinner>\n */\nexport class AparteProgressSpinner extends HTMLElement {\n static get observedAttributes(): string[] { return ['value']; }\n\n /** Radius of the SVG circle (viewBox is 0 0 24 24, center at 12,12) */\n private readonly _r = 9;\n private get _circ(): number { return 2 * Math.PI * this._r; }\n\n connectedCallback(): void { this._render(); }\n attributeChangedCallback(): void { this._render(); }\n\n private _render(): void {\n const raw = this.getAttribute('value');\n const value = raw !== null\n ? Math.min(100, Math.max(0, parseFloat(raw) || 0))\n : null;\n\n this.setAttribute('role', 'progressbar');\n this.setAttribute('aria-valuemin', '0');\n this.setAttribute('aria-valuemax', '100');\n if (value !== null) {\n this.setAttribute('aria-valuenow', String(value));\n } else {\n this.removeAttribute('aria-valuenow');\n }\n\n // Determinate: dashoffset shrinks from circ→0 as value goes 0→100\n const dashoffset = value !== null ? this._circ * (1 - value / 100) : 0;\n // Indeterminate: fixed partial arc (~72% of circumference)\n const dasharray = value !== null\n ? `${this._circ.toFixed(2)}`\n : `${(this._circ * 0.72).toFixed(2)} ${(this._circ * 0.28).toFixed(2)}`;\n\n this.innerHTML = `<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><circle class=\"aparte-spinner-track\" cx=\"12\" cy=\"12\" r=\"${this._r}\"/><circle class=\"aparte-spinner-fill\" cx=\"12\" cy=\"12\" r=\"${this._r}\" stroke-dasharray=\"${dasharray}\" stroke-dashoffset=\"${dashoffset.toFixed(2)}\"/></svg>`;\n }\n}\n\nif (!customElements.get('aparte-progress-spinner')) {\n customElements.define('aparte-progress-spinner', AparteProgressSpinner);\n}\n","import { resolveConfig } from '../../config/config-context.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\nimport type { AparteIconName } from '../../config/icon-provider.js';\n\n/**\n * AparteIcon\n *\n * The library's icon set, reachable from MARKUP.\n *\n * It existed only in JavaScript. Core ships 25 glyphs and `setIconProvider` is sold as the\n * lever that swaps them, but the only door in was `getIcon(name)` — so a consumer writing\n * plain HTML could not place one, and the icon provider could not reach a single icon that\n * consumer wrote themselves. `<aparte-composer-action>`'s own documentation tells you to\n * put an `<svg>` inside it, which is the same gap stated as an instruction.\n *\n * That gap is why every example on the CSS-classes reference carried 265 characters of\n * path data to demonstrate a 60-character class: there was no shorter way to say \"an icon\n * goes here\" that actually drew one. `<aparte-icon name=\"copy\">` is that way.\n *\n * It routes through `getIcon`, so it is not a second icon mechanism — it is a markup\n * entrance to the one that exists. Register a provider and every `<aparte-icon>` on the\n * page follows, including the ones in your own templates.\n *\n * ONE CONSEQUENCE, stated because it is the real cost: the 25 glyph NAMES become public\n * API. `expand`, `copy`, `nextBranch` were internal identifiers; renaming one now breaks a\n * consumer's markup.\n *\n * It renders into itself and takes no children — whatever you put inside is replaced. The\n * SVG is `aria-hidden`, because an icon beside a label is decoration; when the icon IS the\n * button's only content, name the BUTTON (`aria-label`), not this.\n *\n * @element aparte-icon\n * @attr {string} name - Which glyph to draw. One of the names `setIconProvider` accepts;\n * an unknown name draws nothing rather than a broken-image box.\n *\n * @cssprop [--aparte-icon-size=16px] - Width and height. `--sm`/`--lg`/`--xl` set it.\n *\n * @example\n * <aparte-icon name=\"copy\"></aparte-icon>\n * <aparte-icon name=\"check\" class=\"aparte-icon--lg\"></aparte-icon>\n * <button class=\"aparte-btn aparte-btn--icon\" aria-label=\"Copy\">\n * <aparte-icon name=\"copy\"></aparte-icon>\n * </button>\n */\nexport class AparteIcon extends HTMLElement {\n static get observedAttributes(): string[] { return ['name']; }\n\n private _unsubscribe: (() => void) | null = null;\n\n connectedCallback(): void {\n this._render();\n /*\n * A provider registered AFTER this element mounted still reaches it. Without\n * this the icons already on the page kept the built-in glyph while everything\n * rendered later got the consumer's — the same split `<aparte-composer-send>`\n * subscribes to avoid.\n */\n this._unsubscribe = subscribeConfigChange(this, () => this._render());\n }\n\n disconnectedCallback(): void {\n this._unsubscribe?.();\n this._unsubscribe = null;\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this._render();\n }\n\n private _render(): void {\n const name = this.getAttribute('name');\n /*\n * `getIcon` is typed to the known names and falls back per name, so an unknown\n * one would land on `undefined` and print it. Drawing NOTHING is the honest\n * failure: a misspelled name leaves a gap the author can see, where the string\n * \"undefined\" in a button would read as a rendering bug in the library.\n */\n const glyph = name ? resolveConfig(this).getIconProvider()[name as AparteIconName]?.() : null;\n this.innerHTML = glyph ?? '';\n // Decoration by default — see the class note above for when to name what instead.\n this.firstElementChild?.setAttribute('aria-hidden', 'true');\n }\n}\n\nif (!customElements.get('aparte-icon')) {\n customElements.define('aparte-icon', AparteIcon);\n}\n","/**\n * <aparte-elicitation> — the default elicitation presenter.\n *\n * Registers itself as the presenter for the config governing its subtree\n * (`resolveConfig(this)`), so `requestUserInput()` from a tool handler is routed\n * here WITHOUT any window events — the typed presenter contract replaces the\n * stringly-typed `aparte-ask-user-*` events that drifted in Phase 1.\n *\n * On a request it builds the schema-appropriate panel (enum / boolean / string /\n * object) and mounts it inside the nearest `<aparte-composer>` via its\n * `showPanel` API, resolving:\n * - accept — the send button (panel submit), when all fields are complete\n * - decline — the inline \"Skip\" affordance\n * - cancel — the assistant turn was stopped/errored while pending\n *\n * Its CSS ships in `@aparte/core/styles.css` like every other component — it used\n * to inject its own <style> from here, which made it the one surface that could not\n * be themed and whose variables were missing from the generated CSS reference.\n *\n * Place anywhere inside the chat (it renders nothing itself):\n * <aparte-elicitation></aparte-elicitation>\n */\n\nimport { resolveConfig, runWithConfig, type AparteConfigAware } from '../../config/config-context.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\nimport type { AparteConfig } from '../../config/aparte-config.js';\nimport type { AparteComposer, AparteComposerPanelMode } from '../composer/aparte-composer.js';\nimport { buildElicitationPanel, type BuiltElicitationPanel } from '../../elicitation/panel.js';\nimport { buildApprovalPanel } from '../../elicitation/approval-panel.js';\nimport type { AparteElicitationRequest, AparteElicitationResult, AparteElicitationPresenter } from '../../elicitation/types.js';\nimport { AparteElicitationAbortError } from '../../elicitation/types.js';\n\n/**\n * The slice of the composer this presenter drives.\n *\n * DERIVED from the component rather than re-typed by hand. It used to be a literal\n * copy of the three signatures, which is a twin: adding a parameter to the real\n * `setPanelSubmitEnabled` left this one behind, and the compiler pointed at the\n * CALLER instead of the stale declaration. `import type` is erased, so nothing here\n * pulls the composer element into a runtime import — which is why the copy existed.\n */\ntype ComposerEl = HTMLElement & Pick<AparteComposer, 'showPanel' | 'hidePanel' | 'setPanelSubmitEnabled'>;\n\ninterface Pending {\n /** End the request without an answer — see `AparteElicitationAbortError`. */\n abort(): void;\n composer: ComposerEl;\n /**\n * Re-apply every string this request took from the locale, in place.\n *\n * ONE function, where this used to hold the panel and the skip button separately\n * so the component could relabel each. That only worked because there was one kind\n * of panel; an approval's strings live elsewhere, and a second field per kind is\n * how the two would drift.\n */\n relabel(): void;\n}\n\n/**\n * The default presenter for a request to the human. It renders nothing itself: it\n * registers as the presenter for the config governing its subtree, and mounts a panel\n * inside the nearest `<aparte-composer>` when something asks — a tool handler calling\n * `requestUserInput`, or core's own approval gate.\n *\n * It dispatches no events on purpose. A request is answered through the typed presenter\n * contract, not by listening for one; the `aparte-tool-decision` event this replaced\n * existed only because the buttons used to live in a segment renderer with no reference\n * to the client.\n *\n * It has no children to project: `connectedCallback` sets\n * `display: none`, and the composer it presents in is found by walking UP from\n * `this.parentElement` — anything placed inside this element is only hidden with it.\n * So its position matters but its content does not: mount it anywhere inside the\n * `<aparte-chat>` whose questions it should answer.\n *\n * Not the element to reach for when you want a question UI of your own shape. It is\n * one caller of `setElicitationPresenter`, and among presenters registered for the\n * same chat the most recent one wins — so an app with a framework-native presenter\n * registers that and does not mount this, and a second `<aparte-elicitation>` in one\n * chat is redundant rather than additive. It is also not usable outside a chat that\n * has an `<aparte-composer>`: with nowhere to mount a panel the request is REJECTED,\n * on purpose, rather than borrowed into another chat's composer.\n *\n * The panel it mounts is styled by `@aparte/core/styles.css` — the `--aparte-elic-*`\n * and `--aparte-approval-*` knobs below theme it. They are declared here rather than\n * on the composer because this presenter is what builds the panel; the composer only\n * lends it the slot.\n *\n * @element aparte-elicitation\n *\n * @cssprop [--aparte-elic-gap=6px] - Vertical gap between the panel's rows (message, body, tabs).\n * @cssprop [--aparte-elic-padding=6px 4px] - Padding inside the panel.\n * @cssprop [--aparte-elic-max-height=50vh] - Cap on the panel's height; its body scrolls, the panel does not.\n * @cssprop [--aparte-elic-field-gap=8px] - Space and separator padding between two fields of an object schema.\n * @cssprop [--aparte-elic-message-size=0.82rem] - Font size of the question text at the top of the panel.\n * @cssprop [--aparte-elic-title-size=0.8rem] - Font size of a field's title.\n * @cssprop [--aparte-elic-desc-size=0.76rem] - Font size of a field's description.\n * @cssprop [--aparte-elic-option-padding=7px 10px] - Padding of one enum/boolean option row.\n * @cssprop [--aparte-elic-option-radius=8px] - Corner radius of an option row.\n * @cssprop [--aparte-elic-option-title-size=0.875rem] - Font size of an option's label, and of the text inputs.\n * @cssprop [--aparte-elic-option-desc-size=0.78rem] - Font size of an option's secondary line.\n * @cssprop [--aparte-elic-control-size=15px] - Size of the radio/checkbox control in an option row.\n * @cssprop [--aparte-elic-input-radius=6px] - Corner radius of the text inputs and of the Skip button.\n * @cssprop [--aparte-elic-textarea-min-height=64px] - Minimum height of a multi-line string field.\n * @cssprop [--aparte-elic-input-size=0.85rem] - Font size of the approval panel's instruction field (the free-text note the user writes).\n * @cssprop [--aparte-elic-skip-size=0.8rem] - Font size of the corner \"Skip\" affordance (the decline).\n * @cssprop [--aparte-elic-step-size=0.78rem] - Font size of a step tab, when the schema is asked one field at a time.\n * @cssprop [--aparte-elic-step-padding=4px 2px] - Padding of a step tab.\n * @cssprop [--aparte-elic-step-gap=14px] - Gap between step tabs.\n * @cssprop [--aparte-elic-step-underline=2px] - Thickness of the current step's underline (a tab, not a pill).\n * @cssprop [--aparte-elic-dismiss-room=72px] - Space kept clear at the end of the tab rail for the corner escape.\n * @cssprop [--aparte-approval-gap=4px] - Gap between the stacked options of an approval request.\n * @cssprop [--aparte-approval-option-size=0.85rem] - Font size of an approval option button.\n * @cssprop [--aparte-approval-option-padding=8px 10px] - Padding of an approval option button.\n * @cssprop [--aparte-approval-option-radius=8px] - Corner radius of an approval option button.\n *\n * @example\n * <!-- Renders nothing by itself: it registers as the presenter for its subtree, so a\n * tool handler calling requestUserInput() gets its panel mounted in the composer. -->\n * <aparte-chat>\n * <aparte-chat-viewport></aparte-chat-viewport>\n * <aparte-elicitation></aparte-elicitation>\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n */\nexport class AparteElicitation extends HTMLElement implements AparteConfigAware {\n private _pending: Pending | null = null;\n /**\n * A turn ended — cancel the open question only if it was OUR turn.\n *\n * These two listeners sit on `window` and had no instance filter at all, so a\n * Stop (or an error) in one chat cancelled the question a DIFFERENT chat was\n * waiting on — and that chat's model was told the user had refused a question\n * the user was still looking at. Same defect as the `compact()` handler that\n * emptied both chats, which its four sibling handlers in `AparteClient` already\n * guarded against.\n *\n * The leniency rule is the composer's, deliberately: an event with no\n * `targetId` is for everyone (a single-chat app never sets one), and a chat we\n * cannot identify accepts everything rather than becoming deaf. Only a\n * MISMATCH is ignored.\n */\n private _onTurnEnd = (e: Event): void => {\n const evtTargetId = (e as CustomEvent).detail?.targetId as string | undefined;\n const own = this._pendingTargetId();\n if (evtTargetId && own && evtTargetId !== own) return;\n this._cancelPending();\n };\n\n private _unsubscribeConfig: (() => void) | null = null;\n\n connectedCallback(): void {\n this.style.display = 'none';\n // A language switch while a question is OPEN. Every other live-config\n // consumer in core got this seam; the panel could not use it because it kept\n // no reference to itself — see `Pending.panel`.\n this._unsubscribeConfig = subscribeConfigChange(this, () => this._relabelPending());\n // Become the presenter for this instance's config (or the global one).\n // `this` as the owner: it is what lets a request naming a `target` reach the\n // presenter in the SAME chat, instead of whichever one mounted last.\n resolveConfig(this).setElicitationPresenter(this._present, this);\n // Safety net: if the turn is stopped/errored while a request is open,\n // resolve it as cancelled so the client loop unblocks and the composer\n // input is restored.\n window.addEventListener('aparte-message-aborted', this._onTurnEnd);\n window.addEventListener('aparte-message-error', this._onTurnEnd);\n }\n\n /**\n * The boundary above us appeared, changed, or went away — move the\n * registration with it.\n *\n * `connectedCallback` alone is not enough and cannot be: registering is a\n * WRITE, and under all four wrappers it happens before `attachConfig` runs, so\n * it lands on the global singleton. `requestUserInput()` then resolves the\n * instance config, finds nothing, and rejects the request — the model\n * hears the user refuse a question the user never saw.\n *\n * See {@link AparteConfigAware}.\n */\n aparteConfigChanged(next: AparteConfig, previous: AparteConfig): void {\n // Withdraw OURS by name. `setElicitationPresenter(null)` cleared the whole\n // registry, so moving one chat's registration took every other mounted chat's\n // presenter down with it.\n previous.removeElicitationPresenter(this._present);\n next.setElicitationPresenter(this._present, this);\n }\n\n disconnectedCallback(): void {\n // Ours only. This used to clear the slot whenever it happened to hold our\n // presenter, which left a still-mounted sibling chat unable to ask anything for\n // the life of the page — silently, since the no-presenter warning fires once.\n resolveConfig(this).removeElicitationPresenter(this._present);\n window.removeEventListener('aparte-message-aborted', this._onTurnEnd);\n window.removeEventListener('aparte-message-error', this._onTurnEnd);\n this._unsubscribeConfig?.();\n this._unsubscribeConfig = null;\n this._cancelPending();\n }\n\n /**\n * Re-apply the open question's strings, in place.\n *\n * Two owners, and both have to move or the panel goes bilingual: the panel's own\n * defaults (`relabel`), and the Skip button, which this file builds and this file\n * therefore has to re-text. The composer's one button is a third, and it already\n * follows — `aparte-composer-send` remembers the panel state it was given.\n */\n private _relabelPending(): void {\n if (!this._pending) return;\n const cfg = resolveConfig(this);\n runWithConfig(cfg, () => this._pending!.relabel());\n }\n\n private _present: AparteElicitationPresenter = (request: AparteElicitationRequest) => {\n /*\n * No concurrency guard here any more — `AparteConfig.requestUserInput` queues,\n * so a second request arrives only once this one has settled.\n *\n * What was here answered the second request `cancel` immediately: a refusal\n * invented for a question nobody was shown. And it protected only requests\n * that came through THIS presenter, so a consumer's own presenter had nothing.\n * If two ever do overlap, `showPanel` now evicts and NOTIFIES the first, which\n * degrades to a settled request instead of a wedged one.\n */\n const composer = this._getComposer();\n // Mounted outside a chat that has a composer: there is nowhere to put the\n // panel, which is the same situation as no presenter at all.\n if (!composer) return Promise.reject(new AparteElicitationAbortError('no-presenter'));\n\n return new Promise<AparteElicitationResult>((resolve, reject) => {\n let done = false;\n /**\n * The slot this request owns, once `showPanel` has handed it over.\n *\n * `settle` can run BEFORE that — an already-aborted signal settles on the\n * spot — so it starts absent, and `hidePanel(undefined)` then closes whatever\n * is there, which is correct because nothing of ours is open yet.\n *\n * A holder rather than a `let`: `settle` reads it before `showPanel` assigns\n * it, which is exactly the shape `prefer-const` rejects, and the object says\n * \"not handed over yet\" more plainly than an unassigned binding.\n */\n const slot: { token?: symbol } = {};\n /**\n * Who had the focus before the panel took it.\n *\n * The panel focuses itself on open (`panel.focus()`, both branches below)\n * and nothing gave it back: a keyboard user who approved a tool call landed\n * at the top of the document and had to tab through the whole page to write\n * their next message. That is SC 2.4.3, level A, on the one flow the library\n * puts forward — and the ARIA Authoring Practices Guide requires it of every\n * dialogue-shaped pattern.\n *\n * Captured HERE rather than beside `panel.focus()` because there are two\n * branches that open a panel and one `close()` that ends both; a value read\n * once, before either, cannot disagree with itself.\n */\n const focusedBefore = document.activeElement;\n const close = (): boolean => {\n if (done) return false;\n done = true;\n this._pending = null;\n /*\n * Read BEFORE `hidePanel`, because that removes the focused element and\n * the browser then drops focus to `<body>` — after which there is no\n * way to tell whether the user was still in the panel or had moved on.\n *\n * And only if they were still in it. A request can settle late — an\n * abort, or a model that answered while the reader clicked elsewhere —\n * and pulling the focus back from wherever they went would be the same\n * theft in the other direction.\n */\n const active = document.activeElement;\n const focusWasInPanel = active instanceof Node && composer.contains(active);\n\n // Scoped to our own panel: finishing late must not tear down the panel\n // that replaced ours.\n composer.hidePanel(slot.token);\n\n /*\n * `isConnected` because the element that had the focus may itself have\n * been inside what just closed. Nothing further if it is gone: inventing\n * a destination — the editor, the send button — would be a policy this\n * component has no standing to set, and `<body>` is where the browser\n * puts it anyway.\n */\n if (focusWasInPanel && focusedBefore instanceof HTMLElement && focusedBefore.isConnected) {\n focusedBefore.focus();\n }\n return true;\n };\n const settle = (result: AparteElicitationResult): void => {\n if (close()) resolve(result);\n };\n /**\n * End it without an answer.\n *\n * A rejection rather than a third `action`, because a value is easy to\n * mistake for an answer and this one was: the approval gate read the old\n * `cancel` as a refusal and told the model the user had refused a tool they\n * had only stopped.\n */\n const fail = (reason: 'aborted' | 'no-presenter' = 'aborted'): void => {\n if (close()) reject(new AparteElicitationAbortError(reason));\n };\n\n // Caller-side cancellation (tool handler signal: timeout / turn abort).\n if (request.signal) {\n if (request.signal.aborted) { fail(); return; }\n request.signal.addEventListener('abort', () => fail(), { once: true });\n }\n\n // Built INSIDE this instance's config, so the panel's own strings come\n // from the locale of the chat that asked — `contextConfig()` reads the\n // ambient render config, and without this it would fall back to the\n // global one on a page where each chat has its own.\n const cfg = resolveConfig(this);\n\n /*\n * An APPROVAL: a decision, not a value.\n *\n * Same slot, same queue, same teardown — only what goes inside the panel\n * differs, which is the whole claim of one mechanism with two\n * presentations. The options come with the request because only the\n * requester can write them.\n *\n * There is always an exit here without touching the composer's own\n * controls: every option is a button. That matters because a panel takes\n * the send button over, so Stop is unreachable while one is open — for a\n * question the escape is the corner, for an approval it is a refusal.\n */\n if (request.kind === 'approval') {\n /*\n * The button exists only once an INSTRUCTION has been written.\n *\n * The options never route through it — a decision is its own click —\n * so with `mode: 'submit'` from the start it sat there permanently\n * disabled beside them, offering an act that did not exist. It is the\n * written text, and only that, which is the act this button already\n * means; until there is some, the panel has none for it.\n */\n const approvalMode = (): AparteComposerPanelMode => (panel.isComplete() ? 'submit' : 'none');\n const panel = runWithConfig(cfg, () =>\n buildApprovalPanel(request.message, request.options ?? [], () => {\n composer.setPanelSubmitEnabled(panel.isComplete(), approvalMode());\n }));\n panel.onSettle((answer) => settle({ action: 'accept', content: answer }));\n this._pending = { abort: () => fail(), composer, relabel: () => panel.relabel() };\n slot.token = composer.showPanel(panel.el, {\n submitEnabled: panel.isComplete(),\n mode: approvalMode(),\n onSubmit: () => { if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() }); },\n onEvict: () => fail(),\n });\n panel.focus();\n return;\n }\n // A question without a schema has nothing to collect; that is an approval,\n // and it was handled above. The fallback keeps this branch total rather\n // than throwing on a shape the type already forbids.\n const schema = request.schema ?? { type: 'string' as const };\n const panel: BuiltElicitationPanel = runWithConfig(cfg, () =>\n buildElicitationPanel(request.message, schema, () => {\n composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());\n }));\n\n // \"Skip\" → decline (MCP's decline: the user chose not to answer), in the\n // panel's CORNER. It sat beside the button that advances through the form,\n // and that adjacency read as \"skip this question\" while it declines the\n // whole request — see `dismiss` on BuiltElicitationPanel.\n const skip = document.createElement('button');\n skip.type = 'button';\n skip.className = 'aparte-btn aparte-elic-skip';\n skip.textContent = cfg.t('elicitationSkip');\n skip.addEventListener('click', () => settle({ action: 'decline' }));\n panel.dismiss.appendChild(skip);\n\n /*\n * The click that IS the answer — a single question whose options are\n * buttons. Same wiring as the approval panel's, because it is the same\n * act; the panel decides which of its shapes has it, and reports through\n * `mode()` that the composer's button has nothing to do.\n */\n panel.onSettle((content) => settle({ action: 'accept', content }));\n\n this._pending = {\n abort: () => fail(),\n composer,\n relabel: () => { panel.relabel(); skip.textContent = resolveConfig(this).t('elicitationSkip'); },\n };\n slot.token = composer.showPanel(panel.el, {\n submitEnabled: panel.canProceed(),\n mode: panel.mode(),\n /*\n * Something else took the slot — another request, a conversation switch,\n * or a turn ending. The composer tears the panel down either way; only\n * this callback can settle the promise, and without it the request hung\n * AND `_pending` stayed set, so every later question was short-circuited\n * for the life of the page. `cancel`, not `decline`: nobody declined\n * anything, the question was taken away.\n */\n onEvict: () => fail(),\n onSubmit: () => {\n // The same button advances through the form and submits at the end;\n // the panel is what knows which of the two this click is.\n if (panel.mode() === 'advance') {\n panel.proceed();\n composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());\n return;\n }\n if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() });\n },\n });\n panel.focus();\n });\n };\n\n private _cancelPending(): void {\n this._pending?.abort();\n }\n\n /**\n * The host id of the chat whose composer holds the open panel.\n *\n * Walks up from the composer rather than from `this`, because the panel lives in\n * the composer and that is what the turn belongs to. Matches the hosts\n * `aparte-chat-bubble._resolveTargetId()` matches, for the reason written there:\n * Angular's wrapper root IS the `<aparte-chat>` element, while the plain-root\n * wrappers render a `[data-aparte-chat]` div instead, so matching only the tag\n * resolves `undefined` on three wrappers out of four.\n */\n private _pendingTargetId(): string | undefined {\n let el: HTMLElement | null = this._pending?.composer ?? null;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || tag === 'aparte-chat-component' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n /**\n * The composer to present in: the nearest one in an ancestor subtree, and\n * nothing else.\n *\n * There used to be a `document.querySelector('aparte-composer')` fallback, which\n * is the \"first chat on the page\" bug this repo has now fixed in four other\n * places: on a page with two chats, an elicitation that could not find its own\n * composer mounted its panel in the OTHER chat's — so one conversation's question\n * appeared under the other conversation, and answering it resolved a tool call\n * belonging to a chat the user was not looking at.\n *\n * Returning `null` instead REJECTS the request, which is honest: nothing was\n * shown, so nothing was answered. The warning names the fix, because this is a\n * setup mistake and only the developer can correct it — the guide's own example\n * puts `<aparte-elicitation>` inside `<aparte-chat>`.\n */\n private _getComposer(): ComposerEl | null {\n let node: Element | null = this.parentElement;\n while (node) {\n const composer = node.querySelector('aparte-composer') as ComposerEl | null;\n if (composer && typeof composer.showPanel === 'function') return composer;\n // Stop AT the chat boundary. Removing the explicit\n // `document.querySelector` fallback was not enough on its own: this walk\n // reached `<body>`, and a `querySelector` from there searches the whole\n // document — so it found another chat's composer anyway, by a longer\n // route. The two-chat test caught exactly that.\n const tag = node.tagName?.toLowerCase();\n const isChatBoundary = tag === 'aparte-chat' || tag === 'aparte-chat-component' || node.hasAttribute?.('data-aparte-chat');\n if (isChatBoundary) break;\n node = node.parentElement;\n }\n console.warn(\n '[aparte-elicitation] No <aparte-composer> in this element\\'s subtree, so the request '\n + 'could not be shown, so it REJECTED and the turn halted. Nothing was told to the '\n + 'model — there is nothing true to tell it. Move <aparte-elicitation> '\n + 'inside the <aparte-chat> it belongs to. It is deliberately NOT borrowing another '\n + 'chat\\'s composer: on a page with two chats that put the question under the wrong one.',\n );\n return null;\n }\n}\n\nif (typeof customElements !== 'undefined' && !customElements.get('aparte-elicitation')) {\n customElements.define('aparte-elicitation', AparteElicitation);\n}\n","import type { AparteChatViewport } from '../viewport/aparte-chat-viewport.js';\nimport type { AparteComposer } from '../composer/aparte-composer.js';\n// Defines <aparte-elicitation>, which the default composition below writes. A tag\n// nothing has defined is an inert unknown element, so the import is the difference\n// between a presenter and a placeholder.\nimport '../elicitation/aparte-elicitation.js';\nimport { escapeAttr } from '../../utils/escape.js';\n\n/**\n * AparteChat - The Shell\n *\n * The container element for a chat. It lays out its Light DOM children as a flex\n * column: an `<aparte-chat-viewport>` takes the space left over (`flex: 1 1 auto`)\n * and scrolls, an `<aparte-composer>` keeps its own height below it. Light DOM on\n * purpose, so the page's own global CSS reaches inside.\n *\n * The presence of an `<aparte-chat-viewport>` child at connect is the exact test for\n * \"the author composed this\". Find one and the children are used as given — this\n * element moves none of them, so anything else you drop in (a header, a banner above\n * the composer) is simply another row of that column, in DOM order. Find none and\n * `innerHTML` is OVERWRITTEN with a default composition — a viewport, an\n * `<aparte-elicitation>` presenter, and a composer shell holding an input and a send\n * button, plus the two attachment primitives when `attachments` is set — so children\n * written without a viewport anywhere inside them are destroyed, that header included.\n * The test is a DESCENDANT query, so a viewport nested in a wrapper of your own still\n * counts — compose it yourself with the viewport somewhere in the tree, or leave the tag\n * empty. Angular's wrapper sets `framework-managed` instead of relying on that test,\n * because its children do not exist yet when this element upgrades; React, Vue and\n * Svelte never create this element at all, so the question does not arise for them.\n *\n * Being a component (not a bare `<div>`), it also owns behaviour a wrapper div\n * can't: with `center-empty`, it watches its own viewport and keeps the composer\n * centered as a welcome state until the first `<aparte-chat-bubble>` lands, then\n * slides to the normal layout — no external JavaScript. While centered it carries\n * `data-empty` on itself (set and cleared by that same watcher), which is the hook to\n * style the welcome state from an app's own CSS. The watcher needs a viewport somewhere\n * inside, and hand-written markup always has one because composing the default injects\n * it — so the only path where no watcher starts and `data-empty` is never set is\n * `framework-managed`, where the framework owns the subtree anyway. The stylesheet\n * centers through\n * `aparte-chat[center-empty][data-empty]` and its DIRECT viewport child, so a\n * framework-managed host that nests the viewport inside a container of its own gets\n * nothing from the attribute — the wrappers ship their own centered layout.\n *\n * It is also one of the anchors where core re-declares its derived CSS layer, so\n * overriding a master — `--aparte-primary`, a surface, a text colour — on a single\n * `<aparte-chat>` re-derives the values computed from it for that instance rather\n * than moving one button. That is per-instance theming. The literal palette is\n * deliberately not re-declared here, so a chat nested in a dark wrapper stays dark.\n *\n * Presentational only: it does NOT wire a transport/client. Attach an\n * `AparteClient`, or handle `aparte-send` yourself, as with the primitives.\n * Size the element via CSS (a height, or let it fill a sized parent).\n *\n * @element aparte-chat\n * @attr {string} placeholder - Placeholder for the composer input (default composition)\n * @attr {boolean} disabled - Disables the composer\n * @attr {boolean} center-empty - Center the composer as a welcome state until the first message, then slide to the normal layout\n * @attr {boolean} framework-managed - The wrapper's explicit hands-off signal: set it and this\n * element composes none of its own children, because the framework owns them. Read once at\n * connect (it is not observed), so it has to be in the initial markup. Angular's wrapper sets\n * it on this element — its component selector IS `aparte-chat`; React/Vue/Svelte render a\n * `[data-aparte-chat]` div and never create this element at all.\n * @attr {boolean} attachments - Add the file picker + chips strip to the default composition (opt-in: the host must consume the files — an `AparteClient` does, a hand-rolled loop must read `event.detail.files`)\n *\n * @cssprop [--aparte-chat-bottom-gap=var(--aparte-space-8, 16px)] - Space below the\n * composer, as `padding-block-end` on the shell (the same rule covers a wrapper's\n * `[data-aparte-chat]` root). The gap belongs to this element because padding applied\n * from outside would also shrink the scroll area, stopping the transcript short of the\n * edge instead of scrolling to it.\n *\n * Composing it yourself is the other form, and the container still lays it out and still\n * runs `center-empty`. It is written out here rather than as a second `@example` for a\n * mechanical reason: every element-own example is concatenated into ONE live frame on the\n * generated reference page, so a second `<aparte-chat>` there rendered as a second whole\n * chat — two empty composers with 600px of nothing between them.\n *\n * ```html\n * <aparte-chat center-empty attachments style=\"height: 320px\">\n * <aparte-chat-viewport></aparte-chat-viewport>\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n * ```\n *\n * @example\n * <!-- Left empty it fills in a viewport, an input and a send button. -->\n * <aparte-chat center-empty placeholder=\"Say something…\" style=\"height: 320px\"></aparte-chat>\n *\n * <script>\n * // Seeded so the frame shows a real exchange rather than an empty box: this\n * // example is RENDERED, not only read.\n * const chat = document.querySelector('aparte-chat');\n * chat.viewport.appendMessage({ id: 'u1', role: 'user', content: 'What is a transport?' });\n * chat.viewport.appendMessage({\n * id: 'a1',\n * role: 'assistant',\n * content: 'The object that talks to the model. Swap it and the UI does not change.',\n * });\n * </script>\n */\nexport class AparteChat extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['placeholder', 'disabled', 'center-empty', 'attachments'];\n }\n\n private _observer: MutationObserver | null = null;\n\n /**\n * True only for the composition THIS element injected. An author-provided\n * composer (or a `framework-managed` host) is never edited by the attachments\n * toggle below — those own their own markup.\n */\n private _ownsShell = false;\n\n connectedCallback(): void {\n this._render();\n this._forwardAttr('placeholder');\n this._forwardAttr('disabled');\n this._syncEmptyWatch();\n }\n\n disconnectedCallback(): void {\n this._observer?.disconnect();\n this._observer = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n if (name === 'center-empty') {\n this._syncEmptyWatch();\n return;\n }\n if (name === 'attachments') {\n this._syncAttachments();\n return;\n }\n // placeholder / disabled forward to the inner composer. An explicit removal\n // mirrors through; a never-set attribute is left alone (so a caller-provided\n // composer keeps its own).\n const composer = this.querySelector('aparte-composer');\n if (!composer) return;\n if (newValue !== null) composer.setAttribute(name, newValue);\n else composer.removeAttribute(name);\n }\n\n /** The message viewport (yours or the default), or `null` before connect. */\n get viewport(): AparteChatViewport | null {\n return this.querySelector('aparte-chat-viewport');\n }\n\n /** The composer (yours or the default), or `null` before connect. */\n get composer(): AparteComposer | null {\n return this.querySelector('aparte-composer');\n }\n\n private _render(): void {\n // A framework wrapper renders the composition itself — and its children do not\n // exist yet when this runs (the element is upgraded on insert, before the\n // framework's template renders), so the viewport check below can't see them.\n // `framework-managed` is the wrapper's explicit \"hands off\" signal: without it\n // the default composition below would be injected UNDER the framework's own.\n if (this.hasAttribute('framework-managed')) return;\n\n // Author-provided composition wins — if a viewport is already inside, use the\n // children as given and only lay them out (via CSS). Otherwise fill in a\n // default viewport + composer so the empty tag \"just works\".\n if (this.querySelector('aparte-chat-viewport')) return;\n\n // The composer's `placeholder` is read by its input via `closest()` at upgrade\n // time (no event), so it must be on the element in the initial markup.\n const placeholder = this.getAttribute('placeholder');\n const composerAttrs =\n (placeholder !== null ? ` placeholder=\"${escapeAttr(placeholder)}\"` : '') +\n (this.hasAttribute('disabled') ? ' disabled' : '');\n\n // Attachments are opt-in: the picker only makes sense when the host consumes\n // the files (an `AparteClient` inlines them per `rawFileInject`; a hand-rolled\n // loop must read `event.detail.files`). Offering it unconditionally would show\n // a button that silently drops what the user attached.\n const attachments = this.hasAttribute('attachments');\n\n /*\n * The presenter ships in the default composition, and that is a change of tier.\n *\n * It renders nothing by itself — it registers as the presenter for this subtree and\n * mounts a panel in the composer when something asks. It used to be opt-in, which\n * was right while asking the user was a plugin's business. It is not any more: the\n * BUILT-IN approval gate asks through it, so a chat without one cannot honour\n * `needsApproval` at all. An affordance core can honour end to end is on by default\n * (ratified decision #8, tier a); leaving this out would have made the gate depend\n * on a tag nobody was told to write.\n */\n this.innerHTML = `\n <aparte-chat-viewport></aparte-chat-viewport>\n <aparte-elicitation></aparte-elicitation>\n <aparte-composer${composerAttrs}>\n <div class=\"aparte-composer-shell\">\n ${attachments ? '<aparte-composer-attachments></aparte-composer-attachments>' : ''}\n <div class=\"aparte-composer-row\">\n ${attachments ? '<aparte-composer-add-attachment></aparte-composer-add-attachment>' : ''}\n <aparte-composer-input></aparte-composer-input>\n <aparte-composer-send></aparte-composer-send>\n </div>\n </div>\n </aparte-composer>\n `;\n this._ownsShell = true;\n }\n\n /**\n * Add/remove the two attachment primitives on the composition we injected, so\n * toggling the attribute after mount works like the wrappers' reactive prop\n * (there, a re-render does it). Author-provided markup is left alone.\n */\n private _syncAttachments(): void {\n if (!this._ownsShell) return;\n const composer = this.composer;\n const shell = composer?.querySelector('.aparte-composer-shell');\n const row = shell?.querySelector('.aparte-composer-row');\n if (!composer || !shell || !row) return;\n\n const strip = shell.querySelector('aparte-composer-attachments');\n const picker = row.querySelector('aparte-composer-add-attachment');\n\n if (this.hasAttribute('attachments')) {\n if (!strip) shell.insertBefore(document.createElement('aparte-composer-attachments'), row);\n if (!picker) row.insertBefore(document.createElement('aparte-composer-add-attachment'), row.firstChild);\n return;\n }\n\n strip?.remove();\n picker?.remove();\n // Files picked before the capability was withdrawn would otherwise ride on\n // the next send with nothing in the UI showing them.\n composer.clearAttachments();\n }\n\n /** Set an attribute on the inner composer only when the shell carries it. */\n private _forwardAttr(name: string): void {\n if (!this.hasAttribute(name)) return;\n this.querySelector('aparte-composer')?.setAttribute(name, this.getAttribute(name) ?? '');\n }\n\n /** Start/stop watching the viewport so `center-empty` toggles itself. */\n private _syncEmptyWatch(): void {\n this._observer?.disconnect();\n this._observer = null;\n\n if (!this.hasAttribute('center-empty')) {\n this.removeAttribute('data-empty');\n return;\n }\n\n const viewport = this.querySelector('aparte-chat-viewport');\n if (!viewport) return;\n\n this._updateEmpty();\n // A message is an <aparte-chat-bubble>; watch the viewport for the first one.\n this._observer = new MutationObserver(() => this._updateEmpty());\n this._observer.observe(viewport, { childList: true, subtree: true });\n }\n\n private _updateEmpty(): void {\n const viewport = this.querySelector('aparte-chat-viewport');\n const empty = !viewport || !viewport.querySelector('aparte-chat-bubble');\n this.toggleAttribute('data-empty', empty);\n }\n\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat')) {\n customElements.define('aparte-chat', AparteChat);\n}\n","import type {\n AparteBubbleRole,\n AparteSegment,\n AparteAttachment,\n AparteBranchNavigateEventDetail,\n AparteRetryEventDetail,\n AparteEditEventDetail,\n AparteFeedbackEventDetail,\n AparteActionEventDetail,\n AparteMessageInfoEventDetail,\n AparteUsage,\n AparteMessage,\n} from '../../types/index.js';\nimport { getSegmentRenderer, installDefaultRenderersOnce } from '../../renderers/index.js';\nimport { writeStreamedMarkdown, type AparteMarkdownStreamHost } from '../../renderers/markdown-stream.js';\nimport { AparteConfig } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig } from '../../config/config-context.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { mergeSegmentUpdate } from '../../utils/segments.js';\nimport type { AparteComposerInput } from '../composer/aparte-composer-input.js';\nimport { escapeAttr, escapeHtml } from '../../utils/escape.js';\n\n/**\n * Warn ONCE when a segment has no renderer — now only for types core has never\n * heard of, since the built-ins install themselves on first use.\n */\nlet _warnedNoRenderer = false;\nfunction warnMissingRenderer(type: string): void {\n if (_warnedNoRenderer) return;\n _warnedNoRenderer = true;\n console.warn(`[aparte] No renderer for segment \"${type}\". Register one with registerSegmentRenderer({ type: '${type}', render }) from @aparte/core — see https://apartejs.dev/guides/customization/#custom-segment-types`);\n}\n\n/**\n * What a segment renders as when no renderer claims its type.\n *\n * `AparteCustomSegment.fallback` is documented as \"Optional fallback text\n * representation\" and was read by NOTHING — the field existed, the type published it,\n * and a custom segment arriving where its renderer is not registered (a conversation\n * replayed in another app, a client that loads its views lazily, an export) showed\n * `[Unknown segment type: custom]` while carrying the sentence written for exactly that\n * moment. Found while writing the segment's own `@example`, which is the kind of dead\n * declaration documentation is good at surfacing.\n *\n * The developer warning is skipped when a fallback is present: an author who supplied\n * one has already said this can happen, and warning then is crying wolf. Without one it\n * still fires, because a missing renderer is otherwise silent.\n *\n * `textContent`, so a fallback is text and cannot carry markup — the same rule the rest\n * of the library follows for anything a model or a host can produce.\n */\nfunction unrenderedSegment(segment: { type: string; fallback?: unknown }): HTMLElement {\n const fallback = typeof segment.fallback === 'string' && segment.fallback.trim() ? segment.fallback : null;\n if (!fallback) warnMissingRenderer(segment.type);\n const el = document.createElement('div');\n el.className = fallback ? 'aparte-segment aparte-segment-fallback' : 'aparte-segment aparte-segment-unknown';\n el.textContent = fallback ?? `[Unknown segment type: ${segment.type}]`;\n return el;\n}\n\n/**\n * The renderer for `type`, installing core's built-ins the first time a segment\n * finds the registry empty of its type. `registerDefaultRenderers()` therefore\n * becomes optional rather than a call you discover by seeing\n * `[Unknown segment type: text]` on screen (it is still honoured, and\n * `AparteClient({ autoRegister: false })` still keeps the built-ins out).\n */\nfunction resolveSegmentRenderer(\n type: string,\n config: AparteConfig,\n): ReturnType<typeof getSegmentRenderer> {\n // The CONFIG is passed in, not read ambiently.\n //\n // `runWithConfig` wrapped only `render` / `setup` / `update`, so the renderer's\n // OWN work was per-instance while the question \"which renderer is this?\" was\n // answered from a module-level registry. Two chats on a page therefore shared\n // their segment renderers no matter what `config` prop the wrapper was given —\n // half of the promise those props make.\n const renderer = getSegmentRenderer(type, config);\n if (renderer) return renderer;\n installDefaultRenderersOnce(config);\n return getSegmentRenderer(type, config);\n}\n\n/**\n * Normalize a segment renderer's output to a single element. Renderers may return\n * an HTML **string** (parsed via innerHTML — the built-in renderers) or a ready\n * **HTMLElement** (used directly, so custom renderers can wire event listeners /\n * framework nodes with no innerHTML XSS surface). See {@link AparteSegmentRenderer}.\n */\nfunction segmentRenderResultToElement(result: string | HTMLElement): HTMLElement | null {\n if (result instanceof HTMLElement) return result;\n const wrapper = document.createElement('div');\n wrapper.innerHTML = result;\n return wrapper.firstElementChild as HTMLElement | null;\n}\n\n/**\n * One message: plain content or a list of rich segments, in light DOM.\n *\n * Normally created for you by `<aparte-chat-viewport>`, one per message in the store;\n * you write the tag by hand only when you drive the DOM yourself. It is ONE message\n * with one role — a transcript is the viewport's job, and a bubble is not a\n * general-purpose card.\n *\n * **Not a slot host.** `_render()` writes its own markup into the light DOM on\n * connect, so children placed inside the tag are replaced rather than projected.\n * Everything customizable is a registered hook instead of a child: the structural\n * shell (`setBubbleShellRenderer` — it must root at `.aparte-message` and carry the\n * region hooks, since every query here is null-guarded and a partial shell silently\n * loses that region), the avatar (`setAvatarProvider`), the attachment chips\n * (`setAttachmentRenderer`), the `‹1/2›` position indicator\n * (`setSiblingNavRenderer`) and the body itself (`registerSegmentRenderer`).\n *\n * Two content paths, mutually exclusive: the `content` attribute (plain text run\n * through the configured Markdown provider, then highlighted once — after streaming\n * ends, not per token) and `setSegments()` / `addSegment()`. Segments win:\n * `.aparte-content` stays hidden for as long as any exist. The painted\n * `.aparte-message-content` box hides itself when there is nothing in it, so a\n * message that is only attachments is not a coloured rectangle.\n *\n * The bubble owns no transport and no host behaviour. The action bar and the branch\n * picker only dispatch the events below; nothing here retries a turn, persists an\n * edit, opens a stats popover or switches a branch. Which buttons exist follows from\n * that: `copy` is on by default, `edit` / `retry` / `feedback` need\n * `setBubbleActions`, `info` needs both that flag and a prior `setUsage()` (a details\n * button over no numbers is a dead button), and an image attachment becomes a preview\n * button only once `setHostHandlers` declares a lightbox — undeclared it stays a\n * picture, with no role, tab stop or pointer.\n *\n * The error state is derived from the segments (an `error` segment sets `data-error`\n * on `.aparte-message`), never from a status attribute, so it behaves identically in\n * vanilla and in every wrapper.\n *\n * All seven events are declared by hand rather than left to the analyser, which\n * found six. `aparte-branch-navigate` is dispatched from the `_onBranchPickerClick`\n * arrow class field, and the auto-detection visits `ts.isMethodDeclaration` only —\n * so the one event belonging to the branch picker was the one missing from the\n * manifest, and from the generated reference, for as long as both existed.\n *\n * @element aparte-chat-bubble\n *\n * @attr {string} role - The message role. `data-role` is the styled mirror of it.\n * @attr {string} data-role - `user` / `assistant` / `system`; what the CSS keys off.\n * @attr {string} content - Plain text content, for a bubble with no segments.\n * @attr {number | string} timestamp - Epoch milliseconds OR a date string: `_updateTimestamp` accepts either and only coerces when the value is numeric.\n * @attr {string} message-id - How streaming and the action bar address this bubble.\n * @attr {boolean} streaming - Hides the action bar and shows the caret while a reply is in flight.\n * @attr {string} name - The display name in the header.\n *\n * @fires {CustomEvent<AparteActionEventDetail>} aparte-action - A custom action-bar button was pressed.\n * @fires {CustomEvent<AparteRetryEventDetail>} aparte-retry - Retry was pressed; the host forks the turn.\n * @fires {CustomEvent<AparteEditEventDetail>} aparte-edit - An edit was saved.\n * @fires {CustomEvent<AparteFeedbackEventDetail>} aparte-feedback - Thumbs up or down.\n * @fires {CustomEvent<AparteMessageInfoEventDetail>} aparte-message-info - The info affordance was pressed.\n * @fires {CustomEvent<AparteBranchNavigateEventDetail>} aparte-branch-navigate - The `‹1/2›` picker moved between sibling versions.\n * @fires {CustomEvent<AparteAttachmentPreviewEventDetail>} aparte-attachment-preview - An attached image was clicked, asking the app to open it full-size.\n *\n * @cssprop [--aparte-message-gap=12px] - Gap between the avatar column and the body (the viewport reuses it between messages).\n * @cssprop [--aparte-message-padding=16px 12px] - Padding around one message row.\n * @cssprop [--aparte-message-max-width=800px] - Width of the centred message row.\n *\n * @cssprop [--aparte-message-content-radius=14px] - Radius of the painted content box.\n * @cssprop [--aparte-message-content-padding=10px 14px] - Padding of the USER box only; the assistant's content is plain full-width prose.\n * @cssprop [--aparte-message-content-bg-user=#efe7f6] - Background of the user box.\n * @cssprop [--aparte-message-content-bg-assistant=transparent] - Background of the assistant box — transparent on purpose (AI-chat convention, not messaging).\n * @cssprop [--aparte-message-content-text-user=var(--aparte-text)] - Text colour inside the user box.\n * @cssprop [--aparte-message-content-text-assistant=var(--aparte-text)] - Text colour inside the assistant box.\n *\n * @cssprop [--aparte-avatar-size=32px] - Square size of the avatar slot.\n * @cssprop [--aparte-avatar-radius=var(--aparte-radius-avatar)] - Avatar corner radius.\n * @cssprop [--aparte-avatar-font-size=14px] - Size of the initial, for a shell that renders one (the default shell leaves the slot empty, and `.aparte-avatar:empty` hides it).\n * @cssprop [--aparte-avatar-bg-user=var(--aparte-primary)] - Avatar background, user role.\n * @cssprop [--aparte-avatar-text-user=var(--aparte-text-inverse)] - Avatar text colour, user role.\n * @cssprop [--aparte-avatar-bg-assistant=var(--aparte-surface-3)] - Avatar background, assistant role.\n * @cssprop [--aparte-avatar-text-assistant=var(--aparte-text-inverse)] - Avatar text colour, assistant role.\n * @cssprop [--aparte-avatar-image-user=none] - `background-image` for the user avatar — a logo with no AvatarProvider and no JS.\n * @cssprop [--aparte-avatar-image-assistant=none] - `background-image` for the assistant avatar.\n * @cssprop [--aparte-avatar-image-size=90%] - `background-size` for both avatar images.\n *\n * @cssprop [--aparte-name-font-size=14px] - Sender name in the header.\n * @cssprop [--aparte-name-color=var(--aparte-text)] - Sender name colour.\n * @cssprop [--aparte-timestamp-font-size=12px] - Timestamp in the header.\n * @cssprop [--aparte-timestamp-color=var(--aparte-text-muted)] - Timestamp colour.\n * @cssprop [--aparte-content-font-size=15px] - Body type size, applied to both the plain-content and the segments container.\n * @cssprop [--aparte-content-color=var(--aparte-text)] - Body text colour.\n * @cssprop [--aparte-content-line-height=var(--aparte-line-height-loose)] - Body line height.\n *\n * @cssprop [--aparte-attachments-max-height=140px] - Cap on the sent-attachment strip; past it the strip scrolls instead of growing.\n * @cssprop [--aparte-attachment-image-size=40px] - Tile size in the strip. The strip re-declares the global 72px down to 40px, since these are thumbnails inside a conversation.\n * @cssprop [--aparte-thumb-radius=var(--aparte-radius-lg)] - Attachment tile radius (shared with the composer's preview tiles).\n * @cssprop [--aparte-thumb-name-color=#ffffff] - Filename overlaid on a tile.\n * @cssprop --aparte-thumb-name-scrim - Gradient behind that filename, so it stays legible over any image.\n * @cssprop [--aparte-thumb-name-padding=14px 5px 4px] - Padding of the filename overlay.\n *\n * @cssprop [--aparte-action-bar-gap=4px] - Gap between action buttons (and between the footer's two regions).\n * @cssprop [--aparte-action-bar-btn-size=28px] - Square size of an action button; also the footer's reserved height.\n * @cssprop [--aparte-action-bar-btn-color=var(--aparte-text-muted)] - Action icon colour at rest.\n * @cssprop [--aparte-action-bar-btn-hover-bg=var(--aparte-surface-2)] - Action button hover background (the branch arrows reuse it).\n * @cssprop [--aparte-action-bar-btn-hover-color=var(--aparte-text)] - Action icon colour on hover.\n *\n * @cssprop [--aparte-branch-picker-gap=4px] - Gap between the arrows and the position label.\n * @cssprop [--aparte-branch-picker-btn-size=20px] - Square size of each arrow.\n * @cssprop [--aparte-branch-picker-btn-icon-size=16px] - Glyph size inside an arrow.\n * @cssprop [--aparte-branch-picker-btn-color=var(--aparte-text-muted)] - Arrow colour at rest.\n * @cssprop [--aparte-branch-picker-btn-hover-color=var(--aparte-text)] - Arrow colour on hover (a disabled arrow is dimmed instead).\n * @cssprop [--aparte-branch-picker-label-size=12px] - Type size of the position label.\n * @cssprop [--aparte-branch-picker-label-color=var(--aparte-text-muted)] - Colour of the position label.\n * @cssprop [--aparte-branch-picker-label-min-width=32px] - Reserved label width, so `9 / 9` growing to `10 / 12` does not shift the arrows.\n *\n * @cssprop [--aparte-waiting-height=1.5em] - Min height of the waiting region, so the first token does not jump the layout.\n * @cssprop [--aparte-waiting-dot-gap=4px] - Gap between the three waiting dots.\n * @cssprop [--aparte-status-dot-size=6px] - Diameter of a waiting dot (shared with the status indicator).\n * @cssprop [--aparte-status-color=var(--aparte-text-muted)] - Colour of the waiting dots (shared with the status indicator).\n *\n * @cssprop [--aparte-error-solid=#dc2626] - Ring drawn around the avatar while `data-error` is set. The error CARD itself belongs to the error segment renderer.\n *\n * @example\n * <!-- Rendered for you by the viewport. Written by hand only when you drive the DOM\n * yourself: `message-id` is what streaming and the action bar address it by. -->\n * <aparte-chat-bubble\n * message-id=\"a1\"\n * data-role=\"assistant\"\n * name=\"Assistant\"\n * content=\"Hello.\"\n * ></aparte-chat-bubble>\n *\n * <!-- While a reply is in flight: `streaming` hides the action bar and shows the caret. -->\n * <aparte-chat-bubble message-id=\"a2\" data-role=\"assistant\" streaming></aparte-chat-bubble>\n *\n * <!-- One reply among several. `setSiblings(count, index)` is what draws the picker, and\n * it is a METHOD, not an attribute — so a branch cannot be shown by markup alone.\n * Retry forks a sibling instead of overwriting the reply, and this is the control\n * that walks them; each press dispatches `aparte-branch-navigate` for a host to\n * answer. Kept in the example because a guide that describes branching has no other\n * way to SHOW it. -->\n * <aparte-chat-bubble\n * message-id=\"a3\"\n * data-role=\"assistant\"\n * name=\"Assistant\"\n * content=\"A second take on the same question.\"\n * ></aparte-chat-bubble>\n *\n * <script>\n * document.querySelector('aparte-chat-bubble[message-id=\"a3\"]').setSiblings(2, 0);\n * </script>\n */\nexport class AparteChatBubble extends HTMLElement {\n private _contentEl: HTMLDivElement | null = null;\n private _segmentsEl: HTMLDivElement | null = null;\n private _attachmentsEl: HTMLDivElement | null = null;\n private _actionBarEl: HTMLDivElement | null = null;\n private _branchPickerEl: HTMLDivElement | null = null;\n private _footerEl: HTMLDivElement | null = null;\n private _content = '';\n private _streaming = false;\n private _segments: AparteSegment[] = [];\n private _role: AparteBubbleRole = 'assistant';\n private _attachments: AparteAttachment[] = [];\n private _usage: AparteUsage | null = null;\n /** Cleanup returned by the avatar provider — called on disconnect/re-render. */\n private _avatarCleanup: (() => void) | null = null;\n /** Sibling count for tree-based branch navigation (set by setSiblings()) */\n private _siblingCount = 1;\n /** Sibling index for tree-based branch navigation (set by setSiblings()) */\n private _siblingIndex = 0;\n /** True while the user-message inline editor is open. */\n private _editing = false;\n /** The live inline editor (the composer's contenteditable primitive), present only while `_editing`. */\n private _editInput: AparteComposerInput | null = null;\n\n static get observedAttributes(): string[] {\n // Both `data-role` (preferred, set by Angular wrapper) and `role` (legacy\n // / direct usage) feed into the same _role state. The host element gets\n // its own `role=\"article\"` set in _render() for ARIA compliance — that\n // is filtered in attributeChangedCallback so it doesn't loop back as a\n // bubble role of \"article\".\n return ['role', 'data-role', 'content', 'timestamp', 'message-id', 'streaming', 'name'];\n }\n\n constructor() {\n super();\n }\n\n // Rebuild the action bar when the global config changes (e.g. a live skin\n // switch calling setBubbleActions / setIconProvider) so already-rendered\n // bubbles pick up the new per-role actions + icons without being re-created.\n private _onConfigChange = (e: Event): void => {\n // Only rebuild for OUR config. An instance-scoped change on another chat —\n // or a global change while we resolve to an instance — must not touch us.\n // A bare dispatch (no detail.config) always rebuilds (e.g. manual notify).\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n this._updateActionBar();\n // Everything else the locale writes. A language switch is documented as live\n // (\"mounted components re-render immediately\"), and rebuilding only the action\n // bar delivered half of it: the labels changed language while the NAME still\n // read \"You\" and the branch arrows kept their old `aria-label` — a bilingual\n // bubble, fixed only by a reload (which rebuilds the element).\n this._updateName();\n this._updateLocalizedLabels();\n this._updateWaiting();\n // An avatar provider is config too, and it was the one provider a live change\n // never reached: swap the set and every bubble already on screen kept the old\n // one. `_renderAvatar` tears down the previous mount before re-mounting, so\n // calling it again is safe, and it no-ops when no provider is registered.\n this._renderAvatar();\n this._relabelSegments();\n // The clock, too. A tag change is a formatting change, so the timestamp has to\n // be re-rendered or the language switches around a 12-hour time that stays.\n this._updateTimestamp(this.getAttribute('timestamp'));\n };\n\n /**\n * Ask every rendered segment to re-read its config-derived text.\n *\n * Not `_renderSegments()`, which wipes the container and rebuilds: that destroys a\n * mounted artifact preview, reverts a reasoning block the reader expanded by\n * clicking `<summary>` (the DOM's real state is never written back to `collapsed`),\n * resets scroll inside long terminal panes, drops focus from an Approve/Reject\n * gate, and throws away the incremental Markdown parser's buffered lookahead\n * mid-stream — for a change that added no content. It also fires container-wide\n * childList mutations, which is what the viewport's observer reads as \"scroll to\n * the bottom\".\n *\n * `relabel` is the narrow alternative, bound by the same no-child-node rule as\n * `update()`. A renderer that has no config-derived text does not implement it,\n * and this loop simply skips it.\n */\n private _relabelSegments(): void {\n if (!this._segmentsEl) return;\n for (const segment of this._segments) {\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (!renderer?.relabel) continue;\n const el = this._segmentsEl.querySelector(\n `:scope > [data-segment-id=\"${cssEscape(segment.id)}\"]`,\n ) as HTMLElement | null;\n if (!el) continue;\n runWithConfig(this._cfg, () => renderer.relabel!(el, segment));\n }\n }\n\n /**\n * Re-apply the locale strings written straight into the markup by `_render()` —\n * the accessible names a screen reader reads, which nothing else refreshes.\n */\n private _updateLocalizedLabels(): void {\n const locale = this._cfg.getLocale();\n const set = (selector: string, label: string): void => {\n this.querySelector(selector)?.setAttribute('aria-label', label);\n };\n set('.aparte-branch-prev', locale.previousResponse ?? 'Previous response');\n set('.aparte-branch-next', locale.nextResponse ?? 'Next response');\n set('.aparte-action-bar', locale.messageActions ?? 'Message actions');\n }\n\n /**\n * Config governing this bubble: the instance config of the nearest\n * `[data-aparte-host]` boundary, else the global singleton. Resolved live\n * (a single `closest()`) rather than cached — the boundary may be attached\n * AFTER this bubble mounts (AparteChatHost.bind() runs post-mount), so a\n * connect-time cache would freeze the wrong config.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n\n connectedCallback(): void {\n this._render();\n this._updateContent();\n // Populate the timestamp from the current attribute. Frameworks that set\n // attributes BEFORE the element is connected (e.g. the Svelte wrapper) fire\n // attributeChangedCallback while _render() hasn't created `.aparte-timestamp`\n // yet, so the initial time would otherwise stay blank. No-ops when the\n // attribute is absent (set later → attributeChangedCallback handles it).\n this._updateTimestamp(this.getAttribute('timestamp'));\n window.addEventListener('aparte-config-change', this._onConfigChange);\n // Delegated, so a re-render cannot lose a click on the branch arrows.\n this.addEventListener('click', this._onBranchPickerClick);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n this.removeEventListener('click', this._onBranchPickerClick);\n if (this._avatarCleanup) {\n try { this._avatarCleanup(); } catch { /* ignore */ }\n this._avatarCleanup = null;\n }\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'role':\n case 'data-role':\n // Skip the ARIA-compliance value we set ourselves in _render().\n // Real bubble roles are 'user' or 'assistant'; anything else is\n // either the 'article' we wrote for accessibility or stale.\n if (newValue === 'article') return;\n if (newValue === 'user' || newValue === 'assistant') {\n this._role = newValue as AparteBubbleRole;\n this._updateRole();\n }\n break;\n case 'content':\n this._content = newValue || '';\n // A replace, like setContent — see _resetMarkdownStream.\n this._resetMarkdownStream();\n this._updateContent();\n break;\n case 'timestamp':\n this._updateTimestamp(newValue);\n break;\n case 'streaming':\n this._updateStreaming(newValue !== null && newValue !== 'false');\n break;\n case 'name':\n this._updateName();\n break;\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public API\n // ─────────────────────────────────────────────────────────────────────────\n\n /** Append a token chunk (for streaming) */\n appendToken(chunk: string): void {\n this._content += chunk;\n this._updateContent();\n }\n\n /** Set content directly */\n setContent(content: string): void {\n this._content = content;\n this.setAttribute('content', content);\n // A REPLACE, not an append: the incremental parser tracks how many characters it has\n // already written, so leaving its state behind would make the next token's delta a\n // slice of the wrong string. A retry does exactly this — clear, then re-stream.\n this._resetMarkdownStream();\n this._updateContent();\n }\n\n /**\n * Drop the incremental Markdown parser's state.\n *\n * Only needed where `_content` is REPLACED rather than grown. `appendToken` grows it, so\n * the parser's cursor stays valid there — which is the whole point of the seam.\n */\n private _resetMarkdownStream(): void {\n const host = this as AparteMarkdownStreamHost;\n if (host._aparteSmd) host._aparteSmd.renderer.end();\n host._aparteSmd = undefined;\n }\n\n /** Get current content */\n getContent(): string {\n return this._content;\n }\n\n /** Set segments for rich content */\n setSegments(segments: AparteSegment[]): void {\n /*\n * Copy the array IN, the way `getSegments()` already copies it OUT.\n *\n * That asymmetry was the bug: the bubble defended its list on the way out and\n * adopted the caller's on the way in. `populateBubbleFromMessage` hands over\n * `message.segments` — the repository's own array — so the bubble and the model\n * ended up advancing ONE array. `appendToSegment` then wrote each chunk twice:\n * the viewport replaced the slot with `{...segment, content: old + chunk}`, the\n * bubble looked the segment up in what it thought was its own list, found that\n * replacement (chunk already in it) and appended the chunk again. Measured:\n * \"ThatThat deletesdeletes aa filefile\".\n *\n * This is the same failure 3b026bb fixed for `addSegment` — where it does not\n * happen, because the bubble pushes into a list it created itself, so the\n * viewport's replacement decouples the two immediately. A message arriving with\n * its segments already populated went around that fix, exactly as `AparteClient`\n * went around the one before it. One copy here closes the last shared array:\n * `setSegments` has a single production caller, so all three paths through\n * `populateBubbleFromMessage` are covered by this line.\n *\n * The objects stay shared, deliberately — that is the arrangement `addSegment`\n * produces and that `appendToSegment` is written for: the first write on either\n * side replaces its own slot and the two are independent from then on.\n */\n this._segments = [...segments];\n this._renderSegments();\n this._updateWaiting();\n }\n\n /** Add a segment */\n addSegment(segment: AparteSegment): void {\n this._segments.push(segment);\n this._appendSegmentEl(segment);\n this._updateWaiting();\n }\n\n /** Update a specific segment */\n updateSegment(segmentId: string, updates: Partial<AparteSegment>): void {\n const index = this._segments.findIndex(s => s.id === segmentId);\n if (index !== -1) {\n const updated = mergeSegmentUpdate(this._segments[index]!, updates);\n this._segments[index] = updated;\n this._applySegmentUpdate(segmentId, updated, updates);\n }\n }\n\n /** Append content to a segment */\n appendToSegment(segmentId: string, content: string): void {\n const segment = this._segments.find(s => s.id === segmentId);\n if (segment && 'content' in segment) {\n (segment as { content: string }).content += content;\n this._applySegmentUpdate(segmentId, segment, { content: (segment as AparteSegment & { content: string }).content });\n }\n }\n\n /** Get all segments */\n getSegments(): AparteSegment[] {\n return [...this._segments];\n }\n\n /** Remove a segment by id (e.g. to discard a transient waiting indicator) */\n /**\n * Scoped to DIRECT children on purpose.\n *\n * Segments are appended as direct children of the container, but a descendant\n * query returns the first match in document order — and sanitized model\n * markdown renders inside that same container, with `data-*` attributes\n * deliberately preserved (they are inert). So a decoy `data-segment-id` planted\n * in an earlier segment's prose used to win over the real segment element.\n *\n * Parser ids are unguessable UUIDs, but a tool segment is `tool-${toolCallId}`\n * and the MODEL chooses that id — so this was reachable, and pointing an update\n * at a decoy left a rejected tool rendering as still-running: a spoof against\n * the human-in-the-loop control.\n */\n removeSegment(segmentId: string): void {\n const index = this._segments.findIndex(s => s.id === segmentId);\n if (index !== -1) {\n this._segments.splice(index, 1);\n }\n const el = this._segmentsEl?.querySelector(`:scope > [data-segment-id=\"${cssEscape(segmentId)}\"]`);\n el?.remove();\n this._updateWaiting();\n }\n\n /** Set attachments (chips shown above message content, user role only) */\n setAttachments(attachments: AparteAttachment[]): void {\n this._attachments = attachments;\n this._updateAttachments();\n }\n\n /**\n * Set token usage + timing for this message (assistant only).\n *\n * This is the *precondition* for the info (\"i\") action, not the trigger: the\n * button appears only if the app also declared it wants it —\n * `aparteGlobalConfig.setBubbleActions({ info: true })` — because the stats popover it\n * opens (`aparte-message-info`) is the app's, and core has none. Without usage\n * there is nothing to show, so the button never renders either way.\n */\n setUsage(usage: AparteUsage | null | undefined): void {\n this._usage = usage ?? null;\n this._updateActionBar();\n }\n\n /**\n * Update the branch picker UI for tree-based navigation.\n * The viewport calls this after a branch switch or re-render.\n * Prev/Next clicks dispatch `aparte-branch-navigate` (bubbles: true) so\n * the viewport can handle the actual tree switch.\n */\n setSiblings(count: number, index: number): void {\n this._siblingCount = count;\n this._siblingIndex = index;\n this._updateBranchPicker();\n }\n\n /**\n * Atomic update for the message\n */\n updateMessage(updates: Partial<AparteMessage>): void {\n if ('role' in updates) {\n this._role = updates.role!;\n this._updateRole();\n }\n if ('content' in updates) {\n this._content = updates.content!;\n this._updateContent();\n }\n if ('segments' in updates) {\n this._segments = updates.segments!;\n this._renderSegments();\n }\n if ('timestamp' in updates) {\n this._updateTimestamp(updates.timestamp!);\n }\n if ('status' in updates) {\n const isStreaming = updates.status === 'streaming' || updates.status === 'pending';\n this._updateStreaming(isStreaming);\n }\n if ('attachments' in updates) {\n this._attachments = updates.attachments ?? [];\n this._updateAttachments();\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n private _appendSegmentEl(segment: AparteSegment): void {\n if (!this._segmentsEl) {\n console.warn(`[AparteChatBubble] _appendSegmentEl ABORT: _segmentsEl is null`);\n return;\n }\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (renderer) {\n // Renderers are plain functions with no element to resolve from — expose\n // this bubble's config as the ambient render config for the duration.\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));\n if (el) {\n this._segmentsEl.appendChild(el);\n runWithConfig(this._cfg, () => renderer.setup?.(el, segment));\n }\n } else {\n this._segmentsEl.appendChild(unrenderedSegment(segment));\n }\n if (this._contentEl) this._contentEl.style.display = 'none';\n this._reflectError();\n }\n\n private _applySegmentUpdate(segmentId: string, segment: AparteSegment, updates: Partial<AparteSegment>): void {\n const el = this._segmentsEl?.querySelector(`:scope > [data-segment-id=\"${cssEscape(segmentId)}\"]`) as HTMLElement | null;\n if (!el) {\n this._renderSegments();\n return;\n }\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (!renderer) return;\n\n if (renderer.update) {\n runWithConfig(this._cfg, () => renderer.update!(el, segment));\n } else {\n const newEl = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));\n if (newEl) {\n el.replaceWith(newEl);\n runWithConfig(this._cfg, () => renderer.setup?.(newEl, segment));\n }\n }\n\n // Handle collapsed state only when explicitly provided in the update —\n // never override a state the user set by clicking <summary>.\n if ('collapsed' in updates) {\n if ((updates as { collapsed?: boolean }).collapsed) {\n el.removeAttribute('open');\n } else {\n el.setAttribute('open', '');\n }\n }\n }\n\n private _getDisplayName(): string {\n const nameAttr = this.getAttribute('name');\n if (nameAttr) return nameAttr;\n const locale = this._cfg.getLocale();\n return this._role === 'user'\n ? (locale.roleNameUser ?? 'You')\n : (locale.roleNameAssistant ?? 'Assistant');\n }\n\n private _getAvatarInitial(): string {\n const name = this._getDisplayName();\n return name.length > 0 ? name[0]! : (this._role === 'user' ? 'U' : 'A');\n }\n\n private _render(): void {\n // Read the bubble's logical role from `data-role` (preferred — written\n // by the Angular wrapper) or the legacy `role` attribute, then set the\n // host's actual `role` attribute to a valid ARIA value. \"user\" and\n // \"assistant\" are NOT valid ARIA roles and would trigger accessibility\n // warnings in browsers / Lighthouse. The role-based styling lives on\n // inner `data-role` markers, so this swap is transparent to CSS.\n const dataRole = this.getAttribute('data-role');\n const legacyRole = this.getAttribute('role');\n const role = (dataRole && dataRole !== 'article') ? dataRole\n : (legacyRole && legacyRole !== 'article') ? legacyRole\n : 'assistant';\n this._role = role as AparteBubbleRole;\n if (this.getAttribute('role') !== 'article') {\n this.setAttribute('role', 'article');\n }\n if (!this.hasAttribute('data-role')) {\n this.setAttribute('data-role', role);\n }\n\n // Ensure we don't overwrite if already rendered (re-entrancy check)\n if (this.querySelector('.aparte-message')) return;\n\n const displayName = this._getDisplayName();\n const initial = this._getAvatarInitial();\n\n // Custom structural shell (aparteGlobalConfig.setBubbleShellRenderer). Must root at\n // .aparte-message + carry the region hooks; the queries below are null-guarded\n // so a partial shell degrades gracefully. See AparteBubbleShellRenderer.\n const shell = this._cfg.getBubbleShellRenderer?.();\n if (shell) {\n const out = runWithConfig(this._cfg, () => shell({ role: this._role, name: displayName, avatarInitial: initial }));\n if (out instanceof HTMLElement) this.replaceChildren(out);\n else this.innerHTML = out;\n } else {\n this.innerHTML = `\n <div class=\"aparte-message\" data-role=\"${escapeAttr(role)}\" role=\"article\" aria-label=\"${escapeAttr(this._getAriaLabel())}\">\n <div class=\"aparte-avatar\" data-role=\"${escapeAttr(role)}\"></div>\n <div class=\"aparte-body\">\n <div class=\"aparte-header\">\n <span class=\"aparte-name\">${escapeHtml(displayName)}</span>\n <span class=\"aparte-timestamp\"></span>\n </div>\n <div class=\"aparte-attachments\" hidden></div>\n <div class=\"aparte-message-content\">\n <div class=\"aparte-segments\"></div>\n <div class=\"aparte-content\"></div>\n <div class=\"aparte-waiting\" hidden>\n <span class=\"aparte-dots\" aria-hidden=\"true\"><span class=\"aparte-dot\"></span><span class=\"aparte-dot\"></span><span class=\"aparte-dot\"></span></span>\n <span class=\"aparte-sr-only\"></span>\n </div>\n </div>\n <div class=\"aparte-footer\">\n <div class=\"aparte-branch-picker\" hidden>\n <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev\" aria-label=\"${escapeAttr(this._cfg.getLocale().previousResponse ?? 'Previous response')}\">‹</button>\n <span class=\"aparte-branch-label\">1 / 1</span>\n <!-- The move has to be ANNOUNCED. Pressing the arrows deliberately does not\n take focus, so without a live region a screen-reader user gets the new\n branch and no indication anything changed. The visible label cannot be\n the region itself: a custom sibling-nav renderer may replace it with\n dots, which reads as nothing. No new locale key — the position is\n digits, and the buttons beside it already carry translated labels. -->\n <span class=\"aparte-sr-only aparte-branch-status\" aria-live=\"polite\"></span>\n <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next\" aria-label=\"${escapeAttr(this._cfg.getLocale().nextResponse ?? 'Next response')}\">›</button>\n </div>\n <div class=\"aparte-action-bar\" role=\"toolbar\" aria-label=\"${escapeAttr(this._cfg.getLocale().messageActions ?? 'Message actions')}\"></div>\n </div>\n </div>\n </div>\n `;\n }\n\n this._contentEl = this.querySelector('.aparte-content');\n this._segmentsEl = this.querySelector('.aparte-segments');\n this._attachmentsEl = this.querySelector('.aparte-attachments');\n this._actionBarEl = this.querySelector('.aparte-action-bar');\n this._branchPickerEl = this.querySelector('.aparte-branch-picker');\n this._footerEl = this.querySelector('.aparte-footer');\n\n this._updateActionBar();\n this._renderAvatar();\n // Re-apply the streaming state onto the freshly-built `.aparte-message`.\n // Framework wrappers create the element with its attributes already set, so\n // `streaming` arrives BEFORE this render and `_updateStreaming()` had nothing\n // to write to — leaving a pending assistant bubble without `aria-busy` and\n // with its action bar exposed (copy/retry on an empty, still-streaming reply).\n if (this._streaming) this._updateStreaming(true);\n this._updateWaiting();\n }\n\n /**\n * Show the built-in waiting indicator while this bubble is in flight and has\n * nothing to show yet — the gap between \"user sends\" and the first token, which\n * used to be a bubble with a name and an empty body.\n *\n * The dots are CSS (no per-token work, themable, honours reduced-motion); the\n * accessible name is `locale.typing`, next to the `aria-busy` the streaming state\n * already sets. A custom bubble shell without the region simply has no indicator\n * (same null-guarded degradation as the other region hooks).\n */\n private _updateWaiting(): void {\n const empty = this._segments.length === 0 && !this._content.trim();\n const waiting = this._streaming && this._role !== 'user' && empty;\n\n // The painted box is hidden when it has nothing to paint. It carries the user\n // bubble's background, padding and radius, so an empty one is a coloured\n // rectangle with nothing in it — which is exactly what a message that is ONLY\n // attachments produced: the chips render ABOVE this box, so the box had no\n // content, no segments and no dots, and still drew itself.\n //\n // `hidden` and not `style.display`, deliberately: nothing sets an explicit\n // `display` on this class, so the UA sheet's rule applies. Where a component\n // DOES set one, `[hidden]` loses — a trap this repo has already paid for.\n const box = this.querySelector('.aparte-message-content') as HTMLElement | null;\n if (box) box.hidden = empty && !waiting;\n\n const el = this.querySelector('.aparte-waiting') as HTMLElement | null;\n if (!el) return;\n el.hidden = !waiting;\n if (!waiting) return;\n const label = this._cfg.getLocale().typing;\n const sr = el.querySelector('.aparte-sr-only');\n if (sr && sr.textContent !== label) sr.textContent = label;\n }\n\n /**\n * Hand the avatar host element off to the registered AvatarProvider, if any.\n *\n * With no provider the slot is left exactly as the shell rendered it — which for\n * the default shell means EMPTY, and hidden by `.aparte-avatar:empty`. This used\n * to claim it \"falls back to the default initial rendered by `_render()`\"; there\n * is no such initial, and believing there was is what made `_updateRole` write\n * one.\n */\n private _renderAvatar(): void {\n const avatar = this.querySelector('.aparte-avatar') as HTMLElement | null;\n if (!avatar) return;\n\n // Tear down any previously-mounted live component before re-rendering.\n if (this._avatarCleanup) {\n try { this._avatarCleanup(); } catch { /* ignore */ }\n this._avatarCleanup = null;\n }\n\n const provider = this._cfg.getAvatarProvider();\n if (!provider) return; // leave the slot as the shell rendered it\n\n avatar.textContent = '';\n const cleanup = provider.render(this._role, avatar);\n if (typeof cleanup === 'function') this._avatarCleanup = cleanup;\n }\n\n private _updateRole(): void {\n const message = this.querySelector('.aparte-message');\n const avatar = this.querySelector('.aparte-avatar');\n const nameEl = this.querySelector('.aparte-name');\n\n if (message) {\n message.setAttribute('data-role', this._role);\n message.setAttribute('aria-label', this._getAriaLabel());\n }\n if (avatar) {\n avatar.setAttribute('data-role', this._role);\n // Refresh an initial that is ALREADY there; never create one — the default\n // shell renders this slot empty and the stylesheet hides it while it stays\n // empty. Same rule as `_updateName`, which is where it was actually costing\n // something; the reasoning is written out there.\n if (avatar.textContent) avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) {\n nameEl.textContent = this._getDisplayName();\n }\n // Re-render the action bar so buttons match the correct role\n // (critical when the role attribute is set after connectedCallback)\n this._updateActionBar();\n this._renderAvatar();\n }\n\n private _updateName(): void {\n const avatar = this.querySelector('.aparte-avatar') as HTMLElement | null;\n const nameEl = this.querySelector('.aparte-name');\n /*\n * Two conditions, and the second one is the fix.\n *\n * No provider: otherwise a name change would wipe a live avatar component.\n *\n * Already non-empty: the default shell renders this slot EMPTY and the\n * stylesheet hides it while it stays empty — `.aparte-avatar:empty { display:\n * none }`, with the comment \"No message avatar by default — the slot only shows\n * once an AvatarProvider (or a consumer) fills it\". Writing the initial\n * unconditionally contradicted that, and `_onConfigChange` calls this method, so\n * ANY notifying config change filled the slot: `setLocale` (a language switcher\n * is enough), `setBubbleActions`, `setIconProvider`. Avatars appeared across the\n * transcript on a click that had nothing to do with them, and undoing the click\n * did not remove them, because the text was already written.\n *\n * The guard is \"already non-empty\" rather than \"no provider\" on purpose:\n * `avatarInitial` is part of the shell contract, so a CUSTOM shell may render an\n * initial and must still see it refreshed. Empty stays empty; filled stays in\n * sync.\n */\n if (avatar && avatar.textContent && !this._cfg.getAvatarProvider()) {\n avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) nameEl.textContent = this._getDisplayName();\n }\n\n private _updateContent(): void {\n if (!this._contentEl) return;\n\n // If we have segments, don't render simple content\n if (this._segments.length > 0) {\n this._contentEl.style.display = 'none';\n this._updateWaiting();\n return;\n }\n\n this._contentEl.style.display = '';\n /*\n * The SAME incremental seam the text and thinking segment renderers use.\n *\n * This line used to be `innerHTML = renderMarkdown(this._content)` — the whole message\n * re-parsed, re-sanitised and re-inserted on every token. That is the hot path of the\n * first thing getting-started teaches (`appendMessage` / `appendToken` /\n * `completeMessage`), and it made a published promise false: `setStreamingMarkdownProvider`\n * says \"the chat bubble uses it to render the assistant message token-by-token\n * instead of re-parsing the whole string on every token\", and the plugin's own page\n * repeats it. Only the segment path honoured it. Found by a cold audit.\n *\n * With no streaming provider registered, `writeStreamedMarkdown` falls through to the\n * one-shot render — so a consumer who has not installed the plugin sees exactly what\n * they saw before.\n *\n * `runWithConfig`, because the seam reads its provider from the ambient config and this\n * bubble may be one of several with configs of their own.\n */\n runWithConfig(this._cfg, () =>\n writeStreamedMarkdown(this as AparteMarkdownStreamHost, this._contentEl!, this._content, this._streaming),\n );\n // The first token retires the waiting indicator (and a cleared content brings\n // it back, e.g. a retry that resets the bubble before re-streaming).\n this._updateWaiting();\n // The Markdown provider only emits plain <pre><code>; apply the registered\n // syntax highlighter (if any) to those blocks. Skipped while streaming —\n // re-run once on completion (see _updateStreaming) to avoid per-token churn.\n if (!this._streaming) this._highlightContentCode();\n }\n\n /**\n * Apply the registered syntax-highlight provider to the code blocks produced\n * by the Markdown provider in the simple-content path. Provider-agnostic: a\n * full-block provider (e.g. Shiki) returns `<pre>…</pre>` so we replace the\n * element; a token provider (e.g. Prism, highlight.js) returns inner HTML so\n * we fill the existing `<code>`. No-op when no highlighter is installed.\n */\n private _highlightContentCode(): void {\n if (!this._contentEl || !this._cfg.hasHighlightProvider()) return;\n this._contentEl.querySelectorAll('pre > code').forEach((codeEl) => {\n const code = codeEl.textContent ?? '';\n if (!code.trim()) return;\n const match = codeEl.className.match(/language-([\\w+#-]+)/i);\n const lang = match?.[1] ?? '';\n const pre = codeEl.parentElement;\n Promise.resolve(this._cfg.highlightCode(code, lang)).then((html) => {\n const out = (html ?? '').trim();\n if (!out || !pre || !pre.isConnected) return;\n if (/^<pre[\\s>]/i.test(out)) {\n pre.outerHTML = out; // full block (Shiki)\n } else {\n (codeEl as HTMLElement).innerHTML = out; // inner tokens (Prism, hljs)\n }\n }).catch(() => { /* keep the plain block on failure */ });\n });\n }\n\n private _renderSegments(): void {\n if (!this._segmentsEl) return;\n\n // Clear existing segments\n this._segmentsEl.innerHTML = '';\n\n for (const segment of this._segments) {\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (renderer) {\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)));\n if (el) {\n this._segmentsEl.appendChild(el);\n runWithConfig(this._cfg, () => renderer.setup?.(el, segment));\n }\n } else {\n this._segmentsEl.appendChild(unrenderedSegment(segment));\n }\n }\n\n // Hide simple content when segments are present\n if (this._contentEl) {\n this._contentEl.style.display = this._segments.length > 0 ? 'none' : '';\n }\n this._reflectError();\n }\n\n /**\n * Reflect the error state on the bubble: `data-error` on `.aparte-message` while\n * an error segment is present. Derived from segments (not the message `status`\n * attribute) so it works identically in vanilla and in every wrapper — the\n * error segment flows through the reactive list in all of them. CSS themes\n * `.aparte-message[data-error]`; custom error content is via setErrorRenderer.\n */\n private _reflectError(): void {\n const message = this.querySelector('.aparte-message');\n if (!message) return;\n const hasError = this._segments.some(s => s.type === 'error');\n if (hasError) message.setAttribute('data-error', '');\n else message.removeAttribute('data-error');\n }\n\n private _updateTimestamp(value: string | number | null): void {\n const timestampEl = this.querySelector('.aparte-timestamp');\n if (!timestampEl || !value) return;\n\n try {\n const date = new Date(isNaN(Number(value)) ? value : Number(value));\n // The locale's own tag, not `undefined`. `undefined` means \"follow the\n // BROWSER\", which is why a French chat on an en-US browser still read\n // `7:32 PM` — the app had chosen a language and the clock had not heard.\n // Still `undefined` when no tag is declared: that is the documented default\n // and the behaviour every consumer has today.\n timestampEl.textContent = date.toLocaleTimeString(this._cfg.getLocale().tag || undefined, {\n hour: '2-digit',\n minute: '2-digit'\n });\n } catch {\n timestampEl.textContent = '';\n }\n }\n\n private _getAriaLabel(): string {\n const locale = this._cfg.getLocale();\n return this._role === 'user'\n ? (locale.yourMessage ?? 'Your message')\n : (locale.assistantResponse ?? 'Assistant response');\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Attachments\n // ─────────────────────────────────────────────────────────────────────────\n\n private _updateAttachments(): void {\n if (!this._attachmentsEl) return;\n\n if (this._role !== 'user' || this._attachments.length === 0) {\n this._attachmentsEl.hidden = true;\n this._attachmentsEl.innerHTML = '';\n return;\n }\n\n this._attachmentsEl.hidden = false;\n\n // Custom attachment chips (aparteGlobalConfig.setAttachmentRenderer) — one node per\n // attachment; the consumer owns markup + interactions (no default preview wiring).\n const customAttachment = this._cfg.getAttachmentRenderer?.();\n if (customAttachment) {\n this._attachmentsEl.replaceChildren();\n for (const a of this._attachments) {\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(a)));\n if (el) this._attachmentsEl.appendChild(el);\n }\n return;\n }\n\n this._attachmentsEl.innerHTML = this._attachments.map(a => {\n const name = escapeHtml(a.name);\n if (a.type.startsWith('image/')) {\n return `<div class=\"aparte-thumb aparte-thumb--image\" title=\"${name}\">`\n + `<img class=\"aparte-thumb__img\" src=\"${escapeHtml(a.url)}\" alt=\"${name}\" loading=\"lazy\" />`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }\n return `<div class=\"aparte-thumb aparte-thumb--file\" title=\"${name}\">`\n + `<span class=\"aparte-thumb__ext\">${escapeHtml(this._fileExt(a.name))}</span>`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }).join('');\n\n // Image tiles ask for a full-size preview — but the lightbox is the app's, so\n // the tile only becomes a button once the app declared it opens one. Otherwise\n // it stays a plain picture: no role, no tab stop, no pointer (see the CSS,\n // which keys the cursor off role=\"button\").\n if (!this._cfg.getHostHandlers().attachmentPreview) return;\n this._attachmentsEl.querySelectorAll('.aparte-thumb--image').forEach(tile => {\n tile.setAttribute('role', 'button');\n tile.setAttribute('tabindex', '0');\n const open = (): void => {\n const img = tile.querySelector('.aparte-thumb__img') as HTMLImageElement | null;\n if (!img) return;\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true, composed: true,\n detail: { url: img.src, name: tile.getAttribute('title') ?? '' },\n }));\n };\n tile.addEventListener('click', open);\n tile.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key !== 'Enter' && key !== ' ') return;\n e.preventDefault();\n open();\n });\n });\n }\n\n /** Uppercased file extension (≤4 chars), or 'FILE' when there is none. */\n private _fileExt(filename: string): string {\n const dot = filename.lastIndexOf('.');\n return dot > 0 ? filename.slice(dot + 1).toUpperCase().slice(0, 4) : 'FILE';\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Branch Picker\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * The branch arrows are handled by DELEGATION, on this element, bound once.\n *\n * They used to get a fresh listener each, attached by `_render()` to the buttons\n * `_render()` had just created. So a click that landed while a re-render was\n * swapping those nodes hit an element about to be discarded, and did nothing at\n * all — not late, nothing. Invisible on a fast machine; reproducible on\n * WebKit-Linux in CI, where `‹` left the picker on \"2 / 2\" and a 20-second\n * assertion watched it stay there.\n *\n * Delegation makes `_render()` irrelevant to it: the listener lives on the host,\n * which is never replaced, and `closest()` finds whichever button exists at the\n * moment of the click. It is also less work — one listener per bubble instead of\n * two per bubble per render.\n *\n * Bound in `connectedCallback` and removed in `disconnectedCallback` as a stable\n * field, because an inline arrow re-added on every re-connect is how this repo has\n * stacked listeners twice before (the viewport, and `aparte-select`).\n */\n private _onBranchPickerClick = (event: Event): void => {\n const target = event.target as HTMLElement | null;\n const button = target?.closest?.('.aparte-branch-prev, .aparte-branch-next');\n if (!button || !this.contains(button)) return;\n const direction = button.classList.contains('aparte-branch-prev') ? 'prev' : 'next';\n const messageId = this.getAttribute('message-id');\n if (!messageId) return;\n const detail: AparteBranchNavigateEventDetail = { messageId, direction };\n // Tree-based navigation: let the viewport handle the branch switch\n this.dispatchEvent(new CustomEvent<AparteBranchNavigateEventDetail>('aparte-branch-navigate', {\n bubbles: true,\n composed: true,\n detail,\n }));\n };\n\n private _updateBranchPicker(): void {\n if (!this._branchPickerEl) return;\n if (this._siblingCount <= 1 || this._role !== 'assistant') {\n this._branchPickerEl.hidden = true;\n this._syncFooterVisibility();\n return;\n }\n this._branchPickerEl.hidden = false;\n this._syncFooterVisibility();\n const label = this._branchPickerEl.querySelector('.aparte-branch-label');\n if (label) {\n // Custom position indicator (aparteGlobalConfig.setSiblingNavRenderer) — e.g. dots —\n // fills the label between the arrows; the arrows keep their behavior.\n const customNav = this._cfg.getSiblingNavRenderer?.();\n if (customNav) {\n const out = runWithConfig(this._cfg, () => customNav({ count: this._siblingCount, index: this._siblingIndex }));\n if (out instanceof HTMLElement) label.replaceChildren(out);\n else label.innerHTML = out;\n } else {\n label.textContent = `${this._siblingIndex + 1} / ${this._siblingCount}`;\n }\n }\n\n const status = this._branchPickerEl.querySelector('.aparte-branch-status');\n if (status) {\n const position = `${this._siblingIndex + 1} / ${this._siblingCount}`;\n if (status.textContent !== position) status.textContent = position;\n }\n\n const prevBtn = this._branchPickerEl.querySelector('.aparte-branch-prev') as HTMLButtonElement | null;\n const nextBtn = this._branchPickerEl.querySelector('.aparte-branch-next') as HTMLButtonElement | null;\n if (prevBtn) prevBtn.disabled = this._siblingIndex === 0;\n if (nextBtn) nextBtn.disabled = this._siblingIndex === this._siblingCount - 1;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Action Bar\n // ─────────────────────────────────────────────────────────────────────────\n\n private _updateActionBar(): void {\n if (!this._actionBarEl) return;\n // While the inline editor is open the bar shows save (✓) / cancel (✗).\n if (this._editing) {\n this._renderEditActions();\n return;\n }\n const config = this._cfg.getBubbleActions();\n const icons = this._cfg.getIconProvider();\n const locale = this._cfg.getLocale();\n const buttons: string[] = [];\n\n if (this._role === 'user') {\n if (config.user) {\n // Explicit ordered set replaces the flag defaults for user bubbles.\n for (const a of config.user) buttons.push(this._actionButtonHtml(a, icons, locale));\n } else {\n // Flag-driven set. Only `copy` is on by default — see\n // APARTE_DEFAULT_BUBBLE_ACTIONS: edit needs a host to keep the new text.\n if (config.copy) buttons.push(this._actionButtonHtml('copy', icons, locale));\n if (config.edit) buttons.push(this._actionButtonHtml('edit', icons, locale));\n }\n } else if (this._role === 'assistant') {\n if (config.assistant) {\n // Explicit ordered set replaces the flag defaults (incl. the info button).\n for (const a of config.assistant) buttons.push(this._actionButtonHtml(a, icons, locale));\n } else {\n // Flag-driven set. Only `copy` is on by default — retry, feedback and\n // info all need a host or a listener to mean anything.\n if (config.copy) buttons.push(this._actionButtonHtml('copy', icons, locale));\n if (config.retry) buttons.push(this._actionButtonHtml('retry', icons, locale));\n if (config.feedback) {\n buttons.push(this._actionButtonHtml('thumbUp', icons, locale));\n buttons.push(this._actionButtonHtml('thumbDown', icons, locale));\n }\n if (config.info) buttons.push(this._actionButtonHtml('info', icons, locale));\n }\n }\n\n this._actionBarEl.innerHTML = buttons.join('');\n\n // Custom actions registered via aparteGlobalConfig.registerAction — appended\n // after the built-ins, built as DOM (label goes to attributes, never\n // interpolated into innerHTML) so a consumer label can't inject markup.\n this._appendCustomActions(icons);\n\n // Wire up button handlers — messageId read dynamically at click time\n // so it's always correct even when Angular sets the attribute after connectedCallback\n this._actionBarEl.querySelectorAll('.aparte-action-btn').forEach(btn => {\n btn.addEventListener('click', (e) => this._handleActionClick(e as MouseEvent));\n });\n\n this._syncFooterVisibility();\n }\n\n /**\n * An empty action bar is not a bar: with every action off it was still a\n * `role=\"toolbar\"` with nothing in it (announced as such), and it still reserved\n * its fixed height plus the footer's under every bubble. So both follow their\n * contents — the footer stays as long as the branch picker or the bar has\n * something to show.\n */\n private _syncFooterVisibility(): void {\n if (this._actionBarEl) this._actionBarEl.hidden = this._actionBarEl.children.length === 0;\n if (!this._footerEl) return;\n const barEmpty = !this._actionBarEl || this._actionBarEl.hidden;\n const pickerHidden = !this._branchPickerEl || this._branchPickerEl.hidden;\n this._footerEl.hidden = barEmpty && pickerHidden;\n }\n\n /** Append the registered custom action buttons for this bubble's role. */\n private _appendCustomActions(icons: ReturnType<AparteConfig['getIconProvider']>): void {\n if (!this._actionBarEl) return;\n for (const a of this._cfg.getActions('bubble')) {\n const roles = a.bubble?.roles ?? ['user', 'assistant'];\n if (!roles.includes(this._role)) continue;\n const btn = document.createElement('button');\n btn.className = 'aparte-btn aparte-btn--icon aparte-action-btn aparte-action-custom';\n btn.dataset['action'] = `custom:${a.id}`;\n // aria-label/title via setAttribute — safe for consumer-provided strings.\n btn.setAttribute('aria-label', a.label);\n btn.setAttribute('title', a.label);\n // Icon: raw inline SVG/HTML, else an icon-provider key (trusted output).\n const fromProvider = (icons as unknown as Record<string, (() => string) | undefined>)[a.icon];\n btn.innerHTML = a.icon.startsWith('<')\n ? a.icon\n : (typeof fromProvider === 'function' ? fromProvider() : (a.iconFallback ?? ''));\n this._actionBarEl.appendChild(btn);\n }\n }\n\n /** Build the `<button>` HTML for a single named action (shared by flag + per-role rendering). */\n private _actionButtonHtml(\n action: string,\n icons: ReturnType<AparteConfig['getIconProvider']>,\n locale: ReturnType<AparteConfig['getLocale']>,\n ): string {\n switch (action) {\n case 'copy': {\n const l = locale.copy ?? 'Copy';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-copy\" data-action=\"copy\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.copy()}</button>`;\n }\n case 'edit': {\n const l = locale.edit ?? 'Edit message';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-edit\" data-action=\"edit\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.edit()}</button>`;\n }\n case 'retry': {\n const l = locale.retry ?? 'Retry';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-retry\" data-action=\"retry\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.retry()}</button>`;\n }\n case 'thumbUp': {\n const l = locale.feedbackPositive ?? 'Good response';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-pos\" data-action=\"feedback-positive\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.thumbUp()}</button>`;\n }\n case 'thumbDown': {\n const l = locale.feedbackNegative ?? 'Bad response';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-neg\" data-action=\"feedback-negative\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.thumbDown()}</button>`;\n }\n case 'info': {\n // Only when there are numbers to show: a details button over nothing is a\n // dead button. The popover itself is the app's (see `aparte-message-info`).\n if (!this._usage) return '';\n const l = locale.messageInfo ?? 'Details';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-info\" data-action=\"info\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${this._cfg.getIcon('info')}</button>`;\n }\n default:\n return '';\n }\n }\n\n /** Render the edit-mode action bar: ✓ save (green) + ✗ cancel (red). */\n private _renderEditActions(): void {\n if (!this._actionBarEl) return;\n const locale = this._cfg.getLocale();\n const saveLabel = locale.editConfirm ?? 'Save';\n const cancelLabel = locale.editCancel ?? 'Cancel';\n this._actionBarEl.innerHTML =\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--success aparte-action-btn aparte-action-edit-save\" data-action=\"edit-save\" ` +\n `aria-label=\"${escapeAttr(saveLabel)}\" title=\"${escapeAttr(saveLabel)}\">${this._cfg.getIcon('check')}</button>` +\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--danger aparte-action-btn aparte-action-edit-cancel\" data-action=\"edit-cancel\" ` +\n `aria-label=\"${escapeAttr(cancelLabel)}\" title=\"${escapeAttr(cancelLabel)}\">${this._cfg.getIcon('close')}</button>`;\n this._actionBarEl.querySelectorAll('.aparte-action-btn').forEach(btn => {\n btn.addEventListener('click', (e) => this._handleActionClick(e as MouseEvent));\n });\n // Save/cancel must show even when every action flag is off.\n this._syncFooterVisibility();\n }\n\n private _handleActionClick(e: MouseEvent): void {\n const btn = (e.currentTarget as HTMLElement);\n const action = btn.dataset['action'];\n // Read dynamically — attribute may not be set yet at render time\n const messageId = this.getAttribute('message-id');\n\n // Custom actions (aparteGlobalConfig.registerAction) emit a generic aparte-action\n // event carrying the action id — same DOM-event contract as retry/feedback.\n if (action?.startsWith('custom:') && messageId) {\n const actionId = action.slice('custom:'.length);\n const detail: AparteActionEventDetail = {\n actionId,\n zone: 'bubble',\n messageId,\n role: this._role,\n targetId: this._resolveTargetId(),\n };\n this.dispatchEvent(new CustomEvent<AparteActionEventDetail>('aparte-action', {\n bubbles: true, composed: true, detail,\n }));\n this._cfg.getActions('bubble').find(x => x.id === actionId)?.onClick?.(e);\n return;\n }\n\n switch (action) {\n case 'copy': {\n const text = this._content || this._segments.map(s => (s as { content?: string }).content ?? '').join('\\n');\n const icons = this._cfg.getIconProvider();\n const locale = this._cfg.getLocale();\n navigator.clipboard.writeText(text).then(() => {\n btn.innerHTML = icons.check();\n btn.setAttribute('data-copied', '');\n const copiedLabel = locale.copied ?? locale.copy ?? 'Copied';\n btn.setAttribute('title', copiedLabel);\n btn.setAttribute('aria-label', copiedLabel);\n setTimeout(() => {\n btn.removeAttribute('data-copied');\n btn.innerHTML = icons.copy();\n const copyLabel = locale.copy ?? 'Copy';\n btn.setAttribute('title', copyLabel);\n btn.setAttribute('aria-label', copyLabel);\n }, 2000);\n }).catch(() => {\n console.warn('[aparte] Clipboard write failed');\n });\n break;\n }\n case 'retry': {\n if (!messageId) break;\n const targetId = this._resolveTargetId();\n const detail: AparteRetryEventDetail = { messageId, targetId };\n this.dispatchEvent(new CustomEvent<AparteRetryEventDetail>('aparte-retry', {\n bubbles: true, composed: true,\n detail,\n }));\n break;\n }\n case 'edit': {\n this._enterEditMode();\n break;\n }\n case 'edit-save': {\n this._exitEditMode(true);\n break;\n }\n case 'edit-cancel': {\n this._exitEditMode(false);\n break;\n }\n case 'feedback-positive':\n case 'feedback-negative': {\n if (!messageId) break;\n const value: AparteFeedbackEventDetail['value'] = action === 'feedback-positive' ? 'positive' : 'negative';\n btn.setAttribute('data-submitted', '');\n const detail: AparteFeedbackEventDetail = { messageId, value };\n this.dispatchEvent(new CustomEvent<AparteFeedbackEventDetail>('aparte-feedback', {\n bubbles: true, composed: true,\n detail,\n }));\n break;\n }\n case 'info': {\n if (!messageId) break;\n const detail: AparteMessageInfoEventDetail = {\n messageId,\n usage: this._usage ?? undefined,\n };\n this.dispatchEvent(new CustomEvent<AparteMessageInfoEventDetail>('aparte-message-info', {\n bubbles: true, composed: true,\n detail,\n }));\n break;\n }\n }\n }\n\n /**\n * Open the inline editor for a user message. Idempotent — a second `edit`\n * click while already editing is a no-op (no stacked editors).\n *\n * The editor reuses the composer's contenteditable primitive\n * (`<aparte-composer-input>`) so editing is iso with composing: same autosize,\n * IME, paste and styling. With no `<aparte-composer>` root it runs standalone —\n * `Enter` (Shift+Enter = newline) surfaces as `aparte-composer-submit`, which we\n * treat as save; `Esc` cancels.\n */\n private _enterEditMode(): void {\n if (this._editing || !this._contentEl) return;\n this._editing = true;\n this.querySelector('.aparte-message')?.setAttribute('data-editing', '');\n\n const input = document.createElement('aparte-composer-input') as AparteComposerInput;\n input.setAttribute('placeholder', this._cfg.getLocale().edit ?? 'Edit message');\n this._editInput = input;\n\n this._contentEl.style.display = 'none';\n this._contentEl.insertAdjacentElement('afterend', input);\n // `insertAdjacentElement` upgrades + connects synchronously, so the editor is\n // ready — seed it with the current text (autosizes to fit).\n input.setValue(this._content);\n\n // Enter (via the primitive's standalone submit event) saves; Esc cancels.\n input.addEventListener('aparte-composer-submit', () => this._exitEditMode(true));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape' && !e.isComposing) {\n e.preventDefault();\n this._exitEditMode(false);\n }\n });\n\n // Swap the action bar over to ✓ / ✗.\n this._updateActionBar();\n\n input.focusEnd();\n }\n\n /**\n * Leave edit mode. When `save` is true and the text actually changed, emits\n * `aparte-edit`; otherwise restores the original message untouched. Always\n * restores the normal action bar and removes the inline editor.\n */\n private _exitEditMode(save: boolean): void {\n if (!this._editing) return;\n const newContent = this._editInput?.getValue() ?? '';\n const original = this._content;\n\n this._editInput?.remove();\n this._editInput = null;\n if (this._contentEl) this._contentEl.style.display = '';\n this.querySelector('.aparte-message')?.removeAttribute('data-editing');\n this._editing = false;\n this._updateActionBar();\n\n if (save && newContent && newContent !== original) {\n const messageId = this.getAttribute('message-id');\n if (messageId) {\n const detail: AparteEditEventDetail = {\n messageId,\n content: newContent,\n targetId: this._resolveTargetId(),\n };\n this.dispatchEvent(new CustomEvent<AparteEditEventDetail>('aparte-edit', {\n bubbles: true, composed: true,\n detail,\n }));\n }\n }\n }\n\n private _resolveTargetId(): string | undefined {\n // Walk up to the chat host element with an id. Angular's wrapper root IS the\n // `<aparte-chat>` element (its component selector); the plain-root wrappers\n // (React/Vue/Svelte) render a `<div class=\"aparte-chat-container\" data-aparte-chat\n // id=\"…\">` instead — so match `[data-aparte-chat]` too. Without this, retry/edit\n // resolved to `undefined` outside Angular and AparteClient's fallback hit the\n // bare `<aparte-chat-viewport>` (a different message store) → retry regenerated\n // into the void.\n let el: HTMLElement | null = this.parentElement;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || tag === 'aparte-chat-component' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Utilities\n // ─────────────────────────────────────────────────────────────────────────\n\n\n private _updateStreaming(streaming: boolean): void {\n const wasStreaming = this._streaming;\n this._streaming = streaming;\n const message = this.querySelector('.aparte-message');\n if (message) {\n message.setAttribute('data-streaming', String(streaming));\n if (streaming) {\n // Signal \"in progress\" to assistive tech; clearing it on completion\n // cues screen readers (via the viewport's aria-live region) to read\n // the finished response.\n message.setAttribute('aria-busy', 'true');\n message.classList.add('aparte-message-streaming');\n } else {\n message.removeAttribute('aria-busy');\n message.classList.remove('aparte-message-streaming');\n }\n }\n this._updateWaiting();\n // Streaming just finished: highlight the final content once (skipped during\n // streaming to avoid re-highlighting on every token).\n if (wasStreaming && !streaming) this._highlightContentCode();\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-bubble')) {\n customElements.define('aparte-chat-bubble', AparteChatBubble);\n}\n","import { AparteConfig } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig } from '../../config/config-context.js';\n\n/**\n * A standalone status line — a light-DOM indicator the APP owns. Nothing in core turns\n * it on: the framework host only ever flips it back OFF (on the first streamed token),\n * and the four wrappers render one inside the viewport driven by their own `isTyping`\n * prop.\n *\n * It dispatches nothing, deliberately: it reports, it does not ask.\n *\n * Use it for a state only the app knows about — \"Searching the docs…\", \"Uploading…\",\n * a queue position. It is NOT the indicator for the gap between a send and the first\n * token: that one is built into the bubble (`.aparte-waiting`, shown while `streaming`\n * is set on a non-user bubble that has nothing to display yet). Turning this element on\n * for that gap is how a page ends up showing two indicators for one wait.\n *\n * It does not project children: `_render()` writes the subtree — a\n * `.aparte-status-container` row holding an empty avatar div and an `.aparte-body` that\n * wraps `.aparte-status-content` — so markup authored between the tags does not\n * survive. That avatar div never gets contents here, and `.aparte-avatar:empty` is\n * `display: none`, so it is not a spacer: the line sits flush with the row padding\n * rather than indented under an assistant bubble's text column.\n *\n * The seam for custom contents is `setStatusRenderer`, scoped or global: the container\n * keeps owning show/hide (`data-visible`), the accessible name (`aria-label`) and its\n * `.aparte-message` row metrics whatever the renderer returns, but the pulsing dot and\n * the text node belong to the default path only.\n *\n * A `text` attribute renders the visible label and feeds the accessible name; with no\n * `text` the line is dots-only and the name falls back to the literal `Typing` — this\n * element does not read the locale. Hiding happens twice over: the host element is\n * `display: none` without `[visible]`, and `data-visible` drives the fade/translate on\n * the container.\n *\n * The config is resolved live rather than cached, so a `setStatusRenderer` call that\n * lands after this element has already upgraded still reaches it: the element\n * re-renders on `aparte-config-change`, filtered to its own config.\n *\n * The two borrowed row variables below have one scope caveat: inside a viewport\n * narrower than 520px core REASSIGNS `--aparte-message-padding` on `.aparte-message`\n * itself, so a declaration on this host element loses to it there.\n *\n * @element aparte-chat-status\n * @attr {boolean} visible - Shows or hides the indicator.\n * @attr {string} text - The line to show. Absent, the line is dots-only and the\n * accessible name falls back to the literal `Typing` (not the locale's string).\n *\n * @cssprop [--aparte-status-color=var(--aparte-text-muted)] - Colour of the label text and of the pulsing dot in the default line.\n * @cssprop [--aparte-status-font-size=13px] - Size of the visible label (italic by default) in the default line.\n * @cssprop [--aparte-status-dot-size=6px] - Diameter of the single pulsing dot in the default line.\n * @cssprop [--aparte-message-padding=16px 12px] - Padding of the row, read because the container also carries `.aparte-message` — the status line borrows a bubble's row metrics so it lines up with the transcript.\n * @cssprop [--aparte-message-max-width=800px] - Width cap of that same row.\n *\n * @example\n * <!-- The app owns this indicator: core turns it on for nobody, which is also why it\n * is the wrong tool for the wait before the first token — the bubble's built-in\n * waiting state already covers that one. -->\n * <aparte-chat-status visible text=\"Searching the docs…\"></aparte-chat-status>\n */\nexport class AparteChatStatus extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['visible', 'text'];\n }\n\n /**\n * Resolved LIVE, not cached. Caching it at connect made this element\n * permanently deaf to its own instance: `_onConfigChange` filters on\n * `detail.config !== this._cfg`, so once `_cfg` had latched the global config no\n * change for the real instance ever matched, and the filter meant to isolate\n * chats became the thing that silenced one.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n\n constructor() {\n super();\n }\n\n connectedCallback(): void {\n // Cache the resolved config (instance boundary or global fallback), like the\n // other Aparte elements — so a scoped setStatusRenderer applies here too.\n this._render();\n // Re-render on a live config change (e.g. setStatusRenderer called after this\n // element already upgraded — it self-registers on import, so a persistent\n // <aparte-chat-status> in the page mounts before any config runs). Mirrors the\n // bubble's config-change subscription.\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n private _onConfigChange = (e: Event): void => {\n // Only react to OUR config (an instance-scoped change elsewhere must not touch\n // us). A bare notify (no detail.config) always re-renders.\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n // Clear so _render's re-entrancy guard doesn't bail; visible/text are read\n // from attributes, so the shown state is preserved across the re-render.\n this.innerHTML = '';\n this._render();\n };\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'visible':\n this._updateVisibility(newValue !== null);\n break;\n case 'text':\n this._updateText(newValue);\n break;\n }\n }\n\n /**\n * Show the typing indicator\n */\n show(): void {\n this.setAttribute('visible', '');\n }\n\n /**\n * Hide the typing indicator\n */\n hide(): void {\n this.removeAttribute('visible');\n }\n\n /**\n * Toggle visibility\n */\n toggle(): void {\n if (this.hasAttribute('visible')) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n /**\n * Check if visible\n */\n isVisible(): boolean {\n return this.hasAttribute('visible');\n }\n\n private _render(): void {\n const text = this.getAttribute('text') || 'Typing';\n const visible = this.hasAttribute('visible');\n\n // Re-entrancy check\n if (this.querySelector('.aparte-status-container')) return;\n\n // Custom typing indicator (charter §6 render hook): replace the inner markup\n // while the container keeps owning show/hide (data-visible) + accessible name.\n const custom = this._cfg?.getStatusRenderer?.();\n if (custom) {\n this.innerHTML =\n `<div class=\"aparte-message aparte-status-container\" data-visible=\"${visible}\" role=\"status\" aria-live=\"polite\"></div>`;\n const container = this.querySelector('.aparte-status-container') as HTMLElement;\n // `text` set via setAttribute, never interpolated — a `\"` would break out.\n container.setAttribute('aria-label', text);\n const result = runWithConfig(this._cfg, () => custom(text));\n if (result instanceof HTMLElement) container.appendChild(result);\n else container.innerHTML = result;\n return;\n }\n\n this.innerHTML = `\n <div\n class=\"aparte-message aparte-status-container\"\n data-visible=\"${visible}\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <div class=\"aparte-avatar\" data-role=\"assistant\" style=\"visibility: hidden\"></div>\n <div class=\"aparte-body\">\n <div class=\"aparte-status-content\">\n <div class=\"aparte-dots\" aria-hidden=\"true\">\n <span class=\"aparte-dot\"></span>\n </div>\n <span class=\"aparte-status-text\"></span>\n </div>\n </div>\n </div>\n `;\n // Set the (public, attacker-controllable) `text` via setAttribute/textContent\n // rather than interpolating it into the innerHTML template — a `\"` in the\n // attribute would otherwise break out and inject arbitrary attributes.\n this.querySelector('.aparte-status-container')?.setAttribute('aria-label', text);\n // Visible text only when explicitly requested — the default stays dots-only\n // (the aria-label above always carries the accessible name).\n if (this.hasAttribute('text')) {\n const textEl = this.querySelector('.aparte-status-text');\n if (textEl) textEl.textContent = text;\n }\n }\n\n private _updateVisibility(visible: boolean): void {\n const container = this.querySelector('.aparte-status-container');\n if (container) {\n container.setAttribute('data-visible', String(visible));\n }\n }\n\n private _updateText(text: string | null): void {\n const textEl = this.querySelector('.aparte-status-text');\n const container = this.querySelector('.aparte-status-container');\n if (!container) return; // not rendered yet — _render() reads the attribute\n // Removing the attribute restores the dots-only default (empty visible\n // text); the aria-label always keeps an accessible name.\n if (textEl) textEl.textContent = text ?? '';\n container.setAttribute('aria-label', text || 'Typing');\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-status')) {\n customElements.define('aparte-chat-status', AparteChatStatus);\n}\n","import type {\n AparteMessage,\n AparteViewportConfig,\n AparteSegment,\n AparteSegmentUpdateEventDetail,\n ApartePathChangedEventDetail,\n AparteSiblingInfo,\n AparteUsage,\n} from '../../types/index.js';\nimport { resolveConfig } from '../../config/index.js';\nimport { AparteMessageRepository } from '../../runtime/message-repository.js';\nimport type { ExportedMessageRepository } from '../../runtime/message-repository.js';\nimport { populateBubbleFromMessage, type SyncableBubble } from '../bubble/bubble-sync.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { isAwaitingReply } from '../../utils/is-awaiting-reply.js';\nimport { revokeAttachmentUrls } from '../../utils/files-to-attachments.js';\nimport { uuid } from '../../utils/uuid.js';\nimport {\n stampSegmentOnInsert,\n adoptMessageSegments,\n stampSegmentOnUpdate,\n mergeSegmentUpdate,\n renumberSegments,\n openSegmentIds,\n stampSegmentActivity,\n isTerminalStatus,\n} from '../../utils/segments.js';\n\n/**\n * The transcript surface: a light-DOM container with sticky scrolling, token\n * streaming and segment-aware rendering.\n *\n * Features:\n * - Smart Scroll: Sticks to bottom when user is at bottom, stops on manual scroll up\n * - appendToken(): For simple content streaming\n * - appendToSegment(): For segment-aware streaming (thinking, code, etc.)\n *\n * Two DOM modes. By default the element builds its own scroll surface\n * (`.aparte-viewport-container`) around a `.aparte-messages-wrapper`, and creates the\n * `<aparte-chat-bubble>` elements itself (the last `max-rendered-bubbles` of the active\n * path). With `framework-managed` set it builds neither wrapper: the HOST is the scroll\n * surface, the framework owns the bubble elements, and the bottom spacer becomes additive\n * host padding instead of an element — a relocated or removed child is what desynchronises\n * a framework's view tree from the live DOM, so this mode touches neither. The one child\n * it appends in both modes is the scroll-to-bottom button, kept trailing.\n *\n * Children you write inside the element are just children: there is no shadow root and no\n * slot to target. In the default mode they are MOVED into the internal\n * `.aparte-messages-wrapper` at first render, ahead of the bottom spacer, so pre-rendered\n * `<aparte-chat-bubble>` elements land in the transcript flow. A custom element of your own\n * is relocated the same way, and if it carries `data-aparte-bubble` plus a matching\n * `message-id` it also receives the live token and segment pushes, not just a restyle.\n * Do not expect such a child to outlive the transcript, though: anything that re-renders the\n * active path (`addBranch`, `addSiblingOf`, `navigateBranch`, `importTree`) empties the\n * wrapper and rebuilds it from the repository, so only what the repository holds comes back —\n * and `clearAll()` removes `<aparte-chat-bubble>` nodes only, so a `[data-aparte-bubble]`\n * element of your own is left behind with nothing left to render. With `framework-managed`\n * set children are not relocated: they stay direct children of the host, which is itself the\n * scroll surface.\n *\n * Messages are held as a TREE (siblings, branches, an active path), which is what lets\n * a retry fork and a bubble's sibling picker navigate with no host object involved.\n *\n * What it is NOT is storage. `max-rendered-bubbles` is a DOM ceiling and never evicts\n * from the repository — the full tree and its snapshot stay complete, `exportTree()` /\n * `importTree()` hand that snapshot to whoever owns persistence, and real history\n * retention is configured on the conversation manager instead. It is not a chat either:\n * a bare viewport IS a valid `AparteClient` target, but the composer, the transport and\n * the shell layout are other elements.\n *\n * @element aparte-chat-viewport\n *\n * @attr {boolean} framework-managed - The wrapper's explicit hands-off signal: set it and this\n * element builds no wrapper of its own and relocates none of the nodes the FRAMEWORK renders\n * into it, because the framework owns them. Not \"none of its children\": core's own\n * scroll-to-bottom button is re-appended whenever it stops being last, and that path runs in\n * this mode only. All four wrappers set it.\n * @attr {number} scroll-threshold - How close to the bottom still counts as \"at the bottom\".\n * @attr {number} max-rendered-bubbles - Caps how many bubbles stay in the DOM; older ones are released.\n * @attr {number} max-messages - DEPRECATED. It used to evict messages from the model; it now\n * only caps rendered bubbles, which is what `max-rendered-bubbles` says. For real history\n * retention configure the conversation manager instead.\n *\n * @fires {CustomEvent<AparteSegmentUpdateEventDetail>} aparte-segment-update - A segment grew or settled during a stream.\n * @fires aparte-reset-done - `clearAll()` finished emptying the transcript. No detail.\n * @fires {CustomEvent<ApartePathChangedEventDetail>} aparte-path-changed - The active branch path changed, after a retry fork or a navigation.\n *\n * @cssprop [--aparte-viewport-padding=16px] - Padding around the transcript — on\n * `.aparte-messages-wrapper`, or on the host itself in framework-managed mode, where the\n * auto-scroll spacer is added on top of it. A container narrower than 520px tightens it in\n * the default mode only: that rule reassigns the variable on `.aparte-messages-wrapper`,\n * which framework-managed mode never builds.\n * @cssprop [--aparte-message-gap=12px] - Gap between consecutive bubbles in the transcript\n * column (both DOM modes). Shared: it is also the avatar-to-content gap inside a bubble.\n * @cssprop [--aparte-scrollbar-width=6px] - Width of the WebKit scrollbar on the scroll\n * surface. Firefox and the standard property use `scrollbar-width: thin` and ignore it.\n * @cssprop [--aparte-scroll-btn-size=36px] - Diameter of the scroll-to-bottom button. A\n * coarse pointer raises it to `--aparte-touch-target-size`.\n * @cssprop [--aparte-scroll-btn-shadow=0 2px 8px rgba(0, 0, 0, 0.12)] - Its shadow; the dark\n * theme sets a heavier one.\n *\n * @example\n * <!-- On its own, outside `<aparte-chat>`. Give it a height: it fills what it is given\n * and owns the scrolling inside that box, so a viewport in an auto-height parent\n * grows forever instead of scrolling. Messages are pushed in — it fetches nothing. -->\n * <aparte-chat-viewport style=\"height: 320px\"></aparte-chat-viewport>\n *\n * <script>\n * const viewport = document.querySelector('aparte-chat-viewport');\n * viewport.appendMessage({ id: 'u1', role: 'user', content: 'What is a transport?', timestamp: Date.now() });\n * viewport.appendMessage({\n * id: 'a1',\n * role: 'assistant',\n * content: 'The object that talks to the model. Swap it and the UI does not change.',\n * timestamp: Date.now(),\n * });\n * </script>\n *\n * @example\n * // Three calls are a whole streamed turn.\n * const viewport = document.querySelector('aparte-chat-viewport')!;\n *\n * viewport.appendMessage({ id: 'a1', role: 'assistant', content: '', timestamp: Date.now() });\n * for await (const chunk of tokens) viewport.appendToken('a1', chunk);\n * viewport.completeMessage('a1'); // stops the streaming caret\n */\nexport class AparteChatViewport extends HTMLElement {\n // The scroll surface: an internal `.aparte-viewport-container` div (core mode)\n // or the host element itself (framework-managed mode). HTMLElement covers both.\n private _container: HTMLElement | null = null;\n private _scrollBtn: HTMLButtonElement | null = null;\n private _bottomSpacer: HTMLDivElement | null = null;\n /**\n * In framework-managed mode there is no spacer ELEMENT (an extra child would\n * collide with the framework's own DOM reconciliation). The spacer is an\n * additive `padding-bottom` on the host, tracked here so `_recalculateSpacer`\n * can read the current value without measuring an element.\n */\n private _fwSpacerHeight = 0;\n private _spacerRafId: number | null = null;\n private _spacerFrozenUntil: number = 0;\n private _layoutTransitionMs: number = 0;\n private _repo = new AparteMessageRepository();\n private _isAutoScrollEnabled: boolean = true;\n /** The last scroll position we saw, so growth can be told from a gesture. */\n private _lastScrollTop = 0;\n private _scrollThreshold: number = 50;\n /** When true, the next _autoScroll() call uses smooth instead of instant, then resets. */\n private _smoothScrollOnce: boolean = false;\n /**\n * DOM render cap: the max number of `<aparte-chat-bubble>` elements kept in the\n * DOM at once (a perf ceiling for very long conversations). This NEVER evicts\n * messages from the repository — the full conversation tree and its persistence\n * snapshot stay intact; only the oldest rendered bubbles are dropped from view.\n */\n private _maxRenderedBubbles: number = 1000;\n /** One-time guard for the deprecated `maxMessages` warning. */\n private _warnedMaxMessagesDeprecation = false;\n private _resizeObserver: ResizeObserver | null = null;\n private _mutationObserver: MutationObserver | null = null;\n private _boundResetHandler: (() => void) | null = null;\n /**\n * When true, _reRenderActivePath() only dispatches aparte-path-changed without\n * touching the DOM. Set via setFrameworkManagedDOM(true) when a framework\n * (e.g. Angular) owns the bubble elements.\n */\n private _frameworkManagedDOM = false;\n\n static get observedAttributes(): string[] {\n return ['scroll-threshold', 'max-rendered-bubbles', 'max-messages'];\n }\n\n constructor() {\n super();\n this._handleScroll = this._handleScroll.bind(this);\n }\n\n connectedCallback(): void {\n // Framework wrappers set `framework-managed` DECLARATIVELY so the flag is\n // known BEFORE _render() builds the DOM. Otherwise _render()'s child\n // relocation runs at connect — before the host's setFrameworkManagedDOM()\n // call — moving the framework's bubbles into an internal wrapper and\n // breaking its reconciliation (insertBefore NotFoundError on the next\n // append). See _setupFrameworkDOM().\n if (this.hasAttribute('framework-managed')) this._frameworkManagedDOM = true;\n this._render();\n this._setupEventListeners();\n this._setupObservers();\n this._boundResetHandler = () => this.clearAll();\n window.addEventListener('aparte-reset', this._boundResetHandler);\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n /**\n * A locale switch changes the reading direction, and `dir` was applied once at\n * render — so a chat already on screen never flipped to RTL until a reload.\n * Only OUR config: an instance-scoped change elsewhere must not touch us.\n */\n private _onConfigChange = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== resolveConfig(this)) return;\n this._applyDirection();\n };\n\n /** Mirror `locale.direction` onto the scroll container. */\n private _applyDirection(): void {\n const container = this.querySelector('.aparte-viewport-container');\n if (!container) return;\n const direction = resolveConfig(this).getLocale().direction;\n if (direction) container.setAttribute('dir', direction);\n else container.removeAttribute('dir');\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n if (this._boundResetHandler) {\n window.removeEventListener('aparte-reset', this._boundResetHandler);\n this._boundResetHandler = null;\n }\n this._cleanup();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'scroll-threshold':\n this._scrollThreshold = parseInt(newValue || '50', 10);\n break;\n case 'max-rendered-bubbles':\n this._maxRenderedBubbles = parseInt(newValue || '1000', 10);\n this._pruneRenderedBubbles();\n break;\n case 'max-messages':\n // Deprecated alias. It used to evict messages from the tree\n // (destructive, silent data loss); it now only caps rendered\n // bubbles in the DOM. Use `max-rendered-bubbles` instead.\n this._warnMaxMessagesDeprecated();\n this._maxRenderedBubbles = parseInt(newValue || '1000', 10);\n this._pruneRenderedBubbles();\n break;\n }\n }\n\n /**\n * Configure viewport with options\n */\n configure(config: AparteViewportConfig): void {\n if (config.scrollThreshold !== undefined) {\n this._scrollThreshold = config.scrollThreshold;\n }\n if (config.maxRenderedBubbles !== undefined) {\n this._maxRenderedBubbles = config.maxRenderedBubbles;\n this._pruneRenderedBubbles();\n }\n if (config.maxMessages !== undefined) {\n // Deprecated alias (see attributeChangedCallback).\n this._warnMaxMessagesDeprecated();\n this._maxRenderedBubbles = config.maxMessages;\n this._pruneRenderedBubbles();\n }\n if (config.layoutTransitionMs !== undefined) {\n this._layoutTransitionMs = config.layoutTransitionMs;\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Simple Content Streaming\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Append a token chunk to a message's content (simple text streaming)\n * @param messageId - Unique identifier for the message\n * @param chunk - Token chunk to append\n */\n appendToken(messageId: string, chunk: string): void {\n const message = this._getOrCreateMessage(messageId);\n\n // Append to simple content\n message.content = (message.content || '') + chunk;\n\n // Notify bubble\n this._notifyBubble(messageId, 'appendToken', chunk);\n this._autoScroll();\n this._scheduleSpacerUpdate();\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Segment-Aware Streaming\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Append content to a specific segment within a message\n * @param messageId - Message containing the segment\n * @param segmentId - Target segment ID\n * @param chunk - Content to append\n */\n appendToSegment(messageId: string, segmentId: string, chunk: string): void {\n const message = this._getOrCreateMessage(messageId);\n\n // Find or create segment\n if (!message.segments) {\n message.segments = [];\n }\n\n // REPLACE the segment, never mutate it in place. The bubble holds the very\n // same object — `addSegment` handed one object to the repo and to the bubble —\n // and it appends this chunk itself (see `_notifyBubble` below). Mutating here\n // made the two writes land on one object, so every chunk appeared twice, in\n // the model AND on screen (\"BonjourBonjour le le monde\"). Each view now owns\n // the value it advances.\n const index = message.segments.findIndex(s => s.id === segmentId);\n const segment = index === -1 ? undefined : message.segments[index];\n if (segment && 'content' in segment) {\n message.segments[index] = {\n ...segment,\n content: (segment as { content: string }).content + chunk,\n // Content arriving IS the segment's activity, so this is what\n // `endedAt` measures. Without it a thinking block's end would be\n // whenever someone happened to notice it had stopped — the end of\n // the turn, or the start of the next segment — and both of those\n // silently fold the waiting that followed into the duration.\n ...stampSegmentActivity(segment),\n } as AparteSegment;\n }\n\n // Dispatch segment update event\n this.dispatchEvent(new CustomEvent<AparteSegmentUpdateEventDetail>('aparte-segment-update', {\n bubbles: true,\n composed: true,\n detail: { messageId, segmentId, content: chunk, append: true }\n }));\n\n // Notify bubble\n this._notifyBubble(messageId, 'appendToSegment', chunk, segmentId);\n this._autoScroll();\n }\n\n /**\n * The active (head) message id — the target of `AparteClient`'s 1-argument\n * streaming convention (`addSegment(segment)`, `updateSegment(segmentId,\n * updates)`, …) which operates on \"the current message\". Lets a bare\n * `<aparte-chat-viewport>` be a valid `AparteClient` target, exactly like a\n * framework wrapper's host element.\n */\n private _activeMessageId(): string | null {\n // The head, UNLESS a different message is the one actually streaming.\n //\n // The 1-argument convention means \"operate on the message being streamed\",\n // and this resolved it as \"the head\" — but `appendMessage` always moves the\n // head (the repository advances it to any new child). So any message appended\n // mid-stream re-pointed the rest of the reply: measured with the real element,\n // segment two of message A landed on message B, and `updateSegment` for a\n // segment that genuinely lives on A became a silent no-op.\n //\n // `AparteChatHost` has had `_isOrphan` for exactly this, which is why the\n // framework wrappers were protected and the raw viewport — the documented\n // vanilla quick start — was not. Refusing, like the host does, rather than\n // routing: losing the tail is visible, writing it onto someone else's message\n // is not.\n const head = this._repo.headId;\n const streaming = this._streamingMessageId();\n if (streaming !== null && streaming !== head) return null;\n return head;\n }\n\n /** The id of the message currently streaming, if any. */\n private _streamingMessageId(): string | null {\n for (const message of this._repo.getMessages()) {\n if ((message as { isStreaming?: boolean }).isStreaming) return message.id;\n }\n return null;\n }\n\n /**\n * Add a new segment. Two calling conventions are accepted:\n * - `addSegment(segment)` — AparteClient's 1-arg \"operate on the current\n * (head) message\" convention (also what a wrapper host installs);\n * - `addSegment(messageId, segment)` — explicit standalone form.\n * The first argument's type disambiguates (string = messageId, object =\n * segment), so a raw viewport driven by `AparteClient` no longer drops text\n * (the args used to bind one position short, creating a phantom message).\n */\n addSegment(segment: AparteSegment): void;\n addSegment(messageId: string, segment: AparteSegment): void;\n addSegment(messageIdOrSegment: string | AparteSegment, maybeSegment?: AparteSegment): void {\n const messageId = typeof messageIdOrSegment === 'string' ? messageIdOrSegment : this._activeMessageId();\n const segment = typeof messageIdOrSegment === 'string' ? maybeSegment : messageIdOrSegment;\n if (!messageId || !segment) return;\n\n const message = this._getOrCreateMessage(messageId);\n if (!message.segments) {\n message.segments = [];\n }\n // Identity and start time land BEFORE anyone sees the object: the repo and\n // the bubble are handed the same segment, so a later stamp would leave one\n // of the two holding an unstamped copy. This is one of exactly two places\n // that writes those fields (`aparte-chat-host` is the other) — see\n // `utils/segments.ts` for why it is not the parser.\n const stamped = stampSegmentOnInsert(\n message.segments, segment, messageId,\n // THIS chat's defaults, not the page's: two chats on one page can be\n // configured differently, and the config seam is per instance.\n resolveConfig(this).getSegmentDefaults(segment.type),\n );\n message.segments.push(stamped);\n\n // Notify bubble to render the new segment\n this._notifyBubble(messageId, 'addSegment', stamped);\n this._autoScroll();\n }\n\n /**\n * Update a segment. `updateSegment(segmentId, updates)` (1-arg client\n * convention → current message) or `updateSegment(messageId, segmentId,\n * updates)` (explicit). Disambiguated by arity: the 3rd arg is absent and\n * the 2nd is the `updates` object in the 1-arg form.\n */\n updateSegment(segmentId: string, updates: Partial<AparteSegment>): void;\n updateSegment(messageId: string, segmentId: string, updates: Partial<AparteSegment>): void;\n updateSegment(a: string, b: string | Partial<AparteSegment>, c?: Partial<AparteSegment>): void {\n const clientForm = c === undefined && typeof b === 'object';\n const messageId = clientForm ? this._activeMessageId() : a;\n const segmentId = clientForm ? a : (b as string);\n const updates = clientForm ? (b as Partial<AparteSegment>) : (c as Partial<AparteSegment>);\n if (!messageId) return;\n\n const message = this._repo.getMessageById(messageId);\n if (!message?.segments) return;\n\n const segmentIndex = message.segments.findIndex(s => s.id === segmentId);\n if (segmentIndex !== -1) {\n const current = message.segments[segmentIndex]!;\n // An update that settles the segment carries its `endedAt`. Stamped\n // here rather than at each call site, so `completeSegment`, a tool\n // resolution and an app's own `updateSegment` all measure alike.\n const stamped = stampSegmentOnUpdate(current, updates);\n message.segments[segmentIndex] = mergeSegmentUpdate(current, stamped);\n\n this._notifyBubble(messageId, 'updateSegment', { segmentId, updates: stamped });\n }\n }\n\n /**\n * Remove a segment. `removeSegment(segmentId)` (1-arg client convention →\n * current message) or `removeSegment(messageId, segmentId)` (explicit).\n */\n removeSegment(segmentId: string): void;\n removeSegment(messageId: string, segmentId: string): void;\n removeSegment(a: string, b?: string): void {\n const clientForm = b === undefined;\n const messageId = clientForm ? this._activeMessageId() : a;\n const segmentId = clientForm ? a : b;\n if (!messageId || !segmentId) return;\n\n const message = this._repo.getMessageById(messageId);\n if (message?.segments) {\n const idx = message.segments.findIndex(s => s.id === segmentId);\n if (idx !== -1) {\n message.segments.splice(idx, 1);\n // `index` is a position, so a removal has to close the gap it left.\n renumberSegments(message.segments);\n }\n }\n this._notifyBubble(messageId, 'removeSegment', segmentId);\n }\n\n /**\n * Start a new streaming segment (e.g., thinking or code block)\n * Creates the segment and marks it as streaming\n */\n startSegment(messageId: string, segment: AparteSegment): void {\n const streamingSegment = { ...segment, isStreaming: true };\n this.addSegment(messageId, streamingSegment);\n }\n\n /**\n * Complete a streaming segment\n */\n completeSegment(messageId: string, segmentId: string): void {\n this.updateSegment(messageId, segmentId, { isStreaming: false });\n }\n\n /**\n * Persist token usage on a message and propagate to the live bubble, which is\n * what allows the info (\"i\") action to render — provided the app declared it\n * with `aparteGlobalConfig.setBubbleActions({ info: true })`; it is off by default,\n * since the popover it opens belongs to the app.\n */\n setUsage(messageId: string, usage: AparteUsage): void {\n const message = this._repo.getMessageById(messageId);\n if (message) message.usage = usage;\n this._notifyBubble(messageId, 'setUsage', usage);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Message Management\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Mark a message as finished streaming\n */\n completeMessage(messageId: string): void {\n const message = this._repo.getMessageById(messageId);\n if (message) {\n message.isStreaming = false;\n message.status = 'completed';\n\n // The message's end IS its segments' end: nothing in the stream says a\n // thinking block is over. Routed through `updateSegment` rather than\n // written onto the objects, so the bubble is told as well — a silent\n // mutation stamped the model and left the renderer thinking it was still\n // streaming, which is exactly what a browser run showed.\n this._settleSegments(messageId, message);\n\n this._notifyBubble(messageId, 'complete', { status: 'completed' });\n this._recalculateSpacer();\n }\n }\n\n /**\n * Close a finished message's still-open segments, one `updateSegment` each.\n *\n * Deliberately NOT a loop that writes `isStreaming` onto the objects: that path\n * stamps the model and tells the bubble nothing, so a renderer never learns its\n * segment settled — no `endedAt` in the rendered label, and no final Markdown\n * flush either. Going through `updateSegment` reuses the one path that does\n * both.\n */\n private _settleSegments(messageId: string, message: AparteMessage): void {\n if (!message.segments) return;\n for (const id of openSegmentIds(message.segments)) {\n this.updateSegment(messageId, id, { isStreaming: false });\n }\n }\n\n /**\n * Atomic update for a message by ID\n * Supports updating content, status, segments, and other metadata\n */\n updateMessage(messageId: string, updates: Partial<AparteMessage>): void {\n const message = this._repo.getMessageById(messageId);\n if (!message) return;\n\n // Apply updates to internal state\n Object.assign(message, updates);\n\n // Map AparteStatus to isStreaming for legacy bubble support\n if (updates.status) {\n message.isStreaming = updates.status === 'streaming' || updates.status === 'pending';\n // …and close the segments, because THIS is the path a completed turn\n // takes: both agent loops report the end with\n // `updateMessage({ status: 'completed' })`, and `completeMessage()` is\n // called by nobody. Without this a thinking segment kept `isStreaming`\n // unset forever and never recorded an `endedAt` — the duration only\n // worked for tool calls, which settle by their own status.\n if (isTerminalStatus(updates.status)) this._settleSegments(messageId, message);\n }\n\n // Notify bubble\n this._notifyBubble(messageId, 'update', updates);\n this._autoScroll();\n }\n\n /**\n * Add a complete message to the message registry.\n *\n * @remarks\n * **Framework-managed DOM only.** Records the message in the tree but does NOT\n * paint a bubble on its own (a framework wrapper reconciles the DOM from the\n * list). For standalone / vanilla usage call {@link appendMessage} instead,\n * which both records the message and creates its bubble element.\n */\n addMessage(message: AparteMessage): void {\n // Adopted, not stamped: this writes straight to the repository, so it is the\n // caller handing over a message they already hold rather than a turn starting.\n this._repo.addOrUpdateMessage(this._repo.headId, adoptMessageSegments({ ...message }));\n this._pruneRenderedBubbles();\n this._autoScroll();\n }\n\n /**\n * Append a new message and create its bubble in the DOM.\n * Implements the same contract as the Angular wrapper's appendMessage(),\n * making aparte-chat-viewport a fully standalone target for aparte-client.\n * When `_frameworkManagedDOM` is true, only the internal repo is updated —\n * the framework owns the DOM and will create the bubble element itself.\n */\n appendMessage(message: AparteMessage, options?: { historical?: boolean }): void {\n /*\n * A message may arrive with its segments already populated, and the two reasons\n * are not the same act: an app injecting a prefix or the client's own error\n * fallback is producing something NOW, while `setMessages` is handing back\n * something that happened. Both used to take the live path, so reloading a\n * three-week-old conversation stamped every one of its segments with `Date.now()`.\n *\n * Provenance is a parameter and not a guess. \"Arrived with its segments\" cannot\n * mean \"historical\" — `AparteClient` appends a message with a ready-made error\n * segment live, and a consumer streaming into a seeded segment is doing the same\n * thing. Defaulting to live keeps every existing caller's behaviour.\n *\n * Either way the segments go through a seam and into a NEW array, so `index`\n * follows the position and the caller's array is not retained.\n */\n const stored: AparteMessage = options?.historical\n ? adoptMessageSegments(message)\n : message.segments?.length\n ? { ...message, segments: message.segments.reduce<AparteSegment[]>(\n (acc, segment) => {\n acc.push(stampSegmentOnInsert(\n acc, segment, message.id,\n resolveConfig(this).getSegmentDefaults(segment.type),\n ));\n return acc;\n },\n [],\n ) }\n : { ...message };\n this._repo.addOrUpdateMessage(this._repo.headId, stored);\n if (!this._frameworkManagedDOM) {\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (wrapper) {\n const bubble = document.createElement('aparte-chat-bubble') as HTMLElement;\n bubble.setAttribute('message-id', message.id);\n bubble.setAttribute('role', message.role);\n if (message.timestamp) bubble.setAttribute('timestamp', String(message.timestamp));\n if (message.content) bubble.setAttribute('content', message.content);\n // Also true for an empty assistant message with no status: an\n // imperative \"the reply is coming\" shell, which otherwise rendered\n // as a finished answer (action bar and all) before a single token.\n if (isAwaitingReply(message)) {\n bubble.setAttribute('streaming', '');\n }\n // Insert before spacer so spacer stays last\n if (this._bottomSpacer && this._bottomSpacer.parentNode === wrapper) {\n wrapper.insertBefore(bubble, this._bottomSpacer);\n } else {\n wrapper.appendChild(bubble);\n }\n // Attributes alone can't carry segments / attachments / usage —\n // push them through the same helper the full render path uses,\n // or an imperatively appended message renders text-only.\n //\n // `stored`, not `message`: the bubble has to see the STAMPED segments\n // the repository holds. Handed the caller's object it rendered ones\n // with no `index` or `startedAt`, so the same segment was stamped in\n // the model and bare on screen — and an app reading them back off the\n // bubble got the bare ones.\n populateBubbleFromMessage(bubble as unknown as SyncableBubble, stored);\n }\n }\n this._pruneRenderedBubbles();\n this._recalculateSpacer();\n // User sending always anchors to bottom regardless of scroll position.\n if (message.role === 'user') {\n this._isAutoScrollEnabled = true;\n // Smooth scroll for user-initiated sends. Streaming auto-scroll stays\n // instant (via _autoScroll) so it can keep up with rapid token bursts.\n requestAnimationFrame(() => this._smoothScrollToBottom());\n } else {\n this._autoScroll();\n }\n }\n\n /**\n * Update the last message content, optionally appending.\n * Implements the same contract as the Angular wrapper's updateLastMessage(),'\n * making aparte-chat-viewport a fully standalone streaming target for aparte-client.\n */\n updateLastMessage(content: string, options?: { append?: boolean }): void {\n const lastId = this._repo.headId;\n if (!lastId) return;\n if (options?.append) {\n this.appendToken(lastId, content);\n } else {\n const message = this._repo.getMessageById(lastId);\n if (message) message.content = content;\n this._notifyBubble(lastId, 'appendToken', content);\n }\n }\n\n /**\n * Add a new sibling branch to an assistant message (retry flow).\n * Creates a new empty assistant message as a sibling of `messageId`\n * under the same parent, switches the active branch to it, and\n * re-renders the active path.\n * @returns The index of the new branch in the siblings array, or 0 on failure.\n */\n addBranch(messageId: string): number {\n const meta = this._repo.getMessage(messageId);\n if (!meta) return 0;\n\n const newMsg: AparteMessage = {\n id: uuid(),\n role: 'assistant',\n content: '',\n status: 'pending',\n timestamp: Date.now(),\n };\n this._repo.addOrUpdateMessage(meta.parentId, newMsg);\n this._repo.switchToBranch(newMsg.id);\n this._reRenderActivePath();\n\n const siblings = this._repo.getBranches(newMsg.id);\n return siblings.indexOf(newMsg.id);\n }\n\n /**\n * Add a new message relative to `existingId`, switch to it, and re-render.\n *\n * Role-aware semantics:\n * - existingId is an **assistant** message → create a sibling (same parent),\n * so the active path replaces the old response with the new one.\n * - existingId is a **user** message → create a child of that message,\n * so the user message stays on the active path and the new response follows it.\n *\n * Returns the new message's ID, or null if `existingId` is not found.\n */\n addSiblingOf(existingId: string, newMessage: AparteMessage): string | null {\n const meta = this._repo.getMessage(existingId);\n if (!meta) return null;\n\n // User messages: new response is a child (keep user on active path).\n // Assistant messages: new response is a sibling (replace old response).\n const parentId = meta.message.role === 'user'\n ? existingId\n : meta.parentId;\n this._repo.addOrUpdateMessage(parentId, { ...newMessage });\n this._repo.switchToBranch(newMessage.id);\n this._reRenderActivePath();\n return newMessage.id;\n }\n\n /**\n * Navigate to the previous or next sibling branch of a message.\n * Triggers a full re-render of the active path.\n */\n navigateBranch(messageId: string, direction: 'prev' | 'next'): void {\n const siblings = this._repo.getBranches(messageId);\n const currentIdx = siblings.indexOf(messageId);\n if (currentIdx === -1) return;\n\n const targetIdx = direction === 'prev' ? currentIdx - 1 : currentIdx + 1;\n if (targetIdx < 0 || targetIdx >= siblings.length) return;\n\n // Branch navigation is a deliberate user action, so it must not yank a user\n // who is reading mid-transcript: auto-scroll goes off and neither the spacer\n // recalculation nor the MutationObserver callback will scroll them away.\n //\n // But if they were already AT the bottom, staying there IS the expected\n // behaviour — and switching auto-follow off there is what left the\n // scroll-to-bottom button offering to scroll nowhere (bonaparte, React). It\n // also protects the swap itself: a rebuild's height flickers (measured on\n // React: 1730 → 1934 → 1730px as the new bubble renders and settles), so a\n // reader pinned to the bottom would drift up by whatever the flicker was.\n this._isAutoScrollEnabled = this._isAtBottom();\n this._updateScrollButton();\n\n this._repo.switchToBranch(siblings[targetIdx]!);\n this._reRenderActivePath();\n }\n\n /**\n * Remove ALL responses to a user message (every child branch) and set head\n * back to `userMessageId`. Cleaner than `truncateFrom` for edit flows: it\n * discards stale sibling branches so the regenerated response starts alone.\n */\n truncateResponsesAfter(userMessageId: string): void {\n const prevMessages = this._repo.getMessages();\n this._repo.clearChildren(userMessageId);\n\n // In framework-managed mode the host (Angular @for, React, etc.) owns\n // the bubble DOM. Removing nodes from under it triggers\n // `NotFoundError: Failed to execute 'insertBefore'` on the next change\n // detection cycle because the framework's view tree no longer matches\n // the actual DOM. Skip the manual cleanup and let the framework\n // reconcile when the consumer updates its message array.\n if (!this._frameworkManagedDOM) {\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (wrapper) {\n const startIdx = prevMessages.findIndex(m => m.id === userMessageId);\n const toRemove = startIdx >= 0 ? prevMessages.slice(startIdx + 1) : [];\n for (const m of toRemove) {\n wrapper.querySelector(`aparte-chat-bubble[message-id=\"${cssEscape(m.id)}\"]`)?.remove();\n }\n }\n }\n }\n\n /**\n * Remove all messages from `messageId` onwards (inclusive) from state and DOM.\n * Used by edit to truncate history before re-generating.\n */\n truncateFrom(messageId: string): void {\n const allMsgs = this._repo.getMessages();\n const startIdx = allMsgs.findIndex(m => m.id === messageId);\n if (startIdx === -1) return;\n\n const toRemove = allMsgs.slice(startIdx).map(m => m.id);\n this._repo.resetHead(messageId);\n\n // See the note in truncateResponsesAfter: skip DOM ops when a framework\n // owns the bubble elements.\n if (!this._frameworkManagedDOM) {\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n for (const id of toRemove) {\n wrapper?.querySelector(`aparte-chat-bubble[message-id=\"${cssEscape(id)}\"]`)?.remove();\n }\n }\n }\n\n /**\n * Get a message by ID\n */\n getMessage(messageId: string): AparteMessage | undefined {\n return this._repo.getMessageById(messageId);\n }\n\n /**\n * The messages on the currently ACTIVE path, root → head — not the whole tree.\n * A message that was retried contributes only the branch currently selected;\n * `exportTree()` is what returns every sibling.\n */\n getMessages(): AparteMessage[] {\n return this._repo.getMessages();\n }\n\n /**\n * Export the full conversation tree (all branches, not just the active path).\n * The returned snapshot can be persisted and restored via `importTree()`.\n */\n exportTree(): ExportedMessageRepository {\n return this._repo.export();\n }\n\n /**\n * Import a previously-exported tree snapshot, restoring the full branch\n * topology and the active head. Replaces any existing repo content.\n *\n * Always calls `_reRenderActivePath()`:\n * - In native DOM mode: rebuilds bubble elements.\n * - In framework-managed mode: skips DOM manipulation but dispatches\n * `aparte-path-changed` with sibling metadata so the wrapper can update\n * branch arrows on already-rendered bubbles.\n */\n importTree(tree: ExportedMessageRepository): void {\n // Not `clearAll()`: an import re-populates from a snapshot that may hold the\n // very attachment objects currently in the repo — which is exactly what a\n // conversation load does. See the note in `clearAll`.\n this.clearAll({ revokeAttachments: false });\n // A snapshot is history by definition, and this is the path that used to write\n // it to the repository RAW — so `messageId`/`index` stayed whatever the storage\n // held, and a tree saved before those fields existed came back without them.\n // It also runs AFTER `setMessages` on a conversation load, so whatever that\n // stamped was being replaced by this anyway: two paths, one of them silent.\n this._repo.import({\n ...tree,\n messages: tree.messages.map((entry) => ({\n ...entry,\n message: adoptMessageSegments(entry.message),\n })),\n });\n this._reRenderActivePath();\n }\n\n /**\n * Clear all messages and remove all bubble elements from the DOM.\n * Also dispatches a aparte-reset-done event.\n *\n * In framework-managed mode the DOM is owned by the host framework\n * (Angular @for, React, etc.) and we must not clear `innerHTML` — doing\n * so desynchronises the framework's view tree from the live DOM and the\n * next change-detection pass throws `NotFoundError` on insertBefore.\n */\n clearAll(options?: { revokeAttachments?: boolean }): void {\n /*\n * Release the attachments' object URLs before dropping the messages: after\n * `_repo.clear()` there is no way left to reach them, and nothing else\n * revoked them — so every `File` a session had sent stayed reachable for\n * the life of the page.\n *\n * UNLESS the caller is about to put the same messages back. Two callers do:\n * `setMessages` and `importTree`, and `ConversationController._load` runs\n * BOTH in sequence over one conversation. `export()` stores live `node.current`\n * references, so `conv.messages` and `conv.tree` share the very same\n * attachment objects — meaning the second clear revoked the object URLs of\n * the conversation being opened. Every image and file chip was dead on load,\n * and re-opening revoked twice.\n *\n * A reset (`aparte-reset`, the public `clearAll()`) still revokes: there the\n * messages really are gone.\n */\n if (options?.revokeAttachments !== false) {\n for (const message of this._repo.getMessages()) {\n revokeAttachmentUrls(message.attachments);\n }\n }\n this._repo.clear();\n if (!this._frameworkManagedDOM) {\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (wrapper) {\n // Remove bubbles individually so the spacer div is preserved.\n Array.from(wrapper.querySelectorAll('aparte-chat-bubble')).forEach(b => b.remove());\n }\n }\n // Reset spacer and scroll button regardless of mode\n this._setSpacerHeight(0);\n this._isAutoScrollEnabled = true;\n this._updateScrollButton();\n this.dispatchEvent(new CustomEvent('aparte-reset-done', { bubbles: true, composed: true }));\n }\n\n /**\n * Clear all messages\n * @deprecated Use clearAll() to also remove DOM bubbles\n */\n clearMessages(): void {\n this._repo.clear();\n }\n\n /**\n * Replace the entire message list in one shot. Used when switching\n * conversations: clears existing repo + DOM, then appends each message.\n *\n * In framework-managed mode the framework re-renders the bubble DOM\n * itself; we only update the internal repo (used by aparte-client to\n * build chat history).\n */\n setMessages(messages: AparteMessage[]): void {\n // Same reason as `importTree`: the incoming messages may BE the outgoing\n // ones, and a conversation the user can switch back to still holds them.\n this.clearAll({ revokeAttachments: false });\n for (const m of messages) {\n // Historical by definition: this replaces the transcript with a list the\n // caller already had. Nothing here is starting now.\n this.appendMessage(m, { historical: true });\n }\n }\n\n /**\n * Scroll to bottom of viewport\n */\n scrollToBottom(): void {\n this._scrollToBottom();\n }\n\n /**\n * Reset the bottom spacer to 0 height immediately and freeze it for\n * 350 ms so the host-app layout transition (e.g. flex: 0→1 animation)\n * does not trigger a premature recalculation with mid-animation geometry.\n * Call before a full messages swap.\n */\n resetSpacer(): void {\n this._setSpacerHeight(0);\n // Freeze spacer recalculation for the duration of any host layout\n // transition (configured via `layoutTransitionMs`). Without this,\n // ResizeObserver fires on every animation frame while the container\n // is still growing, producing incorrect spacer values.\n if (this._layoutTransitionMs > 0) {\n this._spacerFrozenUntil = Date.now() + this._layoutTransitionMs;\n }\n }\n\n /**\n * Enable or disable auto-scroll\n */\n setAutoScroll(enabled: boolean): void {\n this._isAutoScrollEnabled = enabled;\n }\n\n /**\n * Signal that a framework (e.g. Angular) manages the bubble DOM.\n * When true, branch navigation dispatches `aparte-path-changed` without\n * clearing/rebuilding the messages wrapper — the framework re-renders instead.\n */\n setFrameworkManagedDOM(managed: boolean): void {\n this._frameworkManagedDOM = managed;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private _getOrCreateMessage(messageId: string): AparteMessage {\n const existing = this._repo.getMessageById(messageId);\n if (existing) return existing;\n\n const message: AparteMessage = {\n id: messageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now(),\n isStreaming: true,\n status: 'streaming'\n };\n this._repo.addOrUpdateMessage(this._repo.headId, message);\n return message;\n }\n\n private _notifyBubble(messageId: string, action: string, payload?: unknown, segmentId?: string): void {\n // Find the bubble element — the native `<aparte-chat-bubble>` OR a custom\n // element opting into live streaming via `data-aparte-bubble` (so a raw-core\n // consumer can replace the bubble tag and still receive token/segment\n // pushes, not just a CSS restyle).\n const bubble = this.querySelector(\n `aparte-chat-bubble[message-id=\"${cssEscape(messageId)}\"], [data-aparte-bubble][message-id=\"${cssEscape(messageId)}\"]`,\n ) as HTMLElement & {\n appendToken?: (chunk: string) => void;\n appendToSegment?: (segmentId: string, chunk: string) => void;\n addSegment?: (segment: AparteSegment) => void;\n updateSegment?: (segmentId: string, updates: Partial<AparteSegment>) => void;\n removeSegment?: (segmentId: string) => void;\n setUsage?: (usage: AparteUsage) => void;\n updateMessage?: (updates: Partial<AparteMessage>) => void;\n };\n\n if (!bubble) return;\n\n switch (action) {\n case 'appendToken':\n bubble.appendToken?.(payload as string);\n break;\n case 'appendToSegment':\n bubble.appendToSegment?.(segmentId!, payload as string);\n break;\n case 'addSegment':\n bubble.addSegment?.(payload as AparteSegment);\n break;\n case 'updateSegment': {\n const { segmentId: sid, updates: segUpdates } = payload as { segmentId: string; updates: Partial<AparteSegment> };\n bubble.updateSegment?.(sid, segUpdates);\n break;\n }\n case 'removeSegment':\n bubble.removeSegment?.(payload as string);\n break;\n case 'setUsage':\n bubble.setUsage?.(payload as AparteUsage);\n break;\n case 'update': {\n // Atomic update: forward it when it carries anything the bubble\n // renders. `content` and `attachments` used to be filtered out\n // here, so an edit (which sends `{ content }`) updated the repo —\n // and therefore the history sent to the model — while the bubble\n // kept displaying the old text.\n const updates = payload as Record<string, unknown>;\n const renderable = ['status', 'segments', 'content', 'attachments', 'usage'];\n if (renderable.some((key) => key in updates)) {\n bubble.updateMessage?.(payload as Partial<AparteMessage>);\n }\n break;\n }\n case 'complete':\n bubble.updateMessage?.(payload as Partial<AparteMessage>);\n break;\n }\n }\n\n /**\n * Re-render the active path: clears the messages wrapper and rebuilds bubbles\n * for every message on the current active branch path (root → head).\n * Calls `setSiblings(count, index)` on each bubble that has siblings, and\n * dispatches `aparte-path-changed` so Angular wrapper can sync its signal.\n *\n * When `_frameworkManagedDOM` is true (set via setFrameworkManagedDOM), the DOM\n * manipulation is skipped — only `aparte-path-changed` is dispatched so the\n * framework can re-render from updated signal state.\n */\n private _reRenderActivePath(): void {\n const activeMessages = this._repo.getMessages();\n\n // Compute sibling metadata once and reuse — keeps the event payload\n // identical between framework-managed and default DOM modes.\n const siblingsInfo: AparteSiblingInfo[] = activeMessages.map(m => {\n const sibs = this._repo.getBranches(m.id);\n return { id: m.id, count: sibs.length, index: sibs.indexOf(m.id) };\n });\n\n if (this._frameworkManagedDOM) {\n this._dispatchPathChanged(activeMessages, siblingsInfo);\n return;\n }\n\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (!wrapper) return;\n wrapper.innerHTML = '';\n\n // Only materialise the last N messages of the active path (DOM render cap).\n // The repository keeps the full path; this is a perf ceiling, not eviction.\n const startIdx = Math.max(0, activeMessages.length - this._maxRenderedBubbles);\n for (let i = startIdx; i < activeMessages.length; i++) {\n const message = activeMessages[i]!;\n const sibInfo = siblingsInfo[i];\n\n const bubble = document.createElement('aparte-chat-bubble');\n bubble.setAttribute('message-id', message.id);\n bubble.setAttribute('role', message.role);\n if (message.timestamp) bubble.setAttribute('timestamp', String(message.timestamp));\n if (isAwaitingReply(message)) {\n bubble.setAttribute('streaming', '');\n }\n wrapper.appendChild(bubble);\n\n // Reconcile content / segments / attachments / sibling-picker via\n // the shared helper — same code path the framework wrappers use,\n // so the contract stays in lockstep.\n populateBubbleFromMessage(bubble as unknown as SyncableBubble, message, sibInfo);\n }\n\n this._dispatchPathChanged(activeMessages, siblingsInfo);\n this._recalculateSpacer();\n\n // No post-swap re-measure of the auto-scroll INTENT here, deliberately: a\n // rebuild's height flickers, and a one-shot measurement that lands mid-flicker\n // can only get it wrong (it would disarm auto-follow for a reader who is\n // pinned to the bottom). The intent is decided once, in `navigateBranch`, from\n // the position the user was actually in; the button re-derives itself from\n // geometry on every scroll and on every post-mutation frame.\n }\n\n private _dispatchPathChanged(messages: AparteMessage[], siblings: AparteSiblingInfo[]): void {\n const detail: ApartePathChangedEventDetail = { messages, siblings };\n this.dispatchEvent(new CustomEvent<ApartePathChangedEventDetail>('aparte-path-changed', {\n bubbles: true,\n composed: true,\n detail,\n }));\n }\n\n private _autoScroll(): void {\n if (this._isAutoScrollEnabled) {\n if (this._smoothScrollOnce) {\n this._smoothScrollOnce = false;\n requestAnimationFrame(() => this._smoothScrollToBottom());\n } else {\n requestAnimationFrame(() => this._scrollToBottom());\n }\n }\n this._pruneRenderedBubbles();\n }\n\n /**\n * Request that the next auto-scroll triggered by a DOM mutation uses\n * smooth behaviour instead of instant. Call this just before adding a\n * user message bubble so the viewport animates down rather than jumping.\n * Resets automatically after the first auto-scroll fires.\n */\n requestSmoothScroll(): void {\n this._smoothScrollOnce = true;\n }\n\n private _render(): void {\n // Framework-managed: the framework owns the bubble children directly.\n // Do NOT build the internal container/wrapper or relocate children.\n if (this._frameworkManagedDOM) {\n this._setupFrameworkDOM();\n return;\n }\n // Light DOM rendering\n // Preserving existing children in render allows framework composition\n if (!this.querySelector('.aparte-viewport-container')) {\n const container = document.createElement('div');\n container.className = 'aparte-viewport-container';\n\n // Set direction based on current locale\n const locale = resolveConfig(this).getLocale();\n if (locale.direction) {\n container.setAttribute('dir', locale.direction);\n }\n\n container.setAttribute('role', 'log');\n container.setAttribute('aria-live', 'polite');\n container.setAttribute('aria-atomic', 'false');\n container.setAttribute('aria-relevant', 'additions');\n\n const wrapper = document.createElement('div');\n wrapper.className = 'aparte-messages-wrapper';\n\n // Move existing children (bubbles) into wrapper\n while (this.firstChild) {\n wrapper.appendChild(this.firstChild);\n }\n\n // Bottom spacer — always last in wrapper, height driven by _recalculateSpacer()\n this._bottomSpacer = document.createElement('div');\n this._bottomSpacer.className = 'aparte-bottom-spacer';\n this._bottomSpacer.setAttribute('aria-hidden', 'true');\n wrapper.appendChild(this._bottomSpacer);\n\n container.appendChild(wrapper);\n this.appendChild(container);\n\n this._container = container;\n\n // Scroll-to-bottom button — absolutely positioned over the viewport\n this._scrollBtn = document.createElement('button');\n this._scrollBtn.className = 'aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden';\n this._scrollBtn.setAttribute('type', 'button');\n this._scrollBtn.setAttribute('aria-label', 'Scroll to bottom');\n const scrollIcon = resolveConfig(this).getIcon('scrollDown');\n this._scrollBtn.innerHTML = scrollIcon;\n this.appendChild(this._scrollBtn);\n } else {\n this._container = this.querySelector('.aparte-viewport-container');\n this._scrollBtn = this.querySelector('.aparte-scroll-btn');\n this._bottomSpacer = this.querySelector('.aparte-bottom-spacer');\n }\n }\n\n /**\n * DOM setup for framework-managed mode. The framework (React/Vue/Svelte/\n * Angular) renders the bubble elements as DIRECT children of the host, so we\n * must NOT relocate them into an internal wrapper — that desyncs the\n * framework's virtual DOM from the real DOM and throws NotFoundError on the\n * next append. Instead the HOST itself is the scroll surface, the spacer is\n * additive `padding-bottom` (no element), and the scroll button is a\n * `position: sticky` TRAILING foreign child (kept last by the framework\n * MutationObserver). A present foreign node is still a valid `insertBefore`\n * reference for the framework — the crash came from a RELOCATED node, not a\n * foreign one.\n */\n private _setupFrameworkDOM(): void {\n this._container = this;\n this._bottomSpacer = null;\n if (this.classList.contains('aparte-viewport--framework')) {\n this._scrollBtn = this.querySelector(':scope > .aparte-scroll-btn') as HTMLButtonElement | null;\n return; // already set up (re-entrant _render)\n }\n this.classList.add('aparte-viewport--framework');\n\n const scrollBtn = document.createElement('button');\n scrollBtn.className = 'aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden';\n scrollBtn.setAttribute('type', 'button');\n scrollBtn.setAttribute('aria-label', 'Scroll to bottom');\n const scrollIcon = resolveConfig(this).getIcon('scrollDown');\n scrollBtn.innerHTML = scrollIcon;\n this.appendChild(scrollBtn);\n this._scrollBtn = scrollBtn;\n }\n\n /**\n * Keep the sticky scroll button as the last child in framework-managed mode.\n * The framework usually inserts bubbles before its own trailing nodes (so the\n * button stays last), but a plain `appendChild` at the very end (e.g. some\n * Angular @for paths) can land a bubble after it — move it back. Idempotent:\n * a no-op when already last, so it never loops the MutationObserver.\n */\n private _keepScrollButtonLast(): void {\n if (!this._scrollBtn) return;\n if (this.lastElementChild !== this._scrollBtn) {\n this.appendChild(this._scrollBtn);\n }\n }\n\n /** Current spacer height — a padding value (framework) or the element's height (core). */\n private _getSpacerHeight(): number {\n if (this._frameworkManagedDOM) return this._fwSpacerHeight;\n return this._bottomSpacer?.offsetHeight ?? 0;\n }\n\n /** Set the spacer — host padding (framework, additive to base padding) or element height (core). */\n private _setSpacerHeight(px: number): void {\n if (this._frameworkManagedDOM) {\n this._fwSpacerHeight = px;\n this.style.setProperty('--aparte-fw-spacer', `${px}px`);\n } else if (this._bottomSpacer) {\n this._bottomSpacer.style.height = `${px}px`;\n }\n }\n\n // Bound fields, not inline arrows: a custom element is re-connected every\n // time it is MOVED in the DOM (a portal, a dialog, a framework re-parenting),\n // so `_setupEventListeners` runs again each time. An inline arrow can never\n // be handed to `removeEventListener`, so it just accumulates — one branch\n // click then ran N handlers, N active-path re-renders and N storage writes\n // through the conversation controller. The window listeners next to these\n // were always removed properly; these two, attached to `this`, were not.\n private readonly _onScrollBtnClick = (): void => {\n this._isAutoScrollEnabled = true;\n this._smoothScrollToBottom();\n this._updateScrollButton();\n };\n\n private readonly _onBranchNavigate = (e: Event): void => {\n const evt = e as CustomEvent<{ messageId: string; direction: 'prev' | 'next' }>;\n evt.stopPropagation();\n this.navigateBranch(evt.detail.messageId, evt.detail.direction);\n };\n\n private _setupEventListeners(): void {\n this._container?.addEventListener('scroll', this._handleScroll, { passive: true });\n this._scrollBtn?.addEventListener('click', this._onScrollBtnClick);\n this.addEventListener('aparte-branch-navigate', this._onBranchNavigate);\n }\n\n private _setupObservers(): void {\n this._resizeObserver = new ResizeObserver(() => {\n if (this._isAutoScrollEnabled) {\n this._scrollToBottom();\n }\n this._recalculateSpacer();\n /*\n * And the button, for the same reason the spacer is here.\n *\n * \"Is anything below the fold\" is a pure function of the geometry this\n * observer exists to watch, and only the MUTATION path re-derived it\n * (`_scheduleSpacerUpdate`, whose comment already says the fold may have\n * moved). A resize that changes nothing in the DOM therefore left the\n * button showing whatever the last mutation happened to measure.\n *\n * That gap has a name in this file already: a branch swap rebuilds the\n * transcript and React's height FLICKERS through it — 1730 → 1934 → 1730,\n * measured, see `navigateBranch`. The settle from 1934 back to 1730 is a\n * resize, not a mutation, so a button evaluated at 1934 stayed wrong. CI\n * caught it on react-webkit holding \"visible\" across 43 polls, five seconds\n * after a swap that ended at the bottom.\n *\n * Cheap enough to run unconditionally: one geometry read and a\n * `classList.toggle`.\n */\n this._updateScrollButton();\n });\n\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n\n if (this._container) {\n // Fires on window/viewport resize and when the composer grows.\n // NOTE: we intentionally do NOT observe .aparte-messages-wrapper here.\n // The wrapper contains the spacer div — observing it would create a\n // feedback loop: spacer changes → wrapper resizes → ResizeObserver →\n // _recalculateSpacer → spacer changes → … → height grows unbounded.\n // Streaming content growth is handled by direct _scheduleSpacerUpdate()\n // calls from appendToken(). New bubbles are handled by MutationObserver.\n // Framework-managed: _container IS the host, whose `padding-bottom`\n // carries the spacer — observe the BORDER box (fixed host size) so a\n // spacer/padding change does NOT re-trigger _recalculateSpacer and\n // loop. Core mode observes the container (no dynamic padding).\n if (this._frameworkManagedDOM) {\n this._resizeObserver.observe(this._container, { box: 'border-box' });\n } else {\n this._resizeObserver.observe(this._container);\n }\n }\n\n this._mutationObserver = new MutationObserver(() => {\n // Keep the sticky scroll button trailing after framework appends.\n if (this._frameworkManagedDOM) this._keepScrollButtonLast();\n // The gate is tested HERE, when the frame is queued, and moving it\n // inside the callback is not the improvement it looks like.\n //\n // Queue-time looks like a race — the user could scroll up before the\n // frame runs and be dragged back. Testing it at run-time instead was\n // tried and reverted: a branch swap replaces bubbles, the resulting\n // scroll event makes `_isAtBottom()` briefly false, and the deferred\n // check then refuses to re-anchor, leaving a scroll-to-bottom button on\n // a transcript that IS at the bottom (caught by\n // `bubble-actions.spec.ts` on WebKit, 3 runs out of 3).\n //\n // So queue-time is deliberate: it captures the user's intent BEFORE the\n // DOM churn can confuse the \"am I at the bottom?\" heuristic. Reading it\n // correctly in both cases needs a way to tell our own programmatic\n // scroll from a real gesture — see the note in `_handleScroll`.\n if (this._isAutoScrollEnabled) {\n requestAnimationFrame(() => this._scrollToBottom());\n }\n // Recalculate spacer when DOM mutates (new bubble added, Angular re-render).\n this._scheduleSpacerUpdate();\n });\n\n // Framework-managed: bubbles are direct children of the host (no wrapper).\n const observeTarget = this._frameworkManagedDOM ? this : wrapper;\n if (observeTarget) {\n this._mutationObserver.observe(observeTarget, {\n childList: true,\n subtree: true\n });\n }\n }\n\n /** Is the scroll surface within `_scrollThreshold` of its bottom, right now? */\n private _isAtBottom(): boolean {\n if (!this._container) return true;\n const { scrollTop, scrollHeight, clientHeight } = this._container;\n return scrollHeight - scrollTop - clientHeight <= this._scrollThreshold;\n }\n\n private _handleScroll(): void {\n if (!this._container) return;\n /*\n * A scroll is the reader's intent — but only when the reader caused it.\n *\n * This used to assign `_isAutoScrollEnabled = _isAtBottom()` on every event, and\n * `_isAtBottom()` answers \"no\" for two completely different reasons: the reader\n * moved up, or the CONTENT GREW UNDER THEM. The second one disarmed auto-follow\n * exactly when it was most needed. A branch swap rebuilds the transcript, its\n * height settles in stages, one of those stages fires a scroll event while the\n * distance is briefly large — and the follow that was supposed to put the reader\n * back at the bottom had already switched itself off. CI caught it on\n * react-webkit parked 114px up, five seconds after a swap that started at the\n * bottom (scrollTop 1071, scrollHeight 1718, clientHeight 533).\n *\n * The two cases are told apart BY POSITION, which is what the note this replaces\n * asked for and what an event counter could not do: growth does not move\n * `scrollTop`, and a reader going up does. So a decrease disarms, the bottom\n * re-arms, and everything else leaves the flag exactly as it was.\n */\n const top = this._container.scrollTop;\n const readerWentUp = top < this._lastScrollTop - 1;\n this._lastScrollTop = top;\n\n if (this._isAtBottom()) {\n // `_isAtBottom()` stays deliberately generous (`_scrollThreshold`, 50px): a\n // few pixels of drift must not read as \"the reader walked away\".\n this._isAutoScrollEnabled = true;\n } else if (readerWentUp) {\n this._isAutoScrollEnabled = false;\n }\n this._updateScrollButton();\n }\n\n private _scrollToBottom(): void {\n if (!this._container) return;\n this._container.scrollTop = this._container.scrollHeight;\n this._settleAtBottom(4);\n }\n\n /**\n * Confirm over the next few frames that we actually reached the bottom.\n *\n * One assignment is not enough, and the reason is measured rather than guessed.\n * A timeline of a streamed turn on Safari (framework mode) recorded the content\n * settling in TWO layout passes — 1118 → 1121 → 1152 px. `scrollTop =\n * scrollHeight` ran against the middle one, clamped to that layout's max (603),\n * and nothing ran afterwards: the last 31px never closed. Auto-follow stayed\n * armed the whole time, so the component was not disarmed — it was SATISFIED.\n * `_isAtBottom()` answers \"yes\" for any gap under `_scrollThreshold` (50), which\n * is the right rule for keeping auto-follow armed and the wrong one as a\n * definition of \"anchored\".\n *\n * Ruled out on the way here, so nobody pays for it twice: not a WebKit\n * padding-accounting difference (a probe writing `scrollTop = 1e7` reached\n * exactly `scrollHeight - clientHeight`), not a missing `characterData`\n * mutation, and not a child resize a ResizeObserver could see.\n *\n * A BOUNDED retry, not one corrective frame: a single frame lands on the same\n * stale layout and was measured leaving a wider gap than doing nothing. Bounded\n * so it always terminates; re-reads `_isAutoScrollEnabled` every frame so a\n * reader who scrolls away mid-settle is left alone; stops as soon as the gap is\n * closed, so the common case costs one frame that does nothing.\n */\n private _settleAtBottom(framesLeft: number): void {\n if (framesLeft <= 0) return;\n requestAnimationFrame(() => {\n if (!this._container || !this._isAutoScrollEnabled) return;\n const max = this._container.scrollHeight - this._container.clientHeight;\n if (max - this._container.scrollTop <= 1) return;\n this._container.scrollTop = max;\n this._settleAtBottom(framesLeft - 1);\n });\n }\n\n private _smoothScrollToBottom(): void {\n if (!this._container) return;\n // scrollTo with behavior:'smooth' is not available in all environments (e.g. jsdom).\n // Fall back to instant scroll so tests and SSR environments stay safe.\n // Reduced-motion users get the instant path too — the CSS\n // prefers-reduced-motion block cannot reach a JS-driven smooth scroll.\n if (typeof this._container.scrollTo === 'function' && !this._prefersReducedMotion()) {\n this._container.scrollTo({ top: this._container.scrollHeight, behavior: 'smooth' });\n } else {\n this._container.scrollTop = this._container.scrollHeight;\n }\n }\n\n private _prefersReducedMotion(): boolean {\n return typeof matchMedia === 'function'\n && matchMedia('(prefers-reduced-motion: reduce)').matches;\n }\n\n /**\n * Show/hide the scroll-to-bottom button from the **current geometry**, not from\n * `_isAutoScrollEnabled`.\n *\n * The two answer different questions: the flag is intent (\"should new content\n * pull the view down\"), the button is a fact (\"is there anything below the\n * fold\"). Mirroring the flag made the button lie whenever the two diverged —\n * `navigateBranch` deliberately disarms auto-follow, so swapping a branch while\n * already at the bottom of a scrollable transcript left the button offering to\n * scroll nowhere (reported from bonaparte, React). Re-derived on scroll, on the\n * post-mutation frame and after a path swap, so it converges to the truth\n * whatever a framework's render timing does in between.\n */\n private _updateScrollButton(): void {\n this._scrollBtn?.classList.toggle('aparte-scroll-btn--hidden', this._isAtBottom());\n }\n\n /**\n * Recalculate the bottom spacer height so the last user message is always\n * pinned to the top of the scroll area when a response is being generated.\n *\n * spacer = max(0, viewportHeight - lastUserBubble.offsetHeight - lastAssistantBubble.offsetHeight)\n *\n * The spacer shrinks progressively as the assistant streams content, eventually\n * reaching 0 when the combined height fills the viewport.\n */\n private _recalculateSpacer(): void {\n // Core mode needs the spacer element; framework mode uses host padding\n // (no element) — both need the scroll container.\n if (!this._container) return;\n if (!this._frameworkManagedDOM && !this._bottomSpacer) return;\n // Skip while the host layout is still animating (e.g. the flex transition\n // that moves the composer from the center of the screen to the bottom).\n // Without this guard, every ResizeObserver tick during the transition\n // reads a partially-grown clientHeight and writes an incorrect spacer\n // height that may reach the clientHeight cap and lock the spacer there.\n if (Date.now() < this._spacerFrozenUntil) return;\n\n const allBubbles = Array.from(\n this.querySelectorAll('aparte-chat-bubble')\n ) as HTMLElement[];\n\n if (allBubbles.length === 0) {\n this._setSpacerHeight(0);\n return;\n }\n\n const lastUserBubble = [...allBubbles]\n .reverse()\n .find(b => b.getAttribute('role') === 'user');\n\n if (!lastUserBubble) {\n this._setSpacerHeight(0);\n return;\n }\n\n // Read the current spacer height (may be non-zero during a CSS transition\n // or a previous non-zero value). Subtract it from scrollHeight to get the\n // true content height WITHOUT the spacer — no need to zero-then-reflow,\n // which would both fight the CSS transition and force an extra synchronous\n // layout that could read a stale animated value.\n const currentSpacerH = this._getSpacerHeight();\n\n // Use getBoundingClientRect so gaps, padding, and all children are\n // automatically accounted for — no need to manually sum heights.\n const containerRect = this._container.getBoundingClientRect();\n const userRect = lastUserBubble.getBoundingClientRect();\n\n // Absolute Y position of the user bubble's top within the full scrollable content\n const userTopInContent = userRect.top - containerRect.top + this._container.scrollTop;\n\n // Height of content from user bubble top to end, excluding the spacer\n const scrollHeightWithoutSpacer = this._container.scrollHeight - currentSpacerH;\n\n // If all content already fits in the viewport, no spacer is needed.\n if (scrollHeightWithoutSpacer <= this._container.clientHeight) {\n this._setSpacerHeight(0);\n return;\n }\n\n const contentBelowUserTop = scrollHeightWithoutSpacer - userTopInContent;\n\n const needed = this._container.clientHeight - contentBelowUserTop;\n // Hard cap: the spacer can never exceed the visible viewport height.\n // This acts as a safety net against stale layout reads (e.g. mid-swap)\n // that could produce an astronomical value and push content off-screen.\n const maxSpacer = this._container.clientHeight;\n this._setSpacerHeight(Math.min(Math.max(0, needed), maxSpacer));\n\n // Re-scroll after the spacer height changes so scrollTop is always\n // consistent with the new scrollHeight. Without this, the MutationObserver\n // schedules _scrollToBottom() one RAF *before* _recalculateSpacer() runs,\n // leaving scrollTop based on the pre-spacer scrollHeight. On the next\n // recalculation (e.g. from syncMessagesWithBubbles or a resize) the formula\n // reads a stale scrollTop and may grow the spacer to the clientHeight cap.\n if (this._isAutoScrollEnabled) {\n this._scrollToBottom();\n }\n }\n\n /**\n * Schedule a spacer recalculation on the next animation frame.\n * Batches multiple rapid calls (e.g. during token streaming) into one.\n *\n * Single-RAF intentional: both the scroll-to-bottom queued by MutationObserver\n * and this spacer recalculation must land in the *same* frame so the browser\n * paints exactly once — with the correct scroll position *and* the correct\n * spacer height. A double-RAF would put the spacer shrink one frame after the\n * scroll, causing a 1-frame layout jump during streaming.\n */\n private _scheduleSpacerUpdate(): void {\n if (this._spacerRafId !== null) return;\n this._spacerRafId = requestAnimationFrame(() => {\n this._spacerRafId = null;\n this._recalculateSpacer();\n // The DOM just changed (new bubble, streamed token, framework re-render):\n // whether anything sits below the fold changed with it.\n this._updateScrollButton();\n });\n }\n\n /**\n * Cap the number of rendered bubbles in the DOM (perf ceiling only).\n *\n * Drops the oldest `<aparte-chat-bubble>` elements beyond `_maxRenderedBubbles`\n * from the DOM. It **never** touches the AparteMessageRepository — the conversation\n * model and its persistence snapshot stay complete (retention/eviction is a\n * consumer/persistence concern, not the viewport's). No-op when a framework\n * owns the DOM.\n */\n private _pruneRenderedBubbles(): void {\n if (this._frameworkManagedDOM) return;\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (!wrapper) return;\n const bubbles = wrapper.querySelectorAll('aparte-chat-bubble');\n const excess = bubbles.length - this._maxRenderedBubbles;\n for (let i = 0; i < excess; i++) {\n bubbles[i]?.remove();\n }\n }\n\n private _warnMaxMessagesDeprecated(): void {\n if (this._warnedMaxMessagesDeprecation) return;\n this._warnedMaxMessagesDeprecation = true;\n console.warn(\n '[Aparte] `maxMessages` / `max-messages` on aparte-chat-viewport is deprecated: ' +\n 'it used to silently evict messages from the conversation model. It now only ' +\n 'caps rendered bubbles in the DOM — use `maxRenderedBubbles` / `max-rendered-bubbles`. ' +\n 'For actual history retention, configure it on your AparteConversationManager instead.',\n );\n }\n\n private _cleanup(): void {\n this._container?.removeEventListener('scroll', this._handleScroll);\n this._scrollBtn?.removeEventListener('click', this._onScrollBtnClick);\n this.removeEventListener('aparte-branch-navigate', this._onBranchNavigate);\n this._resizeObserver?.disconnect();\n this._mutationObserver?.disconnect();\n this._resizeObserver = null;\n this._mutationObserver = null;\n if (this._spacerRafId !== null) {\n cancelAnimationFrame(this._spacerRafId);\n this._spacerRafId = null;\n }\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-viewport')) {\n customElements.define('aparte-chat-viewport', AparteChatViewport);\n}\n","import type { AparteSendEventDetail } from '../../types/index.js';\nimport { type AparteConfig } from '../../config/aparte-config.js';\nimport { resolveConfig } from '../../config/config-context.js';\n\n/**\n * What the composer's one button means while a panel is up — and whether it is\n * there at all.\n *\n * `'submit'` answers, `'advance'` moves to the next question of a form, and\n * `'none'` says this panel has NO act left for that button, so it is not drawn.\n *\n * `'none'` exists because a panel could not previously say it. The composer's panel\n * mode was one fixed policy — hide the input and the attachment picker, keep the\n * strip and the toolbar, and ALWAYS keep the send button — and the approval panel\n * showed what that costs: its options settle on the first click by design, so the\n * button beside them sat permanently disabled, meaning nothing, until the optional\n * instruction field was opened. A control that is never the way forward is not\n * disabled, it is absent (ratified decision #8).\n *\n * Named rather than inlined at each of its six readers: this union is exactly the\n * kind of list this repo has watched drift when every reader kept its own copy.\n */\nexport type AparteComposerPanelMode = 'advance' | 'submit' | 'none';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Event map for internal pub/sub between primitives\n// ─────────────────────────────────────────────────────────────────────────────\nexport interface AparteComposerEventMap {\n 'value-change': { value: string };\n 'streaming-change': { streaming: boolean };\n 'disabled-change': { disabled: boolean };\n 'attachments-change': { attachments: File[] };\n 'submit': { value: string; attachments: File[] };\n 'cancel': Record<string, never>;\n 'panel-change': { active: boolean; submitEnabled: boolean; mode: AparteComposerPanelMode };\n}\n\nexport type AparteComposerEventType = keyof AparteComposerEventMap;\n\n/**\n * Public snapshot of the composer's observable state. Delivered on every\n * `aparte-composer-change` DOM event and available synchronously via\n * {@link AparteComposer.getState}. Lets an element OUTSIDE the composer package\n * (a custom send button, a footer control) mirror the composer's live state\n * without the internal `_on`/`_emit` bus.\n */\nexport interface AparteComposerState {\n value: string;\n streaming: boolean;\n disabled: boolean;\n attachments: File[];\n /** A panel (e.g. an elicitation form) is showing in place of the input. */\n panelActive: boolean;\n /** Whether the send button should act as \"submit\" while a panel is active. */\n submitEnabled: boolean;\n}\n\n/** Detail of the public `aparte-composer-change` DOM event. */\nexport interface AparteComposerChangeEventDetail {\n state: AparteComposerState;\n composer: AparteComposer;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AparteComposer — root context provider\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The root context for every `aparte-composer-*` primitive. It imposes no visual\n * layout — the consumer owns the structure — and holds the shared state the parts\n * read: the value, the streaming flag, pending attachments, whether a panel is up.\n *\n * It renders nothing of its own — no shadow root, no markup, no default children — so\n * an `<aparte-composer>` with nothing inside is an empty block. The parts that need\n * that state locate it with `closest('aparte-composer')`, which is why they may sit at\n * any depth and why the opt-in `.aparte-composer-shell` / `.aparte-composer-row`\n * wrappers can exist without this element knowing about them.\n * Not every part looks it up, though: `<aparte-composer-toolbar>` is purely structural\n * — it lays its children out and never resolves this element at all.\n *\n * WHAT GOES INSIDE — ordinary light-DOM children. Core has no shadow root and no\n * `<slot>`, so there is no slot name to write: drop in `<aparte-composer-input>`,\n * `<aparte-composer-send>`, `<aparte-composer-cancel>`,\n * `<aparte-composer-attachments>`, `<aparte-composer-add-attachment>`,\n * `<aparte-composer-action>`, `<aparte-composer-toolbar>`, plus whatever markup you\n * wrap them in. Order and nesting are yours. Two behaviours read the tree rather than a\n * flag, so they depend on what you put in: `focus()` forwards to the first\n * `<aparte-composer-input>` descendant, and `showPanel()` inserts the panel right after\n * it (appending to the host when there is none).\n *\n * A PANEL is neither markup you write nor a named slot: `showPanel()` takes the element,\n * stamps it `data-aparte-panel` and inserts it, `hidePanel()` removes it. One at a time\n * — a second `showPanel()` evicts the first and calls its `onEvict`. While one is up the\n * host carries `[data-panel-active]`, which hides `<aparte-composer-input>` and\n * `<aparte-composer-add-attachment>` and leaves the attachments strip and the toolbar in\n * place.\n *\n * It is not a transport either. `submit()` trims, checks the gates (disabled, empty,\n * no model selected), dispatches `aparte-send` and clears — nothing here talks to a\n * model, so without `AparteClient` or a listener of your own a send is a dispatched\n * event and no answer. With no panel up it doubles as the stop button: while `streaming`\n * it routes to `cancel()`, which is why the `getState` example below keeps a custom send\n * button clickable rather than disabling it mid-stream. With a panel up it means \"answer\n * the question\" instead — it calls the panel's `onSubmit` and returns, so neither the\n * stop branch nor a send is reached.\n *\n * The streaming flag comes from WINDOW lifecycle events, filtered by target. On a page\n * with two chats, give the composer a `target` — or put it under a chat host that has\n * an `id` — otherwise it answers to every chat's events, and one chat's Stop resets the\n * other's composer and evicts its open panel.\n *\n * Prose first, on purpose: when `@element` opens a docblock there is no free text\n * left for the analyser to use, and this component's description came out empty in\n * the manifest and blank on the generated reference page.\n *\n * `aparte-abort` and `aparte-message-aborted` have to be declared by hand and always\n * will: they go out through `window.dispatchEvent` (they concern the whole page, not\n * this subtree), and the analyser's fallback only recognises `this.dispatchEvent`.\n *\n * @element aparte-composer\n *\n * @attr {string} placeholder - Fallback placeholder for `<aparte-composer-input>`, which\n * reads it off this element when it carries none of its own. Read when that input\n * renders, not pushed: changing it here leaves an input already on the page as it was.\n * @attr {boolean} disabled - Disables the composer's own controls — the input, send,\n * add-attachment and `<aparte-composer-action>` buttons each read it. What you put in\n * the toolbar is yours to disable.\n * @attr {string} target - The id of the `<aparte-chat>` this composer drives.\n * @attr {boolean} submit-on-enter - Enter sends and Shift+Enter breaks the line (the\n * default); set it to the string `\"false\"` to swap them. Read lazily by the\n * `submitOnEnter` getter rather than observed, which is why it was missing from the\n * manifest — and so from every typed surface — while all four wrappers wrote it.\n *\n * @fires {CustomEvent<AparteSendEventDetail>} aparte-send - A message was submitted: the text, its attachments and the target.\n * @fires aparte-cancel - The stop button was pressed. No detail; the two window events below carry the target.\n * @fires {CustomEvent<AparteAbortEventDetail>} aparte-abort - Dispatched on `window`: stop the run for this target.\n * @fires {CustomEvent<AparteMessageAbortedEventDetail>} aparte-message-aborted - The run for this target ended early — the user pressed Stop, or `abort()` was called. This element dispatches it on `window`; `AparteClient` also dispatches it on the chat host, so it is listenable on either.\n * @fires {CustomEvent<AparteComposerChangeEventDetail>} aparte-composer-change - Any of value / streaming / disabled / attachments / panel changed, folded into one event.\n *\n * @cssprop [--aparte-composer-control-size=44px] - Width and height of the composer's\n * own control buttons (each is an `.aparte-btn--icon`, so it needs the opt-in\n * `.aparte-composer-row` wrapper, which is what carries the size down) and the\n * minimum height of the input's editor, which needs no wrapper. One knob for the\n * whole control set, so buttons stay aligned with a single line of text and anchored\n * to the bottom once the input grows. It reaches the buttons by declaration, not by\n * out-specifying them, so a panel mounted in the row keeps its own content's sizing.\n * @cssprop [--aparte-input-bg=var(--aparte-surface-1)] - Background of the opt-in\n * `.aparte-composer-shell` wrapper.\n * @cssprop [--aparte-input-border=var(--aparte-border)] - Border colour of that shell.\n * Its `:focus-within` colour is `--aparte-primary`, a global token rather than a\n * composer one.\n * @cssprop [--aparte-radius-input=var(--aparte-radius-lg)] - Corner radius of the shell,\n * and of the dashed outline drawn while files are dragged over the composer.\n * @cssprop [--aparte-message-max-width=800px] - Max width of the shell, which is\n * `margin: 0 auto` at this width — the same width `.aparte-message` uses, so the\n * composer keeps the transcript's column. Set on THIS element it moves the shell only:\n * custom properties inherit downward and the transcript is a sibling subtree, so set\n * it on a shared ancestor (the chat host, `:root`) to move both.\n *\n * @example\n * <!-- It renders nothing of its own — no shadow root, no default children — so this\n * markup IS the component. The shell draws the border; the row keeps the controls\n * on the bottom edge of the text as it grows. Both are opt-in classes: drop them\n * and the parts still work, they just sit wherever your own layout puts them. -->\n * <aparte-composer placeholder=\"Ask anything…\">\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposer extends HTMLElement {\n private _value = '';\n private _streaming = false;\n private _attachments: File[] = [];\n private _listeners = new Map<string, Set<(payload: unknown) => void>>();\n private _panelActive = false;\n /** What the send button means while a panel is up — see `showPanel`. */\n private _panelMode: AparteComposerPanelMode = 'submit';\n private _panelSubmitEnabled = false;\n private _panelOnSubmit: (() => void) | null = null;\n /**\n * Who owns the one panel slot, and how to tell them they lost it.\n *\n * There is exactly one slot, `showPanel` empties it unconditionally, and nothing\n * used to say whose it was. Three paths therefore closed a panel whose owner was\n * still awaiting an answer, and none of them told the owner: a second `showPanel`,\n * the owner-of-record's own `hidePanel`, and — the one that actually bit —\n * `_handleMessageDone`, which fires on EVERY turn end. A question still open when a\n * turn finished lost its panel while `<aparte-elicitation>` kept `_pending` set,\n * so the request never settled AND every later request was short-circuited for the\n * life of the page.\n */\n private _panelToken: symbol | null = null;\n private _panelOnEvict: (() => void) | null = null;\n\n // Internal bus events that represent an observable STATE change — these are\n // mirrored to the public `aparte-composer-change` DOM event. `submit`/`cancel`\n // are actions, not state, and are covered by `aparte-send`/`aparte-cancel`.\n private static readonly _STATE_EVENTS: ReadonlySet<AparteComposerEventType> = new Set([\n 'value-change', 'streaming-change', 'disabled-change', 'attachments-change', 'panel-change',\n ]);\n\n // Window event bindings\n private _onMessageStart = this._handleMessageStart.bind(this);\n private _onMessageDone = this._handleMessageDone.bind(this);\n\n /** Config governing THIS composer (nearest instance boundary, else global). */\n /**\n * Resolved LIVE, not cached at connect.\n *\n * A wrapper runs `AparteChatHost.bind()` — which calls `attachConfig` — from its\n * POST-mount hook, so this element connects BEFORE the boundary exists. Caching\n * here latched the global config forever: an instance `config` carrying an RTL\n * locale flipped the transcript and not the composer, and the\n * `requireModelSelection` gate was read off the wrong object. `aparte-chat-bubble`\n * has always resolved live, and its JSDoc names this exact race.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n /** Only OUR config: a change on another chat's instance must not touch us. */\n private _onConfigChangeEvent = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n this._onConfigChange();\n };\n /** True while `requireModelSelection` is on AND no model is selected — blocks send. */\n private _modelGated = false;\n private _onConfigChange = (): void => { this._evaluateModelGate(); this._applyDirection(); };\n\n /**\n * Mirror `locale.direction` onto ourselves, so everything inside — input, buttons,\n * the toolbar row and whatever the consumer put in it — inherits it.\n *\n * The direction used to stop at the transcript: only the viewport applied `dir`, so\n * an RTL locale flipped the conversation and left the composer left-to-right. It is\n * also what makes the toolbar's placement idiom real: `margin-inline-start: auto` in\n * a subtree that inherits no direction is just `margin-left`.\n *\n * One attribute on the host rather than a stamp per child: inheritance is the\n * mechanism, so nothing needs to know about the consumer's markup.\n */\n private _applyDirection(): void {\n const direction = (this._cfg ?? resolveConfig(this)).getLocale().direction;\n if (direction) this.setAttribute('dir', direction);\n else this.removeAttribute('dir');\n }\n\n static get observedAttributes(): string[] {\n return ['placeholder', 'disabled', 'target'];\n }\n\n connectedCallback(): void {\n window.addEventListener('aparte-message-start', this._onMessageStart);\n window.addEventListener('aparte-message-done', this._onMessageDone);\n window.addEventListener('aparte-message-error', this._onMessageDone);\n window.addEventListener('aparte-message-aborted', this._onMessageDone);\n // Model-selection gate (opt-in via aparteGlobalConfig.setRequireModelSelection).\n // A window listener rather than `_cfg.subscribe(...)`: subscribing binds to\n // whichever config was resolvable AT CONNECT, which is the bug above wearing\n // a different hat. `_notify()` dispatches `aparte-config-change` with\n // `detail.config`, so the same information arrives without the early binding\n // — and the filter below compares against the LIVE config.\n window.addEventListener('aparte-config-change', this._onConfigChangeEvent);\n this._evaluateModelGate();\n this._applyDirection();\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-message-start', this._onMessageStart);\n window.removeEventListener('aparte-message-done', this._onMessageDone);\n window.removeEventListener('aparte-message-error', this._onMessageDone);\n window.removeEventListener('aparte-message-aborted', this._onMessageDone);\n window.removeEventListener('aparte-config-change', this._onConfigChangeEvent);\n this._listeners.clear();\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'disabled') {\n this._emit('disabled-change', { disabled: value !== null });\n }\n if (name === 'placeholder') {\n // Primitives read this directly via closest() — no event needed\n }\n }\n\n // ── Public API ─────────────────────────────────────────────────────────\n\n get value(): string { return this._value; }\n get streaming(): boolean { return this._streaming; }\n get disabled(): boolean { return this.hasAttribute('disabled'); }\n /**\n * When false, Shift+Enter submits and a bare Enter inserts a newline —\n * the inverse of the default. Driven by the `submit-on-enter` attribute.\n */\n get submitOnEnter(): boolean { return this.getAttribute('submit-on-enter') !== 'false'; }\n get attachments(): File[] { return this._attachments; }\n get placeholder(): string { return this.getAttribute('placeholder') ?? ''; }\n get targetId(): string | null { return this.getAttribute('target'); }\n\n /**\n * Snapshot of the composer's observable state. Pair with the\n * `aparte-composer-change` DOM event to drive a custom send button or footer\n * control that lives outside the composer package:\n *\n * @example\n * // A custom send button. Keep it CLICKABLE while streaming — submit()\n * // routes to cancel() when a response is in flight, so one button is\n * // Send/Stop. Disabling it on `streaming` would make \"stop\" unreachable.\n * composer.addEventListener('aparte-composer-change', (e) => {\n * const { streaming, disabled, value, attachments } = e.detail.state;\n * myButton.textContent = streaming ? 'Stop' : 'Send';\n * myButton.disabled = disabled || (!streaming && !value.trim() && attachments.length === 0);\n * });\n * myButton.addEventListener('click', () => composer.submit()); // send or stop\n */\n getState(): AparteComposerState {\n return {\n value: this._value,\n streaming: this._streaming,\n disabled: this.disabled,\n attachments: [...this._attachments],\n panelActive: this._panelActive,\n submitEnabled: this._panelSubmitEnabled,\n };\n }\n\n /**\n * Set the composer's value — both what a send will submit and what the editor\n * shows. `<aparte-composer-input>` writes through any value it does not already\n * hold, so this prefills the visible field (a template button, a restored draft)\n * as readily as it stages text for an immediate `submit()`.\n */\n setValue(value: string): void {\n this._value = value;\n this._emit('value-change', { value });\n }\n\n /** Append files to the pending attachments and notify. Does not de-duplicate. */\n addAttachments(files: FileList | File[]): void {\n this._attachments = [...this._attachments, ...Array.from(files)];\n this._emit('attachments-change', { attachments: this._attachments });\n }\n\n /**\n * Drop one pending attachment and notify. Matched by IDENTITY — pass the same\n * `File` object the composer handed you, not an equal one; two picks of the same\n * file on disk are two distinct objects.\n */\n removeAttachment(file: File): void {\n this._attachments = this._attachments.filter(f => f !== file);\n this._emit('attachments-change', { attachments: this._attachments });\n }\n\n /** Drop every pending attachment and notify. */\n clearAttachments(): void {\n this._attachments = [];\n this._emit('attachments-change', { attachments: [] });\n }\n\n /**\n * Inject a panel into the composer. The send button calls `onSubmit` when clicked.\n *\n * While a panel is up, the composer is answering a QUESTION, not composing a\n * message — so the affordances that lead nowhere go away with the text input:\n * the attachment picker above all, which stayed clickable while the user was\n * being asked something (\"on voyait encore l'icône de upload\", reported from a\n * real session). Ratified decision #8: an affordance nothing can honour is not\n * rendered.\n *\n * What STAYS, deliberately: the attachments strip, because pending attachments\n * are the user's state and not an action to offer — hiding them would look like\n * losing them; and the toolbar, because switching model still does something.\n *\n * The send button is the part the PANEL decides, through `mode`. `'submit'` and\n * `'advance'` keep it; `'none'` says this panel has no act for it and it is not\n * drawn. That third value is what a panel whose options settle on the first click\n * needs — a single-choice question, an approval — and until it existed such a\n * panel left a permanently disabled button beside options that never routed\n * through it. Flip between them at any time with {@link setPanelSubmitEnabled},\n * which is how a panel that grows an act (an \"Other…\" field, a written\n * instruction) turns the button back on.\n *\n * Declared with an attribute + CSS rather than the inline `style.display` this\n * used to set on a child: an attribute is themeable, is visible to a consumer's\n * own rules, and does not clobber a `display` the consumer had set (the restore\n * wrote `''`, not the previous value).\n *\n * Returns the TOKEN for this panel. Pass it back to `hidePanel` so a presenter\n * that has already lost the slot cannot close the panel that replaced it, and\n * supply `onEvict` to be told when that happens — an owner that is not told is an\n * owner whose promise nobody can settle.\n */\n showPanel(\n panel: HTMLElement,\n options?: {\n submitEnabled?: boolean;\n onSubmit?: () => void;\n /**\n * What the send button means for this panel — `'none'` if it has no act\n * for it, in which case the button is not drawn. See\n * {@link AparteComposerPanelMode}.\n */\n mode?: AparteComposerPanelMode;\n /** Called when something other than this owner closes the panel. */\n onEvict?: () => void;\n },\n ): symbol {\n // Evict rather than hide: the previous owner is awaiting an answer it will\n // never get, and it is the only thing that can settle its own promise.\n this._evictPanel();\n const inputEl = this.querySelector('aparte-composer-input') as HTMLElement | null;\n this.setAttribute('data-panel-active', '');\n panel.dataset['apartePanel'] = 'true';\n if (inputEl) {\n inputEl.insertAdjacentElement('afterend', panel);\n } else {\n this.appendChild(panel);\n }\n this._panelActive = true;\n this._panelSubmitEnabled = options?.submitEnabled ?? false;\n this._panelMode = options?.mode ?? 'submit';\n this._reflectPanelMode();\n this._panelOnSubmit = options?.onSubmit ?? null;\n this._panelOnEvict = options?.onEvict ?? null;\n const token = Symbol('aparte-composer-panel');\n this._panelToken = token;\n this._emit('panel-change', { active: true, submitEnabled: this._panelSubmitEnabled, mode: this._panelMode });\n return token;\n }\n\n /**\n * Remove the panel, tell its owner, and restore the composer's own controls.\n *\n * With a `token`, this closes the panel only if that token still owns the slot —\n * so a presenter settling late cannot tear down the panel that replaced it. With\n * no token it closes whatever is there, which is what a consumer driving the\n * composer directly means.\n *\n * BOTH forms notify. The no-token form used to call `_teardownPanel` directly,\n * which nulls `_panelOnEvict` without ever calling it — so the documented public\n * `hidePanel()` closed an open approval panel and left its request pending\n * forever. The turn hung on \"waiting for you\", and because\n * `AparteConfig.requestUserInput` chains each request on the previous one, NO\n * further question or approval on that config was ever presented again, for the\n * life of the page.\n *\n * The old JSDoc justified the silent branch as \"what `reset()` needs\". It was not:\n * `reset()` calls `_evictPanel()`, which notifies. The branch had no consumer and\n * one failure mode.\n *\n * The two forms differ on purpose, and the difference is who already knows:\n *\n * - **With a matching token** the OWNER is closing its own panel, which is what\n * `<aparte-elicitation>`'s `close()` does right after it resolves. It must NOT be\n * notified — telling it \"you were evicted\" for a request it just settled would\n * fire `onEvict` against a finished promise.\n * - **With no token** somebody else is closing a panel they do not own, so the owner\n * cannot know and has to be told. That includes the presenter's own defensive\n * `hidePanel(undefined)` when it settles before `showPanel` ran: whatever is open\n * then belongs to another request, and that request must not orphan.\n */\n hidePanel(token?: symbol): void {\n if (token !== undefined) {\n if (token !== this._panelToken) return;\n this._teardownPanel();\n return;\n }\n this._evictPanel();\n }\n\n /** Close the panel AND tell its owner, so a pending request never orphans. */\n private _evictPanel(): void {\n const onEvict = this._panelOnEvict;\n // State first, callback second: the owner's settle path calls `hidePanel` with\n // its own token, which must find the slot already empty rather than recurse.\n this._teardownPanel();\n onEvict?.();\n }\n\n private _teardownPanel(): void {\n const existing = this.querySelector('[data-aparte-panel]') as HTMLElement | null;\n if (existing) existing.remove();\n this.removeAttribute('data-panel-active');\n this._panelActive = false;\n this._panelSubmitEnabled = false;\n this._panelMode = 'submit';\n this._reflectPanelMode();\n this._panelOnSubmit = null;\n this._panelOnEvict = null;\n this._panelToken = null;\n this._emit('panel-change', { active: false, submitEnabled: false, mode: 'submit' });\n this.focus();\n }\n\n /**\n * Update the send button's state while a panel is active.\n *\n * `mode` moves with it because both change on the same event — answering the\n * question you are on can enable the button AND turn it from \"advance\" into\n * \"submit\" (when it was the last one), and two separate calls would flash a\n * wrong icon between them.\n */\n setPanelSubmitEnabled(enabled: boolean, mode?: AparteComposerPanelMode): void {\n if (!this._panelActive) return;\n this._panelSubmitEnabled = enabled;\n if (mode) this._panelMode = mode;\n this._reflectPanelMode();\n this._emit('panel-change', { active: true, submitEnabled: enabled, mode: this._panelMode });\n }\n\n /**\n * Publish the panel mode as an attribute, so CSS can act on it.\n *\n * The same reasoning `data-panel-active` is set for and the same one that took\n * the old `style.display` off a child: an attribute is themeable, is visible to\n * a consumer's own rules, and does not clobber what the consumer set. It is what\n * lets `'none'` remove the send button without this component reaching into it.\n */\n private _reflectPanelMode(): void {\n if (this._panelActive) this.setAttribute('data-panel-mode', this._panelMode);\n else this.removeAttribute('data-panel-mode');\n }\n\n get panelActive(): boolean { return this._panelActive; }\n\n /**\n * Recompute the model gate from the resolved config. When\n * `requireModelSelection` is on and no model is selected, block sending and\n * reflect `data-model-gated` so the shipped CSS greys the composer. Re-runs on\n * every config change (e.g. the model selector's auto-select firing).\n */\n private _evaluateModelGate(): void {\n const gated = this._cfg.getRequireModelSelection() && !this._cfg.hasSelectedModel();\n if (gated === this._modelGated) return;\n this._modelGated = gated;\n this.toggleAttribute('data-model-gated', gated);\n }\n\n /** Submit the current value. Called by aparte-composer-send or programmatically. */\n submit(): void {\n if (this._panelActive) {\n // `'none'` is authoritative over `submitEnabled`, and deliberately so: the\n // two are set by the same caller and can disagree, and the mode is the one\n // that says whether an act exists at all. Reached by Enter inside the panel\n // and by a consumer calling `submit()` directly — the button itself is not\n // drawn in this mode.\n if (this._panelMode !== 'none' && this._panelSubmitEnabled) this._panelOnSubmit?.();\n return;\n }\n if (this._streaming) {\n this.cancel();\n return;\n }\n const value = this._value.trim();\n if (!value && this._attachments.length === 0) return;\n if (this.disabled) return;\n if (this._modelGated) return; // no model selected yet (require-model gate)\n\n this._emit('submit', { value, attachments: this._attachments });\n\n const detail: AparteSendEventDetail = {\n content: value,\n timestamp: Date.now(),\n targetId: this.targetId ?? undefined,\n files: this._attachments.length > 0 ? [...this._attachments] : undefined,\n };\n\n this.dispatchEvent(new CustomEvent<AparteSendEventDetail>('aparte-send', {\n bubbles: true,\n composed: true,\n detail,\n }));\n\n // Clear after send\n this.setValue('');\n this.clearAttachments();\n }\n\n /** Cancel the current streaming response. */\n cancel(): void {\n this._emit('cancel', {});\n // Public, element-scoped signal — symmetric with `aparte-send` on submit,\n // for consumers that want to observe cancel on the composer itself.\n this.dispatchEvent(new CustomEvent('aparte-cancel', { bubbles: true, composed: true }));\n // aparte-abort → tells AparteClient to actually stop the stream\n // aparte-message-aborted → resets the composer's own streaming state\n // Scope the abort to this composer's host so cancelling one chat doesn't\n // abort every scoped client / reset every composer on the page.\n /*\n * `_ownTargetId()`, not the bare attribute.\n *\n * The receive side got this fix and the SEND side did not, which left the whole\n * scoping inert in raw core: nothing writes the `target` attribute in the\n * hand-written markup the quick start shows, so this detail carried\n * `targetId: undefined` — and `_isForThisComposer` treats a missing id as \"for\n * everyone\". So Stop in chat A still tore down chat B's open panel, which is\n * exactly the failure `_ownTargetId`'s own docblock describes as fixed.\n *\n * Both sides now resolve the same way: the attribute if the wrapper set one,\n * otherwise the id of the chat host above us.\n */\n const abortDetail = { targetId: this._ownTargetId() };\n window.dispatchEvent(new CustomEvent('aparte-abort', { bubbles: false, detail: abortDetail }));\n window.dispatchEvent(new CustomEvent('aparte-message-aborted', { bubbles: false, detail: abortDetail }));\n }\n\n /**\n * Reset the composer to its initial state.\n * Clears value, attachments, and hides any active panel.\n * Call this when switching conversations.\n */\n reset(): void {\n this.setValue('');\n this.clearAttachments();\n if (this._panelActive) this._evictPanel();\n }\n\n /** Focus the input primitive inside this composer. */\n override focus(): void {\n const input = this.querySelector('aparte-composer-input') as HTMLElement | null;\n input?.focus();\n }\n\n // ── Internal pub/sub ────────────────────────────────────────────────────\n\n _emit<K extends AparteComposerEventType>(event: K, payload: AparteComposerEventMap[K]): void {\n this._listeners.get(event)?.forEach(cb => cb(payload));\n // Mirror state changes to a public DOM event so elements outside the\n // composer package can observe them without the private bus.\n if (AparteComposer._STATE_EVENTS.has(event)) {\n this.dispatchEvent(new CustomEvent<AparteComposerChangeEventDetail>('aparte-composer-change', {\n bubbles: true,\n composed: true,\n detail: { state: this.getState(), composer: this },\n }));\n }\n }\n\n _on<K extends AparteComposerEventType>(event: K, cb: (payload: AparteComposerEventMap[K]) => void): () => void {\n if (!this._listeners.has(event)) this._listeners.set(event, new Set());\n this._listeners.get(event)!.add(cb as unknown as (payload: unknown) => void);\n return () => this._listeners.get(event)?.delete(cb as unknown as (payload: unknown) => void);\n }\n\n // ── Window events ───────────────────────────────────────────────────────\n\n /** A window lifecycle event is for THIS composer when neither side is scoped\n * (single-instance broadcast) or the target ids match (multi-chat page).\n * Without this filter, streaming in one chat flips every composer's state. */\n private _isForThisComposer(e: Event): boolean {\n const evtTargetId = (e as CustomEvent).detail?.targetId as string | undefined;\n return !evtTargetId || !this._ownTargetId() || evtTargetId === this._ownTargetId();\n }\n\n /**\n * Which chat this composer belongs to: its `target` attribute, or the id of the\n * chat host above it.\n *\n * All four wrappers set `target` themselves, so the attribute alone identified a\n * composer there. In RAW core — the documented quick start, where the markup is\n * hand-written — nothing sets it, so `!this.targetId` was true and this composer\n * accepted every chat's lifecycle events: on a two-chat page, one chat's Stop\n * tore down the other's open elicitation panel while its tool call kept waiting,\n * i.e. the question vanished and the turn hung.\n *\n * Found by a two-chat test written for the elicitation presenter, which is the\n * only reason it surfaced: raw core with two chats is a shape nothing exercised.\n * The hosts matched are the ones `aparte-chat-bubble._resolveTargetId()` matches,\n * for the reason written there — Angular's wrapper root IS `<aparte-chat>`, the\n * other three render a `[data-aparte-chat]` div.\n */\n private _ownTargetId(): string | undefined {\n const attr = this.targetId;\n if (attr) return attr;\n let el: HTMLElement | null = this.parentElement;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || tag === 'aparte-chat-component' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n private _handleMessageStart(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streaming = true;\n this._emit('streaming-change', { streaming: true });\n }\n\n private _handleMessageDone(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streaming = false;\n this._emit('streaming-change', { streaming: false });\n // Always hide any active panel when a message lifecycle ends\n if (this._panelActive) this._evictPanel();\n }\n}\n\nif (!customElements.get('aparte-composer')) {\n customElements.define('aparte-composer', AparteComposer);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Contenteditable text input primitive.\n *\n * The element owns its subtree: on connect it writes one `.aparte-ci-editor`\n * contenteditable and binds its listeners to that node, so children you place inside are\n * replaced. There is nothing to project here — style the generated editor through the CSS\n * variables below, or replace the whole primitive.\n *\n * Enter submits and Shift+Enter inserts a newline; `submit-on-enter=\"false\"` on the\n * composer inverts that mapping, and Enter never submits mid-IME-composition — the key\n * that confirms a CJK candidate must not send the message. The editor auto-expands with\n * its content up to `max-height`, then scrolls. Paste is intercepted: text lands as plain\n * text with its markup stripped, and a pasted image goes to the composer's attachments.\n *\n * Without an `<aparte-composer>` ancestor it still works, and that is deliberate: a\n * submitting Enter then dispatches `aparte-composer-submit` instead of calling\n * `root.submit()`, which is how the bubble's inline editor reuses this primitive.\n * Everything the root owns goes with it though — the mirrored value, the placeholder\n * fallback, the disabled/streaming sync and image paste all need the composer.\n *\n * Not a `<textarea>` and not a stand-in for one: being a contenteditable it has no form\n * value, no `name` and no native validation, and `getValue()` returns trimmed text with\n * `<br>` serialized back to newlines. Use it for the chat draft, not as a form control.\n *\n * @element aparte-composer-input\n *\n * @fires aparte-composer-submit - A submitting Enter was pressed with no\n * `<aparte-composer>` ancestor to submit to; with one it calls `root.submit()` and\n * dispatches nothing. No detail — the host that placed this primitive reads\n * `getValue()`.\n *\n * @attr {boolean} disabled - Makes the field non-editable; the composer's own `disabled` also reaches it.\n * @attr {string} placeholder - Placeholder text (fallback: reads from aparte-composer)\n * @attr {number} max-height - Max height in px before scroll (default: 200)\n * @attr {number} min-height - Min height in px. When omitted, the stylesheet's\n * min-height governs (44px in aparte.css) — so themes can\n * resize the editor in pure CSS without being fought by\n * an inline height.\n *\n * @cssprop [--aparte-composer-control-size=44px] - Single-line min-height of the editor.\n * Inside the `.aparte-composer-row` layout helper the composer's buttons read\n * the same token, so one value resizes that whole control set and the row stays\n * aligned.\n * @cssprop [--aparte-input-padding-y=10px] - Vertical padding inside the editor.\n * @cssprop [--aparte-input-padding-x=12px] - Horizontal padding inside the editor.\n * @cssprop [--aparte-input-font-size=14px] - Editor font size.\n * @cssprop [--aparte-input-line-height=1.5] - Editor line height — also what the\n * auto-expand measures, so changing it changes the height the editor settles at\n * (until `max-height` clamps it).\n * @cssprop --aparte-text - Text and caret colour of the editor.\n * @cssprop --aparte-input-placeholder - Colour of the placeholder drawn by\n * `:empty::before` (falls back to `--aparte-text-muted`).\n * @cssprop --aparte-input-bg - Field background, applied only when this input is the\n * bubble's inline editor (`.aparte-message[data-editing]`) — inside a composer\n * the shell paints the surface instead.\n * @cssprop --aparte-input-border - Border colour of that same edit-mode box.\n * @cssprop [--aparte-radius-input=8px] - Corner radius of the edit-mode box.\n * @cssprop --aparte-input-focus-border - Border colour of the edit-mode box while it\n * holds focus (`:focus-within`).\n *\n * @example\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input placeholder=\"Ask anything…\" max-height=\"320\" style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerInput extends HTMLElement {\n private _editor: HTMLDivElement | null = null;\n private _maxHeight = 200;\n private _minHeight = 44;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handlers\n private _onInput = this._handleInput.bind(this);\n private _onKeydown = this._handleKeydown.bind(this);\n private _onFocus = this._handleFocus.bind(this);\n private _onBlur = this._handleBlur.bind(this);\n private _onPaste = this._handlePaste.bind(this);\n\n static get observedAttributes(): string[] {\n return ['placeholder', 'max-height', 'min-height', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n this._scheduleInitialReflow();\n // The placeholder is the one string in this composer a sighted user can\n // actually read, and it was frozen at the language of the first render.\n // `_updatePlaceholder` already exists for the attribute path — a locale\n // change is the same refresh, from a different trigger.\n this._unsubscribes.push(subscribeConfigChange(this, () => this._updatePlaceholder()));\n }\n\n disconnectedCallback(): void {\n this._editor?.removeEventListener('input', this._onInput);\n this._editor?.removeEventListener('keydown', this._onKeydown);\n this._editor?.removeEventListener('focus', this._onFocus);\n this._editor?.removeEventListener('blur', this._onBlur);\n this._editor?.removeEventListener('paste', this._onPaste);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'placeholder') this._updatePlaceholder();\n if (name === 'max-height') this._maxHeight = parseInt(value || '200', 10);\n if (name === 'min-height') this._minHeight = parseInt(value || '44', 10);\n if (name === 'disabled') this._updateDisabled(value !== null);\n }\n\n // ── Public API ──────────────────────────────────────────────────────────\n\n /**\n * The editor's text, with `<br>` serialized back to newlines — `textContent`\n * alone would collapse a multi-line draft onto a single line.\n */\n getValue(): string {\n // `textContent` drops `<br>`, so multi-line content would collapse onto one\n // line. Serialize the editor ourselves: text nodes as-is, `<br>` → newline.\n // We keep the editor flat (text + <br>, see _handleKeydown), but descend into\n // any stray wrapper for safety.\n if (!this._editor) return '';\n let out = '';\n const walk = (node: Node): void => {\n node.childNodes.forEach(child => {\n if (child.nodeType === Node.TEXT_NODE) out += child.textContent ?? '';\n else if (child.nodeName === 'BR') out += '\\n';\n else walk(child);\n });\n };\n walk(this._editor);\n return out.trim();\n }\n\n /** Replace the editor's content and mirror the value onto the parent composer. */\n setValue(value: string): void {\n if (!this._editor) return;\n this._editor.textContent = value;\n this._updatePlaceholderVisibility();\n this._adjustHeight();\n this._getRoot()?.setValue(value);\n }\n\n /** Empty the editor and mirror the empty value onto the parent composer. */\n clear(): void {\n if (!this._editor) return;\n this._editor.innerHTML = '';\n this._updatePlaceholderVisibility();\n this._adjustHeight();\n this._getRoot()?.setValue('');\n }\n\n /** Focus the inner contenteditable rather than the host element. */\n override focus(): void { this._editor?.focus(); }\n /** Blur the inner contenteditable rather than the host element. */\n override blur(): void { this._editor?.blur(); }\n\n /** Focus the editor and place the caret at the very end of its content. */\n focusEnd(): void {\n if (!this._editor) return;\n this._editor.focus();\n const sel = this.ownerDocument?.getSelection();\n if (!sel) return;\n const range = this.ownerDocument.createRange();\n range.selectNodeContents(this._editor);\n range.collapse(false);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _getPlaceholder(): string {\n return this.getAttribute('placeholder')\n || this._getRoot()?.placeholder\n || resolveConfig(this).t('inputPlaceholder')\n || 'Type a message...';\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-ci-editor')) return;\n\n const disabled = this.hasAttribute('disabled') || this._getRoot()?.disabled || false;\n // `placeholder` may come straight from a host attribute (often bound to\n // dynamic/translated text) — escape before it lands in a double-quoted\n // attribute so a stray `\"` can't break out and inject markup.\n const placeholder = escapeAttr(this._getPlaceholder());\n\n this.innerHTML = `<div\n class=\"aparte-ci-editor\"\n contenteditable=\"${!disabled}\"\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-label=\"${placeholder}\"\n tabindex=\"0\"\n aria-disabled=\"${disabled}\"\n data-placeholder=\"${placeholder}\"\n ></div>`;\n\n this._editor = this.querySelector('.aparte-ci-editor');\n this._editor?.addEventListener('input', this._onInput);\n this._editor?.addEventListener('keydown', this._onKeydown);\n this._editor?.addEventListener('focus', this._onFocus);\n this._editor?.addEventListener('blur', this._onBlur);\n this._editor?.addEventListener('paste', this._onPaste);\n\n this._adjustHeight();\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n // Sync disabled state from root\n this._unsubscribes.push(\n root._on('disabled-change', ({ disabled }) => this._updateDisabled(disabled))\n );\n\n // The editor shows what the composer says.\n //\n // Compared, not special-cased. While the user types, `_handleInput` has just\n // pushed this very value up, so the two sides are equal and nothing is\n // rewritten — which is what keeps the caret where the user left it, and was the\n // entire reason the old form acted on `''` alone. Any other value arrived from\n // somewhere else: `setValue()` on the composer, or the `''` that `submit()`\n // writes on its way out. Both now land, where only the second one used to.\n //\n // Compared against `value.trim()` because `getValue()` trims. A padded value\n // would never look equal otherwise, and the mirror back through `setValue`\n // would re-enter this callback forever.\n this._unsubscribes.push(\n root._on('value-change', ({ value }) => {\n if (this.getValue() === value.trim()) return;\n if (value === '') this.clear();\n else this.setValue(value);\n })\n );\n\n // Sync streaming state — disable input while streaming\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n this._updateDisabled(streaming || root.disabled);\n })\n );\n }\n\n private _handleInput(): void {\n this._adjustHeight();\n // After a delete-all, contenteditable leaves residual `<br>` tags\n // (Chromium especially) so the `:empty` CSS pseudo-class no longer\n // matches and the placeholder stays hidden. Force-clear when text\n // content reduces to whitespace so the placeholder reappears.\n if (this._editor && !this._editor.textContent?.trim() && this._editor.innerHTML !== '') {\n this._editor.innerHTML = '';\n }\n this._updatePlaceholderVisibility();\n const value = this.getValue();\n this._getRoot()?.setValue(value);\n }\n\n private _handleKeydown(e: KeyboardEvent): void {\n // During IME composition (CJK/Japanese/Korean), Enter confirms the\n // candidate — it must never submit. `keyCode === 229` is the legacy\n // signal for engines that don't set `isComposing`.\n if (e.isComposing || e.keyCode === 229) return;\n if (e.key !== 'Enter') return;\n // `submit-on-enter` (default true): Enter submits, Shift+Enter inserts\n // a newline. When false the mapping inverts — Shift+Enter submits and\n // a bare Enter inserts a newline (lets the user author multi-line).\n const submitOnEnter = this._getRoot()?.submitOnEnter ?? true;\n const submits = submitOnEnter ? !e.shiftKey : e.shiftKey;\n if (submits) {\n e.preventDefault();\n const root = this._getRoot();\n if (root) {\n root.submit();\n } else {\n // Standalone (no <aparte-composer> parent, e.g. the bubble's inline\n // editor): there is no root to submit to, so surface the intent as a\n // DOM event the host can act on. Keeps this primitive reusable on its\n // own — the IME guard + submitOnEnter mapping above stay the single\n // source of truth for \"when to submit\".\n this.dispatchEvent(new CustomEvent('aparte-composer-submit', { bubbles: true }));\n }\n } else {\n // Newline branch. Take control instead of the browser's contenteditable\n // default, which inserts <div>/<br> wrappers that resist deletion.\n // `insertLineBreak` inserts a single <br> and manages the trailing bogus\n // <br> so backspace removes it cleanly (the \"can't delete the newline\" bug).\n e.preventDefault();\n // Nothing to break on an empty field — don't seed a leading blank line.\n if (!this._editor?.textContent) return;\n this.ownerDocument?.execCommand('insertLineBreak');\n }\n }\n\n private _handleFocus(): void {\n this.classList.add('aparte-is-focused');\n }\n\n private _handleBlur(): void {\n this.classList.remove('aparte-is-focused');\n }\n\n private _handlePaste(e: ClipboardEvent): void {\n e.preventDefault();\n const cd = e.clipboardData;\n if (!cd) return;\n\n // Image paste → push to root attachments\n const imageFile = Array.from(cd.items).find(i => i.type.startsWith('image/'))?.getAsFile();\n if (imageFile) {\n this._getRoot()?.addAttachments([imageFile]);\n return;\n }\n\n // Plain text paste\n const text = cd.getData('text/plain');\n if (text) {\n document.execCommand('insertText', false, text);\n }\n }\n\n private _adjustHeight(): void {\n if (!this._editor) return;\n // Measure with height:0 (not auto): an explicit height opts the editor\n // OUT of any parent flex `align-items: stretch`, so scrollHeight reflects\n // the real content — not the (taller) row it may be stretched into. With\n // `auto`, a stretching parent inflates scrollHeight and the editor gets\n // stuck tall until the next reflow.\n this._editor.style.height = '0px';\n // Floor: the `min-height` ATTRIBUTE when explicitly set; otherwise defer\n // to the stylesheet (CSS min-height caps an inline height anyway). A\n // hardcoded JS floor would override theme CSS with an inline style and\n // break editor/controls alignment in restyled composers.\n const floor = this.hasAttribute('min-height') ? this._minHeight : 0;\n const contentHeight = this._editor.scrollHeight;\n const h = Math.min(Math.max(contentHeight, floor), this._maxHeight);\n this._editor.style.height = `${h}px`;\n this._editor.style.overflowY = contentHeight > this._maxHeight ? 'auto' : 'hidden';\n }\n\n /**\n * The first `_adjustHeight()` runs synchronously in `_render()` on connect —\n * before the stylesheet, flex layout and web fonts have necessarily settled.\n * On an unstabilized layout `scrollHeight` can read inflated, leaving the\n * editor stuck tall (misaligned with the composer controls) until the first\n * keystroke re-measures it. Re-measure once the layout is ready so it's\n * correct from the first paint.\n */\n private _scheduleInitialReflow(): void {\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => this._adjustHeight());\n }\n const fonts = (document as Document & { fonts?: { ready?: Promise<unknown> } }).fonts;\n fonts?.ready?.then(() => this._adjustHeight()).catch(() => { /* fonts unavailable — rAF path covers it */ });\n }\n\n private _updatePlaceholder(): void {\n if (this._editor) {\n const p = this._getPlaceholder();\n this._editor.setAttribute('data-placeholder', p);\n this._editor.setAttribute('aria-label', p);\n }\n }\n\n private _updatePlaceholderVisibility(): void {\n // Handled by CSS :empty — nothing to do\n }\n\n private _updateDisabled(disabled: boolean): void {\n if (!this._editor) return;\n this._editor.setAttribute('contenteditable', String(!disabled));\n this._editor.setAttribute('aria-disabled', String(disabled));\n }\n\n /** Escape a value before it lands in a double-quoted HTML attribute. */\n}\n\nif (!customElements.get('aparte-composer-input')) {\n customElements.define('aparte-composer-input', AparteComposerInput);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer, AparteComposerPanelMode } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Submit button primitive for <aparte-composer>.\n *\n * One control, four meanings: **send**, **stop** while the root is streaming, and — when\n * an elicitation panel is open — **submit** this answer or **advance** to the next\n * question. The panel outranks streaming: while one is open the button stays the answer\n * control and a streaming change is ignored. The icon moves with the meaning (paper\n * plane, square, check, chevron), because a check on a form with three questions left is\n * as wrong as a paper plane that means \"answer\". All four are decided by the root's state\n * — its `value`, `attachments`, `disabled`, `streaming` and the panel payload it\n * broadcasts — not by anything this element owns, which is why it recomputes its chrome\n * rather than re-rendering: a rebuild would put a paper plane back mid-stream, drop out\n * of answer mode, and take the focus off the control most likely to be holding it.\n *\n * \"Empty\" counts attachments: a pending attachment with no text still enables the\n * button, because that is a message the composer can send.\n *\n * It owns its subtree — the button is generated on connect and children placed inside\n * are replaced, so there is nothing to project. The host element itself is\n * `display: contents`, so it adds no box: the layout comes from whatever flex row you put\n * it in, and the CSS variables below style the inner button.\n *\n * It needs an `<aparte-composer>` ancestor: without one the button renders disabled, no\n * root event ever reaches it, and a click has nothing to submit to.\n *\n * It is not the place to gate on model selection: the opt-in\n * `aparteGlobalConfig.setRequireModelSelection()` gate already blocks this element's\n * pointer events through `aparte-composer[data-model-gated]`.\n *\n * @element aparte-composer-send\n *\n * @cssprop [--aparte-composer-control-size=44px] - Width/height of the button inside the\n * `.aparte-composer-row` layout helper, shared with the input's single-line\n * height so the row stays aligned. It wins over `--aparte-send-btn-size` there.\n * @cssprop [--aparte-send-btn-size=36px] - Width/height of the button outside that row\n * helper. On coarse pointers it is raised to `--aparte-touch-target-size`.\n * @cssprop [--aparte-touch-target-size=44px] - Hit-area floor applied to the button\n * under `@media (pointer: coarse)`.\n * @cssprop [--aparte-radius-send-btn=6px] - Corner radius of the button.\n * @cssprop --aparte-primary - Button background.\n * @cssprop --aparte-primary-hover - Button background on hover, while enabled.\n * @cssprop --aparte-on-primary - The glyph's colour. Undeclared by default, which means\n * the recipe derives it from `--aparte-primary` itself, so a theme that changes\n * the fill gets a readable glyph with no second edit. Declare it to choose one\n * — it then applies to every primary control, which is the honest scope.\n * @cssprop [--aparte-ink-flip=0.57] - Fill lightness at which the derived ink flips from\n * dark to light, for every solid control.\n * @cssprop [--aparte-ink-dark=0.176] - How dark that derived ink goes. Not 0: at zero\n * lightness OKLCH drops the chroma, and the ink loses the fill's own hue.\n * @cssprop --aparte-send-disabled-bg - Background while disabled (falls back to\n * `--aparte-primary`, which is then dimmed by opacity).\n *\n * @example\n * <!-- One button for both halves of the turn: it submits, and while a reply streams it\n * becomes the stop button. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerSend extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n /**\n * The last `panel-change` payload.\n *\n * This button has four meanings — send, stop, submit an answer, advance to the\n * next question — and three of them are decided by state it does not own: the\n * root's `streaming`, and this payload. It was read straight out of the event's\n * arguments and thrown away, so nothing could recompute the button's chrome\n * afterwards; a config change had no way to know which of the four to write.\n */\n private _panel: { active: boolean; submitEnabled: boolean; mode: AparteComposerPanelMode } | null = null;\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-cs-button')) return;\n\n const label = resolveConfig(this).t('sendButton') || 'Send';\n const icon = this._getSendIcon();\n const root = this._getRoot();\n const disabled = !root || root.disabled || root.value.trim() === '';\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--primary aparte-btn--solid aparte-btn--icon aparte-cs-button aparte-send-button\"\n aria-label=\"${escapeAttr(label)}\"\n title=\"${escapeAttr(label)}\"\n ${disabled ? 'disabled' : ''}\n >${icon}</button>`; // safe-text: _getSendIcon() returns the provider's SVG markup — escaping it would print the source\n\n this._button = this.querySelector('.aparte-cs-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('value-change', () => this._syncState())\n );\n this._unsubscribes.push(\n root._on('disabled-change', () => this._syncState())\n );\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n // If panel is active, streaming state change doesn't affect the button —\n // the panel controls it (submit answer, not stop stream)\n if (this._getRoot()?.panelActive) return;\n this._syncStreamingState(streaming);\n })\n );\n this._unsubscribes.push(\n root._on('attachments-change', () => this._syncState())\n );\n this._unsubscribes.push(\n root._on('panel-change', (payload) => {\n this._panel = payload;\n this._refreshChrome();\n })\n );\n // A config change — a new icon set, another language — has to write the\n // chrome for whichever of the four meanings the button currently carries.\n this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));\n }\n\n /**\n * Write the chrome for the mode the button is IN, deciding before writing.\n *\n * Never `_render()`: it returns early once the button exists, and its own\n * disabled/icon computation consults neither `root.streaming` nor the panel — so\n * rebuilding mid-turn would put a paper plane back while a reply was still\n * streaming, and rebuilding with the question panel open would silently drop out\n * of answer mode. It would also take the focus off the one control in this\n * composer most likely to be holding it.\n */\n private _refreshChrome(): void {\n if (!this._button) return;\n if (this._panel?.active) { this._syncPanelState(); return; }\n if (this._getRoot()?.streaming) { this._syncStreamingState(true); return; }\n this._syncState();\n }\n\n /**\n * Panel open: this one button now means \"answer\", and WHICH answer depends on\n * where you are in the form.\n *\n * The icon has to move with the meaning: it drew a paper plane while the label\n * already said \"Submit\", so it read as \"send a message\" while it meant \"answer\n * this question\". And a check on a form with three questions left was just as\n * wrong — hence a chevron while there is more ahead. The visual is what a user\n * reads.\n */\n private _syncPanelState(): void {\n const panel = this._panel;\n if (!this._button || !panel?.active) return;\n const cfg = resolveConfig(this);\n // No act for this button on this panel: its options settle themselves. The\n // composer's `[data-panel-mode=\"none\"]` rule takes it out of the layout — and\n // `display: none` takes it out of the accessibility tree with it, so there is\n // no `aria-hidden` or `tabindex` to set here and none to restore when the mode\n // flips back. What this branch does is refuse to RELABEL it: leaving it\n // announced as a disabled \"Submit\" is the lie, not the button.\n if (panel.mode === 'none') {\n this._button.disabled = true;\n return;\n }\n const advancing = panel.mode === 'advance';\n this._button.disabled = !panel.submitEnabled;\n this._button.innerHTML = advancing ? cfg.getIcon('nextBranch') : this._getSubmitIcon();\n const label = advancing\n ? (cfg.t('elicitationNext') || 'Next')\n : (cfg.t('submitButton') || 'Submit');\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.classList.remove('aparte-is-streaming');\n }\n\n private _handleClick(e: MouseEvent): void {\n e.preventDefault();\n this._getRoot()?.submit();\n }\n\n private _syncState(): void {\n const root = this._getRoot();\n if (!root || !this._button) return;\n if (root.streaming) return; // streaming state managed separately\n\n const isEmpty = root.value.trim() === '' && root.attachments.length === 0;\n this._button.disabled = root.disabled || isEmpty;\n this._button.innerHTML = this._getSendIcon();\n const label = resolveConfig(this).t('sendButton') || 'Send';\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.classList.remove('aparte-is-streaming');\n }\n\n private _syncStreamingState(streaming: boolean): void {\n if (!this._button) return;\n if (streaming) {\n this._button.disabled = false;\n this._button.innerHTML = this._getStopIcon();\n // Was the bare literal 'Stop', so no locale could reach it — the same\n // gap `aparte-composer-cancel` had, on a second element. The key is\n // declared now.\n const label = resolveConfig(this).t('stopButton') || 'Stop';\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.classList.add('aparte-is-streaming');\n } else {\n this._syncState();\n }\n }\n\n /**\n * The icon for submitting an ANSWER, which is not the same act as sending a\n * message — one button, two meanings, and it has to say which one it is.\n */\n private _getSubmitIcon(): string {\n // No fallback chain: `getIcon` already returns a built-in when the consumer's\n // icon set has no entry, so `|| getIcon('send')` was dead code — written on the\n // assumption that it could come back empty, and a test proved it cannot.\n return resolveConfig(this).getIcon('check');\n }\n\n private _getSendIcon(): string {\n return resolveConfig(this).getIcon('send') || 'Send';\n }\n\n private _getStopIcon(): string {\n return resolveConfig(this).getIcon('stop');\n }\n}\n\nif (!customElements.get('aparte-composer-send')) {\n customElements.define('aparte-composer-send', AparteComposerSend);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Cancel/stop streaming button primitive for <aparte-composer>.\n *\n * Most composers should not use this element. `<aparte-composer-send>` already becomes\n * the stop button while a reply streams, so adding this one gives you a second, equally\n * working way to stop; reach for it only when you want stop to live somewhere the send\n * button is not.\n *\n * It renders hidden, and only the root reveals it — the `root.streaming` check on connect,\n * then each `streaming-change` — so it needs an `<aparte-composer>` ancestor to be\n * reachable at all: standalone, nothing flips `hidden` and the click has no `cancel()` to\n * call. A locale or icon-set change is re-read in place rather than re-rendered, for the\n * same reason: a rebuild renders it hidden again, making the stop button vanish mid-turn.\n *\n * It owns its subtree — the button is generated on connect and children placed inside\n * are replaced, so there is nothing to project. The host element is `display: contents`\n * and adds no box of its own; the row you put it in provides the layout, and the CSS\n * variables below style the inner button, which is deliberately a quiet action button\n * rather than a filled one.\n *\n * @element aparte-composer-cancel\n *\n * @cssprop [--aparte-composer-control-size=44px] - Width/height of the button inside the\n * `.aparte-composer-row` layout helper, shared with the composer's other\n * controls so the row stays aligned.\n * @cssprop [--aparte-radius-action-btn=4px] - Corner radius of the button.\n * @cssprop --aparte-neutral - Icon colour at rest (the button's background is\n * transparent).\n * @cssprop --aparte-text - Icon colour on hover.\n * @cssprop --aparte-surface-2 - Button background on hover.\n *\n * @example\n * <!-- Only needed when you want a SEPARATE stop button: <aparte-composer-send> already\n * turns into one while streaming. This stays hidden until then. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-cancel></aparte-composer-cancel>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerCancel extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-cc-button')) return;\n\n const label = resolveConfig(this).t('stopButton') || 'Stop';\n const icon = this._getStopIcon();\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--icon aparte-cc-button\"\n aria-label=\"${escapeAttr(label)}\"\n title=\"${escapeAttr(label)}\"\n hidden\n >${icon}</button>`; // safe-text: _getStopIcon() returns the provider's SVG markup — escaping it would print the source\n\n this._button = this.querySelector('.aparte-cc-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n if (this._button) this._button.hidden = !streaming;\n })\n );\n\n // Sync initial state\n if (root.streaming && this._button) this._button.hidden = false;\n }\n\n private _handleClick(e: MouseEvent): void {\n e.preventDefault();\n this._getRoot()?.cancel();\n }\n\n /**\n * Re-read the accessible name and the icon in place.\n *\n * `hidden` is NOT touched: `_render()` always renders this button hidden and only\n * the root's `streaming-change` listener ever un-hides it, so a rebuild would make\n * the stop button vanish in the middle of a turn.\n */\n private _refreshChrome(): void {\n if (!this._button) return;\n const label = resolveConfig(this).t('stopButton') || 'Stop';\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.innerHTML = this._getStopIcon();\n }\n\n private _getStopIcon(): string {\n return resolveConfig(this).getIcon('stop');\n }\n}\n\nif (!customElements.get('aparte-composer-cancel')) {\n customElements.define('aparte-composer-cancel', AparteComposerCancel);\n}\n","import type { AparteComposer } from './aparte-composer.js';\nimport { resolveConfig } from '../../config/config-context.js';\nimport { escapeAttr } from '../../utils/escape.js';\n\n\n/**\n * Renders a square thumbnail tile for each file attached to the root composer.\n *\n * Image files show the actual picture; other files show an extension badge.\n * The filename and a remove (✗) button surface on hover. Clicking an image asks\n * the app to open it full-size (`aparte-attachment-preview`) — only when the app\n * declared `attachmentPreview` via `aparteGlobalConfig.setHostHandlers()`.\n * Automatically hidden when there are no attachments. It reads the nearest\n * <aparte-composer> ancestor; without one it renders nothing and stays hidden.\n *\n * This is the PENDING strip: what the user has attached and not yet sent. It mirrors\n * `composer.attachments` and rewrites itself on every `attachments-change` — it is not the\n * strip under a sent message, which the bubble draws with the same `.aparte-thumb` tile\n * rules (minus the remove button), so a tile variable set at the theme root reaches both\n * strips, while one set on this element reaches only this one. It owns its `innerHTML` and\n * therefore projects nothing:\n * children written inside it are discarded on the first render. Removing a tile calls\n * `root.removeAttachment()` rather than mutating a list of its own, and the image previews\n * are blob URLs minted per render and revoked on the next one and on disconnect.\n *\n * @element aparte-composer-attachments\n *\n * @fires {CustomEvent<AparteAttachmentPreviewEventDetail>} aparte-attachment-preview - An attached image was clicked; the app opens it full-size, and only if it declared `attachmentPreview`.\n *\n * @cssprop [--aparte-attachments-max-height=140px] - Height cap on the strip; past it the\n * tiles scroll instead of pushing the composer up.\n * @cssprop [--aparte-attachment-image-size=56px] - Tile edge. The stylesheet sets 56px on\n * this element (the `:root` default is 72px, and the sent-message strip re-sets 40px on\n * itself), so a theme-level value reaches neither strip — target\n * `aparte-composer-attachments` to resize these tiles.\n * @cssprop [--aparte-thumb-radius=var(--aparte-radius-lg)] - Tile corner radius.\n * @cssprop [--aparte-attachment-chip-bg=var(--aparte-surface-2)] - Tile background, seen\n * behind a non-image file.\n * @cssprop [--aparte-attachment-chip-border=var(--aparte-border)] - Tile border colour.\n * @cssprop [--aparte-thumb-name-color=#ffffff] - Filename colour on the hover overlay.\n * @cssprop [--aparte-thumb-name-scrim=linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0))] - Background behind the filename; a bottom-up black\n * gradient by default, so the name stays legible over any picture.\n * @cssprop [--aparte-thumb-name-padding=14px 5px 4px] - Padding of that overlay.\n * @cssprop [--aparte-thumb-remove-size=18px] - Diameter of the ✗ button.\n * @cssprop [--aparte-thumb-remove-inset=3px] - Its inset from the tile's top and right\n * edges (physical `right`, so it does not flip in a right-to-left locale).\n * @cssprop [--aparte-thumb-remove-bg=rgba(0, 0, 0, 0.6)] - Its background.\n * @cssprop [--aparte-thumb-remove-bg-hover=rgba(0, 0, 0, 0.85)] - Its hover background.\n * @cssprop [--aparte-thumb-remove-color=#ffffff] - Its glyph colour.\n *\n * @example\n * <!-- The strip hides itself while nothing is attached. Pair it with the picker, and\n * only if your loop actually reads the files from the send event. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <aparte-composer-attachments></aparte-composer-attachments>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-add-attachment></aparte-composer-add-attachment>\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerAttachments extends HTMLElement {\n private _unsubscribes: (() => void)[] = [];\n /** Object URLs minted for image previews — revoked on re-render/disconnect. */\n private _objectUrls: string[] = [];\n\n connectedCallback(): void {\n this._render([]);\n this._connectToRoot();\n }\n\n disconnectedCallback(): void {\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n this._revokeUrls();\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('attachments-change', ({ attachments }) => this._render(attachments))\n );\n\n // Sync initial state\n this._render(root.attachments);\n }\n\n /** Release the previous render's blob URLs so they don't leak. */\n private _revokeUrls(): void {\n this._objectUrls.forEach(url => URL.revokeObjectURL(url));\n this._objectUrls = [];\n }\n\n private _render(files: File[]): void {\n this.hidden = files.length === 0;\n // Free the previous render's preview URLs before minting new ones.\n this._revokeUrls();\n\n this.innerHTML = files.map((file) => {\n const name = this._escape(file.name);\n const remove =\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-thumb__remove\" type=\"button\" ` +\n `aria-label=\"Remove ${name}\">${resolveConfig(this).getIcon('close')}</button>`;\n\n if (file.type.startsWith('image/')) {\n const url = URL.createObjectURL(file);\n this._objectUrls.push(url);\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--image\" title=\"${escapeAttr(name)}\">` +\n `<img class=\"aparte-thumb__img\" src=\"${escapeAttr(url)}\" alt=\"${escapeAttr(name)}\" />` +\n `<span class=\"aparte-thumb__name\">${name}</span>${remove}</div>`;\n }\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--file\" title=\"${escapeAttr(name)}\">` +\n `<span class=\"aparte-thumb__ext\">${this._escape(this._ext(file.name))}</span>` +\n `<span class=\"aparte-thumb__name\">${name}</span>${remove}</div>`;\n }).join('');\n\n // Remove buttons — every file has exactly one tile, so the button\n // index lines up with the attachments index.\n this.querySelectorAll('.aparte-thumb__remove').forEach((btn, i) => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n const root = this._getRoot();\n if (root) root.removeAttachment(root.attachments[i]!);\n });\n });\n\n // Image tiles ask for the full-size preview — only when the app declared it\n // opens one (same rule as the sent-message strip in the bubble).\n if (!resolveConfig(this).getHostHandlers().attachmentPreview) return;\n this.querySelectorAll('.aparte-thumb--image').forEach(tile => {\n tile.setAttribute('role', 'button');\n tile.setAttribute('tabindex', '0');\n const open = (): void => {\n const img = tile.querySelector('.aparte-thumb__img') as HTMLImageElement | null;\n if (!img) return;\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true,\n composed: true,\n detail: { url: img.src, name: tile.getAttribute('title') ?? '' },\n }));\n };\n tile.addEventListener('click', open);\n tile.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key !== 'Enter' && key !== ' ') return;\n e.preventDefault();\n open();\n });\n });\n }\n\n /** Uppercased file extension (≤4 chars), or 'FILE' when there is none. */\n private _ext(filename: string): string {\n const dot = filename.lastIndexOf('.');\n return dot > 0 ? filename.slice(dot + 1).toUpperCase().slice(0, 4) : 'FILE';\n }\n\n private _escape(str: string): string {\n return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"').replace(/'/g, ''');\n }\n}\n\nif (!customElements.get('aparte-composer-attachments')) {\n customElements.define('aparte-composer-attachments', AparteComposerAttachments);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * File picker button for <aparte-composer>.\n *\n * Opens a native file picker on click, then pushes picked files to root.addAttachments().\n * Also sets up drag & drop on the nearest <aparte-composer> root.\n *\n * It only COLLECTS files: it never reads, uploads or renders them.\n * `<aparte-composer-attachments>` draws the pending strip, and sending is the host's job\n * (`event.detail.files` on `aparte-send`) — which is why the default `<aparte-chat>` shell\n * only includes this button when the `attachments` attribute is set. With nothing reading\n * the files, an attach button is an affordance core cannot honour (ratified decision #8).\n *\n * Drag & drop is installed on the composer ROOT, not on this button, so a drop anywhere\n * over the composer attaches and the root carries `aparte-is-dragover` while a drag is\n * over it. The dashed outline is drawn on `.aparte-composer-shell` when the markup has one\n * and on the composer element itself when it does not — width from\n * `--aparte-focus-outline-width`, colour from `--aparte-primary`, radius from\n * `--aparte-radius-input`, none of them declared here. The drop handler always calls\n * `preventDefault()`, even while disabled, so the browser can never navigate away to the\n * dropped file. `disabled` on the ROOT removes the drop target; `streaming` does not — it\n * only greys the button out, so a drop mid-turn still attaches.\n *\n * The label and the icon are not attributes — they come from the config (`t('actionUpload')`\n * and the `paperclip` icon), so a locale or icon-provider change rewrites the existing\n * button in place instead of re-rendering it.\n *\n * A child already carrying `class=\"aparte-caa-button\"` suppresses core's own render — and\n * core then wires nothing to it: no click listener (so no picker opens), and no label,\n * icon or disabled/streaming writes. Drag & drop still works, since it is installed on the\n * root regardless. Any other child is replaced on the first render. The file input itself\n * is never a child: it is created on `document.body` per click and removed again.\n *\n * @element aparte-composer-add-attachment\n *\n * @attr {string} accept - MIME types / extensions passed to the file input (e.g. \"image/*,.pdf\")\n * @attr {boolean} multiple - Allow multiple file selection (default: true)\n * @attr {boolean} disabled - Greys out the picker. Drops are gated by the composer root's\n * `disabled`, not by this one.\n *\n * @cssprop [--aparte-input-action-btn-size=36px] - Square size of the button. On a coarse\n * pointer the stylesheet re-sets it to `--aparte-touch-target-size` (44px) on\n * `.aparte-action-button` itself, which wins over a value inherited from your theme.\n * @cssprop [--aparte-input-action-btn-icon-size=20px] - Size of the `<svg>` inside it.\n * @cssprop [--aparte-radius-action-btn=var(--aparte-radius-sm)] - Corner radius.\n *\n * @example\n * <!-- Opt-in: nothing consumes the files unless your host does (an AparteClient, or\n * your own listener reading `event.detail.files` off `aparte-send`). -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-add-attachment accept=\"image/*,.pdf\"></aparte-composer-add-attachment>\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerAddAttachment extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _dragCleanup: (() => void) | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n static get observedAttributes(): string[] {\n return ['accept', 'multiple', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n this._setupDragDrop();\n this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._dragCleanup?.();\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'disabled' && this._button) {\n this._button.disabled = value !== null;\n }\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n /**\n * Re-read the label and the icon on the button that already exists.\n *\n * Deliberately not a re-render: `_render()` returns early once the button is\n * there, and its `disabled` computation ignores `root.streaming` while the\n * streaming listener sets `disabled` directly — so rebuilding would silently\n * re-enable the attach button mid-turn, and drop focus if the user were on it.\n * The native file input and the drag listeners live outside this element\n * (on `document.body` and on the composer root), so they are untouched either way.\n */\n private _refreshChrome(): void {\n if (!this._button) return;\n const cfg = resolveConfig(this);\n const label = cfg.t('actionUpload') || 'Attach file';\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.innerHTML = cfg.getIcon('paperclip');\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-caa-button')) return;\n\n const label = resolveConfig(this).t('actionUpload') || 'Attach file';\n const icon = resolveConfig(this).getIcon('paperclip');\n const disabled = this.hasAttribute('disabled') || this._getRoot()?.disabled || false;\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--icon aparte-caa-button aparte-action-button\"\n aria-label=\"${escapeAttr(label)}\"\n title=\"${escapeAttr(label)}\"\n type=\"button\"\n ${disabled ? 'disabled' : ''}\n >${icon}</button>`;\n\n this._button = this.querySelector('.aparte-caa-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('disabled-change', ({ disabled }) => {\n if (this._button) this._button.disabled = disabled;\n })\n );\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n if (this._button) this._button.disabled = streaming || root.disabled;\n })\n );\n }\n\n private _handleClick(): void {\n const input = document.createElement('input');\n input.type = 'file';\n input.multiple = !this.hasAttribute('multiple') || this.getAttribute('multiple') !== 'false';\n const accept = this.getAttribute('accept');\n if (accept) input.accept = accept;\n input.style.display = 'none';\n\n document.body.appendChild(input);\n input.addEventListener('change', () => {\n if (input.files?.length) this._getRoot()?.addAttachments(input.files);\n document.body.removeChild(input);\n }, { once: true });\n input.click();\n }\n\n private _setupDragDrop(): void {\n const root = this._getRoot();\n if (!root) return;\n\n const prevent = (e: Event) => { e.preventDefault(); e.stopPropagation(); };\n const onDragOver = (e: Event) => {\n if (root.disabled) return; // no drop target while disabled (e.g. streaming)\n prevent(e);\n root.classList.add('aparte-is-dragover');\n };\n const onDragLeave = (e: Event) => { prevent(e); root.classList.remove('aparte-is-dragover'); };\n const onDrop = (e: DragEvent) => {\n prevent(e); // always block the browser from navigating to the dropped file\n root.classList.remove('aparte-is-dragover');\n if (root.disabled) return; // don't attach while disabled (the add button is blocked too)\n const files = e.dataTransfer?.files;\n if (files?.length) this._getRoot()?.addAttachments(files);\n };\n\n root.addEventListener('dragover', onDragOver);\n root.addEventListener('dragleave', onDragLeave);\n root.addEventListener('drop', onDrop);\n\n this._dragCleanup = () => {\n root.removeEventListener('dragover', onDragOver);\n root.removeEventListener('dragleave', onDragLeave);\n root.removeEventListener('drop', onDrop);\n };\n }\n\n}\n\nif (!customElements.get('aparte-composer-add-attachment')) {\n customElements.define('aparte-composer-add-attachment', AparteComposerAddAttachment);\n}\n","import { resolveConfig, type AparteIconName } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Generic action button primitive for <aparte-composer>.\n *\n * The consumer declares it directly in markup — no global registration needed.\n *\n * It is the escape hatch for a button core has no opinion about: it renders one icon\n * button wearing `.aparte-action-button` (the shared icon-button look — colour from\n * `--aparte-neutral`, hover tint derived from `--aparte-primary`) and emits\n * `aparte-action-click`. It carries no behaviour of its own and nothing in core listens\n * for that event, so the app is the only thing that can make it do something. Prefer the\n * dedicated element wherever one exists — `<aparte-composer-send>`,\n * `<aparte-composer-cancel>`, `<aparte-composer-add-attachment>` — since those already\n * talk to the composer.\n *\n * The host is `display: contents`, so the `<button>` rather than this element is the flex\n * child of the surrounding `.aparte-composer-row`. It subscribes to the nearest composer's\n * `disabled` and `streaming` changes, so it greys out while a turn is running without the\n * app tracking that. Used outside a composer it still mounts and still fires, with\n * `composer: null` in the detail.\n *\n * A child already carrying `class=\"aparte-cact-button\"` suppresses core's own render — and\n * core then wires nothing to it: no click listener (so no `aparte-action-click`), no\n * `label` → `aria-label`/`title` write, no `icon` write, no disabled/streaming sync. Take\n * that path only for a button your own code drives end to end. Any other child is replaced\n * on the first render.\n *\n * @element aparte-composer-action\n *\n * @attr {string} icon - Icon key for aparteGlobalConfig.getIcon(), or raw SVG/HTML starting with `<`\n * @attr {string} label - Accessible label (also used as tooltip)\n * @attr {boolean} disabled - Disables the button\n * @attr {string} action-id - Identifies WHICH button fired; carried as\n * `AparteActionClickEventDetail.actionId`. Read lazily at dispatch time rather than\n * observed, so changing it takes effect on the next click.\n *\n * @fires {CustomEvent<AparteActionClickEventDetail>} aparte-action-click - Bubbles up when\n * the button is clicked, carrying which button it was and the composer it belongs to.\n * The type argument is not decoration: a BARE `@fires` records `CustomEvent` with no\n * argument, and the bindings generator then emits `EventEmitter<void>` with a\n * listener that drops `$event` — so an Angular consumer with two custom buttons\n * could not tell which one fired.\n * detail: { actionId: string, composer: AparteComposer | null }\n *\n * @cssprop [--aparte-input-action-btn-size=36px] - Square size of the button. On a coarse\n * pointer the stylesheet re-sets it to `--aparte-touch-target-size` (44px) on\n * `.aparte-action-button` itself, which wins over a value inherited from your theme.\n * @cssprop [--aparte-input-action-btn-icon-size=20px] - Size of the `<svg>` inside it.\n * @cssprop [--aparte-radius-action-btn=var(--aparte-radius-sm)] - Corner radius.\n *\n * @example\n * <!-- Inside a composer, because that is what it resolves with `closest()`. `action-id`\n * is what tells two custom buttons apart: it comes back on the event's detail, and\n * a second button without one is indistinguishable from the first. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-action icon=\"star\" label=\"Favourite\" action-id=\"favourite\"></aparte-composer-action>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n *\n * <script>\n * // The event bubbles, so one listener above the composer serves every action.\n * document.addEventListener('aparte-action-click', (event) => {\n * if (event.detail.actionId === 'favourite') console.log('starred');\n * });\n * </script>\n */\nexport class AparteComposerAction extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n static get observedAttributes(): string[] {\n return ['icon', 'label', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n // Icon only, and no locale: this element's label is the consumer's `label`\n // ATTRIBUTE, so the app owns that string and a locale change is correctly a\n // no-op here. The write is the same one `attributeChangedCallback` does for\n // the `icon` attribute — `_resolveIcon` already decides between a provider\n // key and raw markup, so calling it again is idempotent.\n this._unsubscribes.push(subscribeConfigChange(this, () => {\n if (this._button) this._button.innerHTML = this._resolveIcon(this.getAttribute('icon') ?? '');\n }));\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (!this._button) return;\n if (name === 'disabled') {\n this._button.disabled = value !== null;\n }\n if (name === 'label') {\n this._button.setAttribute('aria-label', value ?? '');\n this._button.setAttribute('title', value ?? '');\n }\n if (name === 'icon') {\n this._button.innerHTML = this._resolveIcon(value ?? '');\n }\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-cact-button')) return;\n\n // `label` is a host-set attribute (often bound to dynamic/translated\n // text by the consumer) — escape before it lands in a double-quoted\n // attribute so a stray `\"` can't break out and inject markup.\n const label = escapeAttr(this.getAttribute('label') ?? '');\n const icon = this._resolveIcon(this.getAttribute('icon') ?? ''); // safe-text: _resolveIcon returns provider SVG, or the host-set icon attribute verbatim when it starts with < — documented as trusted markup, same contract as AparteIconProvider\n const disabled = this.hasAttribute('disabled') || this._getRoot()?.disabled || false;\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--icon aparte-cact-button aparte-action-button\"\n aria-label=\"${label}\"\n title=\"${label}\"\n type=\"button\"\n ${disabled ? 'disabled' : ''}\n >${icon}</button>`;\n\n this._button = this.querySelector('.aparte-cact-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('disabled-change', ({ disabled }) => {\n if (this._button) this._button.disabled = disabled || this.hasAttribute('disabled');\n })\n );\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n if (this._button) this._button.disabled = streaming || root.disabled || this.hasAttribute('disabled');\n })\n );\n }\n\n private _handleClick(_e: MouseEvent): void {\n this.dispatchEvent(new CustomEvent<AparteActionClickEventDetail>('aparte-action-click', {\n bubbles: true,\n composed: true,\n detail: { actionId: this.getAttribute('action-id') ?? '', composer: this._getRoot() },\n }));\n }\n\n private _resolveIcon(icon: string): string {\n if (!icon) return '';\n if (icon.trimStart().startsWith('<')) return icon;\n return resolveConfig(this).getIcon(icon as AparteIconName) ?? icon;\n }\n}\n\nif (!customElements.get('aparte-composer-action')) {\n customElements.define('aparte-composer-action', AparteComposerAction);\n}\n\n/**\n * Detail payload for `aparte-action-click`.\n *\n * `<aparte-composer-action>` is a publicly exported element whose only purpose is\n * to emit this event, and nothing in core listens for it — so the app IS the\n * consumer, and it had no type to read `e.detail` with. The shape was already\n * published in prose in the generated API reference; this makes it compile.\n *\n * @event aparte-action-click\n */\nexport interface AparteActionClickEventDetail {\n /** The `action-id` attribute of the button that was clicked, or `''`. */\n actionId: string;\n /** The owning composer, or `null` when the button is used outside one. */\n composer: AparteComposer | null;\n}\n","/**\n * The composer's bottom row — the strip a mode picker, a model selector or a token\n * counter belongs in, rather than a bar of your own floating below the chat. Purely\n * structural: it lays its children out in a row and gets out of the way.\n *\n * **Position is the DOM order.** `margin-inline-start: auto` on a child pushes it (and\n * everything after it) to the end of the row. That is the whole placement API on\n * purpose: there is no `left`/`right` to be wrong about, so the row reads correctly in a\n * right-to-left locale without the author thinking about it.\n *\n * The controls the row is made of can be any element, or plain text. Nothing is wrapped or\n * reordered — the children ARE the row, laid out by flex in DOM order, and they may arrive\n * after connection (a framework commits children in its own order). Non-whitespace TEXT\n * counts as content too, so a hand-written row holding a bare token count stays visible\n * instead of tripping the `data-empty` hide.\n *\n * The row is not part of the default `<aparte-chat>` shell — nothing is drawn until you\n * put something in it.\n *\n * It declares no custom property of its own: the gap, the padding and the top separator\n * come from the global `--aparte-space-*` and `--aparte-border*` tokens, so it inherits a\n * theme rather than exposing knobs to re-set.\n *\n * @element aparte-composer-toolbar\n *\n * @attr {boolean} data-empty - Reflected BY the element while it holds neither an element\n * child nor non-whitespace text; the stylesheet hides it then.\n * Read-only, do not set it yourself.\n *\n * @example\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n *\n * <!-- `aparte-model-selector` is NOT part of core: importing\n * `@aparte/plugin-model-selector` is what defines it. Until then the tag\n * renders empty and inert with no error, and upgrades by itself when the\n * definition arrives. Any element of your own works here too. -->\n * <aparte-composer-toolbar>\n * <my-mode-picker></my-mode-picker>\n * <aparte-model-selector style=\"margin-inline-start:auto\"></aparte-model-selector>\n * </aparte-composer-toolbar>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerToolbar extends HTMLElement {\n private _observer: MutationObserver | null = null;\n\n connectedCallback(): void {\n this._syncEmpty();\n // Children can arrive after connection — a framework commits the element and its\n // children in whichever order suits it, and a consumer may add a control later.\n this._observer ??= new MutationObserver(() => this._syncEmpty());\n this._observer.observe(this, { childList: true });\n }\n\n disconnectedCallback(): void {\n this._observer?.disconnect();\n this._observer = null;\n }\n\n /**\n * Reflect `data-empty` from the presence of an ELEMENT child.\n *\n * Not `:empty` in CSS: that selector does not match an element holding a whitespace\n * text node, so a template that indents its content keeps the row — separator,\n * padding and all — while it looks empty to the user. Every framework template\n * indents. An empty row must not draw its own separator (the same rule as an empty\n * bubble action bar).\n *\n * Non-whitespace TEXT counts as content, not just an element child: a hand-written\n * row holding a bare token count (`<aparte-composer-toolbar>1 240 tokens</…>`) is\n * not empty, and hiding it would be a twenty-minute mystery for whoever wrote it.\n */\n private _syncEmpty(): void {\n const hasContent = Boolean(this.firstElementChild) || this.textContent?.trim() !== '';\n if (hasContent) this.removeAttribute('data-empty');\n else this.setAttribute('data-empty', '');\n }\n}\n\nif (!customElements.get('aparte-composer-toolbar')) {\n customElements.define('aparte-composer-toolbar', AparteComposerToolbar);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { escapeAttr } from '../../utils/escape.js';\n\nexport interface AparteConversationListItem {\n id: string;\n title: string;\n updatedAt?: number;\n /** When set, the item renders the unarchive action instead of archive. */\n archivedAt?: number;\n}\n\nexport interface AparteConversationSelectDetail {\n id: string;\n}\n\nexport interface AparteConversationDeleteDetail {\n id: string;\n}\n\nexport interface AparteConversationArchiveDetail {\n id: string;\n}\n\n/**\n * Conversation-history sidebar — a framework-agnostic web component. The host sets\n * the `conversations` JS property and the `active-id` attribute; this renders the\n * list and fires the user's intent, never acting on it itself.\n *\n * Children are not a composition point: `_render()` assigns `innerHTML` from the\n * `conversations` array, so any light-DOM child a host writes inside the element is\n * discarded the next time the list renders — and switching this element's locale is\n * enough to trigger one. Compose around the element, not inside it: it renders rows\n * and nothing else, with no header, no new-conversation button and no search field.\n *\n * What it is not: a store. Clicking a row selects nothing, and the two row actions\n * delete and archive nothing — the four events carry an id and stop. A row's text\n * comes from the array, so it changes when the host assigns `conversations` again;\n * the exception is an empty title, which falls back to the locale's new-chat label\n * and therefore follows a locale switch. An archived item is still rendered (it gains\n * `aparte-conv-item--archived` and swaps its action's icon and event name); filtering\n * archived conversations out of the list is the host's decision, not this element's.\n * The asymmetry between the two inputs is deliberate: `active-id` is an attribute\n * because moving the selection patches the rendered rows in place, while\n * `conversations` is a JS property because it is structured data an attribute cannot\n * carry, and setting it re-renders the whole list.\n *\n * @element aparte-conversation-list\n * @attr {string} active-id - The id of the conversation to render as selected.\n *\n * @fires {CustomEvent<AparteConversationSelectDetail>} aparte-select-conversation - A row was activated; the host loads that conversation.\n * @fires {CustomEvent<AparteConversationDeleteDetail>} aparte-delete-conversation - The delete action was pressed. Nothing is removed here.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-archive-conversation - The archive action was pressed on a live conversation.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-unarchive-conversation - The same action on an already-archived one; same detail shape, opposite intent.\n *\n * @cssprop [--aparte-conv-list-gap=2px] - Vertical gap between rows. The element itself is the flex column, so this is its `gap`.\n * @cssprop [--aparte-conv-item-padding=7px 10px] - Padding of a row.\n * @cssprop [--aparte-conv-item-gap=6px] - Gap between a row's title and its two action buttons.\n * @cssprop [--aparte-conv-item-radius=var(--aparte-radius-md)] - Corner radius of a row.\n * @cssprop [--aparte-conv-item-font-size=0.8125rem] - Font size of a row's title.\n * @cssprop [--aparte-conv-item-color=var(--aparte-text-muted)] - Title colour of an inactive row.\n * @cssprop [--aparte-conv-item-bg-hover=var(--aparte-surface-3)] - Row background on hover.\n * @cssprop [--aparte-conv-item-bg-active=var(--aparte-surface-3)] - Background of the row matching `active-id`.\n * @cssprop [--aparte-conv-item-color-active=var(--aparte-text)] - Title colour of the active row.\n * @cssprop [--aparte-conv-item-font-weight-active=var(--aparte-font-weight-medium, 500)] - Title weight of the active row.\n * @cssprop [--aparte-conv-action-btn-size=20px] - Square size of both action buttons. Under `(pointer: coarse)` the stylesheet redeclares it as 28px on the buttons themselves, so a value set on the element does not reach them there; the buttons also stay visible instead of appearing on hover.\n * @cssprop [--aparte-conv-delete-color=var(--aparte-text-muted)] - Icon colour of the delete button.\n * @cssprop [--aparte-conv-delete-bg-hover=var(--aparte-error)] - Delete button background on hover.\n * @cssprop [--aparte-conv-delete-color-hover=var(--aparte-text-inverse)] - Delete button icon colour on hover.\n * @cssprop [--aparte-conv-delete-radius=var(--aparte-radius-sm)] - Corner radius of the delete button.\n * @cssprop [--aparte-conv-archive-color=var(--aparte-text-muted)] - Icon colour of the archive/unarchive button.\n * @cssprop [--aparte-conv-archive-bg-hover=var(--aparte-surface-4, var(--aparte-surface-3))] - Archive button background on hover. Core declares no `--aparte-surface-4`, so unset it resolves to `--aparte-surface-3`.\n * @cssprop [--aparte-conv-archive-color-hover=var(--aparte-text)] - Archive button icon colour on hover.\n * @cssprop [--aparte-conv-archive-radius=var(--aparte-radius-sm)] - Corner radius of the archive button.\n *\n * @example\n * <!-- It stores nothing and fetches nothing: an empty tag renders the empty state, and\n * the list appears when the host assigns `conversations`. -->\n * <aparte-conversation-list active-id=\"c1\" style=\"max-width: 20rem\"></aparte-conversation-list>\n *\n * <script>\n * document.querySelector('aparte-conversation-list').conversations = [\n * { id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },\n * { id: 'c2', title: 'Rename the segment types', updatedAt: Date.now() - 864e5 },\n * ];\n * </script>\n *\n * @example\n * // The host owns the data: set the `conversations` property, listen for the intent.\n * const list = document.querySelector('aparte-conversation-list')!;\n * list.conversations = [\n * { id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },\n * { id: 'c2', title: 'Old thread', updatedAt: 0, archivedAt: Date.now() },\n * ];\n * list.setAttribute('active-id', 'c1');\n *\n * list.addEventListener('aparte-select-conversation', (e) => load(e.detail.id));\n * list.addEventListener('aparte-delete-conversation', (e) => remove(e.detail.id));\n */\nexport class AparteConversationList extends HTMLElement {\n private _conversations: AparteConversationListItem[] = [];\n private _activeId: string | null = null;\n\n static get observedAttributes(): string[] {\n return ['active-id'];\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────\n\n connectedCallback(): void {\n if (!this.classList.contains('aparte-conv-list')) {\n this.classList.add('aparte-conv-list');\n }\n if (!this.getAttribute('role')) {\n this.setAttribute('role', 'navigation');\n }\n this._render();\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n if (name === 'active-id') {\n this._activeId = newValue;\n this._updateActiveState();\n }\n }\n\n /**\n * Re-render on a locale switch: every row's title fallback and both button\n * labels come from the locale, so without this the list stayed in the previous\n * language until something else happened to re-render it. Only OUR config.\n */\n private _onConfigChange = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== resolveConfig(this)) return;\n this._render();\n };\n\n // ─── Public API ───────────────────────────────────────────────────────\n\n /** Set the list of conversations to display. Triggers a re-render. */\n set conversations(items: AparteConversationListItem[]) {\n this._conversations = Array.isArray(items) ? items : [];\n this._render();\n }\n\n get conversations(): AparteConversationListItem[] {\n return this._conversations;\n }\n\n // ─── Rendering ────────────────────────────────────────────────────────\n\n private _render(): void {\n this.innerHTML = this._conversations\n .map(conv => this._renderItem(conv))\n .join('');\n this._bindEvents();\n }\n\n private _renderItem(conv: AparteConversationListItem): string {\n const locale = resolveConfig(this).getLocale();\n const isActive = conv.id === this._activeId;\n const isArchived = !!conv.archivedAt;\n const activeClass = isActive ? ' aparte-conv-item--active' : '';\n const archivedClass = isArchived ? ' aparte-conv-item--archived' : '';\n const escapedId = this._esc(conv.id);\n const escapedTitle = this._esc(conv.title || locale.newChat);\n const deleteLabel = this._esc(locale.deleteConversation);\n const archiveLabel = this._esc(locale['archiveConversation'] ?? 'Archive conversation');\n const unarchiveLabel = this._esc(locale['unarchiveConversation'] ?? 'Unarchive conversation');\n const archiveAction = isArchived ? 'unarchive' : 'archive';\n const archiveAriaLabel = isArchived ? unarchiveLabel : archiveLabel;\n // Distinct icons: a downward tray for archive, an upward tray for unarchive.\n // Marked at the declaration because the use site is inside a multi-line template\n // literal, where a `//` would render as text rather than exempt anything.\n const archiveGlyph = resolveConfig(this).getIcon(isArchived ? 'unarchive' : 'archive'); // safe-text: the icon provider's SVG — markup by contract, which is what getIcon returns everywhere in core.\n return `\n<div\n class=\"aparte-menu__item aparte-conv-item${activeClass}${archivedClass}\"\n role=\"button\"\n tabindex=\"0\"\n data-conv-id=\"${escapedId}\"\n aria-current=\"${isActive ? 'page' : 'false'}\"\n>\n <span class=\"aparte-conv-item__title\">${escapedTitle}</span>\n <button\n class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__archive\"\n type=\"button\"\n data-archive-id=\"${escapedId}\"\n data-archive-action=\"${escapeAttr(archiveAction)}\"\n aria-label=\"${escapeAttr(archiveAriaLabel)}\"\n tabindex=\"0\"\n >${archiveGlyph}</button>\n <button\n class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__delete\"\n type=\"button\"\n data-delete-id=\"${escapedId}\"\n aria-label=\"${deleteLabel}\"\n tabindex=\"0\"\n >\n ${resolveConfig(this).getIcon('close')}\n </button>\n</div>`;\n }\n\n private _bindEvents(): void {\n this.addEventListener('click', this._onClick);\n this.addEventListener('keydown', this._onKeydown);\n }\n\n private _onClick = (e: Event): void => {\n const target = e.target as HTMLElement;\n const archiveBtn = target.closest('[data-archive-id]') as HTMLElement | null;\n if (archiveBtn) {\n e.stopPropagation();\n const id = archiveBtn.dataset['archiveId']!;\n const action = archiveBtn.dataset['archiveAction'];\n const eventName = action === 'unarchive'\n ? 'aparte-unarchive-conversation'\n : 'aparte-archive-conversation';\n this.dispatchEvent(new CustomEvent<AparteConversationArchiveDetail>(\n eventName,\n { detail: { id }, bubbles: true, composed: true }\n ));\n return;\n }\n const deleteBtn = target.closest('[data-delete-id]') as HTMLElement | null;\n if (deleteBtn) {\n e.stopPropagation();\n const id = deleteBtn.dataset['deleteId']!;\n this.dispatchEvent(new CustomEvent<AparteConversationDeleteDetail>(\n 'aparte-delete-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n return;\n }\n const item = target.closest('[data-conv-id]') as HTMLElement | null;\n if (item) {\n const id = item.dataset['convId']!;\n this.dispatchEvent(new CustomEvent<AparteConversationSelectDetail>(\n 'aparte-select-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n }\n };\n\n private _onKeydown = (e: KeyboardEvent): void => {\n if (e.key !== 'Enter' && e.key !== ' ') return;\n const target = e.target as HTMLElement;\n /*\n * ONLY the row, and that word is the whole fix.\n *\n * The row is a `role=\"button\"` div, so Enter and Space do nothing on their own and\n * this handler supplies them. The archive and delete controls inside it are real\n * `<button>`s, which already activate on both keys — but this used to reach for\n * `closest('[data-conv-id]')` from whatever was focused, so pressing Enter on\n * Delete found the ROW, called preventDefault() (cancelling the button's own\n * activation) and clicked the row instead. Keyboard users could not archive or\n * delete a conversation at all: both keys selected it.\n *\n * Matching instead of climbing keeps the synthetic activation on the one element\n * that lacks a native one, and leaves every real control alone.\n */\n if (!target.matches('[data-conv-id]')) return;\n e.preventDefault();\n target.click();\n };\n\n /** Update active class without full re-render (perf optimisation). */\n private _updateActiveState(): void {\n const items = this.querySelectorAll<HTMLElement>('[data-conv-id]');\n items.forEach(el => {\n const isActive = el.dataset['convId'] === this._activeId;\n el.classList.toggle('aparte-conv-item--active', isActive);\n el.setAttribute('aria-current', isActive ? 'page' : 'false');\n });\n }\n\n // ─── Helpers ──────────────────────────────────────────────────────────\n\n private _esc(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/'/g, ''');\n }\n}\n\nif (!customElements.get('aparte-conversation-list')) customElements.define('aparte-conversation-list', AparteConversationList);\n","/**\n * Aparte\n * High-performance AI conversation engine in Vanilla TypeScript\n * Zero-dependency Web Components for LLM streaming\n *\n * ⚠️ This is the **browser** entry: it defines the custom elements and imports CSS\n * at module scope, so it needs a DOM. **Node resolves `index.node.ts` instead**\n * (via the `node` condition in package.json) — a DOM-free entry with the client,\n * host, transports, the chat handler and every type. This file sits first in the\n * exports map only because of the repo-local `@aparte-workspace/source` condition,\n * which is why reading it can look like \"this package can't run in Node\".\n * See the \"Node / SSR\" section of the README.\n *\n * @packageDocumentation\n */\nimport './styles/theme.css';\nimport './styles/base.css';\nimport './styles/button.css';\nimport './styles/field.css';\nimport './styles/display/avatar.css';\nimport './styles/display/icon.css';\nimport './styles/display/badge.css';\nimport './styles/display/tag.css';\nimport './styles/display/thumbnail.css';\nimport './styles/display/spinner.css';\nimport './styles/display/progress.css';\nimport './styles/display/skeleton.css';\nimport './styles/display/divider.css';\nimport './styles/display/alert.css';\nimport './styles/display/card.css';\nimport './styles/display/kbd.css';\nimport './styles/surface/tabs.css';\nimport './styles/surface/accordion.css';\nimport './styles/surface/menu.css';\nimport './styles/surface/popover.css';\nimport './styles/surface/tooltip.css';\nimport './styles/primitives/select.css';\nimport './styles/primitives/progress-spinner.css';\nimport './styles/components/shell.css';\nimport './styles/components/bubble.css';\nimport './styles/components/composer.css';\nimport './styles/segment/thinking.css';\nimport './styles/segment/code.css';\nimport './styles/segment/tool-call.css';\nimport './styles/segment/error.css';\nimport './styles/segment/pipeline.css';\nimport './styles/segment/text.css';\nimport './styles/segment/artifact.css';\nimport './styles/components/elicitation.css';\nimport './styles/components/conversation.css';\nimport './styles/prose.css';\nimport './styles/responsive.css';\n\n// Global HTMLElementEventMap augmentation — typed `e.detail` for aparté events.\nimport './types/event-map.js';\n// Global HTMLElementTagNameMap augmentation — `querySelector('aparte-…')` returns\n// the concrete element, not `Element`. Both are DOM-only, hence browser-entry only.\nimport './types/element-map.js';\n\n// Export primitives\nexport { AparteSelect, AparteOption, AparteOptgroup, type AparteSelectChangeDetail, type AparteOptgroupToggleEventDetail, AparteProgressSpinner, AparteIcon } from './primitives/index.js';\n\n// Export types\nexport type {\n AparteBubbleRole,\n AparteMessage,\n AparteContentParser,\n AparteSendEventDetail,\n AparteViewportConfig,\n AparteInputConfig,\n AparteThemeVariables,\n AparteStatus,\n AparteAttachment,\n AparteMessageBranch,\n AparteBubbleActionsConfig,\n AparteBubbleActionName,\n AparteSegment,\n AparteSegmentType,\n AparteTextSegment,\n AparteThinkingSegment,\n AparteCodeSegment,\n AparteSegmentRenderer,\n AparteCustomSegment,\n AparteToolCallSegment,\n AparteArtifactSegment,\n // Five shapes that were public in everything but name. `AparteSegment` is exported\n // and its union names all eight members, yet two of them — the error segment and the\n // pipeline indicator — could not be written down: narrowing on `type: 'error'` gave a\n // consumer the shape and no way to declare a variable of it. `AparteSegmentBase` is\n // worse than an omission: it is the CONSTRAINT on the exported\n // `AparteSegmentRenderer<T>`, so writing a renderer for a segment type of your own\n // required naming a type the package does not export. `AparteSegmentTiming` types\n // `meta.aparte`, and `AparteSegmentDefaults` types what `setSegmentDefaults` takes.\n AparteSegmentBase,\n AparteSegmentDefaults,\n AparteSegmentTiming,\n AparteErrorSegment,\n ApartePipelineWaitingSegment,\n // The detail of the `aparte-segment-update` event. It reached types/index.ts and\n // stopped there — and types/index.ts is not an entry point, so a consumer could\n // bind the event (it is in the published event table) and never name its detail.\n AparteSegmentUpdateEventDetail,\n // AI Provider types (BYORK)\n AparteAIProvider,\n AparteAIModel,\n AparteAIProviderConfigField,\n AparteAIProviderConfigSchema,\n AparteModelConfig,\n ModelStatus,\n ModelLoadProgress,\n AparteModelChangeEventDetail,\n AparteMessageDoneEventDetail,\n AparteMessageStartEventDetail,\n AparteMessageErrorEventDetail,\n AparteMessageAbortedEventDetail,\n AparteAbortEventDetail,\n AparteCompactEventDetail,\n AparteCompactDoneEventDetail,\n AparteCompactErrorEventDetail,\n AparteAttachmentPreviewEventDetail,\n AparteFileGenReadyEventDetail,\n AparteFileGenErrorEventDetail,\n AparteMessageInfoEventDetail,\n AparteSiblingInfo,\n AparteBranchNavigateEventDetail,\n ApartePathChangedEventDetail,\n AparteRetryEventDetail,\n AparteEditEventDetail,\n AparteFeedbackEventDetail,\n AparteActionEventDetail,\n AparteArtifactStartEventDetail,\n AparteArtifactDeltaEventDetail,\n AparteArtifactReadyEventDetail,\n AparteArtifactRedownloadEventDetail,\n // Chat types\n AparteChatRequest,\n AparteChatResponse,\n AparteChatMessage,\n AparteContentPart,\n AparteTextPart,\n AparteImagePart,\n AparteFilePart,\n AparteStreamEvent,\n AparteStreamEventMap,\n AparteUsage,\n // Tool types\n AparteTool,\n AparteToolCall,\n AparteToolResult,\n AparteToolHandler,\n AparteToolContext,\n AparteToolRenderer,\n AparteToolApprovalRequestDetail,\n // Canonical imperative surface (aliased by every wrapper's handle type).\n AparteChatImperativeApi,\n // The attribute surface of every element, for the wrappers to map over.\n AparteElementAttributes,\n AparteElementTagName,\n AparteAttrValue,\n AparteTemplateAttrs,\n AparteNoAttributes,\n AparteChatAttributes,\n AparteChatViewportAttributes,\n AparteChatBubbleAttributes,\n AparteChatStatusAttributes,\n AparteComposerAttributes,\n AparteComposerInputAttributes,\n AparteComposerActionAttributes,\n AparteComposerAddAttachmentAttributes,\n AparteComposerToolbarAttributes,\n AparteConversationListAttributes,\n AparteSelectAttributes,\n AparteOptionAttributes,\n AparteOptgroupAttributes,\n AparteProgressSpinnerAttributes,\n} from './types/index.js';\n\nexport { AparteErrorCode, AparteError, contentToText } from './types/index.js';\n\n// Export renderers\nexport {\n registerSegmentRenderer,\n unregisterSegmentRenderer,\n getSegmentRenderer,\n collectRendererStyles,\n registerDefaultRenderers,\n // The three the public barrel left behind. `renderers/index.ts` has always\n // exported all eight; this one published five, which made the registry\n // half-public: `declineDefaultRenderers` is the ONLY way to say \"do not install\n // the built-ins on this config\" without constructing an `AparteClient`\n // (`autoRegister: false`), and the bring-your-own-loop guide tells you not to\n // construct one. `installDefaultRenderersOnce` is what a hand-written bubble\n // needs, and `getAllRenderers` is the introspection half — the same reason\n // `hasHighlightProvider` and `renderMarkdown` are public.\n installDefaultRenderersOnce,\n declineDefaultRenderers,\n getAllRenderers\n} from './renderers/index.js';\n\n// Export components\nexport { AparteChat } from './components/index.js';\nexport { AparteChatBubble, populateBubbleFromMessage } from './components/index.js';\nexport type { SyncableBubble } from './components/index.js';\nexport { AparteChatStatus } from './components/index.js';\nexport { AparteChatViewport } from './components/index.js';\n\n// Export composer primitives\nexport { AparteComposer, AparteComposerInput, AparteComposerSend, AparteComposerCancel, AparteComposerAttachments, AparteComposerAddAttachment, AparteComposerAction, AparteComposerToolbar } from './components/index.js';\nexport type { AparteComposerEventMap, AparteComposerEventType, AparteComposerState, AparteComposerChangeEventDetail, AparteComposerPanelMode, AparteActionClickEventDetail } from './components/index.js';\n\n// Export conversation list primitive\nexport { AparteConversationList } from './components/index.js';\nexport type { AparteConversationListItem, AparteConversationSelectDetail, AparteConversationDeleteDetail, AparteConversationArchiveDetail } from './components/index.js';\n\n// Export conversations (types, adapter contract, manager)\nexport type {\n AparteConversation,\n AparteConversationMeta,\n AparteStorageAdapter,\n AparteMemoryFact,\n AparteArtifactRow,\n AparteAttachmentRow,\n} from './conversations/index.js';\nexport { APARTE_CONVERSATION_SCHEMA_VERSION } from './conversations/index.js';\nexport { AparteConversationManager, type ConversationManagerOptions } from './conversations/index.js';\nexport {\n AparteConversationController,\n type AparteChatBinding,\n type AparteConversationControllerOptions,\n} from './conversations/index.js';\n\n// Export the framework-agnostic chat-host orchestrator (streaming/branch/\n// host-method layer that every framework wrapper binds to).\nexport {\n AparteChatHost,\n type AparteChatHostBinding,\n type AparteChatHostOptions,\n} from './host/index.js';\n\n// Export parsers\nexport { AparteStreamParser, parseMarkdownToSegments, deriveArtifactKind } from './parsers/index.js';\nexport type { AparteStreamParserOptions, AparteThinkingDelimiterPair, AparteParserState, AparteParserResult } from './parsers/index.js';\nexport { parseAparteEventStream } from './parsers/index.js';\n\n// Export config\nexport { aparteGlobalConfig, AparteConfig, APARTE_DEFAULT_BUBBLE_ACTIONS, APARTE_DEFAULT_HOST_HANDLERS } from './config/index.js';\nexport type { AparteConfigChangeEventDetail } from './config/index.js';\nexport { resolveConfig, attachConfig, detachConfig, runWithConfig, contextConfig, APARTE_HOST_ATTR } from './config/index.js';\nexport { subscribeConfigChange, APARTE_CONFIG_CHANGE } from './config/index.js';\nexport type { AparteConfigAware } from './config/index.js';\nexport type { AparteMarkdownProvider, AparteStreamingMarkdownProvider, AparteStreamingMarkdownRenderer, AparteHighlightProvider, AparteSystemPromptVarsProvider, AparteSkeletonProvider, AparteSkeletonType, AparteLocale, AparteAction, AparteActionZone, AparteIconProvider, AparteIconName, AparteAvatarProvider, AparteStatusRenderer, AparteErrorRenderer, AparteAttachmentRenderer, AparteElicitationFieldRenderer, AparteElicitationFieldContext, AparteElicitationFieldControl, AparteSiblingNavRenderer, AparteBubbleShellRenderer, AparteModelPreference, AparteModelPreferenceProvider, AparteArtifactPreviewBuilder, AparteSanitizer } from './config/index.js';\nexport { APARTE_DEFAULT_ICON_FALLBACKS, APARTE_DEFAULT_SKELETON_FALLBACKS, APARTE_DEFAULT_LOCALE, defaultSanitizer, isSafeUrl } from './config/index.js';\n\n// Export Client\nexport { AparteClient } from './client/aparte-client.js';\n\n// Custom-element interop helpers shared by the framework wrappers' AparteUi.\nexport { applyElementProps, APARTE_DEFAULT_UI_EVENTS } from './interop/element-props.js';\nexport type { AparteUiEventName } from './interop/element-props.js';\n// Turns the `File[]` an `aparte-send` carries into renderable attachments — the\n// same conversion ConversationController does, for consumers driving the\n// imperative API themselves.\nexport { filesToAttachments, revokeAttachmentUrls } from './utils/files-to-attachments.js';\n// Is a message waiting for a reply? Shared by the viewport, the four wrappers and\n// any consumer rendering its own bubble — one rule, so they can't disagree.\nexport { isAwaitingReply } from './utils/is-awaiting-reply.js';\n\n// HTML escaping — one implementation for the whole scope. Exported because the\n// plugins render their own HTML (they cannot reach into core's internals) and\n// because a consumer writing a render hook needs it for exactly the same reason.\n// Nine private copies existed before this line; three of them had drifted to\n// escape only four of the five characters that matter.\nexport { escapeHtml, escapeAttr } from './utils/escape.js';\n// `cssEscape` belongs beside them: `pnpm check:attr-escaping` tells a renderer\n// author \"in a selector, use cssEscape()\", and the customization guide says the\n// same — while it was not exported at all, so the only way to follow that advice\n// was `CSS.escape`, which over-escapes inside a quoted attribute selector.\nexport { cssEscape } from './utils/css-escape.js';\n// Exported because the same wall is hit outside core: a wrapper naming its host\n// element, a provider tagging a request, or any bring-your-own-loop consumer\n// generating message ids all reach for `crypto.randomUUID`, which does not exist\n// on `http://` — the LAN deployment this library's own audience runs.\nexport { uuid } from './utils/uuid.js';\n// A segment's own completion rule, and the two readers of what core measured.\n// Exported because a consumer rendering \"thought for 8 s\" needs to know when the span\n// closed, and a rule kept private is a rule re-derived slightly differently outside —\n// the tool call is the trap: it settles by `status`, never by `isStreaming`.\n//\n// `segmentTiming` joins them because the measurements moved into `meta.aparte`, and\n// `segment.meta?.aparte` spelled at each call site is the same rule re-derived by hand\n// — exactly what the other two are exported to prevent.\n//\n// The WRITERS stay internal: only the two owners of a message's segment array may\n// stamp those fields, which `pnpm check:segment-stamp` enforces.\nexport { isSegmentSettled, segmentDuration, segmentTiming } from './utils/segments.js';\n// The PARAMETER types of two documented setters. They existed and were the declared\n// argument types, but were not exported — so anyone typing a settings layer over\n// `setHostHandlers` / `setKeyProvider` had to re-declare the shape by hand.\nexport type { AparteHostHandlersConfig } from './types/models.js';\nexport type { AparteKeyProvider } from './config/aparte-config.js';\nexport type { AparteClientOptions, AparteToolApprovalResolver, AparteCompactionSelector } from './client/aparte-client.js';\n// Structured-stream adapter — DOM half of the runStreamAgent loop (see stream-adapter.ts).\nexport { createStreamAdapter, readableToAsyncIterable } from './client/stream-adapter.js';\nexport type { AparteStreamRunEvent, AparteStreamRunEmitter, StreamAdapterTarget, CreateStreamAdapterOptions, AparteStreamRunner, AparteStreamRunOptions } from './client/stream-adapter.js';\n\n// Export transport seam (where chat requests go + how auth is handled)\nexport { AparteDirectTransport, AparteBackendTransport, createAparteChatHandler, isFormatAdapter } from './transport/index.js';\nexport type { AparteTransport, AparteTransportContext, AparteFormatAdapter, AparteVendorRequest, BackendTransportOptions, DirectTransportOptions, AparteChatHandlerOptions } from './transport/index.js';\n\n// Export runtime utilities\nexport { AparteMessageRepository } from './runtime/message-repository.js';\nexport type { ExportedMessageRepository } from './runtime/message-repository.js';\n\n// Export elicitation (human-in-the-loop typed input)\nexport { requestUserInput, buildElicitationPanel, buildApprovalPanel, AparteElicitationAbortError } from './elicitation/index.js';\nexport type {\n AparteElicitationSchema,\n AparteElicitationField,\n AparteElicitationEnumField,\n AparteElicitationBooleanField,\n AparteElicitationStringField,\n AparteElicitationObjectSchema,\n AparteElicitationRequest,\n AparteElicitationResult,\n AparteElicitationPresenter,\n AparteApprovalOption,\n AparteApprovalAnswer,\n BuiltApprovalPanel,\n BuiltElicitationPanel,\n} from './elicitation/index.js';\n\n// Export the default elicitation presenter Web Component\nexport { AparteElicitation } from './components/elicitation/aparte-elicitation.js';\n\n// Auto-register components when module is imported\n// Components register themselves in their files\nimport './components/chat/aparte-chat.js';\nimport './components/bubble/aparte-chat-bubble.js';\nimport './components/status/aparte-chat-status.js';\nimport './components/viewport/aparte-chat-viewport.js';\nimport './components/elicitation/aparte-elicitation.js';\n// Import primitives to auto-register\nimport './primitives/select/aparte-select.js';\nimport './primitives/select/aparte-option.js';\nimport './primitives/select/aparte-optgroup.js';\n\n/**\n * Utility to ensure all components are registered\n * Call this if using dynamic imports\n */\nexport function registerAllComponents(): void {\n // Components self-register, but this ensures imports are not tree-shaken\n const _chat = customElements.get('aparte-chat');\n const _viewport = customElements.get('aparte-chat-viewport');\n const _bubble = customElements.get('aparte-chat-bubble');\n const _status = customElements.get('aparte-chat-status');\n\n if (!_chat || !_viewport || !_bubble || !_status) {\n console.warn('[Aparte] Some components may not be registered. Ensure all component files are imported.');\n }\n}\n"],"names":["n","e","o","panel","a","l","b","c","m","i","f","h"],"mappings":"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"}
|