@aparte/core 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/aparte-client.d.ts +5 -22
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +16 -0
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +10 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +66 -19
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/error-renderer.d.ts +3 -1
- package/dist/config/error-renderer.d.ts.map +1 -1
- package/dist/config/locale.d.ts +45 -4
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/config/tool-table.d.ts +28 -0
- package/dist/config/tool-table.d.ts.map +1 -0
- package/dist/custom-elements.json +11043 -10524
- package/dist/{dialog-triggers-3Rk7PuZa.js → dialog-triggers-3G9TqG9g.js} +473 -165
- package/dist/dialog-triggers-3G9TqG9g.js.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -16
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +2 -1
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +4 -3
- package/dist/index.node.js.map +1 -1
- package/dist/renderers/copy-button.d.ts +12 -0
- package/dist/renderers/copy-button.d.ts.map +1 -0
- package/dist/renderers/segment-renderers.d.ts +6 -3
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/code.d.ts.map +1 -1
- package/dist/renderers/segments/error.d.ts.map +1 -1
- package/dist/renderers/segments/thinking.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/transport/agent-handler.d.ts.map +1 -1
- package/dist/types/errors.d.ts +2 -0
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/segments.d.ts +42 -2
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/tools.d.ts +38 -0
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/format-duration.d.ts +9 -0
- package/dist/utils/format-duration.d.ts.map +1 -0
- package/dist/utils/tool-line.d.ts +23 -0
- package/dist/utils/tool-line.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/dialog-triggers-3Rk7PuZa.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/utils/presence.ts","../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/utils/transcript.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/utils/fold-text.ts","../src/components/conversation-list/aparte-conversation-list.ts","../src/components/suggestions/aparte-suggestions.ts","../src/components/context/aparte-context.ts","../src/components/scroll-rail/aparte-scroll-rail.ts","../src/components/sidebar/aparte-sidebar.ts","../src/components/split/geometry.ts","../src/components/split/aparte-split.ts","../src/index.ts"],"sourcesContent":["/**\n * What a presence-attribute SETTER makes of what it was handed.\n *\n * The generated attribute types map a presence attribute to `'' | null | undefined`,\n * because React and Vue stringify what they set on a custom element — so `''` is the\n * documented spelling for ON. Svelte 5 does not take the attribute path: it assigns\n * the PROPERTY whenever the element has one, which hands these setters the `''` the\n * types promise — and `toggleAttribute(name, '')` reads an empty string as falsy, so\n * the attribute would be REMOVED, the opposite of what the template asked for.\n *\n * One rule serves both paths without forking the spelling: on a presence property, an\n * empty string means ON, exactly as an empty attribute does. Everything else keeps\n * JavaScript's own truthiness (`false`, `null`, `undefined` → OFF).\n */\nexport function presenceOn(value: unknown): boolean {\n return value === '' ? true : Boolean(value);\n}\n","// Imports stay ABOVE the docblock: one between the comment and the class detaches the\n// comment — TypeScript stops associating it, the manifest loses the `@example`, and\n// `check:examples` refuses the build. It happened here.\nimport { presenceOn } from '../../utils/presence.js';\n\n/**\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 trigger reads the option's text without\n * the status dot and without an `.aparte-option__meta` child, the optional line under the\n * label (a description); its search filter reads the full `textContent`, that line included.\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)] - Option text colour.\n * @cssprop [--aparte-select-option-hover=var(--aparte-surface-2)] - 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) var(--aparte-mark-tint), 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))] - 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 */\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 this.toggleAttribute('disabled', presenceOn(val));\n }\n\n get selected(): boolean {\n return this.hasAttribute('selected');\n }\n\n set selected(val: boolean) {\n this.toggleAttribute('selected', presenceOn(val));\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';\nimport { presenceOn } from '../../utils/presence.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 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 set collapsible(val: boolean) {\n this.toggleAttribute('collapsible', presenceOn(val));\n }\n\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(val: boolean) {\n this.toggleAttribute('collapsed', presenceOn(val));\n }\n\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(val: boolean) {\n this.toggleAttribute('loading', presenceOn(val));\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 // From the locale: this said \"Fetching models...\" — English whatever the\n // page's language, and the product's word for what a generic group holds.\n const spinner = document.createElement('span');\n spinner.className = 'aparte-spinner-small';\n loader.replaceChildren(spinner, ` ${resolveConfig(this).t('loading') || 'Loading…'}`);\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 { presenceOn } from '../../utils/presence.js';\nimport './aparte-optgroup.js';\nimport { resolveConfig } from '../../config/config-context.js';\nimport { activeElementIn, eventOrigin } from '../../utils/event-target.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 combobox is whichever element holds focus. Without `searchable` that is the\n * trigger. With it, opening focuses the filter field, so the field carries\n * `role=\"combobox\"`, `aria-expanded`, `aria-controls` and the roving\n * `aria-activedescendant`, and the trigger is a `role=\"button\"` — one combobox either\n * way, and always the focused one.\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} searchable - Adds a filter field above the options. Set or removed later, the field comes or goes.\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)] - Trigger background. (The dropdown panel has its own token, `--aparte-select-dropdown-bg`, in both themes.)\n * @cssprop [--aparte-select-border=var(--aparte-border-control)] - Border of the trigger and of the dropdown.\n * @cssprop [--aparte-select-border-hover=var(--aparte-border-control)] - Trigger border on hover.\n * @cssprop [--aparte-radius-select=var(--aparte-radius-md)] - Corner radius of the trigger and the dropdown — the theme's knob, beside `--aparte-radius-input` and the others.\n * @cssprop [--aparte-select-text=var(--aparte-text)] - Colour of the trigger label (and of the options).\n * @cssprop [--aparte-select-chevron=var(--aparte-text-muted)] - Colour of the chevron, which rotates 180° while open.\n * @cssprop [--aparte-select-dropdown-bg=var(--aparte-surface-1)] - Dropdown panel background, in both themes (`--aparte-surface-1` is dark-aware), so a transparent trigger does not make the panel see-through.\n * @cssprop [--aparte-select-shadow=0 1px 3px rgba(0, 0, 0, 0.08), 0 10px 28px rgba(0, 0, 0, 0.16)] - Dropdown panel shadow.\n * @cssprop [--aparte-select-z=var(--aparte-z-dropdown)] - `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) would add\n * another option-click listener and one click would fire the change handler once\n * per re-connect, unless the same bound reference is removed and re-added.\n */\n private _boundHandleOptionClick = this._handleOptionClick.bind(this);\n private _boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n private _boundHandleKeydown = this._handleKeydown.bind(this);\n /**\n * The trigger's own three, bound for the same reason as the option click: the\n * trigger element survives a re-connect, so listeners that cannot be removed would\n * stack. One re-parent would make a click toggle twice — open and shut inside the\n * same click, i.e. a control that looks dead — and announce `aparte-select-open`\n * twice.\n */\n private _boundHandleTriggerClick = (): void => { this._toggle(); };\n private _boundHandleTriggerKeydown = (e: KeyboardEvent): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n // When open, Enter/Space selects the active option — handled by the\n // document-level nav handler. Only toggle when closed, so we don't close the\n // 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 private _boundHandleSearchInput = (e: Event): void => {\n this._filterOptions((e.target as HTMLInputElement).value.toLowerCase());\n };\n\n static get observedAttributes(): string[] {\n return ['value', 'placeholder', 'disabled', 'searchable', 'open'];\n }\n\n connectedCallback(): void {\n this._value = this.getAttribute('value') || '';\n // `open` at mount goes through `_openDropdown()` like every other open, once\n // the structure it needs exists. Rendering \"already open\" instead produced a\n // visible list with `aria-expanded=\"false\"`, no seeded highlight and no event.\n //\n // At MOUNT only. This callback runs again on every re-connect — a portal, a\n // Vue teleport, any framework re-parent, the bug class the bound handlers\n // above document — and an element that never closed has no transition to\n // replay: replaying it fired a second `aparte-select-open` and re-seeded the\n // highlight on the selected option, throwing away where the arrows had got to.\n const wantsOpen = this.hasAttribute('open') && !this._isOpen;\n this._render();\n // A `searchable` set or removed while detached went past attributeChangedCallback.\n this._applySearchable();\n this._applyDisabled();\n this._setupEventListeners();\n this._setupMutationObserver();\n if (wantsOpen) this._openDropdown();\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 // One open path and one close path — the attribute goes through them, so\n // it cannot produce a state a click never produces. Guarded against the\n // re-entry caused by their own reflecting writes: both set `_isOpen`\n // BEFORE touching the attribute, so the nested call finds the two already\n // in agreement and does nothing.\n const wanted = this.hasAttribute('open');\n if (wanted !== this._isOpen) {\n if (wanted) this._openDropdown();\n else this._closeDropdown();\n }\n }\n // Both were observed and neither had a branch here, so the callback fired and\n // did nothing: a placeholder rewritten by a locale switch left the visible\n // label and the combobox's name in the old language, and a select disabled\n // after mount kept a focusable trigger announced as operable.\n if (name === 'placeholder') {\n // The label update carries the names with it — the trigger's own name\n // holds the selected value in the button shape, so the two cannot drift.\n this._updateTriggerLabel();\n }\n if (name === 'searchable') this._applySearchable();\n if (name === 'disabled') {\n this._applyDisabled();\n // The symmetric half of the close `_applyDisabled()` does on the way in:\n // an `open` the select refused while it was disabled is honoured now that\n // it is operable, because that attribute was never taken back.\n if (!this.hasAttribute('disabled') && this.hasAttribute('open')) this._openDropdown();\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 this.toggleAttribute('open', presenceOn(val));\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Rendering\n // ─────────────────────────────────────────────────────────────────────────\n\n /** The filter field: the combobox of a `searchable` select, since it takes the focus. */\n private _buildSearchInput(cfg: ReturnType<typeof resolveConfig>): HTMLInputElement {\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.className = 'aparte-select-search';\n // Two locale keys, not literals. The placeholder is VISIBLE text — a\n // French page opened the model picker and read \"Search...\" in the box —\n // and the aria-label is the field's only name.\n searchInput.placeholder = cfg.t('selectSearchPlaceholder') || 'Search…';\n searchInput.setAttribute('aria-label', cfg.t('selectSearchLabel') || 'Search options');\n // This field takes the focus, so it is the combobox (see the trigger in `_render`).\n searchInput.setAttribute('role', 'combobox');\n searchInput.setAttribute('aria-haspopup', 'listbox');\n searchInput.setAttribute('aria-autocomplete', 'list');\n searchInput.setAttribute('aria-expanded', String(this._isOpen));\n return searchInput;\n }\n\n /**\n * `searchable` set or removed after the first render: the field comes or goes, and the\n * combobox moves with it, so a framework that\n * binds it a beat late — Angular's `[searchable]` inside an `@if` — still gets a field.\n */\n private _applySearchable(): void {\n if (!this._dropdown || !this._trigger) return;\n const wanted = this.hasAttribute('searchable');\n if (wanted === Boolean(this._searchInput)) return;\n if (this._isOpen) this._closeDropdown();\n const listboxId = this._trigger.getAttribute('aria-controls');\n if (wanted) {\n const input = this._buildSearchInput(resolveConfig(this));\n if (listboxId) input.setAttribute('aria-controls', listboxId);\n this._dropdown.prepend(input);\n this._searchInput = input;\n input.addEventListener('input', this._boundHandleSearchInput);\n this._trigger.setAttribute('role', 'button');\n } else {\n this._searchInput!.removeEventListener('input', this._boundHandleSearchInput);\n this._searchInput!.remove();\n this._searchInput = null;\n this._trigger.setAttribute('role', 'combobox');\n }\n // The role decides the name (see `_updateNames`), and on a re-connect nothing else\n // would rename it: the observer is set up after this runs.\n this._updateNames();\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 // Resolved ONCE for this build: the chevron's provider and the search field's\n // two strings all come from it, and three lookups of the same instance is how\n // a component ends up reading two different configs in one render.\n const cfg = resolveConfig(this);\n\n // Create wrapper structure\n const trigger = document.createElement('div');\n trigger.className = 'aparte-select-trigger';\n trigger.setAttribute('tabindex', '0');\n // The combobox goes on whatever element ends up holding focus. A searchable\n // select focuses its filter field the moment the dropdown opens, so the field\n // is the combobox there and the trigger is the button that summons it —\n // otherwise the roving `aria-activedescendant` sits on an unfocused node and\n // is announced to nobody, and the control declares two comboboxes for one\n // value.\n trigger.setAttribute('role', searchable ? 'button' : '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 // Two layers in one grid cell: the text that shows, and a hidden stack of every\n // option's label. The stack is what gives the control its width — the widest\n // option's — so the trigger does not resize with each selection, which is the\n // one thing a native <select> gets right and this one used to get wrong.\n const labelText = document.createElement('span');\n labelText.className = 'aparte-select-label-text';\n labelText.textContent = placeholder;\n const sizer = document.createElement('span');\n sizer.className = 'aparte-select-label-sizer';\n sizer.setAttribute('aria-hidden', 'true');\n labelSpan.append(labelText, sizer);\n const chevronSpan = document.createElement('span');\n chevronSpan.className = 'aparte-select-chevron';\n chevronSpan.innerHTML = cfg.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 const searchInput = searchable ? this._buildSearchInput(cfg) : null;\n if (searchInput) dropdown.appendChild(searchInput);\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 searchInput?.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 // The previous one first: this is called from `_render()` AND from\n // `connectedCallback`, so assigning over the field left an orphan observing from\n // the very first mount, plus one per re-connect. `_updateDropdownContent` silences\n // `this._observer` before its own writes — only the newest — so the orphans woke\n // on core's own rebuilds, which is precisely what the comment below says cannot\n // happen.\n this._observer?.disconnect();\n this._observer = new MutationObserver(() => {\n this._updateDropdownContent();\n // A list refreshed IN PLACE (a consumer writing into `.aparte-select-options`,\n // as the model selector does when providers settle) leaves nothing to move,\n // so the call above returns before it reaches the label — and the trigger\n // kept showing a label the list no longer offered, with a width stack to\n // match. The label follows every change; its writes are guarded so this\n // observer does not wake itself.\n this._updateTriggerLabel();\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: without this the highlight\n * disappears, `aria-activedescendant` points at an id no longer in the document, and\n * the next ArrowDown moves 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 // Removed first, every one of them: this runs on every connect and the elements\n // it binds to survive a re-connect (`_render` keeps the structure it already\n // built). Adding the same bound reference twice is a no-op per spec — the remove\n // is what makes that true and survives a future refactor that rebinds.\n this._trigger?.removeEventListener('click', this._boundHandleTriggerClick);\n this._trigger?.addEventListener('click', this._boundHandleTriggerClick);\n\n this._trigger?.removeEventListener('keydown', this._boundHandleTriggerKeydown);\n this._trigger?.addEventListener('keydown', this._boundHandleTriggerKeydown);\n\n // Option selection.\n this.removeEventListener('click', this._boundHandleOptionClick);\n this.addEventListener('click', this._boundHandleOptionClick);\n\n // Search filter\n this._searchInput?.removeEventListener('input', this._boundHandleSearchInput);\n this._searchInput?.addEventListener('input', this._boundHandleSearchInput);\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 // `eventOrigin`, not `e.target`: this listens on `document`, so a select inside a\n // consumer's shadow root saw the shadow HOST as the target — never itself — and\n // shut its own dropdown on the very click that opened it.\n const origin = eventOrigin(e) as Node | null;\n if (!origin || !this.contains(origin)) {\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 // `activeElementIn`: inside a consumer's shadow root `document.activeElement` is\n // the shadow host, and Home would hijack the caret out of the filter field.\n const inSearch = activeElementIn(this) === 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 /**\n * The element that holds DOM focus while the list is being driven, and therefore\n * the one that must carry the combobox state a reader follows: the filter field\n * when the select is `searchable` (it is focused on open), the trigger otherwise.\n */\n private _activeHost(): HTMLElement | null {\n return this._searchInput ?? this._trigger;\n }\n\n /** `aria-expanded` belongs to the combobox — write it wherever the combobox is. */\n private _setExpanded(open: boolean): void {\n const flag = String(open);\n this._trigger?.setAttribute('aria-expanded', flag);\n this._searchInput?.setAttribute('aria-expanded', flag);\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._activeHost()?.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._activeHost()?.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._activeHost()?.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 // Already open is not a transition. One guard here makes every entry\n // idempotent — the attribute, the property, a click, a re-connect — instead\n // of each caller carrying its own.\n if (this._isOpen) return;\n // A disabled select does not open — and the `open` attribute stays exactly as\n // the caller wrote it. It is the CONSUMER's: a one-way binding writes it once\n // and never again, so taking it back leaves the template saying open and the\n // element saying closed, with no write left to reconcile them. The attribute\n // stands and `attributeChangedCallback` honours it the moment `disabled` goes.\n if (this.hasAttribute('disabled')) return;\n\n this._isOpen = true;\n this._dropdown?.removeAttribute('hidden');\n this._setExpanded(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._setExpanded(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 /**\n * The combobox and its listbox are named by the host's `aria-label`, else by the\n * placeholder — the same rule the first render applies, re-applied whenever the\n * placeholder or the selection changes. Written only when different: the mutation\n * observer watches this subtree.\n *\n * A `combobox` is NAMED by that attribute and VALUED by its visible label span, so\n * the name is the control and nothing else. A `button` — the shape a `searchable`\n * trigger takes, since the filter field is then the combobox — is named by its\n * CONTENT, which this very attribute overrides: a name of just the placeholder\n * (\"Pick a model, button\") would never say which model is selected. So the button's\n * name carries both halves, and drops the second when it would only repeat the\n * first (nothing selected: the value IS the placeholder).\n */\n private _updateNames(): void {\n const placeholder = this.getAttribute('placeholder') || 'Select...';\n const name = this.getAttribute('aria-label') || placeholder;\n const list = this.querySelector('.aparte-select-options');\n if (list && list.getAttribute('aria-label') !== name) list.setAttribute('aria-label', name);\n\n const trigger = this._trigger;\n if (!trigger) return;\n const value = this._getSelectedLabel() || placeholder;\n const wanted = trigger.getAttribute('role') === 'button' && value !== name ? `${name}: ${value}` : name;\n if (trigger.getAttribute('aria-label') !== wanted) trigger.setAttribute('aria-label', wanted);\n }\n\n /**\n * `disabled` takes the trigger out of the tab order and says so — opacity alone\n * left it focusable and announced as operable. An open dropdown closes.\n */\n private _applyDisabled(): void {\n const trigger = this._trigger;\n if (!trigger) return;\n const disabled = this.hasAttribute('disabled');\n trigger.setAttribute('tabindex', disabled ? '-1' : '0');\n if (disabled) {\n trigger.setAttribute('aria-disabled', 'true');\n if (this._isOpen) this._closeDropdown();\n } else {\n trigger.removeAttribute('aria-disabled');\n }\n }\n\n private _updateTriggerLabel(): void {\n // Every write below is guarded by a comparison: this runs from the mutation\n // observer, which watches the whole subtree, and an unconditional write to the\n // trigger would wake the observer that called it, forever. Written only when\n // something changed, the second pass finds nothing to do and the loop closes.\n const placeholder = this.getAttribute('placeholder') || 'Select...';\n const labelText = this._trigger?.querySelector('.aparte-select-label-text');\n if (labelText) {\n const next = this._getSelectedLabel() || placeholder;\n if (labelText.textContent !== next) labelText.textContent = next;\n }\n // The width stack — rebuilt here because this runs on every change to the\n // options too, so an option written later (or a list refreshed in place)\n // still counts. `textContent` per span: a label is consumer text, never markup.\n const sizer = this._trigger?.querySelector('.aparte-select-label-sizer');\n if (sizer) {\n const labels = [placeholder, ...Array.from(this.querySelectorAll('aparte-option'), (o) => optionLabelText(o))];\n const current = Array.from(sizer.children, (s) => s.textContent ?? '');\n if (labels.length !== current.length || labels.some((l, i) => l !== current[i])) {\n sizer.replaceChildren(...labels.map((text) => {\n const span = document.createElement('span');\n span.textContent = text;\n return span;\n }));\n }\n }\n\n // The accessible names follow the selection, not only the placeholder: a\n // searchable trigger is a button, and a button's name has to say which option\n // is selected because its content is overridden by that name.\n this._updateNames();\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 optionLabelText(opt);\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\n/**\n * An option's label as the trigger and its width read it: the option's text without the line\n * under it (`.aparte-option__meta` — a description, a model's context window) or the status dot.\n * The search filter still reads the whole text, so a description is searchable.\n */\nfunction optionLabelText(option: Element): string {\n let text = '';\n for (const node of option.childNodes) {\n if (node.nodeType === Node.ELEMENT_NODE && (node as Element).matches('.aparte-option__meta, .aparte-status-dot')) continue;\n text += node.textContent ?? '';\n }\n return text.trim();\n}\n\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=var(--aparte-spinner-size-md)] - Width and height of the element; the SVG fills it.\n * @cssprop [--aparte-spinner-color=currentColor] - Stroke of the filled (progress) arc.\n * @cssprop [--aparte-spinner-track=var(--aparte-border-control)] - 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';\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=calc(1rem * var(--aparte-font-scale))] - 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).getIcon(name) : 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, through the typed presenter contract rather than window events.\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 * and rejecting with an `AparteElicitationAbortError` when the turn is stopped or\n * errors while the question is open, or when the panel is evicted.\n *\n * Its CSS ships in `@aparte/core/styles.css` like every other component, so it can be\n * themed and its variables appear in 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, so a parameter added to the\n * real `setPanelSubmitEnabled` is followed here instead of leaving a stale twin.\n * `import type` is erased, so nothing here pulls the composer element into a runtime\n * import.\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 for the whole request, whatever its kind: a question's strings and\n * an approval's live in different places, and a field per kind is how the two\n * would drift.\n */\n relabel(): void;\n}\n\n\n/** The element that really holds the focus: through open shadow roots, down to the field. */\nfunction deepActiveElement(root: Document | ShadowRoot): Element | null {\n let active = root.activeElement;\n while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;\n return active;\n}\n\n/** `ancestor.contains(node)`, across shadow roots: a field inside a component inside the chat is in the chat. */\nfunction isWithin(node: Node, ancestor: Node): boolean {\n for (let n: Node | null = node; n; n = n.parentNode ?? (n instanceof ShadowRoot ? n.host : null)) {\n if (n === ancestor) return true;\n }\n return false;\n}\n\n/**\n * A way to put an open panel aside and read the transcript behind it — the same for a\n * question and an approval, since both open here. Hiding leaves a one-line bar that says an\n * answer is waiting and brings the panel back; the request stays open either way, and the\n * composer's editor stays hidden, because the question still waits for its answer. While it is\n * aside the composer's button has no act — what is out of sight cannot be sent — so `onToggle`\n * lets the caller recompute it on both moves.\n *\n * The hide button goes FIRST in the corner, before a question's Skip, so the two never\n * swap places between a question and an approval.\n */\nfunction addRetract(panelEl: HTMLElement, corner: HTMLElement, cfg: AparteConfig, reopen: () => void, onToggle: () => void): { relabel(next: AparteConfig): void } {\n const hide = document.createElement('button');\n hide.type = 'button';\n hide.className = 'aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--ghost aparte-elic-hide';\n hide.setAttribute('aria-expanded', 'true');\n hide.innerHTML = cfg.getIcon('expand');\n\n const reveal = document.createElement('button');\n reveal.type = 'button';\n reveal.className = 'aparte-btn aparte-btn--ghost aparte-elic-reveal';\n reveal.setAttribute('aria-expanded', 'false');\n const revealText = document.createElement('span');\n reveal.append(revealText);\n reveal.insertAdjacentHTML('beforeend', cfg.getIcon('collapse'));\n\n const relabel = (next: AparteConfig): void => {\n const label = next.t('elicitationHide');\n hide.setAttribute('aria-label', label);\n hide.title = label;\n revealText.textContent = next.t('elicitationWaiting');\n };\n relabel(cfg);\n\n hide.addEventListener('click', () => {\n panelEl.setAttribute('data-collapsed', '');\n onToggle();\n reveal.focus();\n });\n reveal.addEventListener('click', () => {\n panelEl.removeAttribute('data-collapsed');\n onToggle();\n reopen();\n });\n corner.prepend(hide);\n panelEl.append(reveal);\n return { relabel };\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.\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=var(--aparte-space-3)] - Vertical gap between the panel's rows (message, body, tabs).\n * @cssprop [--aparte-elic-padding=var(--aparte-space-3) var(--aparte-space-2)] - 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=var(--aparte-space-4)] - Space and separator padding between two fields of an object schema.\n * @cssprop [--aparte-elic-message-size=var(--aparte-font-size-base)] - Font size of the question text at the top of the panel.\n * @cssprop [--aparte-elic-title-size=var(--aparte-font-size-base)] - Font size of a field's title.\n * @cssprop [--aparte-elic-desc-size=var(--aparte-font-size-sm)] - Font size of a field's description.\n * @cssprop [--aparte-elic-option-padding=var(--aparte-space-4) var(--aparte-space-5)] - Padding of one enum/boolean option row.\n * @cssprop [--aparte-elic-option-radius=var(--aparte-radius-md)] - Corner radius of an option row.\n * @cssprop [--aparte-elic-option-title-size=var(--aparte-font-size-md)] - Font size of an option's label, and of the text inputs.\n * @cssprop [--aparte-elic-option-desc-size=var(--aparte-font-size-sm)] - 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=var(--aparte-radius-md)] - 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=var(--aparte-font-size-md)] - Font size of the corner \"Skip\" affordance (the decline).\n * @cssprop [--aparte-elic-step-size=var(--aparte-font-size-sm)] - Font size of a step tab, when the schema is asked one field at a time.\n * @cssprop [--aparte-elic-step-padding=var(--aparte-space-2) var(--aparte-space-1)] - Padding of a step tab.\n * @cssprop [--aparte-elic-step-gap=var(--aparte-space-7)] - 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=104px] - Space the tab rail and the question message keep clear for the corner: the hide button and, on a question, Skip. Widen it for a locale whose \"Skip\" word is wider.\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 * @cssprop [--aparte-approval-args-max-height=8.5rem] - Height cap of the arguments block before it scrolls.\n * @cssprop [--aparte-approval-args-size=var(--aparte-font-size-sm)] - Font size of the arguments block.\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 style=\"height: 20rem\">\n * <aparte-chat-viewport></aparte-chat-viewport>\n * <aparte-elicitation></aparte-elicitation>\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 * </div>\n * </aparte-composer>\n * </aparte-chat>\n *\n * <script>\n * // `aparte` is the `@aparte/core` module the frame exposes; in your app this line is\n * // `import { requestUserInput } from '@aparte/core'`. A tool handler asks; the panel\n * // mounts in the composer of the chat whose presenter this element is.\n * aparte.requestUserInput({\n * message: 'Which environment should I deploy to?',\n * schema: {\n * type: 'enum',\n * options: [\n * { value: 'staging', label: 'Staging', recommended: true },\n * { value: 'prod', label: 'Production', description: 'Live traffic' },\n * ],\n * },\n * });\n * </script>\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`, so they filter by instance: a Stop (or an\n * error) in one chat must not cancel the question a DIFFERENT chat is waiting on,\n * whose model would be told the user refused a question they were still looking at.\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 // end it without an answer (a rejection) so the client loop unblocks and the\n // composer 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: clearing the slot whenever it holds a presenter would leave a\n // still-mounted sibling chat unable to ask anything, silently, since the\n // 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 the focus has to be given back on close: otherwise a keyboard user who\n * approved a tool call lands at the top of the document and has to tab\n * through the whole page to write their next message (WCAG SC 2.4.3, and the\n * ARIA Authoring Practices Guide requires it of every 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 = deepActiveElement(document);\n /*\n * Where the focus goes, in one rule.\n *\n * The focus is how the panel is announced, so it is taken — unless the reader is\n * busy elsewhere: writing in a field outside this chat, in another chat, or inside\n * an iframe. Then it stays where it is and a polite status says a question waits.\n *\n * From a field of THIS chat (typing the next message while a reply streams), it\n * goes to the panel itself, not to its first option: the next key the reader\n * presses must not be Enter on \"Approve\". From a button (this chat's or the app's\n * own beside it) or from nobody (`<body>`: a click in Safari, a screen reader's\n * activation, a script), it goes to the first control, without the page jumping\n * when it came from nobody.\n */\n const messageText = (): string => { const m = request.message as unknown; return typeof m === 'function' ? String((m as () => unknown)()) : String(m ?? ''); };\n const CHAT = 'aparte-chat, [data-aparte-chat], [data-aparte-host]';\n const EDITABLE = 'input, textarea, select, [contenteditable]:not([contenteditable=\"false\"])';\n const chatRoot = composer.closest(CHAT) ?? composer.parentElement ?? composer;\n const fromNobody = !(focusedBefore instanceof HTMLElement) || focusedBefore === document.body;\n const before = fromNobody ? null : (focusedBefore as HTMLElement);\n const inChat = before !== null && isWithin(before, chatRoot);\n const typing = before !== null && before.matches(EDITABLE);\n const busyElsewhere = before !== null && !inChat\n && (typing || before.tagName === 'IFRAME' || before.closest(CHAT) !== null);\n const status = (text: string): void => {\n let region = composer.querySelector<HTMLElement>(':scope > .aparte-elic-status');\n if (!region) {\n region = document.createElement('span');\n region.className = 'aparte-sr-only aparte-elic-status';\n region.setAttribute('role', 'status');\n composer.appendChild(region);\n }\n const live = region;\n live.textContent = '';\n // A region announces a CHANGE: filled a moment after it exists, not with it.\n if (text) setTimeout(() => { if (live.isConnected) live.textContent = text; }, 60);\n };\n const focusPanel = (panel: { el: HTMLElement; focus(): void }, message: string): void => {\n if (busyElsewhere) { status(message); return; }\n const x = window.scrollX, y = window.scrollY;\n if (typing && inChat) {\n if (!panel.el.hasAttribute('tabindex')) panel.el.setAttribute('tabindex', '-1');\n if (!panel.el.hasAttribute('role')) panel.el.setAttribute('role', 'group');\n if (message && !panel.el.hasAttribute('aria-label')) panel.el.setAttribute('aria-label', message);\n panel.el.focus();\n } else {\n panel.focus();\n }\n if (fromNobody && (window.scrollX !== x || window.scrollY !== y)) window.scrollTo(x, y);\n };\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 = deepActiveElement(document);\n const focusWasInPanel = active instanceof Node && isWithin(active, composer);\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 status('');\n\n /*\n * Back to whoever had it, when that is still a place to be: connected, not\n * `<body>`, not inside the composer whose panel just closed, and someone the\n * panel took it from. Otherwise the composer — the editor is where a reader\n * who just answered writes next, and `<body>` would drop a keyboard user at\n * the top of the page (2.4.3).\n */\n if (focusWasInPanel) {\n const back = !busyElsewhere && before !== null && before.isConnected && !isWithin(before, composer) ? before : null;\n (back ?? composer).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: a `cancel` read as a refusal would tell the model\n * the user had refused a tool they 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 sendable = (): boolean => panel.isComplete() && !panel.el.hasAttribute('data-collapsed');\n const approvalMode = (): AparteComposerPanelMode => (sendable() ? 'submit' : 'none');\n const syncSubmit = (): void => composer.setPanelSubmitEnabled(sendable(), approvalMode());\n const panel = runWithConfig(cfg, () =>\n buildApprovalPanel(request.message, request.options ?? [], syncSubmit, request.details));\n panel.onSettle((answer) => settle({ action: 'accept', content: answer }));\n const retract = addRetract(panel.el, panel.dismiss, cfg, () => panel.focus(), syncSubmit);\n this._pending = {\n abort: () => fail(),\n composer,\n relabel: () => { panel.relabel(); retract.relabel(resolveConfig(this)); },\n };\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 focusPanel(panel, messageText());\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 syncSubmit = (): void => {\n const aside = panel.el.hasAttribute('data-collapsed');\n composer.setPanelSubmitEnabled(!aside && panel.canProceed(), aside ? 'none' : panel.mode());\n };\n const panel: BuiltElicitationPanel = runWithConfig(cfg, () =>\n buildElicitationPanel(request.message, schema, syncSubmit));\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 const retract = addRetract(panel.el, panel.dismiss, cfg, () => panel.focus(), syncSubmit);\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: () => {\n panel.relabel();\n const now = resolveConfig(this);\n skip.textContent = now.t('elicitationSkip');\n retract.relabel(now);\n },\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. A rejection (`aborted`), not `decline`:\n * nobody declined anything, the question was taken away.\n */\n onEvict: () => fail(),\n onSubmit: () => {\n // One meaning: submit the lot. Moving between questions is the chips'.\n if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() });\n },\n });\n focusPanel(panel, messageText());\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' || 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 is no `document.querySelector('aparte-composer')` fallback: on a page with\n * two chats, an elicitation that could not find its own composer would mount its\n * panel in the OTHER chat's, so one conversation's question would appear under the\n * other conversation, and answering it would resolve a tool call belonging to a\n * 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' || 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","/**\n * Whether a transcript shows nothing: no message, and none on its way.\n *\n * A message is an `<aparte-chat-bubble>`. A viewport that says `loading` holds no bubble\n * yet and is still not empty, so the welcome and the centred composer stay hidden while a\n * conversation is being fetched.\n *\n * One predicate because two elements ask the question — `<aparte-chat>` for its\n * `data-empty`, `<aparte-suggestions empty-only>` for whether its starters still belong —\n * and the `loading` half is exactly the kind of clause a second copy forgets.\n */\nexport function isTranscriptEmpty(viewport: Element): boolean {\n return !viewport.hasAttribute('loading') && !viewport.querySelector('aparte-chat-bubble');\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';\nimport { isTranscriptEmpty } from '../../utils/transcript.js';\nimport { gateGlide } from './glide-gate.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: it watches its own viewport and says whether the conversation is empty —\n * `data-empty`, in the attribute list below, is that watcher's output — and with\n * `center-empty` the stylesheet keeps the composer centered as a welcome state until\n * the first `<aparte-chat-bubble>` lands, then slides to the normal layout, with no\n * external JavaScript. The watcher needs a viewport somewhere inside: hand-written\n * markup always has one, because composing the default injects it, and Angular's\n * `framework-managed` host gets its watcher once the template has rendered the\n * viewport. The stylesheet 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 * 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: 24rem\">\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></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n * ```\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} submit-on-enter - Forwarded to the composer by value: `submit-on-enter=\"false\"`\n * makes Enter break the line and Shift+Enter send (the bare attribute, or none, keeps the\n * default — Enter sends). The four wrappers expose the same switch as `submitOnEnter`.\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} data-empty - Reflected BY the element while its viewport holds no\n * `<aparte-chat-bubble>` and is not `loading` — set again when the conversation is cleared.\n * With or without `center-empty`: the stylesheet centers the composer through\n * `aparte-chat[center-empty][data-empty]`, and an app styles or scripts its own empty state\n * against `aparte-chat[data-empty]`. Read-only.\n * @attr {boolean} data-glide - Reflected BY the element at the first key or pointer in the page, or\n * the first send: from then on `center-empty` slides the composer between the welcome and the\n * conversation. Before it, a conversation a script restores on load lands in one frame. Read-only.\n * @attr {boolean} overlay-composer - The ChatGPT anatomy, opt-in: the transcript's scroll\n * surface spans the whole column and the composer (with the rest of the bottom stack)\n * floats over it, so the scrollbar runs edge to edge instead of stopping at the\n * composer's top. The viewport measures the stack and publishes\n * `--aparte-bottom-inset`; content, spacer and the scroll button clear it. Read when\n * the viewport wires its observers — set it in the initial markup. Also honoured on a\n * wrapper's `[data-aparte-chat]` root.\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)] - 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 * @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: 24rem\"></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', 'submit-on-enter', 'attachments'];\n }\n\n private _observer: MutationObserver | null = null;\n private _loadingObserver: MutationObserver | null = null;\n private _offGlide: (() => void) | null = null;\n /** Waits for a `<aparte-chat-viewport>` child to appear — see `_watchForViewport`. */\n private _childObserver: MutationObserver | null = null;\n /** The viewport is loading and the gate is on — see `_applyLoadingGate`. */\n private _gated = false;\n /** The composer's `disabled` is the gate's to remove, not one the consumer set. */\n private _gateOwns = false;\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._forwardAttr('submit-on-enter');\n this._syncEmptyWatch();\n this._syncLoadingWatch();\n this._watchForViewport();\n this._offGlide?.();\n this._offGlide = gateGlide(this);\n }\n\n disconnectedCallback(): void {\n this._offGlide?.();\n this._offGlide = null;\n this._observer?.disconnect();\n this._observer = null;\n this._loadingObserver?.disconnect();\n this._loadingObserver = null;\n this._childObserver?.disconnect();\n this._childObserver = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\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). While the transcript is on its way the composer stays\n // disabled whatever the consumer does to the attribute — the gate lifts with the wait.\n if (name === 'disabled' && newValue === null && this._gated) {\n this._gateOwns = true;\n return;\n }\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.\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. The BUILT-IN approval gate\n * asks through it, so a chat without one cannot honour `needsApproval` at all.\n * An affordance core can honour end to end is on by default; leaving this out would\n * make the gate depend 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 /**\n * Watch the viewport so `data-empty` follows the conversation.\n *\n * Runs whether or not `center-empty` is set, so an app that wants starters on an\n * empty thread, or a welcome of its own, can read `data-empty` instead of counting\n * messages itself. Every selector core writes against it requires `[center-empty]`,\n * so reflecting it everywhere moves no layout.\n */\n private _syncEmptyWatch(): void {\n this._observer?.disconnect();\n this._observer = null;\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 — and\n // for its `loading` attribute, the other way of not being empty.\n this._observer = new MutationObserver(() => this._updateEmpty());\n this._observer.observe(viewport, { childList: true, subtree: true, attributes: true, attributeFilter: ['loading'] });\n }\n\n /**\n * A transcript on its way cannot take a message: while the viewport says `loading`\n * the composer is disabled, whatever else the chat is set to. A send in that window\n * raced the fetch — the controller appended to a history that `setMessages`\n * overwrote a moment later. Independent of `center-empty`: every chat gates.\n */\n private _syncLoadingWatch(): void {\n this._loadingObserver?.disconnect();\n this._loadingObserver = null;\n const viewport = this.querySelector('aparte-chat-viewport');\n if (!viewport) return;\n this._applyLoadingGate();\n this._loadingObserver = new MutationObserver(() => this._applyLoadingGate());\n this._loadingObserver.observe(viewport, { attributes: true, attributeFilter: ['loading'] });\n }\n\n /**\n * Both watches above need a viewport to observe, and there might not be one at\n * connect: `<aparte-chat framework-managed>` IS Angular's host element — upgraded on\n * insert, filled by the template afterwards — and hand-written markup upgrades before\n * its children are parsed. This waits for the child to arrive, installs them, and\n * stops, so every chat gates, including those whose transcript is fetched.\n */\n private _watchForViewport(): void {\n this._childObserver?.disconnect();\n this._childObserver = null;\n if (this.querySelector('aparte-chat-viewport')) return;\n this._childObserver = new MutationObserver(() => {\n if (!this.querySelector('aparte-chat-viewport')) return;\n this._childObserver?.disconnect();\n this._childObserver = null;\n this._syncEmptyWatch();\n this._syncLoadingWatch();\n });\n this._childObserver.observe(this, { childList: true, subtree: true });\n }\n\n /**\n * The gate only ever ADDS `disabled` and only ever removes what it added: a `disabled`\n * the consumer set — on this element, or on a composer of their own — stays.\n */\n private _applyLoadingGate(): void {\n const composer = this.querySelector('aparte-composer');\n const loading = this.querySelector('aparte-chat-viewport')?.hasAttribute('loading') ?? false;\n if (!composer) return;\n if (loading && !this._gated) {\n this._gated = true;\n // Already disabled: nothing to add, and nothing to take back later — unless the\n // consumer lifts theirs during the wait, which makes the attribute the gate's.\n this._gateOwns = !composer.hasAttribute('disabled');\n composer.setAttribute('disabled', '');\n } else if (!loading && this._gated) {\n this._gated = false;\n if (this._gateOwns && !this.hasAttribute('disabled')) composer.removeAttribute('disabled');\n this._gateOwns = false;\n }\n }\n\n /**\n * Empty means no message AND none on its way. A viewport that says `loading` holds\n * no bubble yet but does not count as empty, so the welcome and the centred composer\n * stay hidden while a conversation is being fetched.\n */\n private _updateEmpty(): void {\n const viewport = this.querySelector('aparte-chat-viewport');\n this.toggleAttribute('data-empty', !viewport || isTranscriptEmpty(viewport));\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 AparteLinkClickEventDetail,\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, renderingVersionOf } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig, type AparteConfigAware } from '../../config/config-context.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { copyText } from '../../utils/copy-text.js';\nimport { mergeSegmentUpdate } from '../../utils/segments.js';\nimport type { AparteComposerInput } from '../composer/aparte-composer-input.js';\nimport { escapeAttr, escapeHtml } from '../../utils/escape.js';\nimport { isSafeUrl } from '../../config/sanitize.js';\n\n/**\n * Warn ONCE when a segment has no renderer — only for types core has never heard of,\n * 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 * 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) shows its\n * `AparteCustomSegment.fallback` text. Without one it shows\n * `[Unknown segment type: custom]`.\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, segment?: Pick<AparteSegment, 'id'>): HTMLElement | null {\n let el: HTMLElement | null;\n if (result instanceof HTMLElement) {\n el = result;\n } else {\n const wrapper = document.createElement('div');\n wrapper.innerHTML = result;\n el = wrapper.firstElementChild as HTMLElement | null;\n }\n // The container finds its own children by id — this is where that invariant is\n // owned, for both arms and for every renderer, not each renderer's to remember.\n // A root without it made `_applySegmentUpdate` miss and fall back to the full\n // wipe-and-rebuild, which destroys a mounted artifact preview, collapses an\n // opened reasoning block and drops the focus — the ask_user receipt did exactly\n // that on every tool update, since `AparteToolRenderer`'s contract never said so.\n if (el && segment && !el.hasAttribute('data-segment-id')) el.setAttribute('data-segment-id', segment.id);\n return el;\n}\n\n/**\n * Empty avatar slots waiting to learn whether their role's `--aparte-avatar-image-*` names a\n * picture, with the bubble and the role each one reads. Flushed together in one microtask, so\n * a config change that redraws every bubble reads every token first and marks afterwards: a\n * mark set between two reads dirtied the style the next read had to recompute.\n *\n * The token is read on the bubble, not on the slot. A bubble off screen skips its contents\n * (`content-visibility: auto`), and reading a computed style inside a skipped subtree costs a\n * style pass of its own: on 200 messages that was ~50ms per config change in Chromium. So\n * the token is honoured where it is declared on the bubble or any ancestor — `:root`, the\n * chat — which is where the guide declares it.\n */\nconst pendingAvatarMarks = new Map<HTMLElement, { bubble: HTMLElement; role: string }>();\n\nfunction flushAvatarMarks(): void {\n const batch = [...pendingAvatarMarks].filter(([avatar]) => !avatar.firstChild);\n pendingAvatarMarks.clear();\n const shown = batch.map(([, { bubble, role }]) => {\n const image = getComputedStyle(bubble).getPropertyValue(`--aparte-avatar-image-${role}`).trim();\n return image !== '' && image !== 'none';\n });\n batch.forEach(([avatar], i) => avatar.toggleAttribute('data-image', shown[i]));\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 eight events are declared by hand rather than left to the analyser.\n *\n * The bubble element is `content-visibility: auto` for its streaming/scroll performance\n * (see `bubble.css`), and that containment makes the bubble\n * the containing block for `position: fixed` descendants: a popover mounted inside it\n * (an `<aparte-select>` from a `registerToolRenderer` renderer, say) is clipped to the\n * bubble's own box, not the window. Mount it outside instead — [Custom tool\n * renderer](https://apartejs.dev/guides/tools/#custom-tool-renderer) shows the pattern.\n *\n * @element aparte-chat-bubble\n *\n * @attr {string} data-role - The message role, `user` or `assistant` — the one channel for it, and what the CSS keys off. (`role` is ARIA's attribute; this element sets it to `article` itself and does not read a message role from it.)\n * @attr {string} content - Plain text content, for a bubble with no segments.\n * @attr {number | string} timestamp - Epoch milliseconds or a date string; a numeric string is read as milliseconds.\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 - Who wrote the message: shown in the header, and the message's accessible name (\"Message from {name}\"). Leave it out for the reader and the assistant, whose names come from the locale.\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 * @fires {CustomEvent<AparteLinkClickEventDetail>} aparte-link-click - A link in the message body is about to be followed. Cancelable: `preventDefault()` keeps the browser from navigating, so a host can route the link itself.\n *\n * @cssprop [--aparte-message-gap=var(--aparte-space-6)] - Gap between the avatar column and the body (the viewport reuses it between messages).\n * @cssprop [--aparte-message-padding-block=var(--aparte-space-8)] - Vertical padding of one message row.\n * @cssprop [--aparte-message-padding-inline=var(--aparte-space-6)] - Horizontal padding of one message row.\n * @cssprop [--aparte-message-max-width=800px] - Width of the centred message row.\n *\n * @cssprop [--aparte-message-content-radius=var(--aparte-radius-bubble)] - Radius of the painted content box.\n * @cssprop [--aparte-message-content-padding=var(--aparte-space-5) var(--aparte-space-7)] - Padding of the user's box.\n * @cssprop [--aparte-message-content-padding-assistant=0] - Padding of the assistant's box, none by default: its reply is plain prose across the column.\n * @cssprop --aparte-message-content-bg-user - Background of the user box: a wash of `--aparte-primary` over `--aparte-surface-1`, derived in `theme.css` so a rebrand moves it (declare it to override).\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 - Colour of the user's words. Unset by default: they keep `--aparte-content-color`.\n * @cssprop --aparte-message-content-text-assistant - Colour of the assistant's words. Unset by default: they keep `--aparte-content-color`.\n *\n * @cssprop [--aparte-avatar-size=32px] - Square size of the avatar slot.\n * @cssprop [--aparte-avatar-radius-ratio=0.28] - Avatar corner radius, as a fraction of its size (the corner follows the ramp).\n * @cssprop [--aparte-avatar-initials-ratio=0.34] - Size of the initial, as a fraction of the avatar's size.\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)] - Avatar text colour, assistant role.\n * @cssprop [--aparte-avatar-image-user=none] - `background-image` for the user avatar — a logo with no AvatarProvider: the slot shows when it names an image.\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=var(--aparte-font-size-md)] - Sender name in the header.\n * @cssprop [--aparte-name-color=var(--aparte-text)] - Sender name colour.\n * @cssprop [--aparte-timestamp-font-size=var(--aparte-font-size-sm)] - Timestamp in the header.\n * @cssprop [--aparte-timestamp-color=var(--aparte-text-muted)] - Timestamp colour.\n * @cssprop [--aparte-content-font-size=var(--aparte-font-size-base)] - 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=72px] - 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=var(--aparte-space-2)] - Gap between action buttons (and between the footer's two regions).\n * @cssprop [--aparte-action-bar-btn-size=var(--aparte-btn-size-sm)] - Square size of an action button (the recipe's small step); 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=var(--aparte-space-2)] - Gap between the arrows and the position label.\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=var(--aparte-font-size-sm)] - 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=var(--aparte-space-2)] - 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 * 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 * 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 implements AparteConfigAware {\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 /** Whether the avatar slot holds what a provider drew, which goes when the provider does. */\n private _avatarFromProvider = false;\n /** Sibling count for tree-based branch navigation (set by setSiblings()) */\n private _siblingCount = 1;\n /**\n * Which accessible names the bubble wrote itself, and so may refresh on a language switch\n * or a name change. A custom shell's own are its author's: refreshed, a root's or a bar's\n * label of their own became \"Assistant response\" or \"Message actions\" at the first notify.\n */\n private _wroteRootLabel = false;\n private _wroteBarLabel = false;\n private _wroteArrowLabels = false;\n /** The shell renderer the bubble was last drawn with (`null`: the default frame). */\n private _drawnShell: unknown = null;\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 * The `data-action` of the button the editor was opened from, so leaving it can hand\n * the keyboard back. The STRING, not the node: entering and leaving edit mode rewrite\n * the action bar's `innerHTML`, so the opener's identity is destroyed twice.\n */\n private _editReturnAction: string | null = null;\n\n static get observedAttributes(): string[] {\n // `data-role` is the ONE channel for the message role. `role` is ARIA's\n // attribute — this element writes `role=\"article\"` on itself in _render() —\n // and it used to be read as a legacy message-role channel too, which meant\n // filtering our own \"article\" back out at every turn. A rename lands as a\n // rename pre-1.0, so the overload is gone.\n return ['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 // Once, in a microtask: the usual setup sets the Markdown provider, its streaming twin\n // and a highlighter one after another, and each notifies — drawn on each, every message\n // on screen was parsed three times over.\n if (!this._redrawQueued) {\n this._redrawQueued = true;\n queueMicrotask(() => {\n this._redrawQueued = false;\n if (this.isConnected) this._redrawIfStale();\n });\n }\n };\n\n /** A {@link _redrawIfStale} is queued for the end of the current task. */\n private _redrawQueued = false;\n\n /** The {@link renderingVersionOf} count the content was last drawn with. */\n private _drawnWithVersion = 0;\n\n /**\n * A Markdown or highlight provider set after this message was drawn: draw it again.\n * Not mid-stream — the stream reads the providers as it goes, and its end redraws — and\n * not mid-edit, where the drawn message is hidden behind the editor and drawing it would\n * show it again beside the editor; leaving the edit calls this.\n */\n private _redrawIfStale(): void {\n if (this._streaming || this._editing || renderingVersionOf(this._cfg) === this._drawnWithVersion) return;\n if (this._segments.length > 0) this._renderSegments();\n else this._updateContent();\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 if (this._wroteArrowLabels) {\n set('.aparte-branch-prev', locale.previousResponse ?? 'Previous response');\n set('.aparte-branch-next', locale.nextResponse ?? 'Next response');\n }\n if (this._wroteBarLabel) 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 this.addEventListener('click', this._onLinkClick);\n }\n\n /**\n * The boundary above this bubble changed — under every wrapper the chat's own config is\n * attached AFTER the first messages are drawn. When that brings another frame\n * (`setBubbleShellRenderer`), the bubble is drawn again in it and its content put back,\n * so every message wears the chat's frame.\n *\n * Deferred to a microtask: a wrapper detaches the boundary as it unmounts (Vue's\n * `onBeforeUnmount`, Svelte's `onDestroy`), while the bubbles are still in the page, and\n * redrawing every one of them just before their removal would cost a frame per message.\n * After the microtask a removed bubble is no longer connected, and repeated notices fold\n * into one.\n */\n aparteConfigChanged(): void {\n queueMicrotask(() => this._redrawFrameIfChanged());\n }\n\n private _redrawFrameIfChanged(): void {\n if (!this.isConnected || this._editing) return; // an open editor is the reader's: caught up when it closes\n if ((this._cfg.getBubbleShellRenderer?.() ?? null) === this._drawnShell) return;\n if (this._avatarCleanup) {\n try { this._avatarCleanup(); } catch { /* ignore */ }\n this._avatarCleanup = null;\n }\n // The incremental Markdown parser is bound to the content element being replaced: left\n // bound, a stream would go on writing into the detached one and end blank.\n this._resetMarkdownStream();\n this.replaceChildren();\n this._render();\n this._updateContent();\n if (this._segments.length > 0) this._renderSegments();\n this._updateAttachments();\n this._updateTimestamp(this.getAttribute('timestamp'));\n this._updateBranchPicker();\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n this.removeEventListener('click', this._onBranchPickerClick);\n this.removeEventListener('click', this._onLinkClick);\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 'data-role':\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 this._syncCopyAction();\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 this._syncCopyAction();\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 this._syncCopyAction();\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 this._syncCopyAction();\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 would 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 a decoy could point an update at the wrong\n * element and leave 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 this._syncCopyAction();\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 // Attachments are content: a bubble that is only attachments must not be `data-empty`.\n this._updateWaiting();\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 // Copy IN, exactly as `setSegments` does and for the same reason: the\n // viewport `Object.assign`s this very array into the repo message before\n // forwarding it here, so adopting it by reference leaves one array with two\n // writers — the doubling documented on `setSegments` above.\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 this._updateWaiting();\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 // The first segment of a message drawn piece by piece sets the count it is drawn with.\n if (this._segments.length === 1) this._drawnWithVersion = renderingVersionOf(this._cfg);\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)), 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)), 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 // The message role comes from `data-role` alone; the host's `role` attribute is\n // ARIA's and is set to a valid value here. \"user\" and \"assistant\" are not ARIA\n // roles and would trip accessibility tooling — which is exactly why the message\n // role never belonged on that attribute. The role-based styling lives on inner\n // `data-role` markers, so the swap is transparent to CSS.\n const dataRole = this.getAttribute('data-role');\n const role = (dataRole === 'user' || dataRole === 'assistant') ? dataRole : '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 this._drawnShell = shell ?? null;\n this._avatarFromProvider = false; // a new frame, a new slot\n // The default frame's names are all the bubble's; a custom one's, only those stamped below.\n this._wroteRootLabel = this._wroteBarLabel = this._wroteArrowLabels = !shell;\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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev\" aria-label=\"${escapeAttr(this._cfg.getLocale().previousResponse ?? 'Previous response')}\">${this._cfg.getIcon('prevBranch')}</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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next\" aria-label=\"${escapeAttr(this._cfg.getLocale().nextResponse ?? 'Next response')}\">${this._cfg.getIcon('nextBranch')}</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 // The shell contract lists CLASS hooks, so a shell written from it carries no ARIA\n // and the transcript loses one article and one accessible name per custom bubble —\n // measured on this repo's own documented shell. Re-applied here rather than asked of\n // the author: a markup contract that also demanded a `role` fails the first time\n // somebody forgets one, silently (decision #8 — honoured end to end). A root that\n // already declares a `role` keeps it, which is how a shell overrides the default.\n const messageEl = this.querySelector('.aparte-message');\n if (messageEl && !messageEl.hasAttribute('role')) {\n messageEl.setAttribute('role', 'article');\n messageEl.setAttribute('aria-label', this._getAriaLabel());\n this._wroteRootLabel = true;\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 // A custom shell's bar gets the roving tab stop below; it gets the toolbar it implies\n // too, as the root gets `article` above — the contract lists class hooks, so a shell\n // written from it carries no ARIA. A bar that declares its own role keeps it.\n if (this._actionBarEl && !this._actionBarEl.hasAttribute('role')) {\n this._actionBarEl.setAttribute('role', 'toolbar');\n if (!this._actionBarEl.hasAttribute('aria-label')) {\n this._actionBarEl.setAttribute('aria-label', this._cfg.getLocale().messageActions ?? 'Message actions');\n this._wroteBarLabel = true;\n }\n }\n // The branch arrows are icon buttons, and the contract asks no ARIA of a shell's author:\n // unnamed ones get the locale's names, and keep them through a language switch. A shell\n // that named either arrow keeps both as it wrote them — by an attribute, or by words in\n // the button, which a glyph like ‹ is not: a label stamped over a visible word is a name\n // the reader cannot see, and a voice command saying the word finds nothing.\n if (shell) {\n const prev = this.querySelector('.aparte-branch-prev');\n const next = this.querySelector('.aparte-branch-next');\n // Only words a reader can hear: an icon font's ligature or an SVG title under\n // `aria-hidden` is text in the DOM and no name at all.\n const audible = (arrow: Element): string => {\n let text = '';\n const walker = arrow.ownerDocument.createTreeWalker(arrow, NodeFilter.SHOW_TEXT);\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n const hidden = node.parentElement?.closest('[aria-hidden=\"true\"], [hidden]');\n if (!hidden || !arrow.contains(hidden)) text += node.textContent ?? '';\n }\n return text;\n };\n const named = (arrow: Element | null): boolean => !!arrow && (\n arrow.hasAttribute('aria-label') || arrow.hasAttribute('aria-labelledby') || arrow.hasAttribute('title')\n || /\\p{L}/u.test(audible(arrow)));\n if ((prev || next) && !named(prev) && !named(next)) {\n const locale = this._cfg.getLocale();\n prev?.setAttribute('aria-label', locale.previousResponse ?? 'Previous response');\n next?.setAttribute('aria-label', locale.nextResponse ?? 'Next response');\n this._wroteArrowLabels = true;\n }\n }\n // ONE listener on the bar, not one per button: every build path rewrites the bar's\n // innerHTML, so a per-button listener would have to be re-attached three times and\n // would be lost the first time somebody added a fourth path. The bar element itself\n // outlives every rewrite.\n this._actionBarEl?.addEventListener('keydown', this._onActionBarKeydown);\n this._branchPickerEl = this.querySelector('.aparte-branch-picker');\n this._footerEl = this.querySelector('.aparte-footer');\n\n // A bubble mounted while a reply already streams (a framework rendering the list\n // after the viewport flagged itself) starts in the busy state it would have been\n // pushed into.\n this._transcriptBusy = !!this.closest('aparte-chat-viewport')?.hasAttribute('data-busy');\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.\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 // Nothing to show and nothing coming: no chrome either. A turn whose only content\n // is a tool that renders nothing (an `ask_user` with no preamble), or one stopped\n // before its first token, used to leave a name and a timestamp floating over\n // nothing — an orphan header in the transcript. The stylesheet hides the whole\n // row on `data-empty`; attachments count as content, and a streaming bubble is\n // never empty (it shows the waiting dots).\n const message = this.querySelector('.aparte-message') as HTMLElement | null;\n if (message) {\n message.toggleAttribute('data-empty', empty && !this._streaming && this._attachments.length === 0);\n }\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 keeps what the shell rendered — for the default shell,\n * nothing, hidden while empty — and loses what a provider drew there before. An empty\n * slot still shows when the stylesheet names a picture for its role\n * (`--aparte-avatar-image-user` / `-assistant`): see `_markAvatarImage`.\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) {\n // `setAvatarProvider(null)`: what the provider drew goes with it. The slot returned\n // early here, so the old avatars stayed on every message already on screen.\n if (this._avatarFromProvider) {\n avatar.replaceChildren();\n this._avatarFromProvider = false;\n }\n this._markAvatarImage(avatar);\n return;\n }\n\n avatar.textContent = '';\n const cleanup = provider.render(this._role, avatar);\n this._avatarFromProvider = true;\n if (typeof cleanup === 'function') this._avatarCleanup = cleanup;\n // A provider may draw one role and leave the other's slot empty: the stylesheet's\n // picture still shows there.\n this._markAvatarImage(avatar);\n }\n\n /**\n * A picture from the stylesheet alone: an empty slot whose role's `--aparte-avatar-image-*`\n * names an image gets `data-image`, which lifts the rule that hides an empty slot. Hidden\n * while empty, the token painted nothing. Read when the message is drawn and on every\n * config change, in a batch with the other bubbles' (see `pendingAvatarMarks`).\n */\n private _markAvatarImage(avatar: HTMLElement): void {\n if (avatar.firstChild) {\n pendingAvatarMarks.delete(avatar);\n avatar.removeAttribute('data-image');\n return;\n }\n // Before the frame, with every other slot this task redraws: see `pendingAvatarMarks`.\n if (pendingAvatarMarks.size === 0) queueMicrotask(flushAvatarMarks);\n pendingAvatarMarks.set(avatar, { bubble: this, role: this._role });\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 // The accessible name follows the role only on a root whose role the bubble wrote.\n // A shell that declares its own `role` overrides the default (the shell contract\n // says so), and an override that kept the role and lost the name would be half\n // of one.\n if (this._wroteRootLabel && message.getAttribute('role') === 'article') {\n message.setAttribute('aria-label', this._getAriaLabel());\n }\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 && !this._avatarFromProvider) 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:not(:where([data-image]))\n * { display: 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 // Not over what a provider drew either: with the provider just cleared, that DOM is\n // still there until `_renderAvatar` runs its cleanup and empties the slot.\n if (avatar && avatar.textContent && !this._cfg.getAvatarProvider() && !this._avatarFromProvider) {\n avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) nameEl.textContent = this._getDisplayName();\n // The accessible name follows the author too — on a root whose role the bubble wrote,\n // the same rule as `_updateRole`.\n const message = this.querySelector('.aparte-message');\n if (this._wroteRootLabel && message?.getAttribute('role') === 'article') message.setAttribute('aria-label', this._getAriaLabel());\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 this._drawnWithVersion = renderingVersionOf(this._cfg);\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 this._drawnWithVersion = renderingVersionOf(this._cfg);\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)), 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 this._syncCopyAction();\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 /**\n * Named after its author when it has one: a `name` on the bubble is who wrote it (a human\n * agent answering on the assistant's side, a second assistant), and naming every one of\n * them \"Assistant response\" told a screen-reader user the opposite of what the page shows.\n * Without a name, the role names it.\n */\n private _getAriaLabel(): string {\n const locale = this._cfg.getLocale();\n const name = this.getAttribute('name');\n if (name) return (locale.messageFrom || 'Message from {name}').replace('{name}', () => name);\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/') && this._isShowableUrl(a.url)) {\n // `aparte-thumbnail` is the RECIPE (the box, the size, the ground); `aparte-thumb`\n // only maps the strip's measurements onto it. The bubble emitted the mapping\n // without the recipe, so its tiles had no box — a bare \"PDF\" beside a bare image.\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--image\" title=\"${name}\">`\n + `<img class=\"aparte-thumbnail__image\" src=\"${escapeHtml(a.url)}\" alt=\"${name}\" loading=\"lazy\" />`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--file\" title=\"${name}\">`\n + `<span class=\"aparte-thumbnail__label\">${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-thumbnail__image') 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 /**\n * The same answer `copyAttributes` gives before it writes an `src`, plus `blob:`.\n *\n * An attachment's `url` is documented as \"URL or data URI\" and a storage adapter\n * re-mints it when a conversation is restored, so it is the app's value, not the\n * composer's — and, like every other URL core emits, it is asked whether its SCHEME\n * is allowed at all. A picture whose URL is refused is not half-rendered: it falls\n * back to the file chip, which still names the file.\n *\n * `blob:` is added because it is the shape core itself mints — `filesToAttachments`\n * calls `URL.createObjectURL`, and `AparteMessage.attachments` documents the same\n * on hydration. `isSafeUrl` refuses it, and rightly: that list governs MODEL\n * markup, which has no way to make a blob URL resolve.\n */\n private _isShowableUrl(url: string | undefined): boolean {\n if (!url) return false;\n return isSafeUrl(url, 'img') || /^blob:/i.test(url.trim());\n }\n\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 * The listener lives on the host, which is never replaced, and `closest()` finds\n * whichever button exists at the moment of the click — so a click that lands while\n * a re-render is swapping the buttons is not lost.\n *\n * Bound in `connectedCallback` and removed in `disconnectedCallback` as a stable\n * field, so a re-connect never stacks listeners.\n */\n private _onBranchPickerClick = (event: Event): void => {\n // `aparte-branch-navigate` is dispatched from this arrow field, which the manifest\n // analyser (methods only) does not visit: it is declared by hand in the class `@fires`.\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 /**\n * A link the MODEL wrote is about to be followed. Announced as a cancelable event\n * before the browser acts, so a host can route it — an external browser, a\n * confirmation, an embedded view — without intercepting the DOM. With\n * no listener the browser follows it, which the sanitizer has already made open in\n * a new tab for an external URL; `preventDefault()` on the event cancels the click.\n *\n * Only anchors inside the message body: the action bar and the branch picker are\n * buttons, and an attachment tile is its own event.\n */\n private _onLinkClick = (event: Event): void => {\n const target = event.target as HTMLElement | null;\n const anchor = target?.closest?.('a[href]') as HTMLAnchorElement | null;\n if (!anchor || !this.contains(anchor)) return;\n const detail: AparteLinkClickEventDetail = {\n href: anchor.getAttribute('href') ?? '',\n anchor,\n messageId: this.getAttribute('message-id'),\n };\n const announced = this.dispatchEvent(new CustomEvent<AparteLinkClickEventDetail>('aparte-link-click', {\n bubbles: true,\n composed: true,\n cancelable: true,\n detail,\n }));\n if (!announced) event.preventDefault();\n };\n\n /**\n * Whether a reply is streaming somewhere in this bubble's transcript. Set by the\n * viewport (`data-busy` on it, pushed here), read once on connect for a bubble\n * mounted while the flag was already up. While busy, the branch arrows and the\n * retry/edit actions are disabled — the transcript is read-only except for Stop.\n */\n private _transcriptBusy = false;\n\n setTranscriptBusy(busy: boolean): void {\n if (this._transcriptBusy === busy) return;\n this._transcriptBusy = busy;\n this._applyTranscriptBusy();\n this._updateBranchPicker();\n }\n\n private _applyTranscriptBusy(): void {\n if (!this._actionBarEl) return;\n for (const btn of this._actionBarEl.querySelectorAll<HTMLButtonElement>('[data-action=\"retry\"], [data-action=\"edit\"]')) {\n btn.disabled = this._transcriptBusy;\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._transcriptBusy || this._siblingIndex === 0;\n if (nextBtn) nextBtn.disabled = this._transcriptBusy || this._siblingIndex === this._siblingCount - 1;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Action Bar\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * What the copy action copies: the reply, not the reasoning. A `thinking` segment is\n * the model's scratchpad, and the client already keeps it out of the history it sends\n * back (`_segmentsToText`) for the same reason — copying it along with the answer\n * pasted a paragraph of deliberation above every reply, which no assistant on the\n * market does. Segments with no `content` (a tool call) contribute nothing, so a\n * turn made only of those copies as `''` — and the action bar reads this to decide\n * whether to offer the button at all.\n */\n private _copyableText(): string {\n return (this._content || this._segments\n .filter(s => s.type !== 'thinking')\n .map(s => (s as { content?: string }).content ?? '')\n .join('\\n')).trim();\n }\n\n /**\n * The copy button is for text. `hidden` rather than removed: the bar is rebuilt\n * rarely and the text changes on every streamed token, so the button is toggled in\n * place from each path that changes what a copy would paste.\n */\n private _syncCopyAction(): void {\n const btn = this._actionBarEl?.querySelector<HTMLElement>('.aparte-action-copy');\n if (btn) btn.hidden = this._copyableText() === '';\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. (Copy is rendered here\n // and HIDDEN by `_syncCopyAction` while there is no text to copy — a tool-only\n // turn used to offer a button that copied the empty string and said \"Copied\".)\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 this._syncCopyAction();\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();\n this._applyTranscriptBusy();\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 * `role=\"toolbar\"` promises a keyboard model. This is it.\n *\n * A toolbar is ONE tab stop whose members are reached with the arrow keys — that is\n * the whole of what the role means to a screen-reader user. Without it, tabbing\n * through a long transcript would walk every button of every message.\n *\n * The roving index is re-applied from `_syncFooterVisibility`, which is the single\n * funnel all three build paths run through (the flag-driven bar, the edit bar, and\n * the branch picker's own visibility pass). Re-deriving it there rather than in each\n * builder keeps the invariant true after a `setBubbleActions` rebuild.\n *\n * DISABLED buttons are skipped, not merely un-tabbable: while the transcript is busy\n * retry and edit are disabled, and a toolbar whose arrows stop on a dead control\n * reads as broken. If every button is disabled the bar keeps one tab stop anyway, so\n * focus sitting there when the turn ends is not thrown to the top of the page.\n */\n private _rovingButtons(): HTMLButtonElement[] {\n if (!this._actionBarEl) return [];\n return [...this._actionBarEl.querySelectorAll<HTMLButtonElement>('button')];\n }\n\n private _setRovingIndex(): void {\n const buttons = this._rovingButtons();\n if (!buttons.length) return;\n const usable = buttons.filter((b) => !b.disabled);\n const current = buttons.find((b) => b.tabIndex === 0 && !b.disabled);\n const stop = current ?? usable[0] ?? buttons[0];\n for (const button of buttons) button.tabIndex = button === stop ? 0 : -1;\n }\n\n /**\n * Left/Right walk the bar, Home/End jump to its ends, and both wrap.\n *\n * Arrow keys follow the READING direction, per the ARIA practices: in an RTL\n * transcript Left is \"next\". `dir` is resolved from the nearest ancestor that sets\n * it — the viewport writes it from `locale.direction` — because a bubble has none of\n * its own and `getComputedStyle` is not something jsdom can answer.\n */\n private _onActionBarKeydown = (e: Event): void => {\n const event = e as KeyboardEvent;\n const keys = ['ArrowLeft', 'ArrowRight', 'Home', 'End'];\n if (!keys.includes(event.key)) return;\n const buttons = this._rovingButtons().filter((b) => !b.disabled);\n if (buttons.length === 0) return;\n const active = document.activeElement as HTMLElement | null;\n const from = buttons.findIndex((b) => b === active);\n if (from === -1 && event.key !== 'Home' && event.key !== 'End') return;\n const rtl = (this.closest('[dir]')?.getAttribute('dir') ?? document.documentElement.dir) === 'rtl';\n const forward = rtl ? 'ArrowLeft' : 'ArrowRight';\n let next: number;\n if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = buttons.length - 1;\n else if (event.key === forward) next = (from + 1) % buttons.length;\n else next = (from - 1 + buttons.length) % buttons.length;\n event.preventDefault();\n for (const button of this._rovingButtons()) button.tabIndex = button === buttons[next] ? 0 : -1;\n buttons[next]?.focus();\n };\n\n /**\n * An empty action bar is not a bar: with every action off it would be a\n * `role=\"toolbar\"` with nothing in it (announced as such) that still reserves its\n * 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 this._setRovingIndex();\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 // The stylesheet floats an older message's footer out of the flow so that it\n // reserves no row — except while the branch picker shows, which has to stay in\n // the flow and in view. The picker's visibility is decided here, so the flag that\n // CSS reads is written here too.\n this.querySelector('.aparte-message')?.toggleAttribute('data-branches', !pickerHidden);\n }\n\n /** Append the registered custom action buttons for this bubble's role. */\n private _appendCustomActions(): 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.type = 'button';\n btn.className = 'aparte-btn aparte-btn--icon aparte-btn--sm 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 btn.innerHTML = a.icon.startsWith('<')\n ? a.icon\n : (this._cfg.getIcon(a.icon) ?? 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"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 type=\"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._copyableText();\n const icons = this._cfg.getIconProvider();\n const locale = this._cfg.getLocale();\n copyText(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 // Read the opener BEFORE the action bar is rebuilt — after it, the button is gone.\n // Nothing is remembered when the focus is outside this bubble: pulling it back from\n // wherever the reader actually is would be theft, not a restore.\n const opener = document.activeElement;\n this._editReturnAction =\n opener instanceof HTMLElement && this.contains(opener)\n ? (opener.getAttribute('data-action') ?? 'edit')\n : null;\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 // A frame change that arrived while the editor was open is drawn now.\n this._redrawFrameIfChanged();\n this._redrawIfStale();\n this._updateActionBar();\n\n // Both exits destroy the focused element — the editor is removed and the bar is\n // rebuilt — so without this the keyboard lands on `<body>` and the next Tab\n // restarts at the top of the page. Matched by attribute rather than by selector:\n // a custom action's id (`custom:…`) is a consumer string.\n const returnTo = this._editReturnAction;\n this._editReturnAction = null;\n if (returnTo && this._actionBarEl) {\n const back = Array.from(this._actionBarEl.querySelectorAll<HTMLButtonElement>('[data-action]'))\n .find((btn) => btn.getAttribute('data-action') === returnTo);\n // The remembered action can come back DISABLED (the reader sent from the composer\n // mid-edit, so the transcript is busy) or gone (it was turned off while editing).\n // `focus()` on a disabled button is a no-op, which drops the reader on `<body>` —\n // the very bug this restore exists for, silently. So fall back to the bar's first\n // enabled button.\n const target = back && !back.disabled\n ? back\n : this._actionBarEl.querySelector<HTMLButtonElement>('button:not([disabled])');\n // The bar is one tab stop that the arrows move (`_onActionBarKeydown`), and\n // `_updateActionBar` just parked it on the first button. Focusing without moving\n // the stop leaves the reader on a `tabindex=\"-1\"` member of a `role=\"toolbar\"`:\n // Shift+Tab out and Tab back returns them to copy, not to where they were.\n if (target) {\n for (const button of this._rovingButtons()) button.tabIndex = button === target ? 0 : -1;\n target.focus();\n }\n }\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' || 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 /*\n * Streaming just finished — the SETTLE pass.\n *\n * `_updateContent` is what runs it: on `isStreaming === false`\n * `writeStreamedMarkdown` flushes the incremental parser and then either\n * re-renders once through the one-shot provider, whose output goes through\n * `sanitizeHtml`, or — when no one-shot provider is registered — re-sanitises\n * the flushed DOM in place. Either way the settled message is what\n * `sanitizeHtml` produced, which is the reason the streaming provider is\n * allowed to write DOM directly — and this branch never called it, so a streamed\n * `<code class=\"aparte-btn\">` was permanent rather than transient. The live\n * end-of-turn call carries no content (`completeMessage` sends\n * `{ status: 'completed' }` alone), so re-applying `_content` here is the only\n * place it can happen.\n *\n * It also highlights on its way out (it is no longer streaming), which is what\n * this line used to do on its own. A segment-bearing bubble is a no-op there —\n * the content element is hidden and empty — which is what it was for the\n * highlight too.\n */\n if (wasStreaming && !streaming) this._updateContent();\n // Segments have no such pass: if a Markdown or highlight provider arrived while they\n // streamed, the finished message is drawn again with it.\n if (wasStreaming && !streaming && this._segments.length > 0\n && renderingVersionOf(this._cfg) !== this._drawnWithVersion) {\n this._renderSegments();\n }\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 locale's `typing` word\n * (\"Typing\" in English). The container is a polite live region, and a live\n * region announces its CONTENT, not its `aria-label`: so the word also goes INTO the\n * region, in the visible span when `text` is set and in a screen-reader-only span when\n * it is not. Exactly one of the two is ever populated — both would be read twice. That\n * fallback word is driven by `visible`, not written once at render: a live region is\n * announced when its content CHANGES while it is exposed, and every wrapper mounts this\n * element hidden and flips the attribute — so a word written at render time is a word\n * the region already held when it appeared, the reveal-from-`display: none` path, which\n * is the one screen readers are documented not to announce reliably.\n * 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-block` and\n * `--aparte-message-padding-inline` on `.aparte-message` itself, so a declaration on\n * this host element loses to them 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 locale's `typing` word (\"Typing\" in English).\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=var(--aparte-font-size-md)] - 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-block=var(--aparte-space-8)] - Vertical 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-padding-inline=var(--aparte-space-6)] - Horizontal padding of the row, same reason.\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 /** The word said when no `text` is set: the locale's `typing`, never an English literal. */\n private _word(): string {\n return this._cfg.t('typing');\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') || this._word();\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 <span class=\"aparte-sr-only aparte-status-sr\"></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 this._writeText(this.getAttribute('text'));\n }\n\n /**\n * The one writer for both text nodes, so exactly ONE of them is ever populated.\n *\n * The container is a polite live region, and a live region announces its CONTENT.\n * The dots-only default had none — an `aria-hidden` dot and an empty span — so the\n * whole state rode on `aria-label`, which names the region rather than reporting it,\n * and nothing was announced. The screen-reader span carries the fallback word in that\n * case; when `text` is set the visible span already carries the same string, and\n * populating both would have the region read it twice.\n */\n private _writeText(text: string | null): void {\n // `text=\"\"` is the dots-only default too — and it is the reading the post-mount\n // path already took, where mounting with an empty attribute used to print the\n // literal `Typing` on screen. One writer, one answer.\n const line = text || null;\n const textEl = this.querySelector('.aparte-status-text');\n if (textEl) textEl.textContent = line ?? '';\n this._syncLiveWord();\n }\n\n /**\n * The fallback word enters and leaves the live region WITH the element's visibility.\n *\n * Writing it at render time put it in the region while the host was still\n * `display: none` — every wrapper mounts `<aparte-chat-status>` once and flips\n * `visible` — so by the time the region was exposed its content had not changed, and\n * on the second and third turn there was not even a reveal to notice: the text was\n * byte-identical to what was already sitting there. Revealing a region that already\n * holds its text is the path assistive tech is documented not to announce reliably;\n * mutating one that is already exposed is the path that works. So the word is written\n * when `visible` arrives and cleared when it leaves, which makes every turn a real\n * change.\n *\n * With `text` set the visible span is the region's content and the screen-reader span\n * stays empty — one copy, read once.\n */\n private _syncLiveWord(): void {\n const srEl = this.querySelector('.aparte-status-sr');\n if (!srEl) return;\n const dotsOnly = !this.getAttribute('text');\n srEl.textContent = dotsOnly && this.hasAttribute('visible') ? this._word() : '';\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 // The word is the news, and news is only heard while the region is on screen.\n this._syncLiveWord();\n }\n\n private _updateText(text: string | null): void {\n const container = this.querySelector('.aparte-status-container');\n if (!container) return; // not rendered yet — _render() reads the attribute\n // A custom renderer drew the inside from the text it was given, and has no text\n // element of ours to write into: it is asked again with the new text, as at render.\n const custom = this._cfg?.getStatusRenderer?.();\n if (custom) {\n const next = text || this._word();\n container.setAttribute('aria-label', next);\n const result = runWithConfig(this._cfg, () => custom(next));\n if (result instanceof HTMLElement) container.replaceChildren(result);\n else container.innerHTML = result;\n return;\n }\n // Removing the attribute restores the dots-only default (no visible text, the\n // fallback word in the live region); the aria-label always keeps an accessible name.\n this._writeText(text);\n container.setAttribute('aria-label', text || this._word());\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';\n// From the module rather than the barrel: `types/index.ts` re-exports TYPES only, and\n// this is a value — the marker `withParsedSegments` stamps on what it derived.\nimport { APARTE_DERIVED_SEGMENTS } from '../../types/models.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 { parseMarkdownToSegments } from '../../parsers/index.js';\nimport type { AparteStreamBlock } from '../../types/stream-blocks.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { isAwaitingReply } from '../../utils/is-awaiting-reply.js';\nimport { presenceOn } from '../../utils/presence.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 * An assistant reply handed over as a markdown string, split the way the stream would\n * have split it — fences to `code` segments, `<think>` to a reasoning block, the\n * registered stream blocks to theirs. Anything else comes back untouched: a message\n * that already carries segments, a person's message (their fences are their own), an\n * empty or multimodal content, and plain prose with nothing to split, which keeps the\n * cheaper content path.\n *\n * What it derived, it MARKS — see {@link markDerivedSegments}.\n */\nfunction withParsedSegments(message: AparteMessage, blocks: AparteStreamBlock[]): AparteMessage {\n if (message.role !== 'assistant' || message.segments?.length) return message;\n if (typeof message.content !== 'string' || !message.content.trim()) return message;\n const segments = parseMarkdownToSegments(message.content, { blocks });\n const onlyProse = segments.length <= 1 && segments.every((s) => s.type === 'text');\n return onlyProse ? message : markDerivedSegments({ ...message, segments });\n}\n\n/**\n * Say that THIS array of segments is core's reading of the message's own `content`,\n * not something the host wrote.\n *\n * Opening a stored conversation runs the parser over every markdown reply, so\n * `getMessages()` can differ from what the store holds. The marker lets the manager's\n * \"did anything change?\" comparison ignore a derived array, so merely reading a\n * conversation does not bump `updatedAt` or rewrite the stored row.\n *\n * Non-enumerable on purpose: a spread, `JSON.stringify` and a storage adapter never\n * carry it, so nothing of this reaches the wire. Which is also why every place that\n * copies a marked message has to put it back — {@link adoptMessageSegments} and the\n * stamping spread below both return a fresh object.\n */\nfunction markDerivedSegments<T extends object>(message: T): T {\n Object.defineProperty(message, APARTE_DERIVED_SEGMENTS, { value: true, enumerable: false });\n return message;\n}\n\n/** True when {@link withParsedSegments} is the author of this message's segments. */\nfunction hasDerivedSegments(message: AparteMessage): boolean {\n return (message as unknown as Record<symbol, unknown>)[APARTE_DERIVED_SEGMENTS] === true;\n}\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 {boolean} loading - The transcript is on its way: a conversation was chosen and its messages\n * are being fetched. The element draws two skeleton turns, marks its scroll surface\n * `aria-busy` and names the wait for a screen reader; `aparte-chat[center-empty]`\n * does not count a loading viewport as empty. Set by `AparteConversationController`\n * through its binding, or by hand from a loop of your own (`setLoading(true)`).\n * @attr {boolean} data-busy - Reflected BY the element while a turn streams: the transcript is\n * read-only meanwhile, and every bubble inside reads it (at connect, and when it changes). The\n * vanilla path derives it from the repository; a framework host sets it through\n * `setTranscriptBusy()`. Read-only from the outside.\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=var(--aparte-space-8)] - 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=var(--aparte-space-6)] - 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-thumb=var(--aparte-neutral)] - Colour of the transcript's scrollbar thumb. A host page with a scrollbar of its own sets this and the track so the chat's does not read as a second, foreign scrollbar.\n * @cssprop [--aparte-scrollbar-track=transparent] - Colour of the transcript's scrollbar track.\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-transcript-inset=var(--aparte-viewport-padding)] - Written BY the viewport on\n * the chat host: how far from the host's inline edge its rows start (padding plus the\n * scrollbar gutter, at the current container step). The composer pads by it, so the two\n * boxes share one edge at every width. Read-only from the outside.\n * @cssprop [--aparte-top-inset=0px] - How much of the transcript's top is covered by a header floating over it.\n * Set it on the chat, by hand: the first message starts below it, and a jump to a message lands below it.\n * @cssprop [--aparte-bottom-inset=0px] - How much of the transcript's bottom is covered\n * by content floating over it. Written by the viewport itself under\n * `[overlay-composer]` (never set it there — it would be overwritten); a host that\n * overlays a composer of its own, without the attribute, sets it by hand and the\n * spacer, the container padding and the scroll button all clear it.\n * @cssprop [--aparte-scroll-btn-size=var(--aparte-btn-size-lg)] - 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: 24rem\"></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 /** The scroll height at the last scroll event: how much it moved since bounds what churn can do. */\n private _lastScrollHeight = 0;\n /** When the transcript last mutated under our observer — an engine's settle adjustment follows one. */\n private _lastMutationAt = Number.NEGATIVE_INFINITY;\n /** How long after a mutation an engine's settle adjustment can arrive, and how far it moves. */\n private readonly _settleShadowMs = 100;\n private readonly _settleBoundPx = 100;\n /** When this component last moved `scrollTop` itself — see `_handleScroll`. \"Never\" is\n * -Infinity rather than 0: `performance.now()` can be under a second old. */\n private _ownScrollAt = Number.NEGATIVE_INFINITY;\n /** When the reader last touched the transcript (wheel, touch, pointer, key). */\n private _readerInputAt = Number.NEGATIVE_INFINITY;\n /** A decrease inside this window after our own scroll, with no gesture, is the browser's. */\n private readonly _ownScrollWindowMs = 1000;\n /** How long after a scroll of ours we keep re-anchoring while the layout churns.\n * Not `readonly`: a test shortens it to run past the window's end in a few frames. */\n private _settleWindowMs = 400;\n /**\n * Until when a smooth scroll of ours is in flight. A user send glides to the\n * bottom with `scrollTo({ behavior: 'smooth' })`, and the assistant placeholder and\n * first tokens land inside that glide. An instant `scrollTop = scrollHeight` write\n * cancels a running smooth scroll in every engine, so while this deadline is ahead\n * every bottom-pin RE-TARGETS the glide (a second smooth `scrollTo` is continuous)\n * instead of writing `scrollTop`; the browser's `scrollend` closes it early where\n * supported. Streaming is instant after that, as it must be to keep up with token\n * bursts.\n */\n private _glideUntil = 0;\n /** The glide's budget when `scrollend` is not there to close it: Chromium's smooth scroll over a viewport takes ~300–500 ms. */\n private _glideWindowMs = 450;\n /** Armed with each glide: when the window closes, a view still short of the bottom is pinned. */\n private _glideSettleTimer: ReturnType<typeof setTimeout> | null = null;\n /** Deadline of the settle in flight (0 = none). Pushed forward, never stacked. */\n private _settleUntil = 0;\n private _settleRafId: number | null = null;\n /** The keys that scroll a focused scroll container — the only keydowns that are a gesture. */\n private readonly _scrollKeys = new Set(['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' ']);\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 private _resizeObserver: ResizeObserver | null = null;\n private _mutationObserver: MutationObserver | null = null;\n /**\n * Watches the size of the bubbles the bottom spacer measures — the last question and\n * everything after it. A bubble that has never been on screen measures its\n * `content-visibility` fallback (320px) until it is shown, and taking its real height\n * is neither a mutation nor a resize of the scroll container: without this, a spacer\n * sized from two fallbacks stayed wrong once they came into view.\n */\n private _tailObserver: ResizeObserver | null = null;\n private _tail: Element[] = [];\n /** Each tail bubble's border-box height, as measured when it started being watched and as last reported. */\n private _tailHeights = new WeakMap<Element, number>();\n /**\n * Opening or closing a `<details>` — a tool row, a reasoning block, an artifact card, a host's\n * own — changes the transcript's height through an ATTRIBUTE, which neither observer sees: the\n * mutation observer watches children, and the resize observer leaves the wrapper out on\n * purpose. `toggle` does not bubble, so it is caught on the way down.\n */\n private _onDisclosureToggle = (): void => { this._scheduleSpacerUpdate(); };\n /** The `[overlay-composer]` shell this viewport floats in, if any. Read at observer setup, like `framework-managed`. */\n private _overlayRoot: HTMLElement | null = null;\n /** Watches the overlay shell's childList, so a stack child that mounts later (elicitation) gets observed too. */\n private _overlayObserver: MutationObserver | null = null;\n /** Last `--aparte-bottom-inset` written, in px — style writes only when it changes. */\n private _overlayInset = -1;\n /** Last `--aparte-transcript-inset` written on the host, in px — same write-on-change rule. */\n private _transcriptInset = -1;\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', 'loading'];\n }\n\n /**\n * The transcript is on its way: a conversation was chosen and its messages are\n * being fetched. Two skeleton turns stand where they will be, the scroll surface\n * is `aria-busy`, and a visually hidden line names the wait. Reflected as the\n * `loading` attribute — `AparteConversationController` sets it through its\n * binding; a loop of your own sets it by hand. Not the same thing as `data-busy`,\n * which is a reply streaming into a transcript that is already there.\n */\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(value: boolean) {\n // `presenceOn`, like every other boolean property in core: `''` is the ON that\n // React stringifies and Svelte 5 assigns, and `toggleAttribute` reads it as\n // falsy — the documented spelling turned the wait off.\n this.toggleAttribute('loading', presenceOn(value));\n }\n\n setLoading(loading: boolean): void {\n this.loading = loading;\n }\n\n /** Draw or remove the wait, from the attribute. Safe before the structure exists. */\n private _syncLoading(): void {\n const container = this._container;\n if (!container) return;\n const on = this.hasAttribute('loading');\n container.setAttribute('aria-busy', on ? 'true' : 'false');\n // Under `framework-managed` the host is the surface and its children are the\n // wrapper's to reconcile: the attribute and aria-busy say the transcript is on\n // its way, the wrapper draws what that looks like.\n if (this._frameworkManagedDOM) return;\n const existing = container.querySelector(':scope > .aparte-viewport-loading');\n if (!on) {\n existing?.remove();\n container.querySelector(':scope > .aparte-viewport-loading-status')?.remove();\n return;\n }\n if (existing) return;\n const cfg = resolveConfig(this);\n const skeleton = document.createElement('div');\n skeleton.className = 'aparte-viewport-loading';\n skeleton.setAttribute('aria-hidden', 'true');\n // Two turns: a short bubble on the end edge for the person, a few lines for the\n // reply — the kit's own skeleton recipe, sized by its tokens.\n skeleton.innerHTML =\n '<span class=\"aparte-skeleton aparte-skeleton--rect aparte-viewport-loading__user\"></span>'\n + '<span class=\"aparte-skeleton aparte-skeleton--text\"></span>'\n + '<span class=\"aparte-skeleton aparte-skeleton--text\"></span>'\n + '<span class=\"aparte-skeleton aparte-skeleton--text\"></span>'\n + '<span class=\"aparte-skeleton aparte-skeleton--text aparte-viewport-loading__last\"></span>';\n // Screen readers ignore aria-busy alone: the wait is also a line of text, off screen.\n const status = document.createElement('span');\n status.className = 'aparte-viewport-loading-status aparte-sr-only';\n status.setAttribute('role', 'status');\n // A sibling, not a child: inside the aria-hidden skeleton it would be hidden too.\n container.prepend(skeleton, status);\n // The text lands AFTER the node does. A live region created with its content\n // already in it is a new node, not a change — several screen readers announce\n // nothing at all. One frame is enough for the region to be registered; if the\n // wait ends first the node is gone and there is nothing to say.\n const say = (): void => { if (status.isConnected) status.textContent = cfg.t('loadingConversation'); };\n if (typeof requestAnimationFrame === 'function') requestAnimationFrame(say);\n else say();\n }\n\n constructor() {\n super();\n this._handleScroll = this._handleScroll.bind(this);\n this._handleScrollEnd = this._handleScrollEnd.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._updateTranscriptInset();\n /**\n * Empty every mounted transcript on the page.\n *\n * A command, not a notification: this is one of the events that travel the\n * other way, dispatched by YOUR app on `window` and listened for here. Every\n * connected `<aparte-chat-viewport>` clears its repository and its DOM and\n * answers with `aparte-reset-done`, so a \"New chat\" button in a shell that\n * holds no reference to the transcript still empties it. Carries no detail —\n * and therefore no target, so it clears every viewport, not one.\n *\n * @event aparte-reset\n */\n // History, deliberately OUT of the JSDoc: `gen-events-ref.mjs` copies an\n // `@event` body verbatim onto the public page, and this is repo business.\n // The listener had been live and undocumented since it existed — absent from\n // the events reference (the generator's name union read the dispatch sites\n // only, and core never dispatches this one), absent from the manifest (still\n // is, correctly: the element does not FIRE it), and covered by no test. A\n // listener that could have been deleted in silence.\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` follows it, so a chat\n * already on screen flips to RTL without 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 // The button's accessible name is locale text too — a language switch is\n // documented as live, and this was the one chrome string that stayed put.\n this._scrollBtn?.setAttribute('aria-label', resolveConfig(this).t('scrollToBottom'));\n // The transcript's own name is locale text on the same terms.\n const surface = this._frameworkManagedDOM ? this : this.querySelector('.aparte-viewport-container');\n surface?.setAttribute('aria-label', resolveConfig(this).t('transcript'));\n };\n\n /**\n * The transcript is a TAB STOP, with a name.\n *\n * A scrollable region that no element can hold focus in cannot be scrolled from\n * the keyboard in WebKit: Chromium and Firefox hand an unfocusable overflow box a\n * caret or a \"scroller\" tab stop of their own, Safari does not. So a plain-text\n * transcript — no links, no code blocks, nothing focusable inside — would be\n * unreadable past the first screen for a Safari keyboard user, with no error and\n * nothing on screen to say so.\n *\n * `tabindex=\"0\"` on the surface itself, in BOTH DOM modes. In framework mode the\n * scroll button is inside the host and stays tabbable while it is visually hidden,\n * but a tab stop that exists because a hidden button happens to sit there is a\n * coincidence, not an affordance.\n *\n * The name ships WITH the tab stop rather than after it: a focusable `role=\"log\"`\n * with no accessible name is announced as an unnamed region, which is a worse\n * answer than no tab stop at all. And the ROLE ships with both: a name on a\n * generic element is the same defect mirrored — `aria-label` is prohibited on an\n * element whose role resolves to none, which is what a bare custom element is. The\n * default mode already writes `role=\"log\"` on the container before this runs, so\n * the guard here is what gives the framework-mode host the same declaration.\n */\n private _nameScrollSurface(el: HTMLElement): void {\n el.setAttribute('tabindex', '0');\n if (!el.hasAttribute('role')) el.setAttribute('role', 'log');\n el.setAttribute('aria-label', resolveConfig(this).t('transcript'));\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 'loading':\n this._syncLoading();\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.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 /**\n * The id of the message currently streaming, if any — searched across the whole\n * TREE, and answering with an OFF-HEAD one first when there are two.\n *\n * Not a scan of `getMessages()`, the active path: a retry or an edit on an\n * earlier bubble calls `addSiblingOf`, which switches the branch to the new pending\n * reply, so the turn still being written leaves the path and a path scan would find\n * nothing, appending the rest of that turn to the message that just replaced it.\n *\n * A new pending reply is itself marked streaming, so \"the first one found\" would\n * answer the head and let the write through — the very case this exists to refuse.\n * Answering the off-head one instead makes `_activeMessageId` refuse while ANY turn\n * elsewhere in the tree is still open. That also refuses the new turn's own first\n * deltas for as long as the superseded one has not settled, which is the trade this\n * whole path already makes: losing the tail is visible, writing it onto someone\n * else's message is not.\n */\n private _streamingMessageId(): string | null {\n const head = this._repo.headId;\n let atHead: string | null = null;\n for (const message of this._repo.getAllMessages()) {\n if (!(message as { isStreaming?: boolean }).isStreaming) continue;\n if (message.id !== head) return message.id;\n atHead = message.id;\n }\n return atHead;\n }\n\n /**\n * While a reply streams, the transcript is read-only except for Stop.\n *\n * The streaming message's own footer is hidden, and the branch picker and the\n * retry/edit buttons of every OTHER message are disabled: swapping a branch mid-stream would\n * re-render the active path under the reply being written, and a retry would cut\n * that reply off to start another. The state that\n * decides it is the whole transcript's, so it lives on the viewport (`data-busy`)\n * and is pushed to the bubbles it holds; a bubble mounted later, under a\n * framework's DOM, reads the attribute itself when it connects.\n */\n private _syncBusy(): void {\n // One writer per mode. Under a framework the repository is not written during\n // a turn, so deriving it here could only ever say \"not busy\" — and did, under\n // all four wrappers: the host, which knows, writes it through\n // `setTranscriptBusy` instead, and this must not overwrite it from the repo.\n if (this._frameworkManagedDOM) return;\n this.setTranscriptBusy(this._streamingMessageId() !== null);\n }\n\n private _busy = false;\n\n /**\n * The transcript's read-only-while-streaming flag — `data-busy` on this element and\n * fanned out to the bubbles it holds. The vanilla path derives it from the\n * repository (`_syncBusy`); a framework host, whose messages live outside the\n * repository during a turn, writes it directly from its own streaming id.\n */\n setTranscriptBusy(busy: boolean): void {\n this._busy = busy;\n this.toggleAttribute('data-busy', busy);\n for (const bubble of this.querySelectorAll('aparte-chat-bubble')) {\n (bubble as unknown as { setTranscriptBusy?: (busy: boolean) => void }).setTranscriptBusy?.(busy);\n }\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 can be driven by `AparteClient`.\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._syncBusy();\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 * An id the tree does not hold is a silent no-op, deliberately: the message may have\n * been truncated away by an edit while a turn was still writing to it, and inventing\n * it back would put a message the reader deleted on screen. It does mean a late error\n * card can have nowhere to land — `aparte-message-error` still fires, so a host that\n * wants to show one has the event.\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 if (updates.status) this._syncBusy();\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 const stored = adoptMessageSegments({ ...message });\n // Same rule as `appendMessage`: a message that ARRIVES streaming (a pending\n // shell, a reply the host is about to write into) is a streaming one in the\n // tree, or the manual-stream path — which used to invent this message with\n // `isStreaming: true` — would stop making the transcript read-only.\n if (isAwaitingReply(message)) stored.isStreaming = true;\n this._repo.addOrUpdateMessage(this._repo.headId, stored);\n this._pruneRenderedBubbles();\n this._syncBusy();\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 /*\n * One grammar, both paths. A reply that arrives as a markdown STRING — from a\n * loop of your own, a store's `setMessages`, an `importTree` — went to the prose\n * renderer as it was, and the fence the stream would have made a `code` segment\n * (the card, the filename, the copy button) came out as a bare <pre>; a\n * `<think>` stayed text. The guide even told the consumer to run\n * `parseMarkdownToSegments` by hand first. Core knows the grammar: the same\n * parser the stream uses, with the same registered blocks, runs here. Only an\n * assistant's text — a person's fences are their own — and only when there is\n * something to split: plain prose keeps the cheaper content path.\n */\n message = withParsedSegments(message, resolveConfig(this).getStreamBlocks());\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 // The model's own flag, from the same predicate the bubble's `streaming`\n // attribute uses below. `updateMessage` maps a status to it; a message that\n // ARRIVES streaming (the client appends a pending shell, a host appends a\n // reply it is about to write into) used to reach the repository without it,\n // so `_streamingMessageId()` — and everything read-only that hangs off it —\n // could not see a turn that had not yet been updated once. Not for a\n // historical message: a conversation saved mid-reply is a record, not a turn.\n if (!options?.historical && isAwaitingReply(message)) stored.isStreaming = true;\n // Both branches above copy the object, and the marker is non-enumerable, so it\n // has to be put back on the copy the repository will hold — `getMessages()`\n // hands that very object to the store's comparison.\n if (hasDerivedSegments(message)) markDerivedSegments(stored);\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('data-role', message.role);\n if (message.compaction) bubble.setAttribute('data-kind', 'compaction');\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._syncBusy();\n // User sending always anchors to bottom regardless of scroll position — and it\n // GLIDES there. The glide begins before the spacer recalculation below, because\n // that recalculation pins the bottom synchronously: started after it, the smooth\n // scroll found the view already teleported (#57). Streaming auto-scroll stays\n // instant (via _autoScroll) so it can keep up with rapid token bursts — once the\n // glide has ended; inside it, every pin re-targets the glide.\n if (message.role === 'user') {\n this._isAutoScrollEnabled = true;\n this._smoothScrollToBottom();\n }\n this._recalculateSpacer();\n if (message.role === 'user') {\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) 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 // A retry starts a turn here: the sibling arrives pending, and the model's\n // flag has to say so from this moment (see `appendMessage`), not from the\n // first `updateMessage` — the rebuild below already pushes the busy state.\n const stored: AparteMessage = { ...newMessage };\n if (isAwaitingReply(newMessage)) stored.isStreaming = true;\n this._repo.addOrUpdateMessage(parentId, stored);\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 // Not while a reply streams: the active path must not change under it (the\n // pickers are disabled for the same reason — see `setTranscriptBusy`; this covers\n // a programmatic call). The stored flag, so the arrows and this guard agree in\n // both modes — the repository cannot answer for a framework-managed turn.\n if (this._busy) return;\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 (a React consumer). 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 // One grammar, THREE paths. `appendMessage` has run the stream's parser over a\n // reply that arrived as a markdown string since 0.16 — and its docblock names\n // this one as covered. It was not: a tree went to the repository raw, and since\n // the controller runs `setMessages` and then this, the raw snapshot won on every\n // conversation that had ever persisted a tree. The fence came back as bare prose.\n const blocks = resolveConfig(this).getStreamBlocks();\n this._repo.import({\n ...tree,\n messages: tree.messages.map((entry) => {\n const parsed = withParsedSegments(entry.message, blocks);\n const message = adoptMessageSegments(parsed);\n if (hasDerivedSegments(parsed)) markDerivedSegments(message);\n return { ...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 // Every branch, not the active path: a retry or an edit takes a message off\n // the path with whatever it carried, and `_repo.clear()` below makes those\n // unreachable — the one case where nothing else can ever release them.\n for (const message of this._repo.getAllMessages()) {\n revokeAttachmentUrls(message.attachments);\n }\n }\n this._repo.clear();\n // An empty transcript streams nothing: the busy flag is derived from the\n // repository, and this is the one path that empties it without a status.\n this._syncBusy();\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. Turned off, it stays off through the scroll that comes\n * next — a jump of the page's own, smooth or not — and comes back once the reader is at\n * the bottom again.\n */\n setAutoScroll(enabled: boolean): void {\n this._isAutoScrollEnabled = enabled;\n // Off is the reader's choice, and the scroll that follows is theirs: marked as their\n // gesture, so the first frames of a smooth jump — a few pixels, still within the bottom\n // threshold — are not read as the reader staying at the bottom and armed again.\n if (!enabled) this._readerInputAt = performance.now();\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('data-role', message.role);\n if (message.compaction) bubble.setAttribute('data-kind', 'compaction');\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._syncBusy();\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 // `_scrollToBottom` turns a pending smooth request into the glide itself.\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) this._scrollToBottom(); });\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 this._nameScrollSurface(container);\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 // An attribute set before the structure existed is honoured now.\n this._syncLoading();\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', resolveConfig(this).t('scrollToBottom'));\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 this._nameScrollSurface(this);\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', resolveConfig(this).t('scrollToBottom'));\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 /**\n * Only the inputs that can SCROLL count as the reader's hand: a wheel notch, a\n * touch, a navigation key, a press in the scrollbar's gutter. A press on a control\n * inside the transcript — a branch arrow, a copy button — is not a scroll gesture,\n * and counting it disarmed the follow through the swap it triggered (react-webkit,\n * 1 run in 6: the rebuild's height churn moved scrollTop within the second after\n * the click, and the click made that look like the reader's).\n */\n /**\n * Is a pointer pressed on the surface right now? A press on the TEXT is not a scroll\n * gesture (`_noteReaderInput` counts only the gutter), but a hand that is down and\n * dragging a selection past the edge scrolls the container all the same — and that\n * decrease is the reader's. Held from the press to its release, wherever it lands.\n */\n private _pointerHeld = false;\n private readonly _holdPointer = (): void => { this._pointerHeld = true; };\n private readonly _releasePointer = (): void => { this._pointerHeld = false; };\n\n private readonly _noteReaderInput = (e: Event): void => {\n if (e.type === 'keydown' && !this._scrollKeys.has((e as KeyboardEvent).key)) return;\n if (e.type === 'pointerdown') {\n const container = this._container;\n if (!container) return;\n const { clientX } = e as PointerEvent;\n const rect = container.getBoundingClientRect();\n const rtl = getComputedStyle(container).direction === 'rtl';\n const inGutter = rtl\n ? clientX <= rect.right - container.clientWidth\n : clientX >= rect.left + container.clientWidth;\n if (!inGutter) return;\n }\n this._readerInputAt = performance.now();\n // The reader's hand ends a glide. A physical wheel cancels a programmatic smooth\n // scroll on its own; a synthetic one (CI's `page.mouse.wheel`, some assistive\n // input) does not, and the animation ran on to the bottom over the gesture —\n // `streaming-progressive` on CI: \"the scroll-up gesture did not take, top is\n // still 477px\". Writing the CURRENT position is how a running smooth scroll is\n // stopped where it is, in every engine; the reader's own scroll then lands on it.\n //\n // For the WHEEL and a TOUCH only. A scroll key starts the ENGINE'S own smooth\n // animation, and this write landed on it and killed it — the keyboard-scroll\n // spec on vanilla-webkit pressed a key inside a seed send's glide window and the\n // transcript did not move (main CI, flaky). A key closes the window like any\n // reader input; the key's scroll needs no help from us.\n if (this._gliding() && this._container) {\n this._glideUntil = 0;\n if (e.type === 'wheel' || e.type === 'touchmove') {\n this._ownScrollAt = performance.now();\n const here = this._container.scrollTop;\n this._container.scrollTop = here;\n }\n }\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._container?.addEventListener('scrollend', this._handleScrollEnd, { passive: true });\n // The reader's hand on the transcript, so `_handleScroll` can tell their scroll\n // from one the browser made while settling ours.\n // `touchmove`, not `touchstart`: a tap on a control fires touchstart too, and\n // a tap is no more a scroll gesture than a click — a finger that scrolls moves.\n for (const type of ['wheel', 'touchmove', 'pointerdown', 'keydown'] as const) {\n this._container?.addEventListener(type, this._noteReaderInput, { passive: true });\n }\n this._container?.addEventListener('pointerdown', this._holdPointer, { passive: true });\n document.addEventListener('pointerup', this._releasePointer, true);\n document.addEventListener('pointercancel', this._releasePointer, 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 // First, because everything below reads the geometry it changes: in\n // overlay mode a composer that grew is THE resize being reported, and\n // re-anchoring against the stale inset would land the reader short.\n this._updateOverlayInset();\n this._updateTranscriptInset();\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 // Overlay mode: the shell's bottom stack (elicitation, an above-composer\n // row, the composer) floats OVER this scroll surface, so its height is part\n // of the geometry this observer exists to watch — and the container alone\n // no longer says it: with the viewport absolute over the shell, a composer\n // that grows resizes NOTHING this observer was looking at. Observe every\n // stack child with the SAME observer; the callback already does everything\n // an inset change needs (write the var, re-anchor a pinned reader, spacer,\n // button).\n // Bare attribute selector on purpose: the shell is <aparte-chat> in vanilla,\n // a [data-aparte-chat] div in React/Vue/Svelte, Angular's inner\n // .aparte-chat-container - and a hand-rolled host that sets the attribute on\n // its own shell gets the measurement for free (the CSS readers are\n // unconditional; only the recipe rules are qualified).\n this._overlayRoot = this.closest('[overlay-composer]');\n if (this._overlayRoot && this._resizeObserver) {\n for (const child of this._overlayStack()) this._resizeObserver.observe(child);\n // A stack child can mount later (elicitation appears on request):\n // observe it from that moment. childList only, never subtree — what is\n // inside those children is the host's business, and their size changes\n // already reach the ResizeObserver.\n this._overlayObserver = new MutationObserver(() => {\n for (const child of this._overlayStack()) this._resizeObserver?.observe(child);\n this._updateOverlayInset();\n });\n this._overlayObserver.observe(this._overlayRoot, { childList: true });\n this._updateOverlayInset();\n }\n\n this._mutationObserver = new MutationObserver((mutations) => {\n this._lastMutationAt = performance.now();\n // Keep the sticky scroll button trailing after framework appends.\n if (this._frameworkManagedDOM) this._keepScrollButtonLast();\n // A user bubble arriving is a send, whichever framework rendered it: the pin\n // this observer queues below must glide, not jump (#57). The four wrappers ask\n // the same thing one event earlier through requestSmoothScroll(); a host that\n // renders bubbles itself and never heard of that call gets the glide all the\n // same. The viewport owns it.\n // A send is ONE user bubble, appended last. A branch swap or a transcript rebuild\n // re-adds many bubbles in one batch, user ones included, with auto-follow armed\n // again — taken for a send, the swap glided instead of pinning, the settle held\n // its hand for the glide, and the swap's height churn left the view short with a\n // scroll-to-bottom button over a reader who never left (CI, bubble-actions:370).\n if (this._isAutoScrollEnabled && !this._gliding()) {\n const added = mutations\n .flatMap((m) => Array.from(m.addedNodes))\n .filter((n): n is Element => n instanceof Element && n.tagName === 'APARTE-CHAT-BUBBLE');\n const only = added.length === 1 ? added[0] : undefined;\n if (only && only.getAttribute('data-role') === 'user' && only.parentElement) {\n const bubbles = only.parentElement.querySelectorAll('aparte-chat-bubble');\n if (bubbles[bubbles.length - 1] === only) this._smoothScrollOnce = true;\n }\n }\n // The gate is tested TWICE: when the frame is queued, and again when it\n // runs. The second test is what lets a reader leave.\n //\n // During a stream a frame is nearly always queued, and it used to scroll\n // unconditionally when it ran. So a reader who wheeled up was disarmed by\n // that gesture (`_handleScroll` sees the decrease) — and one frame later the\n // already-queued scroll dragged them back to the bottom, whose scroll event\n // re-armed auto-follow. Every attempt to read above the stream lasted one\n // frame (\"on a du mal à remonter\", reported from a real session). The\n // run-time check reads the INTENT flag, which only a real decrease disarms,\n // so it cannot fall into the trap an earlier attempt did — that one\n // re-tested `_isAtBottom()` at run time, which a branch swap's height churn\n // makes briefly false, and it refused to re-anchor a reader who was at the\n // bottom (`bubble-actions.spec.ts` on WebKit, 3 runs out of 3). Growth does\n // not decrease `scrollTop` and a clamp lands at the bottom, so the flag\n // stays armed through a swap; only a person turns it off.\n if (this._isAutoScrollEnabled) {\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) this._scrollToBottom(); });\n }\n // Recalculate spacer when DOM mutates (new bubble added, Angular re-render).\n this._scheduleSpacerUpdate();\n // The turn the spacer measures moves only when a bubble comes or goes. Re-read on\n // every record, it walked the whole transcript per streamed token.\n const movesBubbles = (nodes: NodeList): boolean => Array.from(nodes).some((n) =>\n n instanceof Element && (n.tagName === 'APARTE-CHAT-BUBBLE' || n.querySelector('aparte-chat-bubble') !== null));\n if (mutations.some((m) => movesBubbles(m.addedNodes) || movesBubbles(m.removedNodes))) this._observeTail();\n });\n\n // Height only, against what was measured when the bubble started being watched: a\n // width that moves with a scrollbar says nothing about what the spacer measures, and\n // re-measuring on it let a spacer and a scrollbar chase each other.\n this._tailObserver = new ResizeObserver((entries) => {\n let changed = false;\n for (const entry of entries) {\n const height = Math.round(entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height);\n if (this._tailHeights.get(entry.target) !== height) changed = true;\n this._tailHeights.set(entry.target, height);\n }\n if (changed) this._scheduleSpacerUpdate();\n });\n this._observeTail();\n\n // Framework-managed: bubbles are direct children of the host (no wrapper).\n this.addEventListener('toggle', this._onDisclosureToggle, true);\n\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 /**\n * The overlay shell's element children that are the floating bottom stack: not this\n * viewport, and not the scroll rail, which floats over the transcript by design —\n * shell.css names it the same way (`:not(aparte-chat-viewport, aparte-scroll-rail)`).\n * Counted, a rail centred on the transcript set the inset to the distance from its\n * own top edge (398px on the vanilla example) and held it there while the composer\n * grew under a draft.\n */\n private _overlayStack(): HTMLElement[] {\n if (!this._overlayRoot) return [];\n return Array.from(this._overlayRoot.children).filter(\n (el): el is HTMLElement => el instanceof HTMLElement && el !== (this as HTMLElement)\n && el.tagName.toLowerCase() !== 'aparte-scroll-rail',\n );\n }\n\n /**\n * Measure how much of this viewport the overlay stack covers, and publish it as\n * `--aparte-bottom-inset` — the `::after` spacer (framework mode), the\n * container's own padding (core mode) and the scroll button's `bottom` all read\n * it (shell.css). Measured from the stack's highest visible top edge rather\n * than by summing heights, so margins and anything the host renders between\n * the rows count themselves. No-op outside overlay mode, and when the value\n * has not changed — a var write invalidates layout, and this runs from the\n * ResizeObserver its own write can re-trigger (core mode observes the\n * container's content box, which the padding is part of).\n */\n private _updateOverlayInset(): void {\n if (!this._overlayRoot) return;\n const bottom = this.getBoundingClientRect().bottom;\n let top = Infinity;\n for (const el of this._overlayStack()) {\n const rect = el.getBoundingClientRect();\n if (rect.height > 0) top = Math.min(top, rect.top);\n }\n const inset = top === Infinity ? 0 : Math.max(0, Math.round(bottom - top));\n if (inset === this._overlayInset) return;\n this._overlayInset = inset;\n this.style.setProperty('--aparte-bottom-inset', `${inset}px`);\n }\n\n /**\n * Publish, on the chat host, how far from the host's inline edge the rows start —\n * the transcript's padding plus the scrollbar gutter the scroller reserves on both\n * edges, at whatever step its container query has taken — as\n * `--aparte-transcript-inset`. The composer pads by it (composer.css), so its box\n * lands on the row's box at every width. The composer cannot know the gutter\n * and a container query cannot reach it, so this is the one place both boxes read\n * it from. Written only when the value changes, for the\n * same reason `_updateOverlayInset` is. On the HOST, not on this element: the\n * composer is a sibling, and a custom property only travels down.\n */\n private _updateTranscriptInset(): void {\n const host = this.parentElement;\n if (!host) return;\n const ref = this.querySelector<HTMLElement>('.aparte-messages-wrapper') ?? this._container ?? this;\n const pad = parseFloat(getComputedStyle(ref).paddingInlineStart) || 0;\n const rtl = getComputedStyle(host).direction === 'rtl';\n const refRect = ref.getBoundingClientRect();\n const hostRect = host.getBoundingClientRect();\n const raw = rtl ? hostRect.right - refRect.right : refRect.left - hostRect.left;\n const inset = Math.max(0, Math.round((raw + pad) * 10) / 10);\n if (inset === this._transcriptInset) return;\n this._transcriptInset = inset;\n host.style.setProperty('--aparte-transcript-inset', `${inset}px`);\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 * With one exception, measured after the queued frames started re-reading the\n * flag: a decrease the BROWSER makes while settling a scroll of ours. When a\n * stream ends the action bar appears (+34px) and the bottom spacer gives the\n * same 34px back in the same frame; through that churn WebKit moved `scrollTop`\n * from 829 to 804 — a 25px decrease, no reader anywhere near it — and the\n * decrease disarmed the follow that was in the middle of landing, leaving the\n * transcript 25px short and a scroll-to-bottom button on a reader who never\n * left (vanilla-webkit and react-webkit, 3 runs out of 3; Chromium settles the\n * same churn without moving). So a decrease is the reader's unless both hold: it\n * comes within a second of a scroll this component asked for, and no scroll\n * gesture touched the transcript in that second. A real gesture always leaves a\n * trace (`_noteReaderInput`); a jump the reader did not make either\n * (find-in-page, a host's own `scrollTo`) still disarms, except in that\n * one-second shadow of our own scroll.\n *\n * The size of the decrease alone cannot tell the two apart, and it took three\n * versions to learn what can: a 100px cap was refuted by a React branch swap (the\n * rebuild flickers the height by ~200px and WebKit moves scrollTop by as much); no\n * cap at all snapped a reader drag-selecting text upward back to the bottom on the\n * next token, because during a stream every token refreshes `_ownScrollAt` and\n * the shadow never closes; and bounding it by the churn alone — a decrease no\n * larger than the height change seen at THIS event — was refuted by the settle on\n * WebKit, which hands the event a decrease its regrown height no longer explains\n * (−82 against +30, −27 against nothing, one frame after our own pin; the follow\n * disarmed, the transcript 81px short for good). Three signals decide now. The\n * HAND: a scroll gesture leaves a trace in `_readerInputAt` (wheel, touch, a\n * press in the gutter, a scroll key), and a press on the text that drags a\n * selection past the edge holds the pointer (`_pointerHeld`) while the container\n * scrolls — either one is the reader. The CHURN, still: a decrease no larger than\n * the height change is the layout's. And the SETTLE: a decrease of at most\n * `_settleBoundPx` within `_settleShadowMs` of a transcript mutation we observed\n * is the engine finishing that mutation's layout — where a host's own `scrollTo`\n * or a tool's scroll-into-view comes with no mutation, or moves by far more, and\n * disarms the way a reader does. Measured: the overlay spec's `scrollTop = 0` and\n * Playwright's scroll-into-view before a click both re-pinned in a loop under a\n * hand-only rule.\n */\n const top = this._container.scrollTop;\n const height = this._container.scrollHeight;\n const drop = this._lastScrollTop - top;\n const churn = Math.abs(height - this._lastScrollHeight);\n const now = performance.now();\n // Ours when it falls in the shadow of our own scroll and no hand was on the surface\n // in that window. It used to also require the decrease to be no larger than the\n // height churn visible at THIS event — but an engine that clamps or re-anchors\n // scrollTop between two layouts of a settling reply hands the event a decrease\n // the regrown height no longer explains: measured on WebKit, −82 against a churn\n // of +30, and −27 against none, one frame after our own pin, and the follow was\n // disarmed as \"the reader went up\". The reader's hand is the evidence, and it is\n // recorded (wheel, touch, gutter press, key on the container) or HELD (a press on\n // the text, dragging a selection past the edge); the churn was a proxy for both.\n // An engine settling a reply it just re-laid out moves scrollTop within a few\n // frames of the mutation and by tens of pixels; a host's own jump, or a tool's\n // scroll-into-view before a click, comes with no mutation, or moves it by hundreds.\n // Unless the content got SHORTER since the last event: then the decrease is the\n // engine clamping `scrollTop` to a smaller maximum, whatever its size — no reader\n // shortens the transcript. Measured on react-webkit under load at the end of a\n // reply: the height dipped and recovered between two events (1199 at the last,\n // 1833 at its peak, 1068 at this one), the clamp read 665 -> 338, a decrease past\n // the bound, and the follow was disarmed with the view 490px short for good.\n //\n // Not bounded by how much the height fell, which is the one known limit: the height\n // dips and recovers between two events, so the endpoints understate the clamp (131\n // against 327 in the measurement above), and the tallest height in between is not\n // seen in time to bound it (tried: the flake came back, 8 runs in 60). So any jump up\n // whose first event sees a shorter transcript, within a second of a scroll of ours, is\n // read as a clamp, and an armed follow pins it back to the bottom — mid-stream that is\n // almost every jump into history never rendered, whose bubbles leave their 320px\n // fallback as they come into view. The scroll rail stops the follow before it jumps\n // (`setAutoScroll(false)`); a host's own jump, or find-in-page, does not.\n const shrank = height < this._lastScrollHeight - 1;\n const afterMutation = now - this._lastMutationAt < this._settleShadowMs && (drop <= this._settleBoundPx || shrank);\n const settlingOurs = now - this._ownScrollAt < this._ownScrollWindowMs\n && now - this._readerInputAt >= this._ownScrollWindowMs\n && !this._pointerHeld\n && (drop <= churn + 2 || afterMutation);\n const readerWentUp = drop > 1 && !settlingOurs;\n // The same decrease, asked of the reader's HAND rather than of its size. The\n // generosity below is for layout drift; a gesture we can see is not drift, however\n // few pixels it moved.\n const readerHandOnIt = drop > 1 && now - this._readerInputAt < this._ownScrollWindowMs;\n this._lastScrollTop = top;\n this._lastScrollHeight = height;\n\n if (this._isAtBottom() && !readerHandOnIt) {\n // `_isAtBottom()` stays deliberately generous (`_scrollThreshold`, 50px): a\n // few pixels of drift must not read as \"the reader walked away\".\n //\n // But generosity that outranks the reader PINS them. A wheel notch over the\n // transcript moves WebKit ~33px at a time; 33 < 50, so this branch re-armed\n // the follow, and the settle chain — which re-anchors every frame while\n // armed — undid the notch one millisecond later. Measured in CI: wheel at\n // 135ms, the reader at 565, `scrollTop = 598` from `_settleAtBottom`'s step\n // at 155ms, repeat for every one of twelve notches. Mid-stream on WebKit the\n // reader simply could not read back. So the branch yields when the decrease\n // came with a gesture: `_readerInputAt` already tells the two apart, and\n // `settlingOurs` already trusts it — this is the same evidence, read on the\n // arming side, where it was missing.\n this._isAutoScrollEnabled = true;\n } else if (readerWentUp) {\n this._isAutoScrollEnabled = false;\n } else if (drop > 1 && settlingOurs && this._isAutoScrollEnabled) {\n // Armed, and a gap the reader did not open. Nothing else will close it: the\n // rebuild's mutations are over, the host's border box did not change (so the\n // ResizeObserver is silent in framework mode), and the settle may be spent.\n // Measured on react-webkit: top 763 -> 759 -> 720 while the max churned\n // 891 -> 1091 -> 891, leaving the transcript 171px short with the follow\n // still armed and no code path acting on it. Classifying the churn was only\n // half the job — the other half is doing something about it.\n //\n // Gated on `settlingOurs`, so it can never reach a reader: a drag-selection\n // upward (a decrease with the height standing still) and a find-in-page jump\n // (outside the one-second shadow) both take the disarm branch above.\n //\n // And gated on `drop > 1`, the same threshold `readerWentUp` uses: only a\n // DECREASE is a gap the layout opened, which is the whole of the measured\n // mechanism. A scroll of ours that is still moving DOWN is `settlingOurs`\n // too (`drop` is negative, so the churn test passes trivially) — and that is\n // every frame of a native smooth scroll: `requestSmoothScroll()`, the\n // scroll-to-bottom button, the glide after a user's send. Re-anchoring one\n // of those frames assigns `scrollTop`, which per CSSOM-View performs an\n // instant scroll and ABORTS the running animation, so every glide became a\n // one-frame stutter and a jump.\n this._settleAtBottom();\n }\n this._updateScrollButton();\n }\n\n private _scrollToBottom(): void {\n if (!this._container) return;\n // A smooth scroll was asked for (a user send, by whichever path — appendMessage,\n // a wrapper's requestSmoothScroll(), or a user bubble the mutation observer saw\n // arrive): the FIRST pin after it is the glide, not a jump. In framework-managed\n // mode the observer's own pin used to run first and instant — 630 px in one frame\n // measured on React — and the smooth request was only honoured later, by\n // _autoScroll, on a view that had already teleported.\n if (this._smoothScrollOnce) {\n this._smoothScrollOnce = false;\n this._smoothScrollToBottom();\n return;\n }\n if (this._gliding()) { this._retargetGlide(); return; }\n this._ownScrollAt = performance.now();\n this._container.scrollTop = this._container.scrollHeight;\n this._settleAtBottom();\n }\n\n private _gliding(): boolean {\n return performance.now() < this._glideUntil;\n }\n\n /** A second smooth `scrollTo` is continuous where an instant write is a cut. */\n private _retargetGlide(): void {\n if (!this._container) return;\n this._ownScrollAt = performance.now();\n this._container.scrollTo({ top: this._container.scrollHeight, behavior: 'smooth' });\n }\n\n /**\n * The browser says a scroll came to rest — the glide is over ONLY if it rested at the\n * bottom. WebKit fires `scrollend` when a smooth scroll is REPLACED by another (our own\n * re-target), 45–60 ms into the glide and hundreds of pixels short; taken at its word,\n * that would close the window and the next pin would be the cut this guard exists\n * to prevent. Short of the bottom, the budget keeps bounding the window.\n */\n private _handleScrollEnd(): void {\n if (!this._container) return;\n const max = this._container.scrollHeight - this._container.clientHeight;\n if (max - this._container.scrollTop <= 1) this._glideUntil = 0;\n }\n\n /**\n * Confirm over the next few frames that we actually reached the bottom.\n *\n * One assignment is not enough. Content can settle in several layout passes\n * (1118 → 1121 → 1152 px on Safari in framework mode), and `scrollTop =\n * scrollHeight` run against a middle pass clamps to that layout's max and leaves\n * the rest unclosed. Auto-follow stays armed the whole time, so the component is\n * not disarmed — it is SATISFIED: `_isAtBottom()` answers \"yes\" for any gap under\n * `_scrollThreshold` (50), which is the right rule for keeping auto-follow armed\n * and the wrong one as a definition of \"anchored\".\n *\n * A BOUNDED retry, not one corrective frame: a single frame lands on the same\n * stale layout and can leave a wider gap than doing nothing. Bounded so it always\n * terminates; re-reads `_isAutoScrollEnabled` every frame so a reader who scrolls\n * away mid-settle is left alone.\n *\n * Bounded by TIME, and it does not stop at the first closed gap. A branch swap at\n * the bottom of a long transcript churns the scrollable max (891 -> 1091 -> 891):\n * the gap is closed for a frame against the tall layout, the height falls back with\n * WebKit holding `scrollTop`, and the transcript would stand short with auto-follow\n * still armed. A frame count is a proxy for time that fails exactly on the slow\n * engine, so the budget is real milliseconds, and a closed gap only ends the chain\n * when the window is over. One chain, not one per call: during a stream every token\n * would otherwise start its own.\n */\n private _settleAtBottom(): void {\n this._settleUntil = performance.now() + this._settleWindowMs;\n if (this._settleRafId !== null) return; // one chain; the deadline re-arms it\n const step = (): void => {\n this._settleRafId = null;\n // A detached viewport has nothing to settle. `_cleanup` cancels the chain on\n // disconnect, but a document that goes away whole — a closed window, a test\n // environment torn down — fires no disconnectedCallback, and the next frame\n // then ran against globals that no longer existed (27 errors after teardown\n // in the wrapper suites, whose rAF stub is a 0ms timer that survives it).\n if (!this.isConnected || !this._container || !this._isAutoScrollEnabled) { this._settleUntil = 0; return; }\n const max = this._container.scrollHeight - this._container.clientHeight;\n // A closed gap is NOT the end: the churn re-opens it (891 -> 1091 -> 891).\n // And an open gap during a glide is the glide itself, not a shortfall.\n if (max - this._container.scrollTop > 1 && !this._gliding()) {\n this._ownScrollAt = performance.now();\n this._container.scrollTop = max;\n }\n if (performance.now() < this._settleUntil) this._settleRafId = requestAnimationFrame(step);\n else this._settleUntil = 0;\n };\n this._settleRafId = requestAnimationFrame(step);\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 this._ownScrollAt = performance.now();\n if (typeof this._container.scrollTo === 'function' && !this._prefersReducedMotion()) {\n this._glideUntil = performance.now() + this._glideWindowMs;\n this._container.scrollTo({ top: this._container.scrollHeight, behavior: 'smooth' });\n // A glide can miss: its target was read before a height churn, or the engine\n // dropped it. Nothing re-pins once the mutations are over, so the glide arms\n // the pin itself — an instant one, with the settle chain, once the window is\n // closed and only if the view is still short and the reader still followed.\n if (this._glideSettleTimer !== null) clearTimeout(this._glideSettleTimer);\n this._glideSettleTimer = setTimeout(() => {\n this._glideSettleTimer = null;\n if (!this._container || !this._isAutoScrollEnabled || this._gliding()) return;\n const max = this._container.scrollHeight - this._container.clientHeight;\n // Above where the last scroll event left it, the view was moved by someone the\n // scroll handler has not heard from yet — a host's jump, a reader — and the\n // glide never moves up: that one is the handler's to judge, not ours to undo.\n if (this._container.scrollTop < this._lastScrollTop - 1) return;\n if (max - this._container.scrollTop > 1) this._scrollToBottom();\n }, this._glideWindowMs + 16);\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 would make the button lie whenever the two diverge —\n * `navigateBranch` deliberately disarms auto-follow, so swapping a branch while\n * already at the bottom of a scrollable transcript would leave the button offering\n * to scroll nowhere. 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 const btn = this._scrollBtn;\n if (!btn) return;\n const hidden = this._isAtBottom();\n // This runs on every scroll event of a streaming transcript, so it writes only\n // what changes: `classList.toggle` is already idempotent, `setAttribute` is not\n // — it produces a mutation record whatever the value it writes, and the two\n // below sit in the hottest path the component has. The state is read back from\n // the button rather than cached in a field, because the button is rebuilt on\n // every re-render and a cached flag would skip the stamp on the new one.\n if (btn.classList.contains('aparte-scroll-btn--hidden') === hidden\n && btn.hasAttribute('tabindex') === hidden) return;\n btn.classList.toggle('aparte-scroll-btn--hidden', hidden);\n // Hidden is opacity 0 and no pointer, which the tab order cannot see: the\n // button stayed a stop while invisible, so a keyboard user landed on nothing\n // between the transcript and the composer — and the transcript's own stop,\n // once it had one, pushed the composer past the eighth Tab on the vanilla\n // example. A control nobody can see is not a control anybody can reach.\n if (hidden) {\n btn.setAttribute('tabindex', '-1');\n btn.setAttribute('aria-hidden', 'true');\n } else {\n btn.removeAttribute('tabindex');\n btn.removeAttribute('aria-hidden');\n }\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('data-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 // The question lands below a header floating over the transcript (`--aparte-top-inset`,\n // the surface's scroll padding), not under it at the very top.\n const covered = parseFloat(getComputedStyle(this._container).scrollPaddingTop) || 0;\n const needed = this._container.clientHeight - covered - 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 /** Point `_tailObserver` at the last question and the bubbles after it, and only those. */\n private _observeTail(): void {\n if (!this._tailObserver) return;\n const bubbles: Element[] = Array.from(this.querySelectorAll('aparte-chat-bubble'));\n let from = bubbles.length;\n while (from > 0 && bubbles[from - 1]!.getAttribute('data-role') !== 'user') from--;\n const tail = from > 0 ? bubbles.slice(from - 1) : [];\n for (const el of this._tail) if (!tail.includes(el)) this._tailObserver.unobserve(el);\n for (const el of tail) {\n if (this._tail.includes(el)) continue;\n // The height the spacer measures it at right now — its fallback, if it was never\n // shown — so the report that brings its real height is seen as a change.\n this._tailHeights.set(el, Math.round((el as HTMLElement).offsetHeight));\n this._tailObserver.observe(el, { box: 'border-box' });\n }\n this._tail = tail;\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 _cleanup(): void {\n this._container?.removeEventListener('scroll', this._handleScroll);\n this._container?.removeEventListener('scrollend', this._handleScrollEnd);\n if (this._glideSettleTimer !== null) { clearTimeout(this._glideSettleTimer); this._glideSettleTimer = null; }\n for (const type of ['wheel', 'touchmove', 'pointerdown', 'keydown'] as const) {\n this._container?.removeEventListener(type, this._noteReaderInput);\n }\n this._container?.removeEventListener('pointerdown', this._holdPointer);\n document.removeEventListener('pointerup', this._releasePointer, true);\n document.removeEventListener('pointercancel', this._releasePointer, true);\n this._pointerHeld = false;\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._tailObserver?.disconnect();\n this._tailObserver = null;\n this._tail = [];\n this.removeEventListener('toggle', this._onDisclosureToggle, true);\n this._overlayObserver?.disconnect();\n this._overlayObserver = null;\n this._overlayRoot = null;\n this._overlayInset = -1;\n this._transcriptInset = -1;\n this._resizeObserver = null;\n this._mutationObserver = null;\n if (this._spacerRafId !== null) {\n cancelAnimationFrame(this._spacerRafId);\n this._spacerRafId = null;\n }\n // A custom element is re-connected on every DOM move, so a settle left in flight\n // would accumulate one chain per move.\n if (this._settleRafId !== null) {\n cancelAnimationFrame(this._settleRafId);\n this._settleRafId = null;\n }\n this._settleUntil = 0;\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';\nimport { presenceOn } from '../../utils/presence.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, and `'none'` says this panel has NO act left for that button,\n * so it is not drawn. The chips are the navigation through a form's questions, so the\n * button never means \"next\".\n *\n * `'none'` is for a panel whose options settle on the first click (an approval, a\n * single-choice question): a button beside them would sit permanently disabled,\n * meaning nothing. A control that is never the way forward is not disabled, it is\n * absent.\n *\n * Named rather than inlined at each of its six readers, so they share one list.\n */\nexport type AparteComposerPanelMode = '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 — inside the descendant you marked\n * `data-aparte-panel-host` if there is one, else right after the first input —\n * and `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:\n * `aparte-message-start` sets it, and any of `aparte-message-done` /\n * `aparte-message-error` / `aparte-message-aborted` clears it (and evicts an open\n * panel). `AparteClient` dispatches them on the chat host, bubbling, so they reach\n * both this element's `window` listener and the host-bound readers; a host that runs\n * its own loop dispatches the same two (start, done) the same way, with\n * `detail.targetId` set to this composer's `target` — that is the whole contract,\n * documented under\n * \"Make the composer follow your turn\" in the bring-your-own-loop guide. 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 * `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. Changing it here reaches\n * the inputs already on the page too.\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.\n * @attr {boolean} data-panel-active - Reflected BY the element while a panel (an elicitation, an\n * approval) is mounted inside it; the shipped stylesheet keys off it. Read-only.\n * @attr {string} data-panel-mode - Reflected BY the element while a panel is active: what the send\n * button does meanwhile (`'none'` removes it — through this attribute, not by reaching into the\n * button). Read-only.\n * @attr {boolean} data-model-gated - Reflected BY the element when `requireModelSelection` is on and\n * no model is selected: sending is blocked and the shipped stylesheet greys the composer. Read-only.\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=var(--aparte-btn-size-lg)] - 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 * @cssprop [--aparte-viewport-padding=var(--aparte-space-8)] - Inline gutter between this element and the\n * chat's edge — the same token the transcript reads, so the shell keeps\n * `.aparte-message`'s column BELOW the max-width above as well as above it, where the\n * two would otherwise diverge by the transcript's whole inset. Only the inline halves\n * are read here: the block axis is spaced by the viewport above and\n * `--aparte-chat-bottom-gap` below. A container narrower than 520px tightens the\n * transcript's copy of this token but not the composer's — a container query cannot\n * reach this element, which is its own container root.\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></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 and `showPanel` empties it unconditionally, so every\n * path that closes a panel whose owner is still awaiting an answer — a second\n * `showPanel`, `hidePanel` without the owner's token, `_handleMessageDone` (which\n * fires on EVERY turn end) — must tell the owner. An owner that is not told keeps\n * its request pending, and every later request is short-circuited behind it.\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 * would latch the global config forever: an instance `config` carrying an RTL\n * locale would flip the transcript and not the composer, and the\n * `requireModelSelection` gate would be read off the wrong object.\n * `aparte-chat-bubble` resolves live too, and its JSDoc names this 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 * Without it an RTL locale flips the conversation and leaves the composer\n * left-to-right. It is also what makes the toolbar's placement idiom real:\n * `margin-inline-start: auto` in a subtree that inherits no direction is just\n * `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 // After the current parse: the other chats of the page may be mounted later in\n // the same document, and the question is only worth asking once they are.\n queueMicrotask(() => this._warnIfUnattached());\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 // Pushed, not merely readable: the input reads this element's placeholder\n // when it renders, and nothing else re-read it, so a placeholder bound to a\n // translated string went stale on the first language switch after mount.\n for (const input of this.querySelectorAll('aparte-composer-input')) {\n (input as { syncPlaceholder?: () => void }).syncPlaceholder?.();\n }\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 * The attribute is the state, so the property writes it. It has to exist: React 19\n * and Svelte assign the PROPERTY whenever the element has one of that name, and a\n * getter alone would make `<aparte-composer disabled=\"\">` — a spelling the wrappers\n * type as valid — throw and take the render down. `presenceOn` because `''` is what\n * those templates hand a presence property.\n */\n set disabled(value: boolean) { this.toggleAttribute('disabled', presenceOn(value)); }\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 /** Writes the attribute the inputs read (see `disabled` for why the setter exists). */\n set placeholder(value: string) {\n if (value === null || value === undefined) this.removeAttribute('placeholder');\n else this.setAttribute('placeholder', String(value));\n }\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 would otherwise stay clickable while the\n * user is being asked something. An affordance nothing can honour is not 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'`\n * keeps it; `'none'` says this panel has no act for it and it is not drawn. `'none'`\n * is what a panel whose options settle on the first click needs — a single-choice\n * question, an approval. Flip between them at any time with\n * {@link setPanelSubmitEnabled}, which is how a panel that grows an act (an\n * \"Other…\" field, a written instruction) turns the button back on.\n *\n * The mode is declared with an attribute + CSS rather than inline `style.display`\n * on a child: an attribute is themeable, is visible to a consumer's own rules, and\n * does not clobber a `display` the consumer set.\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 // Where it goes, in order: the element the author marked as the panel's host,\n // else right after the first input, else the end of the composer. The marker\n // exists because \"after the input\" is a position, and a layout with the input\n // in a row and the panel meant for a block of its own had no way to say so.\n const host = this.querySelector('[data-aparte-panel-host]') as HTMLElement | null;\n const inputEl = this.querySelector('aparte-composer-input') as HTMLElement | null;\n this.setAttribute('data-panel-active', '');\n panel.dataset['apartePanel'] = 'true';\n if (host) {\n host.appendChild(panel);\n } else 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 * 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 /*\n * Read BEFORE the panel is removed. Removing the focused element drops the\n * focus to `<body>`, after which \"was the reader in here?\" has no answer —\n * the same reasoning `aparte-elicitation`'s `close()` already records for its\n * own restoration.\n *\n * And it has to be asked at all: the teardown ended on an unconditional\n * `this.focus()`, so every close pulled the caret into the composer's editor.\n * A turn ending closes any open panel, and a turn ends because the model\n * finished, not because the reader did anything — so a reader who had moved to\n * another chat's field, a search box or a link was yanked back mid-keystroke.\n * The panel may hand back the focus it took; it may not take focus it never had.\n */\n const active = document.activeElement;\n const hadFocus = active instanceof Node && this.contains(active);\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 if (hadFocus) 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 can change on the same event — opening the\n * \"Other…\" field of a panel whose options settle on the click gives the button\n * an act it did not have — and two separate calls would flash a wrong state\n * 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: an attribute is themeable, is\n * visible to a consumer's own rules, and does not clobber what the consumer set. It\n * is what lets `'none'` remove the send button without this component reaching\n * 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 /*\n * `_ownTargetId()`, not the bare attribute — the same resolution `cancel()`\n * uses, and for the same reason. The wrappers all set `target`, so this is\n * byte-identical there; in RAW core nothing writes it, so every send carried\n * `targetId: undefined` and a page with two hand-written chats delivered the\n * reply to whichever one the host resolved first.\n */\n targetId: this._ownTargetId(),\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 /** @internal The composer parts' own bus. From outside, listen for `aparte-composer-change`. */\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 /** @internal The composer parts' own bus. From outside, listen for `aparte-composer-change`. */\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. In RAW core — the documented quick\n * start, where the markup is hand-written — nothing sets it, so the chat host above\n * is what identifies the composer; without either, it would accept every chat's\n * lifecycle events and, on a two-chat page, one chat's Stop would tear down the\n * other's open elicitation panel.\n *\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 /**\n * A signal, not a guard: on a page with several chats, a composer attached to none\n * answers to every chat's lifecycle events (see `_ownTargetId`), and the symptom —\n * one chat's Stop evicting another's open question — is nowhere near its cause.\n * Said once, at the console, where the developer is.\n */\n private _warnIfUnattached(): void {\n if (!this.isConnected || this._ownTargetId()) return;\n if (document.querySelectorAll('aparte-chat, [data-aparte-chat]').length < 2) return;\n console.warn('[aparte-composer] this composer belongs to no chat while the page has several: give it target=\"<chat id>\" or place it under a chat host with an id, or it will answer to every chat\\'s events.');\n }\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' || 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 message this composer's turn is writing, when the event that started it said.\n * Kept so the END of a DIFFERENT turn cannot be read as the end of this one — see\n * `_isForThisTurn`.\n */\n private _streamingMessageId: string | null = null;\n\n /**\n * Whether a terminal event ends the turn this composer is following.\n *\n * `_isForThisComposer` answers \"is this chat's?\", and both turns of a retry belong\n * to the same chat: the client aborts the superseded reply and the engine emits\n * `run-aborted` for it, which must not turn Stop back into Send while the new reply\n * is still streaming, nor evict the open panel (the approval question a tool is\n * still waiting on). An event with no `messageId` still ends the turn: a loop of\n * one's own saying \"done\" means this one.\n */\n private _isForThisTurn(e: Event): boolean {\n const id = (e as CustomEvent).detail?.messageId as string | undefined;\n return !id || this._streamingMessageId === null || id === this._streamingMessageId;\n }\n\n private _handleMessageStart(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streamingMessageId = ((e as CustomEvent).detail?.messageId as string | undefined) ?? null;\n this._streaming = true;\n this._emit('streaming-change', { streaming: true });\n }\n\n private _handleMessageDone(e: Event): void {\n if (!this._isForThisComposer(e) || !this._isForThisTurn(e)) return;\n this._streamingMessageId = null;\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 sync and image paste all need the composer. The editor stays\n * editable while a reply streams (the next message is typed while the current one\n * arrives); only the send is gated then — Enter is swallowed, the button is Stop.\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=var(--aparte-btn-size-lg)] - 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=calc((var(--aparte-composer-control-size) - var(--aparte-input-line-height) * 1em) / 2)] - Vertical padding inside the editor: half of what the control size leaves once one line is taken out (`1lh` where the browser has it).\n * @cssprop [--aparte-input-padding-x=var(--aparte-space-6)] - Horizontal padding inside the editor.\n * @cssprop [--aparte-input-font-size=var(--aparte-font-size-base)] - Editor font size.\n * @cssprop [--aparte-input-line-height=var(--aparte-line-height-normal)] - 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). Keep it unitless (`1.5`, not `24px`): the editor's\n * padding reads it as a multiple of `1em` where the browser has no `lh` unit.\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=var(--aparte-radius-lg)] - 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\"></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 /**\n * Re-measures the editor when its WIDTH changes.\n */\n private _resizeObserver: ResizeObserver | null = null;\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 // Bound HERE, never in _render: disconnectedCallback removes these, and on a\n // RECONNECT (a chat moved into a split, a shell, any reparenting) _render\n // finds its DOM already there and returns before the old binding block —\n // which left the editor deaf: the draft landed in the DOM and `root.value`\n // never heard of it, so the send button stayed disabled with text visibly\n // in the box. Wiring is the connect's job; _render only builds.\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 this._connectToRoot();\n this._scheduleInitialReflow();\n // Width is watched because the fixed instants (connect, next frame, fonts.ready)\n // miss a box not yet laid out: the placeholder wraps in a 0 px column and\n // `scrollHeight` reads ~300 px, which would stay the inline height until the\n // first keystroke.\n if (this._editor && typeof ResizeObserver === \"function\") {\n this._resizeObserver = new ResizeObserver(() => this._adjustHeight());\n this._resizeObserver.observe(this._editor);\n }\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._resizeObserver?.disconnect();\n this._resizeObserver = null;\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 /**\n * Re-read the placeholder fallback chain. Called by the composer when ITS\n * `placeholder` changes — this element observes only its own attribute, and the\n * composer's is one of the three sources it falls back to.\n */\n syncPlaceholder(): void {\n this._updatePlaceholder();\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=\"${escapeAttr(String(disabled ? -1 : 0))}\"\n aria-disabled=\"${disabled}\"\n data-placeholder=\"${placeholder}\"\n ></div>`;\n\n this._editor = this.querySelector('.aparte-ci-editor');\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 // No `streaming-change` subscription, on purpose: the editor stays editable while\n // a reply streams. It used to go non-editable for the whole turn, so the next\n // message could not be prepared while the current one arrived — the first thing\n // a person does in every chat. Only the SEND is gated meanwhile (the button is\n // Stop, and Enter is swallowed in `_handleKeydown`); `disabled` still reaches\n // here through `disabled-change`.\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 // While a reply streams, Enter neither sends nor stops: `submit()` is\n // Stop during a turn, and a person finishing the NEXT message with Enter\n // must not cut the current reply off. The draft stays; Enter sends it once\n // the turn is over.\n if (root.streaming) return;\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, where attachments are on: the same signal\n // drag and drop answers to, an `<aparte-composer-add-attachment>` in the composer.\n // Without it a pasted screenshot became an attachment on a chat that offered none —\n // one the app had not said it could send.\n const root = this._getRoot();\n const imageFile = root?.querySelector('aparte-composer-add-attachment')\n ? Array.from(cd.items).find(i => i.type.startsWith('image/'))?.getAsFile()\n : undefined;\n if (imageFile) {\n root?.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 // Out of the tab order as well: a non-editable box that still took focus lit\n // the shell's focus border on click — a field that looks ready and is not.\n this._editor.setAttribute('tabindex', disabled ? '-1' : '0');\n if (disabled && this.ownerDocument?.activeElement === this._editor) this._editor.blur();\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, three meanings: **send**, **stop** while the root is streaming, and — when\n * an elicitation panel is open — **submit** the answer; a panel whose mode is `'none'`\n * (its options settle on the click) takes the button out of the layout entirely. The\n * panel outranks streaming: while one is open the button stays the answer control and a\n * streaming change is ignored. The icon moves with the meaning (paper plane, square,\n * check), because a paper plane that means \"answer\" is a lie. All three 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=var(--aparte-btn-size-lg)] - 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=var(--aparte-btn-size-lg)] - 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=var(--aparte-radius-md)] - 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></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 is kept so the button's chrome can be\n * recomputed after the event, for example on a config change.\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 // Bound here, never in _render — disconnect removes it and a reconnect's\n // _render keeps the existing DOM (see aparte-composer-input for the full rule).\n this._button = this.querySelector('.aparte-cs-button');\n this._button?.addEventListener('click', this._onClick);\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 type=\"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 }\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 means \"submit the answer\".\n *\n * The icon moves with the meaning: a paper plane would read as \"send a message\"\n * while the label says \"Submit\". The button never turns into a \"Next\" while a form\n * has questions ahead (the panel's chips do that, and a button whose meaning\n * changed under the pointer would be a second way to do it). It is disabled\n * instead until every question has an answer, which is what the chips' marks explain.\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 this._button.disabled = !panel.submitEnabled;\n this._button.innerHTML = this._getSubmitIcon();\n const label = 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=var(--aparte-btn-size-lg)] - 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=var(--aparte-radius-sm)] - 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></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 // Bound here, never in _render — disconnect removes it and a reconnect's\n // _render keeps the existing DOM (see aparte-composer-input for the full rule).\n this._button = this.querySelector('.aparte-cc-button');\n this._button?.addEventListener('click', this._onClick);\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 type=\"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 }\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, escapeHtml } 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=72px] - 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></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 const cfg = resolveConfig(this);\n // \"Remove {name}\", from the locale. It was the literal `Remove ${name}` — an\n // icon-only button, so this string is the whole of what a screen-reader user\n // hears, and it stayed English in every language the library shipped.\n // Interpolated from the RAW file name and escaped once, at the end: reusing the\n // already-escaped `name` would escape a `&` twice and read \"rapport & co\".\n const removeLabel = (file: File): string =>\n (cfg.t('removeAttachment') || 'Remove {name}').replace('{name}', () => file.name);\n\n this.innerHTML = files.map((file) => {\n const name = escapeHtml(file.name);\n const remove =\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-thumb__remove\" type=\"button\" ` +\n `aria-label=\"${escapeAttr(removeLabel(file))}\">${cfg.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=\"${name}\">` +\n `<img class=\"aparte-thumbnail__image\" src=\"${escapeAttr(url)}\" alt=\"${name}\" />` +\n `<span class=\"aparte-thumb__name\">${name}</span>${remove}</div>`;\n }\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--file\" title=\"${name}\">` +\n `<span class=\"aparte-thumbnail__label\">${escapeHtml(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 /*\n * The control is the IMAGE, not the tile.\n *\n * The role used to sit on the tile, which wraps the remove `<button>` — a\n * button nested inside a button, which no role permits, and whose name is\n * computed from its contents: the file name from the `title`, again from the\n * `.aparte-thumb__name` overlay, and a third time inside \"Remove report.png\",\n * with no way for the reader to tell which of the two controls Enter reaches.\n *\n * The picture is what the preview opens, so it is what the button is; the ✕\n * sits beside it. The bubble's sent strip keeps the role on its tile and must:\n * there is no ✕ there, nothing is nested, and the tile IS the whole control.\n *\n * The label is interpolated from the RAW `file.name` — `setAttribute` escapes\n * for us, and reusing the already-escaped `name` would escape twice, the trap\n * `removeLabel` above already documents.\n */\n const images = files.filter(f => f.type.startsWith('image/'));\n this.querySelectorAll('.aparte-thumb--image').forEach((tile, i) => {\n const img = tile.querySelector('.aparte-thumbnail__image') as HTMLImageElement | null;\n if (!img) return;\n img.setAttribute('role', 'button');\n img.setAttribute('tabindex', '0');\n img.setAttribute('aria-label', images[i]?.name ?? '');\n const open = (): void => {\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true,\n composed: true,\n detail: { url: img.src, name: images[i]?.name ?? '' },\n }));\n };\n img.addEventListener('click', open);\n img.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\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.\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 and greys the button;\n * `streaming` does neither — a file queued while a reply arrives is part of preparing\n * the next message, and only the send is gated meanwhile.\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 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=var(--aparte-btn-size-lg)] - 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></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 // Bound here, never in _render — disconnect removes it and a reconnect's\n // _render keeps the existing DOM (see aparte-composer-input for the full\n // rule). The drag/drop pair below already lives on the right side.\n this._button = this.querySelector('.aparte-caa-button');\n this._button?.addEventListener('click', this._onClick);\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 rebuilding it would drop focus if the user were on it — a locale or\n * icon change is not a reason to lose the keyboard.\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 }\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 // Attaching is part of preparing the next message, which stays possible\n // while a reply streams; only `disabled` blocks it. `streaming` is read so\n // the subscription still refreshes the button when a turn ends.\n void streaming;\n if (this._button) this._button.disabled = 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\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 } 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 * A key the icon set does not hold renders AS TEXT (`icon=\"star\"` draws the word\n * *star*), so add it with `registerIcons` or pass the SVG.\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=var(--aparte-btn-size-lg)] - 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></aparte-composer-input>\n * <aparte-composer-action icon=\"pin\" label=\"Pin\" action-id=\"pin\"></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 === 'pin') console.log('pinned');\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 // Bound here, never in _render — disconnect removes it and a reconnect's\n // _render keeps the existing DOM (see aparte-composer-input for the full rule).\n this._button = this.querySelector('.aparte-cact-button');\n this._button?.addEventListener('click', this._onClick);\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 }\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) ?? 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 a visible\n * element child nor non-whitespace text; the stylesheet hides it then.\n * A child that hides itself — `hidden`, or its own `data-empty` the\n * way `<aparte-context>` does before its first usage — does not count:\n * a row whose only child shows nothing is an empty band with a border.\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 // Its own data-empty write is a mutation too: an ATTRIBUTE record on this element\n // is skipped, or the write re-enters the observer without end. A childList record\n // also targets this element — the parent — and must not be skipped.\n this._observer ??= new MutationObserver((records) => {\n if (records.some((r) => !(r.type === 'attributes' && r.target === this))) this._syncEmpty();\n });\n // Children's own `hidden` / `data-empty` count too (see _syncEmpty), so the\n // subtree's attributes are watched, filtered to those two.\n this._observer.observe(this, { childList: true, subtree: true, attributes: true, attributeFilter: ['hidden', 'data-empty'] });\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 // A child hidden by the library's own convention shows nothing: `<aparte-context>`\n // sets `data-empty` on itself until the first usage arrives, and a consumer sets\n // `hidden`. Counting it kept a 13px band with a border above the composer's\n // edge, over nothing (the chat-site review, 2026-09-05).\n const visibleChild = Array.from(this.children).some((c) => !c.hasAttribute('hidden') && !c.hasAttribute('data-empty'));\n // Text counts when it is the row's OWN — a text node directly under it — not the\n // text inside a hidden child, which `textContent` would have folded in.\n const ownText = Array.from(this.childNodes).some((n) => n.nodeType === Node.TEXT_NODE && (n.textContent ?? '').trim() !== '');\n const hasContent = visibleChild || ownText;\n if (hasContent === !this.hasAttribute('data-empty')) return;\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","/**\n * Text as a search compares it: no accents, lower case, trimmed — so \"Écrire\" finds \"ecrire\".\n * Shared by the conversation list's filter and the sidebar's search field.\n */\nexport function foldText(text: string): string {\n return text.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().trim();\n}\n\n/**\n * Hide the conversation rows under `root` whose title does not contain `needle` (already\n * folded), and the date groups left with none; return how many rows match. An empty needle\n * shows everything. The conversation list runs it on every render; the sidebar runs it itself\n * on a list that has not upgraded yet, whose rows are the page's own markup.\n */\nexport function filterConversationRows(root: ParentNode, needle: string): number {\n let matches = 0;\n for (const row of root.querySelectorAll<HTMLElement>('[data-conv-id]')) {\n const title = row.querySelector('.aparte-conv-item__title')?.textContent ?? row.textContent ?? '';\n row.hidden = needle !== '' && !foldText(title).includes(needle);\n if (!row.hidden) matches++;\n }\n for (const group of root.querySelectorAll<HTMLElement>('.aparte-conv-group')) {\n group.hidden = needle !== '' && !Array.from(group.querySelectorAll<HTMLElement>('[data-conv-id]')).some((r) => !r.hidden);\n }\n return matches;\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { APARTE_DEFAULT_LOCALE } from '../../config/locale.js';\nimport type { AparteConversationManager } from '../../conversations/conversation-manager.js';\nimport { escapeAttr, escapeHtml } from '../../utils/escape.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { activeElementIn, eventOrigin } from '../../utils/event-target.js';\nimport { presenceOn } from '../../utils/presence.js';\nimport { foldText, filterConversationRows } from '../../utils/fold-text.js';\n\nexport interface AparteConversationListItem {\n id: string;\n title: string;\n /** Drives the date groups. A list where no item carries one renders flat. */\n updatedAt?: number;\n /** When set, the row is italic and its menu offers unarchive instead of archive. */\n archivedAt?: number;\n /** When set, the row sits in the \"Pinned\" group first, and its menu offers unpin. */\n pinnedAt?: 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\nexport interface AparteConversationPinDetail {\n id: string;\n}\n\nexport interface AparteConversationRenameDetail {\n id: string;\n /** The new title, trimmed and non-empty — an unchanged or emptied title fires nothing. */\n title: string;\n}\n\n/** A date group: its heading (null for the undated tail) and its rows, in host order. */\ninterface ConversationGroup {\n key: string;\n label: string | null;\n items: AparteConversationListItem[];\n}\n\n/** The one open menu, if any. */\ninterface OpenMenu {\n id: string;\n row: HTMLElement;\n button: HTMLElement;\n menu: HTMLElement;\n}\n\n/** Locale keys this element reads; each falls back to the English default. */\ntype ListLocaleKey =\n | 'newChat' | 'loadingConversations' | 'conversationList' | 'deleteConversation' | 'archiveConversation' | 'unarchiveConversation'\n | 'conversationActions' | 'renameConversation' | 'conversationTitle'\n | 'pinConversation' | 'unpinConversation' | 'deleteConversationConfirm' | 'cancel'\n | 'conversationGroupPinned' | 'conversationGroupToday' | 'conversationGroupYesterday'\n | 'conversationGroupWeek' | 'conversationGroupMonth' | 'conversationSearchEmpty';\n\n/** A selector for the row button carrying `id` on `attr`. */\nfunction rowSelector(attr: string, id: string): string {\n return `[${attr}=\"${cssEscape(id)}\"]`; // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n}\n\nconst DAY = 864e5;\nconst MENU_GAP = 4;\nconst VIEWPORT_MARGIN = 8;\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 * rows and fires the user's intent. Acting on it is the host's, unless `manage` hands\n * the row menu's writes to the registered conversation manager.\n *\n * A row is two real buttons: the title, which selects, and a `⋯` that opens the row's\n * menu — rename, pin or unpin, archive or unarchive, delete. Every item fires an event\n * and stops; the one exception is delete, which asks first, inline in the menu,\n * because it is the one action the host cannot undo. Rename swaps the title for an\n * input: Enter or leaving the field commits, Escape cancels, and an unchanged or\n * emptied title fires nothing. The rows are grouped by date — Pinned, Today,\n * Yesterday, Previous 7 days, Previous 30 days, then one heading per month — as soon\n * as any item carries `updatedAt`; set `flat` to render them flat.\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. Selecting, renaming, pinning, archiving and deleting all\n * leave the array untouched — the events carry an id (and, for rename, the title) and\n * stop. Set `manage` and the menu's write gestures are carried out on the conversation\n * manager you registered, so you do not write those six listeners; the\n * event still goes out first and `preventDefault()` takes the gesture back. The array\n * is still not this element's: the manager's own change is what re-renders it.\n * A row's text comes from the array, so it changes when the host assigns\n * `conversations` again; the exception is an empty title, which falls back to the\n * locale's new-chat label and therefore follows a locale switch. An archived item is\n * still rendered (it gains `aparte-conv-item--archived`); filtering archived\n * conversations out of the list is the host's decision, not this element's. The\n * asymmetry between the two inputs is deliberate: `active-id` is an attribute because\n * moving the selection patches the rendered rows in place, while `conversations` is a\n * JS property because it is structured data an attribute cannot carry, and setting it\n * 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 * @attr {boolean} flat - Render the rows flat, in host order, with no date headings.\n * @attr {boolean} data-no-match - Reflected BY the element while `filter()` holds a query no title matches. Read-only.\n * @attr {boolean} loading - The list is on its way: six skeleton rows stand where the rows will be, the\n * element is `aria-busy` and a visually hidden line names the wait. A list that is\n * not there yet is not an empty list. Set it while your store answers, clear it when\n * you assign `conversations`.\n * @attr {boolean} manage - Let the list finish the gesture: rename, pin, unpin, archive, unarchive and\n * delete are performed on the conversation manager registered with\n * `setConversationManager()`, instead of leaving you six listeners to write. The\n * event still fires first, and `preventDefault()` on it takes the gesture back.\n * Off by default, and selecting stays yours either way.\n *\n * @fires {CustomEvent<AparteConversationSelectDetail>} aparte-conversation-select - A row's title was activated; the host loads that conversation.\n * @fires {CustomEvent<AparteConversationRenameDetail>} aparte-conversation-rename - A rename was committed with a new, non-empty title. Cancelable: `preventDefault()` takes the gesture back from `manage`.\n * @fires {CustomEvent<AparteConversationPinDetail>} aparte-conversation-pin - The pin item was chosen on an unpinned row. Cancelable: `preventDefault()` takes the gesture back from `manage`.\n * @fires {CustomEvent<AparteConversationPinDetail>} aparte-conversation-unpin - The same item on a pinned row; same detail shape, opposite intent. Cancelable, like the rest.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-conversation-archive - The archive item was chosen on a live conversation. Cancelable: `preventDefault()` takes the gesture back from `manage`.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-conversation-unarchive - The same item on an already-archived one; same detail shape, opposite intent. Cancelable, like the rest.\n * @fires {CustomEvent<AparteConversationDeleteDetail>} aparte-conversation-delete - The delete was confirmed. Cancelable: `preventDefault()` takes the gesture back from `manage`.\n *\n * @cssprop [--aparte-conv-list-gap=var(--aparte-space-1)] - Vertical gap between rows, and between a group's heading and its rows.\n * @cssprop [--aparte-conv-item-padding=var(--aparte-space-4) var(--aparte-space-5)] - Padding of a row's title button.\n * @cssprop [--aparte-conv-item-gap=var(--aparte-space-3)] - Gap between a row's title and its `⋯` button.\n * @cssprop [--aparte-conv-item-radius=var(--aparte-radius-md)] - Corner radius of a row.\n * @cssprop [--aparte-conv-item-font-size=var(--aparte-font-size-md)] - Font size of a row's title.\n * @cssprop [--aparte-conv-item-color=var(--aparte-text)] - 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)] - Title weight of the active row.\n * @cssprop [--aparte-conv-action-btn-size=var(--aparte-btn-size-sm)] - Square size of the `⋯` button (the recipe's small step). Under `(pointer: coarse)` the stylesheet redeclares it as the touch target size on the button itself, so a value set on the element does not reach it there; the button also stays visible instead of appearing on hover.\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 * const day = 864e5;\n * document.querySelector('aparte-conversation-list').conversations = [\n * { id: 'c0', title: 'Release checklist', updatedAt: Date.now() - 3 * day, pinnedAt: Date.now() },\n * { id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },\n * { id: 'c2', title: 'Rename the segment types', updatedAt: Date.now() - day },\n * { id: 'c3', title: 'Tokens, not selectors', updatedAt: Date.now() - 4 * day },\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-conversation-select', (e) => load(e.detail.id));\n * list.addEventListener('aparte-conversation-rename', (e) => manager.updateTitle(e.detail.id, e.detail.title));\n * list.addEventListener('aparte-conversation-pin', (e) => manager.pin(e.detail.id));\n * list.addEventListener('aparte-conversation-delete', (e) => manager.delete(e.detail.id));\n *\n * @example\n * // Or let the list finish the gesture: register the manager once, add `manage`, and\n * // rename / pin / archive / delete are carried out for you. The events still fire.\n * aparteGlobalConfig.setConversationManager(manager);\n * list.setAttribute('manage', '');\n */\nexport class AparteConversationList extends HTMLElement {\n private _conversations: AparteConversationListItem[] = [];\n private _activeId: string | null = null;\n private _open: OpenMenu | null = null;\n private _renaming: { id: string; input: HTMLInputElement; done: boolean } | null = null;\n private _warnedNoManager = false;\n\n static get observedAttributes(): string[] {\n return ['active-id', 'flat', 'loading', 'manage'];\n }\n\n /**\n * The list is on its way: rows of the kit's skeleton stand where the rows will be,\n * the element is `aria-busy`, and a visually hidden line names the wait. Reflected\n * as the `loading` attribute, so a site sets it while its store answers and clears\n * it when `conversations` is set. A list that is not there yet is not an empty list.\n */\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(value: boolean) {\n // `presenceOn`, like every other boolean property in core: `''` is the ON that\n // React stringifies and Svelte 5 assigns, and `toggleAttribute` reads it as\n // falsy — the documented spelling turned the wait off.\n this.toggleAttribute('loading', presenceOn(value));\n }\n\n /**\n * Let the list finish the gesture through the registered conversation manager.\n * Reflected as the `manage` attribute.\n *\n * The property exists because this is the attribute that authorises a WRITE, and Vue\n * and both Sveltes stringify a bound `false` onto an element that has no property of\n * that name — `manage=\"false\"`, which `hasAttribute` reads as on. Without it,\n * turning management off would turn it on. `presenceOn`, like `loading` above it.\n */\n get manage(): boolean {\n return this.hasAttribute('manage');\n }\n\n set manage(value: boolean) {\n this.toggleAttribute('manage', presenceOn(value));\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.addEventListener('click', this._onClick);\n this.addEventListener('keydown', this._onKeydown);\n this.addEventListener('focusout', this._onFocusOut);\n this.addEventListener('pointerdown', this._onPress);\n this._render();\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n this._closeMenu();\n this.removeEventListener('click', this._onClick);\n this.removeEventListener('keydown', this._onKeydown);\n this.removeEventListener('focusout', this._onFocusOut);\n this.removeEventListener('pointerdown', this._onPress);\n window.removeEventListener('pointerup', this._onRelease, true);\n window.removeEventListener('pointercancel', this._onRelease, true);\n this._pressed = false;\n this._renderAfterPress = false;\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 } else if ((name === 'flat' || name === 'loading') && this.isConnected) {\n this._render();\n }\n }\n\n /**\n * Re-render on a locale switch: every row's title fallback, the `⋯` button's name\n * and the group headings come from the locale, so without this the list stayed in\n * the previous language until something else happened to re-render it. Only OUR\n * 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 /**\n * A press holds every render until its click has landed. The host re-assigns\n * `conversations` on every manager change (a save moves a row, a select marks one),\n * and a row replaced between `mousedown` and `mouseup` loses its click: Chromium\n * sends it to the list, where the delegated handler matches nothing, and WebKit\n * sends none. Keeping the row node would not help: a row moved to its new place\n * leaves the document on the way, and the row that moves is often the one pressed\n * (the conversation just saved). So nothing changes under a pointer that is down,\n * and the render runs once, after the click.\n */\n private _pressed = false;\n private _renderAfterPress = false;\n\n private _onPress = (): void => {\n if (this._pressed) return;\n this._pressed = true;\n window.addEventListener('pointerup', this._onRelease, true);\n window.addEventListener('pointercancel', this._onRelease, true);\n };\n\n private _onRelease = (): void => {\n window.removeEventListener('pointerup', this._onRelease, true);\n window.removeEventListener('pointercancel', this._onRelease, true);\n // `mouseup` and `click` follow `pointerup` in the same task: this runs after them.\n setTimeout(() => {\n this._pressed = false;\n if (!this._renderAfterPress) return;\n this._renderAfterPress = false;\n if (this.isConnected) this._render();\n }, 0);\n };\n\n /** The name this element last wrote, so one the app wrote — before or after connect — is left alone. */\n private _ownLabel: string | null = null;\n\n private _render(): void {\n // A landmark needs a name once a page has a second one (axe landmark-unique): the\n // locale's, unless the app named it — an `aria-label` or `aria-labelledby` of its own wins.\n const current = this.getAttribute('aria-label');\n if (!this.hasAttribute('aria-labelledby') && (current === null || current === this._ownLabel)) {\n this._ownLabel = this._t('conversationList');\n this.setAttribute('aria-label', this._ownLabel);\n }\n if (this._pressed) {\n this._renderAfterPress = true;\n return;\n }\n // The menu and the rename input live inside the rows; a render replaces them.\n this._closeMenu();\n this._renaming = null;\n // Where the keyboard was, so it can be put back: `innerHTML` below replaces every\n // row, and Enter on a row is followed by exactly this render (the host marks the\n // active conversation and re-assigns the list). The button the reader was on is\n // gone by then and the focus lands on `<body>`, so the next Tab restarts at the\n // top of the page. Same rule the rename exit already follows.\n // `activeElementIn`, not `document.activeElement`: that retargets to the shadow\n // HOST for a list mounted in a consumer's own root, so `contains` answered no and\n // nothing was ever held.\n const focused = activeElementIn(this) as HTMLElement | null;\n const held = focused && this.contains(focused)\n ? focused.closest<HTMLElement>('[data-select-id], [data-more-id]')\n : null;\n const restore = held?.dataset['selectId']\n ? rowSelector('data-select-id', held.dataset['selectId'])\n : held?.dataset['moreId']\n ? rowSelector('data-more-id', held.dataset['moreId'])\n : null;\n // Where that row sat, for the case where it does not come back: a delete is\n // confirmed from the row's own menu, so the row the keyboard is on is the row\n // that leaves.\n const heldRow = held?.closest<HTMLElement>('[data-conv-id]') ?? null;\n // Among the rows a reader can reach: with a search held, the hidden ones do not count.\n const visible = (el: Element): boolean => !el.closest('[hidden]');\n const heldIndex = heldRow\n ? Array.from(this.querySelectorAll<HTMLElement>('[data-conv-id]')).filter(visible).indexOf(heldRow)\n : -1;\n const loading = this.hasAttribute('loading');\n this.setAttribute('aria-busy', loading ? 'true' : 'false');\n if (loading) {\n // A list on its way matches nothing yet: no \"no match\" while it loads.\n this.removeAttribute('data-no-match');\n // Six rows of the recipe, the widths varied so it reads as a list and not a\n // grid; a status line off screen, because aria-busy alone is often ignored.\n // Literals only, no interpolation at all: the text-escaping guard reads a\n // `.repeat()` on a string as an interpolation it cannot see through, and a red\n // guard is a guard somebody skips.\n const row = '<span class=\"aparte-skeleton aparte-skeleton--text\"></span>';\n this.innerHTML =\n '<div class=\"aparte-conv-list-loading\" aria-hidden=\"true\">' + row + row + row + row + row + row + '</div>'\n + '<span class=\"aparte-conv-list-loading-status aparte-sr-only\" role=\"status\"></span>';\n // The text lands AFTER the node does. A live region created with its content\n // already in it is a new node, not a change, and several screen readers\n // announce nothing at all. One frame is enough for it to be registered; if the\n // wait ends first the node is gone and there is nothing left to say.\n const status = this.querySelector<HTMLElement>('.aparte-conv-list-loading-status');\n const say = (): void => { if (status?.isConnected) status.textContent = this._t('loadingConversations'); };\n if (typeof requestAnimationFrame === 'function') requestAnimationFrame(say);\n else say();\n return;\n }\n // The no-match line outlives the rows, and never leaves the document: rebuilt with\n // them, or detached and put back, it is a new live region to the accessibility tree,\n // whose text may be announced again — \"No conversation matches\" after every save.\n const line = this.querySelector<HTMLElement>(':scope > .aparte-conv-list-no-match');\n const groups = this._groups().map(g => this._renderGroup(g)).join('');\n if (line) {\n for (const child of Array.from(this.childNodes)) if (child !== line) child.remove();\n line.insertAdjacentHTML('beforebegin', groups);\n } else {\n this.innerHTML = groups;\n }\n this._applyFilter();\n if (!restore) return;\n // The same row, by id — not the same element, which no longer exists — when the\n // search still shows it: a focus() inside `hidden` lands on <body>.\n const back = this.querySelector<HTMLElement>(restore); // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n if (back && visible(back)) { back.focus(); return; }\n // That row left the list. The keyboard goes to the row that took its place (the\n // next one, else the one above), and to the list itself when the last\n // conversation goes — never to `<body>`, which would restart the next Tab at the\n // top of the page right after the one action here that cannot be undone.\n const rows = Array.from(this.querySelectorAll<HTMLElement>('[data-select-id]')).filter(visible);\n const next = heldIndex >= 0 ? (rows[heldIndex] ?? rows[rows.length - 1]) : null;\n if (next) { next.focus(); return; }\n // Borrowed, not taken: a host that made the list focusable keeps its own tab\n // stop, and the one we add leaves with the focus rather than sitting in the tab\n // order for the rest of the session.\n if (!this.hasAttribute('tabindex')) {\n this.setAttribute('tabindex', '-1');\n this.addEventListener('blur', () => this.removeAttribute('tabindex'), { once: true });\n }\n this.focus();\n }\n\n /** The query `filter()` holds, folded; re-applied on every render, so new rows obey it. */\n private _query = '';\n\n /**\n * Show only the conversations whose title contains `query`, case- and\n * accent-insensitive, and the date groups that still hold one; return how many match.\n * An empty query shows everything. The query is kept through every render — a new\n * conversation, a rename — and when nothing matches the list says so in a status line\n * (the locale's `conversationSearchEmpty`) and carries `data-no-match`.\n */\n filter(query: string): number {\n this._query = foldText(query);\n return this._applyFilter();\n }\n\n private _applyFilter(): number {\n const needle = this._query;\n // Rows that are not there yet match nothing and miss nothing: the skeleton is shown.\n if (this.hasAttribute('loading')) return 0;\n const matches = filterConversationRows(this, needle);\n const noMatch = needle !== '' && matches === 0;\n this.toggleAttribute('data-no-match', noMatch);\n // Always there, empty until a miss: a live region created with its text already in\n // it is a new node, not a change, and several screen readers announce nothing.\n let line = this.querySelector<HTMLElement>(':scope > .aparte-conv-list-no-match');\n const born = !line;\n if (!line) {\n line = document.createElement('p');\n line.className = 'aparte-conv-list-no-match';\n line.setAttribute('role', 'status');\n this.appendChild(line);\n }\n const text = noMatch ? this._t('conversationSearchEmpty') : '';\n const write = (): void => { if (line!.isConnected && line!.textContent !== text) line!.textContent = text; };\n // A line a render just made is a new node, and its first text has to come a frame\n // later to be announced (same as the loading status).\n if (born && text && typeof requestAnimationFrame === 'function') requestAnimationFrame(write);\n else write();\n return matches;\n }\n\n private _t(key: ListLocaleKey): string {\n const locale = resolveConfig(this).getLocale();\n return locale[key] ?? APARTE_DEFAULT_LOCALE[key] ?? '';\n }\n\n /**\n * Pinned first, then the calendar buckets every chat product uses, then a heading\n * per month (newest first), then whatever carries no date. Flat when nothing is\n * dated or the host asked for `flat`: a heading over the only group would say\n * nothing.\n */\n private _groups(): ConversationGroup[] {\n const items = this._conversations;\n if (this.hasAttribute('flat') || !items.some(c => typeof c.updatedAt === 'number')) {\n return [{ key: 'all', label: null, items }];\n }\n const now = new Date();\n const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n const tag = resolveConfig(this).getLocale().tag;\n const buckets = new Map<string, ConversationGroup>();\n const push = (key: string, label: string | null, item: AparteConversationListItem): void => {\n let g = buckets.get(key);\n if (!g) {\n g = { key, label, items: [] };\n buckets.set(key, g);\n }\n g.items.push(item);\n };\n for (const c of items) {\n if (c.pinnedAt) { push('pinned', this._t('conversationGroupPinned'), c); continue; }\n const at = c.updatedAt;\n if (typeof at !== 'number') { push('undated', null, c); continue; }\n if (at >= today) push('today', this._t('conversationGroupToday'), c);\n else if (at >= today - DAY) push('yesterday', this._t('conversationGroupYesterday'), c);\n else if (at >= today - 7 * DAY) push('week', this._t('conversationGroupWeek'), c);\n else if (at >= today - 30 * DAY) push('month', this._t('conversationGroupMonth'), c);\n else {\n const d = new Date(at);\n const sameYear = d.getFullYear() === now.getFullYear();\n const raw = new Intl.DateTimeFormat(tag, sameYear ? { month: 'long' } : { month: 'long', year: 'numeric' }).format(d);\n // French month names are lower-case; a heading is not.\n const label = raw.charAt(0).toLocaleUpperCase(tag) + raw.slice(1);\n push(`m-${d.getFullYear()}-${String(d.getMonth()).padStart(2, '0')}`, label, c);\n }\n }\n const fixed = ['pinned', 'today', 'yesterday', 'week', 'month']\n .map(k => buckets.get(k))\n .filter((g): g is ConversationGroup => !!g);\n const months = [...buckets.keys()]\n .filter(k => k.startsWith('m-'))\n .sort((a, b) => b.localeCompare(a))\n .map(k => buckets.get(k)!);\n const undated = buckets.get('undated');\n return [...fixed, ...months, ...(undated ? [undated] : [])];\n }\n\n private _renderGroup(group: ConversationGroup): string {\n const rows = group.items.map(conv => this._renderItem(conv)).join(''); // safe-text: each row is built by _renderItem, which escapes every field it interpolates.\n if (group.label === null) {\n return `<div class=\"aparte-conv-group\">${rows}</div>`;\n }\n const label = escapeHtml(group.label);\n return `\n<div class=\"aparte-conv-group\" role=\"group\" aria-label=\"${escapeAttr(group.label)}\">\n <div class=\"aparte-menu__label aparte-conv-group__label\" aria-hidden=\"true\">${label}</div>\n ${rows}\n</div>`;\n }\n\n private _renderItem(conv: AparteConversationListItem): string {\n const isActive = conv.id === this._activeId;\n const classes = [\n 'aparte-menu__item aparte-conv-item',\n isActive ? 'aparte-conv-item--active' : '',\n conv.archivedAt ? 'aparte-conv-item--archived' : '',\n conv.pinnedAt ? 'aparte-conv-item--pinned' : '',\n ].filter(Boolean).join(' ');\n const escapedId = escapeAttr(conv.id);\n const title = escapeHtml(conv.title || this._t('newChat'));\n const actionsLabel = escapeAttr(this._t('conversationActions'));\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 moreGlyph = resolveConfig(this).getIcon('more'); // safe-text: the icon provider's SVG — markup by contract, which is what getIcon returns everywhere in core.\n return `\n<div class=\"${escapeAttr(classes)}\" data-conv-id=\"${escapedId}\">\n <button\n class=\"aparte-conv-item__select\"\n type=\"button\"\n data-select-id=\"${escapedId}\"\n aria-current=\"${isActive ? 'page' : 'false'}\"\n ><span class=\"aparte-conv-item__title\">${title}</span></button>\n <button\n class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__more\"\n type=\"button\"\n data-more-id=\"${escapedId}\"\n aria-label=\"${actionsLabel}\"\n title=\"${actionsLabel}\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n >${moreGlyph}</button>\n</div>`;\n }\n\n // ─── The row's menu ───────────────────────────────────────────────────\n\n private _menuMarkup(conv: AparteConversationListItem): string {\n const config = resolveConfig(this);\n const editGlyph = config.getIcon('edit'); // safe-text: the icon provider's SVG — markup by contract.\n const pinGlyph = config.getIcon('pin'); // safe-text: the icon provider's SVG — markup by contract.\n const archiveGlyph = config.getIcon(conv.archivedAt ? 'unarchive' : 'archive'); // safe-text: the icon provider's SVG — markup by contract.\n const trashGlyph = config.getIcon('trash'); // safe-text: the icon provider's SVG — markup by contract.\n const rename = escapeHtml(this._t('renameConversation'));\n const pin = escapeHtml(this._t(conv.pinnedAt ? 'unpinConversation' : 'pinConversation'));\n const archive = escapeHtml(this._t(conv.archivedAt ? 'unarchiveConversation' : 'archiveConversation'));\n const remove = escapeHtml(this._t('deleteConversation'));\n return `\n<button type=\"button\" class=\"aparte-menu__item\" role=\"menuitem\" data-menu-action=\"rename\">${editGlyph}<span>${rename}</span></button>\n<button type=\"button\" class=\"aparte-menu__item\" role=\"menuitem\" data-menu-action=\"pin\">${pinGlyph}<span>${pin}</span></button>\n<button type=\"button\" class=\"aparte-menu__item\" role=\"menuitem\" data-menu-action=\"archive\">${archiveGlyph}<span>${archive}</span></button>\n<div class=\"aparte-menu__separator\" role=\"separator\"></div>\n<button type=\"button\" class=\"aparte-menu__item aparte-conv-menu__item--danger\" role=\"menuitem\" data-menu-action=\"delete\">${trashGlyph}<span>${remove}</span></button>`;\n }\n\n /** The question the confirm step asks — the popover's accessible name while it asks it. */\n private _confirmQuestion(conv: AparteConversationListItem): string {\n return this._t('deleteConversationConfirm').replace('{title}', () => conv.title || this._t('newChat'));\n }\n\n private _confirmMarkup(conv: AparteConversationListItem): string {\n const question = escapeHtml(this._confirmQuestion(conv));\n const cancel = escapeHtml(this._t('cancel'));\n const remove = escapeHtml(this._t('deleteConversation'));\n return `\n<div class=\"aparte-conv-menu__confirm\">\n <p class=\"aparte-conv-menu__question\">${question}</p>\n <div class=\"aparte-conv-menu__actions\">\n <button type=\"button\" class=\"aparte-btn aparte-btn--sm aparte-btn--ghost\" data-menu-action=\"cancel\">${cancel}</button>\n <button type=\"button\" class=\"aparte-btn aparte-btn--sm aparte-btn--danger aparte-btn--solid\" data-menu-action=\"confirm-delete\">${remove}</button>\n </div>\n</div>`;\n }\n\n private _openMenu(row: HTMLElement, button: HTMLElement): void {\n this._closeMenu();\n const id = row.dataset['convId']!;\n const conv = this._conversations.find(c => c.id === id);\n if (!conv) return;\n const menu = document.createElement('div');\n menu.className = 'aparte-menu aparte-conv-menu';\n menu.setAttribute('role', 'menu');\n menu.setAttribute('aria-label', conv.title || this._t('newChat'));\n menu.innerHTML = this._menuMarkup(conv); // safe-text: _menuMarkup escapes every locale string and only interpolates provider glyphs.\n row.appendChild(menu);\n button.setAttribute('aria-expanded', 'true');\n this._open = { id, row, button, menu };\n this._placeMenu(button, menu);\n menu.querySelector<HTMLElement>('[role=\"menuitem\"]')?.focus();\n document.addEventListener('pointerdown', this._onDocumentPointerDown, true);\n document.addEventListener('pointerup', this._onDocumentPointerUp, true);\n document.addEventListener('pointercancel', this._onDocumentPointerUp, true);\n window.addEventListener('scroll', this._onWindowScroll, true);\n window.addEventListener('resize', this._onWindowScroll);\n }\n\n /**\n * Fixed, below the button's end edge, flipped above it when the viewport ends\n * first. Fixed rather than absolute because a sidebar scrolls: an absolute menu on\n * the last rows would be clipped by the list's own overflow. No anchoring library —\n * the menu closes on any scroll or resize, which is what makes the arithmetic\n * safe to do once.\n */\n private _placeMenu(button: HTMLElement, menu: HTMLElement): void {\n const r = button.getBoundingClientRect();\n const m = menu.getBoundingClientRect();\n let top = r.bottom + MENU_GAP;\n if (top + m.height > window.innerHeight - VIEWPORT_MARGIN && r.top - m.height - MENU_GAP >= VIEWPORT_MARGIN) {\n top = r.top - m.height - MENU_GAP;\n }\n const rtl = getComputedStyle(this).direction === 'rtl';\n let left = rtl ? r.left : r.right - m.width;\n left = Math.max(VIEWPORT_MARGIN, Math.min(left, window.innerWidth - m.width - VIEWPORT_MARGIN));\n menu.style.top = `${top}px`;\n menu.style.left = `${left}px`;\n }\n\n private _closeMenu(returnFocus = false): void {\n const open = this._open;\n if (!open) return;\n this._open = null;\n open.menu.remove();\n open.button.setAttribute('aria-expanded', 'false');\n document.removeEventListener('pointerdown', this._onDocumentPointerDown, true);\n document.removeEventListener('pointerup', this._onDocumentPointerUp, true);\n document.removeEventListener('pointercancel', this._onDocumentPointerUp, true);\n this._pressInMenu = false;\n window.removeEventListener('scroll', this._onWindowScroll, true);\n window.removeEventListener('resize', this._onWindowScroll);\n if (returnFocus && open.button.isConnected) open.button.focus();\n }\n\n /**\n * A pointer is down inside the open menu. WebKit does not move the focus to a\n * button on mousedown: the element that held it is blurred instead — `focusout`\n * with no `relatedTarget` — before the click reaches the item. `_onFocusOut` used\n * to read that as the focus leaving the menu and close it on the press, so with a\n * mouse no item could be chosen at all. Held from the press to its release.\n */\n private _pressInMenu = false;\n\n private _onDocumentPointerUp = (): void => {\n this._pressInMenu = false;\n };\n\n private _onDocumentPointerDown = (e: Event): void => {\n const open = this._open;\n if (!open) return;\n // `eventOrigin`, not `e.target`: this listens on `document`, and a chat mounted\n // inside a consumer's shadow root retargets the event to the shadow host on its\n // way out — every question below would be asked of the wrong element.\n const target = eventOrigin(e) as Node | null;\n this._pressInMenu = !!target && open.menu.contains(target);\n // The button's own click toggles; closing here too would reopen on the click.\n if (open.menu.contains(target) || open.button.contains(target)) return;\n this._closeMenu();\n };\n\n private _onWindowScroll = (): void => {\n this._closeMenu();\n };\n\n private _onFocusOut = (e: FocusEvent): void => {\n const open = this._open;\n if (!open) return;\n const next = e.relatedTarget as Node | null;\n // A blur with nowhere to go, during a press inside the menu: the browser is\n // clearing the focus for a button it will not focus (WebKit), not leaving.\n if (!next && this._pressInMenu) return;\n if (next && (open.menu.contains(next) || open.button.contains(next))) return;\n // Focus left the menu for somewhere else (a Tab, a programmatic move): close\n // without pulling it back.\n if (open.menu.contains(e.target as Node)) this._closeMenu();\n };\n\n /**\n * The gesture, finished. The event has already gone out — cancelable, so a host\n * that wants the write for itself calls `preventDefault()` and this stands down —\n * and what is left is the registered conversation manager doing it. Without\n * `manage` the element behaves exactly as it did before the attribute existed.\n *\n * A `manage` with no manager registered is a menu that does nothing, so the first\n * gesture says so and names the call that fixes it. Said here rather than at\n * `connectedCallback` because a list in the page's HTML connects before the script\n * that registers the manager runs: a warning at connect would fire on a setup that\n * is perfectly correct one tick later.\n */\n private _manage(event: Event, run: (manager: AparteConversationManager) => Promise<unknown>): void {\n if (!this.hasAttribute('manage') || event.defaultPrevented) return;\n const manager = resolveConfig(this).getConversationManager();\n if (!manager) {\n if (this._warnedNoManager) return;\n this._warnedNoManager = true;\n console.warn(\n '[aparte-conversation-list] `manage` is set but no conversation manager is registered, '\n + 'so the row menu writes nothing. Call setConversationManager(manager) on your config, '\n + 'or drop `manage` and handle the events yourself.'\n );\n return;\n }\n void run(manager).catch((err: unknown) => {\n console.warn('[aparte-conversation-list] the conversation manager refused the change:', err);\n });\n }\n\n private _onMenuAction(action: string): void {\n const open = this._open;\n if (!open) return;\n const conv = this._conversations.find(c => c.id === open.id);\n if (!conv) { this._closeMenu(); return; }\n const id = conv.id;\n switch (action) {\n case 'rename':\n this._closeMenu();\n this._startRename(open.row, conv);\n return;\n // The two toggles dispatch each name as a LITERAL, in its own branch: the\n // manifest analyzer records a dispatch whose name is a ternary as an event\n // with no name (the Angular generator then crashes on it), and one whose\n // name is a local as an event called after the local.\n case 'pin':\n this._closeMenu(true);\n if (conv.pinnedAt) {\n const unpin = new CustomEvent<AparteConversationPinDetail>(\n 'aparte-conversation-unpin',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(unpin);\n this._manage(unpin, (m) => m.unpin(id));\n } else {\n const pin = new CustomEvent<AparteConversationPinDetail>(\n 'aparte-conversation-pin',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(pin);\n this._manage(pin, (m) => m.pin(id));\n }\n return;\n case 'archive':\n this._closeMenu(true);\n if (conv.archivedAt) {\n const unarchive = new CustomEvent<AparteConversationArchiveDetail>(\n 'aparte-conversation-unarchive',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(unarchive);\n this._manage(unarchive, (m) => m.unarchive(id));\n } else {\n const archive = new CustomEvent<AparteConversationArchiveDetail>(\n 'aparte-conversation-archive',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(archive);\n this._manage(archive, (m) => m.archive(id));\n }\n return;\n case 'delete': {\n // Ask first. The safe answer takes the focus — and it takes it BEFORE the\n // items it replaces are removed, which is the whole of this dance.\n //\n // `menu.innerHTML = …` destroyed the item the reader had just activated\n // while it still held the focus, and a browser blurs a node it is about to\n // remove: `focusout`, no `relatedTarget`, dispatched while the node is\n // still in the tree. `_onFocusOut` reads that as \"the focus left the menu\"\n // and closes it — so the question never appeared and there was no way at\n // all to delete a conversation. jsdom runs no such fixup, which is why the\n // unit tests were green; see `the-keyboard-keeps-its-place.test.ts`.\n //\n // Adding the question, moving the focus into it, and only then removing the\n // old items means nothing focused is ever removed.\n const previous = [...open.menu.childNodes];\n const holder = document.createElement('div');\n holder.innerHTML = this._confirmMarkup(conv); // safe-text: _confirmMarkup escapes the title and every locale string.\n open.menu.append(...holder.childNodes);\n // While it asks, the popover IS the question: a `role=\"menu\"` may hold\n // menu items and nothing else, and the two answers are buttons — axe reads\n // that as a critical `aria-required-children` violation, on a state one\n // click away, in the element the accessibility guide offers as the pattern\n // to copy. `role=\"menuitem\"` on a destructive confirm would be the cheap\n // lie; a question with two answers is a dialog. `_openMenu` builds the\n // menu afresh, so the role comes back with the items. The row's more\n // button keeps `aria-haspopup=\"menu\"` on purpose: what it opens IS a\n // menu, and the question is one step inside it, not what the button\n // announces.\n open.menu.setAttribute('role', 'dialog');\n open.menu.setAttribute('aria-label', this._confirmQuestion(conv));\n open.menu.querySelector<HTMLElement>('[data-menu-action=\"cancel\"]')?.focus();\n for (const node of previous) node.remove();\n this._placeMenu(open.button, open.menu);\n return;\n }\n case 'cancel':\n this._closeMenu(true);\n return;\n case 'confirm-delete': {\n this._closeMenu(true);\n const remove = new CustomEvent<AparteConversationDeleteDetail>(\n 'aparte-conversation-delete',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(remove);\n this._manage(remove, (m) => m.delete(id));\n return;\n }\n }\n }\n\n private _menuFocusables(): HTMLElement[] {\n const open = this._open;\n if (!open) return [];\n return Array.from(open.menu.querySelectorAll<HTMLElement>('[role=\"menuitem\"], [data-menu-action]'));\n }\n\n private _onMenuKeydown(e: KeyboardEvent): void {\n const items = this._menuFocusables();\n // The focus as this element's own tree reports it — retargeted to the shadow host\n // in `document`, which made the index -1 and every arrow jump from the first item.\n const index = items.indexOf(activeElementIn(this) as HTMLElement);\n const focusAt = (i: number): void => {\n e.preventDefault();\n items[(i + items.length) % items.length]?.focus();\n };\n switch (e.key) {\n case 'Escape':\n e.preventDefault();\n this._closeMenu(true);\n return;\n case 'Tab':\n // APG: Tab leaves the menu. Land on the button rather than on whatever\n // follows a node that no longer exists.\n e.preventDefault();\n this._closeMenu(true);\n return;\n case 'ArrowDown': focusAt(index + 1); return;\n case 'ArrowUp': focusAt(index - 1); return;\n case 'Home': focusAt(0); return;\n case 'End': focusAt(items.length - 1); return;\n }\n }\n\n // ─── Rename ───────────────────────────────────────────────────────────\n\n private _startRename(row: HTMLElement, conv: AparteConversationListItem): void {\n const select = row.querySelector<HTMLElement>('.aparte-conv-item__select');\n if (!select) return;\n const input = document.createElement('input');\n input.type = 'text';\n input.className = 'aparte-field aparte-field--sm aparte-conv-item__input';\n input.value = conv.title;\n input.setAttribute('aria-label', this._t('conversationTitle'));\n input.dataset['renameId'] = conv.id;\n select.replaceWith(input);\n this._renaming = { id: conv.id, input, done: false };\n input.addEventListener('blur', (e) => {\n const next = (e as FocusEvent).relatedTarget as Node | null;\n // Restore when the focus is going nowhere (`<body>`) or into the list this\n // render is about to destroy. Not when it is heading for a live element\n // outside — pulling it back from there is the theft `_onFocusOut` avoids.\n this._finishRename(true, !next || this.contains(next));\n });\n input.focus();\n input.select();\n }\n\n /**\n * One exit for the three ways out (Enter, Escape, leaving the field), guarded so\n * the blur a removal causes cannot commit a second time. The row is re-rendered\n * from the array either way: the list shows data, and the host's re-assignment is\n * what makes the new title appear.\n *\n * `restoreFocus` puts the keyboard back on the row: every exit destroys the field\n * the reader was in, so without it the focus lands on `<body>` and the next Tab\n * restarts at the top of the page. The caller decides — the blur path says no when\n * the focus is already heading somewhere live outside this list.\n */\n private _finishRename(commit: boolean, restoreFocus: boolean): void {\n const renaming = this._renaming;\n if (!renaming || renaming.done) return;\n renaming.done = true;\n const conv = this._conversations.find(c => c.id === renaming.id);\n const title = renaming.input.value.trim();\n this._render();\n if (commit && conv && title && title !== conv.title) {\n const rename = new CustomEvent<AparteConversationRenameDetail>(\n 'aparte-conversation-rename',\n { detail: { id: conv.id, title }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(rename);\n this._manage(rename, (m) => m.updateTitle(conv.id, title));\n }\n // After the event, not before: a host that re-assigns `conversations` on it\n // re-renders the list, and the button focused beforehand is gone by now.\n if (restoreFocus) {\n this.querySelector<HTMLElement>(`[data-select-id=\"${cssEscape(renaming.id)}\"]`)?.focus(); // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n }\n }\n\n // ─── Events ───────────────────────────────────────────────────────────\n\n private _onClick = (e: Event): void => {\n const target = e.target as HTMLElement;\n const menuAction = target.closest<HTMLElement>('[data-menu-action]');\n if (menuAction && this._open?.menu.contains(menuAction)) {\n e.stopPropagation();\n this._onMenuAction(menuAction.dataset['menuAction']!);\n return;\n }\n const more = target.closest<HTMLElement>('[data-more-id]');\n if (more) {\n e.stopPropagation();\n if (this._open?.button === more) {\n this._closeMenu(true);\n } else {\n this._openMenu(more.closest<HTMLElement>('[data-conv-id]')!, more);\n }\n return;\n }\n if (target.closest('[data-rename-id]')) return;\n const select = target.closest<HTMLElement>('[data-select-id]');\n if (select) {\n this._closeMenu();\n this.dispatchEvent(new CustomEvent<AparteConversationSelectDetail>(\n 'aparte-conversation-select',\n { detail: { id: select.dataset['selectId']! }, bubbles: true, composed: true }\n ));\n }\n };\n\n private _onKeydown = (e: KeyboardEvent): void => {\n const target = e.target as HTMLElement;\n if (this._open?.menu.contains(target)) {\n this._onMenuKeydown(e);\n return;\n }\n if (target.matches('[data-rename-id]')) {\n if (e.key === 'Enter') { e.preventDefault(); this._finishRename(true, true); }\n else if (e.key === 'Escape') { e.preventDefault(); this._finishRename(false, true); }\n return;\n }\n // The rows are real buttons: Enter and Space are the browser's.\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.querySelector('[data-select-id]')?.setAttribute('aria-current', isActive ? 'page' : 'false');\n });\n }\n}\n\nif (!customElements.get('aparte-conversation-list')) customElements.define('aparte-conversation-list', AparteConversationList);\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from '../composer/aparte-composer.js';\nimport { chatBoundaryOf } from '../../utils/chat-boundary.js';\nimport { isTranscriptEmpty } from '../../utils/transcript.js';\n\n/**\n * One prompt starter: the text sent, or a visible label over a longer prompt.\n *\n * A string is both. The object form is for the case every welcome screen has —\n * \"Write a haiku\" on the chip, \"Write a haiku about web components.\" in the request.\n */\nexport type AparteSuggestion = string | { label: string; prompt?: string };\n\n/** Detail of `aparte-suggestion`: what was shown and what will be sent. */\nexport interface AparteSuggestionEventDetail {\n label: string;\n prompt: string;\n}\n\nconst isSuggestion = (value: unknown): value is AparteSuggestion =>\n typeof value === 'string'\n || (typeof value === 'object' && value !== null && typeof (value as { label?: unknown }).label === 'string');\n\n/**\n * The `suggestions` JSON, from wherever it came: the attribute, or the property when a\n * framework hands the same string to the setter. One parse, so the two channels the\n * docblock calls equivalent actually are.\n */\nconst parseSuggestions = (raw: string): AparteSuggestion[] => {\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) ? parsed.filter(isSuggestion) : [];\n } catch {\n console.warn('[aparte-suggestions] `suggestions` is not a JSON array. Use [\"…\"] or [{\"label\": \"…\", \"prompt\": \"…\"}].');\n return [];\n }\n};\n\nconst labelOf = (s: AparteSuggestion): string => (typeof s === 'string' ? s : s.label);\nconst promptOf = (s: AparteSuggestion): string => (typeof s === 'string' ? s : (s.prompt ?? s.label));\n\n/**\n * Prompt starters — a row of suggested prompts a reader clicks instead of typing.\n *\n * Every chat product opens on three or four of these. This element is that pattern\n * done once, wearing the button recipe.\n *\n * A click goes THROUGH the composer — `setValue()` then `submit()` — never a synthetic\n * `aparte-send`. The composer's `submit()` is where every gate lives (disabled, already\n * streaming, `requireModelSelection`), and a chip that bypassed them could send a\n * request with an empty model id while the composer was visibly greyed out. So the\n * element needs a composer: the nearest `<aparte-composer>` ancestor, else the one\n * serving the chat named by `target`, else the first one in the document.\n *\n * Core honours the click alone, so it is live by default. `aparte-suggestion` fires first and is cancelable — `preventDefault()`\n * leaves the composer untouched, for an app that wants the prompt for itself.\n *\n * It declares no custom property of its own: the gap comes from the global\n * `--aparte-space-*` tokens and the chips from the `aparte-btn` recipe, so it inherits\n * a theme rather than exposing knobs to re-set.\n *\n * @element aparte-suggestions\n *\n * @attr {string} suggestions - The starters, as a JSON array: strings, or\n * `{ \"label\", \"prompt\" }` objects when the visible label and the sent text differ.\n * The `suggestions` PROPERTY takes the same shape without the JSON.\n * @attr {string} mode - `send` (default): the click fills the composer and submits.\n * `fill`: the click fills the composer and focuses it, so the reader edits first.\n * @attr {boolean} empty-only - Shows the row only while the conversation it serves is\n * empty: hidden (`hidden`) once a message is in its transcript or on its way — a\n * thread being loaded counts — and shown again when the transcript is cleared, by a\n * new chat for instance. The composer's send hides it at once, before its message\n * lands. The element sets and lifts `hidden` itself and never lifts one you set.\n * With no transcript to read — a composer outside any chat — the first send hides it\n * until the attribute is removed.\n * @attr {string} target - The id of the `<aparte-chat>` whose composer should receive\n * the click, when the element is not inside that composer.\n * @attr {boolean} data-empty - Reflected BY the element while it has no suggestion to\n * show; the stylesheet hides it then. Read-only, do not set it yourself.\n *\n * @fires {CustomEvent<AparteSuggestionEventDetail>} aparte-suggestion - A starter was\n * clicked. Bubbles, and is cancelable: `preventDefault()` stops the composer from\n * being filled or submitted.\n *\n * @example\n * <!-- The starters sit above the input, inside the composer; a click fills and sends. -->\n * <aparte-composer>\n * <aparte-suggestions empty-only\n * suggestions='[\"What is aparté?\", {\"label\": \"Write a haiku\", \"prompt\": \"Write a haiku about web components.\"}]'>\n * </aparte-suggestions>\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 * </div>\n * </aparte-composer>\n */\nexport class AparteSuggestions extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['suggestions', 'target', 'empty-only'];\n }\n\n private _suggestions: AparteSuggestion[] = [];\n private _composer: AparteComposer | null = null;\n\n /** The viewport of the chat this row serves, while `empty-only` follows it. */\n private _transcript: Element | null = null;\n private _transcriptObserver: MutationObserver | null = null;\n /** The composer sent since the transcript was last seen empty. */\n private _sent = false;\n private _wasEmpty = true;\n /** `hidden` was written by this element, so it is this element's to lift. */\n private _hidOwn = false;\n\n /*\n * `empty-only` follows the transcript: shown while it is empty, hidden once it has\n * messages, so a restored thread (nothing sent yet) has no starters on top of it\n * and a new chat brings them back. The send is the other half, because it comes first — the row\n * hides on the click rather than a frame later when the bubble lands — and because\n * it is all there is to read when the messages are not `<aparte-chat-bubble>`s\n * (a wrapper's `renderBubble`) or there is no transcript at all. A transcript seen\n * going from full to empty is a new conversation, which forgets the send.\n */\n private _onSend = (): void => {\n if (!this.hasAttribute('empty-only')) return;\n // A composer built after this element had no chat around it yet at connection.\n if (!this._transcript) this._watchTranscript();\n this._sent = true;\n this._applyEmptyOnly();\n };\n\n private _applyEmptyOnly = (): void => {\n const empty = this._transcript ? isTranscriptEmpty(this._transcript) : true;\n if (empty && !this._wasEmpty) this._sent = false;\n this._wasEmpty = empty;\n this._setHidden(!empty || this._sent);\n };\n\n private _onConfigChange = (): void => {\n const row = this.querySelector('.aparte-suggestions');\n if (row) row.setAttribute('aria-label', this._groupLabel());\n };\n\n /** The starters. Setting it re-renders; the `suggestions` attribute is the JSON form. */\n get suggestions(): AparteSuggestion[] {\n return this._suggestions;\n }\n\n /**\n * Takes the array, or the JSON string the attribute documents.\n *\n * Both, because both arrive: React 19 and Svelte assign the PROPERTY whenever the\n * element has one of that name, so `<aparte-suggestions suggestions='[…]'>` — the\n * documented spelling, typed valid by both wrappers — reaches this setter as a\n * string while the attribute (and so `attributeChangedCallback`) is never written.\n */\n set suggestions(value: AparteSuggestion[] | string) {\n this._suggestions = typeof value === 'string'\n ? parseSuggestions(value)\n : Array.isArray(value) ? value.filter(isSuggestion) : [];\n if (this.isConnected) this._render();\n }\n\n connectedCallback(): void {\n if (this.hasAttribute('suggestions')) this._readAttribute();\n this._render();\n this._watchComposer();\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n this._composer?.removeEventListener('aparte-send', this._onSend);\n this._composer = null;\n this._transcriptObserver?.disconnect();\n this._transcriptObserver = null;\n this._transcript = null;\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n attributeChangedCallback(name: string): void {\n if (!this.isConnected) return;\n if (name === 'suggestions') {\n this._readAttribute();\n this._render();\n } else if (name === 'target') {\n this._watchComposer();\n } else if (name === 'empty-only') {\n this._watchTranscript();\n if (!this.hasAttribute('empty-only')) {\n this._sent = false;\n this._setHidden(false);\n }\n }\n }\n\n private _groupLabel(): string {\n return resolveConfig(this).t('suggestionsLabel') || 'Suggested prompts';\n }\n\n private _parsed: string | null = null;\n\n /**\n * Parse the `suggestions` attribute once per value. The attribute callback and\n * `connectedCallback` both arrive for a parsed element, in an order the parser\n * decides; without this, an invalid value warned twice.\n */\n private _readAttribute(): void {\n const raw = this.getAttribute('suggestions');\n if (raw === null || raw === this._parsed) return;\n this._parsed = raw;\n this._suggestions = parseSuggestions(raw);\n }\n\n /**\n * The composer the click goes through, resolved the way the composer's own parts\n * resolve it — by ancestry first. Outside a composer, `target` names the chat and the\n * composer serving it is either inside that chat or points at it; the first composer\n * on the page is the last resort, so the one-chat page needs no attribute at all.\n */\n private _resolveComposer(): AparteComposer | null {\n const nearest = this.closest('aparte-composer');\n if (nearest) return nearest as AparteComposer;\n const target = this.getAttribute('target');\n if (target) {\n const scope = document.getElementById(target);\n const inside = scope?.querySelector('aparte-composer');\n if (inside) return inside as AparteComposer;\n for (const composer of document.querySelectorAll('aparte-composer[target]')) {\n if (composer.getAttribute('target') === target) return composer as AparteComposer;\n }\n }\n return document.querySelector('aparte-composer') as AparteComposer | null;\n }\n\n private _watchComposer(): void {\n this._composer?.removeEventListener('aparte-send', this._onSend);\n this._composer = this._resolveComposer();\n this._composer?.addEventListener('aparte-send', this._onSend);\n this._watchTranscript();\n }\n\n /**\n * The transcript of the chat the click goes to: the chat around the composer, else\n * the one around this element, else the one `target` names (here, or on that\n * composer). No last resort on the page, unlike the composer's: following another\n * chat's transcript would be worse than following none, which falls back to the send.\n */\n private _resolveTranscript(): Element | null {\n const id = this.getAttribute('target') ?? this._composer?.getAttribute('target') ?? null;\n const chat = chatBoundaryOf(this._composer) ?? chatBoundaryOf(this) ?? (id ? document.getElementById(id) : null);\n if (!chat) return null;\n return chat.matches('aparte-chat-viewport') ? chat : chat.querySelector('aparte-chat-viewport');\n }\n\n /** Follow the transcript while `empty-only` is set, and stop following it when it is not. */\n private _watchTranscript(): void {\n const transcript = this.hasAttribute('empty-only') ? this._resolveTranscript() : null;\n if (transcript !== this._transcript) {\n this._transcriptObserver?.disconnect();\n this._transcriptObserver = null;\n this._transcript = transcript;\n if (transcript) {\n // Bubbles land anywhere under the viewport; `loading` is the other way of\n // not being empty. The same watch `<aparte-chat>` runs for `data-empty`.\n this._transcriptObserver = new MutationObserver(this._applyEmptyOnly);\n this._transcriptObserver.observe(transcript, {\n childList: true, subtree: true, attributes: true, attributeFilter: ['loading'],\n });\n }\n }\n if (this.hasAttribute('empty-only')) this._applyEmptyOnly();\n }\n\n /** Set `hidden`, or lift it only when this element set it: a `hidden` the page wrote stays. */\n private _setHidden(hide: boolean): void {\n if (hide) {\n if (this.hidden) return;\n this.hidden = true;\n this._hidOwn = true;\n } else if (this._hidOwn) {\n this.hidden = false;\n this._hidOwn = false;\n }\n }\n\n private _render(): void {\n this.replaceChildren();\n if (this._suggestions.length === 0) {\n this.setAttribute('data-empty', '');\n return;\n }\n this.removeAttribute('data-empty');\n const row = document.createElement('div');\n row.className = 'aparte-suggestions';\n row.setAttribute('role', 'group');\n row.setAttribute('aria-label', this._groupLabel());\n for (const suggestion of this._suggestions) {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'aparte-btn aparte-btn--surface aparte-btn--pill aparte-suggestion';\n button.textContent = labelOf(suggestion);\n const prompt = promptOf(suggestion);\n // The full prompt on hover when the chip shows a shorter label for it.\n if (prompt !== button.textContent) button.title = prompt;\n button.addEventListener('click', () => this._activate(suggestion));\n row.appendChild(button);\n }\n this.appendChild(row);\n }\n\n private _activate(suggestion: AparteSuggestion): void {\n const detail: AparteSuggestionEventDetail = { label: labelOf(suggestion), prompt: promptOf(suggestion) };\n const proceed = this.dispatchEvent(new CustomEvent<AparteSuggestionEventDetail>('aparte-suggestion', {\n detail, bubbles: true, composed: true, cancelable: true,\n }));\n if (!proceed) return;\n // Resolved again at click time: on a page that builds its composer after this\n // element, the one found at connection may have been nothing.\n const composer = this._composer ?? this._resolveComposer();\n if (!composer) {\n console.warn('[aparte-suggestions] no <aparte-composer> to send through — put the element inside one, or point `target` at the chat it serves.');\n return;\n }\n if (composer !== this._composer) this._watchComposer();\n const chipHadFocus = this.contains(document.activeElement);\n composer.setValue(detail.prompt);\n if (this.getAttribute('mode') === 'fill') composer.focus();\n else {\n composer.submit();\n // The chip that sent may go with its row (`empty-only`), and the focus with it,\n // to <body>. The person's next move is the composer, so it goes there.\n if (chipHadFocus) composer.focus();\n }\n }\n}\n\nif (!customElements.get('aparte-suggestions')) {\n customElements.define('aparte-suggestions', AparteSuggestions);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteMessageDoneEventDetail, AparteCompactDoneEventDetail, AparteCompactEventDetail } from '../../types/events.js';\n\n/** How full the window is, against the two thresholds. */\nexport type AparteContextLevel = 'ok' | 'warn' | 'danger';\n\n/** Detail of `aparte-context-threshold`: the level just entered, and the numbers behind it. */\nexport interface AparteContextThresholdEventDetail {\n level: AparteContextLevel;\n /** Tokens the last turn reported as used — the prompt sent plus the reply. */\n used: number;\n /** The window those tokens are measured against. */\n window: number;\n /** `used / window`, capped at 1. */\n ratio: number;\n /** The chat this gauge watches, when it has an id. */\n targetId?: string;\n}\n\nconst fraction = (raw: string | null, fallback: number): number => {\n const n = Number(raw);\n return raw !== null && Number.isFinite(n) && n > 0 && n <= 1 ? n : fallback;\n};\n\nconst el = (tag: string, className: string): HTMLElement => {\n const node = document.createElement(tag);\n node.className = className;\n return node;\n};\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\n/**\n * The ring: a track and a value circle on a 36-unit box. `pathLength=\"100\"` makes the\n * value's dash the percentage itself (`stroke-dasharray: <ratio> 100`, in the sheet), so\n * the element sets one custom property per render and no geometry.\n */\nconst ringSvg = (): SVGSVGElement => {\n const svg = document.createElementNS(SVG_NS, 'svg');\n svg.setAttribute('class', 'aparte-context__ring');\n svg.setAttribute('viewBox', '0 0 36 36');\n for (const part of ['track', 'value'] as const) {\n const circle = document.createElementNS(SVG_NS, 'circle');\n circle.setAttribute('class', `aparte-context__${part}`);\n circle.setAttribute('cx', '18');\n circle.setAttribute('cy', '18');\n circle.setAttribute('r', '15.5');\n if (part === 'value') circle.setAttribute('pathLength', '100');\n svg.appendChild(circle);\n }\n return svg;\n};\n\n/**\n * A gauge of the model's context window: how much of it the conversation uses.\n *\n * It reads two numbers and draws their ratio. The USED part is what each turn reports —\n * `aparte-message-done` carries the provider's usage, and the prompt tokens of the\n * last call are the size of the context as the model saw it. The WINDOW is the\n * current model's `contextWindow` (a provider's `/models` fetch fills it in), or the\n * `window` attribute when you know better. With no window, or before the first turn,\n * it renders nothing — an affordance with nothing to show has no chrome to leave.\n *\n * Two thresholds turn it `warn` then `danger` (`data-level`), and crossing one fires\n * `aparte-context-threshold`. With `auto-compact`, a turn that ends in `danger` dispatches\n * `aparte-compact` for its chat — one request at a time, asked again on the next turn in\n * `danger` once the plugin has answered. What answers the command\n * is `@aparte/plugin-compaction` (`setupCompaction()`): it summarises what no longer\n * fits the window and keeps the recent turns. Core itself does not compact — the\n * gauge asks, the plugin does, and a page without the plugin gets a gauge that only\n * measures.\n *\n * The bar wears the `aparte-progress` recipe. `variant=\"ring\"` draws the same reading\n * as a ring with the percentage beside it — for a toolbar, where a bar wants a width\n * and a ring wants none; the full reading is the ring's `title`. The two share the\n * levels, the events and the accessible name; only the drawing differs.\n *\n * @element aparte-context\n *\n * @attr {number} window - The context window, in tokens. Overrides the current model's `contextWindow`.\n * @attr {number} used - Tokens in use before a turn of this page has reported any: a restored conversation's last usage. The next turn's usage replaces it.\n * @attr {number} warn - Fraction of the window at which the level turns `warn`. Default `0.75`.\n * @attr {number} danger - Fraction at which it turns `danger`. Default `0.9`.\n * @attr {boolean} auto-compact - Dispatch `aparte-compact` on each turn that ends in `danger`, one request at a time.\n * @attr {string} target - The id of the `<aparte-chat>` to watch, when the element is not under it.\n * @attr {string} variant - `bar` (default) or `ring`: a progress bar with the reading beside it, or a ring with the percentage.\n * @attr {string} data-level - Reflected BY the element: `ok`, `warn` or `danger`. Read-only.\n * @attr {boolean} data-empty - Reflected BY the element while it has nothing to show. Read-only.\n *\n * @fires {CustomEvent<AparteContextThresholdEventDetail>} aparte-context-threshold - The level changed. Bubbles.\n * @fires {CustomEvent<AparteCompactEventDetail>} aparte-compact - Dispatched on `window` when `auto-compact` is set and a turn ends in `danger`: compact this target's transcript. One request at a time — the next turn in `danger` asks again once the plugin has answered — and only while the attribute is present. `@aparte/plugin-compaction` answers it; with no listener nothing happens.\n *\n * @cssprop [--aparte-context-ring-size=22px] - Diameter of the ring variant.\n *\n * @example\n * <!-- The same gauge twice: the bar takes the room it is given, the ring takes none. -->\n * <aparte-composer-toolbar>\n * <aparte-context window=\"128000\" auto-compact style=\"flex: 1\"></aparte-context>\n * <aparte-context window=\"128000\" variant=\"ring\"></aparte-context>\n * </aparte-composer-toolbar>\n *\n * <script>\n * // The gauge reads what each turn reports; it draws nothing before the first one.\n * // This is a provider's usage after a long conversation: 78% of a 128k window,\n * // past the `warn` threshold.\n * window.dispatchEvent(new CustomEvent('aparte-message-done', {\n * detail: { usage: { inputTokens: 99400, outputTokens: 600 } },\n * }));\n * </script>\n */\nexport class AparteContext extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['window', 'warn', 'danger', 'target', 'variant', 'used'];\n }\n\n private _used: number | null = null;\n private _level: AparteContextLevel | null = null;\n private _compactRequested = false;\n\n private _onDone = (e: Event): void => {\n const detail = (e as CustomEvent<AparteMessageDoneEventDetail>).detail;\n if (!detail?.usage || !this._isMine(detail.targetId)) return;\n const { usage } = detail;\n this._used = usage.totalTokens ?? (usage.inputTokens + usage.outputTokens);\n this._render();\n };\n\n /**\n * After a real compaction the context is smaller by an amount nobody has measured\n * yet — the next turn's usage will say. Until then the gauge shows nothing rather\n * than a number it knows to be wrong. The event names the chat it compacted, so on a\n * multi-chat page only that chat's gauge resets; an unnamed one resets every gauge,\n * and each is right again one turn later.\n */\n private _onCompacted = (e: Event): void => {\n const detail = (e as CustomEvent<AparteCompactDoneEventDetail>).detail;\n if (!this._isMine(detail?.targetId)) return;\n // The request is spent whatever the outcome. A skip (`empty`, `nothing-to-drop`,\n // `running`, `streaming`) used to return before this line, so with the usage\n // still climbing the level never left `danger` and the gauge never asked again\n // for the life of the element — one refused compaction latched auto-compact off.\n this._compactRequested = false;\n if (detail?.skipped) return;\n this._used = null;\n this._render();\n };\n\n /** A failed compaction spends the request too; the next `danger` asks again. */\n private _onCompactError = (e: Event): void => {\n const detail = (e as CustomEvent<{ targetId?: string }>).detail;\n if (!this._isMine(detail?.targetId)) return;\n this._compactRequested = false;\n };\n\n private _onRerender = (): void => this._render();\n\n /** Tokens the last turn reported — or `used` set, until a turn reports — else `null`. */\n get used(): number | null {\n return this._used;\n }\n\n /**\n * Writes the `used` attribute: a reading for a conversation restored without a turn of\n * this page, which had nothing to report until the next one. `null` clears it.\n */\n set used(value: number | null) {\n if (value === null || value === undefined) this.removeAttribute('used');\n else this.setAttribute('used', String(value));\n }\n\n /** The window in force — the attribute, else the current model's. */\n get window(): number | null {\n return this._window();\n }\n\n /**\n * Writes the `window` attribute; `null` gives the model's own window back.\n *\n * The setter has to exist: React 19 and Svelte assign the PROPERTY whenever the\n * element has one of that name, so `<aparte-context window={8000}>` — a spelling\n * both wrappers type as valid — hit a getter alone and threw.\n */\n set window(value: number | null) {\n if (value === null || value === undefined) this.removeAttribute('window');\n else this.setAttribute('window', String(value));\n }\n\n /** The current level, or `null` while nothing is shown. */\n get level(): AparteContextLevel | null {\n return this._level;\n }\n\n connectedCallback(): void {\n window.addEventListener('aparte-message-done', this._onDone);\n window.addEventListener('aparte-compact-done', this._onCompacted);\n window.addEventListener('aparte-compact-error', this._onCompactError);\n // The model's window is read again at every render, so a model change shows\n // on the next turn; no listener on the picker's event, which is an element's.\n window.addEventListener('aparte-config-change', this._onRerender);\n this._render();\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-message-done', this._onDone);\n window.removeEventListener('aparte-compact-done', this._onCompacted);\n window.removeEventListener('aparte-compact-error', this._onCompactError);\n window.removeEventListener('aparte-config-change', this._onRerender);\n }\n\n attributeChangedCallback(name: string): void {\n if (name === 'used') {\n const n = Number(this.getAttribute('used'));\n this._used = this.hasAttribute('used') && Number.isFinite(n) && n >= 0 ? n : null;\n }\n if (this.isConnected) this._render();\n }\n\n private _window(): number | null {\n const attr = Number(this.getAttribute('window'));\n if (this.hasAttribute('window') && Number.isFinite(attr) && attr > 0) return attr;\n const model = resolveConfig(this).getCurrentModel();\n return model?.contextWindow && model.contextWindow > 0 ? model.contextWindow : null;\n }\n\n /** The chat this gauge watches: `target`, else the chat host above it — the composer's rule. */\n private _ownTargetId(): string | undefined {\n const attr = this.getAttribute('target');\n if (attr) return attr;\n let node: HTMLElement | null = this.parentElement;\n while (node) {\n const isHost = node.tagName.toLowerCase() === 'aparte-chat' || node.hasAttribute('data-aparte-chat');\n if (isHost && node.id) return node.id;\n node = node.parentElement;\n }\n return undefined;\n }\n\n private _isMine(targetId?: string): boolean {\n const own = this._ownTargetId();\n return !targetId || !own || targetId === own;\n }\n\n private _render(): void {\n const capacity = this._window();\n if (capacity === null || this._used === null) {\n this.setAttribute('data-empty', '');\n this.removeAttribute('data-level');\n this.replaceChildren();\n this._level = null;\n return;\n }\n this.removeAttribute('data-empty');\n\n const used = this._used;\n const ratio = Math.min(1, used / capacity);\n const warn = fraction(this.getAttribute('warn'), 0.75);\n const danger = fraction(this.getAttribute('danger'), 0.9);\n const level: AparteContextLevel = ratio >= danger ? 'danger' : ratio >= warn ? 'warn' : 'ok';\n this.setAttribute('data-level', level);\n\n const ring = this.getAttribute('variant') === 'ring';\n let root = this.querySelector<HTMLElement>('.aparte-context');\n // A variant change rebuilds: the bar and the ring share nothing but their text.\n if (root && root.classList.contains('aparte-context--ring') !== ring) {\n this.replaceChildren();\n root = null;\n }\n let meter = this.querySelector<HTMLElement | SVGElement>('[role=\"meter\"]');\n let text = this.querySelector<HTMLElement>('.aparte-context__text');\n if (!root || !meter || !text) {\n root = el('div', ring ? 'aparte-context aparte-context--ring' : 'aparte-context');\n meter = ring ? ringSvg() : el('div', 'aparte-progress');\n meter.setAttribute('role', 'meter');\n if (!ring) meter.appendChild(el('div', 'aparte-progress__bar'));\n text = el('span', 'aparte-context__text');\n root.append(meter, text);\n this.replaceChildren(root);\n }\n /*\n * The locale's tag, not the browser's.\n *\n * `Intl.NumberFormat(undefined, …)` means \"follow the BROWSER\", which is not\n * the locale the app chose — the exact bug `AparteLocale.tag` exists to close,\n * and which `<aparte-conversation-list>` and the bubble's clock already read\n * it for. On a French page `128 000` came out `128K` and the ring read `14%`\n * where French writes `14 %`; on `ja-JP`, `12.8万` is the compact notation\n * and the gauge showed `128K`.\n *\n * `|| undefined` and not `??`: an empty tag is not a language, and passing\n * `''` to `Intl` throws a RangeError rather than falling back.\n */\n const cfg = resolveConfig(this);\n const tag = cfg.getLocale().tag || undefined;\n const format = new Intl.NumberFormat(tag, { notation: 'compact', maximumFractionDigits: 1 });\n const reading = `${format.format(used)} / ${format.format(capacity)}`;\n const percent = Math.round(ratio * 100);\n const label = cfg.t('contextLabel') || 'Context window';\n if (ring) {\n const value = meter.querySelector<SVGElement>('.aparte-context__value');\n value?.style.setProperty('--aparte-context-ratio', String(percent));\n // A zero-length dash with round caps is still a dot; at 0 the value is not drawn.\n value?.classList.toggle('aparte-context__value--empty', percent === 0);\n // The ring shows the percentage; the reading behind it is one hover away.\n root.title = reading;\n } else {\n meter.style.setProperty('--aparte-progress-value', String(percent));\n }\n meter.setAttribute('aria-valuemin', '0');\n meter.setAttribute('aria-valuemax', String(capacity));\n meter.setAttribute('aria-valuenow', String(used));\n meter.setAttribute('aria-label', `${label}: ${reading}`);\n // The label is the SAME integer the dash draws — formatted, not rounded again: two\n // roundings of one ratio disagreed by a point on real windows (0.145 → 14 and \"15 %\").\n text.textContent = ring\n ? new Intl.NumberFormat(tag, { style: 'percent', maximumFractionDigits: 0 }).format(percent / 100)\n : reading;\n\n if (level !== this._level) {\n this._level = level;\n const targetId = this._ownTargetId();\n const detail: AparteContextThresholdEventDetail = { level, used, window: capacity, ratio, targetId };\n this.dispatchEvent(new CustomEvent<AparteContextThresholdEventDetail>('aparte-context-threshold', { detail, bubbles: true, composed: true }));\n if (level !== 'danger') this._compactRequested = false;\n }\n // Asked per TURN, not per level change: one request stays open until the\n // plugin answers (done, skipped or failed), and the next turn in danger asks\n // again. Tied to the level change, a refused compaction — nothing to drop yet,\n // a stream in flight — left the level at danger and the gauge silent for good.\n if (level === 'danger' && this.hasAttribute('auto-compact') && !this._compactRequested) {\n this._compactRequested = true;\n window.dispatchEvent(new CustomEvent<AparteCompactEventDetail>('aparte-compact', { detail: { targetId: this._ownTargetId() } }));\n }\n }\n}\n\nif (!customElements.get('aparte-context')) {\n customElements.define('aparte-context', AparteContext);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { activeElementIn } from '../../utils/event-target.js';\n\n/** Detail of `aparte-scroll-rail-jump`: the message a tick points at. */\nexport interface AparteScrollRailJumpDetail {\n messageId: string;\n}\n\n/** What a tick is drawn for: one per user turn (the default), or one per message. */\nexport type AparteScrollRailEvery = 'user' | 'message';\n\nconst EXCERPT_LENGTH = 60;\n/** The tightest pitch the rail will take before it scrolls itself: a 2px line and a 4px gap. */\nconst MIN_PITCH = 6;\n/** How long the transcript has to be still, after a jump, before the observer marks again. */\nconst SETTLE_MS = 150;\n/** How many times a jump re-aligns on the message once the transcript has settled off it. */\nconst MAX_ALIGN_PASSES = 4;\n/** Within this many pixels of the top, a jump has landed. */\nconst ALIGN_TOLERANCE = 1;\n\n/**\n * A rail of ticks beside the transcript — one per turn — that shows where you are in a\n * long conversation and jumps back to any message on a click.\n *\n * The thing every long chat is missing and no product ships natively: the scrollbar says\n * how far down you are, not which question you are under. This is the minimap of a code\n * editor reduced to its ticks, the search marks of a browser's scrollbar, the scrollspy\n * of a documentation site — the same ancestry, applied to turns. A tick per USER message\n * by default, because a user's turns are the questions and a rail with a tick per reply\n * is unreadable on a long thread; `every=\"message\"` marks each one.\n *\n * It owns nothing of the transcript: the viewport keeps its scroll, the bubbles keep\n * their markup, and the rail only READS them — which bubbles exist (a mutation observer\n * on the chat), which one is under the reader (an intersection observer on the scroll\n * surface, never scroll arithmetic), and the first few words of each for the tick's\n * name. A click is a `scrollIntoView` on the bubble, announced first by a cancelable\n * `aparte-scroll-rail-jump` so a host that pages history in can load it before the\n * jump. Honoured by core alone, so it is live by default.\n *\n * Place it as a direct child of `<aparte-chat>` (or the wrapper's `[data-aparte-chat]`\n * host): the stylesheet floats it on the transcript's end edge, centred on the transcript\n * and clear of a classic scrollbar, and hides it under a coarse pointer, where a 2px\n * tick is not a target. Under two ticks it renders nothing — a rail with one mark says\n * nothing. It is a list, not a minimap: it takes the height of its ticks, up to a share\n * of the transcript (`--aparte-scroll-rail-share`, 60%). When more turns exist than 24px\n * targets fit in that, the pitch tightens to what fits, down to a 6px floor; past that\n * the rail scrolls itself so the current tick is always in view, and the arrow keys walk\n * every tick whatever the pitch.\n *\n * @element aparte-scroll-rail\n *\n * @attr {string} target - The id of the `<aparte-chat>` to follow, when the element is not inside it.\n * @attr {string} every - `user` (default): one tick per user turn. `message`: one per message.\n * @attr {boolean} data-empty - Reflected BY the element while it has fewer than two ticks. Read-only.\n *\n * @fires {CustomEvent<AparteScrollRailJumpDetail>} aparte-scroll-rail-jump - A tick was activated. Bubbles, cancelable: `preventDefault()` leaves the transcript where it is.\n *\n * @cssprop [--aparte-scroll-rail-width=max(var(--aparte-scroll-rail-hit-size), calc(var(--aparte-scroll-rail-tick-size) * 1.6))] - The rail's column, the ticks end-aligned in it. Never under one tick's pressable zone: the rail clips, so a narrower column would cut the target back.\n * @cssprop [--aparte-scroll-rail-tick-size=14px] - Length of a tick; the current one is 1.6× that.\n * @cssprop [--aparte-scroll-rail-tick-thickness=2px] - Thickness of a tick.\n * @cssprop [--aparte-scroll-rail-hit-size=24px] - The pressable zone around a tick (WCAG 2.5.8). The drawn line keeps its own size; this one sizes the pseudo-element and, through the gap below, the pitch — so raising it spaces the ticks out rather than overlapping them. When the ticks would not fit, the rail sets it on itself to the pitch that does, never under 6px.\n * @cssprop [--aparte-scroll-rail-gap=calc(var(--aparte-scroll-rail-hit-size) - var(--aparte-scroll-rail-tick-thickness))] - Space between ticks: the zone minus the line, so gap + thickness is exactly the pitch. Set it smaller and the zones overlap.\n * @cssprop [--aparte-scroll-rail-room=calc(var(--aparte-scroll-rail-width) + var(--aparte-space-2))] - The lane the chat leaves at its end edge while the rail is in it, the rail sitting there, so no line runs under the ticks. `0px` lets the rail float over the text.\n * @cssprop [--aparte-scroll-rail-share=.6] - The share of the transcript's height the rail may take. It is the height of its list up to this, centred on the transcript; past it the pitch tightens, then the rail scrolls.\n * @cssprop [--aparte-scroll-rail-bar=0px] - Published BY the element: the width of the transcript's scrollbar when it is a classic one (0 for an overlay bar), which the stylesheet adds to the rail's end inset so the ticks never sit on the bar.\n * @cssprop [--aparte-scroll-rail-block-start=0px] - Published BY the element: the distance from the host's top edge to the transcript's, so the rail centres on the transcript rather than on the composer too.\n * @cssprop [--aparte-scroll-rail-block-end=0px] - Published BY the element: the distance from the transcript's bottom edge to the host's.\n *\n * @example\n * <aparte-chat style=\"height: 18rem\">\n * <aparte-chat-viewport>\n * <aparte-chat-bubble message-id=\"u1\" data-role=\"user\" content=\"What is a web component?\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"a1\" data-role=\"assistant\" name=\"Assistant\" content=\"A custom element: a tag the browser upgrades to a class you wrote. It carries its own markup, behaviour and, if you want, its own styles — and it works in any framework, or none, because it is the platform's own component model.\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"u2\" data-role=\"user\" content=\"And a light-DOM one?\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"a2\" data-role=\"assistant\" name=\"Assistant\" content=\"One that renders its children into the page rather than into a shadow root, so the page's CSS reaches inside it. aparté is light DOM on purpose: a theme is a stylesheet, not an API.\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"u3\" data-role=\"user\" content=\"Which one is this rail?\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"a3\" data-role=\"assistant\" name=\"Assistant\" content=\"Light DOM, like everything else here. Its ticks are plain buttons in a list; scroll the transcript and watch the current one move.\"></aparte-chat-bubble>\n * </aparte-chat-viewport>\n * <aparte-scroll-rail></aparte-scroll-rail>\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 * </div>\n * </aparte-composer>\n * </aparte-chat>\n */\nexport class AparteScrollRail extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['target', 'every'];\n }\n\n private _host: HTMLElement | null = null;\n private _mutations: MutationObserver | null = null;\n private _resizes: ResizeObserver | null = null;\n private _intersections: IntersectionObserver | null = null;\n /** The bubbles the intersection observer watches — compared by identity, so a re-rendered bubble is re-observed. */\n private _observedBubbles: HTMLElement[] = [];\n /** The scroll surface the observer is rooted on, and whose bottom edge is read. */\n private _observedSurface: HTMLElement | null = null;\n private _rebuildQueued = false;\n private _pickQueued = false;\n private _currentId: string | null = null;\n /** Visible ratio per message id in the reading band, from the intersection observer. */\n private _visible = new Map<string, number>();\n /** The bubbles on screen at all, from the second observer; only the last one is read. */\n private _onScreen = new Set<string>();\n private _onScreenObserver: IntersectionObserver | null = null;\n /** The mark a jump asked for, held until the transcript has stopped moving. */\n private _held: string | null = null;\n private _settleTimer: ReturnType<typeof setTimeout> | null = null;\n private _settleSurface: HTMLElement | null = null;\n /** Re-alignments done for the jump in flight. */\n private _alignPasses = 0;\n /** The custom properties written on this element, so each is written only when it changes. */\n private _written = new Map<string, string>();\n\n private _onConfigChange = (): void => this._rebuild();\n private _onSurfaceScroll = (): void => this._bumpSettle();\n\n /** The id of the message the rail currently marks, or `null`. */\n get currentMessageId(): string | null {\n return this._currentId;\n }\n\n connectedCallback(): void {\n if (!this.classList.contains('aparte-scroll-rail')) this.classList.add('aparte-scroll-rail');\n if (!this.getAttribute('role')) this.setAttribute('role', 'navigation');\n this.addEventListener('click', this._onClick);\n this.addEventListener('keydown', this._onKeydown);\n window.addEventListener('aparte-config-change', this._onConfigChange);\n this._attach();\n }\n\n disconnectedCallback(): void {\n this._detach();\n this.removeEventListener('click', this._onClick);\n this.removeEventListener('keydown', this._onKeydown);\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n attributeChangedCallback(name: string): void {\n if (!this.isConnected) return;\n if (name === 'target') this._attach();\n else this._rebuild();\n }\n\n // ─── Wiring ───────────────────────────────────────────────────────────\n\n /** The chat this rail follows: `target`, else the host above it — the gauge's rule. */\n private _findHost(): HTMLElement | null {\n const target = this.getAttribute('target');\n if (target) return document.getElementById(target);\n let node: HTMLElement | null = this.parentElement;\n while (node) {\n if (node.tagName.toLowerCase() === 'aparte-chat' || node.hasAttribute('data-aparte-chat')) return node;\n node = node.parentElement;\n }\n return null;\n }\n\n private _attach(): void {\n this._detach();\n this._host = this._findHost();\n if (!this._host) {\n this._rebuild();\n return;\n }\n // One observer on the host, subtree-wide: it sees a bubble appended to the\n // viewport AND the viewport itself appearing, which a wrapper mounts after the\n // rail on some frameworks. The rail is in that subtree too, and its own rebuilds\n // are mutations: those are dropped here, or the rail rebuilds itself every frame\n // for ever (measured at 61 to 146 rebuilds a second, at rest, each with a fresh\n // intersection observer — and nothing on a tick survived a frame).\n if (typeof MutationObserver !== 'undefined') {\n this._mutations = new MutationObserver((records) => {\n if (records.some((record) => !this.contains(record.target))) this._queueRebuild();\n });\n this._mutations.observe(this._host, { childList: true, subtree: true, characterData: true });\n }\n // The rail spans the transcript and sits clear of its scrollbar, both measured:\n // re-measured when the chat or the transcript changes size (a composer that grows).\n if (typeof ResizeObserver !== 'undefined') {\n this._resizes = new ResizeObserver(() => this._layout());\n this._resizes.observe(this._host);\n const viewport = this._viewport();\n if (viewport) this._resizes.observe(viewport);\n }\n this._rebuild();\n }\n\n private _detach(): void {\n this._mutations?.disconnect();\n this._mutations = null;\n this._resizes?.disconnect();\n this._resizes = null;\n this._unobserve();\n this._release();\n this._host = null;\n }\n\n /** Coalesce a burst of mutations (a streaming reply is one per token) into one rebuild. */\n private _queueRebuild(): void {\n if (this._rebuildQueued) return;\n this._rebuildQueued = true;\n requestAnimationFrame(() => {\n this._rebuildQueued = false;\n this._rebuild();\n });\n }\n\n private _viewport(): HTMLElement | null {\n return this._host?.querySelector<HTMLElement>('aparte-chat-viewport') ?? null;\n }\n\n /** The element that scrolls: the vanilla container, or the viewport itself under a wrapper. */\n private _scrollSurface(): HTMLElement | null {\n const viewport = this._viewport();\n if (!viewport) return null;\n return viewport.querySelector<HTMLElement>('.aparte-viewport-container') ?? viewport;\n }\n\n private _bubbles(): HTMLElement[] {\n const viewport = this._viewport();\n if (!viewport) return [];\n const every: AparteScrollRailEvery = this.getAttribute('every') === 'message' ? 'message' : 'user';\n const all = Array.from(viewport.querySelectorAll<HTMLElement>('aparte-chat-bubble[message-id]'));\n return every === 'message' ? all : all.filter((b) => b.getAttribute('data-role') === 'user');\n }\n\n // ─── Rendering ────────────────────────────────────────────────────────\n\n /** The first words of a message, for the tick's name. An excerpt, never a summary. */\n private _excerpt(bubble: HTMLElement): string {\n const body = bubble.querySelector<HTMLElement>('.aparte-message-content') ?? bubble;\n const text = (body.textContent ?? '').replace(/\\s+/g, ' ').trim();\n return text.length > EXCERPT_LENGTH ? `${text.slice(0, EXCERPT_LENGTH - 1).trimEnd()}…` : text;\n }\n\n private _tickLabel(bubble: HTMLElement): string {\n const locale = resolveConfig(this).getLocale();\n const role = bubble.getAttribute('data-role') === 'user'\n ? (locale.roleNameUser ?? 'You')\n : (locale.roleNameAssistant ?? 'Assistant');\n const excerpt = this._excerpt(bubble);\n return excerpt ? `${role}: ${excerpt}` : role;\n }\n\n private _list(): HTMLOListElement | null {\n return this.querySelector<HTMLOListElement>(':scope > .aparte-scroll-rail__list');\n }\n\n /**\n * Reconcile the ticks with the bubbles, by message id. A tick that already exists is\n * kept — the same node, so the focus on it, its hover and its tooltip survive a turn\n * being appended or a reply streaming beside it — moved only when its order changed,\n * and dropped when its bubble left.\n */\n private _rebuild(): void {\n const bubbles = this._bubbles();\n const locale = resolveConfig(this).getLocale();\n // Written only when the value changes: a reconcile runs on every host mutation, and\n // an attribute rewritten to the value it already has is still a DOM mutation —\n // measured on CI's WebKit, where a late one landed inside the rail's rest window.\n const railLabel = locale.scrollRailLabel ?? 'Conversation outline';\n if (this.getAttribute('aria-label') !== railLabel) this.setAttribute('aria-label', railLabel);\n\n if (bubbles.length < 2) {\n this.setAttribute('data-empty', '');\n this.replaceChildren();\n this._unobserve();\n this._currentId = null;\n this._write('--aparte-scroll-rail-hit-size', '');\n return;\n }\n this.removeAttribute('data-empty');\n\n let list = this._list();\n if (!list) {\n list = document.createElement('ol');\n list.className = 'aparte-scroll-rail__list';\n this.replaceChildren(list);\n }\n const existing = new Map<string, HTMLLIElement>();\n for (const item of Array.from(list.children) as HTMLLIElement[]) {\n const id = item.firstElementChild instanceof HTMLElement ? item.firstElementChild.dataset['messageId'] : undefined;\n if (id) existing.set(id, item);\n }\n const known = new Set<string>();\n let index = 0;\n for (const bubble of bubbles) {\n const id = bubble.getAttribute('message-id')!;\n known.add(id);\n let item = existing.get(id);\n if (item) {\n existing.delete(id);\n } else {\n item = document.createElement('li');\n const tick = document.createElement('button');\n tick.type = 'button';\n tick.className = 'aparte-scroll-rail__tick';\n tick.dataset['messageId'] = id;\n item.appendChild(tick);\n }\n const tick = item.firstElementChild as HTMLElement;\n const label = this._tickLabel(bubble);\n if (tick.getAttribute('aria-label') !== label) {\n tick.setAttribute('aria-label', label);\n tick.title = label;\n }\n if (id === this._currentId) {\n if (tick.getAttribute('aria-current') !== 'true') tick.setAttribute('aria-current', 'true');\n } else {\n tick.removeAttribute('aria-current');\n }\n const at = list.children[index] ?? null;\n if (at !== item) list.insertBefore(item, at);\n index++;\n }\n for (const stale of existing.values()) stale.remove();\n\n if (this._currentId && !known.has(this._currentId)) this._currentId = null;\n if (!sameElements(this._observedBubbles, bubbles)) this._observe(bubbles);\n this._layout();\n }\n\n /**\n * Which tick is current is read, not computed: the scroll surface is the root and an\n * intersection observer reports which bubbles reach a reading band — the top 30% of\n * the surface, from its very top so that the bubble a jump aligned there counts. The\n * rule is a scrollspy's: the LAST question that has reached the band is the one the\n * reader is under. (The largest visible share would name the first of three questions\n * sitting in the band at the bottom of a thread of short turns.) The one scroll\n * position read is the bottom, because there the reader is under the latest question\n * whatever the band holds, and the observer has nothing to report on the last few\n * pixels.\n */\n private _observe(bubbles: HTMLElement[]): void {\n this._unobserve();\n this._observedBubbles = bubbles;\n if (typeof IntersectionObserver === 'undefined') return;\n const root = this._scrollSurface();\n if (!root) return;\n this._intersections = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).getAttribute('message-id');\n if (id) this._visible.set(id, entry.isIntersecting ? entry.intersectionRatio : 0);\n }\n this._pickCurrent();\n }, { root, rootMargin: '0px 0px -70% 0px', threshold: [0, 0.1, 0.25, 0.5, 0.75, 1] });\n // The whole surface, for one question only: the last. While the end of the thread\n // is on screen the reader is at the end, whatever the band holds — after a send,\n // the viewport rests with the new question low on the screen and older ones in\n // the band, and a rail marking the question above the one just asked is wrong.\n this._onScreenObserver = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).getAttribute('message-id');\n if (!id) continue;\n if (entry.isIntersecting) this._onScreen.add(id);\n else this._onScreen.delete(id);\n }\n this._pickCurrent();\n }, { root, threshold: [0] });\n for (const bubble of bubbles) {\n this._intersections.observe(bubble);\n this._onScreenObserver.observe(bubble);\n }\n this._observedSurface = root;\n root.addEventListener('scroll', this._onRootScroll, { passive: true });\n }\n\n private _unobserve(): void {\n this._intersections?.disconnect();\n this._intersections = null;\n this._onScreenObserver?.disconnect();\n this._onScreenObserver = null;\n this._observedSurface?.removeEventListener('scroll', this._onRootScroll);\n this._observedSurface = null;\n this._observedBubbles = [];\n this._visible.clear();\n this._onScreen.clear();\n }\n\n /** A scroll only matters at the bottom edge, where the observer is silent: one pick per frame. */\n private _onRootScroll = (): void => {\n if (this._pickQueued) return;\n this._pickQueued = true;\n requestAnimationFrame(() => {\n this._pickQueued = false;\n this._pickCurrent();\n });\n };\n\n /** At the bottom edge of a surface that scrolls; a surface with nothing to scroll has no bottom to be at. */\n private _atBottom(): boolean {\n const surface = this._observedSurface;\n if (!surface) return false;\n const max = surface.scrollHeight - surface.clientHeight;\n return max > 0 && max - surface.scrollTop <= 2;\n }\n\n private _pickCurrent(): void {\n // A jump said which mark is right; the band's mid-scroll opinions wait for it to settle.\n if (this._held) return;\n const bubbles = this._observedBubbles;\n if (bubbles.length === 0) return;\n const lastId = bubbles[bubbles.length - 1]!.getAttribute('message-id')!;\n if (this._atBottom() || this._onScreen.has(lastId)) {\n this._setCurrent(lastId);\n return;\n }\n let last: string | null = null;\n for (const bubble of bubbles) {\n const id = bubble.getAttribute('message-id')!;\n if ((this._visible.get(id) ?? 0) > 0) last = id;\n }\n // Nothing in the band (the reader is deep in one reply): keep the last mark\n // rather than clearing it.\n if (last !== null) this._setCurrent(last);\n }\n\n private _setCurrent(id: string | null): void {\n if (this._currentId === id) return;\n this._currentId = id;\n for (const tick of this.querySelectorAll<HTMLElement>('.aparte-scroll-rail__tick')) {\n if (tick.dataset['messageId'] === id) {\n tick.setAttribute('aria-current', 'true');\n this._reveal(tick);\n } else {\n tick.removeAttribute('aria-current');\n }\n }\n }\n\n /** Past the floor the rail scrolls itself: the current tick is kept inside its window. */\n private _reveal(tick: HTMLElement): void {\n const room = this.clientHeight;\n if (!room) return;\n const top = tick.offsetTop;\n const bottom = top + tick.offsetHeight;\n if (top < this.scrollTop) this.scrollTop = top;\n else if (bottom > this.scrollTop + room) this.scrollTop = bottom - room;\n }\n\n // ─── Geometry ─────────────────────────────────────────────────────────\n\n /** Write a custom property on this element only when its value changed; '' removes it. */\n private _write(name: string, value: string): void {\n if (this._written.get(name) === value) return;\n this._written.set(name, value);\n if (value) this.style.setProperty(name, value);\n else this.style.removeProperty(name);\n }\n\n /**\n * The three measurements the stylesheet positions the rail by, and the pitch.\n *\n * The scrollbar: `offsetWidth − clientWidth` of the scroll surface is the width of a\n * classic bar and 0 for an overlay one, so the rail sits clear of the bar exactly\n * where there is one. The extent: the transcript's box within the host, so the rail\n * spans the messages and not the composer under them. The pitch: the stylesheet's\n * 24px targets whenever `count × 24` fits the rail; otherwise the pitch that does,\n * never under `MIN_PITCH` — the rail sets `--aparte-scroll-rail-hit-size` on itself\n * and the gap, the padding and the zone all follow it, as they do for a consumer's\n * value. Past the floor the rail scrolls (`_reveal`), and the arrows still walk it.\n */\n private _layout(): void {\n const host = this._host;\n const viewport = this._viewport();\n const surface = this._scrollSurface();\n if (host && surface) {\n this._write('--aparte-scroll-rail-bar', `${Math.max(0, surface.offsetWidth - surface.clientWidth)}px`);\n }\n if (host && viewport) {\n const v = viewport.getBoundingClientRect();\n const h = host.getBoundingClientRect();\n if (v.height > 0 && h.height > 0) {\n this._write('--aparte-scroll-rail-block-start', `${Math.max(0, Math.round(v.top - h.top))}px`);\n this._write('--aparte-scroll-rail-block-end', `${Math.max(0, Math.round(h.bottom - v.bottom))}px`);\n }\n }\n const count = this._list()?.children.length ?? 0;\n const height = this.clientHeight;\n if (!count || !height) {\n this._write('--aparte-scroll-rail-hit-size', '');\n this._write('--aparte-scroll-rail-gap', '');\n return;\n }\n // The default pitch is the theme's, read above this element so that the value the\n // rail itself wrote is never mistaken for it.\n const above = host ?? document.documentElement;\n const aboveStyle = getComputedStyle(above);\n const hit = parseFloat(aboveStyle.getPropertyValue('--aparte-scroll-rail-hit-size')) || 24;\n const thickness = parseFloat(aboveStyle.getPropertyValue('--aparte-scroll-rail-tick-thickness')) || 2;\n if (count * hit <= height) {\n this._write('--aparte-scroll-rail-hit-size', '');\n this._write('--aparte-scroll-rail-gap', '');\n return;\n }\n const pitch = Math.max(MIN_PITCH, Math.floor(height / count));\n this._write('--aparte-scroll-rail-hit-size', `${pitch}px`);\n // The theme derives the gap from the hit size on `:root`, and a `var()` resolves\n // where the property is DECLARED, not where it is read: the tightened hit size\n // set here would leave the gap at its root value. So the gap is written too.\n this._write('--aparte-scroll-rail-gap', `${Math.max(0, pitch - thickness)}px`);\n }\n\n // ─── Interaction ──────────────────────────────────────────────────────\n\n private _onClick = (e: Event): void => {\n const tick = (e.target as HTMLElement).closest<HTMLElement>('.aparte-scroll-rail__tick');\n if (!tick || !this.contains(tick)) return;\n const messageId = tick.dataset['messageId'];\n if (!messageId) return;\n this.jumpTo(messageId);\n };\n\n /** The ticks are buttons; the arrows walk them, as in any toolbar. */\n private _onKeydown = (e: KeyboardEvent): void => {\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp' && e.key !== 'Home' && e.key !== 'End') return;\n const ticks = Array.from(this.querySelectorAll<HTMLElement>('.aparte-scroll-rail__tick'));\n // The focus as the rail's own tree reports it: `document.activeElement` is the\n // shadow HOST for a rail mounted in a consumer's root, so the index was -1 and\n // every arrow bailed out silently.\n const index = ticks.indexOf(activeElementIn(this) as HTMLElement);\n if (index === -1) return;\n e.preventDefault();\n const next = e.key === 'Home' ? 0\n : e.key === 'End' ? ticks.length - 1\n : e.key === 'ArrowDown' ? Math.min(ticks.length - 1, index + 1)\n : Math.max(0, index - 1);\n ticks[next]?.focus();\n };\n\n /**\n * Scroll the transcript to a message. Announced first, cancelable; then a\n * `scrollIntoView` on the bubble, smooth unless the reader prefers reduced motion.\n * The clicked tick is the mark until the transcript has stopped moving: while a\n * smooth scroll crosses other questions the band would name each in turn.\n *\n * Where the scroll lands is checked once it has settled, and corrected. The bubbles\n * carry `content-visibility: auto`, so a bubble far from the reader has an estimated\n * height until it is rendered: the scroll aims at an estimate, the bubbles above it\n * take their real size on the way, and the message ends up above or below the top,\n * the further away the worse.\n * A second `scrollIntoView` from a rendered neighbourhood lands; a few are allowed.\n */\n jumpTo(messageId: string): void {\n const announced = this.dispatchEvent(new CustomEvent<AparteScrollRailJumpDetail>('aparte-scroll-rail-jump', {\n detail: { messageId },\n bubbles: true,\n composed: true,\n cancelable: true,\n }));\n if (!announced) return;\n const bubble = this._bubbleOf(messageId);\n if (!bubble) return;\n // The reader chose a message, not the bottom: the follow stops first. Mid-stream a\n // jump into history never rendered shortens the transcript at its first scroll event\n // (the bubbles leave their 320px fallback), which the viewport reads as a clamp of\n // its own — armed, it pinned the reader straight back to the bottom.\n (this._viewport() as (HTMLElement & { setAutoScroll?: (enabled: boolean) => void }) | null)?.setAutoScroll?.(false);\n this._scrollTo(bubble);\n this._setCurrent(messageId);\n this._hold(messageId);\n }\n\n private _bubbleOf(messageId: string): HTMLElement | null {\n return this._viewport()?.querySelector<HTMLElement>(`aparte-chat-bubble[message-id=\"${cssEscape(messageId)}\"]`) ?? null; // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n }\n\n private _scrollTo(bubble: HTMLElement): void {\n const reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n bubble.scrollIntoView({ block: 'start', behavior: reduced ? 'auto' : 'smooth' });\n }\n\n /** Keep `id` as the mark until the scroll surface has been still for `SETTLE_MS`. */\n private _hold(id: string): void {\n this._release();\n this._held = id;\n this._alignPasses = 0;\n this._settleSurface = this._scrollSurface();\n this._settleSurface?.addEventListener('scroll', this._onSurfaceScroll);\n // An instant scroll, or none at all, fires no scroll event: the first timer settles it.\n this._bumpSettle();\n }\n\n private _bumpSettle(): void {\n if (this._settleTimer !== null) clearTimeout(this._settleTimer);\n this._settleTimer = setTimeout(() => this._settle(), SETTLE_MS);\n }\n\n /** The transcript is still: land on the message if the scroll did not, else release the hold. */\n private _settle(): void {\n const id = this._held;\n const surface = this._settleSurface;\n const bubble = id ? this._bubbleOf(id) : null;\n if (id && bubble && surface && this._alignPasses < MAX_ALIGN_PASSES) {\n // Measured from where `scrollIntoView` aims: the surface's top, below its scroll\n // padding (`--aparte-top-inset`, a header floating over the transcript).\n const aim = surface.getBoundingClientRect().top + (parseFloat(getComputedStyle(surface).scrollPaddingTop) || 0);\n const off = bubble.getBoundingClientRect().top - aim;\n if (Math.abs(off) > ALIGN_TOLERANCE) {\n this._alignPasses++;\n this._scrollTo(bubble);\n this._bumpSettle();\n return;\n }\n }\n this._release();\n if (id) this._setCurrent(id);\n }\n\n private _release(): void {\n if (this._settleTimer !== null) clearTimeout(this._settleTimer);\n this._settleTimer = null;\n this._settleSurface?.removeEventListener('scroll', this._onSurfaceScroll);\n this._settleSurface = null;\n this._held = null;\n }\n}\n\nfunction sameElements(a: HTMLElement[], b: HTMLElement[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n}\n\nif (!customElements.get('aparte-scroll-rail')) {\n customElements.define('aparte-scroll-rail', AparteScrollRail);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { presenceOn } from '../../utils/presence.js';\nimport { activeElementIn, eventOrigin, rootOf } from '../../utils/event-target.js';\nimport { foldText, filterConversationRows } from '../../utils/fold-text.js';\n\n/** Detail of `aparte-sidebar-toggle`: the state just entered. */\nexport interface AparteSidebarToggleDetail {\n collapsed: boolean;\n /** True while the sidebar is a drawer — the window is under the breakpoint. */\n drawer: boolean;\n}\n\n/** The window width under which the sidebar becomes a drawer. Mirrored in `sidebar.css`. */\nconst DRAWER_QUERY = '(max-width: 48rem)';\n\n/**\n * The column beside the chat — conversations, a new-chat button, a search field, an\n * account row — as an element, because it has behaviour: it collapses, it becomes a\n * drawer on a narrow window, and its search field filters the conversation list.\n *\n * What it does NOT own is its content. Children are yours, in the order you want them:\n * a `.aparte-sidebar__header`, a `.aparte-sidebar__search`, a `.aparte-sidebar__body`\n * holding the `<aparte-conversation-list>`, a `.aparte-sidebar__footer`. The\n * stylesheet lays those out and draws nothing you did not put there — the recipe\n * (`.aparte-sidebar`, in `styles/shell/sidebar.css`) is the look, this element is\n * the three behaviours.\n *\n * **Collapse.** `collapsed` is an attribute, reflected, so the host can set it, read\n * it and persist it. Any element carrying `data-aparte-sidebar-toggle` anywhere on\n * the page toggles the nearest sidebar (or the one whose id the attribute names), so\n * a hamburger in the header needs no script — and the sidebar keeps `aria-expanded`\n * and `aria-controls` on that control in step, whoever changed the state (it gives\n * itself an id when the host wrote none). `aparte-sidebar-toggle` fires on every\n * change, whoever caused it.\n *\n * **Drawer.** Under 48rem of window the sidebar leaves the flow and slides over the\n * page (`data-drawer`, set by the element from a media query); open, it draws a\n * scrim, moves the focus to its first focusable child, closes on Escape from anywhere\n * on the page or on a click outside, and hands the focus back to the control that\n * opened it. Nothing here is a portal: the drawer is the same element in the same\n * place, positioned fixed, which is all a sidebar needs and what a dialog would not\n * get away with. Collapsed — folded as a column or slid off as a drawer — it carries\n * `inert` and `aria-hidden`, so nothing invisible keeps a tab stop.\n *\n * **Search.** An input carrying `data-aparte-sidebar-search` filters the conversation\n * list below it by title as the user types — rows that do not match are hidden, and a\n * date group with nothing left hides with them. Client-side, on the titles the list\n * already has: an index over message bodies is the storage adapter's business.\n *\n * @element aparte-sidebar\n *\n * @attr {boolean} collapsed - Hidden (in the flow) or closed (as a drawer). Reflected; set it to start closed.\n * @attr {string} breakpoint - The window width under which the sidebar becomes a drawer: a length (default `48rem`), or `none` for a column that never does.\n * @attr {boolean} data-drawer - Reflected BY the element while the window is narrower than `breakpoint` (48rem unless you set it). Read-only.\n *\n * @fires {CustomEvent<AparteSidebarToggleDetail>} aparte-sidebar-toggle - The sidebar opened or closed, by a toggle, by Escape, by a click on the scrim or by `collapsed` being set. Bubbles.\n *\n * @cssprop [--aparte-sidebar-width=260px] - Width of the column, and of the drawer.\n * @cssprop [--aparte-sidebar-inset=var(--aparte-space-3)] - The inline inset of the four\n * regions (header, search, body, footer): one value, so their content stands on one axis.\n * @cssprop [--aparte-sidebar-bg=var(--aparte-surface-2)] - Its ground.\n *\n * @example\n * <!-- The sidebar alone, as a column: its header, a search field that filters the list,\n * the list, a footer. The shell it sits in — header, chat — is the app-shell guide's.\n * breakpoint=\"none\" keeps it a column at any width; without it, under 48rem of\n * window it becomes a drawer behind a [data-aparte-sidebar-toggle] control. -->\n * <aparte-sidebar breakpoint=\"none\" style=\"height: 22rem\">\n * <div class=\"aparte-sidebar__header\">\n * <span class=\"aparte-sidebar__brand\">aparté</span>\n * <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm\" type=\"button\" aria-label=\"New chat\">\n * <aparte-icon name=\"edit\"></aparte-icon>\n * </button>\n * </div>\n * <div class=\"aparte-sidebar__search aparte-field-group\">\n * <input class=\"aparte-field aparte-field--sm\" type=\"search\" placeholder=\"Search conversations\" data-aparte-sidebar-search>\n * </div>\n * <div class=\"aparte-sidebar__body\">\n * <aparte-conversation-list active-id=\"c1\"></aparte-conversation-list>\n * </div>\n * <div class=\"aparte-sidebar__footer\">\n * <span class=\"aparte-avatar aparte-avatar--sm\">P</span> Paul\n * </div>\n * </aparte-sidebar>\n * <script>\n * const day = 864e5;\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() - day },\n * { id: 'c3', title: 'Tokens, not selectors', updatedAt: Date.now() - 4 * day },\n * { id: 'c4', title: 'The first release', updatedAt: Date.now() - 60 * day },\n * ];\n * </script>\n */\nexport class AparteSidebar extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['collapsed', 'breakpoint'];\n }\n\n private _media: MediaQueryList | null = null;\n private _scrim: HTMLElement | null = null;\n /** The control that last opened the drawer, to hand the focus back to. */\n private _opener: HTMLElement | null = null;\n private _lastCollapsed: boolean | null = null;\n\n /**\n * `connectedCallback` has run and the element knows what its markup asked for.\n *\n * Not `isConnected`: during an UPGRADE the element is already in the document, so\n * `attributeChangedCallback` fires for every authored attribute — connected, and\n * before `connectedCallback` — which is the ordinary case for a server-rendered\n * `<aparte-sidebar collapsed>` upgraded when the module loads. Announcing there\n * tells the host its sidebar just closed when all that happened is that the markup\n * was read, and says `drawer: false` because the media query has not run yet.\n */\n private _ready = false;\n\n /**\n * The host (or the markup) asked for the column to be folded, so widening the\n * window must not unfold it. Only a collapse taken OUTSIDE the drawer state counts:\n * dismissing an overlay says nothing about what a wide window should show.\n */\n private _closedByHost = false;\n\n /**\n * `_closedByHost` has been taken from the markup once. A re-parent runs\n * `connectedCallback` again, and by then `collapsed` may be the BREAKPOINT's own\n * write — a drawer closes itself on the way in — so reading it back a second time\n * records the element's own doing as the host's word and the column never reopens.\n * Like `_initialCaptured` on `<aparte-split>`, it must survive the move.\n */\n private _hostIntentSeeded = false;\n\n /** Raised around `_applyDrawer`'s own writes, so the breakpoint is never read as a host's intent. */\n private _auto = false;\n\n /**\n * The element wrote `inert`/`aria-hidden` and may take them back. A host that inerts\n * the sidebar behind its own modal wrote them itself, and `_syncHidden` runs on every\n * breakpoint re-evaluation: without this it would un-inert the page behind the overlay.\n * Same convention as `_relabel`, which leaves a host-authored `aria-label` alone.\n */\n private _ownHidden = false;\n /** The query `filter()` was last given, kept so the sidebar's no-match can follow its lists. */\n private _query = '';\n /** Watches the lists' own `data-no-match` and `loading`, which change with their renders. */\n private _listWatch: MutationObserver | null = null;\n\n /** Whether the sidebar is collapsed (hidden in the flow, or closed as a drawer). */\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(value: boolean) {\n this.toggleAttribute('collapsed', presenceOn(value));\n }\n\n /** True while the window is under the breakpoint and the sidebar is a drawer. */\n get drawer(): boolean {\n return this.hasAttribute('data-drawer');\n }\n\n connectedCallback(): void {\n if (!this.classList.contains('aparte-sidebar')) this.classList.add('aparte-sidebar');\n if (!this.getAttribute('role')) this.setAttribute('role', 'complementary');\n this._relabel();\n // Before the breakpoint runs, and only the FIRST time: whatever the markup asked\n // for is the host's word. On a later connect `collapsed` may be our own.\n if (!this._hostIntentSeeded) {\n this._closedByHost = this.collapsed;\n this._hostIntentSeeded = true;\n }\n this.addEventListener('input', this._onInput);\n if (typeof MutationObserver === 'function') {\n this._listWatch = new MutationObserver((records) => {\n if (records.some((r) => r.target !== this)) this._syncNoMatch();\n });\n this._listWatch.observe(this, { subtree: true, attributes: true, attributeFilter: ['data-no-match', 'loading'] });\n }\n // On the DOCUMENT, not on the element: an overlay that only answers Escape when\n // the focus is already inside it answers nobody — the focus is on the page the\n // drawer is covering.\n document.addEventListener('keydown', this._onKeydown);\n document.addEventListener('click', this._onDocumentClick);\n window.addEventListener('aparte-config-change', this._onConfigChange);\n this._watchBreakpoint();\n this._syncHidden();\n // Last: `_watchBreakpoint` may have closed the sidebar on its way in, and that\n // close is the drawer entering, not a change to announce.\n this._lastCollapsed = this.collapsed;\n if (!this.id) this.id = `aparte-sidebar-${++sidebarIdSeq}`;\n this._syncToggles();\n this._ready = true;\n }\n\n /**\n * The drawer's media query, from the `breakpoint` attribute: a length (`48rem`, the\n * default, or `640px`), or `none` for a column that never becomes a drawer — which\n * a narrow host that has room for it wants, and a documentation frame needs.\n */\n private _watchBreakpoint(): void {\n this._media?.removeEventListener('change', this._onMediaChange);\n this._media = null;\n const attr = (this.getAttribute('breakpoint') ?? '').trim();\n if (attr === 'none') {\n this._applyDrawer(false);\n return;\n }\n if (typeof matchMedia !== 'function') return;\n const query = attr ? `(max-width: ${attr})` : DRAWER_QUERY;\n this._media = matchMedia(query);\n this._media.addEventListener('change', this._onMediaChange);\n this._applyDrawer(this._media.matches);\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('input', this._onInput);\n this._listWatch?.disconnect();\n this._listWatch = null;\n document.removeEventListener('keydown', this._onKeydown);\n document.removeEventListener('click', this._onDocumentClick);\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n this._media?.removeEventListener('change', this._onMediaChange);\n this._media = null;\n this._ready = false;\n }\n\n attributeChangedCallback(name: string): void {\n if (!this._ready) return;\n if (name === 'breakpoint') {\n this._watchBreakpoint();\n return;\n }\n if (name !== 'collapsed') return;\n const collapsed = this.collapsed;\n if (collapsed === this._lastCollapsed) return;\n this._lastCollapsed = collapsed;\n // A collapse taken in the flow is a preference about the COLUMN, and survives a\n // resize. One taken over a drawer, or by the breakpoint itself, is not.\n if (!this._auto && !this.drawer) this._closedByHost = collapsed;\n this._syncScrim();\n this._syncHidden();\n this._syncToggles();\n if (collapsed && this.drawer && this._opener?.isConnected) {\n this._opener.focus();\n this._opener = null;\n } else if (!collapsed && this.drawer) {\n this._focusFirst();\n }\n this.dispatchEvent(new CustomEvent<AparteSidebarToggleDetail>('aparte-sidebar-toggle', {\n detail: { collapsed, drawer: this.drawer },\n bubbles: true,\n composed: true,\n }));\n }\n\n // ─── Public API ───────────────────────────────────────────────────────\n\n /** Open the sidebar. `opener` is the control to hand the focus back to when it closes. */\n open(opener?: HTMLElement): void {\n if (opener) this._opener = opener;\n this.collapsed = false;\n }\n\n close(): void {\n this.collapsed = true;\n }\n\n toggle(opener?: HTMLElement): void {\n if (this.collapsed) this.open(opener);\n else this.close();\n }\n\n // ─── Drawer ───────────────────────────────────────────────────────────\n\n private _onMediaChange = (e: MediaQueryListEvent): void => this._applyDrawer(e.matches);\n\n /**\n * Entering the drawer state closes the sidebar: a narrow window that opens on an\n * overlay covering the chat is the wrong first screen. Leaving it reopens, since\n * a wide window has room for the column — unless the host had collapsed it before.\n */\n private _applyDrawer(drawer: boolean): void {\n const was = this.drawer;\n this.toggleAttribute('data-drawer', drawer);\n this._auto = true;\n try {\n if (drawer && !was) this.collapsed = true;\n else if (!drawer && was && !this._closedByHost) this.collapsed = false;\n } finally {\n this._auto = false;\n }\n this._syncScrim();\n this._syncHidden();\n this._syncToggles();\n }\n\n /**\n * Every `[data-aparte-sidebar-toggle]` that drives THIS sidebar says what it does:\n * `aria-expanded` follows the state, `aria-controls` names the element. Synced from\n * the three places the state settles (connect, the attribute, the breakpoint), never\n * from `open()` alone — a close on Escape would otherwise leave an \"expanded\" that\n * lies. The row's own `⋯` button already did this; the sidebar's toggle did not.\n */\n private _syncToggles(): void {\n // Both trees: they are the same one in the ordinary case, and differ when a\n // consumer mounts the chat inside a shadow root of their own — where a control\n // can sit on either side of the boundary and still drive this sidebar.\n const selector = '[data-aparte-sidebar-toggle]';\n const root = rootOf(this);\n const controls = root === document\n ? Array.from(document.querySelectorAll<HTMLElement>(selector))\n : [...root.querySelectorAll<HTMLElement>(selector), ...document.querySelectorAll<HTMLElement>(selector)];\n for (const control of controls) {\n if (!this._drives(control)) continue;\n control.setAttribute('aria-expanded', String(!this.collapsed));\n control.setAttribute('aria-controls', this.id);\n }\n }\n\n /** Whether a toggle control targets this sidebar: named by id, else the nearest one. */\n private _drives(control: HTMLElement): boolean {\n const named = control.getAttribute('data-aparte-sidebar-toggle');\n return named ? named === this.id : nearestSidebar(control) === this;\n }\n\n /**\n * A collapsed sidebar is invisible — folded to nothing in the flow, slid off the\n * screen as a drawer — so it must not keep its tab stops or its place in the\n * accessibility tree. `inert` covers focus and the pointer, `aria-hidden` covers\n * the readers that predate it; a browser without `inert` still gets the second.\n *\n * It removes only what it wrote. Both are standard global attributes a host sets\n * itself — inerting the sidebar behind its own modal is the ordinary pattern — and\n * this runs on every breakpoint re-evaluation, so clearing them unconditionally lets\n * an unrelated resize re-expose the page under an overlay. Same convention as\n * `_relabel`, which leaves a host-authored `aria-label` alone.\n *\n * Neither is declared `@attr`, and `data-drawer` is not the precedent: a `@attr` on a\n * STANDARD name displaces the wrappers' own prop for it — React's JSX props are\n * `Omit<HTMLAttributes, keyof T>`, so declaring `inert` here would retype\n * `<aparte-sidebar inert={busy}>` out of existence. The class docblock says it in prose.\n */\n private _syncHidden(): void {\n if (this.collapsed) {\n this.toggleAttribute('inert', true);\n this.setAttribute('aria-hidden', 'true');\n this._ownHidden = true;\n } else if (this._ownHidden) {\n this.removeAttribute('inert');\n this.removeAttribute('aria-hidden');\n this._ownHidden = false;\n }\n }\n\n /**\n * The drawer covers the page, so the focus has to follow it in — otherwise the next\n * Tab walks the transcript under the overlay. The host owns the children, so this\n * takes the first thing that can hold focus and leaves the rest alone; a drawer with\n * nothing focusable in it keeps the focus where it was, which is the honest outcome.\n */\n private _focusFirst(): void {\n visibleFocusable(this)[0]?.focus();\n }\n\n private _syncScrim(): void {\n const wanted = this.drawer && !this.collapsed;\n if (wanted && !this._scrim) {\n this._scrim = document.createElement('div');\n this._scrim.className = 'aparte-sidebar__scrim';\n this._scrim.setAttribute('aria-hidden', 'true');\n this._scrim.addEventListener('click', () => this.close());\n this.appendChild(this._scrim);\n } else if (!wanted && this._scrim) {\n this._scrim.remove();\n this._scrim = null;\n }\n }\n\n private _onKeydown = (e: KeyboardEvent): void => {\n if (!this.drawer || this.collapsed) return;\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n // The open drawer covers the page, so Tab stays inside it: from the last control\n // back to the first, and Shift+Tab the other way. It used to walk out under the\n // scrim onto the transcript the drawer was covering. Tab is the only path that\n // needs closing — the scrim already takes the pointer — and a `focusin` guard\n // was deliberately NOT added: it would steal the focus back from a dialog the\n // drawer's own content opens onto `<body>`.\n if (e.key === 'Tab') {\n const focusable = visibleFocusable(this);\n if (focusable.length === 0) return;\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n // `activeElementIn`, not `document.activeElement`: inside a consumer's shadow\n // root the latter reads as the shadow host, and the trap then fires on every\n // Tab as if the focus had already left the drawer.\n const active = activeElementIn(this);\n if (!this.contains(active)) {\n e.preventDefault();\n first.focus();\n } else if (e.shiftKey && active === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && active === last) {\n e.preventDefault();\n first.focus();\n }\n }\n };\n\n /** A `[data-aparte-sidebar-toggle]` anywhere toggles this sidebar — no script in the host. */\n private _onDocumentClick = (e: Event): void => {\n // `eventOrigin`, not `e.target`: a click from inside a consumer's shadow root is\n // retargeted to the shadow host by the time it reaches `document`.\n const control = (eventOrigin(e) as HTMLElement | null)?.closest?.<HTMLElement>('[data-aparte-sidebar-toggle]');\n if (!control) return;\n if (!this._drives(control)) return;\n this.toggle(control);\n };\n\n // ─── Search ───────────────────────────────────────────────────────────\n\n private _onInput = (e: Event): void => {\n const input = e.target as HTMLInputElement | null;\n if (!input?.matches?.('[data-aparte-sidebar-search]')) return;\n this.filter(input.value);\n };\n\n /**\n * Filter the conversation lists inside by title (case- and accent-insensitive; an\n * empty query shows everything), and return how many conversations match. Each list\n * keeps the query through its own renders and says when nothing matches; the\n * sidebar carries `data-no-match` then too.\n */\n filter(query: string): number {\n this._query = query;\n let matches = 0;\n let waiting = false;\n for (const list of this._lists()) {\n // A list not upgraded yet has no `filter()`: its rows are the page's markup, and\n // they are filtered here as they always were — and it is handed the query once it\n // upgrades, or its first render would show every row under the query.\n if (typeof list.filter === 'function') matches += list.filter(query);\n else { matches += filterConversationRows(list, foldText(query)); waiting = true; }\n }\n if (waiting && typeof customElements !== 'undefined') {\n void customElements.whenDefined('aparte-conversation-list').then(() => {\n if (this._query === query) for (const list of this._lists()) list.filter?.(query);\n this._syncNoMatch();\n });\n }\n this._syncNoMatch();\n return matches;\n }\n\n private _lists(): Array<HTMLElement & { filter?: (q: string) => number }> {\n return Array.from(this.querySelectorAll<HTMLElement & { filter?: (q: string) => number }>('aparte-conversation-list'));\n }\n\n /**\n * `data-no-match` while the held query matches nothing in any list — read from the lists\n * themselves, so it follows their renders (a conversation that matches arriving, the last\n * match leaving) and is never set while one of them is still loading.\n */\n private _syncNoMatch(): void {\n const needle = foldText(this._query);\n const none = needle !== '' && this._lists().every((list) => (typeof list.filter === 'function'\n ? list.hasAttribute('data-no-match')\n : !list.hasAttribute('loading') && filterConversationRows(list, needle) === 0));\n this.toggleAttribute('data-no-match', none);\n }\n\n // ─── Locale ───────────────────────────────────────────────────────────\n\n private _onConfigChange = (): void => this._relabel();\n\n private _relabel(): void {\n if (this.hasAttribute('aria-label') && this.dataset['ownLabel'] !== 'locale') return;\n const locale = resolveConfig(this).getLocale();\n this.setAttribute('aria-label', locale.sidebarLabel ?? 'Conversations');\n this.dataset['ownLabel'] = 'locale';\n }\n}\n\n/** For the id a sidebar gives itself when the host wrote none, so a toggle can name it. */\nlet sidebarIdSeq = 0;\n\n/** What the drawer hands the focus to when it opens. The host owns the children. */\nconst FOCUSABLE = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n/**\n * The focusables a reader can actually reach. The drawer's own search filter hides rows\n * with `hidden`, and a hidden row's buttons hold no tab stop — so a trap that compared\n * DOM-last rather than reachable-last never fired at the real end of the drawer and let\n * Tab walk out under the scrim.\n */\nfunction visibleFocusable(root: ParentNode): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE))\n .filter((el) => !el.closest('[hidden]') && el.checkVisibility?.() !== false);\n}\n\n/**\n * The sidebar a toggle belongs to: the closest ancestor, else the first in the toggle's\n * own tree, else the first in the document. `rootOf` before `document` is what makes a\n * control that names no id work inside a consumer's shadow root — and what stops a\n * sidebar in the light DOM from answering it.\n */\nfunction nearestSidebar(control: HTMLElement): Element | null {\n return control.closest('aparte-sidebar')\n ?? rootOf(control).querySelector('aparte-sidebar')\n ?? document.querySelector('aparte-sidebar');\n}\n\nif (!customElements.get('aparte-sidebar')) {\n customElements.define('aparte-sidebar', AparteSidebar);\n}\n","/**\n * The split's arithmetic, with no DOM in it.\n *\n * Separate from the element on purpose: a pointer drag is the path jsdom exercises\n * worst — it has no layout, no `PointerEvent` and no pointer capture — so the numbers\n * are proven here, in plain functions a test can call, and the element is left with\n * the listeners and the attributes.\n *\n * Nothing here parses a CSS unit. `--aparte-split-min` and `--aparte-split-max` are\n * clamp bounds in the grid template, so the browser clamps the pane and this module\n * only ever moves a percentage between 0 and 100.\n */\n\n/** One step of a drag: where it started, how far the pointer went, and along which axis. */\nexport interface SplitStep {\n /** The primary pane's size when the gesture began, as a percentage of the container. */\n startPercent: number;\n /** How far the pointer has travelled since, in CSS pixels, along the split's axis. */\n deltaPx: number;\n /** The container's size along that same axis. */\n containerPx: number;\n /** The computed reading direction is right-to-left. Ignored on the vertical axis. */\n rtl: boolean;\n /** The split's orientation is `vertical` — the panes are stacked, the axis is the block one. */\n vertical: boolean;\n /**\n * `primary=\"end\"`: the sized track is the LAST one, so the seam's distance from the\n * container's start is `100% - handle - position` and a bigger number moves the seam\n * TOWARD the start. Without this the seam runs away from the pointer.\n */\n primaryEnd?: boolean;\n}\n\n/** Keep `value` inside `[min, max]`, and answer `min` for anything that is not a number. */\nfunction clamp(value: number, min: number, max: number): number {\n if (!Number.isFinite(value)) return min;\n return value < min ? min : value > max ? max : value;\n}\n\n/**\n * The primary pane's new size, as a percentage of the container, clamped to 0..100.\n *\n * A right-to-left drag moves the other way: in `rtl` the start pane is on the right, so\n * a pointer travelling in +x makes it smaller. The block axis never inverts — `rtl` is a\n * writing direction, not a gravity — so it is ignored when `vertical`.\n *\n * `primaryEnd` inverts it too, and for a different reason: the pane being sized is the\n * one the pointer moves AWAY from. The two inversions compose — an `rtl` split whose\n * primary is the end pane reads like a plain one again — so they are XORed, not summed.\n *\n * A container of zero (measured before layout, or while display:none) yields the\n * starting value rather than `Infinity`: the caller gets a number it can write back.\n */\nexport function nextPosition(step: SplitStep): number {\n const start = Number.isFinite(step.startPercent) ? step.startPercent : 0;\n if (!Number.isFinite(step.containerPx) || step.containerPx <= 0) return clamp(start, 0, 100);\n const inverted = (!step.vertical && step.rtl) !== (step.primaryEnd === true);\n const sign = inverted ? -1 : 1;\n const moved = (sign * step.deltaPx * 100) / step.containerPx;\n return clamp(start + moved, 0, 100);\n}\n\n/**\n * How far a key moves the seam, in percentage points — or `null` for a key that is not\n * ours, so the handler can leave the page's own scrolling alone.\n *\n * Shift is ×10. That is an ecosystem convention (Shoelace's `×10`, Zag's delta of 10),\n * NOT the APG, which specifies only the single step: it is here because a 1% step across\n * a 1600px window is 16px and nobody arrows a pane across a screen one percent at a time.\n *\n * Home and End are the extremes as ±100: the clamp in `nextPosition` — and then the CSS\n * clamp in the grid — decide where that actually lands, so a `--aparte-split-min` of\n * `20rem` means Home stops at 20rem without this function knowing what a rem is.\n *\n * `primaryEnd` mirrors the ARROWS only, for the same reason `nextPosition` mirrors the\n * pointer: an arrow names a direction the SEAM travels in. Home and End name the primary\n * pane's own extremes — its minimum is its minimum whichever end it sits at — so they are\n * left alone.\n */\nexport function keyDelta(\n key: string,\n shift: boolean,\n rtl: boolean,\n vertical: boolean,\n primaryEnd = false,\n): number | null {\n if (key === 'Home') return -100;\n if (key === 'End') return 100;\n const step = (shift ? 10 : 1) * (primaryEnd ? -1 : 1);\n if (vertical) {\n if (key === 'ArrowUp') return -step;\n if (key === 'ArrowDown') return step;\n return null;\n }\n if (key === 'ArrowLeft') return rtl ? step : -step;\n if (key === 'ArrowRight') return rtl ? -step : step;\n return null;\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { nextPosition, keyDelta } from './geometry.js';\nimport { presenceOn } from '../../utils/presence.js';\nimport { eventOrigin, rootOf } from '../../utils/event-target.js';\n\n/** Detail of `aparte-split-resize`: the position that settled, and what settled it. */\nexport interface AparteSplitResizeDetail {\n /** The achieved size of the primary pane, as a percentage of the container. */\n position: number;\n /** The primary pane is folded to its minimum. */\n collapsed: boolean;\n /** The split is showing one pane — under its breakpoint, or told to by `single`. */\n stacked: boolean;\n /** Which pane is shown while stacked. */\n pane: 'start' | 'end';\n /** The container's axis, as the `orientation` attribute names it. */\n orientation: 'horizontal' | 'vertical';\n /** What moved it: a drag, a key, or the host setting the property. */\n source: 'pointer' | 'keyboard' | 'api';\n}\n\n/** The window width under which the split shows one pane. Mirrored in `split.css`'s siblings. */\nconst STACK_QUERY = '(max-width: 48rem)';\n\n/** The primary pane's size when nothing else says — the same number `theme.css` declares. */\nconst DEFAULT_POSITION = 38;\n\n/**\n * How long a retired drag overlay stays in the document after it goes inert. Long\n * enough to outlive the `click` and `dblclick` a press produces on any engine, short\n * enough that nothing accumulates. See `_removeScrim`.\n */\nconst SCRIM_RETIRE_MS = 300;\n\n/** Keep a percentage inside 0..100; anything unreadable answers `fallback`. */\nfunction toPercent(raw: string | null | undefined, fallback: number): number {\n const value = Number.parseFloat(raw ?? '');\n if (!Number.isFinite(value)) return fallback;\n return value < 0 ? 0 : value > 100 ? 100 : value;\n}\n\n/**\n * Two panes and a draggable seam — a chat beside a preview, an editor, an artifact card.\n *\n * The grid is the mechanism: `--aparte-split-position` is the primary pane's size and\n * the min/max are CSS clamp bounds, so the browser clamps and nothing here parses a\n * unit. The element adds only what CSS cannot do — the drag, the arrow keys, the ARIA\n * of an APG window splitter, and one pane at a time under a breakpoint. Without the\n * element the `.aparte-split` recipe is still a split; it just does not move.\n *\n * It stores nothing: `position` in, one `aparte-split-resize` out on commit. The\n * attribute is written on COMMIT only — a release, a key up, a double-click, a property\n * set — and the live value during a drag travels on the custom property, so a\n * framework's reconciler is not in the drag loop. Persist from the event; restore by\n * setting `position`.\n *\n * A pane CONTAINS a chat; a chat never contains a split.\n *\n * `aria-orientation` on the handle is the INVERSE of this element's `orientation`: the\n * attribute names the SEPARATOR's own axis, which is what the APG means by \"Left Arrow\n * moves a vertical splitter\" and what ARIA 1.2 means by the attribute. Do not \"fix\" it —\n * consumers style against it.\n *\n * Shift + an arrow is ±10%. That is an ecosystem convention, not the APG, which\n * specifies the single step alone.\n *\n * @element aparte-split\n *\n * @attr {number} position - The primary pane's size, as a percentage of the container. Reflected on commit, never per drag frame.\n * @attr {string} orientation - `horizontal` (panes side by side, the default) or `vertical` (stacked). Names the CONTAINER's axis.\n * @attr {string} primary - `start` (default) or `end`: which pane `position` sizes.\n * @attr {boolean} collapsed - The primary pane folded to its minimum. Enter on the seam toggles it; a second Enter restores the size it had.\n * @attr {string} breakpoint - Below this width the split shows one pane at a time. A length (default `48rem`), or `none` to never stack.\n * @attr {string} pane - `start` (default) or `end`: which pane is shown while stacked.\n * @attr {boolean} single - Show one pane — the one `pane` names — whatever the width: the seam and the other pane are gone, as under the breakpoint. For a host that decides itself when a second pane exists (a preview with nothing to preview yet). `collapsed` is not this: it folds the primary pane to `--aparte-split-min` and keeps the seam. The CSS route, `.aparte-split--only-start` / `--only-end`, is the same state for a host that owns its breakpoints.\n * @attr {boolean} disabled - No drag, no keys, no tab stop; the seam stays drawn.\n * @attr {string} label - The seam's accessible name. Defaults to the locale's `splitHandleLabel`.\n * @attr {boolean} data-stacked - Written BY the element while one pane is shown. Read-only; style against it.\n *\n * @fires {CustomEvent<AparteSplitResizeDetail>} aparte-split-resize - The position settled: after a drag, a key, a double-click or a property set. Never during a drag — persist from here.\n *\n * @cssprop [--aparte-split-position=38%] - The primary pane's size.\n * @cssprop [--aparte-split-min=min(20rem, 100%)] - Floor of the primary pane. Any length or percentage.\n * @cssprop [--aparte-split-max=60%] - Ceiling of the primary pane.\n * @cssprop [--aparte-split-handle-size=12px] - The seam's track: the grab zone, and what the grid reserves between the panes. The painted line inside it is `--aparte-split-seam-width`.\n * @cssprop [--aparte-split-hit-area=12px] - The invisible grab zone around it. Grows to the touch target on a coarse pointer.\n *\n * @example\n * <!-- The chat in one pane, your own in the other. Drag the seam, or tab to it and use\n * the arrows; double-click resets it. `--aparte-split-min` is a CSS length, so the\n * chat cannot be dragged narrower than 16rem whatever the percentage says. Under\n * 48rem of window the split shows one pane, and the buttons switch it. -->\n * <div class=\"aparte-app-header\">\n * <button class=\"aparte-btn aparte-btn--sm aparte-btn--surface\" type=\"button\" data-aparte-split-pane=\"start\">Chat</button>\n * <button class=\"aparte-btn aparte-btn--sm aparte-btn--surface\" type=\"button\" data-aparte-split-pane=\"end\">Preview</button>\n * </div>\n * <aparte-split position=\"38\" style=\"height: 22rem; --aparte-split-min: 16rem\">\n * <aparte-chat>\n * <aparte-chat-viewport>\n * <aparte-chat-bubble message-id=\"u1\" data-role=\"user\" content=\"Show me the preview beside the chat.\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"a1\" data-role=\"assistant\" name=\"Assistant\" content=\"It is the pane on the right. Drag the seam to give it more room, or double-click the seam to reset.\"></aparte-chat-bubble>\n * </aparte-chat-viewport>\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 * </div>\n * </aparte-composer>\n * </aparte-chat>\n * <section class=\"aparte-split__pane\">\n * <!-- Your document, not ours: it brings its own colour scheme and typeface. -->\n * <iframe title=\"Preview\" style=\"inline-size: 100%; block-size: 100%; border: 0\"\n * srcdoc=\"<!doctype html><style>:root { color-scheme: light dark; font: 15px/1.5 system-ui, sans-serif; padding: 1rem 1.5rem } h1 { font-size: 1.25rem }</style><h1>Your pane</h1><p>A preview, a document, a map — anything that lives beside the conversation.</p>\"></iframe>\n * </section>\n * </aparte-split>\n * <script>\n * document.querySelector('aparte-split').addEventListener('aparte-split-resize', (e) => {\n * localStorage.setItem('split', String(e.detail.position));\n * });\n * </script>\n */\nexport class AparteSplit extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['position', 'orientation', 'primary', 'collapsed', 'breakpoint', 'pane', 'disabled', 'label', 'single'];\n }\n\n private _handle: HTMLElement | null = null;\n private _scrim: HTMLElement | null = null;\n private _media: MediaQueryList | null = null;\n private _observer: ResizeObserver | null = null;\n /** The pending `_measureBounds()` from a resize, batched to one per frame. */\n private _measureRafId: number | null = null;\n /** The host size the last resize tick measured, so an unchanged one does nothing. */\n private _observedPx = 0;\n\n /** Ids for the panes `aria-controls` names — a counter, the way the primitives do it. */\n private static _paneIdSeq = 0;\n\n /** The live position. The attribute holds it only after a commit. */\n private _position = DEFAULT_POSITION;\n /** What `reset()` — and a double-click on the seam — goes back to. */\n private _initialPosition = DEFAULT_POSITION;\n /**\n * `_initialPosition` has been taken from the markup once. A re-parent runs\n * `connectedCallback` again, and by then the attribute holds the LAST COMMIT, not\n * what the author wrote: capturing a second time makes a framework re-render, a tab\n * switch or a drag-and-drop of the panel redefine what \"reset\" means.\n */\n private _initialCaptured = false;\n /** The size the primary pane had before it was collapsed, for the second Enter. */\n private _preCollapsePosition: number | null = null;\n\n /** Committed values, so a reflection of our own does not read as a host's change. */\n private _lastPosition: number | null = null;\n private _lastCollapsed: boolean | null = null;\n private _lastPane: 'start' | 'end' | null = null;\n\n /** What an attribute-driven commit blames. Raised around a key that changes state. */\n private _source: AparteSplitResizeDetail['source'] = 'api';\n\n /**\n * `connectedCallback` has run and the element knows what its markup asked for.\n *\n * Not `isConnected`: during an UPGRADE the element is already in the document, so\n * `attributeChangedCallback` fires for every authored attribute — connected, and\n * before `connectedCallback` — which is the ordinary case for a server-rendered\n * `<aparte-split position=\"38\">` upgraded when the module loads. Committing there\n * measures a layout the element has not set up yet (no `data-stacked`, so on a\n * phone the probe reads the stacked pane and writes `position=\"84\"`), reflects that\n * over the authored number, and sends it to whatever the host persists.\n */\n private _ready = false;\n\n private _dragging = false;\n private _captured = false;\n /** The gesture actually moved the seam. A press that did not commits nothing. */\n private _moved = false;\n private _pointerId: number | null = null;\n private _dragStartPercent = DEFAULT_POSITION;\n private _dragOriginPx = 0;\n private _dragContainerPx = 0;\n /** A key is down and has moved the seam; its keyup is the commit. */\n private _keying = false;\n\n // ─── Properties ───────────────────────────────────────────────────────\n\n /** The primary pane's size, as a percentage of the container. Setting it commits. */\n get position(): number {\n return this._position;\n }\n\n set position(value: number) {\n const next = toPercent(String(value), this._position);\n if (this.isConnected && next === this._lastPosition) return;\n this._position = next;\n this._setLive(next);\n if (this.isConnected) this._commit('api');\n }\n\n /** The container's axis: `horizontal` (side by side) or `vertical` (stacked). */\n get orientation(): 'horizontal' | 'vertical' {\n return this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n }\n\n set orientation(value: 'horizontal' | 'vertical') {\n this.setAttribute('orientation', value);\n }\n\n /** Which pane `position` sizes. */\n get primary(): 'start' | 'end' {\n return this.getAttribute('primary') === 'end' ? 'end' : 'start';\n }\n\n set primary(value: 'start' | 'end') {\n this.setAttribute('primary', value);\n }\n\n /** The primary pane is folded to its minimum. */\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(value: boolean) {\n this.toggleAttribute('collapsed', presenceOn(value));\n }\n\n /** Which pane is shown while the split is stacked. */\n get pane(): 'start' | 'end' {\n return this.getAttribute('pane') === 'end' ? 'end' : 'start';\n }\n\n set pane(value: 'start' | 'end') {\n this.setAttribute('pane', value);\n }\n\n /** No drag, no keys, no tab stop. The seam stays drawn. */\n get disabled(): boolean {\n return this.hasAttribute('disabled');\n }\n\n set disabled(value: boolean) {\n this.toggleAttribute('disabled', presenceOn(value));\n }\n\n /**\n * True while the split is showing one pane. Read-only.\n *\n * Both routes into that state count. `data-stacked` is the one this element writes\n * from its own breakpoint; `.aparte-split--only-start` / `--only-end` are the CSS\n * route a host takes when it owns its breakpoints and sets `breakpoint=\"none\"`. The\n * sheet gives the two byte-identical rules, so the element has to read them the same\n * way — every guard downstream keys on this getter, and a split stacked by the class\n * alone would measure a one-track grid and commit `position=\"100\"`.\n */\n get stacked(): boolean {\n return (\n this.hasAttribute('data-stacked')\n || this.hasAttribute('single')\n || this.classList.contains('aparte-split--only-start')\n || this.classList.contains('aparte-split--only-end')\n );\n }\n\n /** One pane on demand, whatever the width. Reflected. */\n get single(): boolean {\n return this.hasAttribute('single');\n }\n\n set single(value: boolean) {\n this.toggleAttribute('single', presenceOn(value));\n }\n\n // ─── Public API ───────────────────────────────────────────────────────\n\n /** Fold the primary pane to its minimum, remembering the size it had. */\n collapse(): void {\n this.collapsed = true;\n }\n\n /** Unfold it, back to the size it had before it collapsed. */\n expand(): void {\n this.collapsed = false;\n }\n\n toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n }\n\n /** Show one pane, which only has a visible effect while the split is stacked. */\n showPane(which: 'start' | 'end'): void {\n this.pane = which;\n }\n\n /**\n * Back to the position the split was FIRST connected with — a re-parent restores\n * that, it does not redefine it. What a double-click on the seam does.\n */\n reset(): void {\n this._position = this._initialPosition;\n this._setLive(this._position);\n if (this.isConnected) this._commit('api');\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────\n\n connectedCallback(): void {\n if (!this.classList.contains('aparte-split')) this.classList.add('aparte-split');\n this._handle = this._ensureHandle();\n\n const attr = this.getAttribute('position');\n if (attr !== null) {\n this._position = toPercent(attr, DEFAULT_POSITION);\n this._setLive(this._position);\n } else {\n // No attribute: adopt whatever the cascade already resolves to, and write\n // nothing. That is the server-rendered case — a split styled from CSS alone\n // must not snap to a JS default the moment the element upgrades.\n this._position = toPercent(\n getComputedStyle(this).getPropertyValue('--aparte-split-position'),\n DEFAULT_POSITION,\n );\n }\n if (!this._initialCaptured) {\n this._initialPosition = this._position;\n this._initialCaptured = true;\n }\n // A `collapsed` present in the markup is applied HERE, not by the attribute\n // callback: that one fires during upgrade, before this runs, and early-returns\n // on `!_ready`. Without this the attribute and the render disagree from the\n // first frame — server-rendered `<aparte-split collapsed>` came up open.\n if (this.collapsed) {\n // …but only if it is arriving folded, not COMING BACK folded. `_lastCollapsed`\n // survives `disconnectedCallback` and is reassigned four lines down, so here\n // it still answers \"was it folded when it left\" — and a split that left folded\n // has `position=\"0\"` on it, so capturing again would record 0 as the size to\n // restore and `expand()` would reopen onto nothing.\n if (this._lastCollapsed !== true) this._preCollapsePosition = this._position;\n this._position = 0;\n this._setLive(0);\n }\n this._lastPosition = this._position;\n this._lastCollapsed = this.collapsed;\n this._lastPane = this.pane;\n\n this._stampHandle();\n this._relabel();\n this._measureBounds();\n\n this._handle.addEventListener('pointerdown', this._onPointerDown);\n this._handle.addEventListener('dblclick', this._onDoubleClick);\n this._handle.addEventListener('focusout', this._onFocusOut);\n this.addEventListener('keydown', this._onKeydown);\n this.addEventListener('keyup', this._onKeyup);\n document.addEventListener('click', this._onDocumentClick);\n window.addEventListener('aparte-config-change', this._onConfigChange);\n this._watchBreakpoint();\n this._watchSize();\n // Last: everything above reads the markup directly, and until it has run an\n // attribute change is the upgrade replaying what the author already wrote.\n this._ready = true;\n this._syncPaneControls();\n }\n\n disconnectedCallback(): void {\n this._handle?.removeEventListener('pointerdown', this._onPointerDown);\n this._handle?.removeEventListener('dblclick', this._onDoubleClick);\n this._handle?.removeEventListener('focusout', this._onFocusOut);\n this.removeEventListener('keydown', this._onKeydown);\n this.removeEventListener('keyup', this._onKeyup);\n document.removeEventListener('click', this._onDocumentClick);\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n this._releasePointer();\n this._media?.removeEventListener('change', this._onMediaChange);\n this._media = null;\n this._observer?.disconnect();\n this._observer = null;\n if (this._measureRafId !== null) {\n cancelAnimationFrame(this._measureRafId);\n this._measureRafId = null;\n }\n // A scrim left behind is fixed, full-page and transparent: the whole document\n // would go dead to the pointer with nothing on screen to explain it.\n this._removeScrim();\n this._handle?.removeAttribute('data-dragging');\n this._dragging = false;\n this._keying = false;\n this._ready = false;\n }\n\n attributeChangedCallback(name: string): void {\n if (!this._ready) return;\n switch (name) {\n case 'position': {\n const next = toPercent(this.getAttribute('position'), this._position);\n if (next === this._lastPosition) return;\n this._position = next;\n this._setLive(next);\n this._commit();\n return;\n }\n case 'collapsed': {\n const collapsed = this.collapsed;\n if (collapsed === this._lastCollapsed) return;\n this._lastCollapsed = collapsed;\n if (collapsed) {\n this._preCollapsePosition = this._position;\n this._position = 0;\n } else {\n this._position = this._preCollapsePosition ?? this._initialPosition;\n }\n this._setLive(this._position);\n this._commit();\n return;\n }\n case 'pane': {\n const pane = this.pane;\n this._syncPaneControls();\n if (pane === this._lastPane) return;\n this._lastPane = pane;\n this._commit();\n return;\n }\n case 'orientation':\n case 'primary':\n this._stampHandle();\n this._measureBounds();\n return;\n case 'disabled':\n // A drag in flight on a split that just went inert would keep tracking\n // the pointer and commit on release. Cancel it, restoring the value the\n // press started from: the gesture did not finish, so it did not decide.\n if (this.disabled && this._dragging) this._endDrag('pointer', this._dragStartPercent);\n this._stampHandle();\n return;\n case 'single':\n // Same as a breakpoint crossing for the seam: a drag on a split that just\n // went to one pane has nothing left to size.\n if (this.single && this._dragging) this._endDrag('pointer', this._dragStartPercent);\n this._stampHandle();\n // One pane shown, or both again: the switch says which, or nothing.\n this._syncPaneControls();\n return;\n case 'breakpoint':\n this._watchBreakpoint();\n return;\n case 'label':\n this._relabel();\n }\n }\n\n // ─── Structure ────────────────────────────────────────────────────────\n\n /**\n * The seam. An author-written `.aparte-split__handle` is ADOPTED — a second one\n * would be a second tab stop drawn over the first — otherwise one is inserted\n * between the first two children, which is where a two-pane grid needs it.\n *\n * An adopted one is MOVED there if the author put it elsewhere. The sheet is\n * positional — three tracks with the seam in the middle, and the stacked rules hide\n * `:nth-child(1)` / `:nth-child(3)` — so a handle written first would take the\n * primary pane's track and make stacking hide the wrong child.\n */\n private _ensureHandle(): HTMLElement {\n const own = Array.from(this.children).find(\n (child): child is HTMLElement => child instanceof HTMLElement && child.classList.contains('aparte-split__handle'),\n );\n if (own) {\n if (this.children[1] !== own) {\n const second = Array.from(this.children).filter((child) => child !== own)[1] ?? null;\n this.insertBefore(own, second);\n }\n return own;\n }\n const handle = document.createElement('div');\n handle.className = 'aparte-split__handle';\n const second = this.children[1] ?? null;\n if (second) this.insertBefore(handle, second);\n else this.appendChild(handle);\n return handle;\n }\n\n /** The panes: every element child that is not the seam. */\n private _panes(): HTMLElement[] {\n return Array.from(this.children).filter(\n (child): child is HTMLElement => child instanceof HTMLElement && !child.classList.contains('aparte-split__handle'),\n );\n }\n\n private _primaryPane(): HTMLElement | null {\n const panes = this._panes();\n return (this.primary === 'end' ? panes[panes.length - 1] : panes[0]) ?? null;\n }\n\n /**\n * The APG's window splitter, plus the two states that take it out of the tab order.\n *\n * `aria-orientation` is the INVERSE of `orientation`: the attribute names the\n * separator's own axis, so a split whose panes sit side by side has a VERTICAL\n * separator between them.\n */\n private _stampHandle(): void {\n const handle = this._handle;\n if (!handle) return;\n handle.setAttribute('role', 'separator');\n handle.setAttribute('aria-orientation', this.orientation === 'vertical' ? 'horizontal' : 'vertical');\n if (this.disabled || this.stacked) handle.removeAttribute('tabindex');\n else handle.setAttribute('tabindex', '0');\n // The ACHIEVED size, never the requested one — `_stampHandle` runs again after\n // every `_measureBounds`, so announcing the request here would undo the\n // reconciliation and put valuenow back outside the range it just published.\n handle.setAttribute('aria-valuenow', String(Math.round(this._achievedPercent())));\n const pane = this._primaryPane();\n if (pane) {\n if (!pane.id) pane.id = `aparte-split-pane-${++AparteSplit._paneIdSeq}`;\n handle.setAttribute('aria-controls', pane.id);\n }\n }\n\n // ─── Measurement ──────────────────────────────────────────────────────\n\n private _measure(el: Element): number {\n const box = el.getBoundingClientRect();\n return this.orientation === 'vertical' ? box.height : box.width;\n }\n\n /**\n * What the pane ACTUALLY got, which is not what was asked for: the CSS clamp between\n * `--aparte-split-min` and `--aparte-split-max` has the last word, and announcing the\n * requested value instead is how a splitter comes to tell a screen reader 5% while\n * the pane sits at 22%. Without layout (jsdom, `display: none`, before first paint)\n * the request is all there is, so it stands.\n *\n * While STACKED there is nothing to read: one pane is `display: none` and the other\n * spans the single track, so the ratio is 0 or 100 whatever the seam was set to.\n * Measuring it there would write `position=\"0\"` into the attribute, the event and the\n * host's storage when \"Preview\" is tapped on a phone.\n */\n private _achievedPercent(): number {\n if (this.stacked) return this._position;\n const total = this._measure(this);\n const pane = this._primaryPane();\n if (!pane || !(total > 0)) return this._position;\n return Math.max(0, Math.min(100, (this._measure(pane) / total) * 100));\n }\n\n /**\n * The range the seam can actually reach, written onto the separator: park the pane\n * at 0% and read it, park it at 100% and read it, put the position back. Two forced\n * layouts in one synchronous task, so nothing paints in between.\n *\n * It also reconciles `aria-valuenow`, because the two have to be one story: the\n * requested percentage can sit outside the clamped range the probe just announced —\n * with a `--aparte-split-min` of `20rem`, `position=\"38\"` on a 700px container asks\n * for 38 and gets ~46 — and a separator whose value is below its own minimum is the\n * unclamped-announcement bug this probe exists to prevent, just at rest. `_position`\n * is left alone: it is the REQUESTED value, and `reset()` means it.\n *\n * Gated on `!_dragging`: a ResizeObserver tick mid-gesture would move the pane to\n * both ends under the pointer. Gated on `!stacked` for the reason\n * `_achievedPercent()` is: a pane that spans everything measures the same at 0% and\n * at 100%, which would announce a range of one number.\n */\n private _measureBounds(): void {\n const handle = this._handle;\n if (!handle || this._dragging || this.stacked) return;\n const pane = this._primaryPane();\n const total = this._measure(this);\n if (!pane || !(total > 0)) {\n handle.setAttribute('aria-valuemin', '0');\n handle.setAttribute('aria-valuemax', '100');\n return;\n }\n const saved = this.style.getPropertyValue('--aparte-split-position');\n this.style.setProperty('--aparte-split-position', '0%');\n const min = (this._measure(pane) / total) * 100;\n this.style.setProperty('--aparte-split-position', '100%');\n const max = (this._measure(pane) / total) * 100;\n if (saved) this.style.setProperty('--aparte-split-position', saved);\n else this.style.removeProperty('--aparte-split-position');\n handle.setAttribute('aria-valuemin', String(Math.round(min)));\n handle.setAttribute('aria-valuemax', String(Math.round(max)));\n handle.setAttribute('aria-valuenow', String(Math.round(this._achievedPercent())));\n }\n\n /**\n * Re-measure when the container's size changes — and only then. The probe writes the\n * host's `style` three times, and the reflection is commit-only precisely because a\n * `MutationObserver({ attributes: true })` (the docs' preview frame has one) reposts\n * a frame height per mutation, which resizes the split, which re-ticks this observer.\n * So: ignore a tick that did not move the size, and batch the rest to one per frame.\n */\n private _watchSize(): void {\n if (typeof ResizeObserver !== 'function') return;\n this._observer = new ResizeObserver(() => {\n const px = this._measure(this);\n if (Math.abs(px - this._observedPx) <= 1) return;\n this._observedPx = px;\n if (this._measureRafId !== null) return;\n this._measureRafId = requestAnimationFrame(() => {\n this._measureRafId = null;\n this._measureBounds();\n });\n });\n this._observer.observe(this);\n }\n\n // ─── The one write, and the one event ─────────────────────────────────\n\n private _setLive(value: number): void {\n this.style.setProperty('--aparte-split-position', `${Math.round(value * 1000) / 1000}%`);\n }\n\n /**\n * The commit: reflect, announce, dispatch — once. Everything downstream reads the\n * ACHIEVED size, so the attribute, `aria-valuenow` and the detail are one number.\n *\n * There is no per-frame event on purpose. A bubbling, composed CustomEvent per\n * pointermove is a reconciler in the drag loop, and no pane in this repo re-lays\n * itself out in JS — they resize in CSS. The trigger for adding\n * `aparte-split-resizing` is a consumer whose pane needs a JS measurement mid-drag.\n */\n private _commit(source: AparteSplitResizeDetail['source'] = this._source): void {\n const position = Math.round(this._achievedPercent());\n this._position = position;\n this._setLive(position);\n this._lastPosition = position;\n if (this.getAttribute('position') !== String(position)) this.setAttribute('position', String(position));\n this._handle?.setAttribute('aria-valuenow', String(position));\n this.dispatchEvent(\n new CustomEvent<AparteSplitResizeDetail>('aparte-split-resize', {\n detail: {\n position,\n collapsed: this.collapsed,\n stacked: this.stacked,\n pane: this.pane,\n orientation: this.orientation,\n source,\n },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n // ─── Pointer ──────────────────────────────────────────────────────────\n\n /** The computed direction, never `document.dir`: a host can flip one subtree. */\n private _rtl(): boolean {\n return getComputedStyle(this).direction === 'rtl';\n }\n\n private _onPointerDown = (event: PointerEvent): void => {\n if (this.disabled || this.stacked || this._dragging) return;\n if (event.button > 0) return;\n // Not the browser's default: without this a drag starts a text selection in the\n // pane the pointer crosses. The focus that preventDefault costs is given back\n // below, which Safari would not have given at all.\n event.preventDefault();\n this._handle?.focus();\n this._dragging = true;\n this._captured = false;\n this._moved = false;\n this._pointerId = event.pointerId;\n this._dragStartPercent = this._position;\n this._dragOriginPx = this.orientation === 'vertical' ? event.clientY : event.clientX;\n this._dragContainerPx = this._measure(this);\n this._handle?.setAttribute('data-dragging', '');\n this._addScrim();\n window.addEventListener('pointermove', this._onPointerMove);\n window.addEventListener('pointerup', this._onPointerUp);\n window.addEventListener('pointercancel', this._onPointerCancel);\n };\n\n /**\n * Only the pointer that started the gesture. On a hybrid device a hovering pen or a\n * stray touch delivers a `pointermove` with `buttons === 0`, which would otherwise\n * fall into the release branch below and end someone else's mouse drag.\n */\n private _isOurPointer(event: PointerEvent): boolean {\n return this._pointerId === null || event.pointerId === this._pointerId;\n }\n\n private _onPointerMove = (event: PointerEvent): void => {\n if (!this._dragging || !this._isOurPointer(event)) return;\n // A pointer released over a cross-origin iframe never delivers `pointerup`, so\n // the drag would never end. Checked HERE and only here: on `pointerleave`\n // Firefox reports 0 for a button that is still down, and the drag would end at\n // the first pane the pointer crosses.\n if (event.buttons === 0) {\n this._endDrag('pointer');\n return;\n }\n if (!this._captured && this._pointerId !== null) {\n this._captured = true;\n // Deferred to the FIRST MOVE on purpose. Capturing on pointerdown retargets\n // the pointerup and suppresses the click a dblclick is built from — and\n // double-click is how the seam resets. By the first move the gesture is a\n // drag, not a click, so there is nothing left to suppress.\n try {\n this._handle?.setPointerCapture(this._pointerId);\n } catch {\n // Not every engine has capture for every pointer type; the window\n // listeners are what actually carry the drag.\n }\n }\n const point = this.orientation === 'vertical' ? event.clientY : event.clientX;\n this._position = nextPosition({\n startPercent: this._dragStartPercent,\n deltaPx: point - this._dragOriginPx,\n containerPx: this._dragContainerPx,\n rtl: this._rtl(),\n vertical: this.orientation === 'vertical',\n primaryEnd: this.primary === 'end',\n });\n this._moved = true;\n this._setLive(this._position);\n };\n\n private _onPointerUp = (event: PointerEvent): void => {\n if (this._isOurPointer(event)) this._endDrag('pointer');\n };\n\n private _onPointerCancel = (event: PointerEvent): void => {\n if (this._isOurPointer(event)) this._endDrag('pointer');\n };\n\n private _endDrag(source: AparteSplitResizeDetail['source'], restore?: number): void {\n if (!this._dragging) return;\n const moved = this._moved;\n this._dragging = false;\n this._moved = false;\n this._releasePointer();\n this._handle?.removeAttribute('data-dragging');\n // A gesture that MOVED was captured, so its release already targets the handle\n // and the overlay can go at once. A press that did not move is the one a\n // double-click is built from, and its release landed on the scrim — that one is\n // retired rather than removed. See `_removeScrim`.\n this._removeScrim(!moved);\n if (restore !== undefined) {\n this._position = restore;\n this._setLive(restore);\n }\n // A press that never moved settled nothing. Clicking the seam is how it takes\n // focus, and a double-click is two of them — three events for one reset, two of\n // them carrying the pre-reset value into whatever the host persists.\n if (!moved && restore === undefined) return;\n this._measureBounds();\n this._commit(source);\n }\n\n private _releasePointer(): void {\n window.removeEventListener('pointermove', this._onPointerMove);\n window.removeEventListener('pointerup', this._onPointerUp);\n window.removeEventListener('pointercancel', this._onPointerCancel);\n if (this._captured && this._pointerId !== null) {\n try {\n this._handle?.releasePointerCapture(this._pointerId);\n } catch {\n // Already released, or never captured on this engine.\n }\n }\n this._captured = false;\n this._pointerId = null;\n }\n\n /**\n * The drag overlay: a fixed, transparent child of the HANDLE, alive for one gesture.\n * Topmost, so it swallows an iframe pane's hit-testing — the case this exists for —\n * and paints the drag cursor across the page. A child of the handle rather than of\n * the body so `click`, and therefore `dblclick`, still dispatches at the handle.\n */\n private _addScrim(): void {\n if (this._scrim || !this._handle) return;\n const scrim = document.createElement('div');\n scrim.className = 'aparte-split__scrim';\n scrim.setAttribute('aria-hidden', 'true');\n this._handle.appendChild(scrim);\n this._scrim = scrim;\n }\n\n /**\n * Take the overlay off.\n *\n * `retire` is the press that never moved — the one a click, and then a double-click,\n * is built from. Its release happened ON the scrim (nothing captured the pointer\n * yet), and WebKit works out what a click hit by walking the pointerup target's LIVE\n * ancestors: take the node out here and that chain is rooted nowhere, so no `click`\n * fires at all, no `dblclick` follows, and the seam never resets. Chromium and Gecko\n * resolve the target before dispatch and do not care.\n *\n * So a retired scrim goes INERT immediately — `pointer-events: none`, which is what\n * actually matters, since a full-page overlay left hit-testable is a page dead to the\n * pointer — and leaves the document a moment later. Not on the next task: a first\n * measurement removed it there and it still raced the click under a loaded machine\n * (the trace is in `e2e/tests/layout.spec.ts`). The reference is dropped either way,\n * so the next press builds its own; an inert one on its way out costs nothing.\n *\n * Teardown never retires: `disconnectedCallback` takes the live one out at once.\n */\n private _removeScrim(retire = false): void {\n const scrim = this._scrim;\n this._scrim = null;\n if (!scrim) return;\n if (!retire) {\n scrim.remove();\n return;\n }\n scrim.style.pointerEvents = 'none';\n setTimeout(() => scrim.remove(), SCRIM_RETIRE_MS);\n }\n\n private _onDoubleClick = (): void => {\n if (this.disabled || this.stacked) return;\n this.reset();\n };\n\n // ─── Keyboard ─────────────────────────────────────────────────────────\n\n private _onKeydown = (event: KeyboardEvent): void => {\n if (this._dragging && event.key === 'Escape') {\n event.preventDefault();\n this._endDrag('pointer', this._dragStartPercent);\n return;\n }\n if (this.disabled || this.stacked || event.target !== this._handle) return;\n if (event.key === 'Enter') {\n event.preventDefault();\n this._source = 'keyboard';\n try {\n this.toggleCollapse();\n } finally {\n this._source = 'api';\n }\n return;\n }\n const delta = keyDelta(\n event.key,\n event.shiftKey,\n this._rtl(),\n this.orientation === 'vertical',\n this.primary === 'end',\n );\n // An arrow off this split's axis is the page's, not ours: not swallowing it is\n // what lets a pane scroll while the seam has the focus.\n if (delta === null) return;\n event.preventDefault();\n this._keying = true;\n this._position = toPercent(String(this._position + delta), this._position);\n this._setLive(this._position);\n };\n\n /**\n * The keyup commits, not the keydown: a held arrow repeats, and one release should\n * write one attribute and send one event, not thirty.\n */\n private _onKeyup = (event: KeyboardEvent): void => {\n if (!this._keying || event.target !== this._handle) return;\n if (keyDelta(event.key, event.shiftKey, this._rtl(), this.orientation === 'vertical') === null) return;\n this._keying = false;\n this._commit('keyboard');\n };\n\n /**\n * The other end of a key step. If focus leaves the seam while the key is still down —\n * alt-tab, a click into a pane, a host moving focus — the keyup never arrives, and\n * the pane would stay where the keydown put it while the attribute and\n * `aria-valuenow` kept the old number for good. What is rendered is what commits.\n */\n private _onFocusOut = (): void => {\n if (!this._keying) return;\n this._keying = false;\n this._commit('keyboard');\n };\n\n // ─── The breakpoint ───────────────────────────────────────────────────\n\n /**\n * The stacking query, from the `breakpoint` attribute: a length (`48rem`, the\n * default, or `640px`), or `none` for a split that stays two panes at any width —\n * which a documentation frame needs and a host with its own breakpoint wants.\n */\n private _watchBreakpoint(): void {\n this._media?.removeEventListener('change', this._onMediaChange);\n this._media = null;\n const attr = (this.getAttribute('breakpoint') ?? '').trim();\n if (attr === 'none') {\n this._applyStacked(false);\n return;\n }\n if (typeof matchMedia !== 'function') return;\n const query = attr ? `(max-width: ${attr})` : STACK_QUERY;\n this._media = matchMedia(query);\n this._media.addEventListener('change', this._onMediaChange);\n this._applyStacked(this._media.matches);\n }\n\n private _onMediaChange = (e: MediaQueryListEvent): void => this._applyStacked(e.matches);\n\n /**\n * Entering the stacked state shows the START pane: the chat, never a preview of\n * nothing — the same judgement that makes the sidebar's drawer enter closed. Unless\n * the markup already named one, which is an answer to the same question and a better\n * one. Leaving it restores both panes, and with them the seam and its tab stop.\n *\n * `was` is the ATTRIBUTE, not the `stacked` getter: the getter also counts\n * `.aparte-split--only-*`, the CSS route a host takes when it owns its breakpoints\n * and sets `breakpoint=\"none\"`. Read through the getter, that mount looks like a\n * split leaving a stacked state it never entered — and the `pane` the author wrote\n * is deleted on the way in.\n */\n private _applyStacked(stacked: boolean): void {\n const was = this.hasAttribute('data-stacked');\n // Crossing the breakpoint mid-drag hides the seam under the pointer. Cancel\n // first, while there is still a two-pane layout to measure and restore against.\n if (stacked && !was && this._dragging) this._endDrag('pointer', this._dragStartPercent);\n this.toggleAttribute('data-stacked', stacked);\n if (stacked !== was) this._syncPaneControls();\n if (stacked && !was) {\n if (!this.hasAttribute('pane')) this.showPane('start');\n } else if (!stacked && was) {\n this.removeAttribute('pane');\n }\n // Both writes above can happen before `_ready`, from the mount's own\n // `_watchBreakpoint()` — and `attributeChangedCallback` early-returns there, so\n // nothing records them. Left unstamped, the NEXT `showPane` back to the value the\n // markup held reads as \"no change\" and commits nothing.\n this._lastPane = this.pane;\n this._stampHandle();\n if (stacked !== was) this._measureBounds();\n }\n\n /**\n * A `[data-aparte-split-pane]` anywhere on the page switches a split's pane, so a\n * two-button toggle in a header needs no script.\n *\n * The value is `start` or `end` — the pane to show. Any other non-empty value names\n * a split's `id` and TOGGLES that one; empty toggles the nearest split, else the\n * first on the page. Same shape as `[data-aparte-sidebar-toggle]`.\n */\n private _onDocumentClick = (e: Event): void => {\n // `eventOrigin`, not `e.target`: a click from inside a consumer's shadow root is\n // retargeted to the shadow host by the time it reaches `document`.\n const control = (eventOrigin(e) as HTMLElement | null)?.closest?.<HTMLElement>('[data-aparte-split-pane]');\n if (!control) return;\n const value = (control.getAttribute('data-aparte-split-pane') ?? '').trim();\n const which = value === 'start' || value === 'end' ? value : null;\n const named = which ? '' : value;\n if (named) {\n if (named !== this.id) return;\n } else {\n // `rootOf` before `document`: a button that names no id must find the split\n // beside it inside a consumer's shadow root, which `document` cannot see into.\n const nearest = control.closest('aparte-split')\n ?? rootOf(control).querySelector('aparte-split')\n ?? document.querySelector('aparte-split');\n if (nearest !== this) return;\n }\n this.showPane(which ?? (this.pane === 'start' ? 'end' : 'start'));\n };\n\n /**\n * The two-button switch says which pane it shows: `aria-pressed` on every\n * `[data-aparte-split-pane=\"start\"|\"end\"]` that reaches this split, true on the one\n * naming the pane in view, while the split is stacked; side by side both panes are in\n * view and neither button is pressed, so none carries it. A toggle (an id, or empty) is\n * an action and gets none. Read when the split connects, whenever `pane` changes and\n * when it stacks or unstacks, so a control added later is stamped at the next switch.\n */\n private _syncPaneControls(): void {\n const roots = new Set<ParentNode>([rootOf(this), document]);\n for (const root of roots) {\n for (const control of root.querySelectorAll<HTMLElement>('[data-aparte-split-pane]')) {\n // Trimmed, as the click reads it; and only on a button, where `aria-pressed` means something.\n const which = (control.getAttribute('data-aparte-split-pane') ?? '').trim();\n if (which !== 'start' && which !== 'end') continue;\n if (control.localName !== 'button' && control.getAttribute('role') !== 'button') continue;\n const target = control.closest('aparte-split')\n ?? rootOf(control).querySelector('aparte-split')\n ?? document.querySelector('aparte-split');\n if (target !== this) continue;\n if (this.stacked) control.setAttribute('aria-pressed', String(which === this.pane));\n else control.removeAttribute('aria-pressed');\n }\n }\n }\n\n // ─── Locale ───────────────────────────────────────────────────────────\n\n private _onConfigChange = (): void => this._relabel();\n\n /**\n * The seam's accessible name. A host that wrote its own `aria-label` — or an\n * `aria-labelledby` pointing at a heading — keeps it: `data-own-label` marks the\n * one we set, so a language switch replaces ours and never theirs.\n */\n private _relabel(): void {\n const handle = this._handle;\n if (!handle) return;\n if (handle.hasAttribute('aria-labelledby')) return;\n if (handle.hasAttribute('aria-label') && handle.dataset['ownLabel'] !== 'locale') return;\n const locale = resolveConfig(this).getLocale();\n handle.setAttribute('aria-label', this.getAttribute('label') ?? locale.splitHandleLabel ?? 'Resize the panes');\n handle.dataset['ownLabel'] = 'locale';\n }\n}\n\nif (!customElements.get('aparte-split')) {\n customElements.define('aparte-split', AparteSplit);\n}\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. **A server or a worker resolves `index.node.ts`\n * instead** — through the `node`, `worker`, `workerd`, `edge-light`, `deno` and `bun`\n * conditions in package.json, or by name as `@aparte/core/headless` in a worker built\n * with the page's conditions — a DOM-free entry with the client, host, transports,\n * 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/display/mark.css';\nimport './styles/surface/tabs.css';\nimport './styles/surface/accordion.css';\nimport './styles/surface/menu.css';\nimport './styles/surface/popover.css';\nimport './styles/surface/dialog.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/text.css';\nimport './styles/components/elicitation.css';\nimport './styles/components/conversation.css';\nimport './styles/components/suggestions.css';\nimport './styles/components/context.css';\nimport './styles/components/scroll-rail.css';\nimport './styles/shell/sidebar.css';\nimport './styles/shell/app-header.css';\nimport './styles/shell/app-shell.css';\nimport './styles/shell/split.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 AparteStreamBlock,\n AparteStreamBlockMatch,\n AparteSendEventDetail,\n AparteViewportConfig,\n AparteInputConfig,\n AparteThemeVariables,\n AparteStatus,\n AparteAttachment,\n AparteMessageBranch,\n AparteBubbleActionsConfig,\n AparteBubbleActionName,\n AparteSegment,\n AparteSegmentTypes,\n AparteSegmentType,\n AparteTextSegment,\n AparteThinkingSegment,\n AparteCodeSegment,\n AparteSegmentRenderer,\n AparteCustomSegment,\n AparteToolCallSegment,\n // Five shapes that were public in everything but name. `AparteSegment` is exported\n // and its union names all its members, yet the error segment could not be written\n // 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 // 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 AparteAIProviderMetadata,\n AparteAIProviderConfigSchema,\n AparteModelConfig,\n ModelStatus,\n ModelLoadProgress,\n AparteModelChangeEventDetail,\n AparteApprovalModeChangeEventDetail,\n AparteMessageDoneEventDetail,\n AparteMessageStartEventDetail,\n AparteMessageErrorEventDetail,\n AparteMessageAbortedEventDetail,\n AparteMessagePausedEventDetail,\n AparteAbortEventDetail,\n AparteCompactEventDetail,\n AparteCompactDoneEventDetail,\n AparteCompactStartEventDetail,\n AparteCompactErrorEventDetail,\n AparteAttachmentPreviewEventDetail,\n AparteLinkClickEventDetail,\n AparteMessageInfoEventDetail,\n AparteSiblingInfo,\n AparteBranchNavigateEventDetail,\n ApartePathChangedEventDetail,\n AparteRetryEventDetail,\n AparteEditEventDetail,\n AparteFeedbackEventDetail,\n AparteActionEventDetail,\n // Chat types\n AparteChatRequest,\n AparteRequestMeta,\n AparteChatResponse,\n AparteChatMessage,\n AparteContentPart,\n AparteTextPart,\n AparteImagePart,\n AparteStreamEvent,\n AparteStreamEventMap,\n AparteUsage,\n // Tool types\n AparteTool,\n AparteToolCall,\n AparteApprovalPolicy,\n AparteApprovalRuling,\n AparteToolResult,\n AparteToolHandler,\n AparteToolContext,\n AparteToolRenderer,\n AparteToolSummary,\n AparteToolInputFormatter,\n AparteToolApprovalRequestDetail,\n // Canonical imperative surfaces (re-exported by every wrapper: the chat handle,\n // and the <AparteUi> proxy handle the four used to hand-copy).\n AparteChatImperativeApi,\n AparteUiHandle,\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 AparteContextAttributes,\n AparteIconAttributes,\n AparteScrollRailAttributes,\n AparteSidebarAttributes,\n AparteSplitAttributes,\n AparteSuggestionsAttributes,\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';\nexport { AparteSuggestions } from './components/index.js';\nexport type { AparteSuggestion, AparteSuggestionEventDetail } from './components/index.js';\nexport { AparteContext } from './components/index.js';\nexport type { AparteContextLevel, AparteContextThresholdEventDetail } from './components/index.js';\nexport { AparteScrollRail } from './components/index.js';\nexport type { AparteScrollRailJumpDetail, AparteScrollRailEvery } from './components/index.js';\nexport { AparteSidebar } from './components/index.js';\nexport type { AparteSidebarToggleDetail } from './components/index.js';\nexport { AparteSplit } from './components/index.js';\nexport type { AparteSplitResizeDetail } from './components/index.js';\n\n// Export conversation list primitive\nexport { AparteConversationList } from './components/index.js';\nexport type { AparteConversationListItem, AparteConversationSelectDetail, AparteConversationDeleteDetail, AparteConversationArchiveDetail, AparteConversationPinDetail, AparteConversationRenameDetail } from './components/index.js';\n\n// Export conversations (types, adapter contract, manager)\nexport type {\n AparteConversation,\n AparteConversationMeta,\n AparteStorageAdapter,\n AparteAttachmentRow,\n AparteConversationLoadedDetail,\n} from './conversations/index.js';\nexport { APARTE_CONVERSATION_SCHEMA_VERSION } from './conversations/index.js';\nexport {\n AparteConversationManager,\n type ConversationManagerOptions,\n type AparteConversationsListener,\n type AparteConversationTitleProvider,\n} 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 } 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, AparteLocale, AparteLocaleExtensions, AparteAction, AparteActionZone, AparteIconProvider, AparteIconName, AparteCoreIconName, AparteAvatarProvider, AparteStatusRenderer, AparteErrorRenderer, AparteAttachmentRenderer, AparteElicitationFieldRenderer, AparteElicitationFieldContext, AparteElicitationFieldControl, AparteSiblingNavRenderer, AparteBubbleShellRenderer, AparteModelPreference, AparteModelPreferenceProvider, AparteSanitizer } from './config/index.js';\nexport { APARTE_DEFAULT_ICON_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// Same wall, same reason: `navigator.clipboard` is undefined on `http://`, and a\n// consumer's own copy button hits it exactly as core's three did.\nexport { copyText } from './utils/copy-text.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 } 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 { createAgentStreamRunner, type AgentStreamRunnerOptions } from './client/agent-runner.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, createAparteAgentHandler, isFormatAdapter } from './transport/index.js';\nexport type { AparteTransport, AparteTransportContext, AparteFormatAdapter, AparteVendorRequest, BackendTransportOptions, DirectTransportOptions, AparteChatHandlerOptions, AparteAgentHandlerOptions, AparteAgentTool, AparteAgentRequestBody } 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// Every element class, imported by VALUE. This is what replaced the eight\n// side-effect imports that used to sit here (\"components register themselves in their\n// files\"): a side-effect import keeps a module, but it names nothing a reader can check,\n// and the list had fallen eight modules behind the twenty-four elements core defines.\n// Naming the classes makes the anchor and the registry the same list — see\n// `APARTE_ELEMENTS` below.\nimport {\n AparteChat,\n AparteChatBubble,\n AparteChatStatus,\n AparteChatViewport,\n AparteComposer,\n AparteComposerInput,\n AparteComposerSend,\n AparteComposerCancel,\n AparteComposerAttachments,\n AparteComposerAddAttachment,\n AparteComposerAction,\n AparteComposerToolbar,\n AparteConversationList,\n AparteScrollRail,\n AparteSidebar,\n AparteSplit,\n AparteSuggestions,\n AparteContext,\n} from './components/index.js';\nimport { AparteElicitation as AparteElicitationElement } from './components/elicitation/aparte-elicitation.js';\nimport {\n AparteSelect,\n AparteOption,\n AparteOptgroup,\n AparteProgressSpinner,\n AparteIcon,\n} from './primitives/index.js';\n\n/**\n * Every tag core defines, beside the class that defines it.\n *\n * Two jobs, and they are the same list on purpose. It is what the warning below reads,\n * so the function can NAME the tags that are missing instead of saying \"some\n * components\"; and it is the single list `src/__tests__/register-all-components.test.ts`\n * holds against every `customElements.define` literal in the source.\n *\n * What it is NOT is what keeps those 24 `define` calls alive, and the difference is\n * worth stating because the opposite is easy to assume. The browser build is ONE\n * module — `dist/index.js` — and `package.json`'s `sideEffects` names it, so no\n * bundler drops it; all 24 calls sit in it as top-level statements whatever this array\n * references. Naming the classes buys the warning, not the registrations.\n *\n * It checks every element core defines and names the ones that are missing, so a\n * consumer whose bundler dropped the browser entry is told so, and how to keep it.\n *\n * `src/types/__tests__/element-map.test.ts` reads the same\n * `customElements.define` literals out of the source, so this list cannot fall behind\n * them without a red test.\n */\nconst APARTE_ELEMENTS: ReadonlyArray<readonly [string, CustomElementConstructor]> = [\n // Chat shell + transcript\n ['aparte-chat', AparteChat],\n ['aparte-chat-viewport', AparteChatViewport],\n ['aparte-chat-bubble', AparteChatBubble],\n ['aparte-chat-status', AparteChatStatus],\n\n // Composer and its primitives\n ['aparte-composer', AparteComposer],\n ['aparte-composer-input', AparteComposerInput],\n ['aparte-composer-send', AparteComposerSend],\n ['aparte-composer-cancel', AparteComposerCancel],\n ['aparte-composer-attachments', AparteComposerAttachments],\n ['aparte-composer-add-attachment', AparteComposerAddAttachment],\n ['aparte-composer-action', AparteComposerAction],\n ['aparte-composer-toolbar', AparteComposerToolbar],\n\n // Standalone components\n ['aparte-conversation-list', AparteConversationList],\n ['aparte-elicitation', AparteElicitationElement],\n ['aparte-scroll-rail', AparteScrollRail],\n ['aparte-sidebar', AparteSidebar],\n ['aparte-split', AparteSplit],\n ['aparte-suggestions', AparteSuggestions],\n ['aparte-context', AparteContext],\n\n // Primitives\n ['aparte-select', AparteSelect],\n ['aparte-option', AparteOption],\n ['aparte-optgroup', AparteOptgroup],\n ['aparte-progress-spinner', AparteProgressSpinner],\n ['aparte-icon', AparteIcon],\n];\n\n/**\n * Report which of core's elements are not registered.\n *\n * Importing `@aparte/core` already registers all of them. Call this to check it: after a\n * bundler with aggressive side-effect elimination, or a dynamic `import()`, it names on\n * the console every element that is not registered — the sign the browser entry was\n * dropped: import `@aparte/core` at module scope, and keep its `sideEffects`.\n *\n * Idempotent, and safe to call more than once: it defines nothing, it reports.\n */\nexport function registerAllComponents(): void {\n const missing = APARTE_ELEMENTS.filter(([tag]) => !customElements.get(tag)).map(([tag]) => tag);\n if (missing.length > 0) {\n console.warn(\n `[aparte] ${missing.length} of ${APARTE_ELEMENTS.length} elements are not registered: `\n + `${missing.join(', ')}. Import '@aparte/core' at module scope (not behind a lazy `\n + `import()), and check that your bundler is honouring the package's sideEffects field.`,\n );\n }\n}\n\n// The dialog wiring — three `data-aparte-dialog-*` attributes on the browser's own\n// <dialog>, installed once at import like the default renderers. Exported for a host\n// that builds its page before importing core; idempotent.\nimport { installDialogTriggersOnce } from './interop/dialog-triggers.js';\nexport { installDialogTriggersOnce };\nif (typeof document !== 'undefined') installDialogTriggersOnce();\n"],"names":["n","s","l","i","m","panel","y","syncSubmit","retract","el","a","b","c","f","h","p","g","d","k","tick","v","second","AparteChat","AparteChatViewport","AparteChatBubble","AparteChatStatus","AparteComposer","AparteComposerInput","AparteComposerSend","AparteComposerCancel","AparteComposerAttachments","AparteComposerAddAttachment","AparteComposerAction","AparteComposerToolbar","AparteConversationList","AparteElicitationElement","AparteScrollRail","AparteSidebar","AparteSplit","AparteSuggestions","AparteContext","AparteSelect","AparteOption","AparteOptgroup","AparteProgressSpinner","AparteIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils/presence.ts","../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/utils/transcript.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/utils/fold-text.ts","../src/components/conversation-list/aparte-conversation-list.ts","../src/components/suggestions/aparte-suggestions.ts","../src/components/context/aparte-context.ts","../src/components/scroll-rail/aparte-scroll-rail.ts","../src/components/sidebar/aparte-sidebar.ts","../src/components/split/geometry.ts","../src/components/split/aparte-split.ts","../src/index.ts"],"sourcesContent":["/**\n * What a presence-attribute SETTER makes of what it was handed.\n *\n * The generated attribute types map a presence attribute to `'' | null | undefined`,\n * because React and Vue stringify what they set on a custom element — so `''` is the\n * documented spelling for ON. Svelte 5 does not take the attribute path: it assigns\n * the PROPERTY whenever the element has one, which hands these setters the `''` the\n * types promise — and `toggleAttribute(name, '')` reads an empty string as falsy, so\n * the attribute would be REMOVED, the opposite of what the template asked for.\n *\n * One rule serves both paths without forking the spelling: on a presence property, an\n * empty string means ON, exactly as an empty attribute does. Everything else keeps\n * JavaScript's own truthiness (`false`, `null`, `undefined` → OFF).\n */\nexport function presenceOn(value: unknown): boolean {\n return value === '' ? true : Boolean(value);\n}\n","// Imports stay ABOVE the docblock: one between the comment and the class detaches the\n// comment — TypeScript stops associating it, the manifest loses the `@example`, and\n// `check:examples` refuses the build. It happened here.\nimport { presenceOn } from '../../utils/presence.js';\n\n/**\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 trigger reads the option's text without\n * the status dot and without an `.aparte-option__meta` child, the optional line under the\n * label (a description); its search filter reads the full `textContent`, that line included.\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)] - Option text colour.\n * @cssprop [--aparte-select-option-hover=var(--aparte-surface-2)] - 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) var(--aparte-mark-tint), 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))] - 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 */\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 this.toggleAttribute('disabled', presenceOn(val));\n }\n\n get selected(): boolean {\n return this.hasAttribute('selected');\n }\n\n set selected(val: boolean) {\n this.toggleAttribute('selected', presenceOn(val));\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';\nimport { presenceOn } from '../../utils/presence.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 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 set collapsible(val: boolean) {\n this.toggleAttribute('collapsible', presenceOn(val));\n }\n\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(val: boolean) {\n this.toggleAttribute('collapsed', presenceOn(val));\n }\n\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(val: boolean) {\n this.toggleAttribute('loading', presenceOn(val));\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 // From the locale: this said \"Fetching models...\" — English whatever the\n // page's language, and the product's word for what a generic group holds.\n const spinner = document.createElement('span');\n spinner.className = 'aparte-spinner-small';\n loader.replaceChildren(spinner, ` ${resolveConfig(this).t('loading') || 'Loading…'}`);\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 { presenceOn } from '../../utils/presence.js';\nimport './aparte-optgroup.js';\nimport { resolveConfig } from '../../config/config-context.js';\nimport { activeElementIn, eventOrigin } from '../../utils/event-target.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 combobox is whichever element holds focus. Without `searchable` that is the\n * trigger. With it, opening focuses the filter field, so the field carries\n * `role=\"combobox\"`, `aria-expanded`, `aria-controls` and the roving\n * `aria-activedescendant`, and the trigger is a `role=\"button\"` — one combobox either\n * way, and always the focused one.\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} searchable - Adds a filter field above the options. Set or removed later, the field comes or goes.\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)] - Trigger background. (The dropdown panel has its own token, `--aparte-select-dropdown-bg`, in both themes.)\n * @cssprop [--aparte-select-border=var(--aparte-border-control)] - Border of the trigger and of the dropdown.\n * @cssprop [--aparte-select-border-hover=var(--aparte-border-control)] - Trigger border on hover.\n * @cssprop [--aparte-radius-select=var(--aparte-radius-md)] - Corner radius of the trigger and the dropdown — the theme's knob, beside `--aparte-radius-input` and the others.\n * @cssprop [--aparte-select-text=var(--aparte-text)] - Colour of the trigger label (and of the options).\n * @cssprop [--aparte-select-chevron=var(--aparte-text-muted)] - Colour of the chevron, which rotates 180° while open.\n * @cssprop [--aparte-select-dropdown-bg=var(--aparte-surface-1)] - Dropdown panel background, in both themes (`--aparte-surface-1` is dark-aware), so a transparent trigger does not make the panel see-through.\n * @cssprop [--aparte-select-shadow=0 1px 3px rgba(0, 0, 0, 0.08), 0 10px 28px rgba(0, 0, 0, 0.16)] - Dropdown panel shadow.\n * @cssprop [--aparte-select-z=var(--aparte-z-dropdown)] - `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) would add\n * another option-click listener and one click would fire the change handler once\n * per re-connect, unless the same bound reference is removed and re-added.\n */\n private _boundHandleOptionClick = this._handleOptionClick.bind(this);\n private _boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n private _boundHandleKeydown = this._handleKeydown.bind(this);\n /**\n * The trigger's own three, bound for the same reason as the option click: the\n * trigger element survives a re-connect, so listeners that cannot be removed would\n * stack. One re-parent would make a click toggle twice — open and shut inside the\n * same click, i.e. a control that looks dead — and announce `aparte-select-open`\n * twice.\n */\n private _boundHandleTriggerClick = (): void => { this._toggle(); };\n private _boundHandleTriggerKeydown = (e: KeyboardEvent): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n // When open, Enter/Space selects the active option — handled by the\n // document-level nav handler. Only toggle when closed, so we don't close the\n // 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 private _boundHandleSearchInput = (e: Event): void => {\n this._filterOptions((e.target as HTMLInputElement).value.toLowerCase());\n };\n\n static get observedAttributes(): string[] {\n return ['value', 'placeholder', 'disabled', 'searchable', 'open'];\n }\n\n connectedCallback(): void {\n this._value = this.getAttribute('value') || '';\n // `open` at mount goes through `_openDropdown()` like every other open, once\n // the structure it needs exists. Rendering \"already open\" instead produced a\n // visible list with `aria-expanded=\"false\"`, no seeded highlight and no event.\n //\n // At MOUNT only. This callback runs again on every re-connect — a portal, a\n // Vue teleport, any framework re-parent, the bug class the bound handlers\n // above document — and an element that never closed has no transition to\n // replay: replaying it fired a second `aparte-select-open` and re-seeded the\n // highlight on the selected option, throwing away where the arrows had got to.\n const wantsOpen = this.hasAttribute('open') && !this._isOpen;\n this._render();\n // A `searchable` set or removed while detached went past attributeChangedCallback.\n this._applySearchable();\n this._applyDisabled();\n this._setupEventListeners();\n this._setupMutationObserver();\n if (wantsOpen) this._openDropdown();\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 // One open path and one close path — the attribute goes through them, so\n // it cannot produce a state a click never produces. Guarded against the\n // re-entry caused by their own reflecting writes: both set `_isOpen`\n // BEFORE touching the attribute, so the nested call finds the two already\n // in agreement and does nothing.\n const wanted = this.hasAttribute('open');\n if (wanted !== this._isOpen) {\n if (wanted) this._openDropdown();\n else this._closeDropdown();\n }\n }\n // Both were observed and neither had a branch here, so the callback fired and\n // did nothing: a placeholder rewritten by a locale switch left the visible\n // label and the combobox's name in the old language, and a select disabled\n // after mount kept a focusable trigger announced as operable.\n if (name === 'placeholder') {\n // The label update carries the names with it — the trigger's own name\n // holds the selected value in the button shape, so the two cannot drift.\n this._updateTriggerLabel();\n }\n if (name === 'searchable') this._applySearchable();\n if (name === 'disabled') {\n this._applyDisabled();\n // The symmetric half of the close `_applyDisabled()` does on the way in:\n // an `open` the select refused while it was disabled is honoured now that\n // it is operable, because that attribute was never taken back.\n if (!this.hasAttribute('disabled') && this.hasAttribute('open')) this._openDropdown();\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 this.toggleAttribute('open', presenceOn(val));\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Rendering\n // ─────────────────────────────────────────────────────────────────────────\n\n /** The filter field: the combobox of a `searchable` select, since it takes the focus. */\n private _buildSearchInput(cfg: ReturnType<typeof resolveConfig>): HTMLInputElement {\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.className = 'aparte-select-search';\n // Two locale keys, not literals. The placeholder is VISIBLE text — a\n // French page opened the model picker and read \"Search...\" in the box —\n // and the aria-label is the field's only name.\n searchInput.placeholder = cfg.t('selectSearchPlaceholder') || 'Search…';\n searchInput.setAttribute('aria-label', cfg.t('selectSearchLabel') || 'Search options');\n // This field takes the focus, so it is the combobox (see the trigger in `_render`).\n searchInput.setAttribute('role', 'combobox');\n searchInput.setAttribute('aria-haspopup', 'listbox');\n searchInput.setAttribute('aria-autocomplete', 'list');\n searchInput.setAttribute('aria-expanded', String(this._isOpen));\n return searchInput;\n }\n\n /**\n * `searchable` set or removed after the first render: the field comes or goes, and the\n * combobox moves with it, so a framework that\n * binds it a beat late — Angular's `[searchable]` inside an `@if` — still gets a field.\n */\n private _applySearchable(): void {\n if (!this._dropdown || !this._trigger) return;\n const wanted = this.hasAttribute('searchable');\n if (wanted === Boolean(this._searchInput)) return;\n if (this._isOpen) this._closeDropdown();\n const listboxId = this._trigger.getAttribute('aria-controls');\n if (wanted) {\n const input = this._buildSearchInput(resolveConfig(this));\n if (listboxId) input.setAttribute('aria-controls', listboxId);\n this._dropdown.prepend(input);\n this._searchInput = input;\n input.addEventListener('input', this._boundHandleSearchInput);\n this._trigger.setAttribute('role', 'button');\n } else {\n this._searchInput!.removeEventListener('input', this._boundHandleSearchInput);\n this._searchInput!.remove();\n this._searchInput = null;\n this._trigger.setAttribute('role', 'combobox');\n }\n // The role decides the name (see `_updateNames`), and on a re-connect nothing else\n // would rename it: the observer is set up after this runs.\n this._updateNames();\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 // Resolved ONCE for this build: the chevron's provider and the search field's\n // two strings all come from it, and three lookups of the same instance is how\n // a component ends up reading two different configs in one render.\n const cfg = resolveConfig(this);\n\n // Create wrapper structure\n const trigger = document.createElement('div');\n trigger.className = 'aparte-select-trigger';\n trigger.setAttribute('tabindex', '0');\n // The combobox goes on whatever element ends up holding focus. A searchable\n // select focuses its filter field the moment the dropdown opens, so the field\n // is the combobox there and the trigger is the button that summons it —\n // otherwise the roving `aria-activedescendant` sits on an unfocused node and\n // is announced to nobody, and the control declares two comboboxes for one\n // value.\n trigger.setAttribute('role', searchable ? 'button' : '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 // Two layers in one grid cell: the text that shows, and a hidden stack of every\n // option's label. The stack is what gives the control its width — the widest\n // option's — so the trigger does not resize with each selection, which is the\n // one thing a native <select> gets right and this one used to get wrong.\n const labelText = document.createElement('span');\n labelText.className = 'aparte-select-label-text';\n labelText.textContent = placeholder;\n const sizer = document.createElement('span');\n sizer.className = 'aparte-select-label-sizer';\n sizer.setAttribute('aria-hidden', 'true');\n labelSpan.append(labelText, sizer);\n const chevronSpan = document.createElement('span');\n chevronSpan.className = 'aparte-select-chevron';\n chevronSpan.innerHTML = cfg.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 const searchInput = searchable ? this._buildSearchInput(cfg) : null;\n if (searchInput) dropdown.appendChild(searchInput);\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 searchInput?.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 // The previous one first: this is called from `_render()` AND from\n // `connectedCallback`, so assigning over the field left an orphan observing from\n // the very first mount, plus one per re-connect. `_updateDropdownContent` silences\n // `this._observer` before its own writes — only the newest — so the orphans woke\n // on core's own rebuilds, which is precisely what the comment below says cannot\n // happen.\n this._observer?.disconnect();\n this._observer = new MutationObserver(() => {\n this._updateDropdownContent();\n // A list refreshed IN PLACE (a consumer writing into `.aparte-select-options`,\n // as the model selector does when providers settle) leaves nothing to move,\n // so the call above returns before it reaches the label — and the trigger\n // kept showing a label the list no longer offered, with a width stack to\n // match. The label follows every change; its writes are guarded so this\n // observer does not wake itself.\n this._updateTriggerLabel();\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: without this the highlight\n * disappears, `aria-activedescendant` points at an id no longer in the document, and\n * the next ArrowDown moves 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 // Removed first, every one of them: this runs on every connect and the elements\n // it binds to survive a re-connect (`_render` keeps the structure it already\n // built). Adding the same bound reference twice is a no-op per spec — the remove\n // is what makes that true and survives a future refactor that rebinds.\n this._trigger?.removeEventListener('click', this._boundHandleTriggerClick);\n this._trigger?.addEventListener('click', this._boundHandleTriggerClick);\n\n this._trigger?.removeEventListener('keydown', this._boundHandleTriggerKeydown);\n this._trigger?.addEventListener('keydown', this._boundHandleTriggerKeydown);\n\n // Option selection.\n this.removeEventListener('click', this._boundHandleOptionClick);\n this.addEventListener('click', this._boundHandleOptionClick);\n\n // Search filter\n this._searchInput?.removeEventListener('input', this._boundHandleSearchInput);\n this._searchInput?.addEventListener('input', this._boundHandleSearchInput);\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 // `eventOrigin`, not `e.target`: this listens on `document`, so a select inside a\n // consumer's shadow root saw the shadow HOST as the target — never itself — and\n // shut its own dropdown on the very click that opened it.\n const origin = eventOrigin(e) as Node | null;\n if (!origin || !this.contains(origin)) {\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 // `activeElementIn`: inside a consumer's shadow root `document.activeElement` is\n // the shadow host, and Home would hijack the caret out of the filter field.\n const inSearch = activeElementIn(this) === 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 /**\n * The element that holds DOM focus while the list is being driven, and therefore\n * the one that must carry the combobox state a reader follows: the filter field\n * when the select is `searchable` (it is focused on open), the trigger otherwise.\n */\n private _activeHost(): HTMLElement | null {\n return this._searchInput ?? this._trigger;\n }\n\n /** `aria-expanded` belongs to the combobox — write it wherever the combobox is. */\n private _setExpanded(open: boolean): void {\n const flag = String(open);\n this._trigger?.setAttribute('aria-expanded', flag);\n this._searchInput?.setAttribute('aria-expanded', flag);\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._activeHost()?.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._activeHost()?.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._activeHost()?.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 // Already open is not a transition. One guard here makes every entry\n // idempotent — the attribute, the property, a click, a re-connect — instead\n // of each caller carrying its own.\n if (this._isOpen) return;\n // A disabled select does not open — and the `open` attribute stays exactly as\n // the caller wrote it. It is the CONSUMER's: a one-way binding writes it once\n // and never again, so taking it back leaves the template saying open and the\n // element saying closed, with no write left to reconcile them. The attribute\n // stands and `attributeChangedCallback` honours it the moment `disabled` goes.\n if (this.hasAttribute('disabled')) return;\n\n this._isOpen = true;\n this._dropdown?.removeAttribute('hidden');\n this._setExpanded(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._setExpanded(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 /**\n * The combobox and its listbox are named by the host's `aria-label`, else by the\n * placeholder — the same rule the first render applies, re-applied whenever the\n * placeholder or the selection changes. Written only when different: the mutation\n * observer watches this subtree.\n *\n * A `combobox` is NAMED by that attribute and VALUED by its visible label span, so\n * the name is the control and nothing else. A `button` — the shape a `searchable`\n * trigger takes, since the filter field is then the combobox — is named by its\n * CONTENT, which this very attribute overrides: a name of just the placeholder\n * (\"Pick a model, button\") would never say which model is selected. So the button's\n * name carries both halves, and drops the second when it would only repeat the\n * first (nothing selected: the value IS the placeholder).\n */\n private _updateNames(): void {\n const placeholder = this.getAttribute('placeholder') || 'Select...';\n const name = this.getAttribute('aria-label') || placeholder;\n const list = this.querySelector('.aparte-select-options');\n if (list && list.getAttribute('aria-label') !== name) list.setAttribute('aria-label', name);\n\n const trigger = this._trigger;\n if (!trigger) return;\n const value = this._getSelectedLabel() || placeholder;\n const wanted = trigger.getAttribute('role') === 'button' && value !== name ? `${name}: ${value}` : name;\n if (trigger.getAttribute('aria-label') !== wanted) trigger.setAttribute('aria-label', wanted);\n }\n\n /**\n * `disabled` takes the trigger out of the tab order and says so — opacity alone\n * left it focusable and announced as operable. An open dropdown closes.\n */\n private _applyDisabled(): void {\n const trigger = this._trigger;\n if (!trigger) return;\n const disabled = this.hasAttribute('disabled');\n trigger.setAttribute('tabindex', disabled ? '-1' : '0');\n if (disabled) {\n trigger.setAttribute('aria-disabled', 'true');\n if (this._isOpen) this._closeDropdown();\n } else {\n trigger.removeAttribute('aria-disabled');\n }\n }\n\n private _updateTriggerLabel(): void {\n // Every write below is guarded by a comparison: this runs from the mutation\n // observer, which watches the whole subtree, and an unconditional write to the\n // trigger would wake the observer that called it, forever. Written only when\n // something changed, the second pass finds nothing to do and the loop closes.\n const placeholder = this.getAttribute('placeholder') || 'Select...';\n const labelText = this._trigger?.querySelector('.aparte-select-label-text');\n if (labelText) {\n const next = this._getSelectedLabel() || placeholder;\n if (labelText.textContent !== next) labelText.textContent = next;\n }\n // The width stack — rebuilt here because this runs on every change to the\n // options too, so an option written later (or a list refreshed in place)\n // still counts. `textContent` per span: a label is consumer text, never markup.\n const sizer = this._trigger?.querySelector('.aparte-select-label-sizer');\n if (sizer) {\n const labels = [placeholder, ...Array.from(this.querySelectorAll('aparte-option'), (o) => optionLabelText(o))];\n const current = Array.from(sizer.children, (s) => s.textContent ?? '');\n if (labels.length !== current.length || labels.some((l, i) => l !== current[i])) {\n sizer.replaceChildren(...labels.map((text) => {\n const span = document.createElement('span');\n span.textContent = text;\n return span;\n }));\n }\n }\n\n // The accessible names follow the selection, not only the placeholder: a\n // searchable trigger is a button, and a button's name has to say which option\n // is selected because its content is overridden by that name.\n this._updateNames();\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 optionLabelText(opt);\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\n/**\n * An option's label as the trigger and its width read it: the option's text without the line\n * under it (`.aparte-option__meta` — a description, a model's context window) or the status dot.\n * The search filter still reads the whole text, so a description is searchable.\n */\nfunction optionLabelText(option: Element): string {\n let text = '';\n for (const node of option.childNodes) {\n if (node.nodeType === Node.ELEMENT_NODE && (node as Element).matches('.aparte-option__meta, .aparte-status-dot')) continue;\n text += node.textContent ?? '';\n }\n return text.trim();\n}\n\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=var(--aparte-spinner-size-md)] - Width and height of the element; the SVG fills it.\n * @cssprop [--aparte-spinner-color=currentColor] - Stroke of the filled (progress) arc.\n * @cssprop [--aparte-spinner-track=var(--aparte-border-control)] - 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';\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=calc(1rem * var(--aparte-font-scale))] - 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).getIcon(name) : 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, through the typed presenter contract rather than window events.\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 * and rejecting with an `AparteElicitationAbortError` when the turn is stopped or\n * errors while the question is open, or when the panel is evicted.\n *\n * Its CSS ships in `@aparte/core/styles.css` like every other component, so it can be\n * themed and its variables appear in 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, so a parameter added to the\n * real `setPanelSubmitEnabled` is followed here instead of leaving a stale twin.\n * `import type` is erased, so nothing here pulls the composer element into a runtime\n * import.\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 for the whole request, whatever its kind: a question's strings and\n * an approval's live in different places, and a field per kind is how the two\n * would drift.\n */\n relabel(): void;\n}\n\n\n/** The element that really holds the focus: through open shadow roots, down to the field. */\nfunction deepActiveElement(root: Document | ShadowRoot): Element | null {\n let active = root.activeElement;\n while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;\n return active;\n}\n\n/** `ancestor.contains(node)`, across shadow roots: a field inside a component inside the chat is in the chat. */\nfunction isWithin(node: Node, ancestor: Node): boolean {\n for (let n: Node | null = node; n; n = n.parentNode ?? (n instanceof ShadowRoot ? n.host : null)) {\n if (n === ancestor) return true;\n }\n return false;\n}\n\n/**\n * A way to put an open panel aside and read the transcript behind it — the same for a\n * question and an approval, since both open here. Hiding leaves a one-line bar that says an\n * answer is waiting and brings the panel back; the request stays open either way, and the\n * composer's editor stays hidden, because the question still waits for its answer. While it is\n * aside the composer's button has no act — what is out of sight cannot be sent — so `onToggle`\n * lets the caller recompute it on both moves.\n *\n * The hide button goes FIRST in the corner, before a question's Skip, so the two never\n * swap places between a question and an approval.\n */\nfunction addRetract(panelEl: HTMLElement, corner: HTMLElement, cfg: AparteConfig, reopen: () => void, onToggle: () => void): { relabel(next: AparteConfig): void } {\n const hide = document.createElement('button');\n hide.type = 'button';\n hide.className = 'aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--ghost aparte-elic-hide';\n hide.setAttribute('aria-expanded', 'true');\n hide.innerHTML = cfg.getIcon('expand');\n\n const reveal = document.createElement('button');\n reveal.type = 'button';\n reveal.className = 'aparte-btn aparte-btn--ghost aparte-elic-reveal';\n reveal.setAttribute('aria-expanded', 'false');\n const revealText = document.createElement('span');\n reveal.append(revealText);\n reveal.insertAdjacentHTML('beforeend', cfg.getIcon('collapse'));\n\n const relabel = (next: AparteConfig): void => {\n const label = next.t('elicitationHide');\n hide.setAttribute('aria-label', label);\n hide.title = label;\n revealText.textContent = next.t('elicitationWaiting');\n };\n relabel(cfg);\n\n hide.addEventListener('click', () => {\n panelEl.setAttribute('data-collapsed', '');\n onToggle();\n reveal.focus();\n });\n reveal.addEventListener('click', () => {\n panelEl.removeAttribute('data-collapsed');\n onToggle();\n reopen();\n });\n corner.prepend(hide);\n panelEl.append(reveal);\n return { relabel };\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.\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=var(--aparte-space-3)] - Vertical gap between the panel's rows (message, body, tabs).\n * @cssprop [--aparte-elic-padding=var(--aparte-space-3) var(--aparte-space-2)] - 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=var(--aparte-space-4)] - Space and separator padding between two fields of an object schema.\n * @cssprop [--aparte-elic-message-size=var(--aparte-font-size-base)] - Font size of the question text at the top of the panel.\n * @cssprop [--aparte-elic-title-size=var(--aparte-font-size-base)] - Font size of a field's title.\n * @cssprop [--aparte-elic-desc-size=var(--aparte-font-size-sm)] - Font size of a field's description.\n * @cssprop [--aparte-elic-option-padding=var(--aparte-space-4) var(--aparte-space-5)] - Padding of one enum/boolean option row.\n * @cssprop [--aparte-elic-option-radius=var(--aparte-radius-md)] - Corner radius of an option row.\n * @cssprop [--aparte-elic-option-title-size=var(--aparte-font-size-md)] - Font size of an option's label, and of the text inputs.\n * @cssprop [--aparte-elic-option-desc-size=var(--aparte-font-size-sm)] - 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=var(--aparte-radius-md)] - 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=var(--aparte-font-size-md)] - Font size of the corner \"Skip\" affordance (the decline).\n * @cssprop [--aparte-elic-step-size=var(--aparte-font-size-sm)] - Font size of a step tab, when the schema is asked one field at a time.\n * @cssprop [--aparte-elic-step-padding=var(--aparte-space-2) var(--aparte-space-1)] - Padding of a step tab.\n * @cssprop [--aparte-elic-step-gap=var(--aparte-space-7)] - 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=104px] - Space the tab rail and the question message keep clear for the corner: the hide button and, on a question, Skip. Widen it for a locale whose \"Skip\" word is wider.\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 * @cssprop [--aparte-approval-args-max-height=8.5rem] - Height cap of the arguments block before it scrolls.\n * @cssprop [--aparte-approval-args-size=var(--aparte-font-size-sm)] - Font size of the arguments block.\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 style=\"height: 20rem\">\n * <aparte-chat-viewport></aparte-chat-viewport>\n * <aparte-elicitation></aparte-elicitation>\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 * </div>\n * </aparte-composer>\n * </aparte-chat>\n *\n * <script>\n * // `aparte` is the `@aparte/core` module the frame exposes; in your app this line is\n * // `import { requestUserInput } from '@aparte/core'`. A tool handler asks; the panel\n * // mounts in the composer of the chat whose presenter this element is.\n * aparte.requestUserInput({\n * message: 'Which environment should I deploy to?',\n * schema: {\n * type: 'enum',\n * options: [\n * { value: 'staging', label: 'Staging', recommended: true },\n * { value: 'prod', label: 'Production', description: 'Live traffic' },\n * ],\n * },\n * });\n * </script>\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`, so they filter by instance: a Stop (or an\n * error) in one chat must not cancel the question a DIFFERENT chat is waiting on,\n * whose model would be told the user refused a question they were still looking at.\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 // end it without an answer (a rejection) so the client loop unblocks and the\n // composer 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: clearing the slot whenever it holds a presenter would leave a\n // still-mounted sibling chat unable to ask anything, silently, since the\n // 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 the focus has to be given back on close: otherwise a keyboard user who\n * approved a tool call lands at the top of the document and has to tab\n * through the whole page to write their next message (WCAG SC 2.4.3, and the\n * ARIA Authoring Practices Guide requires it of every 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 = deepActiveElement(document);\n /*\n * Where the focus goes, in one rule.\n *\n * The focus is how the panel is announced, so it is taken — unless the reader is\n * busy elsewhere: writing in a field outside this chat, in another chat, or inside\n * an iframe. Then it stays where it is and a polite status says a question waits.\n *\n * From a field of THIS chat (typing the next message while a reply streams), it\n * goes to the panel itself, not to its first option: the next key the reader\n * presses must not be Enter on \"Approve\". From a button (this chat's or the app's\n * own beside it) or from nobody (`<body>`: a click in Safari, a screen reader's\n * activation, a script), it goes to the first control, without the page jumping\n * when it came from nobody.\n */\n const messageText = (): string => { const m = request.message as unknown; return typeof m === 'function' ? String((m as () => unknown)()) : String(m ?? ''); };\n const CHAT = 'aparte-chat, [data-aparte-chat], [data-aparte-host]';\n const EDITABLE = 'input, textarea, select, [contenteditable]:not([contenteditable=\"false\"])';\n const chatRoot = composer.closest(CHAT) ?? composer.parentElement ?? composer;\n const fromNobody = !(focusedBefore instanceof HTMLElement) || focusedBefore === document.body;\n const before = fromNobody ? null : (focusedBefore as HTMLElement);\n const inChat = before !== null && isWithin(before, chatRoot);\n const typing = before !== null && before.matches(EDITABLE);\n const busyElsewhere = before !== null && !inChat\n && (typing || before.tagName === 'IFRAME' || before.closest(CHAT) !== null);\n const status = (text: string): void => {\n let region = composer.querySelector<HTMLElement>(':scope > .aparte-elic-status');\n if (!region) {\n region = document.createElement('span');\n region.className = 'aparte-sr-only aparte-elic-status';\n region.setAttribute('role', 'status');\n composer.appendChild(region);\n }\n const live = region;\n live.textContent = '';\n // A region announces a CHANGE: filled a moment after it exists, not with it.\n if (text) setTimeout(() => { if (live.isConnected) live.textContent = text; }, 60);\n };\n const focusPanel = (panel: { el: HTMLElement; focus(): void }, message: string): void => {\n if (busyElsewhere) { status(message); return; }\n const x = window.scrollX, y = window.scrollY;\n if (typing && inChat) {\n if (!panel.el.hasAttribute('tabindex')) panel.el.setAttribute('tabindex', '-1');\n if (!panel.el.hasAttribute('role')) panel.el.setAttribute('role', 'group');\n if (message && !panel.el.hasAttribute('aria-label')) panel.el.setAttribute('aria-label', message);\n panel.el.focus();\n } else {\n panel.focus();\n }\n if (fromNobody && (window.scrollX !== x || window.scrollY !== y)) window.scrollTo(x, y);\n };\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 = deepActiveElement(document);\n const focusWasInPanel = active instanceof Node && isWithin(active, composer);\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 status('');\n\n /*\n * Back to whoever had it, when that is still a place to be: connected, not\n * `<body>`, not inside the composer whose panel just closed, and someone the\n * panel took it from. Otherwise the composer — the editor is where a reader\n * who just answered writes next, and `<body>` would drop a keyboard user at\n * the top of the page (2.4.3).\n */\n if (focusWasInPanel) {\n const back = !busyElsewhere && before !== null && before.isConnected && !isWithin(before, composer) ? before : null;\n (back ?? composer).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: a `cancel` read as a refusal would tell the model\n * the user had refused a tool they 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 sendable = (): boolean => panel.isComplete() && !panel.el.hasAttribute('data-collapsed');\n const approvalMode = (): AparteComposerPanelMode => (sendable() ? 'submit' : 'none');\n const syncSubmit = (): void => composer.setPanelSubmitEnabled(sendable(), approvalMode());\n const panel = runWithConfig(cfg, () =>\n buildApprovalPanel(request.message, request.options ?? [], syncSubmit, request.details));\n panel.onSettle((answer) => settle({ action: 'accept', content: answer }));\n const retract = addRetract(panel.el, panel.dismiss, cfg, () => panel.focus(), syncSubmit);\n this._pending = {\n abort: () => fail(),\n composer,\n relabel: () => { panel.relabel(); retract.relabel(resolveConfig(this)); },\n };\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 focusPanel(panel, messageText());\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 syncSubmit = (): void => {\n const aside = panel.el.hasAttribute('data-collapsed');\n composer.setPanelSubmitEnabled(!aside && panel.canProceed(), aside ? 'none' : panel.mode());\n };\n const panel: BuiltElicitationPanel = runWithConfig(cfg, () =>\n buildElicitationPanel(request.message, schema, syncSubmit));\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 const retract = addRetract(panel.el, panel.dismiss, cfg, () => panel.focus(), syncSubmit);\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: () => {\n panel.relabel();\n const now = resolveConfig(this);\n skip.textContent = now.t('elicitationSkip');\n retract.relabel(now);\n },\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. A rejection (`aborted`), not `decline`:\n * nobody declined anything, the question was taken away.\n */\n onEvict: () => fail(),\n onSubmit: () => {\n // One meaning: submit the lot. Moving between questions is the chips'.\n if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() });\n },\n });\n focusPanel(panel, messageText());\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' || 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 is no `document.querySelector('aparte-composer')` fallback: on a page with\n * two chats, an elicitation that could not find its own composer would mount its\n * panel in the OTHER chat's, so one conversation's question would appear under the\n * other conversation, and answering it would resolve a tool call belonging to a\n * 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' || 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","/**\n * Whether a transcript shows nothing: no message, and none on its way.\n *\n * A message is an `<aparte-chat-bubble>`. A viewport that says `loading` holds no bubble\n * yet and is still not empty, so the welcome and the centred composer stay hidden while a\n * conversation is being fetched.\n *\n * One predicate because two elements ask the question — `<aparte-chat>` for its\n * `data-empty`, `<aparte-suggestions empty-only>` for whether its starters still belong —\n * and the `loading` half is exactly the kind of clause a second copy forgets.\n */\nexport function isTranscriptEmpty(viewport: Element): boolean {\n return !viewport.hasAttribute('loading') && !viewport.querySelector('aparte-chat-bubble');\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';\nimport { isTranscriptEmpty } from '../../utils/transcript.js';\nimport { gateGlide } from './glide-gate.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: it watches its own viewport and says whether the conversation is empty —\n * `data-empty`, in the attribute list below, is that watcher's output — and with\n * `center-empty` the stylesheet keeps the composer centered as a welcome state until\n * the first `<aparte-chat-bubble>` lands, then slides to the normal layout, with no\n * external JavaScript. The watcher needs a viewport somewhere inside: hand-written\n * markup always has one, because composing the default injects it, and Angular's\n * `framework-managed` host gets its watcher once the template has rendered the\n * viewport. The stylesheet 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 * 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: 24rem\">\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></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n * ```\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} submit-on-enter - Forwarded to the composer by value: `submit-on-enter=\"false\"`\n * makes Enter break the line and Shift+Enter send (the bare attribute, or none, keeps the\n * default — Enter sends). The four wrappers expose the same switch as `submitOnEnter`.\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} data-empty - Reflected BY the element while its viewport holds no\n * `<aparte-chat-bubble>` and is not `loading` — set again when the conversation is cleared.\n * With or without `center-empty`: the stylesheet centers the composer through\n * `aparte-chat[center-empty][data-empty]`, and an app styles or scripts its own empty state\n * against `aparte-chat[data-empty]`. Read-only.\n * @attr {boolean} data-glide - Reflected BY the element at the first key or pointer in the page, or\n * the first send: from then on `center-empty` slides the composer between the welcome and the\n * conversation. Before it, a conversation a script restores on load lands in one frame. Read-only.\n * @attr {boolean} overlay-composer - The ChatGPT anatomy, opt-in: the transcript's scroll\n * surface spans the whole column and the composer (with the rest of the bottom stack)\n * floats over it, so the scrollbar runs edge to edge instead of stopping at the\n * composer's top. The viewport measures the stack and publishes\n * `--aparte-bottom-inset`; content, spacer and the scroll button clear it. Read when\n * the viewport wires its observers — set it in the initial markup. Also honoured on a\n * wrapper's `[data-aparte-chat]` root.\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)] - 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 * @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: 24rem\"></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', 'submit-on-enter', 'attachments'];\n }\n\n private _observer: MutationObserver | null = null;\n private _loadingObserver: MutationObserver | null = null;\n private _offGlide: (() => void) | null = null;\n /** Waits for a `<aparte-chat-viewport>` child to appear — see `_watchForViewport`. */\n private _childObserver: MutationObserver | null = null;\n /** The viewport is loading and the gate is on — see `_applyLoadingGate`. */\n private _gated = false;\n /** The composer's `disabled` is the gate's to remove, not one the consumer set. */\n private _gateOwns = false;\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._forwardAttr('submit-on-enter');\n this._syncEmptyWatch();\n this._syncLoadingWatch();\n this._watchForViewport();\n this._offGlide?.();\n this._offGlide = gateGlide(this);\n }\n\n disconnectedCallback(): void {\n this._offGlide?.();\n this._offGlide = null;\n this._observer?.disconnect();\n this._observer = null;\n this._loadingObserver?.disconnect();\n this._loadingObserver = null;\n this._childObserver?.disconnect();\n this._childObserver = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\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). While the transcript is on its way the composer stays\n // disabled whatever the consumer does to the attribute — the gate lifts with the wait.\n if (name === 'disabled' && newValue === null && this._gated) {\n this._gateOwns = true;\n return;\n }\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.\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. The BUILT-IN approval gate\n * asks through it, so a chat without one cannot honour `needsApproval` at all.\n * An affordance core can honour end to end is on by default; leaving this out would\n * make the gate depend 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 /**\n * Watch the viewport so `data-empty` follows the conversation.\n *\n * Runs whether or not `center-empty` is set, so an app that wants starters on an\n * empty thread, or a welcome of its own, can read `data-empty` instead of counting\n * messages itself. Every selector core writes against it requires `[center-empty]`,\n * so reflecting it everywhere moves no layout.\n */\n private _syncEmptyWatch(): void {\n this._observer?.disconnect();\n this._observer = null;\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 — and\n // for its `loading` attribute, the other way of not being empty.\n this._observer = new MutationObserver(() => this._updateEmpty());\n this._observer.observe(viewport, { childList: true, subtree: true, attributes: true, attributeFilter: ['loading'] });\n }\n\n /**\n * A transcript on its way cannot take a message: while the viewport says `loading`\n * the composer is disabled, whatever else the chat is set to. A send in that window\n * raced the fetch — the controller appended to a history that `setMessages`\n * overwrote a moment later. Independent of `center-empty`: every chat gates.\n */\n private _syncLoadingWatch(): void {\n this._loadingObserver?.disconnect();\n this._loadingObserver = null;\n const viewport = this.querySelector('aparte-chat-viewport');\n if (!viewport) return;\n this._applyLoadingGate();\n this._loadingObserver = new MutationObserver(() => this._applyLoadingGate());\n this._loadingObserver.observe(viewport, { attributes: true, attributeFilter: ['loading'] });\n }\n\n /**\n * Both watches above need a viewport to observe, and there might not be one at\n * connect: `<aparte-chat framework-managed>` IS Angular's host element — upgraded on\n * insert, filled by the template afterwards — and hand-written markup upgrades before\n * its children are parsed. This waits for the child to arrive, installs them, and\n * stops, so every chat gates, including those whose transcript is fetched.\n */\n private _watchForViewport(): void {\n this._childObserver?.disconnect();\n this._childObserver = null;\n if (this.querySelector('aparte-chat-viewport')) return;\n this._childObserver = new MutationObserver(() => {\n if (!this.querySelector('aparte-chat-viewport')) return;\n this._childObserver?.disconnect();\n this._childObserver = null;\n this._syncEmptyWatch();\n this._syncLoadingWatch();\n });\n this._childObserver.observe(this, { childList: true, subtree: true });\n }\n\n /**\n * The gate only ever ADDS `disabled` and only ever removes what it added: a `disabled`\n * the consumer set — on this element, or on a composer of their own — stays.\n */\n private _applyLoadingGate(): void {\n const composer = this.querySelector('aparte-composer');\n const loading = this.querySelector('aparte-chat-viewport')?.hasAttribute('loading') ?? false;\n if (!composer) return;\n if (loading && !this._gated) {\n this._gated = true;\n // Already disabled: nothing to add, and nothing to take back later — unless the\n // consumer lifts theirs during the wait, which makes the attribute the gate's.\n this._gateOwns = !composer.hasAttribute('disabled');\n composer.setAttribute('disabled', '');\n } else if (!loading && this._gated) {\n this._gated = false;\n if (this._gateOwns && !this.hasAttribute('disabled')) composer.removeAttribute('disabled');\n this._gateOwns = false;\n }\n }\n\n /**\n * Empty means no message AND none on its way. A viewport that says `loading` holds\n * no bubble yet but does not count as empty, so the welcome and the centred composer\n * stay hidden while a conversation is being fetched.\n */\n private _updateEmpty(): void {\n const viewport = this.querySelector('aparte-chat-viewport');\n this.toggleAttribute('data-empty', !viewport || isTranscriptEmpty(viewport));\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 AparteLinkClickEventDetail,\n AparteRetryEventDetail,\n AparteEditEventDetail,\n AparteFeedbackEventDetail,\n AparteActionEventDetail,\n AparteMessageInfoEventDetail,\n AparteUsage,\n AparteMessage,\n AparteToolCallSegment,\n} from '../../types/index.js';\nimport { getSegmentRenderer, installDefaultRenderersOnce } from '../../renderers/index.js';\nimport { writeStreamedMarkdown, type AparteMarkdownStreamHost } from '../../renderers/markdown-stream.js';\nimport { AparteConfig, renderingVersionOf } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig, type AparteConfigAware } from '../../config/config-context.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { copyText } from '../../utils/copy-text.js';\nimport { mergeSegmentUpdate, isSegmentSettled } from '../../utils/segments.js';\nimport type { AparteComposerInput } from '../composer/aparte-composer-input.js';\nimport { escapeAttr, escapeHtml } from '../../utils/escape.js';\nimport { isSafeUrl } from '../../config/sanitize.js';\n\n/**\n * Warn ONCE when a segment has no renderer — only for types core has never heard of,\n * 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 * 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) shows its\n * `AparteCustomSegment.fallback` text. Without one it shows\n * `[Unknown segment type: custom]`.\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, segment?: Pick<AparteSegment, 'id'>): HTMLElement | null {\n let el: HTMLElement | null;\n if (result instanceof HTMLElement) {\n el = result;\n } else {\n const wrapper = document.createElement('div');\n wrapper.innerHTML = result;\n el = wrapper.firstElementChild as HTMLElement | null;\n }\n // The container finds its own children by id — this is where that invariant is\n // owned, for both arms and for every renderer, not each renderer's to remember.\n // A root without it made `_applySegmentUpdate` miss and fall back to the full\n // wipe-and-rebuild, which destroys a mounted artifact preview, collapses an\n // opened reasoning block and drops the focus — the ask_user receipt did exactly\n // that on every tool update, since `AparteToolRenderer`'s contract never said so.\n if (el && segment && !el.hasAttribute('data-segment-id')) el.setAttribute('data-segment-id', segment.id);\n return el;\n}\n\n/**\n * Empty avatar slots waiting to learn whether their role's `--aparte-avatar-image-*` names a\n * picture, with the bubble and the role each one reads. Flushed together in one microtask, so\n * a config change that redraws every bubble reads every token first and marks afterwards: a\n * mark set between two reads dirtied the style the next read had to recompute.\n *\n * The token is read on the bubble, not on the slot. A bubble off screen skips its contents\n * (`content-visibility: auto`), and reading a computed style inside a skipped subtree costs a\n * style pass of its own: on 200 messages that was ~50ms per config change in Chromium. So\n * the token is honoured where it is declared on the bubble or any ancestor — `:root`, the\n * chat — which is where the guide declares it.\n */\nconst pendingAvatarMarks = new Map<HTMLElement, { bubble: HTMLElement; role: string }>();\n\nfunction flushAvatarMarks(): void {\n const batch = [...pendingAvatarMarks].filter(([avatar]) => !avatar.firstChild);\n pendingAvatarMarks.clear();\n const shown = batch.map(([, { bubble, role }]) => {\n const image = getComputedStyle(bubble).getPropertyValue(`--aparte-avatar-image-${role}`).trim();\n return image !== '' && image !== 'none';\n });\n batch.forEach(([avatar], i) => avatar.toggleAttribute('data-image', shown[i]));\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 eight events are declared by hand rather than left to the analyser.\n *\n * The bubble element is `content-visibility: auto` for its streaming/scroll performance\n * (see `bubble.css`), and that containment makes the bubble\n * the containing block for `position: fixed` descendants: a popover mounted inside it\n * (an `<aparte-select>` from a `registerToolRenderer` renderer, say) is clipped to the\n * bubble's own box, not the window. Mount it outside instead — [Custom tool\n * renderer](https://apartejs.dev/guides/tools/#custom-tool-renderer) shows the pattern.\n *\n * @element aparte-chat-bubble\n *\n * @attr {string} data-role - The message role, `user` or `assistant` — the one channel for it, and what the CSS keys off. (`role` is ARIA's attribute; this element sets it to `article` itself and does not read a message role from it.)\n * @attr {string} content - Plain text content, for a bubble with no segments.\n * @attr {number | string} timestamp - Epoch milliseconds or a date string; a numeric string is read as milliseconds.\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 - Who wrote the message: shown in the header, and the message's accessible name (\"Message from {name}\"). Leave it out for the reader and the assistant, whose names come from the locale.\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 * @fires {CustomEvent<AparteLinkClickEventDetail>} aparte-link-click - A link in the message body is about to be followed. Cancelable: `preventDefault()` keeps the browser from navigating, so a host can route the link itself.\n *\n * @cssprop [--aparte-message-gap=var(--aparte-space-6)] - Gap between the avatar column and the body (the viewport reuses it between messages).\n * @cssprop [--aparte-message-padding-block=var(--aparte-space-8)] - Vertical padding of one message row.\n * @cssprop [--aparte-message-padding-inline=var(--aparte-space-6)] - Horizontal padding of one message row.\n * @cssprop [--aparte-message-max-width=800px] - Width of the centred message row.\n *\n * @cssprop [--aparte-message-content-radius=var(--aparte-radius-bubble)] - Radius of the painted content box.\n * @cssprop [--aparte-message-content-padding=var(--aparte-space-5) var(--aparte-space-7)] - Padding of the user's box.\n * @cssprop [--aparte-message-content-padding-assistant=0] - Padding of the assistant's box, none by default: its reply is plain prose across the column.\n * @cssprop --aparte-message-content-bg-user - Background of the user box: a wash of `--aparte-primary` over `--aparte-surface-1`, derived in `theme.css` so a rebrand moves it (declare it to override).\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 - Colour of the user's words. Unset by default: they keep `--aparte-content-color`.\n * @cssprop --aparte-message-content-text-assistant - Colour of the assistant's words. Unset by default: they keep `--aparte-content-color`.\n *\n * @cssprop [--aparte-avatar-size=32px] - Square size of the avatar slot.\n * @cssprop [--aparte-avatar-radius-ratio=0.28] - Avatar corner radius, as a fraction of its size (the corner follows the ramp).\n * @cssprop [--aparte-avatar-initials-ratio=0.34] - Size of the initial, as a fraction of the avatar's size.\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)] - Avatar text colour, assistant role.\n * @cssprop [--aparte-avatar-image-user=none] - `background-image` for the user avatar — a logo with no AvatarProvider: the slot shows when it names an image.\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=var(--aparte-font-size-md)] - Sender name in the header.\n * @cssprop [--aparte-name-color=var(--aparte-text)] - Sender name colour.\n * @cssprop [--aparte-timestamp-font-size=var(--aparte-font-size-sm)] - Timestamp in the header.\n * @cssprop [--aparte-timestamp-color=var(--aparte-text-muted)] - Timestamp colour.\n * @cssprop [--aparte-content-font-size=var(--aparte-font-size-base)] - 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=72px] - 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=var(--aparte-space-2)] - Gap between action buttons (and between the footer's two regions).\n * @cssprop [--aparte-action-bar-btn-size=var(--aparte-btn-size-sm)] - Square size of an action button (the recipe's small step); 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=var(--aparte-space-2)] - Gap between the arrows and the position label.\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=var(--aparte-font-size-sm)] - 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=var(--aparte-space-2)] - 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 * 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 * 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 implements AparteConfigAware {\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 /** Whether the avatar slot holds what a provider drew, which goes when the provider does. */\n private _avatarFromProvider = false;\n /** Sibling count for tree-based branch navigation (set by setSiblings()) */\n private _siblingCount = 1;\n /**\n * Which accessible names the bubble wrote itself, and so may refresh on a language switch\n * or a name change. A custom shell's own are its author's: refreshed, a root's or a bar's\n * label of their own became \"Assistant response\" or \"Message actions\" at the first notify.\n */\n private _wroteRootLabel = false;\n private _wroteBarLabel = false;\n private _wroteArrowLabels = false;\n /** The shell renderer the bubble was last drawn with (`null`: the default frame). */\n private _drawnShell: unknown = null;\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 * The `data-action` of the button the editor was opened from, so leaving it can hand\n * the keyboard back. The STRING, not the node: entering and leaving edit mode rewrite\n * the action bar's `innerHTML`, so the opener's identity is destroyed twice.\n */\n private _editReturnAction: string | null = null;\n\n static get observedAttributes(): string[] {\n // `data-role` is the ONE channel for the message role. `role` is ARIA's\n // attribute — this element writes `role=\"article\"` on itself in _render() —\n // and it used to be read as a legacy message-role channel too, which meant\n // filtering our own \"article\" back out at every turn. A rename lands as a\n // rename pre-1.0, so the overload is gone.\n return ['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 // Once, in a microtask: the usual setup sets the Markdown provider, its streaming twin\n // and a highlighter one after another, and each notifies — drawn on each, every message\n // on screen was parsed three times over.\n if (!this._redrawQueued) {\n this._redrawQueued = true;\n queueMicrotask(() => {\n this._redrawQueued = false;\n if (this.isConnected) this._redrawIfStale();\n });\n }\n };\n\n /** A {@link _redrawIfStale} is queued for the end of the current task. */\n private _redrawQueued = false;\n\n /** The {@link renderingVersionOf} count the content was last drawn with. */\n private _drawnWithVersion = 0;\n\n /**\n * A Markdown or highlight provider set after this message was drawn: draw it again.\n * Not mid-stream — the stream reads the providers as it goes, and its end redraws — and\n * not mid-edit, where the drawn message is hidden behind the editor and drawing it would\n * show it again beside the editor; leaving the edit calls this.\n */\n private _redrawIfStale(): void {\n if (this._streaming || this._editing || renderingVersionOf(this._cfg) === this._drawnWithVersion) return;\n if (this._segments.length > 0) this._renderSegments();\n else this._updateContent();\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 if (this._wroteArrowLabels) {\n set('.aparte-branch-prev', locale.previousResponse ?? 'Previous response');\n set('.aparte-branch-next', locale.nextResponse ?? 'Next response');\n }\n if (this._wroteBarLabel) 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 this.addEventListener('click', this._onLinkClick);\n }\n\n /**\n * The boundary above this bubble changed — under every wrapper the chat's own config is\n * attached AFTER the first messages are drawn. When that brings another frame\n * (`setBubbleShellRenderer`), the bubble is drawn again in it and its content put back,\n * so every message wears the chat's frame.\n *\n * Deferred to a microtask: a wrapper detaches the boundary as it unmounts (Vue's\n * `onBeforeUnmount`, Svelte's `onDestroy`), while the bubbles are still in the page, and\n * redrawing every one of them just before their removal would cost a frame per message.\n * After the microtask a removed bubble is no longer connected, and repeated notices fold\n * into one.\n */\n aparteConfigChanged(): void {\n queueMicrotask(() => this._redrawFrameIfChanged());\n }\n\n private _redrawFrameIfChanged(): void {\n if (!this.isConnected || this._editing) return; // an open editor is the reader's: caught up when it closes\n if ((this._cfg.getBubbleShellRenderer?.() ?? null) === this._drawnShell) return;\n if (this._avatarCleanup) {\n try { this._avatarCleanup(); } catch { /* ignore */ }\n this._avatarCleanup = null;\n }\n // The incremental Markdown parser is bound to the content element being replaced: left\n // bound, a stream would go on writing into the detached one and end blank.\n this._resetMarkdownStream();\n this.replaceChildren();\n this._render();\n this._updateContent();\n if (this._segments.length > 0) this._renderSegments();\n this._updateAttachments();\n this._updateTimestamp(this.getAttribute('timestamp'));\n this._updateBranchPicker();\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n this.removeEventListener('click', this._onBranchPickerClick);\n this.removeEventListener('click', this._onLinkClick);\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 'data-role':\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 this._syncCopyAction();\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 this._syncCopyAction();\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 this._syncCopyAction();\n // A call that settles is a step that ended: the dots may be due again.\n if ('status' in updates || 'isStreaming' in updates) this._updateWaiting();\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 this._syncCopyAction();\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 would 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 a decoy could point an update at the wrong\n * element and leave 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 this._syncCopyAction();\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 // Attachments are content: a bubble that is only attachments must not be `data-empty`.\n this._updateWaiting();\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 // Copy IN, exactly as `setSegments` does and for the same reason: the\n // viewport `Object.assign`s this very array into the repo message before\n // forwarding it here, so adopting it by reference leaves one array with two\n // writers — the doubling documented on `setSegments` above.\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 this._updateWaiting();\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 // The first segment of a message drawn piece by piece sets the count it is drawn with.\n if (this._segments.length === 1) this._drawnWithVersion = renderingVersionOf(this._cfg);\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)), 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)), 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 // The message role comes from `data-role` alone; the host's `role` attribute is\n // ARIA's and is set to a valid value here. \"user\" and \"assistant\" are not ARIA\n // roles and would trip accessibility tooling — which is exactly why the message\n // role never belonged on that attribute. The role-based styling lives on inner\n // `data-role` markers, so the swap is transparent to CSS.\n const dataRole = this.getAttribute('data-role');\n const role = (dataRole === 'user' || dataRole === 'assistant') ? dataRole : '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 this._drawnShell = shell ?? null;\n this._avatarFromProvider = false; // a new frame, a new slot\n // The default frame's names are all the bubble's; a custom one's, only those stamped below.\n this._wroteRootLabel = this._wroteBarLabel = this._wroteArrowLabels = !shell;\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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev\" aria-label=\"${escapeAttr(this._cfg.getLocale().previousResponse ?? 'Previous response')}\">${this._cfg.getIcon('prevBranch')}</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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next\" aria-label=\"${escapeAttr(this._cfg.getLocale().nextResponse ?? 'Next response')}\">${this._cfg.getIcon('nextBranch')}</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 // The shell contract lists CLASS hooks, so a shell written from it carries no ARIA\n // and the transcript loses one article and one accessible name per custom bubble —\n // measured on this repo's own documented shell. Re-applied here rather than asked of\n // the author: a markup contract that also demanded a `role` fails the first time\n // somebody forgets one, silently (decision #8 — honoured end to end). A root that\n // already declares a `role` keeps it, which is how a shell overrides the default.\n const messageEl = this.querySelector('.aparte-message');\n if (messageEl && !messageEl.hasAttribute('role')) {\n messageEl.setAttribute('role', 'article');\n messageEl.setAttribute('aria-label', this._getAriaLabel());\n this._wroteRootLabel = true;\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 // A custom shell's bar gets the roving tab stop below; it gets the toolbar it implies\n // too, as the root gets `article` above — the contract lists class hooks, so a shell\n // written from it carries no ARIA. A bar that declares its own role keeps it.\n if (this._actionBarEl && !this._actionBarEl.hasAttribute('role')) {\n this._actionBarEl.setAttribute('role', 'toolbar');\n if (!this._actionBarEl.hasAttribute('aria-label')) {\n this._actionBarEl.setAttribute('aria-label', this._cfg.getLocale().messageActions ?? 'Message actions');\n this._wroteBarLabel = true;\n }\n }\n // The branch arrows are icon buttons, and the contract asks no ARIA of a shell's author:\n // unnamed ones get the locale's names, and keep them through a language switch. A shell\n // that named either arrow keeps both as it wrote them — by an attribute, or by words in\n // the button, which a glyph like ‹ is not: a label stamped over a visible word is a name\n // the reader cannot see, and a voice command saying the word finds nothing.\n if (shell) {\n const prev = this.querySelector('.aparte-branch-prev');\n const next = this.querySelector('.aparte-branch-next');\n // Only words a reader can hear: an icon font's ligature or an SVG title under\n // `aria-hidden` is text in the DOM and no name at all.\n const audible = (arrow: Element): string => {\n let text = '';\n const walker = arrow.ownerDocument.createTreeWalker(arrow, NodeFilter.SHOW_TEXT);\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n const hidden = node.parentElement?.closest('[aria-hidden=\"true\"], [hidden]');\n if (!hidden || !arrow.contains(hidden)) text += node.textContent ?? '';\n }\n return text;\n };\n const named = (arrow: Element | null): boolean => !!arrow && (\n arrow.hasAttribute('aria-label') || arrow.hasAttribute('aria-labelledby') || arrow.hasAttribute('title')\n || /\\p{L}/u.test(audible(arrow)));\n if ((prev || next) && !named(prev) && !named(next)) {\n const locale = this._cfg.getLocale();\n prev?.setAttribute('aria-label', locale.previousResponse ?? 'Previous response');\n next?.setAttribute('aria-label', locale.nextResponse ?? 'Next response');\n this._wroteArrowLabels = true;\n }\n }\n // ONE listener on the bar, not one per button: every build path rewrites the bar's\n // innerHTML, so a per-button listener would have to be re-attached three times and\n // would be lost the first time somebody added a fourth path. The bar element itself\n // outlives every rewrite.\n this._actionBarEl?.addEventListener('keydown', this._onActionBarKeydown);\n this._branchPickerEl = this.querySelector('.aparte-branch-picker');\n this._footerEl = this.querySelector('.aparte-footer');\n\n // A bubble mounted while a reply already streams (a framework rendering the list\n // after the viewport flagged itself) starts in the busy state it would have been\n // pushed into.\n this._transcriptBusy = !!this.closest('aparte-chat-viewport')?.hasAttribute('data-busy');\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.\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 // A segment counts when it draws something: a call the app hides is in the message\n // but its row is an empty `hidden` placeholder, which is nothing for the reader.\n const shown = this._segmentsEl\n ? Array.from(this._segmentsEl.children).some((child) => !(child as HTMLElement).hidden)\n : this._segments.length > 0;\n const empty = !shown && !this._content.trim();\n // Between two steps: the last thing drawn is a call that has finished (or one the app\n // hides) and the next model turn has not written yet. The dots used to stop at the\n // first segment, so a long agent turn looked frozen after each \"Done\". Not while a\n // person is asked: nothing comes until they answer.\n const last = this._segments.at(-1);\n const lastEl = this._segmentsEl?.lastElementChild as HTMLElement | null | undefined;\n // A call waiting for approval is a person being asked — hidden or not, an empty reply\n // included: nothing comes until they answer.\n const asking = last?.type === 'tool_call' && (last as AparteToolCallSegment).status === 'awaiting-approval';\n const betweenSteps = last?.type === 'tool_call' && (isSegmentSettled(last) || lastEl?.hidden === true);\n const waiting = this._streaming && this._role !== 'user' && !asking && (empty || betweenSteps);\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 // Nothing to show and nothing coming: no chrome either. A turn whose only content\n // is a tool that renders nothing (an `ask_user` with no preamble), or one stopped\n // before its first token, used to leave a name and a timestamp floating over\n // nothing — an orphan header in the transcript. The stylesheet hides the whole\n // row on `data-empty`; attachments count as content, and a streaming bubble is\n // never empty (it shows the waiting dots).\n const message = this.querySelector('.aparte-message') as HTMLElement | null;\n if (message) {\n message.toggleAttribute('data-empty', empty && !this._streaming && this._attachments.length === 0);\n }\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 keeps what the shell rendered — for the default shell,\n * nothing, hidden while empty — and loses what a provider drew there before. An empty\n * slot still shows when the stylesheet names a picture for its role\n * (`--aparte-avatar-image-user` / `-assistant`): see `_markAvatarImage`.\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) {\n // `setAvatarProvider(null)`: what the provider drew goes with it. The slot returned\n // early here, so the old avatars stayed on every message already on screen.\n if (this._avatarFromProvider) {\n avatar.replaceChildren();\n this._avatarFromProvider = false;\n }\n this._markAvatarImage(avatar);\n return;\n }\n\n avatar.textContent = '';\n const cleanup = provider.render(this._role, avatar);\n this._avatarFromProvider = true;\n if (typeof cleanup === 'function') this._avatarCleanup = cleanup;\n // A provider may draw one role and leave the other's slot empty: the stylesheet's\n // picture still shows there.\n this._markAvatarImage(avatar);\n }\n\n /**\n * A picture from the stylesheet alone: an empty slot whose role's `--aparte-avatar-image-*`\n * names an image gets `data-image`, which lifts the rule that hides an empty slot. Hidden\n * while empty, the token painted nothing. Read when the message is drawn and on every\n * config change, in a batch with the other bubbles' (see `pendingAvatarMarks`).\n */\n private _markAvatarImage(avatar: HTMLElement): void {\n if (avatar.firstChild) {\n pendingAvatarMarks.delete(avatar);\n avatar.removeAttribute('data-image');\n return;\n }\n // Before the frame, with every other slot this task redraws: see `pendingAvatarMarks`.\n if (pendingAvatarMarks.size === 0) queueMicrotask(flushAvatarMarks);\n pendingAvatarMarks.set(avatar, { bubble: this, role: this._role });\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 // The accessible name follows the role only on a root whose role the bubble wrote.\n // A shell that declares its own `role` overrides the default (the shell contract\n // says so), and an override that kept the role and lost the name would be half\n // of one.\n if (this._wroteRootLabel && message.getAttribute('role') === 'article') {\n message.setAttribute('aria-label', this._getAriaLabel());\n }\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 && !this._avatarFromProvider) 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:not(:where([data-image]))\n * { display: 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 // Not over what a provider drew either: with the provider just cleared, that DOM is\n // still there until `_renderAvatar` runs its cleanup and empties the slot.\n if (avatar && avatar.textContent && !this._cfg.getAvatarProvider() && !this._avatarFromProvider) {\n avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) nameEl.textContent = this._getDisplayName();\n // The accessible name follows the author too — on a root whose role the bubble wrote,\n // the same rule as `_updateRole`.\n const message = this.querySelector('.aparte-message');\n if (this._wroteRootLabel && message?.getAttribute('role') === 'article') message.setAttribute('aria-label', this._getAriaLabel());\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 this._drawnWithVersion = renderingVersionOf(this._cfg);\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 this._drawnWithVersion = renderingVersionOf(this._cfg);\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)), 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 this._syncCopyAction();\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 /**\n * Named after its author when it has one: a `name` on the bubble is who wrote it (a human\n * agent answering on the assistant's side, a second assistant), and naming every one of\n * them \"Assistant response\" told a screen-reader user the opposite of what the page shows.\n * Without a name, the role names it.\n */\n private _getAriaLabel(): string {\n const locale = this._cfg.getLocale();\n const name = this.getAttribute('name');\n if (name) return (locale.messageFrom || 'Message from {name}').replace('{name}', () => name);\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/') && this._isShowableUrl(a.url)) {\n // `aparte-thumbnail` is the RECIPE (the box, the size, the ground); `aparte-thumb`\n // only maps the strip's measurements onto it. The bubble emitted the mapping\n // without the recipe, so its tiles had no box — a bare \"PDF\" beside a bare image.\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--image\" title=\"${name}\">`\n + `<img class=\"aparte-thumbnail__image\" src=\"${escapeHtml(a.url)}\" alt=\"${name}\" loading=\"lazy\" />`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--file\" title=\"${name}\">`\n + `<span class=\"aparte-thumbnail__label\">${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-thumbnail__image') 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 /**\n * The same answer `copyAttributes` gives before it writes an `src`, plus `blob:`.\n *\n * An attachment's `url` is documented as \"URL or data URI\" and a storage adapter\n * re-mints it when a conversation is restored, so it is the app's value, not the\n * composer's — and, like every other URL core emits, it is asked whether its SCHEME\n * is allowed at all. A picture whose URL is refused is not half-rendered: it falls\n * back to the file chip, which still names the file.\n *\n * `blob:` is added because it is the shape core itself mints — `filesToAttachments`\n * calls `URL.createObjectURL`, and `AparteMessage.attachments` documents the same\n * on hydration. `isSafeUrl` refuses it, and rightly: that list governs MODEL\n * markup, which has no way to make a blob URL resolve.\n */\n private _isShowableUrl(url: string | undefined): boolean {\n if (!url) return false;\n return isSafeUrl(url, 'img') || /^blob:/i.test(url.trim());\n }\n\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 * The listener lives on the host, which is never replaced, and `closest()` finds\n * whichever button exists at the moment of the click — so a click that lands while\n * a re-render is swapping the buttons is not lost.\n *\n * Bound in `connectedCallback` and removed in `disconnectedCallback` as a stable\n * field, so a re-connect never stacks listeners.\n */\n private _onBranchPickerClick = (event: Event): void => {\n // `aparte-branch-navigate` is dispatched from this arrow field, which the manifest\n // analyser (methods only) does not visit: it is declared by hand in the class `@fires`.\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 /**\n * A link the MODEL wrote is about to be followed. Announced as a cancelable event\n * before the browser acts, so a host can route it — an external browser, a\n * confirmation, an embedded view — without intercepting the DOM. With\n * no listener the browser follows it, which the sanitizer has already made open in\n * a new tab for an external URL; `preventDefault()` on the event cancels the click.\n *\n * Only anchors inside the message body: the action bar and the branch picker are\n * buttons, and an attachment tile is its own event.\n */\n private _onLinkClick = (event: Event): void => {\n const target = event.target as HTMLElement | null;\n const anchor = target?.closest?.('a[href]') as HTMLAnchorElement | null;\n if (!anchor || !this.contains(anchor)) return;\n const detail: AparteLinkClickEventDetail = {\n href: anchor.getAttribute('href') ?? '',\n anchor,\n messageId: this.getAttribute('message-id'),\n };\n const announced = this.dispatchEvent(new CustomEvent<AparteLinkClickEventDetail>('aparte-link-click', {\n bubbles: true,\n composed: true,\n cancelable: true,\n detail,\n }));\n if (!announced) event.preventDefault();\n };\n\n /**\n * Whether a reply is streaming somewhere in this bubble's transcript. Set by the\n * viewport (`data-busy` on it, pushed here), read once on connect for a bubble\n * mounted while the flag was already up. While busy, the branch arrows and the\n * retry/edit actions are disabled — the transcript is read-only except for Stop.\n */\n private _transcriptBusy = false;\n\n setTranscriptBusy(busy: boolean): void {\n if (this._transcriptBusy === busy) return;\n this._transcriptBusy = busy;\n this._applyTranscriptBusy();\n this._updateBranchPicker();\n }\n\n private _applyTranscriptBusy(): void {\n if (!this._actionBarEl) return;\n for (const btn of this._actionBarEl.querySelectorAll<HTMLButtonElement>('[data-action=\"retry\"], [data-action=\"edit\"]')) {\n btn.disabled = this._transcriptBusy;\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._transcriptBusy || this._siblingIndex === 0;\n if (nextBtn) nextBtn.disabled = this._transcriptBusy || this._siblingIndex === this._siblingCount - 1;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Action Bar\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * What the copy action copies: the reply, not the reasoning. A `thinking` segment is\n * the model's scratchpad, and the client already keeps it out of the history it sends\n * back (`_segmentsToText`) for the same reason — copying it along with the answer\n * pasted a paragraph of deliberation above every reply, which no assistant on the\n * market does. Segments with no `content` (a tool call) contribute nothing, so a\n * turn made only of those copies as `''` — and the action bar reads this to decide\n * whether to offer the button at all.\n */\n private _copyableText(): string {\n return (this._content || this._segments\n .filter(s => s.type !== 'thinking')\n .map(s => (s as { content?: string }).content ?? '')\n .join('\\n')).trim();\n }\n\n /**\n * The copy button is for text. `hidden` rather than removed: the bar is rebuilt\n * rarely and the text changes on every streamed token, so the button is toggled in\n * place from each path that changes what a copy would paste.\n */\n private _syncCopyAction(): void {\n const btn = this._actionBarEl?.querySelector<HTMLElement>('.aparte-action-copy');\n if (btn) btn.hidden = this._copyableText() === '';\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. (Copy is rendered here\n // and HIDDEN by `_syncCopyAction` while there is no text to copy — a tool-only\n // turn used to offer a button that copied the empty string and said \"Copied\".)\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 this._syncCopyAction();\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();\n this._applyTranscriptBusy();\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 * `role=\"toolbar\"` promises a keyboard model. This is it.\n *\n * A toolbar is ONE tab stop whose members are reached with the arrow keys — that is\n * the whole of what the role means to a screen-reader user. Without it, tabbing\n * through a long transcript would walk every button of every message.\n *\n * The roving index is re-applied from `_syncFooterVisibility`, which is the single\n * funnel all three build paths run through (the flag-driven bar, the edit bar, and\n * the branch picker's own visibility pass). Re-deriving it there rather than in each\n * builder keeps the invariant true after a `setBubbleActions` rebuild.\n *\n * DISABLED buttons are skipped, not merely un-tabbable: while the transcript is busy\n * retry and edit are disabled, and a toolbar whose arrows stop on a dead control\n * reads as broken. If every button is disabled the bar keeps one tab stop anyway, so\n * focus sitting there when the turn ends is not thrown to the top of the page.\n */\n private _rovingButtons(): HTMLButtonElement[] {\n if (!this._actionBarEl) return [];\n return [...this._actionBarEl.querySelectorAll<HTMLButtonElement>('button')];\n }\n\n private _setRovingIndex(): void {\n const buttons = this._rovingButtons();\n if (!buttons.length) return;\n const usable = buttons.filter((b) => !b.disabled);\n const current = buttons.find((b) => b.tabIndex === 0 && !b.disabled);\n const stop = current ?? usable[0] ?? buttons[0];\n for (const button of buttons) button.tabIndex = button === stop ? 0 : -1;\n }\n\n /**\n * Left/Right walk the bar, Home/End jump to its ends, and both wrap.\n *\n * Arrow keys follow the READING direction, per the ARIA practices: in an RTL\n * transcript Left is \"next\". `dir` is resolved from the nearest ancestor that sets\n * it — the viewport writes it from `locale.direction` — because a bubble has none of\n * its own and `getComputedStyle` is not something jsdom can answer.\n */\n private _onActionBarKeydown = (e: Event): void => {\n const event = e as KeyboardEvent;\n const keys = ['ArrowLeft', 'ArrowRight', 'Home', 'End'];\n if (!keys.includes(event.key)) return;\n const buttons = this._rovingButtons().filter((b) => !b.disabled);\n if (buttons.length === 0) return;\n const active = document.activeElement as HTMLElement | null;\n const from = buttons.findIndex((b) => b === active);\n if (from === -1 && event.key !== 'Home' && event.key !== 'End') return;\n const rtl = (this.closest('[dir]')?.getAttribute('dir') ?? document.documentElement.dir) === 'rtl';\n const forward = rtl ? 'ArrowLeft' : 'ArrowRight';\n let next: number;\n if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = buttons.length - 1;\n else if (event.key === forward) next = (from + 1) % buttons.length;\n else next = (from - 1 + buttons.length) % buttons.length;\n event.preventDefault();\n for (const button of this._rovingButtons()) button.tabIndex = button === buttons[next] ? 0 : -1;\n buttons[next]?.focus();\n };\n\n /**\n * An empty action bar is not a bar: with every action off it would be a\n * `role=\"toolbar\"` with nothing in it (announced as such) that still reserves its\n * 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 this._setRovingIndex();\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 // The stylesheet floats an older message's footer out of the flow so that it\n // reserves no row — except while the branch picker shows, which has to stay in\n // the flow and in view. The picker's visibility is decided here, so the flag that\n // CSS reads is written here too.\n this.querySelector('.aparte-message')?.toggleAttribute('data-branches', !pickerHidden);\n }\n\n /** Append the registered custom action buttons for this bubble's role. */\n private _appendCustomActions(): 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.type = 'button';\n btn.className = 'aparte-btn aparte-btn--icon aparte-btn--sm 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 btn.innerHTML = a.icon.startsWith('<')\n ? a.icon\n : (this._cfg.getIcon(a.icon) ?? 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm 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 type=\"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 type=\"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._copyableText();\n const icons = this._cfg.getIconProvider();\n const locale = this._cfg.getLocale();\n copyText(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 // Read the opener BEFORE the action bar is rebuilt — after it, the button is gone.\n // Nothing is remembered when the focus is outside this bubble: pulling it back from\n // wherever the reader actually is would be theft, not a restore.\n const opener = document.activeElement;\n this._editReturnAction =\n opener instanceof HTMLElement && this.contains(opener)\n ? (opener.getAttribute('data-action') ?? 'edit')\n : null;\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 // A frame change that arrived while the editor was open is drawn now.\n this._redrawFrameIfChanged();\n this._redrawIfStale();\n this._updateActionBar();\n\n // Both exits destroy the focused element — the editor is removed and the bar is\n // rebuilt — so without this the keyboard lands on `<body>` and the next Tab\n // restarts at the top of the page. Matched by attribute rather than by selector:\n // a custom action's id (`custom:…`) is a consumer string.\n const returnTo = this._editReturnAction;\n this._editReturnAction = null;\n if (returnTo && this._actionBarEl) {\n const back = Array.from(this._actionBarEl.querySelectorAll<HTMLButtonElement>('[data-action]'))\n .find((btn) => btn.getAttribute('data-action') === returnTo);\n // The remembered action can come back DISABLED (the reader sent from the composer\n // mid-edit, so the transcript is busy) or gone (it was turned off while editing).\n // `focus()` on a disabled button is a no-op, which drops the reader on `<body>` —\n // the very bug this restore exists for, silently. So fall back to the bar's first\n // enabled button.\n const target = back && !back.disabled\n ? back\n : this._actionBarEl.querySelector<HTMLButtonElement>('button:not([disabled])');\n // The bar is one tab stop that the arrows move (`_onActionBarKeydown`), and\n // `_updateActionBar` just parked it on the first button. Focusing without moving\n // the stop leaves the reader on a `tabindex=\"-1\"` member of a `role=\"toolbar\"`:\n // Shift+Tab out and Tab back returns them to copy, not to where they were.\n if (target) {\n for (const button of this._rovingButtons()) button.tabIndex = button === target ? 0 : -1;\n target.focus();\n }\n }\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' || 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 /*\n * Streaming just finished — the SETTLE pass.\n *\n * `_updateContent` is what runs it: on `isStreaming === false`\n * `writeStreamedMarkdown` flushes the incremental parser and then either\n * re-renders once through the one-shot provider, whose output goes through\n * `sanitizeHtml`, or — when no one-shot provider is registered — re-sanitises\n * the flushed DOM in place. Either way the settled message is what\n * `sanitizeHtml` produced, which is the reason the streaming provider is\n * allowed to write DOM directly — and this branch never called it, so a streamed\n * `<code class=\"aparte-btn\">` was permanent rather than transient. The live\n * end-of-turn call carries no content (`completeMessage` sends\n * `{ status: 'completed' }` alone), so re-applying `_content` here is the only\n * place it can happen.\n *\n * It also highlights on its way out (it is no longer streaming), which is what\n * this line used to do on its own. A segment-bearing bubble is a no-op there —\n * the content element is hidden and empty — which is what it was for the\n * highlight too.\n */\n if (wasStreaming && !streaming) this._updateContent();\n // Segments have no such pass: if a Markdown or highlight provider arrived while they\n // streamed, the finished message is drawn again with it.\n if (wasStreaming && !streaming && this._segments.length > 0\n && renderingVersionOf(this._cfg) !== this._drawnWithVersion) {\n this._renderSegments();\n }\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 locale's `typing` word\n * (\"Typing\" in English). The container is a polite live region, and a live\n * region announces its CONTENT, not its `aria-label`: so the word also goes INTO the\n * region, in the visible span when `text` is set and in a screen-reader-only span when\n * it is not. Exactly one of the two is ever populated — both would be read twice. That\n * fallback word is driven by `visible`, not written once at render: a live region is\n * announced when its content CHANGES while it is exposed, and every wrapper mounts this\n * element hidden and flips the attribute — so a word written at render time is a word\n * the region already held when it appeared, the reveal-from-`display: none` path, which\n * is the one screen readers are documented not to announce reliably.\n * 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-block` and\n * `--aparte-message-padding-inline` on `.aparte-message` itself, so a declaration on\n * this host element loses to them 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 locale's `typing` word (\"Typing\" in English).\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=var(--aparte-font-size-md)] - 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-block=var(--aparte-space-8)] - Vertical 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-padding-inline=var(--aparte-space-6)] - Horizontal padding of the row, same reason.\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 /** The word said when no `text` is set: the locale's `typing`, never an English literal. */\n private _word(): string {\n return this._cfg.t('typing');\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') || this._word();\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 <span class=\"aparte-sr-only aparte-status-sr\"></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 this._writeText(this.getAttribute('text'));\n }\n\n /**\n * The one writer for both text nodes, so exactly ONE of them is ever populated.\n *\n * The container is a polite live region, and a live region announces its CONTENT.\n * The dots-only default had none — an `aria-hidden` dot and an empty span — so the\n * whole state rode on `aria-label`, which names the region rather than reporting it,\n * and nothing was announced. The screen-reader span carries the fallback word in that\n * case; when `text` is set the visible span already carries the same string, and\n * populating both would have the region read it twice.\n */\n private _writeText(text: string | null): void {\n // `text=\"\"` is the dots-only default too — and it is the reading the post-mount\n // path already took, where mounting with an empty attribute used to print the\n // literal `Typing` on screen. One writer, one answer.\n const line = text || null;\n const textEl = this.querySelector('.aparte-status-text');\n if (textEl) textEl.textContent = line ?? '';\n this._syncLiveWord();\n }\n\n /**\n * The fallback word enters and leaves the live region WITH the element's visibility.\n *\n * Writing it at render time put it in the region while the host was still\n * `display: none` — every wrapper mounts `<aparte-chat-status>` once and flips\n * `visible` — so by the time the region was exposed its content had not changed, and\n * on the second and third turn there was not even a reveal to notice: the text was\n * byte-identical to what was already sitting there. Revealing a region that already\n * holds its text is the path assistive tech is documented not to announce reliably;\n * mutating one that is already exposed is the path that works. So the word is written\n * when `visible` arrives and cleared when it leaves, which makes every turn a real\n * change.\n *\n * With `text` set the visible span is the region's content and the screen-reader span\n * stays empty — one copy, read once.\n */\n private _syncLiveWord(): void {\n const srEl = this.querySelector('.aparte-status-sr');\n if (!srEl) return;\n const dotsOnly = !this.getAttribute('text');\n srEl.textContent = dotsOnly && this.hasAttribute('visible') ? this._word() : '';\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 // The word is the news, and news is only heard while the region is on screen.\n this._syncLiveWord();\n }\n\n private _updateText(text: string | null): void {\n const container = this.querySelector('.aparte-status-container');\n if (!container) return; // not rendered yet — _render() reads the attribute\n // A custom renderer drew the inside from the text it was given, and has no text\n // element of ours to write into: it is asked again with the new text, as at render.\n const custom = this._cfg?.getStatusRenderer?.();\n if (custom) {\n const next = text || this._word();\n container.setAttribute('aria-label', next);\n const result = runWithConfig(this._cfg, () => custom(next));\n if (result instanceof HTMLElement) container.replaceChildren(result);\n else container.innerHTML = result;\n return;\n }\n // Removing the attribute restores the dots-only default (no visible text, the\n // fallback word in the live region); the aria-label always keeps an accessible name.\n this._writeText(text);\n container.setAttribute('aria-label', text || this._word());\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';\n// From the module rather than the barrel: `types/index.ts` re-exports TYPES only, and\n// this is a value — the marker `withParsedSegments` stamps on what it derived.\nimport { APARTE_DERIVED_SEGMENTS } from '../../types/models.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 { parseMarkdownToSegments } from '../../parsers/index.js';\nimport type { AparteStreamBlock } from '../../types/stream-blocks.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { isAwaitingReply } from '../../utils/is-awaiting-reply.js';\nimport { presenceOn } from '../../utils/presence.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 * An assistant reply handed over as a markdown string, split the way the stream would\n * have split it — fences to `code` segments, `<think>` to a reasoning block, the\n * registered stream blocks to theirs. Anything else comes back untouched: a message\n * that already carries segments, a person's message (their fences are their own), an\n * empty or multimodal content, and plain prose with nothing to split, which keeps the\n * cheaper content path.\n *\n * What it derived, it MARKS — see {@link markDerivedSegments}.\n */\nfunction withParsedSegments(message: AparteMessage, blocks: AparteStreamBlock[]): AparteMessage {\n if (message.role !== 'assistant' || message.segments?.length) return message;\n if (typeof message.content !== 'string' || !message.content.trim()) return message;\n const segments = parseMarkdownToSegments(message.content, { blocks });\n const onlyProse = segments.length <= 1 && segments.every((s) => s.type === 'text');\n return onlyProse ? message : markDerivedSegments({ ...message, segments });\n}\n\n/**\n * Say that THIS array of segments is core's reading of the message's own `content`,\n * not something the host wrote.\n *\n * Opening a stored conversation runs the parser over every markdown reply, so\n * `getMessages()` can differ from what the store holds. The marker lets the manager's\n * \"did anything change?\" comparison ignore a derived array, so merely reading a\n * conversation does not bump `updatedAt` or rewrite the stored row.\n *\n * Non-enumerable on purpose: a spread, `JSON.stringify` and a storage adapter never\n * carry it, so nothing of this reaches the wire. Which is also why every place that\n * copies a marked message has to put it back — {@link adoptMessageSegments} and the\n * stamping spread below both return a fresh object.\n */\nfunction markDerivedSegments<T extends object>(message: T): T {\n Object.defineProperty(message, APARTE_DERIVED_SEGMENTS, { value: true, enumerable: false });\n return message;\n}\n\n/** True when {@link withParsedSegments} is the author of this message's segments. */\nfunction hasDerivedSegments(message: AparteMessage): boolean {\n return (message as unknown as Record<symbol, unknown>)[APARTE_DERIVED_SEGMENTS] === true;\n}\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 {boolean} loading - The transcript is on its way: a conversation was chosen and its messages\n * are being fetched. The element draws two skeleton turns, marks its scroll surface\n * `aria-busy` and names the wait for a screen reader; `aparte-chat[center-empty]`\n * does not count a loading viewport as empty. Set by `AparteConversationController`\n * through its binding, or by hand from a loop of your own (`setLoading(true)`).\n * @attr {boolean} data-busy - Reflected BY the element while a turn streams: the transcript is\n * read-only meanwhile, and every bubble inside reads it (at connect, and when it changes). The\n * vanilla path derives it from the repository; a framework host sets it through\n * `setTranscriptBusy()`. Read-only from the outside.\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=var(--aparte-space-8)] - 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=var(--aparte-space-6)] - 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-thumb=var(--aparte-neutral)] - Colour of the transcript's scrollbar thumb. A host page with a scrollbar of its own sets this and the track so the chat's does not read as a second, foreign scrollbar.\n * @cssprop [--aparte-scrollbar-track=transparent] - Colour of the transcript's scrollbar track.\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-transcript-inset=var(--aparte-viewport-padding)] - Written BY the viewport on\n * the chat host: how far from the host's inline edge its rows start (padding plus the\n * scrollbar gutter, at the current container step). The composer pads by it, so the two\n * boxes share one edge at every width. Read-only from the outside.\n * @cssprop [--aparte-top-inset=0px] - How much of the transcript's top is covered by a header floating over it.\n * Set it on the chat, by hand: the first message starts below it, and a jump to a message lands below it.\n * @cssprop [--aparte-bottom-inset=0px] - How much of the transcript's bottom is covered\n * by content floating over it. Written by the viewport itself under\n * `[overlay-composer]` (never set it there — it would be overwritten); a host that\n * overlays a composer of its own, without the attribute, sets it by hand and the\n * spacer, the container padding and the scroll button all clear it.\n * @cssprop [--aparte-scroll-btn-size=var(--aparte-btn-size-lg)] - 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: 24rem\"></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 /** The scroll height at the last scroll event: how much it moved since bounds what churn can do. */\n private _lastScrollHeight = 0;\n /** When the transcript last mutated under our observer — an engine's settle adjustment follows one. */\n private _lastMutationAt = Number.NEGATIVE_INFINITY;\n /** How long after a mutation an engine's settle adjustment can arrive, and how far it moves. */\n private readonly _settleShadowMs = 100;\n private readonly _settleBoundPx = 100;\n /** When this component last moved `scrollTop` itself — see `_handleScroll`. \"Never\" is\n * -Infinity rather than 0: `performance.now()` can be under a second old. */\n private _ownScrollAt = Number.NEGATIVE_INFINITY;\n /** When the reader last touched the transcript (wheel, touch, pointer, key). */\n private _readerInputAt = Number.NEGATIVE_INFINITY;\n /** A decrease inside this window after our own scroll, with no gesture, is the browser's. */\n private readonly _ownScrollWindowMs = 1000;\n /** How long after a scroll of ours we keep re-anchoring while the layout churns.\n * Not `readonly`: a test shortens it to run past the window's end in a few frames. */\n private _settleWindowMs = 400;\n /**\n * Until when a smooth scroll of ours is in flight. A user send glides to the\n * bottom with `scrollTo({ behavior: 'smooth' })`, and the assistant placeholder and\n * first tokens land inside that glide. An instant `scrollTop = scrollHeight` write\n * cancels a running smooth scroll in every engine, so while this deadline is ahead\n * every bottom-pin RE-TARGETS the glide (a second smooth `scrollTo` is continuous)\n * instead of writing `scrollTop`; the browser's `scrollend` closes it early where\n * supported. Streaming is instant after that, as it must be to keep up with token\n * bursts.\n */\n private _glideUntil = 0;\n /** The glide's budget when `scrollend` is not there to close it: Chromium's smooth scroll over a viewport takes ~300–500 ms. */\n private _glideWindowMs = 450;\n /** Armed with each glide: when the window closes, a view still short of the bottom is pinned. */\n private _glideSettleTimer: ReturnType<typeof setTimeout> | null = null;\n /** Deadline of the settle in flight (0 = none). Pushed forward, never stacked. */\n private _settleUntil = 0;\n private _settleRafId: number | null = null;\n /** The keys that scroll a focused scroll container — the only keydowns that are a gesture. */\n private readonly _scrollKeys = new Set(['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' ']);\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 private _resizeObserver: ResizeObserver | null = null;\n private _mutationObserver: MutationObserver | null = null;\n /**\n * Watches the size of the bubbles the bottom spacer measures — the last question and\n * everything after it. A bubble that has never been on screen measures its\n * `content-visibility` fallback (320px) until it is shown, and taking its real height\n * is neither a mutation nor a resize of the scroll container: without this, a spacer\n * sized from two fallbacks stayed wrong once they came into view.\n */\n private _tailObserver: ResizeObserver | null = null;\n private _tail: Element[] = [];\n /** Each tail bubble's border-box height, as measured when it started being watched and as last reported. */\n private _tailHeights = new WeakMap<Element, number>();\n /**\n * Opening or closing a `<details>` — a tool row, a reasoning block, an artifact card, a host's\n * own — changes the transcript's height through an ATTRIBUTE, which neither observer sees: the\n * mutation observer watches children, and the resize observer leaves the wrapper out on\n * purpose. `toggle` does not bubble, so it is caught on the way down.\n */\n private _onDisclosureToggle = (): void => { this._scheduleSpacerUpdate(); };\n /** The `[overlay-composer]` shell this viewport floats in, if any. Read at observer setup, like `framework-managed`. */\n private _overlayRoot: HTMLElement | null = null;\n /** Watches the overlay shell's childList, so a stack child that mounts later (elicitation) gets observed too. */\n private _overlayObserver: MutationObserver | null = null;\n /** Last `--aparte-bottom-inset` written, in px — style writes only when it changes. */\n private _overlayInset = -1;\n /** Last `--aparte-transcript-inset` written on the host, in px — same write-on-change rule. */\n private _transcriptInset = -1;\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', 'loading'];\n }\n\n /**\n * The transcript is on its way: a conversation was chosen and its messages are\n * being fetched. Two skeleton turns stand where they will be, the scroll surface\n * is `aria-busy`, and a visually hidden line names the wait. Reflected as the\n * `loading` attribute — `AparteConversationController` sets it through its\n * binding; a loop of your own sets it by hand. Not the same thing as `data-busy`,\n * which is a reply streaming into a transcript that is already there.\n */\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(value: boolean) {\n // `presenceOn`, like every other boolean property in core: `''` is the ON that\n // React stringifies and Svelte 5 assigns, and `toggleAttribute` reads it as\n // falsy — the documented spelling turned the wait off.\n this.toggleAttribute('loading', presenceOn(value));\n }\n\n setLoading(loading: boolean): void {\n this.loading = loading;\n }\n\n /** Draw or remove the wait, from the attribute. Safe before the structure exists. */\n private _syncLoading(): void {\n const container = this._container;\n if (!container) return;\n const on = this.hasAttribute('loading');\n container.setAttribute('aria-busy', on ? 'true' : 'false');\n // Under `framework-managed` the host is the surface and its children are the\n // wrapper's to reconcile: the attribute and aria-busy say the transcript is on\n // its way, the wrapper draws what that looks like.\n if (this._frameworkManagedDOM) return;\n const existing = container.querySelector(':scope > .aparte-viewport-loading');\n if (!on) {\n existing?.remove();\n container.querySelector(':scope > .aparte-viewport-loading-status')?.remove();\n return;\n }\n if (existing) return;\n const cfg = resolveConfig(this);\n const skeleton = document.createElement('div');\n skeleton.className = 'aparte-viewport-loading';\n skeleton.setAttribute('aria-hidden', 'true');\n // Two turns: a short bubble on the end edge for the person, a few lines for the\n // reply — the kit's own skeleton recipe, sized by its tokens.\n skeleton.innerHTML =\n '<span class=\"aparte-skeleton aparte-skeleton--rect aparte-viewport-loading__user\"></span>'\n + '<span class=\"aparte-skeleton aparte-skeleton--text\"></span>'\n + '<span class=\"aparte-skeleton aparte-skeleton--text\"></span>'\n + '<span class=\"aparte-skeleton aparte-skeleton--text\"></span>'\n + '<span class=\"aparte-skeleton aparte-skeleton--text aparte-viewport-loading__last\"></span>';\n // Screen readers ignore aria-busy alone: the wait is also a line of text, off screen.\n const status = document.createElement('span');\n status.className = 'aparte-viewport-loading-status aparte-sr-only';\n status.setAttribute('role', 'status');\n // A sibling, not a child: inside the aria-hidden skeleton it would be hidden too.\n container.prepend(skeleton, status);\n // The text lands AFTER the node does. A live region created with its content\n // already in it is a new node, not a change — several screen readers announce\n // nothing at all. One frame is enough for the region to be registered; if the\n // wait ends first the node is gone and there is nothing to say.\n const say = (): void => { if (status.isConnected) status.textContent = cfg.t('loadingConversation'); };\n if (typeof requestAnimationFrame === 'function') requestAnimationFrame(say);\n else say();\n }\n\n constructor() {\n super();\n this._handleScroll = this._handleScroll.bind(this);\n this._handleScrollEnd = this._handleScrollEnd.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._updateTranscriptInset();\n /**\n * Empty every mounted transcript on the page.\n *\n * A command, not a notification: this is one of the events that travel the\n * other way, dispatched by YOUR app on `window` and listened for here. Every\n * connected `<aparte-chat-viewport>` clears its repository and its DOM and\n * answers with `aparte-reset-done`, so a \"New chat\" button in a shell that\n * holds no reference to the transcript still empties it. Carries no detail —\n * and therefore no target, so it clears every viewport, not one.\n *\n * @event aparte-reset\n */\n // History, deliberately OUT of the JSDoc: `gen-events-ref.mjs` copies an\n // `@event` body verbatim onto the public page, and this is repo business.\n // The listener had been live and undocumented since it existed — absent from\n // the events reference (the generator's name union read the dispatch sites\n // only, and core never dispatches this one), absent from the manifest (still\n // is, correctly: the element does not FIRE it), and covered by no test. A\n // listener that could have been deleted in silence.\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` follows it, so a chat\n * already on screen flips to RTL without 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 // The button's accessible name is locale text too — a language switch is\n // documented as live, and this was the one chrome string that stayed put.\n this._scrollBtn?.setAttribute('aria-label', resolveConfig(this).t('scrollToBottom'));\n // The transcript's own name is locale text on the same terms.\n const surface = this._frameworkManagedDOM ? this : this.querySelector('.aparte-viewport-container');\n surface?.setAttribute('aria-label', resolveConfig(this).t('transcript'));\n };\n\n /**\n * The transcript is a TAB STOP, with a name.\n *\n * A scrollable region that no element can hold focus in cannot be scrolled from\n * the keyboard in WebKit: Chromium and Firefox hand an unfocusable overflow box a\n * caret or a \"scroller\" tab stop of their own, Safari does not. So a plain-text\n * transcript — no links, no code blocks, nothing focusable inside — would be\n * unreadable past the first screen for a Safari keyboard user, with no error and\n * nothing on screen to say so.\n *\n * `tabindex=\"0\"` on the surface itself, in BOTH DOM modes. In framework mode the\n * scroll button is inside the host and stays tabbable while it is visually hidden,\n * but a tab stop that exists because a hidden button happens to sit there is a\n * coincidence, not an affordance.\n *\n * The name ships WITH the tab stop rather than after it: a focusable `role=\"log\"`\n * with no accessible name is announced as an unnamed region, which is a worse\n * answer than no tab stop at all. And the ROLE ships with both: a name on a\n * generic element is the same defect mirrored — `aria-label` is prohibited on an\n * element whose role resolves to none, which is what a bare custom element is. The\n * default mode already writes `role=\"log\"` on the container before this runs, so\n * the guard here is what gives the framework-mode host the same declaration.\n */\n private _nameScrollSurface(el: HTMLElement): void {\n el.setAttribute('tabindex', '0');\n if (!el.hasAttribute('role')) el.setAttribute('role', 'log');\n el.setAttribute('aria-label', resolveConfig(this).t('transcript'));\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 'loading':\n this._syncLoading();\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.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 /**\n * The id of the message currently streaming, if any — searched across the whole\n * TREE, and answering with an OFF-HEAD one first when there are two.\n *\n * Not a scan of `getMessages()`, the active path: a retry or an edit on an\n * earlier bubble calls `addSiblingOf`, which switches the branch to the new pending\n * reply, so the turn still being written leaves the path and a path scan would find\n * nothing, appending the rest of that turn to the message that just replaced it.\n *\n * A new pending reply is itself marked streaming, so \"the first one found\" would\n * answer the head and let the write through — the very case this exists to refuse.\n * Answering the off-head one instead makes `_activeMessageId` refuse while ANY turn\n * elsewhere in the tree is still open. That also refuses the new turn's own first\n * deltas for as long as the superseded one has not settled, which is the trade this\n * whole path already makes: losing the tail is visible, writing it onto someone\n * else's message is not.\n */\n private _streamingMessageId(): string | null {\n const head = this._repo.headId;\n let atHead: string | null = null;\n for (const message of this._repo.getAllMessages()) {\n if (!(message as { isStreaming?: boolean }).isStreaming) continue;\n if (message.id !== head) return message.id;\n atHead = message.id;\n }\n return atHead;\n }\n\n /**\n * While a reply streams, the transcript is read-only except for Stop.\n *\n * The streaming message's own footer is hidden, and the branch picker and the\n * retry/edit buttons of every OTHER message are disabled: swapping a branch mid-stream would\n * re-render the active path under the reply being written, and a retry would cut\n * that reply off to start another. The state that\n * decides it is the whole transcript's, so it lives on the viewport (`data-busy`)\n * and is pushed to the bubbles it holds; a bubble mounted later, under a\n * framework's DOM, reads the attribute itself when it connects.\n */\n private _syncBusy(): void {\n // One writer per mode. Under a framework the repository is not written during\n // a turn, so deriving it here could only ever say \"not busy\" — and did, under\n // all four wrappers: the host, which knows, writes it through\n // `setTranscriptBusy` instead, and this must not overwrite it from the repo.\n if (this._frameworkManagedDOM) return;\n this.setTranscriptBusy(this._streamingMessageId() !== null);\n }\n\n private _busy = false;\n\n /**\n * The transcript's read-only-while-streaming flag — `data-busy` on this element and\n * fanned out to the bubbles it holds. The vanilla path derives it from the\n * repository (`_syncBusy`); a framework host, whose messages live outside the\n * repository during a turn, writes it directly from its own streaming id.\n */\n setTranscriptBusy(busy: boolean): void {\n this._busy = busy;\n this.toggleAttribute('data-busy', busy);\n for (const bubble of this.querySelectorAll('aparte-chat-bubble')) {\n (bubble as unknown as { setTranscriptBusy?: (busy: boolean) => void }).setTranscriptBusy?.(busy);\n }\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 can be driven by `AparteClient`.\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._syncBusy();\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 * An id the tree does not hold is a silent no-op, deliberately: the message may have\n * been truncated away by an edit while a turn was still writing to it, and inventing\n * it back would put a message the reader deleted on screen. It does mean a late error\n * card can have nowhere to land — `aparte-message-error` still fires, so a host that\n * wants to show one has the event.\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 if (updates.status) this._syncBusy();\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 const stored = adoptMessageSegments({ ...message });\n // Same rule as `appendMessage`: a message that ARRIVES streaming (a pending\n // shell, a reply the host is about to write into) is a streaming one in the\n // tree, or the manual-stream path — which used to invent this message with\n // `isStreaming: true` — would stop making the transcript read-only.\n if (isAwaitingReply(message)) stored.isStreaming = true;\n this._repo.addOrUpdateMessage(this._repo.headId, stored);\n this._pruneRenderedBubbles();\n this._syncBusy();\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 /*\n * One grammar, both paths. A reply that arrives as a markdown STRING — from a\n * loop of your own, a store's `setMessages`, an `importTree` — went to the prose\n * renderer as it was, and the fence the stream would have made a `code` segment\n * (the card, the filename, the copy button) came out as a bare <pre>; a\n * `<think>` stayed text. The guide even told the consumer to run\n * `parseMarkdownToSegments` by hand first. Core knows the grammar: the same\n * parser the stream uses, with the same registered blocks, runs here. Only an\n * assistant's text — a person's fences are their own — and only when there is\n * something to split: plain prose keeps the cheaper content path.\n */\n message = withParsedSegments(message, resolveConfig(this).getStreamBlocks());\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 // The model's own flag, from the same predicate the bubble's `streaming`\n // attribute uses below. `updateMessage` maps a status to it; a message that\n // ARRIVES streaming (the client appends a pending shell, a host appends a\n // reply it is about to write into) used to reach the repository without it,\n // so `_streamingMessageId()` — and everything read-only that hangs off it —\n // could not see a turn that had not yet been updated once. Not for a\n // historical message: a conversation saved mid-reply is a record, not a turn.\n if (!options?.historical && isAwaitingReply(message)) stored.isStreaming = true;\n // Both branches above copy the object, and the marker is non-enumerable, so it\n // has to be put back on the copy the repository will hold — `getMessages()`\n // hands that very object to the store's comparison.\n if (hasDerivedSegments(message)) markDerivedSegments(stored);\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('data-role', message.role);\n if (message.compaction) bubble.setAttribute('data-kind', 'compaction');\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._syncBusy();\n // User sending always anchors to bottom regardless of scroll position — and it\n // GLIDES there. The glide begins before the spacer recalculation below, because\n // that recalculation pins the bottom synchronously: started after it, the smooth\n // scroll found the view already teleported (#57). Streaming auto-scroll stays\n // instant (via _autoScroll) so it can keep up with rapid token bursts — once the\n // glide has ended; inside it, every pin re-targets the glide.\n if (message.role === 'user') {\n this._isAutoScrollEnabled = true;\n this._smoothScrollToBottom();\n }\n this._recalculateSpacer();\n if (message.role === 'user') {\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) 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 // A retry starts a turn here: the sibling arrives pending, and the model's\n // flag has to say so from this moment (see `appendMessage`), not from the\n // first `updateMessage` — the rebuild below already pushes the busy state.\n const stored: AparteMessage = { ...newMessage };\n if (isAwaitingReply(newMessage)) stored.isStreaming = true;\n this._repo.addOrUpdateMessage(parentId, stored);\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 // Not while a reply streams: the active path must not change under it (the\n // pickers are disabled for the same reason — see `setTranscriptBusy`; this covers\n // a programmatic call). The stored flag, so the arrows and this guard agree in\n // both modes — the repository cannot answer for a framework-managed turn.\n if (this._busy) return;\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 (a React consumer). 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 // One grammar, THREE paths. `appendMessage` has run the stream's parser over a\n // reply that arrived as a markdown string since 0.16 — and its docblock names\n // this one as covered. It was not: a tree went to the repository raw, and since\n // the controller runs `setMessages` and then this, the raw snapshot won on every\n // conversation that had ever persisted a tree. The fence came back as bare prose.\n const blocks = resolveConfig(this).getStreamBlocks();\n this._repo.import({\n ...tree,\n messages: tree.messages.map((entry) => {\n const parsed = withParsedSegments(entry.message, blocks);\n const message = adoptMessageSegments(parsed);\n if (hasDerivedSegments(parsed)) markDerivedSegments(message);\n return { ...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 // Every branch, not the active path: a retry or an edit takes a message off\n // the path with whatever it carried, and `_repo.clear()` below makes those\n // unreachable — the one case where nothing else can ever release them.\n for (const message of this._repo.getAllMessages()) {\n revokeAttachmentUrls(message.attachments);\n }\n }\n this._repo.clear();\n // An empty transcript streams nothing: the busy flag is derived from the\n // repository, and this is the one path that empties it without a status.\n this._syncBusy();\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. Turned off, it stays off through the scroll that comes\n * next — a jump of the page's own, smooth or not — and comes back once the reader is at\n * the bottom again.\n */\n setAutoScroll(enabled: boolean): void {\n this._isAutoScrollEnabled = enabled;\n // Off is the reader's choice, and the scroll that follows is theirs: marked as their\n // gesture, so the first frames of a smooth jump — a few pixels, still within the bottom\n // threshold — are not read as the reader staying at the bottom and armed again.\n if (!enabled) this._readerInputAt = performance.now();\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('data-role', message.role);\n if (message.compaction) bubble.setAttribute('data-kind', 'compaction');\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._syncBusy();\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 // `_scrollToBottom` turns a pending smooth request into the glide itself.\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) this._scrollToBottom(); });\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 this._nameScrollSurface(container);\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 // An attribute set before the structure existed is honoured now.\n this._syncLoading();\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', resolveConfig(this).t('scrollToBottom'));\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 this._nameScrollSurface(this);\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', resolveConfig(this).t('scrollToBottom'));\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 /**\n * Only the inputs that can SCROLL count as the reader's hand: a wheel notch, a\n * touch, a navigation key, a press in the scrollbar's gutter. A press on a control\n * inside the transcript — a branch arrow, a copy button — is not a scroll gesture,\n * and counting it disarmed the follow through the swap it triggered (react-webkit,\n * 1 run in 6: the rebuild's height churn moved scrollTop within the second after\n * the click, and the click made that look like the reader's).\n */\n /**\n * Is a pointer pressed on the surface right now? A press on the TEXT is not a scroll\n * gesture (`_noteReaderInput` counts only the gutter), but a hand that is down and\n * dragging a selection past the edge scrolls the container all the same — and that\n * decrease is the reader's. Held from the press to its release, wherever it lands.\n */\n private _pointerHeld = false;\n private readonly _holdPointer = (): void => { this._pointerHeld = true; };\n private readonly _releasePointer = (): void => { this._pointerHeld = false; };\n\n private readonly _noteReaderInput = (e: Event): void => {\n if (e.type === 'keydown' && !this._scrollKeys.has((e as KeyboardEvent).key)) return;\n if (e.type === 'pointerdown') {\n const container = this._container;\n if (!container) return;\n const { clientX } = e as PointerEvent;\n const rect = container.getBoundingClientRect();\n const rtl = getComputedStyle(container).direction === 'rtl';\n const inGutter = rtl\n ? clientX <= rect.right - container.clientWidth\n : clientX >= rect.left + container.clientWidth;\n if (!inGutter) return;\n }\n this._readerInputAt = performance.now();\n // The reader's hand ends a glide. A physical wheel cancels a programmatic smooth\n // scroll on its own; a synthetic one (CI's `page.mouse.wheel`, some assistive\n // input) does not, and the animation ran on to the bottom over the gesture —\n // `streaming-progressive` on CI: \"the scroll-up gesture did not take, top is\n // still 477px\". Writing the CURRENT position is how a running smooth scroll is\n // stopped where it is, in every engine; the reader's own scroll then lands on it.\n //\n // For the WHEEL and a TOUCH only. A scroll key starts the ENGINE'S own smooth\n // animation, and this write landed on it and killed it — the keyboard-scroll\n // spec on vanilla-webkit pressed a key inside a seed send's glide window and the\n // transcript did not move (main CI, flaky). A key closes the window like any\n // reader input; the key's scroll needs no help from us.\n if (this._gliding() && this._container) {\n this._glideUntil = 0;\n if (e.type === 'wheel' || e.type === 'touchmove') {\n this._ownScrollAt = performance.now();\n const here = this._container.scrollTop;\n this._container.scrollTop = here;\n }\n }\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._container?.addEventListener('scrollend', this._handleScrollEnd, { passive: true });\n // The reader's hand on the transcript, so `_handleScroll` can tell their scroll\n // from one the browser made while settling ours.\n // `touchmove`, not `touchstart`: a tap on a control fires touchstart too, and\n // a tap is no more a scroll gesture than a click — a finger that scrolls moves.\n for (const type of ['wheel', 'touchmove', 'pointerdown', 'keydown'] as const) {\n this._container?.addEventListener(type, this._noteReaderInput, { passive: true });\n }\n this._container?.addEventListener('pointerdown', this._holdPointer, { passive: true });\n document.addEventListener('pointerup', this._releasePointer, true);\n document.addEventListener('pointercancel', this._releasePointer, 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 // First, because everything below reads the geometry it changes: in\n // overlay mode a composer that grew is THE resize being reported, and\n // re-anchoring against the stale inset would land the reader short.\n this._updateOverlayInset();\n this._updateTranscriptInset();\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 // Overlay mode: the shell's bottom stack (elicitation, an above-composer\n // row, the composer) floats OVER this scroll surface, so its height is part\n // of the geometry this observer exists to watch — and the container alone\n // no longer says it: with the viewport absolute over the shell, a composer\n // that grows resizes NOTHING this observer was looking at. Observe every\n // stack child with the SAME observer; the callback already does everything\n // an inset change needs (write the var, re-anchor a pinned reader, spacer,\n // button).\n // Bare attribute selector on purpose: the shell is <aparte-chat> in vanilla,\n // a [data-aparte-chat] div in React/Vue/Svelte, Angular's inner\n // .aparte-chat-container - and a hand-rolled host that sets the attribute on\n // its own shell gets the measurement for free (the CSS readers are\n // unconditional; only the recipe rules are qualified).\n this._overlayRoot = this.closest('[overlay-composer]');\n if (this._overlayRoot && this._resizeObserver) {\n for (const child of this._overlayStack()) this._resizeObserver.observe(child);\n // A stack child can mount later (elicitation appears on request):\n // observe it from that moment. childList only, never subtree — what is\n // inside those children is the host's business, and their size changes\n // already reach the ResizeObserver.\n this._overlayObserver = new MutationObserver(() => {\n for (const child of this._overlayStack()) this._resizeObserver?.observe(child);\n this._updateOverlayInset();\n });\n this._overlayObserver.observe(this._overlayRoot, { childList: true });\n this._updateOverlayInset();\n }\n\n this._mutationObserver = new MutationObserver((mutations) => {\n this._lastMutationAt = performance.now();\n // Keep the sticky scroll button trailing after framework appends.\n if (this._frameworkManagedDOM) this._keepScrollButtonLast();\n // A user bubble arriving is a send, whichever framework rendered it: the pin\n // this observer queues below must glide, not jump (#57). The four wrappers ask\n // the same thing one event earlier through requestSmoothScroll(); a host that\n // renders bubbles itself and never heard of that call gets the glide all the\n // same. The viewport owns it.\n // A send is ONE user bubble, appended last. A branch swap or a transcript rebuild\n // re-adds many bubbles in one batch, user ones included, with auto-follow armed\n // again — taken for a send, the swap glided instead of pinning, the settle held\n // its hand for the glide, and the swap's height churn left the view short with a\n // scroll-to-bottom button over a reader who never left (CI, bubble-actions:370).\n if (this._isAutoScrollEnabled && !this._gliding()) {\n const added = mutations\n .flatMap((m) => Array.from(m.addedNodes))\n .filter((n): n is Element => n instanceof Element && n.tagName === 'APARTE-CHAT-BUBBLE');\n const only = added.length === 1 ? added[0] : undefined;\n if (only && only.getAttribute('data-role') === 'user' && only.parentElement) {\n const bubbles = only.parentElement.querySelectorAll('aparte-chat-bubble');\n if (bubbles[bubbles.length - 1] === only) this._smoothScrollOnce = true;\n }\n }\n // The gate is tested TWICE: when the frame is queued, and again when it\n // runs. The second test is what lets a reader leave.\n //\n // During a stream a frame is nearly always queued, and it used to scroll\n // unconditionally when it ran. So a reader who wheeled up was disarmed by\n // that gesture (`_handleScroll` sees the decrease) — and one frame later the\n // already-queued scroll dragged them back to the bottom, whose scroll event\n // re-armed auto-follow. Every attempt to read above the stream lasted one\n // frame (\"on a du mal à remonter\", reported from a real session). The\n // run-time check reads the INTENT flag, which only a real decrease disarms,\n // so it cannot fall into the trap an earlier attempt did — that one\n // re-tested `_isAtBottom()` at run time, which a branch swap's height churn\n // makes briefly false, and it refused to re-anchor a reader who was at the\n // bottom (`bubble-actions.spec.ts` on WebKit, 3 runs out of 3). Growth does\n // not decrease `scrollTop` and a clamp lands at the bottom, so the flag\n // stays armed through a swap; only a person turns it off.\n if (this._isAutoScrollEnabled) {\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) this._scrollToBottom(); });\n }\n // Recalculate spacer when DOM mutates (new bubble added, Angular re-render).\n this._scheduleSpacerUpdate();\n // The turn the spacer measures moves only when a bubble comes or goes. Re-read on\n // every record, it walked the whole transcript per streamed token.\n const movesBubbles = (nodes: NodeList): boolean => Array.from(nodes).some((n) =>\n n instanceof Element && (n.tagName === 'APARTE-CHAT-BUBBLE' || n.querySelector('aparte-chat-bubble') !== null));\n if (mutations.some((m) => movesBubbles(m.addedNodes) || movesBubbles(m.removedNodes))) this._observeTail();\n });\n\n // Height only, against what was measured when the bubble started being watched: a\n // width that moves with a scrollbar says nothing about what the spacer measures, and\n // re-measuring on it let a spacer and a scrollbar chase each other.\n this._tailObserver = new ResizeObserver((entries) => {\n let changed = false;\n for (const entry of entries) {\n const height = Math.round(entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height);\n if (this._tailHeights.get(entry.target) !== height) changed = true;\n this._tailHeights.set(entry.target, height);\n }\n if (changed) this._scheduleSpacerUpdate();\n });\n this._observeTail();\n\n // Framework-managed: bubbles are direct children of the host (no wrapper).\n this.addEventListener('toggle', this._onDisclosureToggle, true);\n\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 /**\n * The overlay shell's element children that are the floating bottom stack: not this\n * viewport, and not the scroll rail, which floats over the transcript by design —\n * shell.css names it the same way (`:not(aparte-chat-viewport, aparte-scroll-rail)`).\n * Counted, a rail centred on the transcript set the inset to the distance from its\n * own top edge (398px on the vanilla example) and held it there while the composer\n * grew under a draft.\n */\n private _overlayStack(): HTMLElement[] {\n if (!this._overlayRoot) return [];\n return Array.from(this._overlayRoot.children).filter(\n (el): el is HTMLElement => el instanceof HTMLElement && el !== (this as HTMLElement)\n && el.tagName.toLowerCase() !== 'aparte-scroll-rail',\n );\n }\n\n /**\n * Measure how much of this viewport the overlay stack covers, and publish it as\n * `--aparte-bottom-inset` — the `::after` spacer (framework mode), the\n * container's own padding (core mode) and the scroll button's `bottom` all read\n * it (shell.css). Measured from the stack's highest visible top edge rather\n * than by summing heights, so margins and anything the host renders between\n * the rows count themselves. No-op outside overlay mode, and when the value\n * has not changed — a var write invalidates layout, and this runs from the\n * ResizeObserver its own write can re-trigger (core mode observes the\n * container's content box, which the padding is part of).\n */\n private _updateOverlayInset(): void {\n if (!this._overlayRoot) return;\n const bottom = this.getBoundingClientRect().bottom;\n let top = Infinity;\n for (const el of this._overlayStack()) {\n const rect = el.getBoundingClientRect();\n if (rect.height > 0) top = Math.min(top, rect.top);\n }\n const inset = top === Infinity ? 0 : Math.max(0, Math.round(bottom - top));\n if (inset === this._overlayInset) return;\n this._overlayInset = inset;\n this.style.setProperty('--aparte-bottom-inset', `${inset}px`);\n }\n\n /**\n * Publish, on the chat host, how far from the host's inline edge the rows start —\n * the transcript's padding plus the scrollbar gutter the scroller reserves on both\n * edges, at whatever step its container query has taken — as\n * `--aparte-transcript-inset`. The composer pads by it (composer.css), so its box\n * lands on the row's box at every width. The composer cannot know the gutter\n * and a container query cannot reach it, so this is the one place both boxes read\n * it from. Written only when the value changes, for the\n * same reason `_updateOverlayInset` is. On the HOST, not on this element: the\n * composer is a sibling, and a custom property only travels down.\n */\n private _updateTranscriptInset(): void {\n const host = this.parentElement;\n if (!host) return;\n const ref = this.querySelector<HTMLElement>('.aparte-messages-wrapper') ?? this._container ?? this;\n const pad = parseFloat(getComputedStyle(ref).paddingInlineStart) || 0;\n const rtl = getComputedStyle(host).direction === 'rtl';\n const refRect = ref.getBoundingClientRect();\n const hostRect = host.getBoundingClientRect();\n const raw = rtl ? hostRect.right - refRect.right : refRect.left - hostRect.left;\n const inset = Math.max(0, Math.round((raw + pad) * 10) / 10);\n if (inset === this._transcriptInset) return;\n this._transcriptInset = inset;\n host.style.setProperty('--aparte-transcript-inset', `${inset}px`);\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 * With one exception, measured after the queued frames started re-reading the\n * flag: a decrease the BROWSER makes while settling a scroll of ours. When a\n * stream ends the action bar appears (+34px) and the bottom spacer gives the\n * same 34px back in the same frame; through that churn WebKit moved `scrollTop`\n * from 829 to 804 — a 25px decrease, no reader anywhere near it — and the\n * decrease disarmed the follow that was in the middle of landing, leaving the\n * transcript 25px short and a scroll-to-bottom button on a reader who never\n * left (vanilla-webkit and react-webkit, 3 runs out of 3; Chromium settles the\n * same churn without moving). So a decrease is the reader's unless both hold: it\n * comes within a second of a scroll this component asked for, and no scroll\n * gesture touched the transcript in that second. A real gesture always leaves a\n * trace (`_noteReaderInput`); a jump the reader did not make either\n * (find-in-page, a host's own `scrollTo`) still disarms, except in that\n * one-second shadow of our own scroll.\n *\n * The size of the decrease alone cannot tell the two apart, and it took three\n * versions to learn what can: a 100px cap was refuted by a React branch swap (the\n * rebuild flickers the height by ~200px and WebKit moves scrollTop by as much); no\n * cap at all snapped a reader drag-selecting text upward back to the bottom on the\n * next token, because during a stream every token refreshes `_ownScrollAt` and\n * the shadow never closes; and bounding it by the churn alone — a decrease no\n * larger than the height change seen at THIS event — was refuted by the settle on\n * WebKit, which hands the event a decrease its regrown height no longer explains\n * (−82 against +30, −27 against nothing, one frame after our own pin; the follow\n * disarmed, the transcript 81px short for good). Three signals decide now. The\n * HAND: a scroll gesture leaves a trace in `_readerInputAt` (wheel, touch, a\n * press in the gutter, a scroll key), and a press on the text that drags a\n * selection past the edge holds the pointer (`_pointerHeld`) while the container\n * scrolls — either one is the reader. The CHURN, still: a decrease no larger than\n * the height change is the layout's. And the SETTLE: a decrease of at most\n * `_settleBoundPx` within `_settleShadowMs` of a transcript mutation we observed\n * is the engine finishing that mutation's layout — where a host's own `scrollTo`\n * or a tool's scroll-into-view comes with no mutation, or moves by far more, and\n * disarms the way a reader does. Measured: the overlay spec's `scrollTop = 0` and\n * Playwright's scroll-into-view before a click both re-pinned in a loop under a\n * hand-only rule.\n */\n const top = this._container.scrollTop;\n const height = this._container.scrollHeight;\n const drop = this._lastScrollTop - top;\n const churn = Math.abs(height - this._lastScrollHeight);\n const now = performance.now();\n // Ours when it falls in the shadow of our own scroll and no hand was on the surface\n // in that window. It used to also require the decrease to be no larger than the\n // height churn visible at THIS event — but an engine that clamps or re-anchors\n // scrollTop between two layouts of a settling reply hands the event a decrease\n // the regrown height no longer explains: measured on WebKit, −82 against a churn\n // of +30, and −27 against none, one frame after our own pin, and the follow was\n // disarmed as \"the reader went up\". The reader's hand is the evidence, and it is\n // recorded (wheel, touch, gutter press, key on the container) or HELD (a press on\n // the text, dragging a selection past the edge); the churn was a proxy for both.\n // An engine settling a reply it just re-laid out moves scrollTop within a few\n // frames of the mutation and by tens of pixels; a host's own jump, or a tool's\n // scroll-into-view before a click, comes with no mutation, or moves it by hundreds.\n // Unless the content got SHORTER since the last event: then the decrease is the\n // engine clamping `scrollTop` to a smaller maximum, whatever its size — no reader\n // shortens the transcript. Measured on react-webkit under load at the end of a\n // reply: the height dipped and recovered between two events (1199 at the last,\n // 1833 at its peak, 1068 at this one), the clamp read 665 -> 338, a decrease past\n // the bound, and the follow was disarmed with the view 490px short for good.\n //\n // Not bounded by how much the height fell, which is the one known limit: the height\n // dips and recovers between two events, so the endpoints understate the clamp (131\n // against 327 in the measurement above), and the tallest height in between is not\n // seen in time to bound it (tried: the flake came back, 8 runs in 60). So any jump up\n // whose first event sees a shorter transcript, within a second of a scroll of ours, is\n // read as a clamp, and an armed follow pins it back to the bottom — mid-stream that is\n // almost every jump into history never rendered, whose bubbles leave their 320px\n // fallback as they come into view. The scroll rail stops the follow before it jumps\n // (`setAutoScroll(false)`); a host's own jump, or find-in-page, does not.\n const shrank = height < this._lastScrollHeight - 1;\n const afterMutation = now - this._lastMutationAt < this._settleShadowMs && (drop <= this._settleBoundPx || shrank);\n const settlingOurs = now - this._ownScrollAt < this._ownScrollWindowMs\n && now - this._readerInputAt >= this._ownScrollWindowMs\n && !this._pointerHeld\n && (drop <= churn + 2 || afterMutation);\n const readerWentUp = drop > 1 && !settlingOurs;\n // The same decrease, asked of the reader's HAND rather than of its size. The\n // generosity below is for layout drift; a gesture we can see is not drift, however\n // few pixels it moved.\n const readerHandOnIt = drop > 1 && now - this._readerInputAt < this._ownScrollWindowMs;\n this._lastScrollTop = top;\n this._lastScrollHeight = height;\n\n if (this._isAtBottom() && !readerHandOnIt) {\n // `_isAtBottom()` stays deliberately generous (`_scrollThreshold`, 50px): a\n // few pixels of drift must not read as \"the reader walked away\".\n //\n // But generosity that outranks the reader PINS them. A wheel notch over the\n // transcript moves WebKit ~33px at a time; 33 < 50, so this branch re-armed\n // the follow, and the settle chain — which re-anchors every frame while\n // armed — undid the notch one millisecond later. Measured in CI: wheel at\n // 135ms, the reader at 565, `scrollTop = 598` from `_settleAtBottom`'s step\n // at 155ms, repeat for every one of twelve notches. Mid-stream on WebKit the\n // reader simply could not read back. So the branch yields when the decrease\n // came with a gesture: `_readerInputAt` already tells the two apart, and\n // `settlingOurs` already trusts it — this is the same evidence, read on the\n // arming side, where it was missing.\n this._isAutoScrollEnabled = true;\n } else if (readerWentUp) {\n this._isAutoScrollEnabled = false;\n } else if (drop > 1 && settlingOurs && this._isAutoScrollEnabled) {\n // Armed, and a gap the reader did not open. Nothing else will close it: the\n // rebuild's mutations are over, the host's border box did not change (so the\n // ResizeObserver is silent in framework mode), and the settle may be spent.\n // Measured on react-webkit: top 763 -> 759 -> 720 while the max churned\n // 891 -> 1091 -> 891, leaving the transcript 171px short with the follow\n // still armed and no code path acting on it. Classifying the churn was only\n // half the job — the other half is doing something about it.\n //\n // Gated on `settlingOurs`, so it can never reach a reader: a drag-selection\n // upward (a decrease with the height standing still) and a find-in-page jump\n // (outside the one-second shadow) both take the disarm branch above.\n //\n // And gated on `drop > 1`, the same threshold `readerWentUp` uses: only a\n // DECREASE is a gap the layout opened, which is the whole of the measured\n // mechanism. A scroll of ours that is still moving DOWN is `settlingOurs`\n // too (`drop` is negative, so the churn test passes trivially) — and that is\n // every frame of a native smooth scroll: `requestSmoothScroll()`, the\n // scroll-to-bottom button, the glide after a user's send. Re-anchoring one\n // of those frames assigns `scrollTop`, which per CSSOM-View performs an\n // instant scroll and ABORTS the running animation, so every glide became a\n // one-frame stutter and a jump.\n this._settleAtBottom();\n }\n this._updateScrollButton();\n }\n\n private _scrollToBottom(): void {\n if (!this._container) return;\n // A smooth scroll was asked for (a user send, by whichever path — appendMessage,\n // a wrapper's requestSmoothScroll(), or a user bubble the mutation observer saw\n // arrive): the FIRST pin after it is the glide, not a jump. In framework-managed\n // mode the observer's own pin used to run first and instant — 630 px in one frame\n // measured on React — and the smooth request was only honoured later, by\n // _autoScroll, on a view that had already teleported.\n if (this._smoothScrollOnce) {\n this._smoothScrollOnce = false;\n this._smoothScrollToBottom();\n return;\n }\n if (this._gliding()) { this._retargetGlide(); return; }\n this._ownScrollAt = performance.now();\n this._container.scrollTop = this._container.scrollHeight;\n this._settleAtBottom();\n }\n\n private _gliding(): boolean {\n return performance.now() < this._glideUntil;\n }\n\n /**\n * A second smooth `scrollTo` is continuous where an instant write is a cut. It is also a\n * new smooth scroll: the engine runs it from where the view is, for as long as a first\n * one takes, so it opens a window of its own. Left on the first glide's deadline, the\n * window closed in the middle of the re-targeted run and the next pin cut it —\n * react-webkit in CI, a write 4 px short of the bottom ~330 ms into a send that had\n * re-targeted a glide already running.\n */\n private _retargetGlide(): void {\n if (!this._container) return;\n this._ownScrollAt = performance.now();\n this._armGlide();\n this._container.scrollTo({ top: this._container.scrollHeight, behavior: 'smooth' });\n }\n\n /** Open the glide window from now, and arm the pin that catches a glide that missed. */\n private _armGlide(): void {\n this._glideUntil = performance.now() + this._glideWindowMs;\n // A glide can miss: its target was read before a height churn, or the engine\n // dropped it. Nothing re-pins once the mutations are over, so the glide arms\n // the pin itself — an instant one, with the settle chain, once the window is\n // closed and only if the view is still short and the reader still followed.\n if (this._glideSettleTimer !== null) clearTimeout(this._glideSettleTimer);\n this._glideSettleTimer = setTimeout(() => {\n this._glideSettleTimer = null;\n if (!this._container || !this._isAutoScrollEnabled || this._gliding()) return;\n const max = this._container.scrollHeight - this._container.clientHeight;\n // Above where the last scroll event left it, the view was moved by someone the\n // scroll handler has not heard from yet — a host's jump, a reader — and the\n // glide never moves up: that one is the handler's to judge, not ours to undo.\n if (this._container.scrollTop < this._lastScrollTop - 1) return;\n if (max - this._container.scrollTop > 1) this._scrollToBottom();\n }, this._glideWindowMs + 16);\n }\n\n /**\n * The browser says a scroll came to rest — the glide is over ONLY if it rested at the\n * bottom. WebKit fires `scrollend` when a smooth scroll is REPLACED by another (our own\n * re-target), 45–60 ms into the glide and hundreds of pixels short; taken at its word,\n * that would close the window and the next pin would be the cut this guard exists\n * to prevent. Short of the bottom, the budget keeps bounding the window.\n */\n private _handleScrollEnd(): void {\n if (!this._container) return;\n const max = this._container.scrollHeight - this._container.clientHeight;\n if (max - this._container.scrollTop <= 1) this._glideUntil = 0;\n }\n\n /**\n * Confirm over the next few frames that we actually reached the bottom.\n *\n * One assignment is not enough. Content can settle in several layout passes\n * (1118 → 1121 → 1152 px on Safari in framework mode), and `scrollTop =\n * scrollHeight` run against a middle pass clamps to that layout's max and leaves\n * the rest unclosed. Auto-follow stays armed the whole time, so the component is\n * not disarmed — it is SATISFIED: `_isAtBottom()` answers \"yes\" for any gap under\n * `_scrollThreshold` (50), which is the right rule for keeping auto-follow armed\n * and the wrong one as a definition of \"anchored\".\n *\n * A BOUNDED retry, not one corrective frame: a single frame lands on the same\n * stale layout and can leave a wider gap than doing nothing. Bounded so it always\n * terminates; re-reads `_isAutoScrollEnabled` every frame so a reader who scrolls\n * away mid-settle is left alone.\n *\n * Bounded by TIME, and it does not stop at the first closed gap. A branch swap at\n * the bottom of a long transcript churns the scrollable max (891 -> 1091 -> 891):\n * the gap is closed for a frame against the tall layout, the height falls back with\n * WebKit holding `scrollTop`, and the transcript would stand short with auto-follow\n * still armed. A frame count is a proxy for time that fails exactly on the slow\n * engine, so the budget is real milliseconds, and a closed gap only ends the chain\n * when the window is over. One chain, not one per call: during a stream every token\n * would otherwise start its own.\n */\n private _settleAtBottom(): void {\n this._settleUntil = performance.now() + this._settleWindowMs;\n if (this._settleRafId !== null) return; // one chain; the deadline re-arms it\n const step = (): void => {\n this._settleRafId = null;\n // A detached viewport has nothing to settle. `_cleanup` cancels the chain on\n // disconnect, but a document that goes away whole — a closed window, a test\n // environment torn down — fires no disconnectedCallback, and the next frame\n // then ran against globals that no longer existed (27 errors after teardown\n // in the wrapper suites, whose rAF stub is a 0ms timer that survives it).\n if (!this.isConnected || !this._container || !this._isAutoScrollEnabled) { this._settleUntil = 0; return; }\n const max = this._container.scrollHeight - this._container.clientHeight;\n // A closed gap is NOT the end: the churn re-opens it (891 -> 1091 -> 891).\n // And an open gap during a glide is the glide itself, not a shortfall.\n if (max - this._container.scrollTop > 1 && !this._gliding()) {\n this._ownScrollAt = performance.now();\n this._container.scrollTop = max;\n }\n if (performance.now() < this._settleUntil) this._settleRafId = requestAnimationFrame(step);\n else this._settleUntil = 0;\n };\n this._settleRafId = requestAnimationFrame(step);\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 this._ownScrollAt = performance.now();\n if (typeof this._container.scrollTo === 'function' && !this._prefersReducedMotion()) {\n this._armGlide();\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 would make the button lie whenever the two diverge —\n * `navigateBranch` deliberately disarms auto-follow, so swapping a branch while\n * already at the bottom of a scrollable transcript would leave the button offering\n * to scroll nowhere. 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 const btn = this._scrollBtn;\n if (!btn) return;\n const hidden = this._isAtBottom();\n // This runs on every scroll event of a streaming transcript, so it writes only\n // what changes: `classList.toggle` is already idempotent, `setAttribute` is not\n // — it produces a mutation record whatever the value it writes, and the two\n // below sit in the hottest path the component has. The state is read back from\n // the button rather than cached in a field, because the button is rebuilt on\n // every re-render and a cached flag would skip the stamp on the new one.\n if (btn.classList.contains('aparte-scroll-btn--hidden') === hidden\n && btn.hasAttribute('tabindex') === hidden) return;\n btn.classList.toggle('aparte-scroll-btn--hidden', hidden);\n // Hidden is opacity 0 and no pointer, which the tab order cannot see: the\n // button stayed a stop while invisible, so a keyboard user landed on nothing\n // between the transcript and the composer — and the transcript's own stop,\n // once it had one, pushed the composer past the eighth Tab on the vanilla\n // example. A control nobody can see is not a control anybody can reach.\n if (hidden) {\n btn.setAttribute('tabindex', '-1');\n btn.setAttribute('aria-hidden', 'true');\n } else {\n btn.removeAttribute('tabindex');\n btn.removeAttribute('aria-hidden');\n }\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('data-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 // The question lands below a header floating over the transcript (`--aparte-top-inset`,\n // the surface's scroll padding), not under it at the very top.\n const covered = parseFloat(getComputedStyle(this._container).scrollPaddingTop) || 0;\n const needed = this._container.clientHeight - covered - 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 /** Point `_tailObserver` at the last question and the bubbles after it, and only those. */\n private _observeTail(): void {\n if (!this._tailObserver) return;\n const bubbles: Element[] = Array.from(this.querySelectorAll('aparte-chat-bubble'));\n let from = bubbles.length;\n while (from > 0 && bubbles[from - 1]!.getAttribute('data-role') !== 'user') from--;\n const tail = from > 0 ? bubbles.slice(from - 1) : [];\n for (const el of this._tail) if (!tail.includes(el)) this._tailObserver.unobserve(el);\n for (const el of tail) {\n if (this._tail.includes(el)) continue;\n // The height the spacer measures it at right now — its fallback, if it was never\n // shown — so the report that brings its real height is seen as a change.\n this._tailHeights.set(el, Math.round((el as HTMLElement).offsetHeight));\n this._tailObserver.observe(el, { box: 'border-box' });\n }\n this._tail = tail;\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 _cleanup(): void {\n this._container?.removeEventListener('scroll', this._handleScroll);\n this._container?.removeEventListener('scrollend', this._handleScrollEnd);\n if (this._glideSettleTimer !== null) { clearTimeout(this._glideSettleTimer); this._glideSettleTimer = null; }\n for (const type of ['wheel', 'touchmove', 'pointerdown', 'keydown'] as const) {\n this._container?.removeEventListener(type, this._noteReaderInput);\n }\n this._container?.removeEventListener('pointerdown', this._holdPointer);\n document.removeEventListener('pointerup', this._releasePointer, true);\n document.removeEventListener('pointercancel', this._releasePointer, true);\n this._pointerHeld = false;\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._tailObserver?.disconnect();\n this._tailObserver = null;\n this._tail = [];\n this.removeEventListener('toggle', this._onDisclosureToggle, true);\n this._overlayObserver?.disconnect();\n this._overlayObserver = null;\n this._overlayRoot = null;\n this._overlayInset = -1;\n this._transcriptInset = -1;\n this._resizeObserver = null;\n this._mutationObserver = null;\n if (this._spacerRafId !== null) {\n cancelAnimationFrame(this._spacerRafId);\n this._spacerRafId = null;\n }\n // A custom element is re-connected on every DOM move, so a settle left in flight\n // would accumulate one chain per move.\n if (this._settleRafId !== null) {\n cancelAnimationFrame(this._settleRafId);\n this._settleRafId = null;\n }\n this._settleUntil = 0;\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';\nimport { presenceOn } from '../../utils/presence.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, and `'none'` says this panel has NO act left for that button,\n * so it is not drawn. The chips are the navigation through a form's questions, so the\n * button never means \"next\".\n *\n * `'none'` is for a panel whose options settle on the first click (an approval, a\n * single-choice question): a button beside them would sit permanently disabled,\n * meaning nothing. A control that is never the way forward is not disabled, it is\n * absent.\n *\n * Named rather than inlined at each of its six readers, so they share one list.\n */\nexport type AparteComposerPanelMode = '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 — inside the descendant you marked\n * `data-aparte-panel-host` if there is one, else right after the first input —\n * and `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:\n * `aparte-message-start` sets it, and any of `aparte-message-done` /\n * `aparte-message-error` / `aparte-message-aborted` clears it (and evicts an open\n * panel). `AparteClient` dispatches them on the chat host, bubbling, so they reach\n * both this element's `window` listener and the host-bound readers; a host that runs\n * its own loop dispatches the same two (start, done) the same way, with\n * `detail.targetId` set to this composer's `target` — that is the whole contract,\n * documented under\n * \"Make the composer follow your turn\" in the bring-your-own-loop guide. 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 * `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. Changing it here reaches\n * the inputs already on the page too.\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.\n * @attr {boolean} data-panel-active - Reflected BY the element while a panel (an elicitation, an\n * approval) is mounted inside it; the shipped stylesheet keys off it. Read-only.\n * @attr {string} data-panel-mode - Reflected BY the element while a panel is active: what the send\n * button does meanwhile (`'none'` removes it — through this attribute, not by reaching into the\n * button). Read-only.\n * @attr {boolean} data-model-gated - Reflected BY the element when `requireModelSelection` is on and\n * no model is selected: sending is blocked and the shipped stylesheet greys the composer. Read-only.\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=var(--aparte-btn-size-lg)] - 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 * @cssprop [--aparte-viewport-padding=var(--aparte-space-8)] - Inline gutter between this element and the\n * chat's edge — the same token the transcript reads, so the shell keeps\n * `.aparte-message`'s column BELOW the max-width above as well as above it, where the\n * two would otherwise diverge by the transcript's whole inset. Only the inline halves\n * are read here: the block axis is spaced by the viewport above and\n * `--aparte-chat-bottom-gap` below. A container narrower than 520px tightens the\n * transcript's copy of this token but not the composer's — a container query cannot\n * reach this element, which is its own container root.\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></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 and `showPanel` empties it unconditionally, so every\n * path that closes a panel whose owner is still awaiting an answer — a second\n * `showPanel`, `hidePanel` without the owner's token, `_handleMessageDone` (which\n * fires on EVERY turn end) — must tell the owner. An owner that is not told keeps\n * its request pending, and every later request is short-circuited behind it.\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 * would latch the global config forever: an instance `config` carrying an RTL\n * locale would flip the transcript and not the composer, and the\n * `requireModelSelection` gate would be read off the wrong object.\n * `aparte-chat-bubble` resolves live too, and its JSDoc names this 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 * Without it an RTL locale flips the conversation and leaves the composer\n * left-to-right. It is also what makes the toolbar's placement idiom real:\n * `margin-inline-start: auto` in a subtree that inherits no direction is just\n * `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 // After the current parse: the other chats of the page may be mounted later in\n // the same document, and the question is only worth asking once they are.\n queueMicrotask(() => this._warnIfUnattached());\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 // Pushed, not merely readable: the input reads this element's placeholder\n // when it renders, and nothing else re-read it, so a placeholder bound to a\n // translated string went stale on the first language switch after mount.\n for (const input of this.querySelectorAll('aparte-composer-input')) {\n (input as { syncPlaceholder?: () => void }).syncPlaceholder?.();\n }\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 * The attribute is the state, so the property writes it. It has to exist: React 19\n * and Svelte assign the PROPERTY whenever the element has one of that name, and a\n * getter alone would make `<aparte-composer disabled=\"\">` — a spelling the wrappers\n * type as valid — throw and take the render down. `presenceOn` because `''` is what\n * those templates hand a presence property.\n */\n set disabled(value: boolean) { this.toggleAttribute('disabled', presenceOn(value)); }\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 /** Writes the attribute the inputs read (see `disabled` for why the setter exists). */\n set placeholder(value: string) {\n if (value === null || value === undefined) this.removeAttribute('placeholder');\n else this.setAttribute('placeholder', String(value));\n }\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 would otherwise stay clickable while the\n * user is being asked something. An affordance nothing can honour is not 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'`\n * keeps it; `'none'` says this panel has no act for it and it is not drawn. `'none'`\n * is what a panel whose options settle on the first click needs — a single-choice\n * question, an approval. Flip between them at any time with\n * {@link setPanelSubmitEnabled}, which is how a panel that grows an act (an\n * \"Other…\" field, a written instruction) turns the button back on.\n *\n * The mode is declared with an attribute + CSS rather than inline `style.display`\n * on a child: an attribute is themeable, is visible to a consumer's own rules, and\n * does not clobber a `display` the consumer set.\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 // Where it goes, in order: the element the author marked as the panel's host,\n // else right after the first input, else the end of the composer. The marker\n // exists because \"after the input\" is a position, and a layout with the input\n // in a row and the panel meant for a block of its own had no way to say so.\n const host = this.querySelector('[data-aparte-panel-host]') as HTMLElement | null;\n const inputEl = this.querySelector('aparte-composer-input') as HTMLElement | null;\n this.setAttribute('data-panel-active', '');\n panel.dataset['apartePanel'] = 'true';\n if (host) {\n host.appendChild(panel);\n } else 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 * 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 /*\n * Read BEFORE the panel is removed. Removing the focused element drops the\n * focus to `<body>`, after which \"was the reader in here?\" has no answer —\n * the same reasoning `aparte-elicitation`'s `close()` already records for its\n * own restoration.\n *\n * And it has to be asked at all: the teardown ended on an unconditional\n * `this.focus()`, so every close pulled the caret into the composer's editor.\n * A turn ending closes any open panel, and a turn ends because the model\n * finished, not because the reader did anything — so a reader who had moved to\n * another chat's field, a search box or a link was yanked back mid-keystroke.\n * The panel may hand back the focus it took; it may not take focus it never had.\n */\n const active = document.activeElement;\n const hadFocus = active instanceof Node && this.contains(active);\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 if (hadFocus) 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 can change on the same event — opening the\n * \"Other…\" field of a panel whose options settle on the click gives the button\n * an act it did not have — and two separate calls would flash a wrong state\n * 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: an attribute is themeable, is\n * visible to a consumer's own rules, and does not clobber what the consumer set. It\n * is what lets `'none'` remove the send button without this component reaching\n * 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 /*\n * `_ownTargetId()`, not the bare attribute — the same resolution `cancel()`\n * uses, and for the same reason. The wrappers all set `target`, so this is\n * byte-identical there; in RAW core nothing writes it, so every send carried\n * `targetId: undefined` and a page with two hand-written chats delivered the\n * reply to whichever one the host resolved first.\n */\n targetId: this._ownTargetId(),\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 /** @internal The composer parts' own bus. From outside, listen for `aparte-composer-change`. */\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 /** @internal The composer parts' own bus. From outside, listen for `aparte-composer-change`. */\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. In RAW core — the documented quick\n * start, where the markup is hand-written — nothing sets it, so the chat host above\n * is what identifies the composer; without either, it would accept every chat's\n * lifecycle events and, on a two-chat page, one chat's Stop would tear down the\n * other's open elicitation panel.\n *\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 /**\n * A signal, not a guard: on a page with several chats, a composer attached to none\n * answers to every chat's lifecycle events (see `_ownTargetId`), and the symptom —\n * one chat's Stop evicting another's open question — is nowhere near its cause.\n * Said once, at the console, where the developer is.\n */\n private _warnIfUnattached(): void {\n if (!this.isConnected || this._ownTargetId()) return;\n if (document.querySelectorAll('aparte-chat, [data-aparte-chat]').length < 2) return;\n console.warn('[aparte-composer] this composer belongs to no chat while the page has several: give it target=\"<chat id>\" or place it under a chat host with an id, or it will answer to every chat\\'s events.');\n }\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' || 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 message this composer's turn is writing, when the event that started it said.\n * Kept so the END of a DIFFERENT turn cannot be read as the end of this one — see\n * `_isForThisTurn`.\n */\n private _streamingMessageId: string | null = null;\n\n /**\n * Whether a terminal event ends the turn this composer is following.\n *\n * `_isForThisComposer` answers \"is this chat's?\", and both turns of a retry belong\n * to the same chat: the client aborts the superseded reply and the engine emits\n * `run-aborted` for it, which must not turn Stop back into Send while the new reply\n * is still streaming, nor evict the open panel (the approval question a tool is\n * still waiting on). An event with no `messageId` still ends the turn: a loop of\n * one's own saying \"done\" means this one.\n */\n private _isForThisTurn(e: Event): boolean {\n const id = (e as CustomEvent).detail?.messageId as string | undefined;\n return !id || this._streamingMessageId === null || id === this._streamingMessageId;\n }\n\n private _handleMessageStart(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streamingMessageId = ((e as CustomEvent).detail?.messageId as string | undefined) ?? null;\n this._streaming = true;\n this._emit('streaming-change', { streaming: true });\n }\n\n private _handleMessageDone(e: Event): void {\n if (!this._isForThisComposer(e) || !this._isForThisTurn(e)) return;\n this._streamingMessageId = null;\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 sync and image paste all need the composer. The editor stays\n * editable while a reply streams (the next message is typed while the current one\n * arrives); only the send is gated then — Enter is swallowed, the button is Stop.\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=var(--aparte-btn-size-lg)] - 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=calc((var(--aparte-composer-control-size) - var(--aparte-input-line-height) * 1em) / 2)] - Vertical padding inside the editor: half of what the control size leaves once one line is taken out (`1lh` where the browser has it).\n * @cssprop [--aparte-input-padding-x=var(--aparte-space-6)] - Horizontal padding inside the editor.\n * @cssprop [--aparte-input-font-size=var(--aparte-font-size-base)] - Editor font size.\n * @cssprop [--aparte-input-line-height=var(--aparte-line-height-normal)] - 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). Keep it unitless (`1.5`, not `24px`): the editor's\n * padding reads it as a multiple of `1em` where the browser has no `lh` unit.\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=var(--aparte-radius-lg)] - 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\"></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 /**\n * Re-measures the editor when its WIDTH changes.\n */\n private _resizeObserver: ResizeObserver | null = null;\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 // Bound HERE, never in _render: disconnectedCallback removes these, and on a\n // RECONNECT (a chat moved into a split, a shell, any reparenting) _render\n // finds its DOM already there and returns before the old binding block —\n // which left the editor deaf: the draft landed in the DOM and `root.value`\n // never heard of it, so the send button stayed disabled with text visibly\n // in the box. Wiring is the connect's job; _render only builds.\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 this._connectToRoot();\n this._scheduleInitialReflow();\n // Width is watched because the fixed instants (connect, next frame, fonts.ready)\n // miss a box not yet laid out: the placeholder wraps in a 0 px column and\n // `scrollHeight` reads ~300 px, which would stay the inline height until the\n // first keystroke.\n if (this._editor && typeof ResizeObserver === \"function\") {\n this._resizeObserver = new ResizeObserver(() => this._adjustHeight());\n this._resizeObserver.observe(this._editor);\n }\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._resizeObserver?.disconnect();\n this._resizeObserver = null;\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 /**\n * Re-read the placeholder fallback chain. Called by the composer when ITS\n * `placeholder` changes — this element observes only its own attribute, and the\n * composer's is one of the three sources it falls back to.\n */\n syncPlaceholder(): void {\n this._updatePlaceholder();\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=\"${escapeAttr(String(disabled ? -1 : 0))}\"\n aria-disabled=\"${disabled}\"\n data-placeholder=\"${placeholder}\"\n ></div>`;\n\n this._editor = this.querySelector('.aparte-ci-editor');\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 // No `streaming-change` subscription, on purpose: the editor stays editable while\n // a reply streams. It used to go non-editable for the whole turn, so the next\n // message could not be prepared while the current one arrived — the first thing\n // a person does in every chat. Only the SEND is gated meanwhile (the button is\n // Stop, and Enter is swallowed in `_handleKeydown`); `disabled` still reaches\n // here through `disabled-change`.\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 // While a reply streams, Enter neither sends nor stops: `submit()` is\n // Stop during a turn, and a person finishing the NEXT message with Enter\n // must not cut the current reply off. The draft stays; Enter sends it once\n // the turn is over.\n if (root.streaming) return;\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, where attachments are on: the same signal\n // drag and drop answers to, an `<aparte-composer-add-attachment>` in the composer.\n // Without it a pasted screenshot became an attachment on a chat that offered none —\n // one the app had not said it could send.\n const root = this._getRoot();\n const imageFile = root?.querySelector('aparte-composer-add-attachment')\n ? Array.from(cd.items).find(i => i.type.startsWith('image/'))?.getAsFile()\n : undefined;\n if (imageFile) {\n root?.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 // Out of the tab order as well: a non-editable box that still took focus lit\n // the shell's focus border on click — a field that looks ready and is not.\n this._editor.setAttribute('tabindex', disabled ? '-1' : '0');\n if (disabled && this.ownerDocument?.activeElement === this._editor) this._editor.blur();\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, three meanings: **send**, **stop** while the root is streaming, and — when\n * an elicitation panel is open — **submit** the answer; a panel whose mode is `'none'`\n * (its options settle on the click) takes the button out of the layout entirely. The\n * panel outranks streaming: while one is open the button stays the answer control and a\n * streaming change is ignored. The icon moves with the meaning (paper plane, square,\n * check), because a paper plane that means \"answer\" is a lie. All three 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=var(--aparte-btn-size-lg)] - 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=var(--aparte-btn-size-lg)] - 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=var(--aparte-radius-md)] - 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></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 is kept so the button's chrome can be\n * recomputed after the event, for example on a config change.\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 // Bound here, never in _render — disconnect removes it and a reconnect's\n // _render keeps the existing DOM (see aparte-composer-input for the full rule).\n this._button = this.querySelector('.aparte-cs-button');\n this._button?.addEventListener('click', this._onClick);\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 type=\"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 }\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 means \"submit the answer\".\n *\n * The icon moves with the meaning: a paper plane would read as \"send a message\"\n * while the label says \"Submit\". The button never turns into a \"Next\" while a form\n * has questions ahead (the panel's chips do that, and a button whose meaning\n * changed under the pointer would be a second way to do it). It is disabled\n * instead until every question has an answer, which is what the chips' marks explain.\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 this._button.disabled = !panel.submitEnabled;\n this._button.innerHTML = this._getSubmitIcon();\n const label = 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=var(--aparte-btn-size-lg)] - 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=var(--aparte-radius-sm)] - 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></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 // Bound here, never in _render — disconnect removes it and a reconnect's\n // _render keeps the existing DOM (see aparte-composer-input for the full rule).\n this._button = this.querySelector('.aparte-cc-button');\n this._button?.addEventListener('click', this._onClick);\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 type=\"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 }\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, escapeHtml } 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=72px] - 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></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 const cfg = resolveConfig(this);\n // \"Remove {name}\", from the locale. It was the literal `Remove ${name}` — an\n // icon-only button, so this string is the whole of what a screen-reader user\n // hears, and it stayed English in every language the library shipped.\n // Interpolated from the RAW file name and escaped once, at the end: reusing the\n // already-escaped `name` would escape a `&` twice and read \"rapport & co\".\n const removeLabel = (file: File): string =>\n (cfg.t('removeAttachment') || 'Remove {name}').replace('{name}', () => file.name);\n\n this.innerHTML = files.map((file) => {\n const name = escapeHtml(file.name);\n const remove =\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-thumb__remove\" type=\"button\" ` +\n `aria-label=\"${escapeAttr(removeLabel(file))}\">${cfg.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=\"${name}\">` +\n `<img class=\"aparte-thumbnail__image\" src=\"${escapeAttr(url)}\" alt=\"${name}\" />` +\n `<span class=\"aparte-thumb__name\">${name}</span>${remove}</div>`;\n }\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--file\" title=\"${name}\">` +\n `<span class=\"aparte-thumbnail__label\">${escapeHtml(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 /*\n * The control is the IMAGE, not the tile.\n *\n * The role used to sit on the tile, which wraps the remove `<button>` — a\n * button nested inside a button, which no role permits, and whose name is\n * computed from its contents: the file name from the `title`, again from the\n * `.aparte-thumb__name` overlay, and a third time inside \"Remove report.png\",\n * with no way for the reader to tell which of the two controls Enter reaches.\n *\n * The picture is what the preview opens, so it is what the button is; the ✕\n * sits beside it. The bubble's sent strip keeps the role on its tile and must:\n * there is no ✕ there, nothing is nested, and the tile IS the whole control.\n *\n * The label is interpolated from the RAW `file.name` — `setAttribute` escapes\n * for us, and reusing the already-escaped `name` would escape twice, the trap\n * `removeLabel` above already documents.\n */\n const images = files.filter(f => f.type.startsWith('image/'));\n this.querySelectorAll('.aparte-thumb--image').forEach((tile, i) => {\n const img = tile.querySelector('.aparte-thumbnail__image') as HTMLImageElement | null;\n if (!img) return;\n img.setAttribute('role', 'button');\n img.setAttribute('tabindex', '0');\n img.setAttribute('aria-label', images[i]?.name ?? '');\n const open = (): void => {\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true,\n composed: true,\n detail: { url: img.src, name: images[i]?.name ?? '' },\n }));\n };\n img.addEventListener('click', open);\n img.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\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.\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 and greys the button;\n * `streaming` does neither — a file queued while a reply arrives is part of preparing\n * the next message, and only the send is gated meanwhile.\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 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=var(--aparte-btn-size-lg)] - 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></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 // Bound here, never in _render — disconnect removes it and a reconnect's\n // _render keeps the existing DOM (see aparte-composer-input for the full\n // rule). The drag/drop pair below already lives on the right side.\n this._button = this.querySelector('.aparte-caa-button');\n this._button?.addEventListener('click', this._onClick);\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 rebuilding it would drop focus if the user were on it — a locale or\n * icon change is not a reason to lose the keyboard.\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 }\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 // Attaching is part of preparing the next message, which stays possible\n // while a reply streams; only `disabled` blocks it. `streaming` is read so\n // the subscription still refreshes the button when a turn ends.\n void streaming;\n if (this._button) this._button.disabled = 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\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 } 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 * A key the icon set does not hold renders AS TEXT (`icon=\"star\"` draws the word\n * *star*), so add it with `registerIcons` or pass the SVG.\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=var(--aparte-btn-size-lg)] - 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></aparte-composer-input>\n * <aparte-composer-action icon=\"pin\" label=\"Pin\" action-id=\"pin\"></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 === 'pin') console.log('pinned');\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 // Bound here, never in _render — disconnect removes it and a reconnect's\n // _render keeps the existing DOM (see aparte-composer-input for the full rule).\n this._button = this.querySelector('.aparte-cact-button');\n this._button?.addEventListener('click', this._onClick);\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 }\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) ?? 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 a visible\n * element child nor non-whitespace text; the stylesheet hides it then.\n * A child that hides itself — `hidden`, or its own `data-empty` the\n * way `<aparte-context>` does before its first usage — does not count:\n * a row whose only child shows nothing is an empty band with a border.\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 // Its own data-empty write is a mutation too: an ATTRIBUTE record on this element\n // is skipped, or the write re-enters the observer without end. A childList record\n // also targets this element — the parent — and must not be skipped.\n this._observer ??= new MutationObserver((records) => {\n if (records.some((r) => !(r.type === 'attributes' && r.target === this))) this._syncEmpty();\n });\n // Children's own `hidden` / `data-empty` count too (see _syncEmpty), so the\n // subtree's attributes are watched, filtered to those two.\n this._observer.observe(this, { childList: true, subtree: true, attributes: true, attributeFilter: ['hidden', 'data-empty'] });\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 // A child hidden by the library's own convention shows nothing: `<aparte-context>`\n // sets `data-empty` on itself until the first usage arrives, and a consumer sets\n // `hidden`. Counting it kept a 13px band with a border above the composer's\n // edge, over nothing (the chat-site review, 2026-09-05).\n const visibleChild = Array.from(this.children).some((c) => !c.hasAttribute('hidden') && !c.hasAttribute('data-empty'));\n // Text counts when it is the row's OWN — a text node directly under it — not the\n // text inside a hidden child, which `textContent` would have folded in.\n const ownText = Array.from(this.childNodes).some((n) => n.nodeType === Node.TEXT_NODE && (n.textContent ?? '').trim() !== '');\n const hasContent = visibleChild || ownText;\n if (hasContent === !this.hasAttribute('data-empty')) return;\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","/**\n * Text as a search compares it: no accents, lower case, trimmed — so \"Écrire\" finds \"ecrire\".\n * Shared by the conversation list's filter and the sidebar's search field.\n */\nexport function foldText(text: string): string {\n return text.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().trim();\n}\n\n/**\n * Hide the conversation rows under `root` whose title does not contain `needle` (already\n * folded), and the date groups left with none; return how many rows match. An empty needle\n * shows everything. The conversation list runs it on every render; the sidebar runs it itself\n * on a list that has not upgraded yet, whose rows are the page's own markup.\n */\nexport function filterConversationRows(root: ParentNode, needle: string): number {\n let matches = 0;\n for (const row of root.querySelectorAll<HTMLElement>('[data-conv-id]')) {\n const title = row.querySelector('.aparte-conv-item__title')?.textContent ?? row.textContent ?? '';\n row.hidden = needle !== '' && !foldText(title).includes(needle);\n if (!row.hidden) matches++;\n }\n for (const group of root.querySelectorAll<HTMLElement>('.aparte-conv-group')) {\n group.hidden = needle !== '' && !Array.from(group.querySelectorAll<HTMLElement>('[data-conv-id]')).some((r) => !r.hidden);\n }\n return matches;\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { APARTE_DEFAULT_LOCALE } from '../../config/locale.js';\nimport type { AparteConversationManager } from '../../conversations/conversation-manager.js';\nimport { escapeAttr, escapeHtml } from '../../utils/escape.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { activeElementIn, eventOrigin } from '../../utils/event-target.js';\nimport { presenceOn } from '../../utils/presence.js';\nimport { foldText, filterConversationRows } from '../../utils/fold-text.js';\n\nexport interface AparteConversationListItem {\n id: string;\n title: string;\n /** Drives the date groups. A list where no item carries one renders flat. */\n updatedAt?: number;\n /** When set, the row is italic and its menu offers unarchive instead of archive. */\n archivedAt?: number;\n /** When set, the row sits in the \"Pinned\" group first, and its menu offers unpin. */\n pinnedAt?: 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\nexport interface AparteConversationPinDetail {\n id: string;\n}\n\nexport interface AparteConversationRenameDetail {\n id: string;\n /** The new title, trimmed and non-empty — an unchanged or emptied title fires nothing. */\n title: string;\n}\n\n/** A date group: its heading (null for the undated tail) and its rows, in host order. */\ninterface ConversationGroup {\n key: string;\n label: string | null;\n items: AparteConversationListItem[];\n}\n\n/** The one open menu, if any. */\ninterface OpenMenu {\n id: string;\n row: HTMLElement;\n button: HTMLElement;\n menu: HTMLElement;\n}\n\n/** Locale keys this element reads; each falls back to the English default. */\ntype ListLocaleKey =\n | 'newChat' | 'loadingConversations' | 'conversationList' | 'deleteConversation' | 'archiveConversation' | 'unarchiveConversation'\n | 'conversationActions' | 'renameConversation' | 'conversationTitle'\n | 'pinConversation' | 'unpinConversation' | 'deleteConversationConfirm' | 'cancel'\n | 'conversationGroupPinned' | 'conversationGroupToday' | 'conversationGroupYesterday'\n | 'conversationGroupWeek' | 'conversationGroupMonth' | 'conversationSearchEmpty';\n\n/** A selector for the row button carrying `id` on `attr`. */\nfunction rowSelector(attr: string, id: string): string {\n return `[${attr}=\"${cssEscape(id)}\"]`; // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n}\n\nconst DAY = 864e5;\nconst MENU_GAP = 4;\nconst VIEWPORT_MARGIN = 8;\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 * rows and fires the user's intent. Acting on it is the host's, unless `manage` hands\n * the row menu's writes to the registered conversation manager.\n *\n * A row is two real buttons: the title, which selects, and a `⋯` that opens the row's\n * menu — rename, pin or unpin, archive or unarchive, delete. Every item fires an event\n * and stops; the one exception is delete, which asks first, inline in the menu,\n * because it is the one action the host cannot undo. Rename swaps the title for an\n * input: Enter or leaving the field commits, Escape cancels, and an unchanged or\n * emptied title fires nothing. The rows are grouped by date — Pinned, Today,\n * Yesterday, Previous 7 days, Previous 30 days, then one heading per month — as soon\n * as any item carries `updatedAt`; set `flat` to render them flat.\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. Selecting, renaming, pinning, archiving and deleting all\n * leave the array untouched — the events carry an id (and, for rename, the title) and\n * stop. Set `manage` and the menu's write gestures are carried out on the conversation\n * manager you registered, so you do not write those six listeners; the\n * event still goes out first and `preventDefault()` takes the gesture back. The array\n * is still not this element's: the manager's own change is what re-renders it.\n * A row's text comes from the array, so it changes when the host assigns\n * `conversations` again; the exception is an empty title, which falls back to the\n * locale's new-chat label and therefore follows a locale switch. An archived item is\n * still rendered (it gains `aparte-conv-item--archived`); filtering archived\n * conversations out of the list is the host's decision, not this element's. The\n * asymmetry between the two inputs is deliberate: `active-id` is an attribute because\n * moving the selection patches the rendered rows in place, while `conversations` is a\n * JS property because it is structured data an attribute cannot carry, and setting it\n * 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 * @attr {boolean} flat - Render the rows flat, in host order, with no date headings.\n * @attr {boolean} data-no-match - Reflected BY the element while `filter()` holds a query no title matches. Read-only.\n * @attr {boolean} loading - The list is on its way: six skeleton rows stand where the rows will be, the\n * element is `aria-busy` and a visually hidden line names the wait. A list that is\n * not there yet is not an empty list. Set it while your store answers, clear it when\n * you assign `conversations`.\n * @attr {boolean} manage - Let the list finish the gesture: rename, pin, unpin, archive, unarchive and\n * delete are performed on the conversation manager registered with\n * `setConversationManager()`, instead of leaving you six listeners to write. The\n * event still fires first, and `preventDefault()` on it takes the gesture back.\n * Off by default, and selecting stays yours either way.\n *\n * @fires {CustomEvent<AparteConversationSelectDetail>} aparte-conversation-select - A row's title was activated; the host loads that conversation.\n * @fires {CustomEvent<AparteConversationRenameDetail>} aparte-conversation-rename - A rename was committed with a new, non-empty title. Cancelable: `preventDefault()` takes the gesture back from `manage`.\n * @fires {CustomEvent<AparteConversationPinDetail>} aparte-conversation-pin - The pin item was chosen on an unpinned row. Cancelable: `preventDefault()` takes the gesture back from `manage`.\n * @fires {CustomEvent<AparteConversationPinDetail>} aparte-conversation-unpin - The same item on a pinned row; same detail shape, opposite intent. Cancelable, like the rest.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-conversation-archive - The archive item was chosen on a live conversation. Cancelable: `preventDefault()` takes the gesture back from `manage`.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-conversation-unarchive - The same item on an already-archived one; same detail shape, opposite intent. Cancelable, like the rest.\n * @fires {CustomEvent<AparteConversationDeleteDetail>} aparte-conversation-delete - The delete was confirmed. Cancelable: `preventDefault()` takes the gesture back from `manage`.\n *\n * @cssprop [--aparte-conv-list-gap=var(--aparte-space-1)] - Vertical gap between rows, and between a group's heading and its rows.\n * @cssprop [--aparte-conv-item-padding=var(--aparte-space-4) var(--aparte-space-5)] - Padding of a row's title button.\n * @cssprop [--aparte-conv-item-gap=var(--aparte-space-3)] - Gap between a row's title and its `⋯` button.\n * @cssprop [--aparte-conv-item-radius=var(--aparte-radius-md)] - Corner radius of a row.\n * @cssprop [--aparte-conv-item-font-size=var(--aparte-font-size-md)] - Font size of a row's title.\n * @cssprop [--aparte-conv-item-color=var(--aparte-text)] - 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)] - Title weight of the active row.\n * @cssprop [--aparte-conv-action-btn-size=var(--aparte-btn-size-sm)] - Square size of the `⋯` button (the recipe's small step). Under `(pointer: coarse)` the stylesheet redeclares it as the touch target size on the button itself, so a value set on the element does not reach it there; the button also stays visible instead of appearing on hover.\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 * const day = 864e5;\n * document.querySelector('aparte-conversation-list').conversations = [\n * { id: 'c0', title: 'Release checklist', updatedAt: Date.now() - 3 * day, pinnedAt: Date.now() },\n * { id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },\n * { id: 'c2', title: 'Rename the segment types', updatedAt: Date.now() - day },\n * { id: 'c3', title: 'Tokens, not selectors', updatedAt: Date.now() - 4 * day },\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-conversation-select', (e) => load(e.detail.id));\n * list.addEventListener('aparte-conversation-rename', (e) => manager.updateTitle(e.detail.id, e.detail.title));\n * list.addEventListener('aparte-conversation-pin', (e) => manager.pin(e.detail.id));\n * list.addEventListener('aparte-conversation-delete', (e) => manager.delete(e.detail.id));\n *\n * @example\n * // Or let the list finish the gesture: register the manager once, add `manage`, and\n * // rename / pin / archive / delete are carried out for you. The events still fire.\n * aparteGlobalConfig.setConversationManager(manager);\n * list.setAttribute('manage', '');\n */\nexport class AparteConversationList extends HTMLElement {\n private _conversations: AparteConversationListItem[] = [];\n private _activeId: string | null = null;\n private _open: OpenMenu | null = null;\n private _renaming: { id: string; input: HTMLInputElement; done: boolean } | null = null;\n private _warnedNoManager = false;\n\n static get observedAttributes(): string[] {\n return ['active-id', 'flat', 'loading', 'manage'];\n }\n\n /**\n * The list is on its way: rows of the kit's skeleton stand where the rows will be,\n * the element is `aria-busy`, and a visually hidden line names the wait. Reflected\n * as the `loading` attribute, so a site sets it while its store answers and clears\n * it when `conversations` is set. A list that is not there yet is not an empty list.\n */\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(value: boolean) {\n // `presenceOn`, like every other boolean property in core: `''` is the ON that\n // React stringifies and Svelte 5 assigns, and `toggleAttribute` reads it as\n // falsy — the documented spelling turned the wait off.\n this.toggleAttribute('loading', presenceOn(value));\n }\n\n /**\n * Let the list finish the gesture through the registered conversation manager.\n * Reflected as the `manage` attribute.\n *\n * The property exists because this is the attribute that authorises a WRITE, and Vue\n * and both Sveltes stringify a bound `false` onto an element that has no property of\n * that name — `manage=\"false\"`, which `hasAttribute` reads as on. Without it,\n * turning management off would turn it on. `presenceOn`, like `loading` above it.\n */\n get manage(): boolean {\n return this.hasAttribute('manage');\n }\n\n set manage(value: boolean) {\n this.toggleAttribute('manage', presenceOn(value));\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.addEventListener('click', this._onClick);\n this.addEventListener('keydown', this._onKeydown);\n this.addEventListener('focusout', this._onFocusOut);\n this.addEventListener('pointerdown', this._onPress);\n this._render();\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n this._closeMenu();\n this.removeEventListener('click', this._onClick);\n this.removeEventListener('keydown', this._onKeydown);\n this.removeEventListener('focusout', this._onFocusOut);\n this.removeEventListener('pointerdown', this._onPress);\n window.removeEventListener('pointerup', this._onRelease, true);\n window.removeEventListener('pointercancel', this._onRelease, true);\n this._pressed = false;\n this._renderAfterPress = false;\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 } else if ((name === 'flat' || name === 'loading') && this.isConnected) {\n this._render();\n }\n }\n\n /**\n * Re-render on a locale switch: every row's title fallback, the `⋯` button's name\n * and the group headings come from the locale, so without this the list stayed in\n * the previous language until something else happened to re-render it. Only OUR\n * 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 /**\n * A press holds every render until its click has landed. The host re-assigns\n * `conversations` on every manager change (a save moves a row, a select marks one),\n * and a row replaced between `mousedown` and `mouseup` loses its click: Chromium\n * sends it to the list, where the delegated handler matches nothing, and WebKit\n * sends none. Keeping the row node would not help: a row moved to its new place\n * leaves the document on the way, and the row that moves is often the one pressed\n * (the conversation just saved). So nothing changes under a pointer that is down,\n * and the render runs once, after the click.\n */\n private _pressed = false;\n private _renderAfterPress = false;\n\n private _onPress = (): void => {\n if (this._pressed) return;\n this._pressed = true;\n window.addEventListener('pointerup', this._onRelease, true);\n window.addEventListener('pointercancel', this._onRelease, true);\n };\n\n private _onRelease = (): void => {\n window.removeEventListener('pointerup', this._onRelease, true);\n window.removeEventListener('pointercancel', this._onRelease, true);\n // `mouseup` and `click` follow `pointerup` in the same task: this runs after them.\n setTimeout(() => {\n this._pressed = false;\n if (!this._renderAfterPress) return;\n this._renderAfterPress = false;\n if (this.isConnected) this._render();\n }, 0);\n };\n\n /** The name this element last wrote, so one the app wrote — before or after connect — is left alone. */\n private _ownLabel: string | null = null;\n\n private _render(): void {\n // A landmark needs a name once a page has a second one (axe landmark-unique): the\n // locale's, unless the app named it — an `aria-label` or `aria-labelledby` of its own wins.\n const current = this.getAttribute('aria-label');\n if (!this.hasAttribute('aria-labelledby') && (current === null || current === this._ownLabel)) {\n this._ownLabel = this._t('conversationList');\n this.setAttribute('aria-label', this._ownLabel);\n }\n if (this._pressed) {\n this._renderAfterPress = true;\n return;\n }\n // The menu and the rename input live inside the rows; a render replaces them.\n this._closeMenu();\n this._renaming = null;\n // Where the keyboard was, so it can be put back: `innerHTML` below replaces every\n // row, and Enter on a row is followed by exactly this render (the host marks the\n // active conversation and re-assigns the list). The button the reader was on is\n // gone by then and the focus lands on `<body>`, so the next Tab restarts at the\n // top of the page. Same rule the rename exit already follows.\n // `activeElementIn`, not `document.activeElement`: that retargets to the shadow\n // HOST for a list mounted in a consumer's own root, so `contains` answered no and\n // nothing was ever held.\n const focused = activeElementIn(this) as HTMLElement | null;\n const held = focused && this.contains(focused)\n ? focused.closest<HTMLElement>('[data-select-id], [data-more-id]')\n : null;\n const restore = held?.dataset['selectId']\n ? rowSelector('data-select-id', held.dataset['selectId'])\n : held?.dataset['moreId']\n ? rowSelector('data-more-id', held.dataset['moreId'])\n : null;\n // Where that row sat, for the case where it does not come back: a delete is\n // confirmed from the row's own menu, so the row the keyboard is on is the row\n // that leaves.\n const heldRow = held?.closest<HTMLElement>('[data-conv-id]') ?? null;\n // Among the rows a reader can reach: with a search held, the hidden ones do not count.\n const visible = (el: Element): boolean => !el.closest('[hidden]');\n const heldIndex = heldRow\n ? Array.from(this.querySelectorAll<HTMLElement>('[data-conv-id]')).filter(visible).indexOf(heldRow)\n : -1;\n const loading = this.hasAttribute('loading');\n this.setAttribute('aria-busy', loading ? 'true' : 'false');\n if (loading) {\n // A list on its way matches nothing yet: no \"no match\" while it loads.\n this.removeAttribute('data-no-match');\n // Six rows of the recipe, the widths varied so it reads as a list and not a\n // grid; a status line off screen, because aria-busy alone is often ignored.\n // Literals only, no interpolation at all: the text-escaping guard reads a\n // `.repeat()` on a string as an interpolation it cannot see through, and a red\n // guard is a guard somebody skips.\n const row = '<span class=\"aparte-skeleton aparte-skeleton--text\"></span>';\n this.innerHTML =\n '<div class=\"aparte-conv-list-loading\" aria-hidden=\"true\">' + row + row + row + row + row + row + '</div>'\n + '<span class=\"aparte-conv-list-loading-status aparte-sr-only\" role=\"status\"></span>';\n // The text lands AFTER the node does. A live region created with its content\n // already in it is a new node, not a change, and several screen readers\n // announce nothing at all. One frame is enough for it to be registered; if the\n // wait ends first the node is gone and there is nothing left to say.\n const status = this.querySelector<HTMLElement>('.aparte-conv-list-loading-status');\n const say = (): void => { if (status?.isConnected) status.textContent = this._t('loadingConversations'); };\n if (typeof requestAnimationFrame === 'function') requestAnimationFrame(say);\n else say();\n return;\n }\n // The no-match line outlives the rows, and never leaves the document: rebuilt with\n // them, or detached and put back, it is a new live region to the accessibility tree,\n // whose text may be announced again — \"No conversation matches\" after every save.\n const line = this.querySelector<HTMLElement>(':scope > .aparte-conv-list-no-match');\n const groups = this._groups().map(g => this._renderGroup(g)).join('');\n if (line) {\n for (const child of Array.from(this.childNodes)) if (child !== line) child.remove();\n line.insertAdjacentHTML('beforebegin', groups);\n } else {\n this.innerHTML = groups;\n }\n this._applyFilter();\n if (!restore) return;\n // The same row, by id — not the same element, which no longer exists — when the\n // search still shows it: a focus() inside `hidden` lands on <body>.\n const back = this.querySelector<HTMLElement>(restore); // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n if (back && visible(back)) { back.focus(); return; }\n // That row left the list. The keyboard goes to the row that took its place (the\n // next one, else the one above), and to the list itself when the last\n // conversation goes — never to `<body>`, which would restart the next Tab at the\n // top of the page right after the one action here that cannot be undone.\n const rows = Array.from(this.querySelectorAll<HTMLElement>('[data-select-id]')).filter(visible);\n const next = heldIndex >= 0 ? (rows[heldIndex] ?? rows[rows.length - 1]) : null;\n if (next) { next.focus(); return; }\n // Borrowed, not taken: a host that made the list focusable keeps its own tab\n // stop, and the one we add leaves with the focus rather than sitting in the tab\n // order for the rest of the session.\n if (!this.hasAttribute('tabindex')) {\n this.setAttribute('tabindex', '-1');\n this.addEventListener('blur', () => this.removeAttribute('tabindex'), { once: true });\n }\n this.focus();\n }\n\n /** The query `filter()` holds, folded; re-applied on every render, so new rows obey it. */\n private _query = '';\n\n /**\n * Show only the conversations whose title contains `query`, case- and\n * accent-insensitive, and the date groups that still hold one; return how many match.\n * An empty query shows everything. The query is kept through every render — a new\n * conversation, a rename — and when nothing matches the list says so in a status line\n * (the locale's `conversationSearchEmpty`) and carries `data-no-match`.\n */\n filter(query: string): number {\n this._query = foldText(query);\n return this._applyFilter();\n }\n\n private _applyFilter(): number {\n const needle = this._query;\n // Rows that are not there yet match nothing and miss nothing: the skeleton is shown.\n if (this.hasAttribute('loading')) return 0;\n const matches = filterConversationRows(this, needle);\n const noMatch = needle !== '' && matches === 0;\n this.toggleAttribute('data-no-match', noMatch);\n // Always there, empty until a miss: a live region created with its text already in\n // it is a new node, not a change, and several screen readers announce nothing.\n let line = this.querySelector<HTMLElement>(':scope > .aparte-conv-list-no-match');\n const born = !line;\n if (!line) {\n line = document.createElement('p');\n line.className = 'aparte-conv-list-no-match';\n line.setAttribute('role', 'status');\n this.appendChild(line);\n }\n const text = noMatch ? this._t('conversationSearchEmpty') : '';\n const write = (): void => { if (line!.isConnected && line!.textContent !== text) line!.textContent = text; };\n // A line a render just made is a new node, and its first text has to come a frame\n // later to be announced (same as the loading status).\n if (born && text && typeof requestAnimationFrame === 'function') requestAnimationFrame(write);\n else write();\n return matches;\n }\n\n private _t(key: ListLocaleKey): string {\n const locale = resolveConfig(this).getLocale();\n return locale[key] ?? APARTE_DEFAULT_LOCALE[key] ?? '';\n }\n\n /**\n * Pinned first, then the calendar buckets every chat product uses, then a heading\n * per month (newest first), then whatever carries no date. Flat when nothing is\n * dated or the host asked for `flat`: a heading over the only group would say\n * nothing.\n */\n private _groups(): ConversationGroup[] {\n const items = this._conversations;\n if (this.hasAttribute('flat') || !items.some(c => typeof c.updatedAt === 'number')) {\n return [{ key: 'all', label: null, items }];\n }\n const now = new Date();\n const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n const tag = resolveConfig(this).getLocale().tag;\n const buckets = new Map<string, ConversationGroup>();\n const push = (key: string, label: string | null, item: AparteConversationListItem): void => {\n let g = buckets.get(key);\n if (!g) {\n g = { key, label, items: [] };\n buckets.set(key, g);\n }\n g.items.push(item);\n };\n for (const c of items) {\n if (c.pinnedAt) { push('pinned', this._t('conversationGroupPinned'), c); continue; }\n const at = c.updatedAt;\n if (typeof at !== 'number') { push('undated', null, c); continue; }\n if (at >= today) push('today', this._t('conversationGroupToday'), c);\n else if (at >= today - DAY) push('yesterday', this._t('conversationGroupYesterday'), c);\n else if (at >= today - 7 * DAY) push('week', this._t('conversationGroupWeek'), c);\n else if (at >= today - 30 * DAY) push('month', this._t('conversationGroupMonth'), c);\n else {\n const d = new Date(at);\n const sameYear = d.getFullYear() === now.getFullYear();\n const raw = new Intl.DateTimeFormat(tag, sameYear ? { month: 'long' } : { month: 'long', year: 'numeric' }).format(d);\n // French month names are lower-case; a heading is not.\n const label = raw.charAt(0).toLocaleUpperCase(tag) + raw.slice(1);\n push(`m-${d.getFullYear()}-${String(d.getMonth()).padStart(2, '0')}`, label, c);\n }\n }\n const fixed = ['pinned', 'today', 'yesterday', 'week', 'month']\n .map(k => buckets.get(k))\n .filter((g): g is ConversationGroup => !!g);\n const months = [...buckets.keys()]\n .filter(k => k.startsWith('m-'))\n .sort((a, b) => b.localeCompare(a))\n .map(k => buckets.get(k)!);\n const undated = buckets.get('undated');\n return [...fixed, ...months, ...(undated ? [undated] : [])];\n }\n\n private _renderGroup(group: ConversationGroup): string {\n const rows = group.items.map(conv => this._renderItem(conv)).join(''); // safe-text: each row is built by _renderItem, which escapes every field it interpolates.\n if (group.label === null) {\n return `<div class=\"aparte-conv-group\">${rows}</div>`;\n }\n const label = escapeHtml(group.label);\n return `\n<div class=\"aparte-conv-group\" role=\"group\" aria-label=\"${escapeAttr(group.label)}\">\n <div class=\"aparte-menu__label aparte-conv-group__label\" aria-hidden=\"true\">${label}</div>\n ${rows}\n</div>`;\n }\n\n private _renderItem(conv: AparteConversationListItem): string {\n const isActive = conv.id === this._activeId;\n const classes = [\n 'aparte-menu__item aparte-conv-item',\n isActive ? 'aparte-conv-item--active' : '',\n conv.archivedAt ? 'aparte-conv-item--archived' : '',\n conv.pinnedAt ? 'aparte-conv-item--pinned' : '',\n ].filter(Boolean).join(' ');\n const escapedId = escapeAttr(conv.id);\n const title = escapeHtml(conv.title || this._t('newChat'));\n const actionsLabel = escapeAttr(this._t('conversationActions'));\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 moreGlyph = resolveConfig(this).getIcon('more'); // safe-text: the icon provider's SVG — markup by contract, which is what getIcon returns everywhere in core.\n return `\n<div class=\"${escapeAttr(classes)}\" data-conv-id=\"${escapedId}\">\n <button\n class=\"aparte-conv-item__select\"\n type=\"button\"\n data-select-id=\"${escapedId}\"\n aria-current=\"${isActive ? 'page' : 'false'}\"\n ><span class=\"aparte-conv-item__title\">${title}</span></button>\n <button\n class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__more\"\n type=\"button\"\n data-more-id=\"${escapedId}\"\n aria-label=\"${actionsLabel}\"\n title=\"${actionsLabel}\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n >${moreGlyph}</button>\n</div>`;\n }\n\n // ─── The row's menu ───────────────────────────────────────────────────\n\n private _menuMarkup(conv: AparteConversationListItem): string {\n const config = resolveConfig(this);\n const editGlyph = config.getIcon('edit'); // safe-text: the icon provider's SVG — markup by contract.\n const pinGlyph = config.getIcon('pin'); // safe-text: the icon provider's SVG — markup by contract.\n const archiveGlyph = config.getIcon(conv.archivedAt ? 'unarchive' : 'archive'); // safe-text: the icon provider's SVG — markup by contract.\n const trashGlyph = config.getIcon('trash'); // safe-text: the icon provider's SVG — markup by contract.\n const rename = escapeHtml(this._t('renameConversation'));\n const pin = escapeHtml(this._t(conv.pinnedAt ? 'unpinConversation' : 'pinConversation'));\n const archive = escapeHtml(this._t(conv.archivedAt ? 'unarchiveConversation' : 'archiveConversation'));\n const remove = escapeHtml(this._t('deleteConversation'));\n return `\n<button type=\"button\" class=\"aparte-menu__item\" role=\"menuitem\" data-menu-action=\"rename\">${editGlyph}<span>${rename}</span></button>\n<button type=\"button\" class=\"aparte-menu__item\" role=\"menuitem\" data-menu-action=\"pin\">${pinGlyph}<span>${pin}</span></button>\n<button type=\"button\" class=\"aparte-menu__item\" role=\"menuitem\" data-menu-action=\"archive\">${archiveGlyph}<span>${archive}</span></button>\n<div class=\"aparte-menu__separator\" role=\"separator\"></div>\n<button type=\"button\" class=\"aparte-menu__item aparte-conv-menu__item--danger\" role=\"menuitem\" data-menu-action=\"delete\">${trashGlyph}<span>${remove}</span></button>`;\n }\n\n /** The question the confirm step asks — the popover's accessible name while it asks it. */\n private _confirmQuestion(conv: AparteConversationListItem): string {\n return this._t('deleteConversationConfirm').replace('{title}', () => conv.title || this._t('newChat'));\n }\n\n private _confirmMarkup(conv: AparteConversationListItem): string {\n const question = escapeHtml(this._confirmQuestion(conv));\n const cancel = escapeHtml(this._t('cancel'));\n const remove = escapeHtml(this._t('deleteConversation'));\n return `\n<div class=\"aparte-conv-menu__confirm\">\n <p class=\"aparte-conv-menu__question\">${question}</p>\n <div class=\"aparte-conv-menu__actions\">\n <button type=\"button\" class=\"aparte-btn aparte-btn--sm aparte-btn--ghost\" data-menu-action=\"cancel\">${cancel}</button>\n <button type=\"button\" class=\"aparte-btn aparte-btn--sm aparte-btn--danger aparte-btn--solid\" data-menu-action=\"confirm-delete\">${remove}</button>\n </div>\n</div>`;\n }\n\n private _openMenu(row: HTMLElement, button: HTMLElement): void {\n this._closeMenu();\n const id = row.dataset['convId']!;\n const conv = this._conversations.find(c => c.id === id);\n if (!conv) return;\n const menu = document.createElement('div');\n menu.className = 'aparte-menu aparte-conv-menu';\n menu.setAttribute('role', 'menu');\n menu.setAttribute('aria-label', conv.title || this._t('newChat'));\n menu.innerHTML = this._menuMarkup(conv); // safe-text: _menuMarkup escapes every locale string and only interpolates provider glyphs.\n row.appendChild(menu);\n button.setAttribute('aria-expanded', 'true');\n this._open = { id, row, button, menu };\n this._placeMenu(button, menu);\n menu.querySelector<HTMLElement>('[role=\"menuitem\"]')?.focus();\n document.addEventListener('pointerdown', this._onDocumentPointerDown, true);\n document.addEventListener('pointerup', this._onDocumentPointerUp, true);\n document.addEventListener('pointercancel', this._onDocumentPointerUp, true);\n window.addEventListener('scroll', this._onWindowScroll, true);\n window.addEventListener('resize', this._onWindowScroll);\n }\n\n /**\n * Fixed, below the button's end edge, flipped above it when the viewport ends\n * first. Fixed rather than absolute because a sidebar scrolls: an absolute menu on\n * the last rows would be clipped by the list's own overflow. No anchoring library —\n * the menu closes on any scroll or resize, which is what makes the arithmetic\n * safe to do once.\n */\n private _placeMenu(button: HTMLElement, menu: HTMLElement): void {\n const r = button.getBoundingClientRect();\n const m = menu.getBoundingClientRect();\n let top = r.bottom + MENU_GAP;\n if (top + m.height > window.innerHeight - VIEWPORT_MARGIN && r.top - m.height - MENU_GAP >= VIEWPORT_MARGIN) {\n top = r.top - m.height - MENU_GAP;\n }\n const rtl = getComputedStyle(this).direction === 'rtl';\n let left = rtl ? r.left : r.right - m.width;\n left = Math.max(VIEWPORT_MARGIN, Math.min(left, window.innerWidth - m.width - VIEWPORT_MARGIN));\n menu.style.top = `${top}px`;\n menu.style.left = `${left}px`;\n }\n\n private _closeMenu(returnFocus = false): void {\n const open = this._open;\n if (!open) return;\n this._open = null;\n open.menu.remove();\n open.button.setAttribute('aria-expanded', 'false');\n document.removeEventListener('pointerdown', this._onDocumentPointerDown, true);\n document.removeEventListener('pointerup', this._onDocumentPointerUp, true);\n document.removeEventListener('pointercancel', this._onDocumentPointerUp, true);\n this._pressInMenu = false;\n window.removeEventListener('scroll', this._onWindowScroll, true);\n window.removeEventListener('resize', this._onWindowScroll);\n if (returnFocus && open.button.isConnected) open.button.focus();\n }\n\n /**\n * A pointer is down inside the open menu. WebKit does not move the focus to a\n * button on mousedown: the element that held it is blurred instead — `focusout`\n * with no `relatedTarget` — before the click reaches the item. `_onFocusOut` used\n * to read that as the focus leaving the menu and close it on the press, so with a\n * mouse no item could be chosen at all. Held from the press to its release.\n */\n private _pressInMenu = false;\n\n private _onDocumentPointerUp = (): void => {\n this._pressInMenu = false;\n };\n\n private _onDocumentPointerDown = (e: Event): void => {\n const open = this._open;\n if (!open) return;\n // `eventOrigin`, not `e.target`: this listens on `document`, and a chat mounted\n // inside a consumer's shadow root retargets the event to the shadow host on its\n // way out — every question below would be asked of the wrong element.\n const target = eventOrigin(e) as Node | null;\n this._pressInMenu = !!target && open.menu.contains(target);\n // The button's own click toggles; closing here too would reopen on the click.\n if (open.menu.contains(target) || open.button.contains(target)) return;\n this._closeMenu();\n };\n\n private _onWindowScroll = (): void => {\n this._closeMenu();\n };\n\n private _onFocusOut = (e: FocusEvent): void => {\n const open = this._open;\n if (!open) return;\n const next = e.relatedTarget as Node | null;\n // A blur with nowhere to go, during a press inside the menu: the browser is\n // clearing the focus for a button it will not focus (WebKit), not leaving.\n if (!next && this._pressInMenu) return;\n if (next && (open.menu.contains(next) || open.button.contains(next))) return;\n // Focus left the menu for somewhere else (a Tab, a programmatic move): close\n // without pulling it back.\n if (open.menu.contains(e.target as Node)) this._closeMenu();\n };\n\n /**\n * The gesture, finished. The event has already gone out — cancelable, so a host\n * that wants the write for itself calls `preventDefault()` and this stands down —\n * and what is left is the registered conversation manager doing it. Without\n * `manage` the element behaves exactly as it did before the attribute existed.\n *\n * A `manage` with no manager registered is a menu that does nothing, so the first\n * gesture says so and names the call that fixes it. Said here rather than at\n * `connectedCallback` because a list in the page's HTML connects before the script\n * that registers the manager runs: a warning at connect would fire on a setup that\n * is perfectly correct one tick later.\n */\n private _manage(event: Event, run: (manager: AparteConversationManager) => Promise<unknown>): void {\n if (!this.hasAttribute('manage') || event.defaultPrevented) return;\n const manager = resolveConfig(this).getConversationManager();\n if (!manager) {\n if (this._warnedNoManager) return;\n this._warnedNoManager = true;\n console.warn(\n '[aparte-conversation-list] `manage` is set but no conversation manager is registered, '\n + 'so the row menu writes nothing. Call setConversationManager(manager) on your config, '\n + 'or drop `manage` and handle the events yourself.'\n );\n return;\n }\n void run(manager).catch((err: unknown) => {\n console.warn('[aparte-conversation-list] the conversation manager refused the change:', err);\n });\n }\n\n private _onMenuAction(action: string): void {\n const open = this._open;\n if (!open) return;\n const conv = this._conversations.find(c => c.id === open.id);\n if (!conv) { this._closeMenu(); return; }\n const id = conv.id;\n switch (action) {\n case 'rename':\n this._closeMenu();\n this._startRename(open.row, conv);\n return;\n // The two toggles dispatch each name as a LITERAL, in its own branch: the\n // manifest analyzer records a dispatch whose name is a ternary as an event\n // with no name (the Angular generator then crashes on it), and one whose\n // name is a local as an event called after the local.\n case 'pin':\n this._closeMenu(true);\n if (conv.pinnedAt) {\n const unpin = new CustomEvent<AparteConversationPinDetail>(\n 'aparte-conversation-unpin',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(unpin);\n this._manage(unpin, (m) => m.unpin(id));\n } else {\n const pin = new CustomEvent<AparteConversationPinDetail>(\n 'aparte-conversation-pin',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(pin);\n this._manage(pin, (m) => m.pin(id));\n }\n return;\n case 'archive':\n this._closeMenu(true);\n if (conv.archivedAt) {\n const unarchive = new CustomEvent<AparteConversationArchiveDetail>(\n 'aparte-conversation-unarchive',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(unarchive);\n this._manage(unarchive, (m) => m.unarchive(id));\n } else {\n const archive = new CustomEvent<AparteConversationArchiveDetail>(\n 'aparte-conversation-archive',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(archive);\n this._manage(archive, (m) => m.archive(id));\n }\n return;\n case 'delete': {\n // Ask first. The safe answer takes the focus — and it takes it BEFORE the\n // items it replaces are removed, which is the whole of this dance.\n //\n // `menu.innerHTML = …` destroyed the item the reader had just activated\n // while it still held the focus, and a browser blurs a node it is about to\n // remove: `focusout`, no `relatedTarget`, dispatched while the node is\n // still in the tree. `_onFocusOut` reads that as \"the focus left the menu\"\n // and closes it — so the question never appeared and there was no way at\n // all to delete a conversation. jsdom runs no such fixup, which is why the\n // unit tests were green; see `the-keyboard-keeps-its-place.test.ts`.\n //\n // Adding the question, moving the focus into it, and only then removing the\n // old items means nothing focused is ever removed.\n const previous = [...open.menu.childNodes];\n const holder = document.createElement('div');\n holder.innerHTML = this._confirmMarkup(conv); // safe-text: _confirmMarkup escapes the title and every locale string.\n open.menu.append(...holder.childNodes);\n // While it asks, the popover IS the question: a `role=\"menu\"` may hold\n // menu items and nothing else, and the two answers are buttons — axe reads\n // that as a critical `aria-required-children` violation, on a state one\n // click away, in the element the accessibility guide offers as the pattern\n // to copy. `role=\"menuitem\"` on a destructive confirm would be the cheap\n // lie; a question with two answers is a dialog. `_openMenu` builds the\n // menu afresh, so the role comes back with the items. The row's more\n // button keeps `aria-haspopup=\"menu\"` on purpose: what it opens IS a\n // menu, and the question is one step inside it, not what the button\n // announces.\n open.menu.setAttribute('role', 'dialog');\n open.menu.setAttribute('aria-label', this._confirmQuestion(conv));\n open.menu.querySelector<HTMLElement>('[data-menu-action=\"cancel\"]')?.focus();\n for (const node of previous) node.remove();\n this._placeMenu(open.button, open.menu);\n return;\n }\n case 'cancel':\n this._closeMenu(true);\n return;\n case 'confirm-delete': {\n this._closeMenu(true);\n const remove = new CustomEvent<AparteConversationDeleteDetail>(\n 'aparte-conversation-delete',\n { detail: { id }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(remove);\n this._manage(remove, (m) => m.delete(id));\n return;\n }\n }\n }\n\n private _menuFocusables(): HTMLElement[] {\n const open = this._open;\n if (!open) return [];\n return Array.from(open.menu.querySelectorAll<HTMLElement>('[role=\"menuitem\"], [data-menu-action]'));\n }\n\n private _onMenuKeydown(e: KeyboardEvent): void {\n const items = this._menuFocusables();\n // The focus as this element's own tree reports it — retargeted to the shadow host\n // in `document`, which made the index -1 and every arrow jump from the first item.\n const index = items.indexOf(activeElementIn(this) as HTMLElement);\n const focusAt = (i: number): void => {\n e.preventDefault();\n items[(i + items.length) % items.length]?.focus();\n };\n switch (e.key) {\n case 'Escape':\n e.preventDefault();\n this._closeMenu(true);\n return;\n case 'Tab':\n // APG: Tab leaves the menu. Land on the button rather than on whatever\n // follows a node that no longer exists.\n e.preventDefault();\n this._closeMenu(true);\n return;\n case 'ArrowDown': focusAt(index + 1); return;\n case 'ArrowUp': focusAt(index - 1); return;\n case 'Home': focusAt(0); return;\n case 'End': focusAt(items.length - 1); return;\n }\n }\n\n // ─── Rename ───────────────────────────────────────────────────────────\n\n private _startRename(row: HTMLElement, conv: AparteConversationListItem): void {\n const select = row.querySelector<HTMLElement>('.aparte-conv-item__select');\n if (!select) return;\n const input = document.createElement('input');\n input.type = 'text';\n input.className = 'aparte-field aparte-field--sm aparte-conv-item__input';\n input.value = conv.title;\n input.setAttribute('aria-label', this._t('conversationTitle'));\n input.dataset['renameId'] = conv.id;\n select.replaceWith(input);\n this._renaming = { id: conv.id, input, done: false };\n input.addEventListener('blur', (e) => {\n const next = (e as FocusEvent).relatedTarget as Node | null;\n // Restore when the focus is going nowhere (`<body>`) or into the list this\n // render is about to destroy. Not when it is heading for a live element\n // outside — pulling it back from there is the theft `_onFocusOut` avoids.\n this._finishRename(true, !next || this.contains(next));\n });\n input.focus();\n input.select();\n }\n\n /**\n * One exit for the three ways out (Enter, Escape, leaving the field), guarded so\n * the blur a removal causes cannot commit a second time. The row is re-rendered\n * from the array either way: the list shows data, and the host's re-assignment is\n * what makes the new title appear.\n *\n * `restoreFocus` puts the keyboard back on the row: every exit destroys the field\n * the reader was in, so without it the focus lands on `<body>` and the next Tab\n * restarts at the top of the page. The caller decides — the blur path says no when\n * the focus is already heading somewhere live outside this list.\n */\n private _finishRename(commit: boolean, restoreFocus: boolean): void {\n const renaming = this._renaming;\n if (!renaming || renaming.done) return;\n renaming.done = true;\n const conv = this._conversations.find(c => c.id === renaming.id);\n const title = renaming.input.value.trim();\n this._render();\n if (commit && conv && title && title !== conv.title) {\n const rename = new CustomEvent<AparteConversationRenameDetail>(\n 'aparte-conversation-rename',\n { detail: { id: conv.id, title }, bubbles: true, composed: true, cancelable: true }\n );\n this.dispatchEvent(rename);\n this._manage(rename, (m) => m.updateTitle(conv.id, title));\n }\n // After the event, not before: a host that re-assigns `conversations` on it\n // re-renders the list, and the button focused beforehand is gone by now.\n if (restoreFocus) {\n this.querySelector<HTMLElement>(`[data-select-id=\"${cssEscape(renaming.id)}\"]`)?.focus(); // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n }\n }\n\n // ─── Events ───────────────────────────────────────────────────────────\n\n private _onClick = (e: Event): void => {\n const target = e.target as HTMLElement;\n const menuAction = target.closest<HTMLElement>('[data-menu-action]');\n if (menuAction && this._open?.menu.contains(menuAction)) {\n e.stopPropagation();\n this._onMenuAction(menuAction.dataset['menuAction']!);\n return;\n }\n const more = target.closest<HTMLElement>('[data-more-id]');\n if (more) {\n e.stopPropagation();\n if (this._open?.button === more) {\n this._closeMenu(true);\n } else {\n this._openMenu(more.closest<HTMLElement>('[data-conv-id]')!, more);\n }\n return;\n }\n if (target.closest('[data-rename-id]')) return;\n const select = target.closest<HTMLElement>('[data-select-id]');\n if (select) {\n this._closeMenu();\n this.dispatchEvent(new CustomEvent<AparteConversationSelectDetail>(\n 'aparte-conversation-select',\n { detail: { id: select.dataset['selectId']! }, bubbles: true, composed: true }\n ));\n }\n };\n\n private _onKeydown = (e: KeyboardEvent): void => {\n const target = e.target as HTMLElement;\n if (this._open?.menu.contains(target)) {\n this._onMenuKeydown(e);\n return;\n }\n if (target.matches('[data-rename-id]')) {\n if (e.key === 'Enter') { e.preventDefault(); this._finishRename(true, true); }\n else if (e.key === 'Escape') { e.preventDefault(); this._finishRename(false, true); }\n return;\n }\n // The rows are real buttons: Enter and Space are the browser's.\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.querySelector('[data-select-id]')?.setAttribute('aria-current', isActive ? 'page' : 'false');\n });\n }\n}\n\nif (!customElements.get('aparte-conversation-list')) customElements.define('aparte-conversation-list', AparteConversationList);\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from '../composer/aparte-composer.js';\nimport { chatBoundaryOf } from '../../utils/chat-boundary.js';\nimport { isTranscriptEmpty } from '../../utils/transcript.js';\n\n/**\n * One prompt starter: the text sent, or a visible label over a longer prompt.\n *\n * A string is both. The object form is for the case every welcome screen has —\n * \"Write a haiku\" on the chip, \"Write a haiku about web components.\" in the request.\n */\nexport type AparteSuggestion = string | { label: string; prompt?: string };\n\n/** Detail of `aparte-suggestion`: what was shown and what will be sent. */\nexport interface AparteSuggestionEventDetail {\n label: string;\n prompt: string;\n}\n\nconst isSuggestion = (value: unknown): value is AparteSuggestion =>\n typeof value === 'string'\n || (typeof value === 'object' && value !== null && typeof (value as { label?: unknown }).label === 'string');\n\n/**\n * The `suggestions` JSON, from wherever it came: the attribute, or the property when a\n * framework hands the same string to the setter. One parse, so the two channels the\n * docblock calls equivalent actually are.\n */\nconst parseSuggestions = (raw: string): AparteSuggestion[] => {\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) ? parsed.filter(isSuggestion) : [];\n } catch {\n console.warn('[aparte-suggestions] `suggestions` is not a JSON array. Use [\"…\"] or [{\"label\": \"…\", \"prompt\": \"…\"}].');\n return [];\n }\n};\n\nconst labelOf = (s: AparteSuggestion): string => (typeof s === 'string' ? s : s.label);\nconst promptOf = (s: AparteSuggestion): string => (typeof s === 'string' ? s : (s.prompt ?? s.label));\n\n/**\n * Prompt starters — a row of suggested prompts a reader clicks instead of typing.\n *\n * Every chat product opens on three or four of these. This element is that pattern\n * done once, wearing the button recipe.\n *\n * A click goes THROUGH the composer — `setValue()` then `submit()` — never a synthetic\n * `aparte-send`. The composer's `submit()` is where every gate lives (disabled, already\n * streaming, `requireModelSelection`), and a chip that bypassed them could send a\n * request with an empty model id while the composer was visibly greyed out. So the\n * element needs a composer: the nearest `<aparte-composer>` ancestor, else the one\n * serving the chat named by `target`, else the first one in the document.\n *\n * Core honours the click alone, so it is live by default. `aparte-suggestion` fires first and is cancelable — `preventDefault()`\n * leaves the composer untouched, for an app that wants the prompt for itself.\n *\n * It declares no custom property of its own: the gap comes from the global\n * `--aparte-space-*` tokens and the chips from the `aparte-btn` recipe, so it inherits\n * a theme rather than exposing knobs to re-set.\n *\n * @element aparte-suggestions\n *\n * @attr {string} suggestions - The starters, as a JSON array: strings, or\n * `{ \"label\", \"prompt\" }` objects when the visible label and the sent text differ.\n * The `suggestions` PROPERTY takes the same shape without the JSON.\n * @attr {string} mode - `send` (default): the click fills the composer and submits.\n * `fill`: the click fills the composer and focuses it, so the reader edits first.\n * @attr {boolean} empty-only - Shows the row only while the conversation it serves is\n * empty: hidden (`hidden`) once a message is in its transcript or on its way — a\n * thread being loaded counts — and shown again when the transcript is cleared, by a\n * new chat for instance. The composer's send hides it at once, before its message\n * lands. The element sets and lifts `hidden` itself and never lifts one you set.\n * With no transcript to read — a composer outside any chat — the first send hides it\n * until the attribute is removed.\n * @attr {string} target - The id of the `<aparte-chat>` whose composer should receive\n * the click, when the element is not inside that composer.\n * @attr {boolean} data-empty - Reflected BY the element while it has no suggestion to\n * show; the stylesheet hides it then. Read-only, do not set it yourself.\n *\n * @fires {CustomEvent<AparteSuggestionEventDetail>} aparte-suggestion - A starter was\n * clicked. Bubbles, and is cancelable: `preventDefault()` stops the composer from\n * being filled or submitted.\n *\n * @example\n * <!-- The starters sit above the input, inside the composer; a click fills and sends. -->\n * <aparte-composer>\n * <aparte-suggestions empty-only\n * suggestions='[\"What is aparté?\", {\"label\": \"Write a haiku\", \"prompt\": \"Write a haiku about web components.\"}]'>\n * </aparte-suggestions>\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 * </div>\n * </aparte-composer>\n */\nexport class AparteSuggestions extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['suggestions', 'target', 'empty-only'];\n }\n\n private _suggestions: AparteSuggestion[] = [];\n private _composer: AparteComposer | null = null;\n\n /** The viewport of the chat this row serves, while `empty-only` follows it. */\n private _transcript: Element | null = null;\n private _transcriptObserver: MutationObserver | null = null;\n /** The composer sent since the transcript was last seen empty. */\n private _sent = false;\n private _wasEmpty = true;\n /** `hidden` was written by this element, so it is this element's to lift. */\n private _hidOwn = false;\n\n /*\n * `empty-only` follows the transcript: shown while it is empty, hidden once it has\n * messages, so a restored thread (nothing sent yet) has no starters on top of it\n * and a new chat brings them back. The send is the other half, because it comes first — the row\n * hides on the click rather than a frame later when the bubble lands — and because\n * it is all there is to read when the messages are not `<aparte-chat-bubble>`s\n * (a wrapper's `renderBubble`) or there is no transcript at all. A transcript seen\n * going from full to empty is a new conversation, which forgets the send.\n */\n private _onSend = (): void => {\n if (!this.hasAttribute('empty-only')) return;\n // A composer built after this element had no chat around it yet at connection.\n if (!this._transcript) this._watchTranscript();\n this._sent = true;\n this._applyEmptyOnly();\n };\n\n private _applyEmptyOnly = (): void => {\n const empty = this._transcript ? isTranscriptEmpty(this._transcript) : true;\n if (empty && !this._wasEmpty) this._sent = false;\n this._wasEmpty = empty;\n this._setHidden(!empty || this._sent);\n };\n\n private _onConfigChange = (): void => {\n const row = this.querySelector('.aparte-suggestions');\n if (row) row.setAttribute('aria-label', this._groupLabel());\n };\n\n /** The starters. Setting it re-renders; the `suggestions` attribute is the JSON form. */\n get suggestions(): AparteSuggestion[] {\n return this._suggestions;\n }\n\n /**\n * Takes the array, or the JSON string the attribute documents.\n *\n * Both, because both arrive: React 19 and Svelte assign the PROPERTY whenever the\n * element has one of that name, so `<aparte-suggestions suggestions='[…]'>` — the\n * documented spelling, typed valid by both wrappers — reaches this setter as a\n * string while the attribute (and so `attributeChangedCallback`) is never written.\n */\n set suggestions(value: AparteSuggestion[] | string) {\n this._suggestions = typeof value === 'string'\n ? parseSuggestions(value)\n : Array.isArray(value) ? value.filter(isSuggestion) : [];\n if (this.isConnected) this._render();\n }\n\n connectedCallback(): void {\n if (this.hasAttribute('suggestions')) this._readAttribute();\n this._render();\n this._watchComposer();\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n this._composer?.removeEventListener('aparte-send', this._onSend);\n this._composer = null;\n this._transcriptObserver?.disconnect();\n this._transcriptObserver = null;\n this._transcript = null;\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n attributeChangedCallback(name: string): void {\n if (!this.isConnected) return;\n if (name === 'suggestions') {\n this._readAttribute();\n this._render();\n } else if (name === 'target') {\n this._watchComposer();\n } else if (name === 'empty-only') {\n this._watchTranscript();\n if (!this.hasAttribute('empty-only')) {\n this._sent = false;\n this._setHidden(false);\n }\n }\n }\n\n private _groupLabel(): string {\n return resolveConfig(this).t('suggestionsLabel') || 'Suggested prompts';\n }\n\n private _parsed: string | null = null;\n\n /**\n * Parse the `suggestions` attribute once per value. The attribute callback and\n * `connectedCallback` both arrive for a parsed element, in an order the parser\n * decides; without this, an invalid value warned twice.\n */\n private _readAttribute(): void {\n const raw = this.getAttribute('suggestions');\n if (raw === null || raw === this._parsed) return;\n this._parsed = raw;\n this._suggestions = parseSuggestions(raw);\n }\n\n /**\n * The composer the click goes through, resolved the way the composer's own parts\n * resolve it — by ancestry first. Outside a composer, `target` names the chat and the\n * composer serving it is either inside that chat or points at it; the first composer\n * on the page is the last resort, so the one-chat page needs no attribute at all.\n */\n private _resolveComposer(): AparteComposer | null {\n const nearest = this.closest('aparte-composer');\n if (nearest) return nearest as AparteComposer;\n const target = this.getAttribute('target');\n if (target) {\n const scope = document.getElementById(target);\n const inside = scope?.querySelector('aparte-composer');\n if (inside) return inside as AparteComposer;\n for (const composer of document.querySelectorAll('aparte-composer[target]')) {\n if (composer.getAttribute('target') === target) return composer as AparteComposer;\n }\n }\n return document.querySelector('aparte-composer') as AparteComposer | null;\n }\n\n private _watchComposer(): void {\n this._composer?.removeEventListener('aparte-send', this._onSend);\n this._composer = this._resolveComposer();\n this._composer?.addEventListener('aparte-send', this._onSend);\n this._watchTranscript();\n }\n\n /**\n * The transcript of the chat the click goes to: the chat around the composer, else\n * the one around this element, else the one `target` names (here, or on that\n * composer). No last resort on the page, unlike the composer's: following another\n * chat's transcript would be worse than following none, which falls back to the send.\n */\n private _resolveTranscript(): Element | null {\n const id = this.getAttribute('target') ?? this._composer?.getAttribute('target') ?? null;\n const chat = chatBoundaryOf(this._composer) ?? chatBoundaryOf(this) ?? (id ? document.getElementById(id) : null);\n if (!chat) return null;\n return chat.matches('aparte-chat-viewport') ? chat : chat.querySelector('aparte-chat-viewport');\n }\n\n /** Follow the transcript while `empty-only` is set, and stop following it when it is not. */\n private _watchTranscript(): void {\n const transcript = this.hasAttribute('empty-only') ? this._resolveTranscript() : null;\n if (transcript !== this._transcript) {\n this._transcriptObserver?.disconnect();\n this._transcriptObserver = null;\n this._transcript = transcript;\n if (transcript) {\n // Bubbles land anywhere under the viewport; `loading` is the other way of\n // not being empty. The same watch `<aparte-chat>` runs for `data-empty`.\n this._transcriptObserver = new MutationObserver(this._applyEmptyOnly);\n this._transcriptObserver.observe(transcript, {\n childList: true, subtree: true, attributes: true, attributeFilter: ['loading'],\n });\n }\n }\n if (this.hasAttribute('empty-only')) this._applyEmptyOnly();\n }\n\n /** Set `hidden`, or lift it only when this element set it: a `hidden` the page wrote stays. */\n private _setHidden(hide: boolean): void {\n if (hide) {\n if (this.hidden) return;\n this.hidden = true;\n this._hidOwn = true;\n } else if (this._hidOwn) {\n this.hidden = false;\n this._hidOwn = false;\n }\n }\n\n private _render(): void {\n this.replaceChildren();\n if (this._suggestions.length === 0) {\n this.setAttribute('data-empty', '');\n return;\n }\n this.removeAttribute('data-empty');\n const row = document.createElement('div');\n row.className = 'aparte-suggestions';\n row.setAttribute('role', 'group');\n row.setAttribute('aria-label', this._groupLabel());\n for (const suggestion of this._suggestions) {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'aparte-btn aparte-btn--surface aparte-btn--pill aparte-suggestion';\n button.textContent = labelOf(suggestion);\n const prompt = promptOf(suggestion);\n // The full prompt on hover when the chip shows a shorter label for it.\n if (prompt !== button.textContent) button.title = prompt;\n button.addEventListener('click', () => this._activate(suggestion));\n row.appendChild(button);\n }\n this.appendChild(row);\n }\n\n private _activate(suggestion: AparteSuggestion): void {\n const detail: AparteSuggestionEventDetail = { label: labelOf(suggestion), prompt: promptOf(suggestion) };\n const proceed = this.dispatchEvent(new CustomEvent<AparteSuggestionEventDetail>('aparte-suggestion', {\n detail, bubbles: true, composed: true, cancelable: true,\n }));\n if (!proceed) return;\n // Resolved again at click time: on a page that builds its composer after this\n // element, the one found at connection may have been nothing.\n const composer = this._composer ?? this._resolveComposer();\n if (!composer) {\n console.warn('[aparte-suggestions] no <aparte-composer> to send through — put the element inside one, or point `target` at the chat it serves.');\n return;\n }\n if (composer !== this._composer) this._watchComposer();\n const chipHadFocus = this.contains(document.activeElement);\n composer.setValue(detail.prompt);\n if (this.getAttribute('mode') === 'fill') composer.focus();\n else {\n composer.submit();\n // The chip that sent may go with its row (`empty-only`), and the focus with it,\n // to <body>. The person's next move is the composer, so it goes there.\n if (chipHadFocus) composer.focus();\n }\n }\n}\n\nif (!customElements.get('aparte-suggestions')) {\n customElements.define('aparte-suggestions', AparteSuggestions);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteMessageDoneEventDetail, AparteCompactDoneEventDetail, AparteCompactEventDetail } from '../../types/events.js';\n\n/** How full the window is, against the two thresholds. */\nexport type AparteContextLevel = 'ok' | 'warn' | 'danger';\n\n/** Detail of `aparte-context-threshold`: the level just entered, and the numbers behind it. */\nexport interface AparteContextThresholdEventDetail {\n level: AparteContextLevel;\n /** Tokens the last turn reported as used — the prompt sent plus the reply. */\n used: number;\n /** The window those tokens are measured against. */\n window: number;\n /** `used / window`, capped at 1. */\n ratio: number;\n /** The chat this gauge watches, when it has an id. */\n targetId?: string;\n}\n\nconst fraction = (raw: string | null, fallback: number): number => {\n const n = Number(raw);\n return raw !== null && Number.isFinite(n) && n > 0 && n <= 1 ? n : fallback;\n};\n\nconst el = (tag: string, className: string): HTMLElement => {\n const node = document.createElement(tag);\n node.className = className;\n return node;\n};\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\n/**\n * The ring: a track and a value circle on a 36-unit box. `pathLength=\"100\"` makes the\n * value's dash the percentage itself (`stroke-dasharray: <ratio> 100`, in the sheet), so\n * the element sets one custom property per render and no geometry.\n */\nconst ringSvg = (): SVGSVGElement => {\n const svg = document.createElementNS(SVG_NS, 'svg');\n svg.setAttribute('class', 'aparte-context__ring');\n svg.setAttribute('viewBox', '0 0 36 36');\n for (const part of ['track', 'value'] as const) {\n const circle = document.createElementNS(SVG_NS, 'circle');\n circle.setAttribute('class', `aparte-context__${part}`);\n circle.setAttribute('cx', '18');\n circle.setAttribute('cy', '18');\n circle.setAttribute('r', '15.5');\n if (part === 'value') circle.setAttribute('pathLength', '100');\n svg.appendChild(circle);\n }\n return svg;\n};\n\n/**\n * A gauge of the model's context window: how much of it the conversation uses.\n *\n * It reads two numbers and draws their ratio. The USED part is what each turn reports —\n * `aparte-message-done` carries the provider's usage, and the prompt tokens of the\n * last call are the size of the context as the model saw it. The WINDOW is the\n * current model's `contextWindow` (a provider's `/models` fetch fills it in), or the\n * `window` attribute when you know better. With no window, or before the first turn,\n * it renders nothing — an affordance with nothing to show has no chrome to leave.\n *\n * Two thresholds turn it `warn` then `danger` (`data-level`), and crossing one fires\n * `aparte-context-threshold`. With `auto-compact`, a turn that ends in `danger` dispatches\n * `aparte-compact` for its chat — one request at a time, asked again on the next turn in\n * `danger` once the plugin has answered. What answers the command\n * is `@aparte/plugin-compaction` (`setupCompaction()`): it summarises what no longer\n * fits the window and keeps the recent turns. Core itself does not compact — the\n * gauge asks, the plugin does, and a page without the plugin gets a gauge that only\n * measures.\n *\n * The bar wears the `aparte-progress` recipe. `variant=\"ring\"` draws the same reading\n * as a ring with the percentage beside it — for a toolbar, where a bar wants a width\n * and a ring wants none; the full reading is the ring's `title`. The two share the\n * levels, the events and the accessible name; only the drawing differs.\n *\n * @element aparte-context\n *\n * @attr {number} window - The context window, in tokens. Overrides the current model's `contextWindow`.\n * @attr {number} used - Tokens in use before a turn of this page has reported any: a restored conversation's last usage. The next turn's usage replaces it.\n * @attr {number} warn - Fraction of the window at which the level turns `warn`. Default `0.75`.\n * @attr {number} danger - Fraction at which it turns `danger`. Default `0.9`.\n * @attr {boolean} auto-compact - Dispatch `aparte-compact` on each turn that ends in `danger`, one request at a time.\n * @attr {string} target - The id of the `<aparte-chat>` to watch, when the element is not under it.\n * @attr {string} variant - `bar` (default) or `ring`: a progress bar with the reading beside it, or a ring with the percentage.\n * @attr {string} data-level - Reflected BY the element: `ok`, `warn` or `danger`. Read-only.\n * @attr {boolean} data-empty - Reflected BY the element while it has nothing to show. Read-only.\n *\n * @fires {CustomEvent<AparteContextThresholdEventDetail>} aparte-context-threshold - The level changed. Bubbles.\n * @fires {CustomEvent<AparteCompactEventDetail>} aparte-compact - Dispatched on `window` when `auto-compact` is set and a turn ends in `danger`: compact this target's transcript. One request at a time — the next turn in `danger` asks again once the plugin has answered — and only while the attribute is present. `@aparte/plugin-compaction` answers it; with no listener nothing happens.\n *\n * @cssprop [--aparte-context-ring-size=22px] - Diameter of the ring variant.\n *\n * @example\n * <!-- The same gauge twice: the bar takes the room it is given, the ring takes none. -->\n * <aparte-composer-toolbar>\n * <aparte-context window=\"128000\" auto-compact style=\"flex: 1\"></aparte-context>\n * <aparte-context window=\"128000\" variant=\"ring\"></aparte-context>\n * </aparte-composer-toolbar>\n *\n * <script>\n * // The gauge reads what each turn reports; it draws nothing before the first one.\n * // This is a provider's usage after a long conversation: 78% of a 128k window,\n * // past the `warn` threshold.\n * window.dispatchEvent(new CustomEvent('aparte-message-done', {\n * detail: { usage: { inputTokens: 99400, outputTokens: 600 } },\n * }));\n * </script>\n */\nexport class AparteContext extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['window', 'warn', 'danger', 'target', 'variant', 'used'];\n }\n\n private _used: number | null = null;\n private _level: AparteContextLevel | null = null;\n private _compactRequested = false;\n\n private _onDone = (e: Event): void => {\n const detail = (e as CustomEvent<AparteMessageDoneEventDetail>).detail;\n if (!detail?.usage || !this._isMine(detail.targetId)) return;\n const { usage } = detail;\n this._used = usage.totalTokens ?? (usage.inputTokens + usage.outputTokens);\n this._render();\n };\n\n /**\n * After a real compaction the context is smaller by an amount nobody has measured\n * yet — the next turn's usage will say. Until then the gauge shows nothing rather\n * than a number it knows to be wrong. The event names the chat it compacted, so on a\n * multi-chat page only that chat's gauge resets; an unnamed one resets every gauge,\n * and each is right again one turn later.\n */\n private _onCompacted = (e: Event): void => {\n const detail = (e as CustomEvent<AparteCompactDoneEventDetail>).detail;\n if (!this._isMine(detail?.targetId)) return;\n // The request is spent whatever the outcome. A skip (`empty`, `nothing-to-drop`,\n // `running`, `streaming`) used to return before this line, so with the usage\n // still climbing the level never left `danger` and the gauge never asked again\n // for the life of the element — one refused compaction latched auto-compact off.\n this._compactRequested = false;\n if (detail?.skipped) return;\n this._used = null;\n this._render();\n };\n\n /** A failed compaction spends the request too; the next `danger` asks again. */\n private _onCompactError = (e: Event): void => {\n const detail = (e as CustomEvent<{ targetId?: string }>).detail;\n if (!this._isMine(detail?.targetId)) return;\n this._compactRequested = false;\n };\n\n private _onRerender = (): void => this._render();\n\n /** Tokens the last turn reported — or `used` set, until a turn reports — else `null`. */\n get used(): number | null {\n return this._used;\n }\n\n /**\n * Writes the `used` attribute: a reading for a conversation restored without a turn of\n * this page, which had nothing to report until the next one. `null` clears it.\n */\n set used(value: number | null) {\n if (value === null || value === undefined) this.removeAttribute('used');\n else this.setAttribute('used', String(value));\n }\n\n /** The window in force — the attribute, else the current model's. */\n get window(): number | null {\n return this._window();\n }\n\n /**\n * Writes the `window` attribute; `null` gives the model's own window back.\n *\n * The setter has to exist: React 19 and Svelte assign the PROPERTY whenever the\n * element has one of that name, so `<aparte-context window={8000}>` — a spelling\n * both wrappers type as valid — hit a getter alone and threw.\n */\n set window(value: number | null) {\n if (value === null || value === undefined) this.removeAttribute('window');\n else this.setAttribute('window', String(value));\n }\n\n /** The current level, or `null` while nothing is shown. */\n get level(): AparteContextLevel | null {\n return this._level;\n }\n\n connectedCallback(): void {\n window.addEventListener('aparte-message-done', this._onDone);\n window.addEventListener('aparte-compact-done', this._onCompacted);\n window.addEventListener('aparte-compact-error', this._onCompactError);\n // The model's window is read again at every render, so a model change shows\n // on the next turn; no listener on the picker's event, which is an element's.\n window.addEventListener('aparte-config-change', this._onRerender);\n this._render();\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-message-done', this._onDone);\n window.removeEventListener('aparte-compact-done', this._onCompacted);\n window.removeEventListener('aparte-compact-error', this._onCompactError);\n window.removeEventListener('aparte-config-change', this._onRerender);\n }\n\n attributeChangedCallback(name: string): void {\n if (name === 'used') {\n const n = Number(this.getAttribute('used'));\n this._used = this.hasAttribute('used') && Number.isFinite(n) && n >= 0 ? n : null;\n }\n if (this.isConnected) this._render();\n }\n\n private _window(): number | null {\n const attr = Number(this.getAttribute('window'));\n if (this.hasAttribute('window') && Number.isFinite(attr) && attr > 0) return attr;\n const model = resolveConfig(this).getCurrentModel();\n return model?.contextWindow && model.contextWindow > 0 ? model.contextWindow : null;\n }\n\n /** The chat this gauge watches: `target`, else the chat host above it — the composer's rule. */\n private _ownTargetId(): string | undefined {\n const attr = this.getAttribute('target');\n if (attr) return attr;\n let node: HTMLElement | null = this.parentElement;\n while (node) {\n const isHost = node.tagName.toLowerCase() === 'aparte-chat' || node.hasAttribute('data-aparte-chat');\n if (isHost && node.id) return node.id;\n node = node.parentElement;\n }\n return undefined;\n }\n\n private _isMine(targetId?: string): boolean {\n const own = this._ownTargetId();\n return !targetId || !own || targetId === own;\n }\n\n private _render(): void {\n const capacity = this._window();\n if (capacity === null || this._used === null) {\n this.setAttribute('data-empty', '');\n this.removeAttribute('data-level');\n this.replaceChildren();\n this._level = null;\n return;\n }\n this.removeAttribute('data-empty');\n\n const used = this._used;\n const ratio = Math.min(1, used / capacity);\n const warn = fraction(this.getAttribute('warn'), 0.75);\n const danger = fraction(this.getAttribute('danger'), 0.9);\n const level: AparteContextLevel = ratio >= danger ? 'danger' : ratio >= warn ? 'warn' : 'ok';\n this.setAttribute('data-level', level);\n\n const ring = this.getAttribute('variant') === 'ring';\n let root = this.querySelector<HTMLElement>('.aparte-context');\n // A variant change rebuilds: the bar and the ring share nothing but their text.\n if (root && root.classList.contains('aparte-context--ring') !== ring) {\n this.replaceChildren();\n root = null;\n }\n let meter = this.querySelector<HTMLElement | SVGElement>('[role=\"meter\"]');\n let text = this.querySelector<HTMLElement>('.aparte-context__text');\n if (!root || !meter || !text) {\n root = el('div', ring ? 'aparte-context aparte-context--ring' : 'aparte-context');\n meter = ring ? ringSvg() : el('div', 'aparte-progress');\n meter.setAttribute('role', 'meter');\n if (!ring) meter.appendChild(el('div', 'aparte-progress__bar'));\n text = el('span', 'aparte-context__text');\n root.append(meter, text);\n this.replaceChildren(root);\n }\n /*\n * The locale's tag, not the browser's.\n *\n * `Intl.NumberFormat(undefined, …)` means \"follow the BROWSER\", which is not\n * the locale the app chose — the exact bug `AparteLocale.tag` exists to close,\n * and which `<aparte-conversation-list>` and the bubble's clock already read\n * it for. On a French page `128 000` came out `128K` and the ring read `14%`\n * where French writes `14 %`; on `ja-JP`, `12.8万` is the compact notation\n * and the gauge showed `128K`.\n *\n * `|| undefined` and not `??`: an empty tag is not a language, and passing\n * `''` to `Intl` throws a RangeError rather than falling back.\n */\n const cfg = resolveConfig(this);\n const tag = cfg.getLocale().tag || undefined;\n const format = new Intl.NumberFormat(tag, { notation: 'compact', maximumFractionDigits: 1 });\n const reading = `${format.format(used)} / ${format.format(capacity)}`;\n const percent = Math.round(ratio * 100);\n const label = cfg.t('contextLabel') || 'Context window';\n if (ring) {\n const value = meter.querySelector<SVGElement>('.aparte-context__value');\n value?.style.setProperty('--aparte-context-ratio', String(percent));\n // A zero-length dash with round caps is still a dot; at 0 the value is not drawn.\n value?.classList.toggle('aparte-context__value--empty', percent === 0);\n // The ring shows the percentage; the reading behind it is one hover away.\n root.title = reading;\n } else {\n meter.style.setProperty('--aparte-progress-value', String(percent));\n }\n meter.setAttribute('aria-valuemin', '0');\n meter.setAttribute('aria-valuemax', String(capacity));\n meter.setAttribute('aria-valuenow', String(used));\n meter.setAttribute('aria-label', `${label}: ${reading}`);\n // The label is the SAME integer the dash draws — formatted, not rounded again: two\n // roundings of one ratio disagreed by a point on real windows (0.145 → 14 and \"15 %\").\n text.textContent = ring\n ? new Intl.NumberFormat(tag, { style: 'percent', maximumFractionDigits: 0 }).format(percent / 100)\n : reading;\n\n if (level !== this._level) {\n this._level = level;\n const targetId = this._ownTargetId();\n const detail: AparteContextThresholdEventDetail = { level, used, window: capacity, ratio, targetId };\n this.dispatchEvent(new CustomEvent<AparteContextThresholdEventDetail>('aparte-context-threshold', { detail, bubbles: true, composed: true }));\n if (level !== 'danger') this._compactRequested = false;\n }\n // Asked per TURN, not per level change: one request stays open until the\n // plugin answers (done, skipped or failed), and the next turn in danger asks\n // again. Tied to the level change, a refused compaction — nothing to drop yet,\n // a stream in flight — left the level at danger and the gauge silent for good.\n if (level === 'danger' && this.hasAttribute('auto-compact') && !this._compactRequested) {\n this._compactRequested = true;\n window.dispatchEvent(new CustomEvent<AparteCompactEventDetail>('aparte-compact', { detail: { targetId: this._ownTargetId() } }));\n }\n }\n}\n\nif (!customElements.get('aparte-context')) {\n customElements.define('aparte-context', AparteContext);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { activeElementIn } from '../../utils/event-target.js';\n\n/** Detail of `aparte-scroll-rail-jump`: the message a tick points at. */\nexport interface AparteScrollRailJumpDetail {\n messageId: string;\n}\n\n/** What a tick is drawn for: one per user turn (the default), or one per message. */\nexport type AparteScrollRailEvery = 'user' | 'message';\n\nconst EXCERPT_LENGTH = 60;\n/** The tightest pitch the rail will take before it scrolls itself: a 2px line and a 4px gap. */\nconst MIN_PITCH = 6;\n/** How long the transcript has to be still, after a jump, before the observer marks again. */\nconst SETTLE_MS = 150;\n/** How many times a jump re-aligns on the message once the transcript has settled off it. */\nconst MAX_ALIGN_PASSES = 4;\n/** Within this many pixels of the top, a jump has landed. */\nconst ALIGN_TOLERANCE = 1;\n\n/**\n * A rail of ticks beside the transcript — one per turn — that shows where you are in a\n * long conversation and jumps back to any message on a click.\n *\n * The thing every long chat is missing and no product ships natively: the scrollbar says\n * how far down you are, not which question you are under. This is the minimap of a code\n * editor reduced to its ticks, the search marks of a browser's scrollbar, the scrollspy\n * of a documentation site — the same ancestry, applied to turns. A tick per USER message\n * by default, because a user's turns are the questions and a rail with a tick per reply\n * is unreadable on a long thread; `every=\"message\"` marks each one.\n *\n * It owns nothing of the transcript: the viewport keeps its scroll, the bubbles keep\n * their markup, and the rail only READS them — which bubbles exist (a mutation observer\n * on the chat), which one is under the reader (an intersection observer on the scroll\n * surface, never scroll arithmetic), and the first few words of each for the tick's\n * name. A click is a `scrollIntoView` on the bubble, announced first by a cancelable\n * `aparte-scroll-rail-jump` so a host that pages history in can load it before the\n * jump. Honoured by core alone, so it is live by default.\n *\n * Place it as a direct child of `<aparte-chat>` (or the wrapper's `[data-aparte-chat]`\n * host): the stylesheet floats it on the transcript's end edge, centred on the transcript\n * and clear of a classic scrollbar, and hides it under a coarse pointer, where a 2px\n * tick is not a target. Under two ticks it renders nothing — a rail with one mark says\n * nothing. It is a list, not a minimap: it takes the height of its ticks, up to a share\n * of the transcript (`--aparte-scroll-rail-share`, 60%). When more turns exist than 24px\n * targets fit in that, the pitch tightens to what fits, down to a 6px floor; past that\n * the rail scrolls itself so the current tick is always in view, and the arrow keys walk\n * every tick whatever the pitch.\n *\n * @element aparte-scroll-rail\n *\n * @attr {string} target - The id of the `<aparte-chat>` to follow, when the element is not inside it.\n * @attr {string} every - `user` (default): one tick per user turn. `message`: one per message.\n * @attr {boolean} data-empty - Reflected BY the element while it has fewer than two ticks. Read-only.\n *\n * @fires {CustomEvent<AparteScrollRailJumpDetail>} aparte-scroll-rail-jump - A tick was activated. Bubbles, cancelable: `preventDefault()` leaves the transcript where it is.\n *\n * @cssprop [--aparte-scroll-rail-width=max(var(--aparte-scroll-rail-hit-size), calc(var(--aparte-scroll-rail-tick-size) * 1.6))] - The rail's column, the ticks end-aligned in it. Never under one tick's pressable zone: the rail clips, so a narrower column would cut the target back.\n * @cssprop [--aparte-scroll-rail-tick-size=14px] - Length of a tick; the current one is 1.6× that.\n * @cssprop [--aparte-scroll-rail-tick-thickness=2px] - Thickness of a tick.\n * @cssprop [--aparte-scroll-rail-hit-size=24px] - The pressable zone around a tick (WCAG 2.5.8). The drawn line keeps its own size; this one sizes the pseudo-element and, through the gap below, the pitch — so raising it spaces the ticks out rather than overlapping them. When the ticks would not fit, the rail sets it on itself to the pitch that does, never under 6px.\n * @cssprop [--aparte-scroll-rail-gap=calc(var(--aparte-scroll-rail-hit-size) - var(--aparte-scroll-rail-tick-thickness))] - Space between ticks: the zone minus the line, so gap + thickness is exactly the pitch. Set it smaller and the zones overlap.\n * @cssprop [--aparte-scroll-rail-room=calc(var(--aparte-scroll-rail-width) + var(--aparte-space-2))] - The lane the chat leaves at its end edge while the rail is in it, the rail sitting there, so no line runs under the ticks. `0px` lets the rail float over the text.\n * @cssprop [--aparte-scroll-rail-share=.6] - The share of the transcript's height the rail may take. It is the height of its list up to this, centred on the transcript; past it the pitch tightens, then the rail scrolls.\n * @cssprop [--aparte-scroll-rail-bar=0px] - Published BY the element: the width of the transcript's scrollbar when it is a classic one (0 for an overlay bar), which the stylesheet adds to the rail's end inset so the ticks never sit on the bar.\n * @cssprop [--aparte-scroll-rail-block-start=0px] - Published BY the element: the distance from the host's top edge to the transcript's, so the rail centres on the transcript rather than on the composer too.\n * @cssprop [--aparte-scroll-rail-block-end=0px] - Published BY the element: the distance from the transcript's bottom edge to the host's.\n *\n * @example\n * <aparte-chat style=\"height: 18rem\">\n * <aparte-chat-viewport>\n * <aparte-chat-bubble message-id=\"u1\" data-role=\"user\" content=\"What is a web component?\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"a1\" data-role=\"assistant\" name=\"Assistant\" content=\"A custom element: a tag the browser upgrades to a class you wrote. It carries its own markup, behaviour and, if you want, its own styles — and it works in any framework, or none, because it is the platform's own component model.\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"u2\" data-role=\"user\" content=\"And a light-DOM one?\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"a2\" data-role=\"assistant\" name=\"Assistant\" content=\"One that renders its children into the page rather than into a shadow root, so the page's CSS reaches inside it. aparté is light DOM on purpose: a theme is a stylesheet, not an API.\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"u3\" data-role=\"user\" content=\"Which one is this rail?\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"a3\" data-role=\"assistant\" name=\"Assistant\" content=\"Light DOM, like everything else here. Its ticks are plain buttons in a list; scroll the transcript and watch the current one move.\"></aparte-chat-bubble>\n * </aparte-chat-viewport>\n * <aparte-scroll-rail></aparte-scroll-rail>\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 * </div>\n * </aparte-composer>\n * </aparte-chat>\n */\nexport class AparteScrollRail extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['target', 'every'];\n }\n\n private _host: HTMLElement | null = null;\n private _mutations: MutationObserver | null = null;\n private _resizes: ResizeObserver | null = null;\n private _intersections: IntersectionObserver | null = null;\n /** The bubbles the intersection observer watches — compared by identity, so a re-rendered bubble is re-observed. */\n private _observedBubbles: HTMLElement[] = [];\n /** The scroll surface the observer is rooted on, and whose bottom edge is read. */\n private _observedSurface: HTMLElement | null = null;\n private _rebuildQueued = false;\n private _pickQueued = false;\n private _currentId: string | null = null;\n /** Visible ratio per message id in the reading band, from the intersection observer. */\n private _visible = new Map<string, number>();\n /** The bubbles on screen at all, from the second observer; only the last one is read. */\n private _onScreen = new Set<string>();\n private _onScreenObserver: IntersectionObserver | null = null;\n /** The mark a jump asked for, held until the transcript has stopped moving. */\n private _held: string | null = null;\n private _settleTimer: ReturnType<typeof setTimeout> | null = null;\n private _settleSurface: HTMLElement | null = null;\n /** Re-alignments done for the jump in flight. */\n private _alignPasses = 0;\n /** The custom properties written on this element, so each is written only when it changes. */\n private _written = new Map<string, string>();\n\n private _onConfigChange = (): void => this._rebuild();\n private _onSurfaceScroll = (): void => this._bumpSettle();\n\n /** The id of the message the rail currently marks, or `null`. */\n get currentMessageId(): string | null {\n return this._currentId;\n }\n\n connectedCallback(): void {\n if (!this.classList.contains('aparte-scroll-rail')) this.classList.add('aparte-scroll-rail');\n if (!this.getAttribute('role')) this.setAttribute('role', 'navigation');\n this.addEventListener('click', this._onClick);\n this.addEventListener('keydown', this._onKeydown);\n window.addEventListener('aparte-config-change', this._onConfigChange);\n this._attach();\n }\n\n disconnectedCallback(): void {\n this._detach();\n this.removeEventListener('click', this._onClick);\n this.removeEventListener('keydown', this._onKeydown);\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n attributeChangedCallback(name: string): void {\n if (!this.isConnected) return;\n if (name === 'target') this._attach();\n else this._rebuild();\n }\n\n // ─── Wiring ───────────────────────────────────────────────────────────\n\n /** The chat this rail follows: `target`, else the host above it — the gauge's rule. */\n private _findHost(): HTMLElement | null {\n const target = this.getAttribute('target');\n if (target) return document.getElementById(target);\n let node: HTMLElement | null = this.parentElement;\n while (node) {\n if (node.tagName.toLowerCase() === 'aparte-chat' || node.hasAttribute('data-aparte-chat')) return node;\n node = node.parentElement;\n }\n return null;\n }\n\n private _attach(): void {\n this._detach();\n this._host = this._findHost();\n if (!this._host) {\n this._rebuild();\n return;\n }\n // One observer on the host, subtree-wide: it sees a bubble appended to the\n // viewport AND the viewport itself appearing, which a wrapper mounts after the\n // rail on some frameworks. The rail is in that subtree too, and its own rebuilds\n // are mutations: those are dropped here, or the rail rebuilds itself every frame\n // for ever (measured at 61 to 146 rebuilds a second, at rest, each with a fresh\n // intersection observer — and nothing on a tick survived a frame).\n if (typeof MutationObserver !== 'undefined') {\n this._mutations = new MutationObserver((records) => {\n if (records.some((record) => !this.contains(record.target))) this._queueRebuild();\n });\n this._mutations.observe(this._host, { childList: true, subtree: true, characterData: true });\n }\n // The rail spans the transcript and sits clear of its scrollbar, both measured:\n // re-measured when the chat or the transcript changes size (a composer that grows).\n if (typeof ResizeObserver !== 'undefined') {\n this._resizes = new ResizeObserver(() => this._layout());\n this._resizes.observe(this._host);\n const viewport = this._viewport();\n if (viewport) this._resizes.observe(viewport);\n }\n this._rebuild();\n }\n\n private _detach(): void {\n this._mutations?.disconnect();\n this._mutations = null;\n this._resizes?.disconnect();\n this._resizes = null;\n this._unobserve();\n this._release();\n this._host = null;\n }\n\n /** Coalesce a burst of mutations (a streaming reply is one per token) into one rebuild. */\n private _queueRebuild(): void {\n if (this._rebuildQueued) return;\n this._rebuildQueued = true;\n requestAnimationFrame(() => {\n this._rebuildQueued = false;\n this._rebuild();\n });\n }\n\n private _viewport(): HTMLElement | null {\n return this._host?.querySelector<HTMLElement>('aparte-chat-viewport') ?? null;\n }\n\n /** The element that scrolls: the vanilla container, or the viewport itself under a wrapper. */\n private _scrollSurface(): HTMLElement | null {\n const viewport = this._viewport();\n if (!viewport) return null;\n return viewport.querySelector<HTMLElement>('.aparte-viewport-container') ?? viewport;\n }\n\n private _bubbles(): HTMLElement[] {\n const viewport = this._viewport();\n if (!viewport) return [];\n const every: AparteScrollRailEvery = this.getAttribute('every') === 'message' ? 'message' : 'user';\n const all = Array.from(viewport.querySelectorAll<HTMLElement>('aparte-chat-bubble[message-id]'));\n return every === 'message' ? all : all.filter((b) => b.getAttribute('data-role') === 'user');\n }\n\n // ─── Rendering ────────────────────────────────────────────────────────\n\n /** The first words of a message, for the tick's name. An excerpt, never a summary. */\n private _excerpt(bubble: HTMLElement): string {\n const body = bubble.querySelector<HTMLElement>('.aparte-message-content') ?? bubble;\n const text = (body.textContent ?? '').replace(/\\s+/g, ' ').trim();\n return text.length > EXCERPT_LENGTH ? `${text.slice(0, EXCERPT_LENGTH - 1).trimEnd()}…` : text;\n }\n\n private _tickLabel(bubble: HTMLElement): string {\n const locale = resolveConfig(this).getLocale();\n const role = bubble.getAttribute('data-role') === 'user'\n ? (locale.roleNameUser ?? 'You')\n : (locale.roleNameAssistant ?? 'Assistant');\n const excerpt = this._excerpt(bubble);\n return excerpt ? `${role}: ${excerpt}` : role;\n }\n\n private _list(): HTMLOListElement | null {\n return this.querySelector<HTMLOListElement>(':scope > .aparte-scroll-rail__list');\n }\n\n /**\n * Reconcile the ticks with the bubbles, by message id. A tick that already exists is\n * kept — the same node, so the focus on it, its hover and its tooltip survive a turn\n * being appended or a reply streaming beside it — moved only when its order changed,\n * and dropped when its bubble left.\n */\n private _rebuild(): void {\n const bubbles = this._bubbles();\n const locale = resolveConfig(this).getLocale();\n // Written only when the value changes: a reconcile runs on every host mutation, and\n // an attribute rewritten to the value it already has is still a DOM mutation —\n // measured on CI's WebKit, where a late one landed inside the rail's rest window.\n const railLabel = locale.scrollRailLabel ?? 'Conversation outline';\n if (this.getAttribute('aria-label') !== railLabel) this.setAttribute('aria-label', railLabel);\n\n if (bubbles.length < 2) {\n this.setAttribute('data-empty', '');\n this.replaceChildren();\n this._unobserve();\n this._currentId = null;\n this._write('--aparte-scroll-rail-hit-size', '');\n return;\n }\n this.removeAttribute('data-empty');\n\n let list = this._list();\n if (!list) {\n list = document.createElement('ol');\n list.className = 'aparte-scroll-rail__list';\n this.replaceChildren(list);\n }\n const existing = new Map<string, HTMLLIElement>();\n for (const item of Array.from(list.children) as HTMLLIElement[]) {\n const id = item.firstElementChild instanceof HTMLElement ? item.firstElementChild.dataset['messageId'] : undefined;\n if (id) existing.set(id, item);\n }\n const known = new Set<string>();\n let index = 0;\n for (const bubble of bubbles) {\n const id = bubble.getAttribute('message-id')!;\n known.add(id);\n let item = existing.get(id);\n if (item) {\n existing.delete(id);\n } else {\n item = document.createElement('li');\n const tick = document.createElement('button');\n tick.type = 'button';\n tick.className = 'aparte-scroll-rail__tick';\n tick.dataset['messageId'] = id;\n item.appendChild(tick);\n }\n const tick = item.firstElementChild as HTMLElement;\n const label = this._tickLabel(bubble);\n if (tick.getAttribute('aria-label') !== label) {\n tick.setAttribute('aria-label', label);\n tick.title = label;\n }\n if (id === this._currentId) {\n if (tick.getAttribute('aria-current') !== 'true') tick.setAttribute('aria-current', 'true');\n } else {\n tick.removeAttribute('aria-current');\n }\n const at = list.children[index] ?? null;\n if (at !== item) list.insertBefore(item, at);\n index++;\n }\n for (const stale of existing.values()) stale.remove();\n\n if (this._currentId && !known.has(this._currentId)) this._currentId = null;\n if (!sameElements(this._observedBubbles, bubbles)) this._observe(bubbles);\n this._layout();\n }\n\n /**\n * Which tick is current is read, not computed: the scroll surface is the root and an\n * intersection observer reports which bubbles reach a reading band — the top 30% of\n * the surface, from its very top so that the bubble a jump aligned there counts. The\n * rule is a scrollspy's: the LAST question that has reached the band is the one the\n * reader is under. (The largest visible share would name the first of three questions\n * sitting in the band at the bottom of a thread of short turns.) The one scroll\n * position read is the bottom, because there the reader is under the latest question\n * whatever the band holds, and the observer has nothing to report on the last few\n * pixels.\n */\n private _observe(bubbles: HTMLElement[]): void {\n this._unobserve();\n this._observedBubbles = bubbles;\n if (typeof IntersectionObserver === 'undefined') return;\n const root = this._scrollSurface();\n if (!root) return;\n this._intersections = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).getAttribute('message-id');\n if (id) this._visible.set(id, entry.isIntersecting ? entry.intersectionRatio : 0);\n }\n this._pickCurrent();\n }, { root, rootMargin: '0px 0px -70% 0px', threshold: [0, 0.1, 0.25, 0.5, 0.75, 1] });\n // The whole surface, for one question only: the last. While the end of the thread\n // is on screen the reader is at the end, whatever the band holds — after a send,\n // the viewport rests with the new question low on the screen and older ones in\n // the band, and a rail marking the question above the one just asked is wrong.\n this._onScreenObserver = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).getAttribute('message-id');\n if (!id) continue;\n if (entry.isIntersecting) this._onScreen.add(id);\n else this._onScreen.delete(id);\n }\n this._pickCurrent();\n }, { root, threshold: [0] });\n for (const bubble of bubbles) {\n this._intersections.observe(bubble);\n this._onScreenObserver.observe(bubble);\n }\n this._observedSurface = root;\n root.addEventListener('scroll', this._onRootScroll, { passive: true });\n }\n\n private _unobserve(): void {\n this._intersections?.disconnect();\n this._intersections = null;\n this._onScreenObserver?.disconnect();\n this._onScreenObserver = null;\n this._observedSurface?.removeEventListener('scroll', this._onRootScroll);\n this._observedSurface = null;\n this._observedBubbles = [];\n this._visible.clear();\n this._onScreen.clear();\n }\n\n /** A scroll only matters at the bottom edge, where the observer is silent: one pick per frame. */\n private _onRootScroll = (): void => {\n if (this._pickQueued) return;\n this._pickQueued = true;\n requestAnimationFrame(() => {\n this._pickQueued = false;\n this._pickCurrent();\n });\n };\n\n /** At the bottom edge of a surface that scrolls; a surface with nothing to scroll has no bottom to be at. */\n private _atBottom(): boolean {\n const surface = this._observedSurface;\n if (!surface) return false;\n const max = surface.scrollHeight - surface.clientHeight;\n return max > 0 && max - surface.scrollTop <= 2;\n }\n\n private _pickCurrent(): void {\n // A jump said which mark is right; the band's mid-scroll opinions wait for it to settle.\n if (this._held) return;\n const bubbles = this._observedBubbles;\n if (bubbles.length === 0) return;\n const lastId = bubbles[bubbles.length - 1]!.getAttribute('message-id')!;\n if (this._atBottom() || this._onScreen.has(lastId)) {\n this._setCurrent(lastId);\n return;\n }\n let last: string | null = null;\n for (const bubble of bubbles) {\n const id = bubble.getAttribute('message-id')!;\n if ((this._visible.get(id) ?? 0) > 0) last = id;\n }\n // Nothing in the band (the reader is deep in one reply): keep the last mark\n // rather than clearing it.\n if (last !== null) this._setCurrent(last);\n }\n\n private _setCurrent(id: string | null): void {\n if (this._currentId === id) return;\n this._currentId = id;\n for (const tick of this.querySelectorAll<HTMLElement>('.aparte-scroll-rail__tick')) {\n if (tick.dataset['messageId'] === id) {\n tick.setAttribute('aria-current', 'true');\n this._reveal(tick);\n } else {\n tick.removeAttribute('aria-current');\n }\n }\n }\n\n /** Past the floor the rail scrolls itself: the current tick is kept inside its window. */\n private _reveal(tick: HTMLElement): void {\n const room = this.clientHeight;\n if (!room) return;\n const top = tick.offsetTop;\n const bottom = top + tick.offsetHeight;\n if (top < this.scrollTop) this.scrollTop = top;\n else if (bottom > this.scrollTop + room) this.scrollTop = bottom - room;\n }\n\n // ─── Geometry ─────────────────────────────────────────────────────────\n\n /** Write a custom property on this element only when its value changed; '' removes it. */\n private _write(name: string, value: string): void {\n if (this._written.get(name) === value) return;\n this._written.set(name, value);\n if (value) this.style.setProperty(name, value);\n else this.style.removeProperty(name);\n }\n\n /**\n * The three measurements the stylesheet positions the rail by, and the pitch.\n *\n * The scrollbar: `offsetWidth − clientWidth` of the scroll surface is the width of a\n * classic bar and 0 for an overlay one, so the rail sits clear of the bar exactly\n * where there is one. The extent: the transcript's box within the host, so the rail\n * spans the messages and not the composer under them. The pitch: the stylesheet's\n * 24px targets whenever `count × 24` fits the rail; otherwise the pitch that does,\n * never under `MIN_PITCH` — the rail sets `--aparte-scroll-rail-hit-size` on itself\n * and the gap, the padding and the zone all follow it, as they do for a consumer's\n * value. Past the floor the rail scrolls (`_reveal`), and the arrows still walk it.\n */\n private _layout(): void {\n const host = this._host;\n const viewport = this._viewport();\n const surface = this._scrollSurface();\n if (host && surface) {\n this._write('--aparte-scroll-rail-bar', `${Math.max(0, surface.offsetWidth - surface.clientWidth)}px`);\n }\n if (host && viewport) {\n const v = viewport.getBoundingClientRect();\n const h = host.getBoundingClientRect();\n if (v.height > 0 && h.height > 0) {\n this._write('--aparte-scroll-rail-block-start', `${Math.max(0, Math.round(v.top - h.top))}px`);\n this._write('--aparte-scroll-rail-block-end', `${Math.max(0, Math.round(h.bottom - v.bottom))}px`);\n }\n }\n const count = this._list()?.children.length ?? 0;\n const height = this.clientHeight;\n if (!count || !height) {\n this._write('--aparte-scroll-rail-hit-size', '');\n this._write('--aparte-scroll-rail-gap', '');\n return;\n }\n // The default pitch is the theme's, read above this element so that the value the\n // rail itself wrote is never mistaken for it.\n const above = host ?? document.documentElement;\n const aboveStyle = getComputedStyle(above);\n const hit = parseFloat(aboveStyle.getPropertyValue('--aparte-scroll-rail-hit-size')) || 24;\n const thickness = parseFloat(aboveStyle.getPropertyValue('--aparte-scroll-rail-tick-thickness')) || 2;\n if (count * hit <= height) {\n this._write('--aparte-scroll-rail-hit-size', '');\n this._write('--aparte-scroll-rail-gap', '');\n return;\n }\n const pitch = Math.max(MIN_PITCH, Math.floor(height / count));\n this._write('--aparte-scroll-rail-hit-size', `${pitch}px`);\n // The theme derives the gap from the hit size on `:root`, and a `var()` resolves\n // where the property is DECLARED, not where it is read: the tightened hit size\n // set here would leave the gap at its root value. So the gap is written too.\n this._write('--aparte-scroll-rail-gap', `${Math.max(0, pitch - thickness)}px`);\n }\n\n // ─── Interaction ──────────────────────────────────────────────────────\n\n private _onClick = (e: Event): void => {\n const tick = (e.target as HTMLElement).closest<HTMLElement>('.aparte-scroll-rail__tick');\n if (!tick || !this.contains(tick)) return;\n const messageId = tick.dataset['messageId'];\n if (!messageId) return;\n this.jumpTo(messageId);\n };\n\n /** The ticks are buttons; the arrows walk them, as in any toolbar. */\n private _onKeydown = (e: KeyboardEvent): void => {\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp' && e.key !== 'Home' && e.key !== 'End') return;\n const ticks = Array.from(this.querySelectorAll<HTMLElement>('.aparte-scroll-rail__tick'));\n // The focus as the rail's own tree reports it: `document.activeElement` is the\n // shadow HOST for a rail mounted in a consumer's root, so the index was -1 and\n // every arrow bailed out silently.\n const index = ticks.indexOf(activeElementIn(this) as HTMLElement);\n if (index === -1) return;\n e.preventDefault();\n const next = e.key === 'Home' ? 0\n : e.key === 'End' ? ticks.length - 1\n : e.key === 'ArrowDown' ? Math.min(ticks.length - 1, index + 1)\n : Math.max(0, index - 1);\n ticks[next]?.focus();\n };\n\n /**\n * Scroll the transcript to a message. Announced first, cancelable; then a\n * `scrollIntoView` on the bubble, smooth unless the reader prefers reduced motion.\n * The clicked tick is the mark until the transcript has stopped moving: while a\n * smooth scroll crosses other questions the band would name each in turn.\n *\n * Where the scroll lands is checked once it has settled, and corrected. The bubbles\n * carry `content-visibility: auto`, so a bubble far from the reader has an estimated\n * height until it is rendered: the scroll aims at an estimate, the bubbles above it\n * take their real size on the way, and the message ends up above or below the top,\n * the further away the worse.\n * A second `scrollIntoView` from a rendered neighbourhood lands; a few are allowed.\n */\n jumpTo(messageId: string): void {\n const announced = this.dispatchEvent(new CustomEvent<AparteScrollRailJumpDetail>('aparte-scroll-rail-jump', {\n detail: { messageId },\n bubbles: true,\n composed: true,\n cancelable: true,\n }));\n if (!announced) return;\n const bubble = this._bubbleOf(messageId);\n if (!bubble) return;\n // The reader chose a message, not the bottom: the follow stops first. Mid-stream a\n // jump into history never rendered shortens the transcript at its first scroll event\n // (the bubbles leave their 320px fallback), which the viewport reads as a clamp of\n // its own — armed, it pinned the reader straight back to the bottom.\n (this._viewport() as (HTMLElement & { setAutoScroll?: (enabled: boolean) => void }) | null)?.setAutoScroll?.(false);\n this._scrollTo(bubble);\n this._setCurrent(messageId);\n this._hold(messageId);\n }\n\n private _bubbleOf(messageId: string): HTMLElement | null {\n return this._viewport()?.querySelector<HTMLElement>(`aparte-chat-bubble[message-id=\"${cssEscape(messageId)}\"]`) ?? null; // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n }\n\n private _scrollTo(bubble: HTMLElement): void {\n const reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n bubble.scrollIntoView({ block: 'start', behavior: reduced ? 'auto' : 'smooth' });\n }\n\n /** Keep `id` as the mark until the scroll surface has been still for `SETTLE_MS`. */\n private _hold(id: string): void {\n this._release();\n this._held = id;\n this._alignPasses = 0;\n this._settleSurface = this._scrollSurface();\n this._settleSurface?.addEventListener('scroll', this._onSurfaceScroll);\n // An instant scroll, or none at all, fires no scroll event: the first timer settles it.\n this._bumpSettle();\n }\n\n private _bumpSettle(): void {\n if (this._settleTimer !== null) clearTimeout(this._settleTimer);\n this._settleTimer = setTimeout(() => this._settle(), SETTLE_MS);\n }\n\n /** The transcript is still: land on the message if the scroll did not, else release the hold. */\n private _settle(): void {\n const id = this._held;\n const surface = this._settleSurface;\n const bubble = id ? this._bubbleOf(id) : null;\n if (id && bubble && surface && this._alignPasses < MAX_ALIGN_PASSES) {\n // Measured from where `scrollIntoView` aims: the surface's top, below its scroll\n // padding (`--aparte-top-inset`, a header floating over the transcript).\n const aim = surface.getBoundingClientRect().top + (parseFloat(getComputedStyle(surface).scrollPaddingTop) || 0);\n const off = bubble.getBoundingClientRect().top - aim;\n if (Math.abs(off) > ALIGN_TOLERANCE) {\n this._alignPasses++;\n this._scrollTo(bubble);\n this._bumpSettle();\n return;\n }\n }\n this._release();\n if (id) this._setCurrent(id);\n }\n\n private _release(): void {\n if (this._settleTimer !== null) clearTimeout(this._settleTimer);\n this._settleTimer = null;\n this._settleSurface?.removeEventListener('scroll', this._onSurfaceScroll);\n this._settleSurface = null;\n this._held = null;\n }\n}\n\nfunction sameElements(a: HTMLElement[], b: HTMLElement[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n}\n\nif (!customElements.get('aparte-scroll-rail')) {\n customElements.define('aparte-scroll-rail', AparteScrollRail);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { presenceOn } from '../../utils/presence.js';\nimport { activeElementIn, eventOrigin, rootOf } from '../../utils/event-target.js';\nimport { foldText, filterConversationRows } from '../../utils/fold-text.js';\n\n/** Detail of `aparte-sidebar-toggle`: the state just entered. */\nexport interface AparteSidebarToggleDetail {\n collapsed: boolean;\n /** True while the sidebar is a drawer — the window is under the breakpoint. */\n drawer: boolean;\n}\n\n/** The window width under which the sidebar becomes a drawer. Mirrored in `sidebar.css`. */\nconst DRAWER_QUERY = '(max-width: 48rem)';\n\n/**\n * The column beside the chat — conversations, a new-chat button, a search field, an\n * account row — as an element, because it has behaviour: it collapses, it becomes a\n * drawer on a narrow window, and its search field filters the conversation list.\n *\n * What it does NOT own is its content. Children are yours, in the order you want them:\n * a `.aparte-sidebar__header`, a `.aparte-sidebar__search`, a `.aparte-sidebar__body`\n * holding the `<aparte-conversation-list>`, a `.aparte-sidebar__footer`. The\n * stylesheet lays those out and draws nothing you did not put there — the recipe\n * (`.aparte-sidebar`, in `styles/shell/sidebar.css`) is the look, this element is\n * the three behaviours.\n *\n * **Collapse.** `collapsed` is an attribute, reflected, so the host can set it, read\n * it and persist it. Any element carrying `data-aparte-sidebar-toggle` anywhere on\n * the page toggles the nearest sidebar (or the one whose id the attribute names), so\n * a hamburger in the header needs no script — and the sidebar keeps `aria-expanded`\n * and `aria-controls` on that control in step, whoever changed the state (it gives\n * itself an id when the host wrote none). `aparte-sidebar-toggle` fires on every\n * change, whoever caused it.\n *\n * **Drawer.** Under 48rem of window the sidebar leaves the flow and slides over the\n * page (`data-drawer`, set by the element from a media query); open, it draws a\n * scrim, moves the focus to its first focusable child, closes on Escape from anywhere\n * on the page or on a click outside, and hands the focus back to the control that\n * opened it. Nothing here is a portal: the drawer is the same element in the same\n * place, positioned fixed, which is all a sidebar needs and what a dialog would not\n * get away with. Collapsed — folded as a column or slid off as a drawer — it carries\n * `inert` and `aria-hidden`, so nothing invisible keeps a tab stop.\n *\n * **Search.** An input carrying `data-aparte-sidebar-search` filters the conversation\n * list below it by title as the user types — rows that do not match are hidden, and a\n * date group with nothing left hides with them. Client-side, on the titles the list\n * already has: an index over message bodies is the storage adapter's business.\n *\n * @element aparte-sidebar\n *\n * @attr {boolean} collapsed - Hidden (in the flow) or closed (as a drawer). Reflected; set it to start closed.\n * @attr {string} breakpoint - The window width under which the sidebar becomes a drawer: a length (default `48rem`), or `none` for a column that never does.\n * @attr {boolean} data-drawer - Reflected BY the element while the window is narrower than `breakpoint` (48rem unless you set it). Read-only.\n *\n * @fires {CustomEvent<AparteSidebarToggleDetail>} aparte-sidebar-toggle - The sidebar opened or closed, by a toggle, by Escape, by a click on the scrim or by `collapsed` being set. Bubbles.\n *\n * @cssprop [--aparte-sidebar-width=260px] - Width of the column, and of the drawer.\n * @cssprop [--aparte-sidebar-inset=var(--aparte-space-3)] - The inline inset of the four\n * regions (header, search, body, footer): one value, so their content stands on one axis.\n * @cssprop [--aparte-sidebar-bg=var(--aparte-surface-2)] - Its ground.\n *\n * @example\n * <!-- The sidebar alone, as a column: its header, a search field that filters the list,\n * the list, a footer. The shell it sits in — header, chat — is the app-shell guide's.\n * breakpoint=\"none\" keeps it a column at any width; without it, under 48rem of\n * window it becomes a drawer behind a [data-aparte-sidebar-toggle] control. -->\n * <aparte-sidebar breakpoint=\"none\" style=\"height: 22rem\">\n * <div class=\"aparte-sidebar__header\">\n * <span class=\"aparte-sidebar__brand\">aparté</span>\n * <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm\" type=\"button\" aria-label=\"New chat\">\n * <aparte-icon name=\"edit\"></aparte-icon>\n * </button>\n * </div>\n * <div class=\"aparte-sidebar__search aparte-field-group\">\n * <input class=\"aparte-field aparte-field--sm\" type=\"search\" placeholder=\"Search conversations\" data-aparte-sidebar-search>\n * </div>\n * <div class=\"aparte-sidebar__body\">\n * <aparte-conversation-list active-id=\"c1\"></aparte-conversation-list>\n * </div>\n * <div class=\"aparte-sidebar__footer\">\n * <span class=\"aparte-avatar aparte-avatar--sm\">P</span> Paul\n * </div>\n * </aparte-sidebar>\n * <script>\n * const day = 864e5;\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() - day },\n * { id: 'c3', title: 'Tokens, not selectors', updatedAt: Date.now() - 4 * day },\n * { id: 'c4', title: 'The first release', updatedAt: Date.now() - 60 * day },\n * ];\n * </script>\n */\nexport class AparteSidebar extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['collapsed', 'breakpoint'];\n }\n\n private _media: MediaQueryList | null = null;\n private _scrim: HTMLElement | null = null;\n /** The control that last opened the drawer, to hand the focus back to. */\n private _opener: HTMLElement | null = null;\n private _lastCollapsed: boolean | null = null;\n\n /**\n * `connectedCallback` has run and the element knows what its markup asked for.\n *\n * Not `isConnected`: during an UPGRADE the element is already in the document, so\n * `attributeChangedCallback` fires for every authored attribute — connected, and\n * before `connectedCallback` — which is the ordinary case for a server-rendered\n * `<aparte-sidebar collapsed>` upgraded when the module loads. Announcing there\n * tells the host its sidebar just closed when all that happened is that the markup\n * was read, and says `drawer: false` because the media query has not run yet.\n */\n private _ready = false;\n\n /**\n * The host (or the markup) asked for the column to be folded, so widening the\n * window must not unfold it. Only a collapse taken OUTSIDE the drawer state counts:\n * dismissing an overlay says nothing about what a wide window should show.\n */\n private _closedByHost = false;\n\n /**\n * `_closedByHost` has been taken from the markup once. A re-parent runs\n * `connectedCallback` again, and by then `collapsed` may be the BREAKPOINT's own\n * write — a drawer closes itself on the way in — so reading it back a second time\n * records the element's own doing as the host's word and the column never reopens.\n * Like `_initialCaptured` on `<aparte-split>`, it must survive the move.\n */\n private _hostIntentSeeded = false;\n\n /** Raised around `_applyDrawer`'s own writes, so the breakpoint is never read as a host's intent. */\n private _auto = false;\n\n /**\n * The element wrote `inert`/`aria-hidden` and may take them back. A host that inerts\n * the sidebar behind its own modal wrote them itself, and `_syncHidden` runs on every\n * breakpoint re-evaluation: without this it would un-inert the page behind the overlay.\n * Same convention as `_relabel`, which leaves a host-authored `aria-label` alone.\n */\n private _ownHidden = false;\n /** The query `filter()` was last given, kept so the sidebar's no-match can follow its lists. */\n private _query = '';\n /** Watches the lists' own `data-no-match` and `loading`, which change with their renders. */\n private _listWatch: MutationObserver | null = null;\n\n /** Whether the sidebar is collapsed (hidden in the flow, or closed as a drawer). */\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(value: boolean) {\n this.toggleAttribute('collapsed', presenceOn(value));\n }\n\n /** True while the window is under the breakpoint and the sidebar is a drawer. */\n get drawer(): boolean {\n return this.hasAttribute('data-drawer');\n }\n\n connectedCallback(): void {\n if (!this.classList.contains('aparte-sidebar')) this.classList.add('aparte-sidebar');\n if (!this.getAttribute('role')) this.setAttribute('role', 'complementary');\n this._relabel();\n // Before the breakpoint runs, and only the FIRST time: whatever the markup asked\n // for is the host's word. On a later connect `collapsed` may be our own.\n if (!this._hostIntentSeeded) {\n this._closedByHost = this.collapsed;\n this._hostIntentSeeded = true;\n }\n this.addEventListener('input', this._onInput);\n if (typeof MutationObserver === 'function') {\n this._listWatch = new MutationObserver((records) => {\n if (records.some((r) => r.target !== this)) this._syncNoMatch();\n });\n this._listWatch.observe(this, { subtree: true, attributes: true, attributeFilter: ['data-no-match', 'loading'] });\n }\n // On the DOCUMENT, not on the element: an overlay that only answers Escape when\n // the focus is already inside it answers nobody — the focus is on the page the\n // drawer is covering.\n document.addEventListener('keydown', this._onKeydown);\n document.addEventListener('click', this._onDocumentClick);\n window.addEventListener('aparte-config-change', this._onConfigChange);\n this._watchBreakpoint();\n this._syncHidden();\n // Last: `_watchBreakpoint` may have closed the sidebar on its way in, and that\n // close is the drawer entering, not a change to announce.\n this._lastCollapsed = this.collapsed;\n if (!this.id) this.id = `aparte-sidebar-${++sidebarIdSeq}`;\n this._syncToggles();\n this._ready = true;\n }\n\n /**\n * The drawer's media query, from the `breakpoint` attribute: a length (`48rem`, the\n * default, or `640px`), or `none` for a column that never becomes a drawer — which\n * a narrow host that has room for it wants, and a documentation frame needs.\n */\n private _watchBreakpoint(): void {\n this._media?.removeEventListener('change', this._onMediaChange);\n this._media = null;\n const attr = (this.getAttribute('breakpoint') ?? '').trim();\n if (attr === 'none') {\n this._applyDrawer(false);\n return;\n }\n if (typeof matchMedia !== 'function') return;\n const query = attr ? `(max-width: ${attr})` : DRAWER_QUERY;\n this._media = matchMedia(query);\n this._media.addEventListener('change', this._onMediaChange);\n this._applyDrawer(this._media.matches);\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('input', this._onInput);\n this._listWatch?.disconnect();\n this._listWatch = null;\n document.removeEventListener('keydown', this._onKeydown);\n document.removeEventListener('click', this._onDocumentClick);\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n this._media?.removeEventListener('change', this._onMediaChange);\n this._media = null;\n this._ready = false;\n }\n\n attributeChangedCallback(name: string): void {\n if (!this._ready) return;\n if (name === 'breakpoint') {\n this._watchBreakpoint();\n return;\n }\n if (name !== 'collapsed') return;\n const collapsed = this.collapsed;\n if (collapsed === this._lastCollapsed) return;\n this._lastCollapsed = collapsed;\n // A collapse taken in the flow is a preference about the COLUMN, and survives a\n // resize. One taken over a drawer, or by the breakpoint itself, is not.\n if (!this._auto && !this.drawer) this._closedByHost = collapsed;\n this._syncScrim();\n this._syncHidden();\n this._syncToggles();\n if (collapsed && this.drawer && this._opener?.isConnected) {\n this._opener.focus();\n this._opener = null;\n } else if (!collapsed && this.drawer) {\n this._focusFirst();\n }\n this.dispatchEvent(new CustomEvent<AparteSidebarToggleDetail>('aparte-sidebar-toggle', {\n detail: { collapsed, drawer: this.drawer },\n bubbles: true,\n composed: true,\n }));\n }\n\n // ─── Public API ───────────────────────────────────────────────────────\n\n /** Open the sidebar. `opener` is the control to hand the focus back to when it closes. */\n open(opener?: HTMLElement): void {\n if (opener) this._opener = opener;\n this.collapsed = false;\n }\n\n close(): void {\n this.collapsed = true;\n }\n\n toggle(opener?: HTMLElement): void {\n if (this.collapsed) this.open(opener);\n else this.close();\n }\n\n // ─── Drawer ───────────────────────────────────────────────────────────\n\n private _onMediaChange = (e: MediaQueryListEvent): void => this._applyDrawer(e.matches);\n\n /**\n * Entering the drawer state closes the sidebar: a narrow window that opens on an\n * overlay covering the chat is the wrong first screen. Leaving it reopens, since\n * a wide window has room for the column — unless the host had collapsed it before.\n */\n private _applyDrawer(drawer: boolean): void {\n const was = this.drawer;\n this.toggleAttribute('data-drawer', drawer);\n this._auto = true;\n try {\n if (drawer && !was) this.collapsed = true;\n else if (!drawer && was && !this._closedByHost) this.collapsed = false;\n } finally {\n this._auto = false;\n }\n this._syncScrim();\n this._syncHidden();\n this._syncToggles();\n }\n\n /**\n * Every `[data-aparte-sidebar-toggle]` that drives THIS sidebar says what it does:\n * `aria-expanded` follows the state, `aria-controls` names the element. Synced from\n * the three places the state settles (connect, the attribute, the breakpoint), never\n * from `open()` alone — a close on Escape would otherwise leave an \"expanded\" that\n * lies. The row's own `⋯` button already did this; the sidebar's toggle did not.\n */\n private _syncToggles(): void {\n // Both trees: they are the same one in the ordinary case, and differ when a\n // consumer mounts the chat inside a shadow root of their own — where a control\n // can sit on either side of the boundary and still drive this sidebar.\n const selector = '[data-aparte-sidebar-toggle]';\n const root = rootOf(this);\n const controls = root === document\n ? Array.from(document.querySelectorAll<HTMLElement>(selector))\n : [...root.querySelectorAll<HTMLElement>(selector), ...document.querySelectorAll<HTMLElement>(selector)];\n for (const control of controls) {\n if (!this._drives(control)) continue;\n control.setAttribute('aria-expanded', String(!this.collapsed));\n control.setAttribute('aria-controls', this.id);\n }\n }\n\n /** Whether a toggle control targets this sidebar: named by id, else the nearest one. */\n private _drives(control: HTMLElement): boolean {\n const named = control.getAttribute('data-aparte-sidebar-toggle');\n return named ? named === this.id : nearestSidebar(control) === this;\n }\n\n /**\n * A collapsed sidebar is invisible — folded to nothing in the flow, slid off the\n * screen as a drawer — so it must not keep its tab stops or its place in the\n * accessibility tree. `inert` covers focus and the pointer, `aria-hidden` covers\n * the readers that predate it; a browser without `inert` still gets the second.\n *\n * It removes only what it wrote. Both are standard global attributes a host sets\n * itself — inerting the sidebar behind its own modal is the ordinary pattern — and\n * this runs on every breakpoint re-evaluation, so clearing them unconditionally lets\n * an unrelated resize re-expose the page under an overlay. Same convention as\n * `_relabel`, which leaves a host-authored `aria-label` alone.\n *\n * Neither is declared `@attr`, and `data-drawer` is not the precedent: a `@attr` on a\n * STANDARD name displaces the wrappers' own prop for it — React's JSX props are\n * `Omit<HTMLAttributes, keyof T>`, so declaring `inert` here would retype\n * `<aparte-sidebar inert={busy}>` out of existence. The class docblock says it in prose.\n */\n private _syncHidden(): void {\n if (this.collapsed) {\n this.toggleAttribute('inert', true);\n this.setAttribute('aria-hidden', 'true');\n this._ownHidden = true;\n } else if (this._ownHidden) {\n this.removeAttribute('inert');\n this.removeAttribute('aria-hidden');\n this._ownHidden = false;\n }\n }\n\n /**\n * The drawer covers the page, so the focus has to follow it in — otherwise the next\n * Tab walks the transcript under the overlay. The host owns the children, so this\n * takes the first thing that can hold focus and leaves the rest alone; a drawer with\n * nothing focusable in it keeps the focus where it was, which is the honest outcome.\n */\n private _focusFirst(): void {\n visibleFocusable(this)[0]?.focus();\n }\n\n private _syncScrim(): void {\n const wanted = this.drawer && !this.collapsed;\n if (wanted && !this._scrim) {\n this._scrim = document.createElement('div');\n this._scrim.className = 'aparte-sidebar__scrim';\n this._scrim.setAttribute('aria-hidden', 'true');\n this._scrim.addEventListener('click', () => this.close());\n this.appendChild(this._scrim);\n } else if (!wanted && this._scrim) {\n this._scrim.remove();\n this._scrim = null;\n }\n }\n\n private _onKeydown = (e: KeyboardEvent): void => {\n if (!this.drawer || this.collapsed) return;\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n // The open drawer covers the page, so Tab stays inside it: from the last control\n // back to the first, and Shift+Tab the other way. It used to walk out under the\n // scrim onto the transcript the drawer was covering. Tab is the only path that\n // needs closing — the scrim already takes the pointer — and a `focusin` guard\n // was deliberately NOT added: it would steal the focus back from a dialog the\n // drawer's own content opens onto `<body>`.\n if (e.key === 'Tab') {\n const focusable = visibleFocusable(this);\n if (focusable.length === 0) return;\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n // `activeElementIn`, not `document.activeElement`: inside a consumer's shadow\n // root the latter reads as the shadow host, and the trap then fires on every\n // Tab as if the focus had already left the drawer.\n const active = activeElementIn(this);\n if (!this.contains(active)) {\n e.preventDefault();\n first.focus();\n } else if (e.shiftKey && active === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && active === last) {\n e.preventDefault();\n first.focus();\n }\n }\n };\n\n /** A `[data-aparte-sidebar-toggle]` anywhere toggles this sidebar — no script in the host. */\n private _onDocumentClick = (e: Event): void => {\n // `eventOrigin`, not `e.target`: a click from inside a consumer's shadow root is\n // retargeted to the shadow host by the time it reaches `document`.\n const control = (eventOrigin(e) as HTMLElement | null)?.closest?.<HTMLElement>('[data-aparte-sidebar-toggle]');\n if (!control) return;\n if (!this._drives(control)) return;\n this.toggle(control);\n };\n\n // ─── Search ───────────────────────────────────────────────────────────\n\n private _onInput = (e: Event): void => {\n const input = e.target as HTMLInputElement | null;\n if (!input?.matches?.('[data-aparte-sidebar-search]')) return;\n this.filter(input.value);\n };\n\n /**\n * Filter the conversation lists inside by title (case- and accent-insensitive; an\n * empty query shows everything), and return how many conversations match. Each list\n * keeps the query through its own renders and says when nothing matches; the\n * sidebar carries `data-no-match` then too.\n */\n filter(query: string): number {\n this._query = query;\n let matches = 0;\n let waiting = false;\n for (const list of this._lists()) {\n // A list not upgraded yet has no `filter()`: its rows are the page's markup, and\n // they are filtered here as they always were — and it is handed the query once it\n // upgrades, or its first render would show every row under the query.\n if (typeof list.filter === 'function') matches += list.filter(query);\n else { matches += filterConversationRows(list, foldText(query)); waiting = true; }\n }\n if (waiting && typeof customElements !== 'undefined') {\n void customElements.whenDefined('aparte-conversation-list').then(() => {\n if (this._query === query) for (const list of this._lists()) list.filter?.(query);\n this._syncNoMatch();\n });\n }\n this._syncNoMatch();\n return matches;\n }\n\n private _lists(): Array<HTMLElement & { filter?: (q: string) => number }> {\n return Array.from(this.querySelectorAll<HTMLElement & { filter?: (q: string) => number }>('aparte-conversation-list'));\n }\n\n /**\n * `data-no-match` while the held query matches nothing in any list — read from the lists\n * themselves, so it follows their renders (a conversation that matches arriving, the last\n * match leaving) and is never set while one of them is still loading.\n */\n private _syncNoMatch(): void {\n const needle = foldText(this._query);\n const none = needle !== '' && this._lists().every((list) => (typeof list.filter === 'function'\n ? list.hasAttribute('data-no-match')\n : !list.hasAttribute('loading') && filterConversationRows(list, needle) === 0));\n this.toggleAttribute('data-no-match', none);\n }\n\n // ─── Locale ───────────────────────────────────────────────────────────\n\n private _onConfigChange = (): void => this._relabel();\n\n private _relabel(): void {\n if (this.hasAttribute('aria-label') && this.dataset['ownLabel'] !== 'locale') return;\n const locale = resolveConfig(this).getLocale();\n this.setAttribute('aria-label', locale.sidebarLabel ?? 'Conversations');\n this.dataset['ownLabel'] = 'locale';\n }\n}\n\n/** For the id a sidebar gives itself when the host wrote none, so a toggle can name it. */\nlet sidebarIdSeq = 0;\n\n/** What the drawer hands the focus to when it opens. The host owns the children. */\nconst FOCUSABLE = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n/**\n * The focusables a reader can actually reach. The drawer's own search filter hides rows\n * with `hidden`, and a hidden row's buttons hold no tab stop — so a trap that compared\n * DOM-last rather than reachable-last never fired at the real end of the drawer and let\n * Tab walk out under the scrim.\n */\nfunction visibleFocusable(root: ParentNode): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE))\n .filter((el) => !el.closest('[hidden]') && el.checkVisibility?.() !== false);\n}\n\n/**\n * The sidebar a toggle belongs to: the closest ancestor, else the first in the toggle's\n * own tree, else the first in the document. `rootOf` before `document` is what makes a\n * control that names no id work inside a consumer's shadow root — and what stops a\n * sidebar in the light DOM from answering it.\n */\nfunction nearestSidebar(control: HTMLElement): Element | null {\n return control.closest('aparte-sidebar')\n ?? rootOf(control).querySelector('aparte-sidebar')\n ?? document.querySelector('aparte-sidebar');\n}\n\nif (!customElements.get('aparte-sidebar')) {\n customElements.define('aparte-sidebar', AparteSidebar);\n}\n","/**\n * The split's arithmetic, with no DOM in it.\n *\n * Separate from the element on purpose: a pointer drag is the path jsdom exercises\n * worst — it has no layout, no `PointerEvent` and no pointer capture — so the numbers\n * are proven here, in plain functions a test can call, and the element is left with\n * the listeners and the attributes.\n *\n * Nothing here parses a CSS unit. `--aparte-split-min` and `--aparte-split-max` are\n * clamp bounds in the grid template, so the browser clamps the pane and this module\n * only ever moves a percentage between 0 and 100.\n */\n\n/** One step of a drag: where it started, how far the pointer went, and along which axis. */\nexport interface SplitStep {\n /** The primary pane's size when the gesture began, as a percentage of the container. */\n startPercent: number;\n /** How far the pointer has travelled since, in CSS pixels, along the split's axis. */\n deltaPx: number;\n /** The container's size along that same axis. */\n containerPx: number;\n /** The computed reading direction is right-to-left. Ignored on the vertical axis. */\n rtl: boolean;\n /** The split's orientation is `vertical` — the panes are stacked, the axis is the block one. */\n vertical: boolean;\n /**\n * `primary=\"end\"`: the sized track is the LAST one, so the seam's distance from the\n * container's start is `100% - handle - position` and a bigger number moves the seam\n * TOWARD the start. Without this the seam runs away from the pointer.\n */\n primaryEnd?: boolean;\n}\n\n/** Keep `value` inside `[min, max]`, and answer `min` for anything that is not a number. */\nfunction clamp(value: number, min: number, max: number): number {\n if (!Number.isFinite(value)) return min;\n return value < min ? min : value > max ? max : value;\n}\n\n/**\n * The primary pane's new size, as a percentage of the container, clamped to 0..100.\n *\n * A right-to-left drag moves the other way: in `rtl` the start pane is on the right, so\n * a pointer travelling in +x makes it smaller. The block axis never inverts — `rtl` is a\n * writing direction, not a gravity — so it is ignored when `vertical`.\n *\n * `primaryEnd` inverts it too, and for a different reason: the pane being sized is the\n * one the pointer moves AWAY from. The two inversions compose — an `rtl` split whose\n * primary is the end pane reads like a plain one again — so they are XORed, not summed.\n *\n * A container of zero (measured before layout, or while display:none) yields the\n * starting value rather than `Infinity`: the caller gets a number it can write back.\n */\nexport function nextPosition(step: SplitStep): number {\n const start = Number.isFinite(step.startPercent) ? step.startPercent : 0;\n if (!Number.isFinite(step.containerPx) || step.containerPx <= 0) return clamp(start, 0, 100);\n const inverted = (!step.vertical && step.rtl) !== (step.primaryEnd === true);\n const sign = inverted ? -1 : 1;\n const moved = (sign * step.deltaPx * 100) / step.containerPx;\n return clamp(start + moved, 0, 100);\n}\n\n/**\n * How far a key moves the seam, in percentage points — or `null` for a key that is not\n * ours, so the handler can leave the page's own scrolling alone.\n *\n * Shift is ×10. That is an ecosystem convention (Shoelace's `×10`, Zag's delta of 10),\n * NOT the APG, which specifies only the single step: it is here because a 1% step across\n * a 1600px window is 16px and nobody arrows a pane across a screen one percent at a time.\n *\n * Home and End are the extremes as ±100: the clamp in `nextPosition` — and then the CSS\n * clamp in the grid — decide where that actually lands, so a `--aparte-split-min` of\n * `20rem` means Home stops at 20rem without this function knowing what a rem is.\n *\n * `primaryEnd` mirrors the ARROWS only, for the same reason `nextPosition` mirrors the\n * pointer: an arrow names a direction the SEAM travels in. Home and End name the primary\n * pane's own extremes — its minimum is its minimum whichever end it sits at — so they are\n * left alone.\n */\nexport function keyDelta(\n key: string,\n shift: boolean,\n rtl: boolean,\n vertical: boolean,\n primaryEnd = false,\n): number | null {\n if (key === 'Home') return -100;\n if (key === 'End') return 100;\n const step = (shift ? 10 : 1) * (primaryEnd ? -1 : 1);\n if (vertical) {\n if (key === 'ArrowUp') return -step;\n if (key === 'ArrowDown') return step;\n return null;\n }\n if (key === 'ArrowLeft') return rtl ? step : -step;\n if (key === 'ArrowRight') return rtl ? -step : step;\n return null;\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { nextPosition, keyDelta } from './geometry.js';\nimport { presenceOn } from '../../utils/presence.js';\nimport { eventOrigin, rootOf } from '../../utils/event-target.js';\n\n/** Detail of `aparte-split-resize`: the position that settled, and what settled it. */\nexport interface AparteSplitResizeDetail {\n /** The achieved size of the primary pane, as a percentage of the container. */\n position: number;\n /** The primary pane is folded to its minimum. */\n collapsed: boolean;\n /** The split is showing one pane — under its breakpoint, or told to by `single`. */\n stacked: boolean;\n /** Which pane is shown while stacked. */\n pane: 'start' | 'end';\n /** The container's axis, as the `orientation` attribute names it. */\n orientation: 'horizontal' | 'vertical';\n /** What moved it: a drag, a key, or the host setting the property. */\n source: 'pointer' | 'keyboard' | 'api';\n}\n\n/** The window width under which the split shows one pane. Mirrored in `split.css`'s siblings. */\nconst STACK_QUERY = '(max-width: 48rem)';\n\n/** The primary pane's size when nothing else says — the same number `theme.css` declares. */\nconst DEFAULT_POSITION = 38;\n\n/**\n * How long a retired drag overlay stays in the document after it goes inert. Long\n * enough to outlive the `click` and `dblclick` a press produces on any engine, short\n * enough that nothing accumulates. See `_removeScrim`.\n */\nconst SCRIM_RETIRE_MS = 300;\n\n/** Keep a percentage inside 0..100; anything unreadable answers `fallback`. */\nfunction toPercent(raw: string | null | undefined, fallback: number): number {\n const value = Number.parseFloat(raw ?? '');\n if (!Number.isFinite(value)) return fallback;\n return value < 0 ? 0 : value > 100 ? 100 : value;\n}\n\n/**\n * Two panes and a draggable seam — a chat beside a preview, an editor, an artifact card.\n *\n * The grid is the mechanism: `--aparte-split-position` is the primary pane's size and\n * the min/max are CSS clamp bounds, so the browser clamps and nothing here parses a\n * unit. The element adds only what CSS cannot do — the drag, the arrow keys, the ARIA\n * of an APG window splitter, and one pane at a time under a breakpoint. Without the\n * element the `.aparte-split` recipe is still a split; it just does not move.\n *\n * It stores nothing: `position` in, one `aparte-split-resize` out on commit. The\n * attribute is written on COMMIT only — a release, a key up, a double-click, a property\n * set — and the live value during a drag travels on the custom property, so a\n * framework's reconciler is not in the drag loop. Persist from the event; restore by\n * setting `position`.\n *\n * A pane CONTAINS a chat; a chat never contains a split.\n *\n * `aria-orientation` on the handle is the INVERSE of this element's `orientation`: the\n * attribute names the SEPARATOR's own axis, which is what the APG means by \"Left Arrow\n * moves a vertical splitter\" and what ARIA 1.2 means by the attribute. Do not \"fix\" it —\n * consumers style against it.\n *\n * Shift + an arrow is ±10%. That is an ecosystem convention, not the APG, which\n * specifies the single step alone.\n *\n * @element aparte-split\n *\n * @attr {number} position - The primary pane's size, as a percentage of the container. Reflected on commit, never per drag frame.\n * @attr {string} orientation - `horizontal` (panes side by side, the default) or `vertical` (stacked). Names the CONTAINER's axis.\n * @attr {string} primary - `start` (default) or `end`: which pane `position` sizes.\n * @attr {boolean} collapsed - The primary pane folded to its minimum. Enter on the seam toggles it; a second Enter restores the size it had.\n * @attr {string} breakpoint - Below this width the split shows one pane at a time. A length (default `48rem`), or `none` to never stack.\n * @attr {string} pane - `start` (default) or `end`: which pane is shown while stacked.\n * @attr {boolean} single - Show one pane — the one `pane` names — whatever the width: the seam and the other pane are gone, as under the breakpoint. For a host that decides itself when a second pane exists (a preview with nothing to preview yet). `collapsed` is not this: it folds the primary pane to `--aparte-split-min` and keeps the seam. The CSS route, `.aparte-split--only-start` / `--only-end`, is the same state for a host that owns its breakpoints.\n * @attr {boolean} disabled - No drag, no keys, no tab stop; the seam stays drawn.\n * @attr {string} label - The seam's accessible name. Defaults to the locale's `splitHandleLabel`.\n * @attr {boolean} data-stacked - Written BY the element while one pane is shown. Read-only; style against it.\n *\n * @fires {CustomEvent<AparteSplitResizeDetail>} aparte-split-resize - The position settled: after a drag, a key, a double-click or a property set. Never during a drag — persist from here.\n *\n * @cssprop [--aparte-split-position=38%] - The primary pane's size.\n * @cssprop [--aparte-split-min=min(20rem, 100%)] - Floor of the primary pane. Any length or percentage.\n * @cssprop [--aparte-split-max=60%] - Ceiling of the primary pane.\n * @cssprop [--aparte-split-handle-size=12px] - The seam's track: the grab zone, and what the grid reserves between the panes. The painted line inside it is `--aparte-split-seam-width`.\n * @cssprop [--aparte-split-hit-area=12px] - The invisible grab zone around it. Grows to the touch target on a coarse pointer.\n *\n * @example\n * <!-- The chat in one pane, your own in the other. Drag the seam, or tab to it and use\n * the arrows; double-click resets it. `--aparte-split-min` is a CSS length, so the\n * chat cannot be dragged narrower than 16rem whatever the percentage says. Under\n * 48rem of window the split shows one pane, and the buttons switch it. -->\n * <div class=\"aparte-app-header\">\n * <button class=\"aparte-btn aparte-btn--sm aparte-btn--surface\" type=\"button\" data-aparte-split-pane=\"start\">Chat</button>\n * <button class=\"aparte-btn aparte-btn--sm aparte-btn--surface\" type=\"button\" data-aparte-split-pane=\"end\">Preview</button>\n * </div>\n * <aparte-split position=\"38\" style=\"height: 22rem; --aparte-split-min: 16rem\">\n * <aparte-chat>\n * <aparte-chat-viewport>\n * <aparte-chat-bubble message-id=\"u1\" data-role=\"user\" content=\"Show me the preview beside the chat.\"></aparte-chat-bubble>\n * <aparte-chat-bubble message-id=\"a1\" data-role=\"assistant\" name=\"Assistant\" content=\"It is the pane on the right. Drag the seam to give it more room, or double-click the seam to reset.\"></aparte-chat-bubble>\n * </aparte-chat-viewport>\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 * </div>\n * </aparte-composer>\n * </aparte-chat>\n * <section class=\"aparte-split__pane\">\n * <!-- Your document, not ours: it brings its own colour scheme and typeface. -->\n * <iframe title=\"Preview\" style=\"inline-size: 100%; block-size: 100%; border: 0\"\n * srcdoc=\"<!doctype html><style>:root { color-scheme: light dark; font: 15px/1.5 system-ui, sans-serif; padding: 1rem 1.5rem } h1 { font-size: 1.25rem }</style><h1>Your pane</h1><p>A preview, a document, a map — anything that lives beside the conversation.</p>\"></iframe>\n * </section>\n * </aparte-split>\n * <script>\n * document.querySelector('aparte-split').addEventListener('aparte-split-resize', (e) => {\n * localStorage.setItem('split', String(e.detail.position));\n * });\n * </script>\n */\nexport class AparteSplit extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['position', 'orientation', 'primary', 'collapsed', 'breakpoint', 'pane', 'disabled', 'label', 'single'];\n }\n\n private _handle: HTMLElement | null = null;\n private _scrim: HTMLElement | null = null;\n private _media: MediaQueryList | null = null;\n private _observer: ResizeObserver | null = null;\n /** The pending `_measureBounds()` from a resize, batched to one per frame. */\n private _measureRafId: number | null = null;\n /** The host size the last resize tick measured, so an unchanged one does nothing. */\n private _observedPx = 0;\n\n /** Ids for the panes `aria-controls` names — a counter, the way the primitives do it. */\n private static _paneIdSeq = 0;\n\n /** The live position. The attribute holds it only after a commit. */\n private _position = DEFAULT_POSITION;\n /** What `reset()` — and a double-click on the seam — goes back to. */\n private _initialPosition = DEFAULT_POSITION;\n /**\n * `_initialPosition` has been taken from the markup once. A re-parent runs\n * `connectedCallback` again, and by then the attribute holds the LAST COMMIT, not\n * what the author wrote: capturing a second time makes a framework re-render, a tab\n * switch or a drag-and-drop of the panel redefine what \"reset\" means.\n */\n private _initialCaptured = false;\n /** The size the primary pane had before it was collapsed, for the second Enter. */\n private _preCollapsePosition: number | null = null;\n\n /** Committed values, so a reflection of our own does not read as a host's change. */\n private _lastPosition: number | null = null;\n private _lastCollapsed: boolean | null = null;\n private _lastPane: 'start' | 'end' | null = null;\n\n /** What an attribute-driven commit blames. Raised around a key that changes state. */\n private _source: AparteSplitResizeDetail['source'] = 'api';\n\n /**\n * `connectedCallback` has run and the element knows what its markup asked for.\n *\n * Not `isConnected`: during an UPGRADE the element is already in the document, so\n * `attributeChangedCallback` fires for every authored attribute — connected, and\n * before `connectedCallback` — which is the ordinary case for a server-rendered\n * `<aparte-split position=\"38\">` upgraded when the module loads. Committing there\n * measures a layout the element has not set up yet (no `data-stacked`, so on a\n * phone the probe reads the stacked pane and writes `position=\"84\"`), reflects that\n * over the authored number, and sends it to whatever the host persists.\n */\n private _ready = false;\n\n private _dragging = false;\n private _captured = false;\n /** The gesture actually moved the seam. A press that did not commits nothing. */\n private _moved = false;\n private _pointerId: number | null = null;\n private _dragStartPercent = DEFAULT_POSITION;\n private _dragOriginPx = 0;\n private _dragContainerPx = 0;\n /** A key is down and has moved the seam; its keyup is the commit. */\n private _keying = false;\n\n // ─── Properties ───────────────────────────────────────────────────────\n\n /** The primary pane's size, as a percentage of the container. Setting it commits. */\n get position(): number {\n return this._position;\n }\n\n set position(value: number) {\n const next = toPercent(String(value), this._position);\n if (this.isConnected && next === this._lastPosition) return;\n this._position = next;\n this._setLive(next);\n if (this.isConnected) this._commit('api');\n }\n\n /** The container's axis: `horizontal` (side by side) or `vertical` (stacked). */\n get orientation(): 'horizontal' | 'vertical' {\n return this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n }\n\n set orientation(value: 'horizontal' | 'vertical') {\n this.setAttribute('orientation', value);\n }\n\n /** Which pane `position` sizes. */\n get primary(): 'start' | 'end' {\n return this.getAttribute('primary') === 'end' ? 'end' : 'start';\n }\n\n set primary(value: 'start' | 'end') {\n this.setAttribute('primary', value);\n }\n\n /** The primary pane is folded to its minimum. */\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(value: boolean) {\n this.toggleAttribute('collapsed', presenceOn(value));\n }\n\n /** Which pane is shown while the split is stacked. */\n get pane(): 'start' | 'end' {\n return this.getAttribute('pane') === 'end' ? 'end' : 'start';\n }\n\n set pane(value: 'start' | 'end') {\n this.setAttribute('pane', value);\n }\n\n /** No drag, no keys, no tab stop. The seam stays drawn. */\n get disabled(): boolean {\n return this.hasAttribute('disabled');\n }\n\n set disabled(value: boolean) {\n this.toggleAttribute('disabled', presenceOn(value));\n }\n\n /**\n * True while the split is showing one pane. Read-only.\n *\n * Both routes into that state count. `data-stacked` is the one this element writes\n * from its own breakpoint; `.aparte-split--only-start` / `--only-end` are the CSS\n * route a host takes when it owns its breakpoints and sets `breakpoint=\"none\"`. The\n * sheet gives the two byte-identical rules, so the element has to read them the same\n * way — every guard downstream keys on this getter, and a split stacked by the class\n * alone would measure a one-track grid and commit `position=\"100\"`.\n */\n get stacked(): boolean {\n return (\n this.hasAttribute('data-stacked')\n || this.hasAttribute('single')\n || this.classList.contains('aparte-split--only-start')\n || this.classList.contains('aparte-split--only-end')\n );\n }\n\n /** One pane on demand, whatever the width. Reflected. */\n get single(): boolean {\n return this.hasAttribute('single');\n }\n\n set single(value: boolean) {\n this.toggleAttribute('single', presenceOn(value));\n }\n\n // ─── Public API ───────────────────────────────────────────────────────\n\n /** Fold the primary pane to its minimum, remembering the size it had. */\n collapse(): void {\n this.collapsed = true;\n }\n\n /** Unfold it, back to the size it had before it collapsed. */\n expand(): void {\n this.collapsed = false;\n }\n\n toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n }\n\n /** Show one pane, which only has a visible effect while the split is stacked. */\n showPane(which: 'start' | 'end'): void {\n this.pane = which;\n }\n\n /**\n * Back to the position the split was FIRST connected with — a re-parent restores\n * that, it does not redefine it. What a double-click on the seam does.\n */\n reset(): void {\n this._position = this._initialPosition;\n this._setLive(this._position);\n if (this.isConnected) this._commit('api');\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────\n\n connectedCallback(): void {\n if (!this.classList.contains('aparte-split')) this.classList.add('aparte-split');\n this._handle = this._ensureHandle();\n\n const attr = this.getAttribute('position');\n if (attr !== null) {\n this._position = toPercent(attr, DEFAULT_POSITION);\n this._setLive(this._position);\n } else {\n // No attribute: adopt whatever the cascade already resolves to, and write\n // nothing. That is the server-rendered case — a split styled from CSS alone\n // must not snap to a JS default the moment the element upgrades.\n this._position = toPercent(\n getComputedStyle(this).getPropertyValue('--aparte-split-position'),\n DEFAULT_POSITION,\n );\n }\n if (!this._initialCaptured) {\n this._initialPosition = this._position;\n this._initialCaptured = true;\n }\n // A `collapsed` present in the markup is applied HERE, not by the attribute\n // callback: that one fires during upgrade, before this runs, and early-returns\n // on `!_ready`. Without this the attribute and the render disagree from the\n // first frame — server-rendered `<aparte-split collapsed>` came up open.\n if (this.collapsed) {\n // …but only if it is arriving folded, not COMING BACK folded. `_lastCollapsed`\n // survives `disconnectedCallback` and is reassigned four lines down, so here\n // it still answers \"was it folded when it left\" — and a split that left folded\n // has `position=\"0\"` on it, so capturing again would record 0 as the size to\n // restore and `expand()` would reopen onto nothing.\n if (this._lastCollapsed !== true) this._preCollapsePosition = this._position;\n this._position = 0;\n this._setLive(0);\n }\n this._lastPosition = this._position;\n this._lastCollapsed = this.collapsed;\n this._lastPane = this.pane;\n\n this._stampHandle();\n this._relabel();\n this._measureBounds();\n\n this._handle.addEventListener('pointerdown', this._onPointerDown);\n this._handle.addEventListener('dblclick', this._onDoubleClick);\n this._handle.addEventListener('focusout', this._onFocusOut);\n this.addEventListener('keydown', this._onKeydown);\n this.addEventListener('keyup', this._onKeyup);\n document.addEventListener('click', this._onDocumentClick);\n window.addEventListener('aparte-config-change', this._onConfigChange);\n this._watchBreakpoint();\n this._watchSize();\n // Last: everything above reads the markup directly, and until it has run an\n // attribute change is the upgrade replaying what the author already wrote.\n this._ready = true;\n this._syncPaneControls();\n }\n\n disconnectedCallback(): void {\n this._handle?.removeEventListener('pointerdown', this._onPointerDown);\n this._handle?.removeEventListener('dblclick', this._onDoubleClick);\n this._handle?.removeEventListener('focusout', this._onFocusOut);\n this.removeEventListener('keydown', this._onKeydown);\n this.removeEventListener('keyup', this._onKeyup);\n document.removeEventListener('click', this._onDocumentClick);\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n this._releasePointer();\n this._media?.removeEventListener('change', this._onMediaChange);\n this._media = null;\n this._observer?.disconnect();\n this._observer = null;\n if (this._measureRafId !== null) {\n cancelAnimationFrame(this._measureRafId);\n this._measureRafId = null;\n }\n // A scrim left behind is fixed, full-page and transparent: the whole document\n // would go dead to the pointer with nothing on screen to explain it.\n this._removeScrim();\n this._handle?.removeAttribute('data-dragging');\n this._dragging = false;\n this._keying = false;\n this._ready = false;\n }\n\n attributeChangedCallback(name: string): void {\n if (!this._ready) return;\n switch (name) {\n case 'position': {\n const next = toPercent(this.getAttribute('position'), this._position);\n if (next === this._lastPosition) return;\n this._position = next;\n this._setLive(next);\n this._commit();\n return;\n }\n case 'collapsed': {\n const collapsed = this.collapsed;\n if (collapsed === this._lastCollapsed) return;\n this._lastCollapsed = collapsed;\n if (collapsed) {\n this._preCollapsePosition = this._position;\n this._position = 0;\n } else {\n this._position = this._preCollapsePosition ?? this._initialPosition;\n }\n this._setLive(this._position);\n this._commit();\n return;\n }\n case 'pane': {\n const pane = this.pane;\n this._syncPaneControls();\n if (pane === this._lastPane) return;\n this._lastPane = pane;\n this._commit();\n return;\n }\n case 'orientation':\n case 'primary':\n this._stampHandle();\n this._measureBounds();\n return;\n case 'disabled':\n // A drag in flight on a split that just went inert would keep tracking\n // the pointer and commit on release. Cancel it, restoring the value the\n // press started from: the gesture did not finish, so it did not decide.\n if (this.disabled && this._dragging) this._endDrag('pointer', this._dragStartPercent);\n this._stampHandle();\n return;\n case 'single':\n // Same as a breakpoint crossing for the seam: a drag on a split that just\n // went to one pane has nothing left to size.\n if (this.single && this._dragging) this._endDrag('pointer', this._dragStartPercent);\n this._stampHandle();\n // One pane shown, or both again: the switch says which, or nothing.\n this._syncPaneControls();\n return;\n case 'breakpoint':\n this._watchBreakpoint();\n return;\n case 'label':\n this._relabel();\n }\n }\n\n // ─── Structure ────────────────────────────────────────────────────────\n\n /**\n * The seam. An author-written `.aparte-split__handle` is ADOPTED — a second one\n * would be a second tab stop drawn over the first — otherwise one is inserted\n * between the first two children, which is where a two-pane grid needs it.\n *\n * An adopted one is MOVED there if the author put it elsewhere. The sheet is\n * positional — three tracks with the seam in the middle, and the stacked rules hide\n * `:nth-child(1)` / `:nth-child(3)` — so a handle written first would take the\n * primary pane's track and make stacking hide the wrong child.\n */\n private _ensureHandle(): HTMLElement {\n const own = Array.from(this.children).find(\n (child): child is HTMLElement => child instanceof HTMLElement && child.classList.contains('aparte-split__handle'),\n );\n if (own) {\n if (this.children[1] !== own) {\n const second = Array.from(this.children).filter((child) => child !== own)[1] ?? null;\n this.insertBefore(own, second);\n }\n return own;\n }\n const handle = document.createElement('div');\n handle.className = 'aparte-split__handle';\n const second = this.children[1] ?? null;\n if (second) this.insertBefore(handle, second);\n else this.appendChild(handle);\n return handle;\n }\n\n /** The panes: every element child that is not the seam. */\n private _panes(): HTMLElement[] {\n return Array.from(this.children).filter(\n (child): child is HTMLElement => child instanceof HTMLElement && !child.classList.contains('aparte-split__handle'),\n );\n }\n\n private _primaryPane(): HTMLElement | null {\n const panes = this._panes();\n return (this.primary === 'end' ? panes[panes.length - 1] : panes[0]) ?? null;\n }\n\n /**\n * The APG's window splitter, plus the two states that take it out of the tab order.\n *\n * `aria-orientation` is the INVERSE of `orientation`: the attribute names the\n * separator's own axis, so a split whose panes sit side by side has a VERTICAL\n * separator between them.\n */\n private _stampHandle(): void {\n const handle = this._handle;\n if (!handle) return;\n handle.setAttribute('role', 'separator');\n handle.setAttribute('aria-orientation', this.orientation === 'vertical' ? 'horizontal' : 'vertical');\n if (this.disabled || this.stacked) handle.removeAttribute('tabindex');\n else handle.setAttribute('tabindex', '0');\n // The ACHIEVED size, never the requested one — `_stampHandle` runs again after\n // every `_measureBounds`, so announcing the request here would undo the\n // reconciliation and put valuenow back outside the range it just published.\n handle.setAttribute('aria-valuenow', String(Math.round(this._achievedPercent())));\n const pane = this._primaryPane();\n if (pane) {\n if (!pane.id) pane.id = `aparte-split-pane-${++AparteSplit._paneIdSeq}`;\n handle.setAttribute('aria-controls', pane.id);\n }\n }\n\n // ─── Measurement ──────────────────────────────────────────────────────\n\n private _measure(el: Element): number {\n const box = el.getBoundingClientRect();\n return this.orientation === 'vertical' ? box.height : box.width;\n }\n\n /**\n * What the pane ACTUALLY got, which is not what was asked for: the CSS clamp between\n * `--aparte-split-min` and `--aparte-split-max` has the last word, and announcing the\n * requested value instead is how a splitter comes to tell a screen reader 5% while\n * the pane sits at 22%. Without layout (jsdom, `display: none`, before first paint)\n * the request is all there is, so it stands.\n *\n * While STACKED there is nothing to read: one pane is `display: none` and the other\n * spans the single track, so the ratio is 0 or 100 whatever the seam was set to.\n * Measuring it there would write `position=\"0\"` into the attribute, the event and the\n * host's storage when \"Preview\" is tapped on a phone.\n */\n private _achievedPercent(): number {\n if (this.stacked) return this._position;\n const total = this._measure(this);\n const pane = this._primaryPane();\n if (!pane || !(total > 0)) return this._position;\n return Math.max(0, Math.min(100, (this._measure(pane) / total) * 100));\n }\n\n /**\n * The range the seam can actually reach, written onto the separator: park the pane\n * at 0% and read it, park it at 100% and read it, put the position back. Two forced\n * layouts in one synchronous task, so nothing paints in between.\n *\n * It also reconciles `aria-valuenow`, because the two have to be one story: the\n * requested percentage can sit outside the clamped range the probe just announced —\n * with a `--aparte-split-min` of `20rem`, `position=\"38\"` on a 700px container asks\n * for 38 and gets ~46 — and a separator whose value is below its own minimum is the\n * unclamped-announcement bug this probe exists to prevent, just at rest. `_position`\n * is left alone: it is the REQUESTED value, and `reset()` means it.\n *\n * Gated on `!_dragging`: a ResizeObserver tick mid-gesture would move the pane to\n * both ends under the pointer. Gated on `!stacked` for the reason\n * `_achievedPercent()` is: a pane that spans everything measures the same at 0% and\n * at 100%, which would announce a range of one number.\n */\n private _measureBounds(): void {\n const handle = this._handle;\n if (!handle || this._dragging || this.stacked) return;\n const pane = this._primaryPane();\n const total = this._measure(this);\n if (!pane || !(total > 0)) {\n handle.setAttribute('aria-valuemin', '0');\n handle.setAttribute('aria-valuemax', '100');\n return;\n }\n const saved = this.style.getPropertyValue('--aparte-split-position');\n this.style.setProperty('--aparte-split-position', '0%');\n const min = (this._measure(pane) / total) * 100;\n this.style.setProperty('--aparte-split-position', '100%');\n const max = (this._measure(pane) / total) * 100;\n if (saved) this.style.setProperty('--aparte-split-position', saved);\n else this.style.removeProperty('--aparte-split-position');\n handle.setAttribute('aria-valuemin', String(Math.round(min)));\n handle.setAttribute('aria-valuemax', String(Math.round(max)));\n handle.setAttribute('aria-valuenow', String(Math.round(this._achievedPercent())));\n }\n\n /**\n * Re-measure when the container's size changes — and only then. The probe writes the\n * host's `style` three times, and the reflection is commit-only precisely because a\n * `MutationObserver({ attributes: true })` (the docs' preview frame has one) reposts\n * a frame height per mutation, which resizes the split, which re-ticks this observer.\n * So: ignore a tick that did not move the size, and batch the rest to one per frame.\n */\n private _watchSize(): void {\n if (typeof ResizeObserver !== 'function') return;\n this._observer = new ResizeObserver(() => {\n const px = this._measure(this);\n if (Math.abs(px - this._observedPx) <= 1) return;\n this._observedPx = px;\n if (this._measureRafId !== null) return;\n this._measureRafId = requestAnimationFrame(() => {\n this._measureRafId = null;\n this._measureBounds();\n });\n });\n this._observer.observe(this);\n }\n\n // ─── The one write, and the one event ─────────────────────────────────\n\n private _setLive(value: number): void {\n this.style.setProperty('--aparte-split-position', `${Math.round(value * 1000) / 1000}%`);\n }\n\n /**\n * The commit: reflect, announce, dispatch — once. Everything downstream reads the\n * ACHIEVED size, so the attribute, `aria-valuenow` and the detail are one number.\n *\n * There is no per-frame event on purpose. A bubbling, composed CustomEvent per\n * pointermove is a reconciler in the drag loop, and no pane in this repo re-lays\n * itself out in JS — they resize in CSS. The trigger for adding\n * `aparte-split-resizing` is a consumer whose pane needs a JS measurement mid-drag.\n */\n private _commit(source: AparteSplitResizeDetail['source'] = this._source): void {\n const position = Math.round(this._achievedPercent());\n this._position = position;\n this._setLive(position);\n this._lastPosition = position;\n if (this.getAttribute('position') !== String(position)) this.setAttribute('position', String(position));\n this._handle?.setAttribute('aria-valuenow', String(position));\n this.dispatchEvent(\n new CustomEvent<AparteSplitResizeDetail>('aparte-split-resize', {\n detail: {\n position,\n collapsed: this.collapsed,\n stacked: this.stacked,\n pane: this.pane,\n orientation: this.orientation,\n source,\n },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n // ─── Pointer ──────────────────────────────────────────────────────────\n\n /** The computed direction, never `document.dir`: a host can flip one subtree. */\n private _rtl(): boolean {\n return getComputedStyle(this).direction === 'rtl';\n }\n\n private _onPointerDown = (event: PointerEvent): void => {\n if (this.disabled || this.stacked || this._dragging) return;\n if (event.button > 0) return;\n // Not the browser's default: without this a drag starts a text selection in the\n // pane the pointer crosses. The focus that preventDefault costs is given back\n // below, which Safari would not have given at all.\n event.preventDefault();\n this._handle?.focus();\n this._dragging = true;\n this._captured = false;\n this._moved = false;\n this._pointerId = event.pointerId;\n this._dragStartPercent = this._position;\n this._dragOriginPx = this.orientation === 'vertical' ? event.clientY : event.clientX;\n this._dragContainerPx = this._measure(this);\n this._handle?.setAttribute('data-dragging', '');\n this._addScrim();\n window.addEventListener('pointermove', this._onPointerMove);\n window.addEventListener('pointerup', this._onPointerUp);\n window.addEventListener('pointercancel', this._onPointerCancel);\n };\n\n /**\n * Only the pointer that started the gesture. On a hybrid device a hovering pen or a\n * stray touch delivers a `pointermove` with `buttons === 0`, which would otherwise\n * fall into the release branch below and end someone else's mouse drag.\n */\n private _isOurPointer(event: PointerEvent): boolean {\n return this._pointerId === null || event.pointerId === this._pointerId;\n }\n\n private _onPointerMove = (event: PointerEvent): void => {\n if (!this._dragging || !this._isOurPointer(event)) return;\n // A pointer released over a cross-origin iframe never delivers `pointerup`, so\n // the drag would never end. Checked HERE and only here: on `pointerleave`\n // Firefox reports 0 for a button that is still down, and the drag would end at\n // the first pane the pointer crosses.\n if (event.buttons === 0) {\n this._endDrag('pointer');\n return;\n }\n if (!this._captured && this._pointerId !== null) {\n this._captured = true;\n // Deferred to the FIRST MOVE on purpose. Capturing on pointerdown retargets\n // the pointerup and suppresses the click a dblclick is built from — and\n // double-click is how the seam resets. By the first move the gesture is a\n // drag, not a click, so there is nothing left to suppress.\n try {\n this._handle?.setPointerCapture(this._pointerId);\n } catch {\n // Not every engine has capture for every pointer type; the window\n // listeners are what actually carry the drag.\n }\n }\n const point = this.orientation === 'vertical' ? event.clientY : event.clientX;\n this._position = nextPosition({\n startPercent: this._dragStartPercent,\n deltaPx: point - this._dragOriginPx,\n containerPx: this._dragContainerPx,\n rtl: this._rtl(),\n vertical: this.orientation === 'vertical',\n primaryEnd: this.primary === 'end',\n });\n this._moved = true;\n this._setLive(this._position);\n };\n\n private _onPointerUp = (event: PointerEvent): void => {\n if (this._isOurPointer(event)) this._endDrag('pointer');\n };\n\n private _onPointerCancel = (event: PointerEvent): void => {\n if (this._isOurPointer(event)) this._endDrag('pointer');\n };\n\n private _endDrag(source: AparteSplitResizeDetail['source'], restore?: number): void {\n if (!this._dragging) return;\n const moved = this._moved;\n this._dragging = false;\n this._moved = false;\n this._releasePointer();\n this._handle?.removeAttribute('data-dragging');\n // A gesture that MOVED was captured, so its release already targets the handle\n // and the overlay can go at once. A press that did not move is the one a\n // double-click is built from, and its release landed on the scrim — that one is\n // retired rather than removed. See `_removeScrim`.\n this._removeScrim(!moved);\n if (restore !== undefined) {\n this._position = restore;\n this._setLive(restore);\n }\n // A press that never moved settled nothing. Clicking the seam is how it takes\n // focus, and a double-click is two of them — three events for one reset, two of\n // them carrying the pre-reset value into whatever the host persists.\n if (!moved && restore === undefined) return;\n this._measureBounds();\n this._commit(source);\n }\n\n private _releasePointer(): void {\n window.removeEventListener('pointermove', this._onPointerMove);\n window.removeEventListener('pointerup', this._onPointerUp);\n window.removeEventListener('pointercancel', this._onPointerCancel);\n if (this._captured && this._pointerId !== null) {\n try {\n this._handle?.releasePointerCapture(this._pointerId);\n } catch {\n // Already released, or never captured on this engine.\n }\n }\n this._captured = false;\n this._pointerId = null;\n }\n\n /**\n * The drag overlay: a fixed, transparent child of the HANDLE, alive for one gesture.\n * Topmost, so it swallows an iframe pane's hit-testing — the case this exists for —\n * and paints the drag cursor across the page. A child of the handle rather than of\n * the body so `click`, and therefore `dblclick`, still dispatches at the handle.\n */\n private _addScrim(): void {\n if (this._scrim || !this._handle) return;\n const scrim = document.createElement('div');\n scrim.className = 'aparte-split__scrim';\n scrim.setAttribute('aria-hidden', 'true');\n this._handle.appendChild(scrim);\n this._scrim = scrim;\n }\n\n /**\n * Take the overlay off.\n *\n * `retire` is the press that never moved — the one a click, and then a double-click,\n * is built from. Its release happened ON the scrim (nothing captured the pointer\n * yet), and WebKit works out what a click hit by walking the pointerup target's LIVE\n * ancestors: take the node out here and that chain is rooted nowhere, so no `click`\n * fires at all, no `dblclick` follows, and the seam never resets. Chromium and Gecko\n * resolve the target before dispatch and do not care.\n *\n * So a retired scrim goes INERT immediately — `pointer-events: none`, which is what\n * actually matters, since a full-page overlay left hit-testable is a page dead to the\n * pointer — and leaves the document a moment later. Not on the next task: a first\n * measurement removed it there and it still raced the click under a loaded machine\n * (the trace is in `e2e/tests/layout.spec.ts`). The reference is dropped either way,\n * so the next press builds its own; an inert one on its way out costs nothing.\n *\n * Teardown never retires: `disconnectedCallback` takes the live one out at once.\n */\n private _removeScrim(retire = false): void {\n const scrim = this._scrim;\n this._scrim = null;\n if (!scrim) return;\n if (!retire) {\n scrim.remove();\n return;\n }\n scrim.style.pointerEvents = 'none';\n setTimeout(() => scrim.remove(), SCRIM_RETIRE_MS);\n }\n\n private _onDoubleClick = (): void => {\n if (this.disabled || this.stacked) return;\n this.reset();\n };\n\n // ─── Keyboard ─────────────────────────────────────────────────────────\n\n private _onKeydown = (event: KeyboardEvent): void => {\n if (this._dragging && event.key === 'Escape') {\n event.preventDefault();\n this._endDrag('pointer', this._dragStartPercent);\n return;\n }\n if (this.disabled || this.stacked || event.target !== this._handle) return;\n if (event.key === 'Enter') {\n event.preventDefault();\n this._source = 'keyboard';\n try {\n this.toggleCollapse();\n } finally {\n this._source = 'api';\n }\n return;\n }\n const delta = keyDelta(\n event.key,\n event.shiftKey,\n this._rtl(),\n this.orientation === 'vertical',\n this.primary === 'end',\n );\n // An arrow off this split's axis is the page's, not ours: not swallowing it is\n // what lets a pane scroll while the seam has the focus.\n if (delta === null) return;\n event.preventDefault();\n this._keying = true;\n this._position = toPercent(String(this._position + delta), this._position);\n this._setLive(this._position);\n };\n\n /**\n * The keyup commits, not the keydown: a held arrow repeats, and one release should\n * write one attribute and send one event, not thirty.\n */\n private _onKeyup = (event: KeyboardEvent): void => {\n if (!this._keying || event.target !== this._handle) return;\n if (keyDelta(event.key, event.shiftKey, this._rtl(), this.orientation === 'vertical') === null) return;\n this._keying = false;\n this._commit('keyboard');\n };\n\n /**\n * The other end of a key step. If focus leaves the seam while the key is still down —\n * alt-tab, a click into a pane, a host moving focus — the keyup never arrives, and\n * the pane would stay where the keydown put it while the attribute and\n * `aria-valuenow` kept the old number for good. What is rendered is what commits.\n */\n private _onFocusOut = (): void => {\n if (!this._keying) return;\n this._keying = false;\n this._commit('keyboard');\n };\n\n // ─── The breakpoint ───────────────────────────────────────────────────\n\n /**\n * The stacking query, from the `breakpoint` attribute: a length (`48rem`, the\n * default, or `640px`), or `none` for a split that stays two panes at any width —\n * which a documentation frame needs and a host with its own breakpoint wants.\n */\n private _watchBreakpoint(): void {\n this._media?.removeEventListener('change', this._onMediaChange);\n this._media = null;\n const attr = (this.getAttribute('breakpoint') ?? '').trim();\n if (attr === 'none') {\n this._applyStacked(false);\n return;\n }\n if (typeof matchMedia !== 'function') return;\n const query = attr ? `(max-width: ${attr})` : STACK_QUERY;\n this._media = matchMedia(query);\n this._media.addEventListener('change', this._onMediaChange);\n this._applyStacked(this._media.matches);\n }\n\n private _onMediaChange = (e: MediaQueryListEvent): void => this._applyStacked(e.matches);\n\n /**\n * Entering the stacked state shows the START pane: the chat, never a preview of\n * nothing — the same judgement that makes the sidebar's drawer enter closed. Unless\n * the markup already named one, which is an answer to the same question and a better\n * one. Leaving it restores both panes, and with them the seam and its tab stop.\n *\n * `was` is the ATTRIBUTE, not the `stacked` getter: the getter also counts\n * `.aparte-split--only-*`, the CSS route a host takes when it owns its breakpoints\n * and sets `breakpoint=\"none\"`. Read through the getter, that mount looks like a\n * split leaving a stacked state it never entered — and the `pane` the author wrote\n * is deleted on the way in.\n */\n private _applyStacked(stacked: boolean): void {\n const was = this.hasAttribute('data-stacked');\n // Crossing the breakpoint mid-drag hides the seam under the pointer. Cancel\n // first, while there is still a two-pane layout to measure and restore against.\n if (stacked && !was && this._dragging) this._endDrag('pointer', this._dragStartPercent);\n this.toggleAttribute('data-stacked', stacked);\n if (stacked !== was) this._syncPaneControls();\n if (stacked && !was) {\n if (!this.hasAttribute('pane')) this.showPane('start');\n } else if (!stacked && was) {\n this.removeAttribute('pane');\n }\n // Both writes above can happen before `_ready`, from the mount's own\n // `_watchBreakpoint()` — and `attributeChangedCallback` early-returns there, so\n // nothing records them. Left unstamped, the NEXT `showPane` back to the value the\n // markup held reads as \"no change\" and commits nothing.\n this._lastPane = this.pane;\n this._stampHandle();\n if (stacked !== was) this._measureBounds();\n }\n\n /**\n * A `[data-aparte-split-pane]` anywhere on the page switches a split's pane, so a\n * two-button toggle in a header needs no script.\n *\n * The value is `start` or `end` — the pane to show. Any other non-empty value names\n * a split's `id` and TOGGLES that one; empty toggles the nearest split, else the\n * first on the page. Same shape as `[data-aparte-sidebar-toggle]`.\n */\n private _onDocumentClick = (e: Event): void => {\n // `eventOrigin`, not `e.target`: a click from inside a consumer's shadow root is\n // retargeted to the shadow host by the time it reaches `document`.\n const control = (eventOrigin(e) as HTMLElement | null)?.closest?.<HTMLElement>('[data-aparte-split-pane]');\n if (!control) return;\n const value = (control.getAttribute('data-aparte-split-pane') ?? '').trim();\n const which = value === 'start' || value === 'end' ? value : null;\n const named = which ? '' : value;\n if (named) {\n if (named !== this.id) return;\n } else {\n // `rootOf` before `document`: a button that names no id must find the split\n // beside it inside a consumer's shadow root, which `document` cannot see into.\n const nearest = control.closest('aparte-split')\n ?? rootOf(control).querySelector('aparte-split')\n ?? document.querySelector('aparte-split');\n if (nearest !== this) return;\n }\n this.showPane(which ?? (this.pane === 'start' ? 'end' : 'start'));\n };\n\n /**\n * The two-button switch says which pane it shows: `aria-pressed` on every\n * `[data-aparte-split-pane=\"start\"|\"end\"]` that reaches this split, true on the one\n * naming the pane in view, while the split is stacked; side by side both panes are in\n * view and neither button is pressed, so none carries it. A toggle (an id, or empty) is\n * an action and gets none. Read when the split connects, whenever `pane` changes and\n * when it stacks or unstacks, so a control added later is stamped at the next switch.\n */\n private _syncPaneControls(): void {\n const roots = new Set<ParentNode>([rootOf(this), document]);\n for (const root of roots) {\n for (const control of root.querySelectorAll<HTMLElement>('[data-aparte-split-pane]')) {\n // Trimmed, as the click reads it; and only on a button, where `aria-pressed` means something.\n const which = (control.getAttribute('data-aparte-split-pane') ?? '').trim();\n if (which !== 'start' && which !== 'end') continue;\n if (control.localName !== 'button' && control.getAttribute('role') !== 'button') continue;\n const target = control.closest('aparte-split')\n ?? rootOf(control).querySelector('aparte-split')\n ?? document.querySelector('aparte-split');\n if (target !== this) continue;\n if (this.stacked) control.setAttribute('aria-pressed', String(which === this.pane));\n else control.removeAttribute('aria-pressed');\n }\n }\n }\n\n // ─── Locale ───────────────────────────────────────────────────────────\n\n private _onConfigChange = (): void => this._relabel();\n\n /**\n * The seam's accessible name. A host that wrote its own `aria-label` — or an\n * `aria-labelledby` pointing at a heading — keeps it: `data-own-label` marks the\n * one we set, so a language switch replaces ours and never theirs.\n */\n private _relabel(): void {\n const handle = this._handle;\n if (!handle) return;\n if (handle.hasAttribute('aria-labelledby')) return;\n if (handle.hasAttribute('aria-label') && handle.dataset['ownLabel'] !== 'locale') return;\n const locale = resolveConfig(this).getLocale();\n handle.setAttribute('aria-label', this.getAttribute('label') ?? locale.splitHandleLabel ?? 'Resize the panes');\n handle.dataset['ownLabel'] = 'locale';\n }\n}\n\nif (!customElements.get('aparte-split')) {\n customElements.define('aparte-split', AparteSplit);\n}\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. **A server or a worker resolves `index.node.ts`\n * instead** — through the `node`, `worker`, `workerd`, `edge-light`, `deno` and `bun`\n * conditions in package.json, or by name as `@aparte/core/headless` in a worker built\n * with the page's conditions — a DOM-free entry with the client, host, transports,\n * 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/display/mark.css';\nimport './styles/surface/tabs.css';\nimport './styles/surface/accordion.css';\nimport './styles/surface/menu.css';\nimport './styles/surface/popover.css';\nimport './styles/surface/dialog.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/text.css';\nimport './styles/components/elicitation.css';\nimport './styles/components/conversation.css';\nimport './styles/components/suggestions.css';\nimport './styles/components/context.css';\nimport './styles/components/scroll-rail.css';\nimport './styles/shell/sidebar.css';\nimport './styles/shell/app-header.css';\nimport './styles/shell/app-shell.css';\nimport './styles/shell/split.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 AparteStreamBlock,\n AparteStreamBlockMatch,\n AparteSendEventDetail,\n AparteViewportConfig,\n AparteInputConfig,\n AparteThemeVariables,\n AparteStatus,\n AparteAttachment,\n AparteMessageBranch,\n AparteBubbleActionsConfig,\n AparteBubbleActionName,\n AparteSegment,\n AparteSegmentTypes,\n AparteSegmentType,\n AparteTextSegment,\n AparteThinkingSegment,\n AparteCodeSegment,\n AparteSegmentRenderer,\n AparteCustomSegment,\n AparteToolCallSegment,\n // Five shapes that were public in everything but name. `AparteSegment` is exported\n // and its union names all its members, yet the error segment could not be written\n // 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 // 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 AparteAIProviderMetadata,\n AparteAIProviderConfigSchema,\n AparteModelConfig,\n ModelStatus,\n ModelLoadProgress,\n AparteModelChangeEventDetail,\n AparteApprovalModeChangeEventDetail,\n AparteMessageDoneEventDetail,\n AparteMessageStartEventDetail,\n AparteMessageErrorEventDetail,\n AparteMessageAbortedEventDetail,\n AparteMessagePausedEventDetail,\n AparteAbortEventDetail,\n AparteCompactEventDetail,\n AparteCompactDoneEventDetail,\n AparteCompactStartEventDetail,\n AparteCompactErrorEventDetail,\n AparteAttachmentPreviewEventDetail,\n AparteLinkClickEventDetail,\n AparteMessageInfoEventDetail,\n AparteSiblingInfo,\n AparteBranchNavigateEventDetail,\n ApartePathChangedEventDetail,\n AparteRetryEventDetail,\n AparteEditEventDetail,\n AparteFeedbackEventDetail,\n AparteActionEventDetail,\n // Chat types\n AparteChatRequest,\n AparteRequestMeta,\n AparteChatResponse,\n AparteChatMessage,\n AparteContentPart,\n AparteTextPart,\n AparteImagePart,\n AparteStreamEvent,\n AparteStreamEventMap,\n AparteUsage,\n // Tool types\n AparteTool,\n AparteToolCall,\n AparteApprovalPolicy,\n AparteApprovalRuling,\n AparteToolResult,\n AparteToolHandler,\n AparteToolContext,\n AparteToolRenderer,\n AparteToolSummary,\n AparteToolInputFormatter,\n AparteToolMatcher,\n AparteToolDisplay,\n AparteToolApprovalRequestDetail,\n // Canonical imperative surfaces (re-exported by every wrapper: the chat handle,\n // and the <AparteUi> proxy handle the four used to hand-copy).\n AparteChatImperativeApi,\n AparteUiHandle,\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 AparteContextAttributes,\n AparteIconAttributes,\n AparteScrollRailAttributes,\n AparteSidebarAttributes,\n AparteSplitAttributes,\n AparteSuggestionsAttributes,\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';\nexport { AparteSuggestions } from './components/index.js';\nexport type { AparteSuggestion, AparteSuggestionEventDetail } from './components/index.js';\nexport { AparteContext } from './components/index.js';\nexport type { AparteContextLevel, AparteContextThresholdEventDetail } from './components/index.js';\nexport { AparteScrollRail } from './components/index.js';\nexport type { AparteScrollRailJumpDetail, AparteScrollRailEvery } from './components/index.js';\nexport { AparteSidebar } from './components/index.js';\nexport type { AparteSidebarToggleDetail } from './components/index.js';\nexport { AparteSplit } from './components/index.js';\nexport type { AparteSplitResizeDetail } from './components/index.js';\n\n// Export conversation list primitive\nexport { AparteConversationList } from './components/index.js';\nexport type { AparteConversationListItem, AparteConversationSelectDetail, AparteConversationDeleteDetail, AparteConversationArchiveDetail, AparteConversationPinDetail, AparteConversationRenameDetail } from './components/index.js';\n\n// Export conversations (types, adapter contract, manager)\nexport type {\n AparteConversation,\n AparteConversationMeta,\n AparteStorageAdapter,\n AparteAttachmentRow,\n AparteConversationLoadedDetail,\n} from './conversations/index.js';\nexport { APARTE_CONVERSATION_SCHEMA_VERSION } from './conversations/index.js';\nexport {\n AparteConversationManager,\n type ConversationManagerOptions,\n type AparteConversationsListener,\n type AparteConversationTitleProvider,\n} 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 } 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, AparteLocale, AparteLocaleExtensions, AparteAction, AparteActionZone, AparteIconProvider, AparteIconName, AparteCoreIconName, AparteAvatarProvider, AparteStatusRenderer, AparteErrorRenderer, AparteAttachmentRenderer, AparteElicitationFieldRenderer, AparteElicitationFieldContext, AparteElicitationFieldControl, AparteSiblingNavRenderer, AparteBubbleShellRenderer, AparteModelPreference, AparteModelPreferenceProvider, AparteSanitizer } from './config/index.js';\nexport { APARTE_DEFAULT_ICON_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// Same wall, same reason: `navigator.clipboard` is undefined on `http://`, and a\n// consumer's own copy button hits it exactly as core's three did.\nexport { copyText } from './utils/copy-text.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 } 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 { toolRefusal } from './client/stream-adapter.js';\nexport { createAgentStreamRunner, type AgentStreamRunnerOptions } from './client/agent-runner.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, createAparteAgentHandler, isFormatAdapter } from './transport/index.js';\nexport type { AparteTransport, AparteTransportContext, AparteFormatAdapter, AparteVendorRequest, BackendTransportOptions, DirectTransportOptions, AparteChatHandlerOptions, AparteAgentHandlerOptions, AparteAgentTool, AparteAgentRequestBody } 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// Every element class, imported by VALUE. This is what replaced the eight\n// side-effect imports that used to sit here (\"components register themselves in their\n// files\"): a side-effect import keeps a module, but it names nothing a reader can check,\n// and the list had fallen eight modules behind the twenty-four elements core defines.\n// Naming the classes makes the anchor and the registry the same list — see\n// `APARTE_ELEMENTS` below.\nimport {\n AparteChat,\n AparteChatBubble,\n AparteChatStatus,\n AparteChatViewport,\n AparteComposer,\n AparteComposerInput,\n AparteComposerSend,\n AparteComposerCancel,\n AparteComposerAttachments,\n AparteComposerAddAttachment,\n AparteComposerAction,\n AparteComposerToolbar,\n AparteConversationList,\n AparteScrollRail,\n AparteSidebar,\n AparteSplit,\n AparteSuggestions,\n AparteContext,\n} from './components/index.js';\nimport { AparteElicitation as AparteElicitationElement } from './components/elicitation/aparte-elicitation.js';\nimport {\n AparteSelect,\n AparteOption,\n AparteOptgroup,\n AparteProgressSpinner,\n AparteIcon,\n} from './primitives/index.js';\n\n/**\n * Every tag core defines, beside the class that defines it.\n *\n * Two jobs, and they are the same list on purpose. It is what the warning below reads,\n * so the function can NAME the tags that are missing instead of saying \"some\n * components\"; and it is the single list `src/__tests__/register-all-components.test.ts`\n * holds against every `customElements.define` literal in the source.\n *\n * What it is NOT is what keeps those 24 `define` calls alive, and the difference is\n * worth stating because the opposite is easy to assume. The browser build is ONE\n * module — `dist/index.js` — and `package.json`'s `sideEffects` names it, so no\n * bundler drops it; all 24 calls sit in it as top-level statements whatever this array\n * references. Naming the classes buys the warning, not the registrations.\n *\n * It checks every element core defines and names the ones that are missing, so a\n * consumer whose bundler dropped the browser entry is told so, and how to keep it.\n *\n * `src/types/__tests__/element-map.test.ts` reads the same\n * `customElements.define` literals out of the source, so this list cannot fall behind\n * them without a red test.\n */\nconst APARTE_ELEMENTS: ReadonlyArray<readonly [string, CustomElementConstructor]> = [\n // Chat shell + transcript\n ['aparte-chat', AparteChat],\n ['aparte-chat-viewport', AparteChatViewport],\n ['aparte-chat-bubble', AparteChatBubble],\n ['aparte-chat-status', AparteChatStatus],\n\n // Composer and its primitives\n ['aparte-composer', AparteComposer],\n ['aparte-composer-input', AparteComposerInput],\n ['aparte-composer-send', AparteComposerSend],\n ['aparte-composer-cancel', AparteComposerCancel],\n ['aparte-composer-attachments', AparteComposerAttachments],\n ['aparte-composer-add-attachment', AparteComposerAddAttachment],\n ['aparte-composer-action', AparteComposerAction],\n ['aparte-composer-toolbar', AparteComposerToolbar],\n\n // Standalone components\n ['aparte-conversation-list', AparteConversationList],\n ['aparte-elicitation', AparteElicitationElement],\n ['aparte-scroll-rail', AparteScrollRail],\n ['aparte-sidebar', AparteSidebar],\n ['aparte-split', AparteSplit],\n ['aparte-suggestions', AparteSuggestions],\n ['aparte-context', AparteContext],\n\n // Primitives\n ['aparte-select', AparteSelect],\n ['aparte-option', AparteOption],\n ['aparte-optgroup', AparteOptgroup],\n ['aparte-progress-spinner', AparteProgressSpinner],\n ['aparte-icon', AparteIcon],\n];\n\n/**\n * Report which of core's elements are not registered.\n *\n * Importing `@aparte/core` already registers all of them. Call this to check it: after a\n * bundler with aggressive side-effect elimination, or a dynamic `import()`, it names on\n * the console every element that is not registered — the sign the browser entry was\n * dropped: import `@aparte/core` at module scope, and keep its `sideEffects`.\n *\n * Idempotent, and safe to call more than once: it defines nothing, it reports.\n */\nexport function registerAllComponents(): void {\n const missing = APARTE_ELEMENTS.filter(([tag]) => !customElements.get(tag)).map(([tag]) => tag);\n if (missing.length > 0) {\n console.warn(\n `[aparte] ${missing.length} of ${APARTE_ELEMENTS.length} elements are not registered: `\n + `${missing.join(', ')}. Import '@aparte/core' at module scope (not behind a lazy `\n + `import()), and check that your bundler is honouring the package's sideEffects field.`,\n );\n }\n}\n\n// The dialog wiring — three `data-aparte-dialog-*` attributes on the browser's own\n// <dialog>, installed once at import like the default renderers. Exported for a host\n// that builds its page before importing core; idempotent.\nimport { installDialogTriggersOnce } from './interop/dialog-triggers.js';\nexport { installDialogTriggersOnce };\nif (typeof document !== 'undefined') installDialogTriggersOnce();\n"],"names":["n","s","l","i","m","panel","y","syncSubmit","retract","el","a","b","c","f","h","p","g","d","k","tick","v","second","AparteChat","AparteChatViewport","AparteChatBubble","AparteChatStatus","AparteComposer","AparteComposerInput","AparteComposerSend","AparteComposerCancel","AparteComposerAttachments","AparteComposerAddAttachment","AparteComposerAction","AparteComposerToolbar","AparteConversationList","AparteElicitationElement","AparteScrollRail","AparteSidebar","AparteSplit","AparteSuggestions","AparteContext","AparteSelect","AparteOption","AparteOptgroup","AparteProgressSpinner","AparteIcon"],"mappings":"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"}
|