@aparte/core 0.16.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/components/chat/aparte-chat.d.ts +8 -1
  2. package/dist/components/chat/aparte-chat.d.ts.map +1 -1
  3. package/dist/components/composer/aparte-composer-action.d.ts +1 -1
  4. package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
  5. package/dist/components/composer/aparte-composer-add-attachment.d.ts +1 -1
  6. package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
  7. package/dist/components/composer/aparte-composer-attachments.d.ts +1 -1
  8. package/dist/components/composer/aparte-composer-cancel.d.ts +1 -1
  9. package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
  10. package/dist/components/composer/aparte-composer-input.d.ts +1 -1
  11. package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
  12. package/dist/components/composer/aparte-composer-send.d.ts +1 -1
  13. package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
  14. package/dist/components/composer/aparte-composer.d.ts +9 -1
  15. package/dist/components/composer/aparte-composer.d.ts.map +1 -1
  16. package/dist/components/elicitation/aparte-elicitation.d.ts +1 -1
  17. package/dist/components/split/aparte-split.d.ts +1 -1
  18. package/dist/components/suggestions/aparte-suggestions.d.ts +1 -1
  19. package/dist/components/viewport/aparte-chat-viewport.d.ts +25 -0
  20. package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
  21. package/dist/custom-elements.json +12104 -12035
  22. package/dist/generated/element-attributes.d.ts +2 -0
  23. package/dist/generated/element-attributes.d.ts.map +1 -1
  24. package/dist/index.css +194 -9
  25. package/dist/index.js +68 -14
  26. package/dist/index.js.map +1 -1
  27. package/package.json +2 -2
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/primitives/select/aparte-option.ts","../src/primitives/select/aparte-optgroup.ts","../src/primitives/select/aparte-select.ts","../src/primitives/progress-spinner/aparte-progress-spinner.ts","../src/primitives/icon/aparte-icon.ts","../src/components/elicitation/aparte-elicitation.ts","../src/components/chat/aparte-chat.ts","../src/components/bubble/aparte-chat-bubble.ts","../src/components/status/aparte-chat-status.ts","../src/components/viewport/aparte-chat-viewport.ts","../src/components/composer/aparte-composer.ts","../src/components/composer/aparte-composer-input.ts","../src/components/composer/aparte-composer-send.ts","../src/components/composer/aparte-composer-cancel.ts","../src/components/composer/aparte-composer-attachments.ts","../src/components/composer/aparte-composer-add-attachment.ts","../src/components/composer/aparte-composer-action.ts","../src/components/composer/aparte-composer-toolbar.ts","../src/components/conversation-list/aparte-conversation-list.ts","../src/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 * AparteOption\n * \n * Option element for aparte-select dropdown.\n *\n * One selectable row. Only meaningful inside `<aparte-select>` (directly, or nested in an\n * `<aparte-optgroup>`): the parent owns `selected` outright — on its first render, and on\n * every value change after it, it sets that attribute on the option whose `value` matches\n * its own and strips it from all the others. So `selected` written by hand does not\n * survive; set `value` on the select instead. Outside a select nothing selects it: it only\n * styles the row and sets `role=\"option\"` / `aria-selected` from its own attributes.\n *\n * It is not an `<option>`. It carries no form value, `disabled` blocks the click and the\n * keyboard walk but is not a form-disabled state, and when the `value` attribute is\n * absent the trimmed text content is used as the value instead.\n *\n * Keep the label one text node: the `label` property reads only the FIRST text node — that\n * is what keeps the injected status dot out of it — so wrapping the label in an element\n * makes `label` fall back to `value`. The select's own trigger label and its search filter\n * read the full `textContent`, so a wrapped label still displays and still matches.\n *\n * `data-status` is a rendering hook, not state core interprets: any non-empty value\n * appends an `aria-hidden` `.aparte-status-dot` span as the last child, and only\n * `ready`, `cached` and `not-downloaded` have a colour in the stylesheet — anything else\n * renders an uncoloured dot until you style it.\n *\n * @element aparte-option\n * @attr {string} value - Option value\n * @attr {boolean} disabled - Disabled state\n * @attr {boolean} selected - Selected state\n * @attr {string} data-status - Free-form status the host sets; styled, never read by core.\n *\n * @cssprop [--aparte-select-text=var(--aparte-text, #1e293b)] - Option text colour.\n * @cssprop [--aparte-select-option-hover=var(--aparte-surface-2, #f1f5f9)] - Background on hover, and for the keyboard-active row (`[data-active]`), which adds an inset `--aparte-primary` ring on top so the two are distinguishable.\n * @cssprop [--aparte-select-option-selected=color-mix(in srgb, var(--aparte-primary, #3b82f6) 18%, transparent)] - Background of the selected row. A tint by default: a solid accent fill with white text failed WCAG AA in both themes.\n * @cssprop [--aparte-select-option-selected-text=var(--aparte-select-text, var(--aparte-text, #1e293b))] - Text colour of the selected row. Set both this and the background to go back to a solid fill.\n *\n * @example\n * <aparte-select placeholder=\"Pick a model\" value=\"gpt-4o-mini\">\n * <aparte-option value=\"gpt-4o-mini\">GPT-4o mini</aparte-option>\n * <aparte-option value=\"o3\" disabled>o3 (no access)</aparte-option>\n * </aparte-select>\n */\n\nexport class AparteOption extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['value', 'disabled', 'selected', 'data-status'];\n }\n\n connectedCallback(): void {\n this.setAttribute('role', 'option');\n this._updateAriaSelected();\n this._updateStatusDot();\n }\n\n attributeChangedCallback(name: string): void {\n if (name === 'selected') {\n this._updateAriaSelected();\n }\n if (name === 'disabled') {\n this.setAttribute('aria-disabled', this.hasAttribute('disabled') ? 'true' : 'false');\n }\n if (name === 'data-status') {\n this._updateStatusDot();\n }\n }\n\n get value(): string {\n return this.getAttribute('value') || this.textContent?.trim() || '';\n }\n\n set value(val: string) {\n this.setAttribute('value', val);\n }\n\n get label(): string {\n // Use only the first text node, ignoring injected spans (e.g. status dot)\n const textNode = Array.from(this.childNodes).find(n => n.nodeType === Node.TEXT_NODE);\n return textNode?.textContent?.trim() || this.value;\n }\n\n get disabled(): boolean {\n return this.hasAttribute('disabled');\n }\n\n set disabled(val: boolean) {\n if (val) {\n this.setAttribute('disabled', '');\n } else {\n this.removeAttribute('disabled');\n }\n }\n\n get selected(): boolean {\n return this.hasAttribute('selected');\n }\n\n set selected(val: boolean) {\n if (val) {\n this.setAttribute('selected', '');\n } else {\n this.removeAttribute('selected');\n }\n }\n\n private _updateAriaSelected(): void {\n this.setAttribute('aria-selected', this.selected ? 'true' : 'false');\n }\n\n private _updateStatusDot(): void {\n const status = this.getAttribute('data-status');\n let dot = this.querySelector<HTMLSpanElement>('.aparte-status-dot');\n\n if (!status) {\n dot?.remove();\n return;\n }\n\n if (!dot) {\n dot = document.createElement('span');\n dot.className = 'aparte-status-dot';\n dot.setAttribute('aria-hidden', 'true');\n this.appendChild(dot);\n }\n\n dot.setAttribute('data-status', status);\n }\n}\n\n// Register\nif (!customElements.get('aparte-option')) {\n customElements.define('aparte-option', AparteOption);\n}\n","import { resolveConfig } from '../../config/config-context.js';\n\n/**\n * AparteOptgroup\n * \n * Option group element for aparte-select dropdown.\n *\n * A labelled band of options inside `<aparte-select>`. Presentational only: the group\n * holds no value, and collapsing it just sets `display: none` on its `<aparte-option>`\n * descendants — they stay in the DOM, which is what keeps the select's keyboard walk\n * skipping them, since it works off `display`. The label header is inserted before the\n * children and the loading row appended after them, so both live inside the group, and\n * neither is hidden when it collapses: only the options are.\n *\n * Two consequences of that worth knowing before you reach for it. The select's search\n * writes that same `display` property on every option in the select, so filtering can\n * reveal matches inside a collapsed group. And the header is built once, on the first\n * render that finds a `label`: changing `label` afterwards does not rewrite it — set the\n * label before inserting the group, or replace the group.\n *\n * `loading` is a display state, not a fetch. It appends a spinner row to the group and\n * nothing else happens — the host still owns the request. \"Fetch on expand\" is driven by\n * `aparte-optgroup-toggle`, whose `detail.collapsed` says which way the group just went;\n * the attribute and the options' `display` are already updated when it fires, since\n * setting the attribute runs `attributeChangedCallback` synchronously.\n *\n * @element aparte-optgroup\n * @attr {string} label - Group label\n * @attr {boolean} collapsible - Adds the chevron and the click handler to the header — so it needs a `label`, and it is read only when that header is first built.\n * @attr {boolean} collapsed - Collapsed state\n * @attr {boolean} loading - Appends a spinner row to the group; the options stay visible.\n *\n * @fires {CustomEvent<AparteOptgroupToggleEventDetail>} aparte-optgroup-toggle - The group was collapsed or expanded.\n *\n * @cssprop --aparte-text-muted - Colour of the group header label and of the loading row.\n * The shared theme token: there is no optgroup-specific override, so restyling one group's\n * header means setting this on that element.\n *\n * @example\n * <!-- Collapsed groups keep a long list readable; the label is the group's header. -->\n * <aparte-select grouped placeholder=\"Pick a model\">\n * <aparte-optgroup label=\"Ollama\" collapsible collapsed>\n * <aparte-option value=\"ollama::llama3\">Llama 3</aparte-option>\n * </aparte-optgroup>\n * <aparte-optgroup label=\"OpenRouter\" collapsible>\n * <aparte-option value=\"openrouter::gpt-4o-mini\">GPT-4o mini</aparte-option>\n * </aparte-optgroup>\n * </aparte-select>\n */\n\nexport class AparteOptgroup extends HTMLElement {\n /** Ids for the label span the group points `aria-labelledby` at. */\n private static _labelIdSeq = 0;\n\n static get observedAttributes(): string[] {\n return ['label', 'collapsible', 'collapsed', 'loading'];\n }\n\n connectedCallback(): void {\n this.setAttribute('role', 'group');\n this._render();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n if (name === 'collapsed') {\n this._updateCollapsedState();\n }\n\n if (this.isConnected) {\n this._render();\n }\n }\n\n get label(): string {\n return this.getAttribute('label') || '';\n }\n\n set label(val: string) {\n this.setAttribute('label', val);\n }\n\n get collapsible(): boolean {\n return this.hasAttribute('collapsible');\n }\n\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(val: boolean) {\n if (val) {\n this.setAttribute('collapsed', '');\n } else {\n this.removeAttribute('collapsed');\n }\n }\n\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(val: boolean) {\n if (val) this.setAttribute('loading', '');\n else this.removeAttribute('loading');\n }\n\n private _render(): void {\n // Only render header if we have a label\n if (this.label) {\n const existingHeader = this.querySelector('.aparte-optgroup-header');\n if (!existingHeader) {\n const header = document.createElement('div');\n header.className = 'aparte-optgroup-header';\n // The header stays a GENERIC node on purpose: naming it (it used to\n // carry `aria-label`) makes it a real element inside a `listbox`,\n // where only options and groups may live (axe:\n // aria-required-children, critical). The name belongs on the group,\n // via aria-labelledby below.\n // `label` is an attribute value — with the model-selector it carries a\n // provider-supplied name — so it goes through textContent, never\n // innerHTML (a hostile name would otherwise inject here).\n const labelSpan = document.createElement('span');\n labelSpan.className = 'aparte-optgroup-label';\n labelSpan.id = `aparte-optgroup-label-${++AparteOptgroup._labelIdSeq}`;\n labelSpan.textContent = this.label;\n this.setAttribute('aria-labelledby', labelSpan.id);\n header.appendChild(labelSpan);\n\n if (this.collapsible) {\n const chevron = document.createElement('span');\n chevron.className = 'aparte-optgroup-chevron';\n // The library's own `expand`, not a shape drawn here. This span was\n // painted as a CSS border-triangle — the second hand-drawn chevron in\n // core, built from PHYSICAL border sides where the tool row's used\n // logical ones: two markers, two constructions, which is the divergence\n // the icons rule was written after.\n chevron.innerHTML = resolveConfig(this).getIcon('expand');\n header.appendChild(chevron);\n header.style.cursor = 'pointer';\n header.addEventListener('click', (e) => {\n e.stopPropagation();\n this._toggleCollapse();\n });\n }\n\n this.insertBefore(header, this.firstChild);\n }\n }\n\n // Update loading state\n this._updateLoadingState();\n\n // Update collapsed state\n this._updateCollapsedState();\n }\n\n private _updateLoadingState(): void {\n let loader = this.querySelector('.aparte-optgroup-loader');\n if (this.loading) {\n if (!loader) {\n loader = document.createElement('div');\n loader.className = 'aparte-optgroup-loader';\n loader.innerHTML = '<span class=\"aparte-spinner-small\"></span> Fetching models...';\n this.appendChild(loader);\n }\n } else if (loader) {\n loader.remove();\n }\n }\n\n private _toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n\n // Dispatch event before updating UI to allow parent to react (e.g. fetch data)\n this.dispatchEvent(new CustomEvent<AparteOptgroupToggleEventDetail>('aparte-optgroup-toggle', {\n bubbles: true,\n composed: true,\n detail: {\n label: this.label,\n collapsed: this.collapsed\n }\n }));\n\n this._updateCollapsedState();\n }\n\n private _updateCollapsedState(): void {\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n (opt as HTMLElement).style.display = this.collapsed ? 'none' : '';\n });\n }\n}\n\n// Register\nif (!customElements.get('aparte-optgroup')) {\n customElements.define('aparte-optgroup', AparteOptgroup);\n}\n\n/**\n * Detail payload for `aparte-optgroup-toggle`.\n *\n * `types/event-map.ts` types `aparte-optgroup-toggle` with this detail, and\n * `@aparte/plugin-model-selector` reads both of its fields, from another package,\n * through an untyped cast — while the event is published in the generated CEM event\n * table. It is a contract.\n *\n * @event aparte-optgroup-toggle\n */\nexport interface AparteOptgroupToggleEventDetail {\n /** The group's label. */\n label: string;\n /** `true` when the group just collapsed. */\n collapsed: boolean;\n}\n","import './aparte-option.js';\nimport './aparte-optgroup.js';\nimport { resolveConfig } from '../../config/config-context.js';\n\nexport interface AparteSelectChangeDetail {\n value: string;\n label: string;\n previousValue: string;\n}\n\n/**\n * Dropdown select for aparté — a vanilla web component with optional grouping, a\n * search filter and a keyboard-driven listbox.\n *\n * The element is light DOM and takes `<aparte-option>` / `<aparte-optgroup>` children, in\n * the order they should appear. On its first render it captures its children, keeps those\n * two kinds and moves them into the `role=\"listbox\"` container it builds, then rewrites\n * its own `innerHTML` — so any other child is dropped, and a wrapper element around your\n * options takes the options down with it: only DIRECT children are captured.\n *\n * Children written later are picked up by a `subtree` MutationObserver and moved into that\n * same container, and the keyboard highlight is re-asserted on the new elements. The move\n * EMPTIES the container first, so a later write replaces the list instead of adding to it:\n * write the whole list, not one option. Writing straight into `.aparte-select-options`\n * skips the move (the observer then only re-asserts the highlight), and that is the path\n * `@aparte/plugin-model-selector` takes to refresh a live list in place.\n *\n * It is not a form control: no `name`, no `multiple`, no participation in form submission.\n * It holds exactly one value and reports it through `aparte-select-change`.\n *\n * The dropdown is `position: fixed` and placed from script so it escapes an\n * `overflow: hidden` ancestor — which is why its stacking order is a variable\n * (`--aparte-select-z`) rather than a fixed rule, and why an `open` dropdown does not\n * scroll with the trigger.\n *\n * @element aparte-select\n * @attr {string} value - The selected option's value.\n * @attr {string} placeholder - Shown while nothing is selected.\n * @attr {boolean} disabled - Blocks opening the dropdown.\n * @attr {boolean} grouped - Observed, never read: `<aparte-optgroup>` children render as groups without it.\n * @attr {boolean} searchable - Adds a filter field above the options. Read on the first render only.\n * @attr {boolean} open - Reflects (and controls) whether the dropdown is open.\n *\n * @fires {CustomEvent<AparteSelectChangeDetail>} aparte-select-change - The selection changed; carries the new value, its label and the previous value.\n * @fires aparte-select-open - The dropdown opened. No detail.\n * @fires aparte-select-close - The dropdown closed. No detail.\n *\n * @cssprop [--aparte-select-bg=var(--aparte-surface-1, #fff)] - Trigger background. (The dropdown panel has its own token, `--aparte-select-dropdown-bg`, in both themes.)\n * @cssprop [--aparte-select-border=var(--aparte-border, #e2e8f0)] - Border of the trigger and of the dropdown.\n * @cssprop [--aparte-select-border-hover=var(--aparte-primary, #3b82f6)] - Trigger border on hover.\n * @cssprop [--aparte-select-border-focus=var(--aparte-primary, #3b82f6)] - Trigger border while focused.\n * @cssprop [--aparte-select-ring=rgba(59, 130, 246, 0.2)] - Colour of the 2px focus ring around the trigger.\n * @cssprop [--aparte-select-radius=0.5rem] - Corner radius of the trigger and the dropdown.\n * @cssprop [--aparte-select-text=var(--aparte-text, #1e293b)] - Colour of the trigger label (and of the options).\n * @cssprop [--aparte-select-chevron=var(--aparte-text-muted, #94a3b8)] - Colour of the chevron, which rotates 180° while open.\n * @cssprop [--aparte-select-dropdown-bg=var(--aparte-surface-1, #fff)] - Dropdown panel background, in both themes (`--aparte-surface-1` is dark-aware). It used to be read in the light theme only, the dark rule taking the TRIGGER's background instead — so a transparent trigger made the panel see-through in the dark.\n * @cssprop [--aparte-select-shadow=0 4px 12px rgba(0, 0, 0, 0.1)] - Dropdown panel shadow.\n * @cssprop [--aparte-select-z=1000] - `z-index` of the dropdown. It is `position: fixed`, so this is the one knob that decides whether it lands above the rest of your page.\n *\n * @example\n * <aparte-select placeholder=\"Pick a model\" searchable value=\"gpt-4o-mini\">\n * <aparte-option value=\"gpt-4o-mini\">GPT-4o mini</aparte-option>\n * <aparte-option value=\"llama-3.1-8b\">Llama 3.1 8B</aparte-option>\n * </aparte-select>\n *\n * <script>\n * document.querySelector('aparte-select').addEventListener('aparte-select-change', (e) => {\n * console.log(e.detail.value, e.detail.label, e.detail.previousValue);\n * });\n * </script>\n */\nexport class AparteSelect extends HTMLElement {\n private static _optIdSeq = 0;\n /** Fallback ids for the listbox `aria-controls` target when the host has no id. */\n private static _listboxSeq = 0;\n\n private _value = '';\n private _isOpen = false;\n private _activeIndex = -1;\n private _trigger: HTMLElement | null = null;\n private _dropdown: HTMLElement | null = null;\n private _searchInput: HTMLInputElement | null = null;\n private _observer: MutationObserver | null = null;\n\n // Bound handlers for cleanup\n /**\n * Bound, like its two neighbours below — because an inline arrow on `this` can\n * never be removed.\n *\n * `_setupEventListeners()` runs on EVERY `connectedCallback`, and `_render()`'s\n * idempotency guard means the host element survives a re-connect. So each move\n * of the element (a portal, a Vue teleport, any framework re-parent) added\n * another option-click listener: measured, one click fired the change handler\n * FIVE times after five re-connects. `disconnectedCallback` removed the two\n * document-level handlers and could not touch this one.\n *\n * This is verbatim the bug class `aparte-chat-viewport` documents having fixed\n * for its own listeners.\n */\n private _boundHandleOptionClick = this._handleOptionClick.bind(this);\n private _boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n private _boundHandleKeydown = this._handleKeydown.bind(this);\n\n static get observedAttributes(): string[] {\n return ['value', 'placeholder', 'disabled', 'grouped', 'searchable', 'open'];\n }\n\n connectedCallback(): void {\n this._value = this.getAttribute('value') || '';\n this._isOpen = this.hasAttribute('open');\n this._render();\n this._setupEventListeners();\n this._setupMutationObserver();\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('click', this._boundHandleOptionClick);\n document.removeEventListener('click', this._boundHandleDocumentClick);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n this._observer?.disconnect();\n }\n\n attributeChangedCallback(name: string, oldValue: string, newValue: string): void {\n if (!this.isConnected) return;\n\n if (name === 'value' && oldValue !== newValue && newValue !== this._value) {\n this._value = newValue || '';\n this._updateTriggerLabel();\n }\n if (name === 'open') {\n this._isOpen = this.hasAttribute('open');\n if (this._isOpen) {\n this._dropdown?.removeAttribute('hidden');\n this._searchInput?.focus();\n } else {\n this._dropdown?.setAttribute('hidden', '');\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public API\n // ─────────────────────────────────────────────────────────────────────────\n\n get value(): string {\n return this._value;\n }\n\n set value(val: string) {\n if (val === this._value) return;\n const previousValue = this._value;\n this._value = val;\n this.setAttribute('value', val);\n this._updateTriggerLabel();\n this._emitChange(previousValue);\n }\n\n get open(): boolean {\n return this._isOpen;\n }\n\n set open(val: boolean) {\n if (val) {\n this.setAttribute('open', '');\n } else {\n this.removeAttribute('open');\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Rendering\n // ─────────────────────────────────────────────────────────────────────────\n\n private _render(): void {\n const placeholder = this.getAttribute('placeholder') || 'Select...';\n const searchable = this.hasAttribute('searchable');\n\n // Check if already rendered (has dropdown structure)\n if (this.querySelector('.aparte-select-dropdown')) {\n this._updateTriggerLabel();\n return;\n }\n\n // First render: capture any slotted children before modifying DOM\n const slottedChildren = Array.from(this.children);\n // 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 trigger.setAttribute('role', 'combobox');\n trigger.setAttribute('aria-haspopup', 'listbox');\n trigger.setAttribute('aria-expanded', 'false');\n // Accessible name (axe aria-input-field-name): the visible label span is\n // the combobox VALUE, not its name — name it from the host's aria-label\n // when provided, else the placeholder (\"Select model…\" etc.).\n trigger.setAttribute('aria-label', this.getAttribute('aria-label') || placeholder);\n // The label is placeholder text (consumer/attribute-supplied) → textContent,\n // same path as _updateTriggerLabel(), never innerHTML. Only the static SVG\n // chevron uses innerHTML.\n const labelSpan = document.createElement('span');\n labelSpan.className = 'aparte-select-label';\n // 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 if (searchable) {\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 dropdown.appendChild(searchInput);\n }\n\n const optionsContainer = document.createElement('div');\n optionsContainer.className = 'aparte-select-options';\n optionsContainer.setAttribute('role', 'listbox');\n // A listbox is an ARIA input field, so it needs its own name; reuse the\n // combobox's (axe: aria-input-field-name).\n optionsContainer.setAttribute('aria-label', trigger.getAttribute('aria-label') ?? placeholder);\n // `role=\"combobox\"` REQUIRES aria-controls (axe: aria-required-attr).\n optionsContainer.id = this.id ? `${this.id}-listbox` : `aparte-listbox-${++AparteSelect._listboxSeq}`;\n trigger.setAttribute('aria-controls', optionsContainer.id);\n\n // Move slotted children (aparte-option, aparte-optgroup) into options container\n slottedChildren.forEach(child => {\n if (child.tagName === 'APARTE-OPTION' || child.tagName === 'APARTE-OPTGROUP') {\n optionsContainer.appendChild(child);\n }\n });\n\n dropdown.appendChild(optionsContainer);\n\n // Clear and rebuild DOM\n this._observer?.disconnect();\n this.innerHTML = '';\n this.appendChild(trigger);\n this.appendChild(dropdown);\n\n this._trigger = trigger;\n this._dropdown = dropdown;\n this._searchInput = dropdown.querySelector('.aparte-select-search');\n\n if (this.isConnected) {\n this._setupMutationObserver();\n }\n\n // Update label based on current value\n this._updateTriggerLabel();\n }\n\n private _setupMutationObserver(): void {\n this._observer = new MutationObserver(() => {\n this._updateDropdownContent();\n // 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: the highlight disappeared,\n * `aria-activedescendant` kept pointing at an id no longer in the document, and\n * the next ArrowDown moved from the stale index — skipping an option. Only when\n * open and only when a position was held, so a refresh never invents one.\n */\n private _restoreActive(): void {\n if (!this._isOpen || this._activeIndex < 0) return;\n if (this.querySelector('aparte-option[data-active]')) return;\n this._setActive(this._activeIndex);\n }\n\n private _updateDropdownContent(): void {\n // If trigger is gone, the component was likely wiped by innerHTML\n if (!this._trigger || !this.contains(this._trigger)) {\n this._render();\n return;\n }\n\n const optionsContainer = this.querySelector('.aparte-select-options');\n if (!optionsContainer) {\n // If internal UI is present but container is gone\n this._render();\n return;\n }\n\n // Collect all potential options from light DOM\n // (those aren't internal UI elements)\n const lightChildren = Array.from(this.children).filter(child =>\n child.className !== 'aparte-select-trigger' &&\n child.className !== 'aparte-select-dropdown'\n );\n\n if (lightChildren.length === 0) return;\n\n // Pause observer to prevent self-triggering loop\n this._observer?.disconnect();\n\n // Clear container (but keep internal stuff if any)\n optionsContainer.innerHTML = '';\n\n // Move/Append children to container\n lightChildren.forEach(child => {\n optionsContainer.appendChild(child);\n });\n\n // Resume observer\n if (this.isConnected) {\n this._observer?.observe(this, { childList: true, subtree: true });\n }\n\n this._updateTriggerLabel();\n this._restoreActive();\n }\n\n private _setupEventListeners(): void {\n // Trigger click\n this._trigger?.addEventListener('click', () => this._toggle());\n\n // Trigger keyboard\n this._trigger?.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n // When open, Enter/Space selects the active option — handled by\n // the document-level nav handler. Only toggle when closed, so we\n // don't close the dropdown on the very keystroke meant to select.\n if (this._isOpen) return;\n e.preventDefault();\n this._toggle();\n }\n if (e.key === 'ArrowDown' && !this._isOpen) {\n e.preventDefault();\n this._openDropdown();\n }\n });\n\n // Option selection. Removed first: `_setupEventListeners` runs on every\n // connect, and adding the same bound reference twice is a no-op per spec —\n // but being explicit costs nothing and survives a future refactor that\n // rebinds.\n this.removeEventListener('click', this._boundHandleOptionClick);\n this.addEventListener('click', this._boundHandleOptionClick);\n\n // Search filter\n this._searchInput?.addEventListener('input', (e) => {\n const query = (e.target as HTMLInputElement).value.toLowerCase();\n this._filterOptions(query);\n });\n\n // Close on outside click\n document.addEventListener('click', this._boundHandleDocumentClick);\n\n // Keyboard navigation\n document.addEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _handleOptionClick(e: Event): void {\n const option = (e.target as HTMLElement).closest('aparte-option');\n if (option && !option.hasAttribute('disabled')) {\n this._selectOption(option as HTMLElement);\n }\n }\n\n private _handleDocumentClick(e: Event): void {\n if (!this.contains(e.target as Node)) {\n this._closeDropdown();\n }\n }\n\n private _handleKeydown(e: KeyboardEvent): void {\n if (!this._isOpen) return;\n\n // Home/End move the caret when typing in the search box; only hijack\n // them for option navigation when focus is not in the search field.\n const inSearch = document.activeElement === this._searchInput;\n\n switch (e.key) {\n case 'Escape':\n e.preventDefault();\n this._closeDropdown();\n this._trigger?.focus();\n break;\n case 'ArrowDown':\n e.preventDefault();\n this._moveActive(1);\n break;\n case 'ArrowUp':\n e.preventDefault();\n this._moveActive(-1);\n break;\n case 'Home':\n if (inSearch) break;\n e.preventDefault();\n this._setActive(0);\n break;\n case 'End':\n if (inSearch) break;\n e.preventDefault();\n this._setActive(this._visibleOptions().length - 1);\n break;\n case 'Enter': {\n const active = this._visibleOptions()[this._activeIndex];\n if (active) {\n e.preventDefault();\n this._selectOption(active);\n }\n break;\n }\n }\n }\n\n /** Non-disabled, non-filtered options in DOM order. */\n private _visibleOptions(): HTMLElement[] {\n return Array.from(this.querySelectorAll<HTMLElement>('aparte-option')).filter(\n opt => !opt.hasAttribute('disabled') && opt.style.display !== 'none',\n );\n }\n\n /** Move the active (keyboard-highlighted) option by `delta`, clamped. */\n private _moveActive(delta: number): void {\n const opts = this._visibleOptions();\n if (opts.length === 0) return;\n const base = this._activeIndex < 0 ? (delta > 0 ? -1 : 0) : this._activeIndex;\n this._setActive(base + delta);\n }\n\n /** Highlight the option at `index` (clamped) and point aria-activedescendant at it. */\n private _setActive(index: number): void {\n const all = this.querySelectorAll<HTMLElement>('aparte-option');\n all.forEach(o => o.removeAttribute('data-active'));\n\n const opts = this._visibleOptions();\n if (opts.length === 0) {\n this._activeIndex = -1;\n this._trigger?.removeAttribute('aria-activedescendant');\n return;\n }\n const clamped = Math.max(0, Math.min(index, opts.length - 1));\n this._activeIndex = clamped;\n\n const active = opts[clamped]!;\n if (!active.id) active.id = `aparte-option-${++AparteSelect._optIdSeq}`;\n active.setAttribute('data-active', '');\n this._trigger?.setAttribute('aria-activedescendant', active.id);\n active.scrollIntoView?.({ block: 'nearest' });\n }\n\n /** Clear the keyboard highlight (on close). */\n private _clearActive(): void {\n this._activeIndex = -1;\n this._trigger?.removeAttribute('aria-activedescendant');\n this.querySelectorAll('aparte-option').forEach(o => o.removeAttribute('data-active'));\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Actions\n // ─────────────────────────────────────────────────────────────────────────\n\n private _toggle(): void {\n if (this._isOpen) {\n this._closeDropdown();\n } else {\n this._openDropdown();\n }\n }\n\n private _openDropdown(): void {\n if (this.hasAttribute('disabled')) return;\n\n this._isOpen = true;\n this._dropdown?.removeAttribute('hidden');\n this._trigger?.setAttribute('aria-expanded', 'true');\n this.setAttribute('open', '');\n\n // Smart Positioning\n this._updatePosition();\n\n // Focus search if available\n this._searchInput?.focus();\n\n // Seed the keyboard highlight on the current selection (or the first\n // option) so ArrowUp/Down have an anchor and screen readers announce it.\n const opts = this._visibleOptions();\n const selectedIdx = opts.findIndex(o => o.getAttribute('value') === this._value);\n this._setActive(selectedIdx >= 0 ? selectedIdx : 0);\n\n this.dispatchEvent(new CustomEvent('aparte-select-open', { bubbles: true }));\n }\n\n private _closeDropdown(): void {\n this._isOpen = false;\n this._clearActive();\n this._dropdown?.setAttribute('hidden', '');\n this._trigger?.setAttribute('aria-expanded', 'false');\n this.removeAttribute('open');\n this.removeAttribute('position'); // Reset position attribute\n\n // Clear position styles set by _updatePosition\n if (this._dropdown) {\n this._dropdown.style.top = '';\n this._dropdown.style.bottom = '';\n this._dropdown.style.left = '';\n this._dropdown.style.width = '';\n }\n\n // Clear search\n if (this._searchInput) {\n this._searchInput.value = '';\n this._filterOptions('');\n }\n\n this.dispatchEvent(new CustomEvent('aparte-select-close', { bubbles: true }));\n }\n\n private _updatePosition(): void {\n if (!this._dropdown || !this._trigger) return;\n\n const rect = this._trigger.getBoundingClientRect();\n const dropdownHeight = this._dropdown.offsetHeight || 300;\n const viewportHeight = window.innerHeight;\n const spaceBelow = viewportHeight - rect.bottom;\n const GAP = 4; // px gap between trigger and dropdown\n\n // Always size to trigger width\n this._dropdown.style.left = `${rect.left}px`;\n this._dropdown.style.width = `${rect.width}px`;\n\n // Decide whether to open upward or downward\n if (spaceBelow < dropdownHeight && rect.top > dropdownHeight) {\n // Open upward\n this._dropdown.style.top = '';\n this._dropdown.style.bottom = `${viewportHeight - rect.top + GAP}px`;\n this.setAttribute('position', 'top');\n } else {\n // Open downward\n this._dropdown.style.top = `${rect.bottom + GAP}px`;\n this._dropdown.style.bottom = '';\n this.removeAttribute('position');\n }\n }\n\n private _selectOption(option: HTMLElement): void {\n const value = option.getAttribute('value') || option.textContent?.trim() || '';\n const previousValue = this._value;\n\n this._value = value;\n this.setAttribute('value', value);\n this._updateTriggerLabel();\n this._closeDropdown();\n this._emitChange(previousValue);\n this._trigger?.focus();\n }\n\n private _updateTriggerLabel(): void {\n // 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) => o.textContent?.trim() || '')];\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 // Update selected state on options\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n const isSelected = opt.getAttribute('value') === this._value;\n if (isSelected) {\n opt.setAttribute('selected', '');\n } else {\n opt.removeAttribute('selected');\n }\n });\n }\n\n private _getSelectedLabel(): string {\n // Match by property, not an interpolated attribute selector — a value with\n // `\"`/`]` (e.g. a remote model id) would make querySelector throw SyntaxError.\n for (const opt of this.querySelectorAll('aparte-option')) {\n if (opt.getAttribute('value') === this._value) return opt.textContent?.trim() || '';\n }\n return '';\n }\n\n private _filterOptions(query: string): void {\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n const label = opt.textContent?.toLowerCase() || '';\n const matches = label.includes(query);\n (opt as HTMLElement).style.display = matches ? '' : 'none';\n });\n // Re-anchor the keyboard highlight on the first still-visible option.\n if (this._isOpen) this._setActive(0);\n }\n\n private _emitChange(previousValue: string): void {\n const detail: AparteSelectChangeDetail = {\n value: this._value,\n label: this._getSelectedLabel(),\n previousValue\n };\n\n this.dispatchEvent(new CustomEvent<AparteSelectChangeDetail>('aparte-select-change', {\n bubbles: true,\n composed: true,\n detail\n }));\n }\n}\n\n// Register\nif (!customElements.get('aparte-select')) {\n customElements.define('aparte-select', AparteSelect);\n}\n","/**\n * AparteProgressSpinner\n *\n * Circular progress spinner web component.\n * - Indeterminate (no `value` attribute): continuous rotation animation\n * - Determinate (`value=\"0–100\"`): fills the arc proportionally\n *\n * The ABSENCE of the attribute is what selects indeterminate, so `value=\"\"` is not\n * \"unknown progress\" — it parses to 0, i.e. an empty determinate arc. `value` is clamped\n * to 0–100 and anything non-numeric reads as 0; nothing throws.\n *\n * It renders its own SVG into itself on connect and on every `value` change, so it takes\n * no children: whatever you put inside is overwritten. The SVG is `aria-hidden` and the\n * ARIA lives on the host (`role=\"progressbar\"`, `aria-valuemin`/`aria-valuemax`, plus\n * `aria-valuenow` only when determinate) — there is no accessible NAME, so give the\n * element an `aria-label` unless the surrounding text already says what is loading.\n *\n * It draws an arc; it does not manage a loading lifecycle — no delay before appearing, no\n * timeout, no label, no live announcement. Under `prefers-reduced-motion: reduce` the\n * rotation stops (aparte.css scopes that rule to the library's own elements), which is the\n * other reason the indeterminate arc must not be the only signal that work is in flight.\n *\n * @element aparte-progress-spinner\n * @attr {number} value - Progress percentage 0–100 (omit for indeterminate)\n *\n * @cssprop [--aparte-spinner-size=16px] - Width and height of the element; the SVG fills it.\n * @cssprop [--aparte-spinner-stroke=2.5] - Stroke width of both arcs, in the units of the 24×24 viewBox.\n * @cssprop [--aparte-spinner-color=currentColor] - Stroke of the filled (progress) arc.\n * @cssprop [--aparte-spinner-track=color-mix(in srgb, currentColor 15%, transparent)] - Stroke of the track arc behind it.\n *\n * @example\n * <!-- Omit `value` for the indeterminate spin; set it to show real progress. -->\n * <aparte-progress-spinner></aparte-progress-spinner>\n * <aparte-progress-spinner value=\"62\"></aparte-progress-spinner>\n */\nexport class AparteProgressSpinner extends HTMLElement {\n static get observedAttributes(): string[] { return ['value']; }\n\n /** Radius of the SVG circle (viewBox is 0 0 24 24, center at 12,12) */\n private readonly _r = 9;\n private get _circ(): number { return 2 * Math.PI * this._r; }\n\n connectedCallback(): void { this._render(); }\n attributeChangedCallback(): void { this._render(); }\n\n private _render(): void {\n const raw = this.getAttribute('value');\n const value = raw !== null\n ? Math.min(100, Math.max(0, parseFloat(raw) || 0))\n : null;\n\n this.setAttribute('role', 'progressbar');\n this.setAttribute('aria-valuemin', '0');\n this.setAttribute('aria-valuemax', '100');\n if (value !== null) {\n this.setAttribute('aria-valuenow', String(value));\n } else {\n this.removeAttribute('aria-valuenow');\n }\n\n // Determinate: dashoffset shrinks from circ→0 as value goes 0→100\n const dashoffset = value !== null ? this._circ * (1 - value / 100) : 0;\n // Indeterminate: fixed partial arc (~72% of circumference)\n const dasharray = value !== null\n ? `${this._circ.toFixed(2)}`\n : `${(this._circ * 0.72).toFixed(2)} ${(this._circ * 0.28).toFixed(2)}`;\n\n this.innerHTML = `<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><circle class=\"aparte-spinner-track\" cx=\"12\" cy=\"12\" r=\"${this._r}\"/><circle class=\"aparte-spinner-fill\" cx=\"12\" cy=\"12\" r=\"${this._r}\" stroke-dasharray=\"${dasharray}\" stroke-dashoffset=\"${dashoffset.toFixed(2)}\"/></svg>`;\n }\n}\n\nif (!customElements.get('aparte-progress-spinner')) {\n customElements.define('aparte-progress-spinner', AparteProgressSpinner);\n}\n","import { resolveConfig } from '../../config/config-context.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\nimport type { AparteIconName } from '../../config/icon-provider.js';\n\n/**\n * AparteIcon\n *\n * The library's icon set, reachable from MARKUP.\n *\n * It existed only in JavaScript. Core ships 25 glyphs and `setIconProvider` is sold as the\n * lever that swaps them, but the only door in was `getIcon(name)` — so a consumer writing\n * plain HTML could not place one, and the icon provider could not reach a single icon that\n * consumer wrote themselves. `<aparte-composer-action>`'s own documentation tells you to\n * put an `<svg>` inside it, which is the same gap stated as an instruction.\n *\n * That gap is why every example on the CSS-classes reference carried 265 characters of\n * path data to demonstrate a 60-character class: there was no shorter way to say \"an icon\n * goes here\" that actually drew one. `<aparte-icon name=\"copy\">` is that way.\n *\n * It routes through `getIcon`, so it is not a second icon mechanism — it is a markup\n * entrance to the one that exists. Register a provider and every `<aparte-icon>` on the\n * page follows, including the ones in your own templates.\n *\n * ONE CONSEQUENCE, stated because it is the real cost: the 25 glyph NAMES become public\n * API. `expand`, `copy`, `nextBranch` were internal identifiers; renaming one now breaks a\n * consumer's markup.\n *\n * It renders into itself and takes no children — whatever you put inside is replaced. The\n * SVG is `aria-hidden`, because an icon beside a label is decoration; when the icon IS the\n * button's only content, name the BUTTON (`aria-label`), not this.\n *\n * @element aparte-icon\n * @attr {string} name - Which glyph to draw. One of the names `setIconProvider` accepts;\n * an unknown name draws nothing rather than a broken-image box.\n *\n * @cssprop [--aparte-icon-size=16px] - Width and height. `--sm`/`--lg`/`--xl` set it.\n *\n * @example\n * <aparte-icon name=\"copy\"></aparte-icon>\n * <aparte-icon name=\"check\" class=\"aparte-icon--lg\"></aparte-icon>\n * <button class=\"aparte-btn aparte-btn--icon\" aria-label=\"Copy\">\n * <aparte-icon name=\"copy\"></aparte-icon>\n * </button>\n */\nexport class AparteIcon extends HTMLElement {\n static get observedAttributes(): string[] { return ['name']; }\n\n private _unsubscribe: (() => void) | null = null;\n\n connectedCallback(): void {\n this._render();\n /*\n * A provider registered AFTER this element mounted still reaches it. Without\n * this the icons already on the page kept the built-in glyph while everything\n * rendered later got the consumer's — the same split `<aparte-composer-send>`\n * subscribes to avoid.\n */\n this._unsubscribe = subscribeConfigChange(this, () => this._render());\n }\n\n disconnectedCallback(): void {\n this._unsubscribe?.();\n this._unsubscribe = null;\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this._render();\n }\n\n private _render(): void {\n const name = this.getAttribute('name');\n /*\n * `getIcon` is typed to the known names and falls back per name, so an unknown\n * one would land on `undefined` and print it. Drawing NOTHING is the honest\n * failure: a misspelled name leaves a gap the author can see, where the string\n * \"undefined\" in a button would read as a rendering bug in the library.\n */\n const glyph = name ? resolveConfig(this).getIconProvider()[name as AparteIconName]?.() : null;\n this.innerHTML = glyph ?? '';\n // Decoration by default — see the class note above for when to name what instead.\n this.firstElementChild?.setAttribute('aria-hidden', 'true');\n }\n}\n\nif (!customElements.get('aparte-icon')) {\n customElements.define('aparte-icon', AparteIcon);\n}\n","/**\n * <aparte-elicitation> — the default elicitation presenter.\n *\n * Registers itself as the presenter for the config governing its subtree\n * (`resolveConfig(this)`), so `requestUserInput()` from a tool handler is routed\n * here WITHOUT any window events — the typed presenter contract replaces the\n * stringly-typed `aparte-ask-user-*` events that drifted in Phase 1.\n *\n * On a request it builds the schema-appropriate panel (enum / boolean / string /\n * object) and mounts it inside the nearest `<aparte-composer>` via its\n * `showPanel` API, resolving:\n * - accept — the send button (panel submit), when all fields are complete\n * - decline — the inline \"Skip\" affordance\n * - cancel — the assistant turn was stopped/errored while pending\n *\n * Its CSS ships in `@aparte/core/styles.css` like every other component — it used\n * to inject its own <style> from here, which made it the one surface that could not\n * be themed and whose variables were missing from the generated CSS reference.\n *\n * Place anywhere inside the chat (it renders nothing itself):\n * <aparte-elicitation></aparte-elicitation>\n */\n\nimport { resolveConfig, runWithConfig, type AparteConfigAware } from '../../config/config-context.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\nimport type { AparteConfig } from '../../config/aparte-config.js';\nimport type { AparteComposer, AparteComposerPanelMode } from '../composer/aparte-composer.js';\nimport { buildElicitationPanel, type BuiltElicitationPanel } from '../../elicitation/panel.js';\nimport { buildApprovalPanel } from '../../elicitation/approval-panel.js';\nimport type { AparteElicitationRequest, AparteElicitationResult, AparteElicitationPresenter } from '../../elicitation/types.js';\nimport { AparteElicitationAbortError } from '../../elicitation/types.js';\n\n/**\n * The slice of the composer this presenter drives.\n *\n * DERIVED from the component rather than re-typed by hand. It used to be a literal\n * copy of the three signatures, which is a twin: adding a parameter to the real\n * `setPanelSubmitEnabled` left this one behind, and the compiler pointed at the\n * CALLER instead of the stale declaration. `import type` is erased, so nothing here\n * pulls the composer element into a runtime import — which is why the copy existed.\n */\ntype ComposerEl = HTMLElement & Pick<AparteComposer, 'showPanel' | 'hidePanel' | 'setPanelSubmitEnabled'>;\n\ninterface Pending {\n /** End the request without an answer — see `AparteElicitationAbortError`. */\n abort(): void;\n composer: ComposerEl;\n /**\n * Re-apply every string this request took from the locale, in place.\n *\n * ONE function, where this used to hold the panel and the skip button separately\n * so the component could relabel each. That only worked because there was one kind\n * of panel; an approval's strings live elsewhere, and a second field per kind is\n * how the two would drift.\n */\n relabel(): void;\n}\n\n/**\n * The default presenter for a request to the human. It renders nothing itself: it\n * registers as the presenter for the config governing its subtree, and mounts a panel\n * inside the nearest `<aparte-composer>` when something asks — a tool handler calling\n * `requestUserInput`, or core's own approval gate.\n *\n * It dispatches no events on purpose. A request is answered through the typed presenter\n * contract, not by listening for one; the `aparte-tool-decision` event this replaced\n * existed only because the buttons used to live in a segment renderer with no reference\n * to the client.\n *\n * It has no children to project: `connectedCallback` sets\n * `display: none`, and the composer it presents in is found by walking UP from\n * `this.parentElement` — anything placed inside this element is only hidden with it.\n * So its position matters but its content does not: mount it anywhere inside the\n * `<aparte-chat>` whose questions it should answer.\n *\n * Not the element to reach for when you want a question UI of your own shape. It is\n * one caller of `setElicitationPresenter`, and among presenters registered for the\n * same chat the most recent one wins — so an app with a framework-native presenter\n * registers that and does not mount this, and a second `<aparte-elicitation>` in one\n * chat is redundant rather than additive. It is also not usable outside a chat that\n * has an `<aparte-composer>`: with nowhere to mount a panel the request is REJECTED,\n * on purpose, rather than borrowed into another chat's composer.\n *\n * The panel it mounts is styled by `@aparte/core/styles.css` — the `--aparte-elic-*`\n * and `--aparte-approval-*` knobs below theme it. They are declared here rather than\n * on the composer because this presenter is what builds the panel; the composer only\n * lends it the slot.\n *\n * @element aparte-elicitation\n *\n * @cssprop [--aparte-elic-gap=6px] - Vertical gap between the panel's rows (message, body, tabs).\n * @cssprop [--aparte-elic-padding=6px 4px] - Padding inside the panel.\n * @cssprop [--aparte-elic-max-height=50vh] - Cap on the panel's height; its body scrolls, the panel does not.\n * @cssprop [--aparte-elic-field-gap=8px] - Space and separator padding between two fields of an object schema.\n * @cssprop [--aparte-elic-message-size=0.82rem] - Font size of the question text at the top of the panel.\n * @cssprop [--aparte-elic-title-size=0.8rem] - Font size of a field's title.\n * @cssprop [--aparte-elic-desc-size=0.76rem] - Font size of a field's description.\n * @cssprop [--aparte-elic-option-padding=7px 10px] - Padding of one enum/boolean option row.\n * @cssprop [--aparte-elic-option-radius=8px] - Corner radius of an option row.\n * @cssprop [--aparte-elic-option-title-size=0.875rem] - Font size of an option's label, and of the text inputs.\n * @cssprop [--aparte-elic-option-desc-size=0.78rem] - Font size of an option's secondary line.\n * @cssprop [--aparte-elic-control-size=15px] - Size of the radio/checkbox control in an option row.\n * @cssprop [--aparte-elic-input-radius=6px] - Corner radius of the text inputs and of the Skip button.\n * @cssprop [--aparte-elic-textarea-min-height=64px] - Minimum height of a multi-line string field.\n * @cssprop [--aparte-elic-input-size=0.85rem] - Font size of the approval panel's instruction field (the free-text note the user writes).\n * @cssprop [--aparte-elic-skip-size=0.8rem] - Font size of the corner \"Skip\" affordance (the decline).\n * @cssprop [--aparte-elic-step-size=0.78rem] - Font size of a step tab, when the schema is asked one field at a time.\n * @cssprop [--aparte-elic-step-padding=4px 2px] - Padding of a step tab.\n * @cssprop [--aparte-elic-step-gap=14px] - Gap between step tabs.\n * @cssprop [--aparte-elic-step-underline=2px] - Thickness of the current step's underline (a tab, not a pill).\n * @cssprop [--aparte-elic-dismiss-room=72px] - Space kept clear at the end of the tab rail for the corner escape.\n * @cssprop [--aparte-approval-gap=4px] - Gap between the stacked options of an approval request.\n * @cssprop [--aparte-approval-option-size=0.85rem] - Font size of an approval option button.\n * @cssprop [--aparte-approval-option-padding=8px 10px] - Padding of an approval option button.\n * @cssprop [--aparte-approval-option-radius=8px] - Corner radius of an approval option button.\n * @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-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n *\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` and had no instance filter at all, so a\n * Stop (or an error) in one chat cancelled the question a DIFFERENT chat was\n * waiting on — and that chat's model was told the user had refused a question\n * the user was still looking at. Same defect as the `compact()` handler that\n * emptied both chats, which its four sibling handlers in `AparteClient` already\n * guarded against.\n *\n * The leniency rule is the composer's, deliberately: an event with no\n * `targetId` is for everyone (a single-chat app never sets one), and a chat we\n * cannot identify accepts everything rather than becoming deaf. Only a\n * MISMATCH is ignored.\n */\n private _onTurnEnd = (e: Event): void => {\n const evtTargetId = (e as CustomEvent).detail?.targetId as string | undefined;\n const own = this._pendingTargetId();\n if (evtTargetId && own && evtTargetId !== own) return;\n this._cancelPending();\n };\n\n private _unsubscribeConfig: (() => void) | null = null;\n\n connectedCallback(): void {\n this.style.display = 'none';\n // A language switch while a question is OPEN. Every other live-config\n // consumer in core got this seam; the panel could not use it because it kept\n // no reference to itself — see `Pending.panel`.\n this._unsubscribeConfig = subscribeConfigChange(this, () => this._relabelPending());\n // Become the presenter for this instance's config (or the global one).\n // `this` as the owner: it is what lets a request naming a `target` reach the\n // presenter in the SAME chat, instead of whichever one mounted last.\n resolveConfig(this).setElicitationPresenter(this._present, this);\n // Safety net: if the turn is stopped/errored while a request is open,\n // resolve it as cancelled so the client loop unblocks and the composer\n // input is restored.\n window.addEventListener('aparte-message-aborted', this._onTurnEnd);\n window.addEventListener('aparte-message-error', this._onTurnEnd);\n }\n\n /**\n * The boundary above us appeared, changed, or went away — move the\n * registration with it.\n *\n * `connectedCallback` alone is not enough and cannot be: registering is a\n * WRITE, and under all four wrappers it happens before `attachConfig` runs, so\n * it lands on the global singleton. `requestUserInput()` then resolves the\n * instance config, finds nothing, and rejects the request — the model\n * hears the user refuse a question the user never saw.\n *\n * See {@link AparteConfigAware}.\n */\n aparteConfigChanged(next: AparteConfig, previous: AparteConfig): void {\n // Withdraw OURS by name. `setElicitationPresenter(null)` cleared the whole\n // registry, so moving one chat's registration took every other mounted chat's\n // presenter down with it.\n previous.removeElicitationPresenter(this._present);\n next.setElicitationPresenter(this._present, this);\n }\n\n disconnectedCallback(): void {\n // Ours only. This used to clear the slot whenever it happened to hold our\n // presenter, which left a still-mounted sibling chat unable to ask anything for\n // the life of the page — silently, since the no-presenter warning fires once.\n resolveConfig(this).removeElicitationPresenter(this._present);\n window.removeEventListener('aparte-message-aborted', this._onTurnEnd);\n window.removeEventListener('aparte-message-error', this._onTurnEnd);\n this._unsubscribeConfig?.();\n this._unsubscribeConfig = null;\n this._cancelPending();\n }\n\n /**\n * Re-apply the open question's strings, in place.\n *\n * Two owners, and both have to move or the panel goes bilingual: the panel's own\n * defaults (`relabel`), and the Skip button, which this file builds and this file\n * therefore has to re-text. The composer's one button is a third, and it already\n * follows — `aparte-composer-send` remembers the panel state it was given.\n */\n private _relabelPending(): void {\n if (!this._pending) return;\n const cfg = resolveConfig(this);\n runWithConfig(cfg, () => this._pending!.relabel());\n }\n\n private _present: AparteElicitationPresenter = (request: AparteElicitationRequest) => {\n /*\n * No concurrency guard here any more — `AparteConfig.requestUserInput` queues,\n * so a second request arrives only once this one has settled.\n *\n * What was here answered the second request `cancel` immediately: a refusal\n * invented for a question nobody was shown. And it protected only requests\n * that came through THIS presenter, so a consumer's own presenter had nothing.\n * If two ever do overlap, `showPanel` now evicts and NOTIFIES the first, which\n * degrades to a settled request instead of a wedged one.\n */\n const composer = this._getComposer();\n // Mounted outside a chat that has a composer: there is nowhere to put the\n // panel, which is the same situation as no presenter at all.\n if (!composer) return Promise.reject(new AparteElicitationAbortError('no-presenter'));\n\n return new Promise<AparteElicitationResult>((resolve, reject) => {\n let done = false;\n /**\n * The slot this request owns, once `showPanel` has handed it over.\n *\n * `settle` can run BEFORE that — an already-aborted signal settles on the\n * spot — so it starts absent, and `hidePanel(undefined)` then closes whatever\n * is there, which is correct because nothing of ours is open yet.\n *\n * A holder rather than a `let`: `settle` reads it before `showPanel` assigns\n * it, which is exactly the shape `prefer-const` rejects, and the object says\n * \"not handed over yet\" more plainly than an unassigned binding.\n */\n const slot: { token?: symbol } = {};\n /**\n * Who had the focus before the panel took it.\n *\n * The panel focuses itself on open (`panel.focus()`, both branches below)\n * and nothing gave it back: a keyboard user who approved a tool call landed\n * at the top of the document and had to tab through the whole page to write\n * their next message. That is SC 2.4.3, level A, on the one flow the library\n * puts forward — and the ARIA Authoring Practices Guide requires it of every\n * dialogue-shaped pattern.\n *\n * Captured HERE rather than beside `panel.focus()` because there are two\n * branches that open a panel and one `close()` that ends both; a value read\n * once, before either, cannot disagree with itself.\n */\n const focusedBefore = document.activeElement;\n const close = (): boolean => {\n if (done) return false;\n done = true;\n this._pending = null;\n /*\n * Read BEFORE `hidePanel`, because that removes the focused element and\n * the browser then drops focus to `<body>` — after which there is no\n * way to tell whether the user was still in the panel or had moved on.\n *\n * And only if they were still in it. A request can settle late — an\n * abort, or a model that answered while the reader clicked elsewhere —\n * and pulling the focus back from wherever they went would be the same\n * theft in the other direction.\n */\n const active = document.activeElement;\n const focusWasInPanel = active instanceof Node && composer.contains(active);\n\n // Scoped to our own panel: finishing late must not tear down the panel\n // that replaced ours.\n composer.hidePanel(slot.token);\n\n /*\n * `isConnected` because the element that had the focus may itself have\n * been inside what just closed. Nothing further if it is gone: inventing\n * a destination — the editor, the send button — would be a policy this\n * component has no standing to set, and `<body>` is where the browser\n * puts it anyway.\n */\n if (focusWasInPanel && focusedBefore instanceof HTMLElement && focusedBefore.isConnected) {\n focusedBefore.focus();\n }\n return true;\n };\n const settle = (result: AparteElicitationResult): void => {\n if (close()) resolve(result);\n };\n /**\n * End it without an answer.\n *\n * A rejection rather than a third `action`, because a value is easy to\n * mistake for an answer and this one was: the approval gate read the old\n * `cancel` as a refusal and told the model the user had refused a tool they\n * had only stopped.\n */\n const fail = (reason: 'aborted' | 'no-presenter' = 'aborted'): void => {\n if (close()) reject(new AparteElicitationAbortError(reason));\n };\n\n // Caller-side cancellation (tool handler signal: timeout / turn abort).\n if (request.signal) {\n if (request.signal.aborted) { fail(); return; }\n request.signal.addEventListener('abort', () => fail(), { once: true });\n }\n\n // Built INSIDE this instance's config, so the panel's own strings come\n // from the locale of the chat that asked — `contextConfig()` reads the\n // ambient render config, and without this it would fall back to the\n // global one on a page where each chat has its own.\n const cfg = resolveConfig(this);\n\n /*\n * An APPROVAL: a decision, not a value.\n *\n * Same slot, same queue, same teardown — only what goes inside the panel\n * differs, which is the whole claim of one mechanism with two\n * presentations. The options come with the request because only the\n * requester can write them.\n *\n * There is always an exit here without touching the composer's own\n * controls: every option is a button. That matters because a panel takes\n * the send button over, so Stop is unreachable while one is open — for a\n * question the escape is the corner, for an approval it is a refusal.\n */\n if (request.kind === 'approval') {\n /*\n * The button exists only once an INSTRUCTION has been written.\n *\n * The options never route through it — a decision is its own click —\n * so with `mode: 'submit'` from the start it sat there permanently\n * disabled beside them, offering an act that did not exist. It is the\n * written text, and only that, which is the act this button already\n * means; until there is some, the panel has none for it.\n */\n const approvalMode = (): AparteComposerPanelMode => (panel.isComplete() ? 'submit' : 'none');\n const panel = runWithConfig(cfg, () =>\n buildApprovalPanel(request.message, request.options ?? [], () => {\n composer.setPanelSubmitEnabled(panel.isComplete(), approvalMode());\n }, request.details));\n panel.onSettle((answer) => settle({ action: 'accept', content: answer }));\n this._pending = { abort: () => fail(), composer, relabel: () => panel.relabel() };\n slot.token = composer.showPanel(panel.el, {\n submitEnabled: panel.isComplete(),\n mode: approvalMode(),\n onSubmit: () => { if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() }); },\n onEvict: () => fail(),\n });\n panel.focus();\n return;\n }\n // A question without a schema has nothing to collect; that is an approval,\n // and it was handled above. The fallback keeps this branch total rather\n // than throwing on a shape the type already forbids.\n const schema = request.schema ?? { type: 'string' as const };\n const panel: BuiltElicitationPanel = runWithConfig(cfg, () =>\n buildElicitationPanel(request.message, schema, () => {\n composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());\n }));\n\n // \"Skip\" → decline (MCP's decline: the user chose not to answer), in the\n // panel's CORNER. It sat beside the button that advances through the form,\n // and that adjacency read as \"skip this question\" while it declines the\n // whole request — see `dismiss` on BuiltElicitationPanel.\n const skip = document.createElement('button');\n skip.type = 'button';\n skip.className = 'aparte-btn aparte-elic-skip';\n skip.textContent = cfg.t('elicitationSkip');\n skip.addEventListener('click', () => settle({ action: 'decline' }));\n panel.dismiss.appendChild(skip);\n\n /*\n * The click that IS the answer — a single question whose options are\n * buttons. Same wiring as the approval panel's, because it is the same\n * act; the panel decides which of its shapes has it, and reports through\n * `mode()` that the composer's button has nothing to do.\n */\n panel.onSettle((content) => settle({ action: 'accept', content }));\n\n this._pending = {\n abort: () => fail(),\n composer,\n relabel: () => { panel.relabel(); skip.textContent = resolveConfig(this).t('elicitationSkip'); },\n };\n slot.token = composer.showPanel(panel.el, {\n submitEnabled: panel.canProceed(),\n mode: panel.mode(),\n /*\n * Something else took the slot — another request, a conversation switch,\n * or a turn ending. The composer tears the panel down either way; only\n * this callback can settle the promise, and without it the request hung\n * AND `_pending` stayed set, so every later question was short-circuited\n * for the life of the page. `cancel`, not `decline`: nobody declined\n * anything, the question was taken away.\n */\n onEvict: () => fail(),\n onSubmit: () => {\n // One meaning: submit the lot. Moving between questions is the chips'.\n if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() });\n },\n });\n panel.focus();\n });\n };\n\n private _cancelPending(): void {\n this._pending?.abort();\n }\n\n /**\n * The host id of the chat whose composer holds the open panel.\n *\n * Walks up from the composer rather than from `this`, because the panel lives in\n * the composer and that is what the turn belongs to. Matches the hosts\n * `aparte-chat-bubble._resolveTargetId()` matches, for the reason written there:\n * Angular's wrapper root IS the `<aparte-chat>` element, while the plain-root\n * wrappers render a `[data-aparte-chat]` div instead, so matching only the tag\n * resolves `undefined` on three wrappers out of four.\n */\n private _pendingTargetId(): string | undefined {\n let el: HTMLElement | null = this._pending?.composer ?? null;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n /**\n * The composer to present in: the nearest one in an ancestor subtree, and\n * nothing else.\n *\n * There used to be a `document.querySelector('aparte-composer')` fallback, which\n * is the \"first chat on the page\" bug this repo has now fixed in four other\n * places: on a page with two chats, an elicitation that could not find its own\n * composer mounted its panel in the OTHER chat's — so one conversation's question\n * appeared under the other conversation, and answering it resolved a tool call\n * belonging to a chat the user was not looking at.\n *\n * Returning `null` instead REJECTS the request, which is honest: nothing was\n * shown, so nothing was answered. The warning names the fix, because this is a\n * setup mistake and only the developer can correct it — the guide's own example\n * puts `<aparte-elicitation>` inside `<aparte-chat>`.\n */\n private _getComposer(): ComposerEl | null {\n let node: Element | null = this.parentElement;\n while (node) {\n const composer = node.querySelector('aparte-composer') as ComposerEl | null;\n if (composer && typeof composer.showPanel === 'function') return composer;\n // Stop AT the chat boundary. Removing the explicit\n // `document.querySelector` fallback was not enough on its own: this walk\n // reached `<body>`, and a `querySelector` from there searches the whole\n // document — so it found another chat's composer anyway, by a longer\n // route. The two-chat test caught exactly that.\n const tag = node.tagName?.toLowerCase();\n const isChatBoundary = tag === 'aparte-chat' || node.hasAttribute?.('data-aparte-chat');\n if (isChatBoundary) break;\n node = node.parentElement;\n }\n console.warn(\n '[aparte-elicitation] No <aparte-composer> in this element\\'s subtree, so the request '\n + 'could not be shown, so it REJECTED and the turn halted. Nothing was told to the '\n + 'model — there is nothing true to tell it. Move <aparte-elicitation> '\n + 'inside the <aparte-chat> it belongs to. It is deliberately NOT borrowing another '\n + 'chat\\'s composer: on a page with two chats that put the question under the wrong one.',\n );\n return null;\n }\n}\n\nif (typeof customElements !== 'undefined' && !customElements.get('aparte-elicitation')) {\n customElements.define('aparte-elicitation', AparteElicitation);\n}\n","import type { AparteChatViewport } from '../viewport/aparte-chat-viewport.js';\nimport type { AparteComposer } from '../composer/aparte-composer.js';\n// Defines <aparte-elicitation>, which the default composition below writes. A tag\n// nothing has defined is an inert unknown element, so the import is the difference\n// between a presenter and a placeholder.\nimport '../elicitation/aparte-elicitation.js';\nimport { escapeAttr } from '../../utils/escape.js';\n\n/**\n * AparteChat - The Shell\n *\n * The container element for a chat. It lays out its Light DOM children as a flex\n * column: an `<aparte-chat-viewport>` takes the space left over (`flex: 1 1 auto`)\n * and scrolls, an `<aparte-composer>` keeps its own height below it. Light DOM on\n * purpose, so the page's own global CSS reaches inside.\n *\n * The presence of an `<aparte-chat-viewport>` child at connect is the exact test for\n * \"the author composed this\". Find one and the children are used as given — this\n * element moves none of them, so anything else you drop in (a header, a banner above\n * the composer) is simply another row of that column, in DOM order. Find none and\n * `innerHTML` is OVERWRITTEN with a default composition — a viewport, an\n * `<aparte-elicitation>` presenter, and a composer shell holding an input and a send\n * button, plus the two attachment primitives when `attachments` is set — so children\n * written without a viewport anywhere inside them are destroyed, that header included.\n * The test is a DESCENDANT query, so a viewport nested in a wrapper of your own still\n * counts — compose it yourself with the viewport somewhere in the tree, or leave the tag\n * empty. Angular's wrapper sets `framework-managed` instead of relying on that test,\n * because its children do not exist yet when this element upgrades; React, Vue and\n * Svelte never create this element at all, so the question does not arise for them.\n *\n * Being a component (not a bare `<div>`), it also owns behaviour a wrapper div\n * can't: with `center-empty`, it watches its own viewport and keeps the composer\n * centered as a welcome state until the first `<aparte-chat-bubble>` lands, then\n * slides to the normal layout — no external JavaScript. While centered it carries\n * `data-empty` on itself (set and cleared by that same watcher), which is the hook to\n * style the welcome state from an app's own CSS. The watcher needs a viewport somewhere\n * inside, and hand-written markup always has one because composing the default injects\n * it — so the only path where no watcher starts and `data-empty` is never set is\n * `framework-managed`, where the framework owns the subtree anyway. The stylesheet\n * centers through\n * `aparte-chat[center-empty][data-empty]` and its DIRECT viewport child, so a\n * framework-managed host that nests the viewport inside a container of its own gets\n * nothing from the attribute — the wrappers ship their own centered layout.\n *\n * It is also one of the anchors where core re-declares its derived CSS layer, so\n * overriding a master — `--aparte-primary`, a surface, a text colour — on a single\n * `<aparte-chat>` re-derives the values computed from it for that instance rather\n * than moving one button. That is per-instance theming. The literal palette is\n * deliberately not re-declared here, so a chat nested in a dark wrapper stays dark.\n *\n * Presentational only: it does NOT wire a transport/client. Attach an\n * `AparteClient`, or handle `aparte-send` yourself, as with the primitives.\n * Size the element via CSS (a height, or let it fill a sized parent).\n *\n * @element aparte-chat\n * @attr {string} placeholder - Placeholder for the composer input (default composition)\n * @attr {boolean} disabled - Disables the composer\n * @attr {boolean} center-empty - Center the composer as a welcome state until the first message, then slide to the normal layout\n * @attr {boolean} framework-managed - The wrapper's explicit hands-off signal: set it and this\n * element composes none of its own children, because the framework owns them. Read once at\n * connect (it is not observed), so it has to be in the initial markup. Angular's wrapper sets\n * it on this element — its component selector IS `aparte-chat`; React/Vue/Svelte render a\n * `[data-aparte-chat]` div and never create this element at all.\n * @attr {boolean} attachments - Add the file picker + chips strip to the default composition (opt-in: the host must consume the files — an `AparteClient` does, a hand-rolled loop must read `event.detail.files`)\n *\n * @cssprop [--aparte-chat-bottom-gap=var(--aparte-space-8, 16px)] - Space below the\n * composer, as `padding-block-end` on the shell (the same rule covers a wrapper's\n * `[data-aparte-chat]` root). The gap belongs to this element because padding applied\n * from outside would also shrink the scroll area, stopping the transcript short of the\n * edge instead of scrolling to it.\n *\n * Composing it yourself is the other form, and the container still lays it out and still\n * runs `center-empty`. It is written out here rather than as a second `@example` for a\n * mechanical reason: every element-own example is concatenated into ONE live frame on the\n * generated reference page, so a second `<aparte-chat>` there rendered as a second whole\n * chat — two empty composers with 600px of nothing between them.\n *\n * ```html\n * <aparte-chat center-empty attachments style=\"height: 320px\">\n * <aparte-chat-viewport></aparte-chat-viewport>\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n * ```\n *\n * @example\n * <!-- Left empty it fills in a viewport, an input and a send button. -->\n * <aparte-chat center-empty placeholder=\"Say something…\" style=\"height: 320px\"></aparte-chat>\n *\n * <script>\n * // Seeded so the frame shows a real exchange rather than an empty box: this\n * // example is RENDERED, not only read.\n * const chat = document.querySelector('aparte-chat');\n * chat.viewport.appendMessage({ id: 'u1', role: 'user', content: 'What is a transport?' });\n * chat.viewport.appendMessage({\n * id: 'a1',\n * role: 'assistant',\n * content: 'The object that talks to the model. Swap it and the UI does not change.',\n * });\n * </script>\n */\nexport class AparteChat extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['placeholder', 'disabled', 'center-empty', 'attachments'];\n }\n\n private _observer: MutationObserver | null = null;\n\n /**\n * True only for the composition THIS element injected. An author-provided\n * composer (or a `framework-managed` host) is never edited by the attachments\n * toggle below — those own their own markup.\n */\n private _ownsShell = false;\n\n connectedCallback(): void {\n this._render();\n this._forwardAttr('placeholder');\n this._forwardAttr('disabled');\n this._syncEmptyWatch();\n }\n\n disconnectedCallback(): void {\n this._observer?.disconnect();\n this._observer = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n if (name === 'center-empty') {\n this._syncEmptyWatch();\n return;\n }\n if (name === 'attachments') {\n this._syncAttachments();\n return;\n }\n // placeholder / disabled forward to the inner composer. An explicit removal\n // mirrors through; a never-set attribute is left alone (so a caller-provided\n // composer keeps its own).\n const composer = this.querySelector('aparte-composer');\n if (!composer) return;\n if (newValue !== null) composer.setAttribute(name, newValue);\n else composer.removeAttribute(name);\n }\n\n /** The message viewport (yours or the default), or `null` before connect. */\n get viewport(): AparteChatViewport | null {\n return this.querySelector('aparte-chat-viewport');\n }\n\n /** The composer (yours or the default), or `null` before connect. */\n get composer(): AparteComposer | null {\n return this.querySelector('aparte-composer');\n }\n\n private _render(): void {\n // A framework wrapper renders the composition itself — and its children do not\n // exist yet when this runs (the element is upgraded on insert, before the\n // framework's template renders), so the viewport check below can't see them.\n // `framework-managed` is the wrapper's explicit \"hands off\" signal: without it\n // the default composition below would be injected UNDER the framework's own.\n if (this.hasAttribute('framework-managed')) return;\n\n // Author-provided composition wins — if a viewport is already inside, use the\n // children as given and only lay them out (via CSS). Otherwise fill in a\n // default viewport + composer so the empty tag \"just works\".\n if (this.querySelector('aparte-chat-viewport')) return;\n\n // The composer's `placeholder` is read by its input via `closest()` at upgrade\n // time (no event), so it must be on the element in the initial markup.\n const placeholder = this.getAttribute('placeholder');\n const composerAttrs =\n (placeholder !== null ? ` placeholder=\"${escapeAttr(placeholder)}\"` : '') +\n (this.hasAttribute('disabled') ? ' disabled' : '');\n\n // Attachments are opt-in: the picker only makes sense when the host consumes\n // the files (an `AparteClient` inlines them per `rawFileInject`; a hand-rolled\n // loop must read `event.detail.files`). Offering it unconditionally would show\n // a button that silently drops what the user attached.\n const attachments = this.hasAttribute('attachments');\n\n /*\n * The presenter ships in the default composition, and that is a change of tier.\n *\n * It renders nothing by itself — it registers as the presenter for this subtree and\n * mounts a panel in the composer when something asks. It used to be opt-in, which\n * was right while asking the user was a plugin's business. It is not any more: the\n * BUILT-IN approval gate asks through it, so a chat without one cannot honour\n * `needsApproval` at all. An affordance core can honour end to end is on by default\n * (ratified decision #8, tier a); leaving this out would have made the gate depend\n * on a tag nobody was told to write.\n */\n this.innerHTML = `\n <aparte-chat-viewport></aparte-chat-viewport>\n <aparte-elicitation></aparte-elicitation>\n <aparte-composer${composerAttrs}>\n <div class=\"aparte-composer-shell\">\n ${attachments ? '<aparte-composer-attachments></aparte-composer-attachments>' : ''}\n <div class=\"aparte-composer-row\">\n ${attachments ? '<aparte-composer-add-attachment></aparte-composer-add-attachment>' : ''}\n <aparte-composer-input></aparte-composer-input>\n <aparte-composer-send></aparte-composer-send>\n </div>\n </div>\n </aparte-composer>\n `;\n this._ownsShell = true;\n }\n\n /**\n * Add/remove the two attachment primitives on the composition we injected, so\n * toggling the attribute after mount works like the wrappers' reactive prop\n * (there, a re-render does it). Author-provided markup is left alone.\n */\n private _syncAttachments(): void {\n if (!this._ownsShell) return;\n const composer = this.composer;\n const shell = composer?.querySelector('.aparte-composer-shell');\n const row = shell?.querySelector('.aparte-composer-row');\n if (!composer || !shell || !row) return;\n\n const strip = shell.querySelector('aparte-composer-attachments');\n const picker = row.querySelector('aparte-composer-add-attachment');\n\n if (this.hasAttribute('attachments')) {\n if (!strip) shell.insertBefore(document.createElement('aparte-composer-attachments'), row);\n if (!picker) row.insertBefore(document.createElement('aparte-composer-add-attachment'), row.firstChild);\n return;\n }\n\n strip?.remove();\n picker?.remove();\n // Files picked before the capability was withdrawn would otherwise ride on\n // the next send with nothing in the UI showing them.\n composer.clearAttachments();\n }\n\n /** Set an attribute on the inner composer only when the shell carries it. */\n private _forwardAttr(name: string): void {\n if (!this.hasAttribute(name)) return;\n this.querySelector('aparte-composer')?.setAttribute(name, this.getAttribute(name) ?? '');\n }\n\n /** Start/stop watching the viewport so `center-empty` toggles itself. */\n private _syncEmptyWatch(): void {\n this._observer?.disconnect();\n this._observer = null;\n\n if (!this.hasAttribute('center-empty')) {\n this.removeAttribute('data-empty');\n return;\n }\n\n const viewport = this.querySelector('aparte-chat-viewport');\n if (!viewport) return;\n\n this._updateEmpty();\n // A message is an <aparte-chat-bubble>; watch the viewport for the first one.\n this._observer = new MutationObserver(() => this._updateEmpty());\n this._observer.observe(viewport, { childList: true, subtree: true });\n }\n\n private _updateEmpty(): void {\n const viewport = this.querySelector('aparte-chat-viewport');\n const empty = !viewport || !viewport.querySelector('aparte-chat-bubble');\n this.toggleAttribute('data-empty', empty);\n }\n\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat')) {\n customElements.define('aparte-chat', AparteChat);\n}\n","import type {\n AparteBubbleRole,\n AparteSegment,\n AparteAttachment,\n AparteBranchNavigateEventDetail,\n 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 } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig } 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';\n\n/**\n * Warn ONCE when a segment has no renderer — now only for types core has never\n * heard of, since the built-ins install themselves on first use.\n */\nlet _warnedNoRenderer = false;\nfunction warnMissingRenderer(type: string): void {\n if (_warnedNoRenderer) return;\n _warnedNoRenderer = true;\n console.warn(`[aparte] No renderer for segment \"${type}\". Register one with registerSegmentRenderer({ type: '${type}', render }) from @aparte/core — see https://apartejs.dev/guides/customization/#custom-segment-types`);\n}\n\n/**\n * What a segment renders as when no renderer claims its type.\n *\n * `AparteCustomSegment.fallback` is documented as \"Optional fallback text\n * representation\" and was read by NOTHING — the field existed, the type published it,\n * and a custom segment arriving where its renderer is not registered (a conversation\n * replayed in another app, a client that loads its views lazily, an export) showed\n * `[Unknown segment type: custom]` while carrying the sentence written for exactly that\n * moment. Found while writing the segment's own `@example`, which is the kind of dead\n * declaration documentation is good at surfacing.\n *\n * The developer warning is skipped when a fallback is present: an author who supplied\n * one has already said this can happen, and warning then is crying wolf. Without one it\n * still fires, because a missing renderer is otherwise silent.\n *\n * `textContent`, so a fallback is text and cannot carry markup — the same rule the rest\n * of the library follows for anything a model or a host can produce.\n */\nfunction unrenderedSegment(segment: { type: string; fallback?: unknown }): HTMLElement {\n const fallback = typeof segment.fallback === 'string' && segment.fallback.trim() ? segment.fallback : null;\n if (!fallback) warnMissingRenderer(segment.type);\n const el = document.createElement('div');\n el.className = fallback ? 'aparte-segment aparte-segment-fallback' : 'aparte-segment aparte-segment-unknown';\n el.textContent = fallback ?? `[Unknown segment type: ${segment.type}]`;\n return el;\n}\n\n/**\n * The renderer for `type`, installing core's built-ins the first time a segment\n * finds the registry empty of its type. `registerDefaultRenderers()` therefore\n * becomes optional rather than a call you discover by seeing\n * `[Unknown segment type: text]` on screen (it is still honoured, and\n * `AparteClient({ autoRegister: false })` still keeps the built-ins out).\n */\nfunction resolveSegmentRenderer(\n type: string,\n config: AparteConfig,\n): ReturnType<typeof getSegmentRenderer> {\n // The CONFIG is passed in, not read ambiently.\n //\n // `runWithConfig` wrapped only `render` / `setup` / `update`, so the renderer's\n // OWN work was per-instance while the question \"which renderer is this?\" was\n // answered from a module-level registry. Two chats on a page therefore shared\n // their segment renderers no matter what `config` prop the wrapper was given —\n // half of the promise those props make.\n const renderer = getSegmentRenderer(type, config);\n if (renderer) return renderer;\n installDefaultRenderersOnce(config);\n return getSegmentRenderer(type, config);\n}\n\n/**\n * Normalize a segment renderer's output to a single element. Renderers may return\n * an HTML **string** (parsed via innerHTML — the built-in renderers) or a ready\n * **HTMLElement** (used directly, so custom renderers can wire event listeners /\n * framework nodes with no innerHTML XSS surface). See {@link AparteSegmentRenderer}.\n */\nfunction segmentRenderResultToElement(result: string | HTMLElement, 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 * One message: plain content or a list of rich segments, in light DOM.\n *\n * Normally created for you by `<aparte-chat-viewport>`, one per message in the store;\n * you write the tag by hand only when you drive the DOM yourself. It is ONE message\n * with one role — a transcript is the viewport's job, and a bubble is not a\n * general-purpose card.\n *\n * **Not a slot host.** `_render()` writes its own markup into the light DOM on\n * connect, so children placed inside the tag are replaced rather than projected.\n * Everything customizable is a registered hook instead of a child: the structural\n * shell (`setBubbleShellRenderer` — it must root at `.aparte-message` and carry the\n * region hooks, since every query here is null-guarded and a partial shell silently\n * loses that region), the avatar (`setAvatarProvider`), the attachment chips\n * (`setAttachmentRenderer`), the `‹1/2›` position indicator\n * (`setSiblingNavRenderer`) and the body itself (`registerSegmentRenderer`).\n *\n * Two content paths, mutually exclusive: the `content` attribute (plain text run\n * through the configured Markdown provider, then highlighted once — after streaming\n * ends, not per token) and `setSegments()` / `addSegment()`. Segments win:\n * `.aparte-content` stays hidden for as long as any exist. The painted\n * `.aparte-message-content` box hides itself when there is nothing in it, so a\n * message that is only attachments is not a coloured rectangle.\n *\n * The bubble owns no transport and no host behaviour. The action bar and the branch\n * picker only dispatch the events below; nothing here retries a turn, persists an\n * edit, opens a stats popover or switches a branch. Which buttons exist follows from\n * that: `copy` is on by default, `edit` / `retry` / `feedback` need\n * `setBubbleActions`, `info` needs both that flag and a prior `setUsage()` (a details\n * button over no numbers is a dead button), and an image attachment becomes a preview\n * button only once `setHostHandlers` declares a lightbox — undeclared it stays a\n * picture, with no role, tab stop or pointer.\n *\n * The error state is derived from the segments (an `error` segment sets `data-error`\n * on `.aparte-message`), never from a status attribute, so it behaves identically in\n * vanilla and in every wrapper.\n *\n * All seven events are declared by hand rather than left to the analyser, which\n * found six. `aparte-branch-navigate` is dispatched from the `_onBranchPickerClick`\n * arrow class field, and the auto-detection visits `ts.isMethodDeclaration` only —\n * so the one event belonging to the branch picker was the one missing from the\n * manifest, and from the generated reference, for as long as both existed.\n *\n * @element aparte-chat-bubble\n *\n * @attr {string} 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 no longer reads 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: `_updateTimestamp` accepts either and only coerces when the value is numeric.\n * @attr {string} message-id - How streaming and the action bar address this bubble.\n * @attr {boolean} streaming - Hides the action bar and shows the caret while a reply is in flight.\n * @attr {string} name - The display name in the header.\n *\n * @fires {CustomEvent<AparteActionEventDetail>} aparte-action - A custom action-bar button was pressed.\n * @fires {CustomEvent<AparteRetryEventDetail>} aparte-retry - Retry was pressed; the host forks the turn.\n * @fires {CustomEvent<AparteEditEventDetail>} aparte-edit - An edit was saved.\n * @fires {CustomEvent<AparteFeedbackEventDetail>} aparte-feedback - Thumbs up or down.\n * @fires {CustomEvent<AparteMessageInfoEventDetail>} aparte-message-info - The info affordance was pressed.\n * @fires {CustomEvent<AparteBranchNavigateEventDetail>} aparte-branch-navigate - The `‹1/2›` picker moved between sibling versions.\n * @fires {CustomEvent<AparteAttachmentPreviewEventDetail>} aparte-attachment-preview - An attached image was clicked, asking the app to open it full-size.\n * @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=12px] - Gap between the avatar column and the body (the viewport reuses it between messages).\n * @cssprop [--aparte-message-padding=16px 12px] - Padding around one message row.\n * @cssprop [--aparte-message-max-width=800px] - Width of the centred message row.\n *\n * @cssprop [--aparte-message-content-radius=14px] - Radius of the painted content box.\n * @cssprop [--aparte-message-content-padding=10px 14px] - Padding of the USER box only; the assistant's content is plain full-width prose.\n * @cssprop --aparte-message-content-bg-user - 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=var(--aparte-text)] - Text colour inside the user box.\n * @cssprop [--aparte-message-content-text-assistant=var(--aparte-text)] - Text colour inside the assistant box.\n *\n * @cssprop [--aparte-avatar-size=32px] - Square size of the avatar slot.\n * @cssprop [--aparte-avatar-radius=var(--aparte-radius-avatar)] - Avatar corner radius.\n * @cssprop [--aparte-avatar-font-size=14px] - Size of the initial, for a shell that renders one (the default shell leaves the slot empty, and `.aparte-avatar:empty` hides it).\n * @cssprop [--aparte-avatar-bg-user=var(--aparte-primary)] - Avatar background, user role.\n * @cssprop [--aparte-avatar-text-user=var(--aparte-text-inverse)] - Avatar text colour, user role.\n * @cssprop [--aparte-avatar-bg-assistant=var(--aparte-surface-3)] - Avatar background, assistant role.\n * @cssprop [--aparte-avatar-text-assistant=var(--aparte-text-inverse)] - Avatar text colour, assistant role.\n * @cssprop [--aparte-avatar-image-user=none] - `background-image` for the user avatar — a logo with no AvatarProvider and no JS.\n * @cssprop [--aparte-avatar-image-assistant=none] - `background-image` for the assistant avatar.\n * @cssprop [--aparte-avatar-image-size=90%] - `background-size` for both avatar images.\n *\n * @cssprop [--aparte-name-font-size=14px] - Sender name in the header.\n * @cssprop [--aparte-name-color=var(--aparte-text)] - Sender name colour.\n * @cssprop [--aparte-timestamp-font-size=12px] - Timestamp in the header.\n * @cssprop [--aparte-timestamp-color=var(--aparte-text-muted)] - Timestamp colour.\n * @cssprop [--aparte-content-font-size=15px] - Body type size, applied to both the plain-content and the segments container.\n * @cssprop [--aparte-content-color=var(--aparte-text)] - Body text colour.\n * @cssprop [--aparte-content-line-height=var(--aparte-line-height-loose)] - Body line height.\n *\n * @cssprop [--aparte-attachments-max-height=140px] - Cap on the sent-attachment strip; past it the strip scrolls instead of growing.\n * @cssprop [--aparte-attachment-image-size=40px] - Tile size in the strip. The strip re-declares the global 72px down to 40px, since these are thumbnails inside a conversation.\n * @cssprop [--aparte-thumb-radius=var(--aparte-radius-lg)] - Attachment tile radius (shared with the composer's preview tiles).\n * @cssprop [--aparte-thumb-name-color=#ffffff] - Filename overlaid on a tile.\n * @cssprop --aparte-thumb-name-scrim - Gradient behind that filename, so it stays legible over any image.\n * @cssprop [--aparte-thumb-name-padding=14px 5px 4px] - Padding of the filename overlay.\n *\n * @cssprop [--aparte-action-bar-gap=4px] - Gap between action buttons (and between the footer's two regions).\n * @cssprop [--aparte-action-bar-btn-size=28px] - Square size of an action button; also the footer's reserved height.\n * @cssprop [--aparte-action-bar-btn-color=var(--aparte-text-muted)] - Action icon colour at rest.\n * @cssprop [--aparte-action-bar-btn-hover-bg=var(--aparte-surface-2)] - Action button hover background (the branch arrows reuse it).\n * @cssprop [--aparte-action-bar-btn-hover-color=var(--aparte-text)] - Action icon colour on hover.\n *\n * @cssprop [--aparte-branch-picker-gap=4px] - Gap between the arrows and the position label.\n * @cssprop [--aparte-branch-picker-btn-size=20px] - Square size of each arrow.\n * @cssprop [--aparte-branch-picker-btn-icon-size=16px] - Glyph size inside an arrow.\n * @cssprop [--aparte-branch-picker-btn-color=var(--aparte-text-muted)] - Arrow colour at rest.\n * @cssprop [--aparte-branch-picker-btn-hover-color=var(--aparte-text)] - Arrow colour on hover (a disabled arrow is dimmed instead).\n * @cssprop [--aparte-branch-picker-label-size=12px] - Type size of the position label.\n * @cssprop [--aparte-branch-picker-label-color=var(--aparte-text-muted)] - Colour of the position label.\n * @cssprop [--aparte-branch-picker-label-min-width=32px] - Reserved label width, so `9 / 9` growing to `10 / 12` does not shift the arrows.\n *\n * @cssprop [--aparte-waiting-height=1.5em] - Min height of the waiting region, so the first token does not jump the layout.\n * @cssprop [--aparte-waiting-dot-gap=4px] - Gap between the three waiting dots.\n * @cssprop [--aparte-status-dot-size=6px] - Diameter of a waiting dot (shared with the status indicator).\n * @cssprop [--aparte-status-color=var(--aparte-text-muted)] - Colour of the waiting dots (shared with the status indicator).\n *\n * @cssprop [--aparte-error-solid=#dc2626] - Ring drawn around the avatar while `data-error` is set. The error CARD itself belongs to the error segment renderer.\n *\n * @example\n * <!-- Rendered for you by the viewport. Written by hand only when you drive the DOM\n * yourself: `message-id` is what streaming and the action bar address it by. -->\n * <aparte-chat-bubble\n * message-id=\"a1\"\n * data-role=\"assistant\"\n * name=\"Assistant\"\n * content=\"Hello.\"\n * ></aparte-chat-bubble>\n *\n * <!-- While a reply is in flight: `streaming` hides the action bar and shows the caret. -->\n * <aparte-chat-bubble message-id=\"a2\" data-role=\"assistant\" streaming></aparte-chat-bubble>\n *\n * <!-- One reply among several. `setSiblings(count, index)` is what draws the picker, and\n * it is a METHOD, not an attribute — so a branch cannot be shown by markup alone.\n * Retry forks a sibling instead of overwriting the reply, and this is the control\n * that walks them; each press dispatches `aparte-branch-navigate` for a host to\n * answer. Kept in the example because a guide that describes branching has no other\n * way to SHOW it. -->\n * <aparte-chat-bubble\n * message-id=\"a3\"\n * data-role=\"assistant\"\n * name=\"Assistant\"\n * content=\"A second take on the same question.\"\n * ></aparte-chat-bubble>\n *\n * <script>\n * document.querySelector('aparte-chat-bubble[message-id=\"a3\"]').setSiblings(2, 0);\n * </script>\n */\nexport class AparteChatBubble extends HTMLElement {\n private _contentEl: HTMLDivElement | null = null;\n private _segmentsEl: HTMLDivElement | null = null;\n private _attachmentsEl: HTMLDivElement | null = null;\n private _actionBarEl: HTMLDivElement | null = null;\n private _branchPickerEl: HTMLDivElement | null = null;\n private _footerEl: HTMLDivElement | null = null;\n private _content = '';\n private _streaming = false;\n private _segments: AparteSegment[] = [];\n private _role: AparteBubbleRole = 'assistant';\n private _attachments: AparteAttachment[] = [];\n private _usage: AparteUsage | null = null;\n /** Cleanup returned by the avatar provider — called on disconnect/re-render. */\n private _avatarCleanup: (() => void) | null = null;\n /** Sibling count for tree-based branch navigation (set by setSiblings()) */\n private _siblingCount = 1;\n /** Sibling index for tree-based branch navigation (set by setSiblings()) */\n private _siblingIndex = 0;\n /** True while the user-message inline editor is open. */\n private _editing = false;\n /** The live inline editor (the composer's contenteditable primitive), present only while `_editing`. */\n private _editInput: AparteComposerInput | null = null;\n\n static get observedAttributes(): string[] {\n // `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 };\n\n /**\n * Ask every rendered segment to re-read its config-derived text.\n *\n * Not `_renderSegments()`, which wipes the container and rebuilds: that destroys a\n * mounted artifact preview, reverts a reasoning block the reader expanded by\n * clicking `<summary>` (the DOM's real state is never written back to `collapsed`),\n * resets scroll inside long terminal panes, drops focus from an Approve/Reject\n * gate, and throws away the incremental Markdown parser's buffered lookahead\n * mid-stream — for a change that added no content. It also fires container-wide\n * childList mutations, which is what the viewport's observer reads as \"scroll to\n * the bottom\".\n *\n * `relabel` is the narrow alternative, bound by the same no-child-node rule as\n * `update()`. A renderer that has no config-derived text does not implement it,\n * and this loop simply skips it.\n */\n private _relabelSegments(): void {\n if (!this._segmentsEl) return;\n for (const segment of this._segments) {\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (!renderer?.relabel) continue;\n const el = this._segmentsEl.querySelector(\n `:scope > [data-segment-id=\"${cssEscape(segment.id)}\"]`,\n ) as HTMLElement | null;\n if (!el) continue;\n runWithConfig(this._cfg, () => renderer.relabel!(el, segment));\n }\n }\n\n /**\n * Re-apply the locale strings written straight into the markup by `_render()` —\n * the accessible names a screen reader reads, which nothing else refreshes.\n */\n private _updateLocalizedLabels(): void {\n const locale = this._cfg.getLocale();\n const set = (selector: string, label: string): void => {\n this.querySelector(selector)?.setAttribute('aria-label', label);\n };\n set('.aparte-branch-prev', locale.previousResponse ?? 'Previous response');\n set('.aparte-branch-next', locale.nextResponse ?? 'Next response');\n set('.aparte-action-bar', locale.messageActions ?? 'Message actions');\n }\n\n /**\n * Config governing this bubble: the instance config of the nearest\n * `[data-aparte-host]` boundary, else the global singleton. Resolved live\n * (a single `closest()`) rather than cached — the boundary may be attached\n * AFTER this bubble mounts (AparteChatHost.bind() runs post-mount), so a\n * connect-time cache would freeze the wrong config.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n\n connectedCallback(): void {\n this._render();\n this._updateContent();\n // Populate the timestamp from the current attribute. Frameworks that set\n // attributes BEFORE the element is connected (e.g. the Svelte wrapper) fire\n // attributeChangedCallback while _render() hasn't created `.aparte-timestamp`\n // yet, so the initial time would otherwise stay blank. No-ops when the\n // attribute is absent (set later → attributeChangedCallback handles it).\n this._updateTimestamp(this.getAttribute('timestamp'));\n window.addEventListener('aparte-config-change', this._onConfigChange);\n // Delegated, so a re-render cannot lose a click on the branch arrows.\n this.addEventListener('click', this._onBranchPickerClick);\n this.addEventListener('click', this._onLinkClick);\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 }\n\n /** Set content directly */\n setContent(content: string): void {\n this._content = content;\n this.setAttribute('content', content);\n // A REPLACE, not an append: the incremental parser tracks how many characters it has\n // already written, so leaving its state behind would make the next token's delta a\n // slice of the wrong string. A retry does exactly this — clear, then re-stream.\n this._resetMarkdownStream();\n this._updateContent();\n }\n\n /**\n * Drop the incremental Markdown parser's state.\n *\n * Only needed where `_content` is REPLACED rather than grown. `appendToken` grows it, so\n * the parser's cursor stays valid there — which is the whole point of the seam.\n */\n private _resetMarkdownStream(): void {\n const host = this as AparteMarkdownStreamHost;\n if (host._aparteSmd) host._aparteSmd.renderer.end();\n host._aparteSmd = undefined;\n }\n\n /** Get current content */\n getContent(): string {\n return this._content;\n }\n\n /** Set segments for rich content */\n setSegments(segments: AparteSegment[]): void {\n /*\n * Copy the array IN, the way `getSegments()` already copies it OUT.\n *\n * That asymmetry was the bug: the bubble defended its list on the way out and\n * adopted the caller's on the way in. `populateBubbleFromMessage` hands over\n * `message.segments` — the repository's own array — so the bubble and the model\n * ended up advancing ONE array. `appendToSegment` then wrote each chunk twice:\n * the viewport replaced the slot with `{...segment, content: old + chunk}`, the\n * bubble looked the segment up in what it thought was its own list, found that\n * replacement (chunk already in it) and appended the chunk again. Measured:\n * \"ThatThat deletesdeletes aa filefile\".\n *\n * This is the same failure 3b026bb fixed for `addSegment` — where it does not\n * happen, because the bubble pushes into a list it created itself, so the\n * viewport's replacement decouples the two immediately. A message arriving with\n * its segments already populated went around that fix, exactly as `AparteClient`\n * went around the one before it. One copy here closes the last shared array:\n * `setSegments` has a single production caller, so all three paths through\n * `populateBubbleFromMessage` are covered by this line.\n *\n * The objects stay shared, deliberately — that is the arrangement `addSegment`\n * produces and that `appendToSegment` is written for: the first write on either\n * side replaces its own slot and the two are independent from then on.\n */\n this._segments = [...segments];\n this._renderSegments();\n this._updateWaiting();\n }\n\n /** Add a segment */\n addSegment(segment: AparteSegment): void {\n this._segments.push(segment);\n this._appendSegmentEl(segment);\n this._updateWaiting();\n }\n\n /** Update a specific segment */\n updateSegment(segmentId: string, updates: Partial<AparteSegment>): void {\n const index = this._segments.findIndex(s => s.id === segmentId);\n if (index !== -1) {\n const updated = mergeSegmentUpdate(this._segments[index]!, updates);\n this._segments[index] = updated;\n this._applySegmentUpdate(segmentId, updated, updates);\n }\n }\n\n /** Append content to a segment */\n appendToSegment(segmentId: string, content: string): void {\n const segment = this._segments.find(s => s.id === segmentId);\n if (segment && 'content' in segment) {\n (segment as { content: string }).content += content;\n this._applySegmentUpdate(segmentId, segment, { content: (segment as AparteSegment & { content: string }).content });\n }\n }\n\n /** Get all segments */\n getSegments(): AparteSegment[] {\n return [...this._segments];\n }\n\n /** Remove a segment by id (e.g. to discard a transient waiting indicator) */\n /**\n * Scoped to DIRECT children on purpose.\n *\n * Segments are appended as direct children of the container, but a descendant\n * query returns the first match in document order — and sanitized model\n * markdown renders inside that same container, with `data-*` attributes\n * deliberately preserved (they are inert). So a decoy `data-segment-id` planted\n * in an earlier segment's prose used to win over the real segment element.\n *\n * Parser ids are unguessable UUIDs, but a tool segment is `tool-${toolCallId}`\n * and the MODEL chooses that id — so this was reachable, and pointing an update\n * at a decoy left a rejected tool rendering as still-running: a spoof against\n * the human-in-the-loop control.\n */\n removeSegment(segmentId: string): void {\n const index = this._segments.findIndex(s => s.id === segmentId);\n if (index !== -1) {\n this._segments.splice(index, 1);\n }\n const el = this._segmentsEl?.querySelector(`:scope > [data-segment-id=\"${cssEscape(segmentId)}\"]`);\n el?.remove();\n this._updateWaiting();\n }\n\n /** Set attachments (chips shown above message content, user role only) */\n setAttachments(attachments: AparteAttachment[]): void {\n this._attachments = attachments;\n this._updateAttachments();\n // 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 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 if (shell) {\n const out = runWithConfig(this._cfg, () => shell({ role: this._role, name: displayName, avatarInitial: initial }));\n if (out instanceof HTMLElement) this.replaceChildren(out);\n else this.innerHTML = out;\n } else {\n this.innerHTML = `\n <div class=\"aparte-message\" data-role=\"${escapeAttr(role)}\" role=\"article\" aria-label=\"${escapeAttr(this._getAriaLabel())}\">\n <div class=\"aparte-avatar\" data-role=\"${escapeAttr(role)}\"></div>\n <div class=\"aparte-body\">\n <div class=\"aparte-header\">\n <span class=\"aparte-name\">${escapeHtml(displayName)}</span>\n <span class=\"aparte-timestamp\"></span>\n </div>\n <div class=\"aparte-attachments\" hidden></div>\n <div class=\"aparte-message-content\">\n <div class=\"aparte-segments\"></div>\n <div class=\"aparte-content\"></div>\n <div class=\"aparte-waiting\" hidden>\n <span class=\"aparte-dots\" aria-hidden=\"true\"><span class=\"aparte-dot\"></span><span class=\"aparte-dot\"></span><span class=\"aparte-dot\"></span></span>\n <span class=\"aparte-sr-only\"></span>\n </div>\n </div>\n <div class=\"aparte-footer\">\n <div class=\"aparte-branch-picker\" hidden>\n <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev\" aria-label=\"${escapeAttr(this._cfg.getLocale().previousResponse ?? 'Previous response')}\">&#8249;</button>\n <span class=\"aparte-branch-label\">1 / 1</span>\n <!-- The move has to be ANNOUNCED. Pressing the arrows deliberately does not\n take focus, so without a live region a screen-reader user gets the new\n branch and no indication anything changed. The visible label cannot be\n the region itself: a custom sibling-nav renderer may replace it with\n dots, which reads as nothing. No new locale key — the position is\n digits, and the buttons beside it already carry translated labels. -->\n <span class=\"aparte-sr-only aparte-branch-status\" aria-live=\"polite\"></span>\n <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next\" aria-label=\"${escapeAttr(this._cfg.getLocale().nextResponse ?? 'Next response')}\">&#8250;</button>\n </div>\n <div class=\"aparte-action-bar\" role=\"toolbar\" aria-label=\"${escapeAttr(this._cfg.getLocale().messageActions ?? 'Message actions')}\"></div>\n </div>\n </div>\n </div>\n `;\n }\n\n this._contentEl = this.querySelector('.aparte-content');\n this._segmentsEl = this.querySelector('.aparte-segments');\n this._attachmentsEl = this.querySelector('.aparte-attachments');\n this._actionBarEl = this.querySelector('.aparte-action-bar');\n // 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, which\n * used to be a bubble with a name and an empty body.\n *\n * The dots are CSS (no per-token work, themable, honours reduced-motion); the\n * accessible name is `locale.typing`, next to the `aria-busy` the streaming state\n * already sets. A custom bubble shell without the region simply has no indicator\n * (same null-guarded degradation as the other region hooks).\n */\n private _updateWaiting(): void {\n const empty = this._segments.length === 0 && !this._content.trim();\n const waiting = this._streaming && this._role !== 'user' && empty;\n\n // The painted box is hidden when it has nothing to paint. It carries the user\n // bubble's background, padding and radius, so an empty one is a coloured\n // rectangle with nothing in it — which is exactly what a message that is ONLY\n // attachments produced: the chips render ABOVE this box, so the box had no\n // content, no segments and no dots, and still drew itself.\n //\n // `hidden` and not `style.display`, deliberately: nothing sets an explicit\n // `display` on this class, so the UA sheet's rule applies. Where a component\n // DOES set one, `[hidden]` loses — a trap this repo has already paid for.\n const box = this.querySelector('.aparte-message-content') as HTMLElement | null;\n if (box) box.hidden = empty && !waiting;\n\n // 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 is left exactly as the shell rendered it — which for\n * the default shell means EMPTY, and hidden by `.aparte-avatar:empty`. This used\n * to claim it \"falls back to the default initial rendered by `_render()`\"; there\n * is no such initial, and believing there was is what made `_updateRole` write\n * one.\n */\n private _renderAvatar(): void {\n const avatar = this.querySelector('.aparte-avatar') as HTMLElement | null;\n if (!avatar) return;\n\n // Tear down any previously-mounted live component before re-rendering.\n if (this._avatarCleanup) {\n try { this._avatarCleanup(); } catch { /* ignore */ }\n this._avatarCleanup = null;\n }\n\n const provider = this._cfg.getAvatarProvider();\n if (!provider) return; // leave the slot as the shell rendered it\n\n avatar.textContent = '';\n const cleanup = provider.render(this._role, avatar);\n if (typeof cleanup === 'function') this._avatarCleanup = cleanup;\n }\n\n private _updateRole(): void {\n const message = this.querySelector('.aparte-message');\n const avatar = this.querySelector('.aparte-avatar');\n const nameEl = this.querySelector('.aparte-name');\n\n if (message) {\n message.setAttribute('data-role', this._role);\n message.setAttribute('aria-label', this._getAriaLabel());\n }\n if (avatar) {\n avatar.setAttribute('data-role', this._role);\n // Refresh an initial that is ALREADY there; never create one — the default\n // shell renders this slot empty and the stylesheet hides it while it stays\n // empty. Same rule as `_updateName`, which is where it was actually costing\n // something; the reasoning is written out there.\n if (avatar.textContent) avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) {\n nameEl.textContent = this._getDisplayName();\n }\n // Re-render the action bar so buttons match the correct role\n // (critical when the role attribute is set after connectedCallback)\n this._updateActionBar();\n this._renderAvatar();\n }\n\n private _updateName(): void {\n const avatar = this.querySelector('.aparte-avatar') as HTMLElement | null;\n const nameEl = this.querySelector('.aparte-name');\n /*\n * Two conditions, and the second one is the fix.\n *\n * No provider: otherwise a name change would wipe a live avatar component.\n *\n * Already non-empty: the default shell renders this slot EMPTY and the\n * stylesheet hides it while it stays empty — `.aparte-avatar:empty { display:\n * none }`, with the comment \"No message avatar by default — the slot only shows\n * once an AvatarProvider (or a consumer) fills it\". Writing the initial\n * unconditionally contradicted that, and `_onConfigChange` calls this method, so\n * ANY notifying config change filled the slot: `setLocale` (a language switcher\n * is enough), `setBubbleActions`, `setIconProvider`. Avatars appeared across the\n * transcript on a click that had nothing to do with them, and undoing the click\n * did not remove them, because the text was already written.\n *\n * The guard is \"already non-empty\" rather than \"no provider\" on purpose:\n * `avatarInitial` is part of the shell contract, so a CUSTOM shell may render an\n * initial and must still see it refreshed. Empty stays empty; filled stays in\n * sync.\n */\n if (avatar && avatar.textContent && !this._cfg.getAvatarProvider()) {\n avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) nameEl.textContent = this._getDisplayName();\n }\n\n private _updateContent(): void {\n if (!this._contentEl) return;\n\n // If we have segments, don't render simple content\n if (this._segments.length > 0) {\n this._contentEl.style.display = 'none';\n this._updateWaiting();\n return;\n }\n\n this._contentEl.style.display = '';\n /*\n * The SAME incremental seam the text and thinking segment renderers use.\n *\n * This line used to be `innerHTML = renderMarkdown(this._content)` — the whole message\n * re-parsed, re-sanitised and re-inserted on every token. That is the hot path of the\n * first thing getting-started teaches (`appendMessage` / `appendToken` /\n * `completeMessage`), and it made a published promise false: `setStreamingMarkdownProvider`\n * says \"the chat bubble uses it to render the assistant message token-by-token\n * instead of re-parsing the whole string on every token\", and the plugin's own page\n * repeats it. Only the segment path honoured it. Found by a cold audit.\n *\n * With no streaming provider registered, `writeStreamedMarkdown` falls through to the\n * one-shot render — so a consumer who has not installed the plugin sees exactly what\n * they saw before.\n *\n * `runWithConfig`, because the seam reads its provider from the ambient config and this\n * bubble may be one of several with configs of their own.\n */\n runWithConfig(this._cfg, () =>\n writeStreamedMarkdown(this as AparteMarkdownStreamHost, this._contentEl!, this._content, this._streaming),\n );\n // The first token retires the waiting indicator (and a cleared content brings\n // it back, e.g. a retry that resets the bubble before re-streaming).\n this._updateWaiting();\n // The Markdown provider only emits plain <pre><code>; apply the registered\n // syntax highlighter (if any) to those blocks. Skipped while streaming —\n // re-run once on completion (see _updateStreaming) to avoid per-token churn.\n if (!this._streaming) this._highlightContentCode();\n }\n\n /**\n * Apply the registered syntax-highlight provider to the code blocks produced\n * by the Markdown provider in the simple-content path. Provider-agnostic: a\n * full-block provider (e.g. Shiki) returns `<pre>…</pre>` so we replace the\n * element; a token provider (e.g. Prism, highlight.js) returns inner HTML so\n * we fill the existing `<code>`. No-op when no highlighter is installed.\n */\n private _highlightContentCode(): void {\n if (!this._contentEl || !this._cfg.hasHighlightProvider()) return;\n this._contentEl.querySelectorAll('pre > code').forEach((codeEl) => {\n const code = codeEl.textContent ?? '';\n if (!code.trim()) return;\n const match = codeEl.className.match(/language-([\\w+#-]+)/i);\n const lang = match?.[1] ?? '';\n const pre = codeEl.parentElement;\n Promise.resolve(this._cfg.highlightCode(code, lang)).then((html) => {\n const out = (html ?? '').trim();\n if (!out || !pre || !pre.isConnected) return;\n if (/^<pre[\\s>]/i.test(out)) {\n pre.outerHTML = out; // full block (Shiki)\n } else {\n (codeEl as HTMLElement).innerHTML = out; // inner tokens (Prism, hljs)\n }\n }).catch(() => { /* keep the plain block on failure */ });\n });\n }\n\n private _renderSegments(): void {\n if (!this._segmentsEl) return;\n\n // Clear existing segments\n this._segmentsEl.innerHTML = '';\n\n for (const segment of this._segments) {\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (renderer) {\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)), segment);\n if (el) {\n this._segmentsEl.appendChild(el);\n runWithConfig(this._cfg, () => renderer.setup?.(el, segment));\n }\n } else {\n this._segmentsEl.appendChild(unrenderedSegment(segment));\n }\n }\n\n // Hide simple content when segments are present\n if (this._contentEl) {\n this._contentEl.style.display = this._segments.length > 0 ? 'none' : '';\n }\n this._reflectError();\n }\n\n /**\n * Reflect the error state on the bubble: `data-error` on `.aparte-message` while\n * an error segment is present. Derived from segments (not the message `status`\n * attribute) so it works identically in vanilla and in every wrapper — the\n * error segment flows through the reactive list in all of them. CSS themes\n * `.aparte-message[data-error]`; custom error content is via setErrorRenderer.\n */\n private _reflectError(): void {\n const message = this.querySelector('.aparte-message');\n if (!message) return;\n const hasError = this._segments.some(s => s.type === 'error');\n if (hasError) message.setAttribute('data-error', '');\n else message.removeAttribute('data-error');\n }\n\n private _updateTimestamp(value: string | number | null): void {\n const timestampEl = this.querySelector('.aparte-timestamp');\n if (!timestampEl || !value) return;\n\n try {\n const date = new Date(isNaN(Number(value)) ? value : Number(value));\n // The locale's own tag, not `undefined`. `undefined` means \"follow the\n // BROWSER\", which is why a French chat on an en-US browser still read\n // `7:32 PM` — the app had chosen a language and the clock had not heard.\n // Still `undefined` when no tag is declared: that is the documented default\n // and the behaviour every consumer has today.\n timestampEl.textContent = date.toLocaleTimeString(this._cfg.getLocale().tag || undefined, {\n hour: '2-digit',\n minute: '2-digit'\n });\n } catch {\n timestampEl.textContent = '';\n }\n }\n\n private _getAriaLabel(): string {\n const locale = this._cfg.getLocale();\n return this._role === 'user'\n ? (locale.yourMessage ?? 'Your message')\n : (locale.assistantResponse ?? 'Assistant response');\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Attachments\n // ─────────────────────────────────────────────────────────────────────────\n\n private _updateAttachments(): void {\n if (!this._attachmentsEl) return;\n\n if (this._role !== 'user' || this._attachments.length === 0) {\n this._attachmentsEl.hidden = true;\n this._attachmentsEl.innerHTML = '';\n return;\n }\n\n this._attachmentsEl.hidden = false;\n\n // Custom attachment chips (aparteGlobalConfig.setAttachmentRenderer) — one node per\n // attachment; the consumer owns markup + interactions (no default preview wiring).\n const customAttachment = this._cfg.getAttachmentRenderer?.();\n if (customAttachment) {\n this._attachmentsEl.replaceChildren();\n for (const a of this._attachments) {\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(a)));\n if (el) this._attachmentsEl.appendChild(el);\n }\n return;\n }\n\n this._attachmentsEl.innerHTML = this._attachments.map(a => {\n const name = escapeHtml(a.name);\n if (a.type.startsWith('image/')) {\n // `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-thumb__img\" src=\"${escapeHtml(a.url)}\" alt=\"${name}\" loading=\"lazy\" />`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--file\" title=\"${name}\">`\n + `<span class=\"aparte-thumb__ext\">${escapeHtml(this._fileExt(a.name))}</span>`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }).join('');\n\n // Image tiles ask for a full-size preview — but the lightbox is the app's, so\n // the tile only becomes a button once the app declared it opens one. Otherwise\n // it stays a plain picture: no role, no tab stop, no pointer (see the CSS,\n // which keys the cursor off role=\"button\").\n if (!this._cfg.getHostHandlers().attachmentPreview) return;\n this._attachmentsEl.querySelectorAll('.aparte-thumb--image').forEach(tile => {\n tile.setAttribute('role', 'button');\n tile.setAttribute('tabindex', '0');\n const open = (): void => {\n const img = tile.querySelector('.aparte-thumb__img') as HTMLImageElement | null;\n if (!img) return;\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true, composed: true,\n detail: { url: img.src, name: tile.getAttribute('title') ?? '' },\n }));\n };\n tile.addEventListener('click', open);\n tile.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key !== 'Enter' && key !== ' ') return;\n e.preventDefault();\n open();\n });\n });\n }\n\n /** Uppercased file extension (≤4 chars), or 'FILE' when there is none. */\n private _fileExt(filename: string): string {\n const dot = filename.lastIndexOf('.');\n return dot > 0 ? filename.slice(dot + 1).toUpperCase().slice(0, 4) : 'FILE';\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Branch Picker\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * The branch arrows are handled by DELEGATION, on this element, bound once.\n *\n * They used to get a fresh listener each, attached by `_render()` to the buttons\n * `_render()` had just created. So a click that landed while a re-render was\n * swapping those nodes hit an element about to be discarded, and did nothing at\n * all — not late, nothing. Invisible on a fast machine; reproducible on\n * WebKit-Linux in CI, where `‹` left the picker on \"2 / 2\" and a 20-second\n * assertion watched it stay there.\n *\n * Delegation makes `_render()` irrelevant to it: the listener lives on the host,\n * which is never replaced, and `closest()` finds whichever button exists at the\n * moment of the click. It is also less work — one listener per bubble instead of\n * two per bubble per render.\n *\n * Bound in `connectedCallback` and removed in `disconnectedCallback` as a stable\n * field, because an inline arrow re-added on every re-connect is how this repo has\n * stacked listeners twice before (the viewport, and `aparte-select`).\n */\n private _onBranchPickerClick = (event: Event): void => {\n const target = event.target as HTMLElement | null;\n const button = target?.closest?.('.aparte-branch-prev, .aparte-branch-next');\n if (!button || !this.contains(button)) return;\n const direction = button.classList.contains('aparte-branch-prev') ? 'prev' : 'next';\n const messageId = this.getAttribute('message-id');\n if (!messageId) return;\n const detail: AparteBranchNavigateEventDetail = { messageId, direction };\n // Tree-based navigation: let the viewport handle the branch switch\n this.dispatchEvent(new CustomEvent<AparteBranchNavigateEventDetail>('aparte-branch-navigate', {\n bubbles: true,\n composed: true,\n detail,\n }));\n };\n\n /**\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 (issue #38). 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 private _updateActionBar(): void {\n if (!this._actionBarEl) return;\n // While the inline editor is open the bar shows save (✓) / cancel (✗).\n if (this._editing) {\n this._renderEditActions();\n return;\n }\n const config = this._cfg.getBubbleActions();\n const icons = this._cfg.getIconProvider();\n const locale = this._cfg.getLocale();\n const buttons: string[] = [];\n\n if (this._role === 'user') {\n if (config.user) {\n // Explicit ordered set replaces the flag defaults for user bubbles.\n for (const a of config.user) buttons.push(this._actionButtonHtml(a, icons, locale));\n } else {\n // Flag-driven set. Only `copy` is on by default — see\n // APARTE_DEFAULT_BUBBLE_ACTIONS: edit needs a host to keep the new text.\n if (config.copy) buttons.push(this._actionButtonHtml('copy', icons, locale));\n if (config.edit) buttons.push(this._actionButtonHtml('edit', icons, locale));\n }\n } else if (this._role === 'assistant') {\n if (config.assistant) {\n // Explicit ordered set replaces the flag defaults (incl. the info button).\n for (const a of config.assistant) buttons.push(this._actionButtonHtml(a, icons, locale));\n } else {\n // Flag-driven set. Only `copy` is on by default — retry, feedback and\n // info all need a host or a listener to mean anything.\n if (config.copy) buttons.push(this._actionButtonHtml('copy', icons, locale));\n if (config.retry) buttons.push(this._actionButtonHtml('retry', icons, locale));\n if (config.feedback) {\n buttons.push(this._actionButtonHtml('thumbUp', icons, locale));\n buttons.push(this._actionButtonHtml('thumbDown', icons, locale));\n }\n if (config.info) buttons.push(this._actionButtonHtml('info', icons, locale));\n }\n }\n\n this._actionBarEl.innerHTML = buttons.join('');\n\n // Custom actions registered via aparteGlobalConfig.registerAction — appended\n // after the built-ins, built as DOM (label goes to attributes, never\n // interpolated into innerHTML) so a consumer label can't inject markup.\n this._appendCustomActions(icons);\n 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 * The bar has announced itself as a toolbar since it existed, and a toolbar is ONE\n * tab stop whose members are reached with the arrow keys — that is the whole of what\n * the role means to a screen-reader user. What shipped was five independent tab\n * stops per message, so the role described a behaviour that did not exist: a reader\n * told to press Right heard nothing move, and tabbing through a long transcript\n * walked 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 already run through (the flag-driven bar, the edit\n * bar, and the branch picker's own visibility pass). Re-deriving it there rather\n * than in each builder is what keeps the invariant true after a `setBubbleActions`\n * rebuild — the drift this pattern usually dies of.\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 was still a\n * `role=\"toolbar\"` with nothing in it (announced as such), and it still reserved\n * its fixed height plus the footer's under every bubble. So both follow their\n * contents — the footer stays as long as the branch picker or the bar has\n * something to show.\n */\n private _syncFooterVisibility(): void {\n if (this._actionBarEl) this._actionBarEl.hidden = this._actionBarEl.children.length === 0;\n 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(icons: ReturnType<AparteConfig['getIconProvider']>): void {\n if (!this._actionBarEl) return;\n for (const a of this._cfg.getActions('bubble')) {\n const roles = a.bubble?.roles ?? ['user', 'assistant'];\n if (!roles.includes(this._role)) continue;\n const btn = document.createElement('button');\n btn.className = 'aparte-btn aparte-btn--icon aparte-action-btn aparte-action-custom';\n btn.dataset['action'] = `custom:${a.id}`;\n // aria-label/title via setAttribute — safe for consumer-provided strings.\n btn.setAttribute('aria-label', a.label);\n btn.setAttribute('title', a.label);\n // Icon: raw inline SVG/HTML, else an icon-provider key (trusted output).\n const fromProvider = (icons as unknown as Record<string, (() => string) | undefined>)[a.icon];\n btn.innerHTML = a.icon.startsWith('<')\n ? a.icon\n : (typeof fromProvider === 'function' ? fromProvider() : (a.iconFallback ?? ''));\n this._actionBarEl.appendChild(btn);\n }\n }\n\n /** Build the `<button>` HTML for a single named action (shared by flag + per-role rendering). */\n private _actionButtonHtml(\n action: string,\n icons: ReturnType<AparteConfig['getIconProvider']>,\n locale: ReturnType<AparteConfig['getLocale']>,\n ): string {\n switch (action) {\n case 'copy': {\n const l = locale.copy ?? 'Copy';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-copy\" data-action=\"copy\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.copy()}</button>`;\n }\n case 'edit': {\n const l = locale.edit ?? 'Edit message';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-edit\" data-action=\"edit\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.edit()}</button>`;\n }\n case 'retry': {\n const l = locale.retry ?? 'Retry';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-retry\" data-action=\"retry\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.retry()}</button>`;\n }\n case 'thumbUp': {\n const l = locale.feedbackPositive ?? 'Good response';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-pos\" data-action=\"feedback-positive\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.thumbUp()}</button>`;\n }\n case 'thumbDown': {\n const l = locale.feedbackNegative ?? 'Bad response';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-neg\" data-action=\"feedback-negative\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.thumbDown()}</button>`;\n }\n case 'info': {\n // Only when there are numbers to show: a details button over nothing is a\n // dead button. The popover itself is the app's (see `aparte-message-info`).\n if (!this._usage) return '';\n const l = locale.messageInfo ?? 'Details';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-info\" data-action=\"info\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${this._cfg.getIcon('info')}</button>`;\n }\n default:\n return '';\n }\n }\n\n /** Render the edit-mode action bar: ✓ save (green) + ✗ cancel (red). */\n private _renderEditActions(): void {\n if (!this._actionBarEl) return;\n const locale = this._cfg.getLocale();\n const saveLabel = locale.editConfirm ?? 'Save';\n const cancelLabel = locale.editCancel ?? 'Cancel';\n this._actionBarEl.innerHTML =\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--success aparte-action-btn aparte-action-edit-save\" data-action=\"edit-save\" ` +\n `aria-label=\"${escapeAttr(saveLabel)}\" title=\"${escapeAttr(saveLabel)}\">${this._cfg.getIcon('check')}</button>` +\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--danger aparte-action-btn aparte-action-edit-cancel\" data-action=\"edit-cancel\" ` +\n `aria-label=\"${escapeAttr(cancelLabel)}\" title=\"${escapeAttr(cancelLabel)}\">${this._cfg.getIcon('close')}</button>`;\n this._actionBarEl.querySelectorAll('.aparte-action-btn').forEach(btn => {\n btn.addEventListener('click', (e) => this._handleActionClick(e as MouseEvent));\n });\n // Save/cancel must show even when every action flag is off.\n this._syncFooterVisibility();\n }\n\n private _handleActionClick(e: MouseEvent): void {\n const btn = (e.currentTarget as HTMLElement);\n const action = btn.dataset['action'];\n // Read dynamically — attribute may not be set yet at render time\n const messageId = this.getAttribute('message-id');\n\n // Custom actions (aparteGlobalConfig.registerAction) emit a generic aparte-action\n // event carrying the action id — same DOM-event contract as retry/feedback.\n if (action?.startsWith('custom:') && messageId) {\n const actionId = action.slice('custom:'.length);\n const detail: AparteActionEventDetail = {\n actionId,\n zone: 'bubble',\n messageId,\n role: this._role,\n targetId: this._resolveTargetId(),\n };\n this.dispatchEvent(new CustomEvent<AparteActionEventDetail>('aparte-action', {\n bubbles: true, composed: true, detail,\n }));\n this._cfg.getActions('bubble').find(x => x.id === actionId)?.onClick?.(e);\n return;\n }\n\n switch (action) {\n case 'copy': {\n // The reply, not the reasoning: a `thinking` segment is the model's scratchpad,\n // and the client already keeps it out of the history it sends back\n // (`_segmentsToText`) for the same reason. Copying it along with the answer\n // pasted a paragraph of deliberation above every reply — no assistant on the\n // market does that.\n const text = this._content || this._segments\n .filter(s => s.type !== 'thinking')\n .map(s => (s as { content?: string }).content ?? '')\n .join('\\n');\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 this._editing = true;\n this.querySelector('.aparte-message')?.setAttribute('data-editing', '');\n\n const input = document.createElement('aparte-composer-input') as AparteComposerInput;\n input.setAttribute('placeholder', this._cfg.getLocale().edit ?? 'Edit message');\n this._editInput = input;\n\n this._contentEl.style.display = 'none';\n this._contentEl.insertAdjacentElement('afterend', input);\n // `insertAdjacentElement` upgrades + connects synchronously, so the editor is\n // ready — seed it with the current text (autosizes to fit).\n input.setValue(this._content);\n\n // Enter (via the primitive's standalone submit event) saves; Esc cancels.\n input.addEventListener('aparte-composer-submit', () => this._exitEditMode(true));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape' && !e.isComposing) {\n e.preventDefault();\n this._exitEditMode(false);\n }\n });\n\n // Swap the action bar over to ✓ / ✗.\n this._updateActionBar();\n\n input.focusEnd();\n }\n\n /**\n * Leave edit mode. When `save` is true and the text actually changed, emits\n * `aparte-edit`; otherwise restores the original message untouched. Always\n * restores the normal action bar and removes the inline editor.\n */\n private _exitEditMode(save: boolean): void {\n if (!this._editing) return;\n const newContent = this._editInput?.getValue() ?? '';\n const original = this._content;\n\n this._editInput?.remove();\n this._editInput = null;\n if (this._contentEl) this._contentEl.style.display = '';\n this.querySelector('.aparte-message')?.removeAttribute('data-editing');\n this._editing = false;\n this._updateActionBar();\n\n if (save && newContent && newContent !== original) {\n const messageId = this.getAttribute('message-id');\n if (messageId) {\n const detail: AparteEditEventDetail = {\n messageId,\n content: newContent,\n targetId: this._resolveTargetId(),\n };\n this.dispatchEvent(new CustomEvent<AparteEditEventDetail>('aparte-edit', {\n bubbles: true, composed: true,\n detail,\n }));\n }\n }\n }\n\n private _resolveTargetId(): string | undefined {\n // Walk up to the chat host element with an id. Angular's wrapper root IS the\n // `<aparte-chat>` element (its component selector); the plain-root wrappers\n // (React/Vue/Svelte) render a `<div class=\"aparte-chat-container\" data-aparte-chat\n // id=\"…\">` instead — so match `[data-aparte-chat]` too. Without this, retry/edit\n // resolved to `undefined` outside Angular and AparteClient's fallback hit the\n // bare `<aparte-chat-viewport>` (a different message store) → retry regenerated\n // into the void.\n let el: HTMLElement | null = this.parentElement;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Utilities\n // ─────────────────────────────────────────────────────────────────────────\n\n\n private _updateStreaming(streaming: boolean): void {\n const wasStreaming = this._streaming;\n this._streaming = streaming;\n const message = this.querySelector('.aparte-message');\n if (message) {\n message.setAttribute('data-streaming', String(streaming));\n if (streaming) {\n // Signal \"in progress\" to assistive tech; clearing it on completion\n // cues screen readers (via the viewport's aria-live region) to read\n // the finished response.\n message.setAttribute('aria-busy', 'true');\n message.classList.add('aparte-message-streaming');\n } else {\n message.removeAttribute('aria-busy');\n message.classList.remove('aparte-message-streaming');\n }\n }\n this._updateWaiting();\n // Streaming just finished: highlight the final content once (skipped during\n // streaming to avoid re-highlighting on every token).\n if (wasStreaming && !streaming) this._highlightContentCode();\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-bubble')) {\n customElements.define('aparte-chat-bubble', AparteChatBubble);\n}\n","import { AparteConfig } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig } from '../../config/config-context.js';\n\n/**\n * A standalone status line — a light-DOM indicator the APP owns. Nothing in core turns\n * it on: the framework host only ever flips it back OFF (on the first streamed token),\n * and the four wrappers render one inside the viewport driven by their own `isTyping`\n * prop.\n *\n * It dispatches nothing, deliberately: it reports, it does not ask.\n *\n * Use it for a state only the app knows about — \"Searching the docs…\", \"Uploading…\",\n * a queue position. It is NOT the indicator for the gap between a send and the first\n * token: that one is built into the bubble (`.aparte-waiting`, shown while `streaming`\n * is set on a non-user bubble that has nothing to display yet). Turning this element on\n * for that gap is how a page ends up showing two indicators for one wait.\n *\n * It does not project children: `_render()` writes the subtree — a\n * `.aparte-status-container` row holding an empty avatar div and an `.aparte-body` that\n * wraps `.aparte-status-content` — so markup authored between the tags does not\n * survive. That avatar div never gets contents here, and `.aparte-avatar:empty` is\n * `display: none`, so it is not a spacer: the line sits flush with the row padding\n * rather than indented under an assistant bubble's text column.\n *\n * The seam for custom contents is `setStatusRenderer`, scoped or global: the container\n * keeps owning show/hide (`data-visible`), the accessible name (`aria-label`) and its\n * `.aparte-message` row metrics whatever the renderer returns, but the pulsing dot and\n * the text node belong to the default path only.\n *\n * A `text` attribute renders the visible label and feeds the accessible name; with no\n * `text` the line is dots-only and the name falls back to the literal `Typing` — this\n * element does not read the locale. Hiding happens twice over: the host element is\n * `display: none` without `[visible]`, and `data-visible` drives the fade/translate on\n * the container.\n *\n * The config is resolved live rather than cached, so a `setStatusRenderer` call that\n * lands after this element has already upgraded still reaches it: the element\n * re-renders on `aparte-config-change`, filtered to its own config.\n *\n * The two borrowed row variables below have one scope caveat: inside a viewport\n * narrower than 520px core REASSIGNS `--aparte-message-padding` on `.aparte-message`\n * itself, so a declaration on this host element loses to it there.\n *\n * @element aparte-chat-status\n * @attr {boolean} visible - Shows or hides the indicator.\n * @attr {string} text - The line to show. Absent, the line is dots-only and the\n * accessible name falls back to the literal `Typing` (not the locale's string).\n *\n * @cssprop [--aparte-status-color=var(--aparte-text-muted)] - Colour of the label text and of the pulsing dot in the default line.\n * @cssprop [--aparte-status-font-size=13px] - Size of the visible label (italic by default) in the default line.\n * @cssprop [--aparte-status-dot-size=6px] - Diameter of the single pulsing dot in the default line.\n * @cssprop [--aparte-message-padding=16px 12px] - Padding of the row, read because the container also carries `.aparte-message` — the status line borrows a bubble's row metrics so it lines up with the transcript.\n * @cssprop [--aparte-message-max-width=800px] - Width cap of that same row.\n *\n * @example\n * <!-- The app owns this indicator: core turns it on for nobody, which is also why it\n * is the wrong tool for the wait before the first token — the bubble's built-in\n * waiting state already covers that one. -->\n * <aparte-chat-status visible text=\"Searching the docs…\"></aparte-chat-status>\n */\nexport class AparteChatStatus extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['visible', 'text'];\n }\n\n /**\n * Resolved LIVE, not cached. Caching it at connect made this element\n * permanently deaf to its own instance: `_onConfigChange` filters on\n * `detail.config !== this._cfg`, so once `_cfg` had latched the global config no\n * change for the real instance ever matched, and the filter meant to isolate\n * chats became the thing that silenced one.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n\n constructor() {\n super();\n }\n\n connectedCallback(): void {\n // Cache the resolved config (instance boundary or global fallback), like the\n // other Aparte elements — so a scoped setStatusRenderer applies here too.\n this._render();\n // Re-render on a live config change (e.g. setStatusRenderer called after this\n // element already upgraded — it self-registers on import, so a persistent\n // <aparte-chat-status> in the page mounts before any config runs). Mirrors the\n // bubble's config-change subscription.\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n private _onConfigChange = (e: Event): void => {\n // Only react to OUR config (an instance-scoped change elsewhere must not touch\n // us). A bare notify (no detail.config) always re-renders.\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n // Clear so _render's re-entrancy guard doesn't bail; visible/text are read\n // from attributes, so the shown state is preserved across the re-render.\n this.innerHTML = '';\n this._render();\n };\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'visible':\n this._updateVisibility(newValue !== null);\n break;\n case 'text':\n this._updateText(newValue);\n break;\n }\n }\n\n /**\n * Show the typing indicator\n */\n show(): void {\n this.setAttribute('visible', '');\n }\n\n /**\n * Hide the typing indicator\n */\n hide(): void {\n this.removeAttribute('visible');\n }\n\n /**\n * Toggle visibility\n */\n toggle(): void {\n if (this.hasAttribute('visible')) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n /**\n * Check if visible\n */\n isVisible(): boolean {\n return this.hasAttribute('visible');\n }\n\n private _render(): void {\n const text = this.getAttribute('text') || 'Typing';\n const visible = this.hasAttribute('visible');\n\n // Re-entrancy check\n if (this.querySelector('.aparte-status-container')) return;\n\n // Custom typing indicator (charter §6 render hook): replace the inner markup\n // while the container keeps owning show/hide (data-visible) + accessible name.\n const custom = this._cfg?.getStatusRenderer?.();\n if (custom) {\n this.innerHTML =\n `<div class=\"aparte-message aparte-status-container\" data-visible=\"${visible}\" role=\"status\" aria-live=\"polite\"></div>`;\n const container = this.querySelector('.aparte-status-container') as HTMLElement;\n // `text` set via setAttribute, never interpolated — a `\"` would break out.\n container.setAttribute('aria-label', text);\n const result = runWithConfig(this._cfg, () => custom(text));\n if (result instanceof HTMLElement) container.appendChild(result);\n else container.innerHTML = result;\n return;\n }\n\n this.innerHTML = `\n <div\n class=\"aparte-message aparte-status-container\"\n data-visible=\"${visible}\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <div class=\"aparte-avatar\" data-role=\"assistant\" style=\"visibility: hidden\"></div>\n <div class=\"aparte-body\">\n <div class=\"aparte-status-content\">\n <div class=\"aparte-dots\" aria-hidden=\"true\">\n <span class=\"aparte-dot\"></span>\n </div>\n <span class=\"aparte-status-text\"></span>\n </div>\n </div>\n </div>\n `;\n // Set the (public, attacker-controllable) `text` via setAttribute/textContent\n // rather than interpolating it into the innerHTML template — a `\"` in the\n // attribute would otherwise break out and inject arbitrary attributes.\n this.querySelector('.aparte-status-container')?.setAttribute('aria-label', text);\n // Visible text only when explicitly requested — the default stays dots-only\n // (the aria-label above always carries the accessible name).\n if (this.hasAttribute('text')) {\n const textEl = this.querySelector('.aparte-status-text');\n if (textEl) textEl.textContent = text;\n }\n }\n\n private _updateVisibility(visible: boolean): void {\n const container = this.querySelector('.aparte-status-container');\n if (container) {\n container.setAttribute('data-visible', String(visible));\n }\n }\n\n private _updateText(text: string | null): void {\n const textEl = this.querySelector('.aparte-status-text');\n const container = this.querySelector('.aparte-status-container');\n if (!container) return; // not rendered yet — _render() reads the attribute\n // Removing the attribute restores the dots-only default (empty visible\n // text); the aria-label always keeps an accessible name.\n if (textEl) textEl.textContent = text ?? '';\n container.setAttribute('aria-label', text || 'Typing');\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-status')) {\n customElements.define('aparte-chat-status', AparteChatStatus);\n}\n","import type {\n AparteMessage,\n AparteViewportConfig,\n AparteSegment,\n AparteSegmentUpdateEventDetail,\n ApartePathChangedEventDetail,\n AparteSiblingInfo,\n AparteUsage,\n} from '../../types/index.js';\nimport { resolveConfig } from '../../config/index.js';\nimport { AparteMessageRepository } from '../../runtime/message-repository.js';\nimport type { ExportedMessageRepository } from '../../runtime/message-repository.js';\nimport { populateBubbleFromMessage, type SyncableBubble } from '../bubble/bubble-sync.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { isAwaitingReply } from '../../utils/is-awaiting-reply.js';\nimport { revokeAttachmentUrls } from '../../utils/files-to-attachments.js';\nimport { uuid } from '../../utils/uuid.js';\nimport {\n stampSegmentOnInsert,\n adoptMessageSegments,\n stampSegmentOnUpdate,\n mergeSegmentUpdate,\n renumberSegments,\n openSegmentIds,\n stampSegmentActivity,\n isTerminalStatus,\n} from '../../utils/segments.js';\n\n/**\n * The transcript surface: a light-DOM container with sticky scrolling, token\n * streaming and segment-aware rendering.\n *\n * Features:\n * - Smart Scroll: Sticks to bottom when user is at bottom, stops on manual scroll up\n * - appendToken(): For simple content streaming\n * - appendToSegment(): For segment-aware streaming (thinking, code, etc.)\n *\n * Two DOM modes. By default the element builds its own scroll surface\n * (`.aparte-viewport-container`) around a `.aparte-messages-wrapper`, and creates the\n * `<aparte-chat-bubble>` elements itself (the last `max-rendered-bubbles` of the active\n * path). With `framework-managed` set it builds neither wrapper: the HOST is the scroll\n * surface, the framework owns the bubble elements, and the bottom spacer becomes additive\n * host padding instead of an element — a relocated or removed child is what desynchronises\n * a framework's view tree from the live DOM, so this mode touches neither. The one child\n * it appends in both modes is the scroll-to-bottom button, kept trailing.\n *\n * Children you write inside the element are just children: there is no shadow root and no\n * slot to target. In the default mode they are MOVED into the internal\n * `.aparte-messages-wrapper` at first render, ahead of the bottom spacer, so pre-rendered\n * `<aparte-chat-bubble>` elements land in the transcript flow. A custom element of your own\n * is relocated the same way, and if it carries `data-aparte-bubble` plus a matching\n * `message-id` it also receives the live token and segment pushes, not just a restyle.\n * Do not expect such a child to outlive the transcript, though: anything that re-renders the\n * active path (`addBranch`, `addSiblingOf`, `navigateBranch`, `importTree`) empties the\n * wrapper and rebuilds it from the repository, so only what the repository holds comes back —\n * and `clearAll()` removes `<aparte-chat-bubble>` nodes only, so a `[data-aparte-bubble]`\n * element of your own is left behind with nothing left to render. With `framework-managed`\n * set children are not relocated: they stay direct children of the host, which is itself the\n * scroll surface.\n *\n * Messages are held as a TREE (siblings, branches, an active path), which is what lets\n * a retry fork and a bubble's sibling picker navigate with no host object involved.\n *\n * What it is NOT is storage. `max-rendered-bubbles` is a DOM ceiling and never evicts\n * from the repository — the full tree and its snapshot stay complete, `exportTree()` /\n * `importTree()` hand that snapshot to whoever owns persistence, and real history\n * retention is configured on the conversation manager instead. It is not a chat either:\n * a bare viewport IS a valid `AparteClient` target, but the composer, the transport and\n * the shell layout are other elements.\n *\n * @element aparte-chat-viewport\n *\n * @attr {boolean} framework-managed - The wrapper's explicit hands-off signal: set it and this\n * element builds no wrapper of its own and relocates none of the nodes the FRAMEWORK renders\n * into it, because the framework owns them. Not \"none of its children\": core's own\n * scroll-to-bottom button is re-appended whenever it stops being last, and that path runs in\n * this mode only. All four wrappers set it.\n * @attr {number} scroll-threshold - How close to the bottom still counts as \"at the bottom\".\n * @attr {number} max-rendered-bubbles - Caps how many bubbles stay in the DOM; older ones are released.\n *\n * @fires {CustomEvent<AparteSegmentUpdateEventDetail>} aparte-segment-update - A segment grew or settled during a stream.\n * @fires aparte-reset-done - `clearAll()` finished emptying the transcript. No detail.\n * @fires {CustomEvent<ApartePathChangedEventDetail>} aparte-path-changed - The active branch path changed, after a retry fork or a navigation.\n *\n * @cssprop [--aparte-viewport-padding=16px] - Padding around the transcript — on\n * `.aparte-messages-wrapper`, or on the host itself in framework-managed mode, where the\n * auto-scroll spacer is added on top of it. A container narrower than 520px tightens it in\n * the default mode only: that rule reassigns the variable on `.aparte-messages-wrapper`,\n * which framework-managed mode never builds.\n * @cssprop [--aparte-message-gap=12px] - Gap between consecutive bubbles in the transcript\n * column (both DOM modes). Shared: it is also the avatar-to-content gap inside a bubble.\n * @cssprop [--aparte-scrollbar-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-scroll-btn-size=36px] - Diameter of the scroll-to-bottom button. A\n * coarse pointer raises it to `--aparte-touch-target-size`.\n * @cssprop [--aparte-scroll-btn-shadow=0 2px 8px rgba(0, 0, 0, 0.12)] - Its shadow; the dark\n * theme sets a heavier one.\n *\n * @example\n * <!-- On its own, outside `<aparte-chat>`. Give it a height: it fills what it is given\n * and owns the scrolling inside that box, so a viewport in an auto-height parent\n * grows forever instead of scrolling. Messages are pushed in — it fetches nothing. -->\n * <aparte-chat-viewport style=\"height: 320px\"></aparte-chat-viewport>\n *\n * <script>\n * const viewport = document.querySelector('aparte-chat-viewport');\n * viewport.appendMessage({ id: 'u1', role: 'user', content: 'What is a transport?', timestamp: Date.now() });\n * viewport.appendMessage({\n * id: 'a1',\n * role: 'assistant',\n * content: 'The object that talks to the model. Swap it and the UI does not change.',\n * timestamp: Date.now(),\n * });\n * </script>\n *\n * @example\n * // Three calls are a whole streamed turn.\n * const viewport = document.querySelector('aparte-chat-viewport')!;\n *\n * viewport.appendMessage({ id: 'a1', role: 'assistant', content: '', timestamp: Date.now() });\n * for await (const chunk of tokens) viewport.appendToken('a1', chunk);\n * viewport.completeMessage('a1'); // stops the streaming caret\n */\nexport class AparteChatViewport extends HTMLElement {\n // The scroll surface: an internal `.aparte-viewport-container` div (core mode)\n // or the host element itself (framework-managed mode). HTMLElement covers both.\n private _container: HTMLElement | null = null;\n private _scrollBtn: HTMLButtonElement | null = null;\n private _bottomSpacer: HTMLDivElement | null = null;\n /**\n * In framework-managed mode there is no spacer ELEMENT (an extra child would\n * collide with the framework's own DOM reconciliation). The spacer is an\n * additive `padding-bottom` on the host, tracked here so `_recalculateSpacer`\n * can read the current value without measuring an element.\n */\n private _fwSpacerHeight = 0;\n private _spacerRafId: number | null = null;\n private _spacerFrozenUntil: number = 0;\n private _layoutTransitionMs: number = 0;\n private _repo = new AparteMessageRepository();\n private _isAutoScrollEnabled: boolean = true;\n /** The last scroll position we saw, so growth can be told from a gesture. */\n private _lastScrollTop = 0;\n /** The scroll height at the last scroll event: how much it moved since bounds what churn can do. */\n private _lastScrollHeight = 0;\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 /** 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 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'];\n }\n\n constructor() {\n super();\n this._handleScroll = this._handleScroll.bind(this);\n }\n\n connectedCallback(): void {\n // Framework wrappers set `framework-managed` DECLARATIVELY so the flag is\n // known BEFORE _render() builds the DOM. Otherwise _render()'s child\n // relocation runs at connect — before the host's setFrameworkManagedDOM()\n // call — moving the framework's bubbles into an internal wrapper and\n // breaking its reconciliation (insertBefore NotFoundError on the next\n // append). See _setupFrameworkDOM().\n if (this.hasAttribute('framework-managed')) this._frameworkManagedDOM = true;\n this._render();\n this._setupEventListeners();\n this._setupObservers();\n /**\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` was applied once at\n * render — so a chat already on screen never flipped to RTL until a reload.\n * Only OUR config: an instance-scoped change elsewhere must not touch us.\n */\n private _onConfigChange = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== resolveConfig(this)) return;\n this._applyDirection();\n // 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 — was unreadable\n * past the first screen for a Safari keyboard user, with no error and nothing on\n * screen to say so.\n *\n * `tabindex=\"0\"` on the surface itself, in BOTH DOM modes. The framework mode\n * appeared to work already, and only by accident: the scroll button is inside the\n * host and stays tabbable while it is visually hidden, so Tab happened to land\n * somewhere that scrolled. A tab stop that exists because a hidden button happens\n * to sit there is not an affordance, it is a coincidence.\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 }\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 /** The id of the message currently streaming, if any. */\n private _streamingMessageId(): string | null {\n for (const message of this._repo.getMessages()) {\n if ((message as { isStreaming?: boolean }).isStreaming) return message.id;\n }\n return null;\n }\n\n /**\n * While a reply streams, the transcript is read-only except for Stop.\n *\n * The streaming message's own footer was already hidden; every OTHER message kept\n * its branch picker and its retry/edit buttons live. Swapping a branch mid-stream\n * re-rendered the active path under the reply being written, and a retry cut that\n * reply off to start another — both seen on the landing page. 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 driven by `AparteClient` no longer drops text\n * (the args used to bind one position short, creating a phantom message).\n */\n addSegment(segment: AparteSegment): void;\n addSegment(messageId: string, segment: AparteSegment): void;\n addSegment(messageIdOrSegment: string | AparteSegment, maybeSegment?: AparteSegment): void {\n const messageId = typeof messageIdOrSegment === 'string' ? messageIdOrSegment : this._activeMessageId();\n const segment = typeof messageIdOrSegment === 'string' ? maybeSegment : messageIdOrSegment;\n if (!messageId || !segment) return;\n\n const message = this._getOrCreateMessage(messageId);\n if (!message.segments) {\n message.segments = [];\n }\n // Identity and start time land BEFORE anyone sees the object: the repo and\n // the bubble are handed the same segment, so a later stamp would leave one\n // of the two holding an unstamped copy. This is one of exactly two places\n // that writes those fields (`aparte-chat-host` is the other) — see\n // `utils/segments.ts` for why it is not the parser.\n const stamped = stampSegmentOnInsert(\n message.segments, segment, messageId,\n // THIS chat's defaults, not the page's: two chats on one page can be\n // configured differently, and the config seam is per instance.\n resolveConfig(this).getSegmentDefaults(segment.type),\n );\n message.segments.push(stamped);\n\n // Notify bubble to render the new segment\n this._notifyBubble(messageId, 'addSegment', stamped);\n this._autoScroll();\n }\n\n /**\n * Update a segment. `updateSegment(segmentId, updates)` (1-arg client\n * convention → current message) or `updateSegment(messageId, segmentId,\n * updates)` (explicit). Disambiguated by arity: the 3rd arg is absent and\n * the 2nd is the `updates` object in the 1-arg form.\n */\n updateSegment(segmentId: string, updates: Partial<AparteSegment>): void;\n updateSegment(messageId: string, segmentId: string, updates: Partial<AparteSegment>): void;\n updateSegment(a: string, b: string | Partial<AparteSegment>, c?: Partial<AparteSegment>): void {\n const clientForm = c === undefined && typeof b === 'object';\n const messageId = clientForm ? this._activeMessageId() : a;\n const segmentId = clientForm ? a : (b as string);\n const updates = clientForm ? (b as Partial<AparteSegment>) : (c as Partial<AparteSegment>);\n if (!messageId) return;\n\n const message = this._repo.getMessageById(messageId);\n if (!message?.segments) return;\n\n const segmentIndex = message.segments.findIndex(s => s.id === segmentId);\n if (segmentIndex !== -1) {\n const current = message.segments[segmentIndex]!;\n // An update that settles the segment carries its `endedAt`. Stamped\n // here rather than at each call site, so `completeSegment`, a tool\n // resolution and an app's own `updateSegment` all measure alike.\n const stamped = stampSegmentOnUpdate(current, updates);\n message.segments[segmentIndex] = mergeSegmentUpdate(current, stamped);\n\n this._notifyBubble(messageId, 'updateSegment', { segmentId, updates: stamped });\n }\n }\n\n /**\n * Remove a segment. `removeSegment(segmentId)` (1-arg client convention →\n * current message) or `removeSegment(messageId, segmentId)` (explicit).\n */\n removeSegment(segmentId: string): void;\n removeSegment(messageId: string, segmentId: string): void;\n removeSegment(a: string, b?: string): void {\n const clientForm = b === undefined;\n const messageId = clientForm ? this._activeMessageId() : a;\n const segmentId = clientForm ? a : b;\n if (!messageId || !segmentId) return;\n\n const message = this._repo.getMessageById(messageId);\n if (message?.segments) {\n const idx = message.segments.findIndex(s => s.id === segmentId);\n if (idx !== -1) {\n message.segments.splice(idx, 1);\n // `index` is a position, so a removal has to close the gap it left.\n renumberSegments(message.segments);\n }\n }\n this._notifyBubble(messageId, 'removeSegment', segmentId);\n }\n\n /**\n * Start a new streaming segment (e.g., thinking or code block)\n * Creates the segment and marks it as streaming\n */\n startSegment(messageId: string, segment: AparteSegment): void {\n const streamingSegment = { ...segment, isStreaming: true };\n this.addSegment(messageId, streamingSegment);\n }\n\n /**\n * Complete a streaming segment\n */\n completeSegment(messageId: string, segmentId: string): void {\n this.updateSegment(messageId, segmentId, { isStreaming: false });\n }\n\n /**\n * Persist token usage on a message and propagate to the live bubble, which is\n * what allows the info (\"i\") action to render — provided the app declared it\n * with `aparteGlobalConfig.setBubbleActions({ info: true })`; it is off by default,\n * since the popover it opens belongs to the app.\n */\n setUsage(messageId: string, usage: AparteUsage): void {\n const message = this._repo.getMessageById(messageId);\n if (message) message.usage = usage;\n this._notifyBubble(messageId, 'setUsage', usage);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Message Management\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Mark a message as finished streaming\n */\n completeMessage(messageId: string): void {\n const message = this._repo.getMessageById(messageId);\n if (message) {\n message.isStreaming = false;\n message.status = 'completed';\n\n // The message's end IS its segments' end: nothing in the stream says a\n // thinking block is over. Routed through `updateSegment` rather than\n // written onto the objects, so the bubble is told as well — a silent\n // mutation stamped the model and left the renderer thinking it was still\n // streaming, which is exactly what a browser run showed.\n this._settleSegments(messageId, message);\n\n this._notifyBubble(messageId, 'complete', { status: 'completed' });\n this._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 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 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 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 this._recalculateSpacer();\n // User sending always anchors to bottom regardless of scroll position.\n if (message.role === 'user') {\n this._isAutoScrollEnabled = true;\n // Smooth scroll for user-initiated sends. Streaming auto-scroll stays\n // instant (via _autoScroll) so it can keep up with rapid token bursts.\n requestAnimationFrame(() => { 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 this._repo.import({\n ...tree,\n messages: tree.messages.map((entry) => ({\n ...entry,\n message: adoptMessageSegments(entry.message),\n })),\n });\n this._reRenderActivePath();\n }\n\n /**\n * Clear all messages and remove all bubble elements from the DOM.\n * Also dispatches a aparte-reset-done event.\n *\n * In framework-managed mode the DOM is owned by the host framework\n * (Angular @for, React, etc.) and we must not clear `innerHTML` — doing\n * so desynchronises the framework's view tree from the live DOM and the\n * next change-detection pass throws `NotFoundError` on insertBefore.\n */\n clearAll(options?: { revokeAttachments?: boolean }): void {\n /*\n * Release the attachments' object URLs before dropping the messages: after\n * `_repo.clear()` there is no way left to reach them, and nothing else\n * revoked them — so every `File` a session had sent stayed reachable for\n * the life of the page.\n *\n * UNLESS the caller is about to put the same messages back. Two callers do:\n * `setMessages` and `importTree`, and `ConversationController._load` runs\n * BOTH in sequence over one conversation. `export()` stores live `node.current`\n * references, so `conv.messages` and `conv.tree` share the very same\n * attachment objects — meaning the second clear revoked the object URLs of\n * the conversation being opened. Every image and file chip was dead on load,\n * and re-opening revoked twice.\n *\n * A reset (`aparte-reset`, the public `clearAll()`) still revokes: there the\n * messages really are gone.\n */\n if (options?.revokeAttachments !== false) {\n for (const message of this._repo.getMessages()) {\n revokeAttachmentUrls(message.attachments);\n }\n }\n this._repo.clear();\n // 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\n */\n setAutoScroll(enabled: boolean): void {\n this._isAutoScrollEnabled = enabled;\n }\n\n /**\n * Signal that a framework (e.g. Angular) manages the bubble DOM.\n * When true, branch navigation dispatches `aparte-path-changed` without\n * clearing/rebuilding the messages wrapper — the framework re-renders instead.\n */\n setFrameworkManagedDOM(managed: boolean): void {\n this._frameworkManagedDOM = managed;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private _getOrCreateMessage(messageId: string): AparteMessage {\n const existing = this._repo.getMessageById(messageId);\n if (existing) return existing;\n\n const message: AparteMessage = {\n id: messageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now(),\n isStreaming: true,\n status: 'streaming'\n };\n this._repo.addOrUpdateMessage(this._repo.headId, message);\n return message;\n }\n\n private _notifyBubble(messageId: string, action: string, payload?: unknown, segmentId?: string): void {\n // Find the bubble element — the native `<aparte-chat-bubble>` OR a custom\n // element opting into live streaming via `data-aparte-bubble` (so a raw-core\n // consumer can replace the bubble tag and still receive token/segment\n // pushes, not just a CSS restyle).\n const bubble = this.querySelector(\n `aparte-chat-bubble[message-id=\"${cssEscape(messageId)}\"], [data-aparte-bubble][message-id=\"${cssEscape(messageId)}\"]`,\n ) as HTMLElement & {\n appendToken?: (chunk: string) => void;\n appendToSegment?: (segmentId: string, chunk: string) => void;\n addSegment?: (segment: AparteSegment) => void;\n updateSegment?: (segmentId: string, updates: Partial<AparteSegment>) => void;\n removeSegment?: (segmentId: string) => void;\n setUsage?: (usage: AparteUsage) => void;\n updateMessage?: (updates: Partial<AparteMessage>) => void;\n };\n\n if (!bubble) return;\n\n switch (action) {\n case 'appendToken':\n bubble.appendToken?.(payload as string);\n break;\n case 'appendToSegment':\n bubble.appendToSegment?.(segmentId!, payload as string);\n break;\n case 'addSegment':\n bubble.addSegment?.(payload as AparteSegment);\n break;\n case 'updateSegment': {\n const { segmentId: sid, updates: segUpdates } = payload as { segmentId: string; updates: Partial<AparteSegment> };\n bubble.updateSegment?.(sid, segUpdates);\n break;\n }\n case 'removeSegment':\n bubble.removeSegment?.(payload as string);\n break;\n case 'setUsage':\n bubble.setUsage?.(payload as AparteUsage);\n break;\n case 'update': {\n // Atomic update: forward it when it carries anything the bubble\n // renders. `content` and `attachments` used to be filtered out\n // here, so an edit (which sends `{ content }`) updated the repo —\n // and therefore the history sent to the model — while the bubble\n // kept displaying the old text.\n const updates = payload as Record<string, unknown>;\n const renderable = ['status', 'segments', 'content', 'attachments', 'usage'];\n if (renderable.some((key) => key in updates)) {\n bubble.updateMessage?.(payload as Partial<AparteMessage>);\n }\n break;\n }\n case 'complete':\n bubble.updateMessage?.(payload as Partial<AparteMessage>);\n break;\n }\n }\n\n /**\n * Re-render the active path: clears the messages wrapper and rebuilds bubbles\n * for every message on the current active branch path (root → head).\n * Calls `setSiblings(count, index)` on each bubble that has siblings, and\n * dispatches `aparte-path-changed` so Angular wrapper can sync its signal.\n *\n * When `_frameworkManagedDOM` is true (set via setFrameworkManagedDOM), the DOM\n * manipulation is skipped — only `aparte-path-changed` is dispatched so the\n * framework can re-render from updated signal state.\n */\n private _reRenderActivePath(): void {\n const activeMessages = this._repo.getMessages();\n\n // Compute sibling metadata once and reuse — keeps the event payload\n // identical between framework-managed and default DOM modes.\n const siblingsInfo: AparteSiblingInfo[] = activeMessages.map(m => {\n const sibs = this._repo.getBranches(m.id);\n return { id: m.id, count: sibs.length, index: sibs.indexOf(m.id) };\n });\n\n if (this._frameworkManagedDOM) {\n this._dispatchPathChanged(activeMessages, siblingsInfo);\n return;\n }\n\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (!wrapper) return;\n wrapper.innerHTML = '';\n\n // Only materialise the last N messages of the active path (DOM render cap).\n // The repository keeps the full path; this is a perf ceiling, not eviction.\n const startIdx = Math.max(0, activeMessages.length - this._maxRenderedBubbles);\n for (let i = startIdx; i < activeMessages.length; i++) {\n const message = activeMessages[i]!;\n const sibInfo = siblingsInfo[i];\n\n const bubble = document.createElement('aparte-chat-bubble');\n bubble.setAttribute('message-id', message.id);\n bubble.setAttribute('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 if (this._smoothScrollOnce) {\n this._smoothScrollOnce = false;\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) this._smoothScrollToBottom(); });\n } else {\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) this._scrollToBottom(); });\n }\n }\n this._pruneRenderedBubbles();\n }\n\n /**\n * Request that the next auto-scroll triggered by a DOM mutation uses\n * smooth behaviour instead of instant. Call this just before adding a\n * user message bubble so the viewport animates down rather than jumping.\n * Resets automatically after the first auto-scroll fires.\n */\n requestSmoothScroll(): void {\n this._smoothScrollOnce = true;\n }\n\n private _render(): void {\n // Framework-managed: the framework owns the bubble children directly.\n // Do NOT build the internal container/wrapper or relocate children.\n if (this._frameworkManagedDOM) {\n this._setupFrameworkDOM();\n return;\n }\n // Light DOM rendering\n // Preserving existing children in render allows framework composition\n if (!this.querySelector('.aparte-viewport-container')) {\n const container = document.createElement('div');\n container.className = 'aparte-viewport-container';\n\n // Set direction based on current locale\n const locale = resolveConfig(this).getLocale();\n if (locale.direction) {\n container.setAttribute('dir', locale.direction);\n }\n\n container.setAttribute('role', 'log');\n container.setAttribute('aria-live', 'polite');\n container.setAttribute('aria-atomic', 'false');\n container.setAttribute('aria-relevant', 'additions');\n 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\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 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 };\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 // 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._scrollBtn?.addEventListener('click', this._onScrollBtnClick);\n this.addEventListener('aparte-branch-navigate', this._onBranchNavigate);\n }\n\n private _setupObservers(): void {\n this._resizeObserver = new ResizeObserver(() => {\n if (this._isAutoScrollEnabled) {\n this._scrollToBottom();\n }\n this._recalculateSpacer();\n /*\n * And the button, for the same reason the spacer is here.\n *\n * \"Is anything below the fold\" is a pure function of the geometry this\n * observer exists to watch, and only the MUTATION path re-derived it\n * (`_scheduleSpacerUpdate`, whose comment already says the fold may have\n * moved). A resize that changes nothing in the DOM therefore left the\n * button showing whatever the last mutation happened to measure.\n *\n * That gap has a name in this file already: a branch swap rebuilds the\n * transcript and React's height FLICKERS through it — 1730 → 1934 → 1730,\n * measured, see `navigateBranch`. The settle from 1934 back to 1730 is a\n * resize, not a mutation, so a button evaluated at 1934 stayed wrong. CI\n * caught it on react-webkit holding \"visible\" across 43 polls, five seconds\n * after a swap that ended at the bottom.\n *\n * Cheap enough to run unconditionally: one geometry read and a\n * `classList.toggle`.\n */\n this._updateScrollButton();\n });\n\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n\n if (this._container) {\n // Fires on window/viewport resize and when the composer grows.\n // NOTE: we intentionally do NOT observe .aparte-messages-wrapper here.\n // The wrapper contains the spacer div — observing it would create a\n // feedback loop: spacer changes → wrapper resizes → ResizeObserver →\n // _recalculateSpacer → spacer changes → … → height grows unbounded.\n // Streaming content growth is handled by direct _scheduleSpacerUpdate()\n // calls from appendToken(). New bubbles are handled by MutationObserver.\n // Framework-managed: _container IS the host, whose `padding-bottom`\n // carries the spacer — observe the BORDER box (fixed host size) so a\n // spacer/padding change does NOT re-trigger _recalculateSpacer and\n // loop. Core mode observes the container (no dynamic padding).\n if (this._frameworkManagedDOM) {\n this._resizeObserver.observe(this._container, { box: 'border-box' });\n } else {\n this._resizeObserver.observe(this._container);\n }\n }\n\n this._mutationObserver = new MutationObserver(() => {\n // Keep the sticky scroll button trailing after framework appends.\n if (this._frameworkManagedDOM) this._keepScrollButtonLast();\n // The gate is tested 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 });\n\n // Framework-managed: bubbles are direct children of the host (no wrapper).\n const observeTarget = this._frameworkManagedDOM ? this : wrapper;\n if (observeTarget) {\n this._mutationObserver.observe(observeTarget, {\n childList: true,\n subtree: true\n });\n }\n }\n\n /** Is the scroll surface within `_scrollThreshold` of its bottom, right now? */\n private _isAtBottom(): boolean {\n if (!this._container) return true;\n const { scrollTop, scrollHeight, clientHeight } = this._container;\n return scrollHeight - scrollTop - clientHeight <= this._scrollThreshold;\n }\n\n private _handleScroll(): void {\n if (!this._container) return;\n /*\n * A scroll is the reader's intent — but only when the reader caused it.\n *\n * This used to assign `_isAutoScrollEnabled = _isAtBottom()` on every event, and\n * `_isAtBottom()` answers \"no\" for two completely different reasons: the reader\n * moved up, or the CONTENT GREW UNDER THEM. The second one disarmed auto-follow\n * exactly when it was most needed. A branch swap rebuilds the transcript, its\n * height settles in stages, one of those stages fires a scroll event while the\n * distance is briefly large — and the follow that was supposed to put the reader\n * back at the bottom had already switched itself off. CI caught it on\n * react-webkit parked 114px up, five seconds after a swap that started at the\n * bottom (scrollTop 1071, scrollHeight 1718, clientHeight 533).\n *\n * The two cases are told apart BY POSITION, which is what the note this replaces\n * asked for and what an event counter could not do: growth does not move\n * `scrollTop`, and a reader going up does. So a decrease disarms, the bottom\n * re-arms, and everything else leaves the flag exactly as it was.\n *\n * 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 is bounded by the EVIDENCE of churn — how much the\n * scroll height moved since the last scroll event — rather than by a number: a\n * first version capped it at 100px and a branch swap on React refuted that\n * (the rebuild flickers the height by ~200px, measured in `navigateBranch`,\n * and WebKit moves scrollTop by as much); a second version dropped the cap\n * altogether, and during a stream — where every token refreshes\n * `_ownScrollAt`, so the shadow never closes — a reader drag-selecting text\n * upward, whose press lands on the text and not in the gutter, was snapped\n * back to the bottom by the next token. Churn moves scrollTop by at most the\n * height it changed; a reader, a find-in-page jump or a host's `scrollTo` move\n * it with the height standing still.\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 const settlingOurs = drop <= churn + 2\n && now - this._ownScrollAt < this._ownScrollWindowMs\n && now - this._readerInputAt >= this._ownScrollWindowMs;\n const readerWentUp = drop > 1 && !settlingOurs;\n this._lastScrollTop = top;\n this._lastScrollHeight = height;\n\n if (this._isAtBottom()) {\n // `_isAtBottom()` stays deliberately generous (`_scrollThreshold`, 50px): a\n // few pixels of drift must not read as \"the reader walked away\".\n this._isAutoScrollEnabled = true;\n } else if (readerWentUp) {\n this._isAutoScrollEnabled = false;\n } 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 this._ownScrollAt = performance.now();\n this._container.scrollTop = this._container.scrollHeight;\n this._settleAtBottom();\n }\n\n /**\n * Confirm over the next few frames that we actually reached the bottom.\n *\n * One assignment is not enough, and the reason is measured rather than guessed.\n * A timeline of a streamed turn on Safari (framework mode) recorded the content\n * settling in TWO layout passes — 1118 → 1121 → 1152 px. `scrollTop =\n * scrollHeight` ran against the middle one, clamped to that layout's max (603),\n * and nothing ran afterwards: the last 31px never closed. Auto-follow stayed\n * armed the whole time, so the component was not disarmed — it was SATISFIED.\n * `_isAtBottom()` answers \"yes\" for any gap under `_scrollThreshold` (50), which\n * is the right rule for keeping auto-follow armed and the wrong one as a\n * definition of \"anchored\".\n *\n * Ruled out on the way here, so nobody pays for it twice: not a WebKit\n * padding-accounting difference (a probe writing `scrollTop = 1e7` reached\n * exactly `scrollHeight - clientHeight`), not a missing `characterData`\n * mutation, and not a child resize a ResizeObserver could see.\n *\n * A BOUNDED retry, not one corrective frame: a single frame lands on the same\n * stale layout and was measured leaving a wider gap than doing nothing. Bounded\n * so it always terminates; re-reads `_isAutoScrollEnabled` every frame so a\n * reader who scrolls away mid-settle is left alone.\n *\n * Bounded by TIME, and it does not stop at the first closed gap — both of those\n * were the second half of the same bug. It used to be four frames (~64ms on an\n * idle 60Hz machine) and to return permanently the frame the gap first closed.\n * A branch swap at the bottom of a long transcript on react-webkit falsified\n * both at once: the rebuild's scrollable max churned 891 -> 1091 -> 891, the gap\n * WAS closed for a frame against the tall layout, the chain returned, the height\n * fell back with WebKit holding `scrollTop` at 720 — and the transcript stood\n * 171px short with auto-follow still armed. A frame count is a proxy for time\n * that fails exactly on the slow engine, so the budget is real milliseconds now,\n * and a closed gap only ends the chain when the window is over. One chain, not\n * one per call: during a stream every token used to 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 if (max - this._container.scrollTop > 1) {\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._container.scrollTo({ top: this._container.scrollHeight, behavior: 'smooth' });\n } else {\n this._container.scrollTop = this._container.scrollHeight;\n }\n }\n\n private _prefersReducedMotion(): boolean {\n return typeof matchMedia === 'function'\n && matchMedia('(prefers-reduced-motion: reduce)').matches;\n }\n\n /**\n * Show/hide the scroll-to-bottom button from the **current geometry**, not from\n * `_isAutoScrollEnabled`.\n *\n * The two answer different questions: the flag is intent (\"should new content\n * pull the view down\"), the button is a fact (\"is there anything below the\n * fold\"). Mirroring the flag made the button lie whenever the two diverged —\n * `navigateBranch` deliberately disarms auto-follow, so swapping a branch while\n * already at the bottom of a scrollable transcript left the button offering to\n * scroll nowhere (reported from a React consumer). 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 const needed = this._container.clientHeight - contentBelowUserTop;\n // Hard cap: the spacer can never exceed the visible viewport height.\n // This acts as a safety net against stale layout reads (e.g. mid-swap)\n // that could produce an astronomical value and push content off-screen.\n const maxSpacer = this._container.clientHeight;\n this._setSpacerHeight(Math.min(Math.max(0, needed), maxSpacer));\n\n // Re-scroll after the spacer height changes so scrollTop is always\n // consistent with the new scrollHeight. Without this, the MutationObserver\n // schedules _scrollToBottom() one RAF *before* _recalculateSpacer() runs,\n // leaving scrollTop based on the pre-spacer scrollHeight. On the next\n // recalculation (e.g. from syncMessagesWithBubbles or a resize) the formula\n // reads a stale scrollTop and may grow the spacer to the clientHeight cap.\n if (this._isAutoScrollEnabled) {\n this._scrollToBottom();\n }\n }\n\n /**\n * Schedule a spacer recalculation on the next animation frame.\n * Batches multiple rapid calls (e.g. during token streaming) into one.\n *\n * Single-RAF intentional: both the scroll-to-bottom queued by MutationObserver\n * and this spacer recalculation must land in the *same* frame so the browser\n * paints exactly once — with the correct scroll position *and* the correct\n * spacer height. A double-RAF would put the spacer shrink one frame after the\n * scroll, causing a 1-frame layout jump during streaming.\n */\n private _scheduleSpacerUpdate(): void {\n if (this._spacerRafId !== null) return;\n this._spacerRafId = requestAnimationFrame(() => {\n this._spacerRafId = null;\n this._recalculateSpacer();\n // The DOM just changed (new bubble, streamed token, framework re-render):\n // whether anything sits below the fold changed with it.\n this._updateScrollButton();\n });\n }\n\n /**\n * Cap the number of rendered bubbles in the DOM (perf ceiling only).\n *\n * Drops the oldest `<aparte-chat-bubble>` elements beyond `_maxRenderedBubbles`\n * from the DOM. It **never** touches the AparteMessageRepository — the conversation\n * model and its persistence snapshot stay complete (retention/eviction is a\n * consumer/persistence concern, not the viewport's). No-op when a framework\n * owns the DOM.\n */\n private _pruneRenderedBubbles(): void {\n if (this._frameworkManagedDOM) return;\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (!wrapper) return;\n const bubbles = wrapper.querySelectorAll('aparte-chat-bubble');\n const excess = bubbles.length - this._maxRenderedBubbles;\n for (let i = 0; i < excess; i++) {\n bubbles[i]?.remove();\n }\n }\n\n private _cleanup(): void {\n this._container?.removeEventListener('scroll', this._handleScroll);\n for (const type of ['wheel', 'touchmove', 'pointerdown', 'keydown'] as const) {\n this._container?.removeEventListener(type, this._noteReaderInput);\n }\n this._scrollBtn?.removeEventListener('click', this._onScrollBtnClick);\n this.removeEventListener('aparte-branch-navigate', this._onBranchNavigate);\n this._resizeObserver?.disconnect();\n this._mutationObserver?.disconnect();\n this._resizeObserver = null;\n this._mutationObserver = null;\n if (this._spacerRafId !== null) {\n cancelAnimationFrame(this._spacerRafId);\n this._spacerRafId = null;\n }\n // 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';\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. (`'advance'` — the button as a \"next\" through a form — is gone:\n * the chips are the navigation, and a button that changes meaning under the pointer\n * was one more thing to explain.)\n *\n * `'none'` exists because a panel could not previously say it. The composer's panel\n * mode was one fixed policy — hide the input and the attachment picker, keep the\n * strip and the toolbar, and ALWAYS keep the send button — and the approval panel\n * showed what that costs: its options settle on the first click by design, so the\n * button beside them sat permanently disabled, meaning nothing, until the optional\n * instruction field was opened. A control that is never the way forward is not\n * disabled, it is absent (ratified decision #8).\n *\n * Named rather than inlined at each of its six readers: this union is exactly the\n * kind of list this repo has watched drift when every reader kept its own copy.\n */\nexport type AparteComposerPanelMode = '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 * Prose first, on purpose: when `@element` opens a docblock there is no free text\n * left for the analyser to use, and this component's description came out empty in\n * the manifest and blank on the generated reference page.\n *\n * `aparte-abort` and `aparte-message-aborted` have to be declared by hand and always\n * will: they go out through `window.dispatchEvent` (they concern the whole page, not\n * this subtree), and the analyser's fallback only recognises `this.dispatchEvent`.\n *\n * @element aparte-composer\n *\n * @attr {string} placeholder - Fallback placeholder for `<aparte-composer-input>`, which\n * reads it off this element when it carries none of its own. 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, which is why it was missing from the\n * manifest — and so from every typed surface — while all four wrappers wrote it.\n *\n * @fires {CustomEvent<AparteSendEventDetail>} aparte-send - A message was submitted: the text, its attachments and the target.\n * @fires aparte-cancel - The stop button was pressed. No detail; the two window events below carry the target.\n * @fires {CustomEvent<AparteAbortEventDetail>} aparte-abort - Dispatched on `window`: stop the run for this target.\n * @fires {CustomEvent<AparteMessageAbortedEventDetail>} aparte-message-aborted - The run for this target ended early — the user pressed Stop, or `abort()` was called. This element dispatches it on `window`; `AparteClient` also dispatches it on the chat host, so it is listenable on either.\n * @fires {CustomEvent<AparteComposerChangeEventDetail>} aparte-composer-change - Any of value / streaming / disabled / attachments / panel changed, folded into one event.\n *\n * @cssprop [--aparte-composer-control-size=44px] - Width and height of the composer's\n * own control buttons (each is an `.aparte-btn--icon`, so it needs the opt-in\n * `.aparte-composer-row` wrapper, which is what carries the size down) and the\n * minimum height of the input's editor, which needs no wrapper. One knob for the\n * whole control set, so buttons stay aligned with a single line of text and anchored\n * to the bottom once the input grows. It reaches the buttons by declaration, not by\n * out-specifying them, so a panel mounted in the row keeps its own content's sizing.\n * @cssprop [--aparte-input-bg=var(--aparte-surface-1)] - Background of the opt-in\n * `.aparte-composer-shell` wrapper.\n * @cssprop [--aparte-input-border=var(--aparte-border)] - Border colour of that shell.\n * Its `:focus-within` colour is `--aparte-primary`, a global token rather than a\n * composer one.\n * @cssprop [--aparte-radius-input=var(--aparte-radius-lg)] - Corner radius of the shell,\n * and of the dashed outline drawn while files are dragged over the composer.\n * @cssprop [--aparte-message-max-width=800px] - Max width of the shell, which is\n * `margin: 0 auto` at this width — the same width `.aparte-message` uses, so the\n * composer keeps the transcript's column. Set on THIS element it moves the shell only:\n * custom properties inherit downward and the transcript is a sibling subtree, so set\n * it on a shared ancestor (the chat host, `:root`) to move both.\n *\n * @example\n * <!-- It renders nothing of its own — no shadow root, no default children — so this\n * markup IS the component. The shell draws the border; the row keeps the controls\n * on the bottom edge of the text as it grows. Both are opt-in classes: drop them\n * and the parts still work, they just sit wherever your own layout puts them. -->\n * <aparte-composer placeholder=\"Ask anything…\">\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposer extends HTMLElement {\n private _value = '';\n private _streaming = false;\n private _attachments: File[] = [];\n private _listeners = new Map<string, Set<(payload: unknown) => void>>();\n private _panelActive = false;\n /** What the send button means while a panel is up — see `showPanel`. */\n private _panelMode: AparteComposerPanelMode = 'submit';\n private _panelSubmitEnabled = false;\n private _panelOnSubmit: (() => void) | null = null;\n /**\n * Who owns the one panel slot, and how to tell them they lost it.\n *\n * There is exactly one slot, `showPanel` empties it unconditionally, and nothing\n * used to say whose it was. Three paths therefore closed a panel whose owner was\n * still awaiting an answer, and none of them told the owner: a second `showPanel`,\n * the owner-of-record's own `hidePanel`, and — the one that actually bit —\n * `_handleMessageDone`, which fires on EVERY turn end. A question still open when a\n * turn finished lost its panel while `<aparte-elicitation>` kept `_pending` set,\n * so the request never settled AND every later request was short-circuited for the\n * life of the page.\n */\n private _panelToken: symbol | null = null;\n private _panelOnEvict: (() => void) | null = null;\n\n // Internal bus events that represent an observable STATE change — these are\n // mirrored to the public `aparte-composer-change` DOM event. `submit`/`cancel`\n // are actions, not state, and are covered by `aparte-send`/`aparte-cancel`.\n private static readonly _STATE_EVENTS: ReadonlySet<AparteComposerEventType> = new Set([\n 'value-change', 'streaming-change', 'disabled-change', 'attachments-change', 'panel-change',\n ]);\n\n // Window event bindings\n private _onMessageStart = this._handleMessageStart.bind(this);\n private _onMessageDone = this._handleMessageDone.bind(this);\n\n /** Config governing THIS composer (nearest instance boundary, else global). */\n /**\n * Resolved LIVE, not cached at connect.\n *\n * A wrapper runs `AparteChatHost.bind()` — which calls `attachConfig` — from its\n * POST-mount hook, so this element connects BEFORE the boundary exists. Caching\n * here latched the global config forever: an instance `config` carrying an RTL\n * locale flipped the transcript and not the composer, and the\n * `requireModelSelection` gate was read off the wrong object. `aparte-chat-bubble`\n * has always resolved live, and its JSDoc names this exact race.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n /** Only OUR config: a change on another chat's instance must not touch us. */\n private _onConfigChangeEvent = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n this._onConfigChange();\n };\n /** True while `requireModelSelection` is on AND no model is selected — blocks send. */\n private _modelGated = false;\n private _onConfigChange = (): void => { this._evaluateModelGate(); this._applyDirection(); };\n\n /**\n * Mirror `locale.direction` onto ourselves, so everything inside — input, buttons,\n * the toolbar row and whatever the consumer put in it — inherits it.\n *\n * The direction used to stop at the transcript: only the viewport applied `dir`, so\n * an RTL locale flipped the conversation and left the composer left-to-right. It is\n * also what makes the toolbar's placement idiom real: `margin-inline-start: auto` in\n * a subtree that inherits no direction is just `margin-left`.\n *\n * One attribute on the host rather than a stamp per child: inheritance is the\n * mechanism, so nothing needs to know about the consumer's markup.\n */\n private _applyDirection(): void {\n const direction = (this._cfg ?? resolveConfig(this)).getLocale().direction;\n if (direction) this.setAttribute('dir', direction);\n else this.removeAttribute('dir');\n }\n\n static get observedAttributes(): string[] {\n return ['placeholder', 'disabled', 'target'];\n }\n\n connectedCallback(): void {\n window.addEventListener('aparte-message-start', this._onMessageStart);\n window.addEventListener('aparte-message-done', this._onMessageDone);\n window.addEventListener('aparte-message-error', this._onMessageDone);\n window.addEventListener('aparte-message-aborted', this._onMessageDone);\n // Model-selection gate (opt-in via aparteGlobalConfig.setRequireModelSelection).\n // A window listener rather than `_cfg.subscribe(...)`: subscribing binds to\n // whichever config was resolvable AT CONNECT, which is the bug above wearing\n // a different hat. `_notify()` dispatches `aparte-config-change` with\n // `detail.config`, so the same information arrives without the early binding\n // — and the filter below compares against the LIVE config.\n window.addEventListener('aparte-config-change', this._onConfigChangeEvent);\n // 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 * When false, Shift+Enter submits and a bare Enter inserts a newline —\n * the inverse of the default. Driven by the `submit-on-enter` attribute.\n */\n get submitOnEnter(): boolean { return this.getAttribute('submit-on-enter') !== 'false'; }\n get attachments(): File[] { return this._attachments; }\n get placeholder(): string { return this.getAttribute('placeholder') ?? ''; }\n get targetId(): string | null { return this.getAttribute('target'); }\n\n /**\n * Snapshot of the composer's observable state. Pair with the\n * `aparte-composer-change` DOM event to drive a custom send button or footer\n * control that lives outside the composer package:\n *\n * @example\n * // A custom send button. Keep it CLICKABLE while streaming — submit()\n * // routes to cancel() when a response is in flight, so one button is\n * // Send/Stop. Disabling it on `streaming` would make \"stop\" unreachable.\n * composer.addEventListener('aparte-composer-change', (e) => {\n * const { streaming, disabled, value, attachments } = e.detail.state;\n * myButton.textContent = streaming ? 'Stop' : 'Send';\n * myButton.disabled = disabled || (!streaming && !value.trim() && attachments.length === 0);\n * });\n * myButton.addEventListener('click', () => composer.submit()); // send or stop\n */\n getState(): AparteComposerState {\n return {\n value: this._value,\n streaming: this._streaming,\n disabled: this.disabled,\n attachments: [...this._attachments],\n panelActive: this._panelActive,\n submitEnabled: this._panelSubmitEnabled,\n };\n }\n\n /**\n * Set the composer's value — both what a send will submit and what the editor\n * shows. `<aparte-composer-input>` writes through any value it does not already\n * hold, so this prefills the visible field (a template button, a restored draft)\n * as readily as it stages text for an immediate `submit()`.\n */\n setValue(value: string): void {\n this._value = value;\n this._emit('value-change', { value });\n }\n\n /** Append files to the pending attachments and notify. Does not de-duplicate. */\n addAttachments(files: FileList | File[]): void {\n this._attachments = [...this._attachments, ...Array.from(files)];\n this._emit('attachments-change', { attachments: this._attachments });\n }\n\n /**\n * Drop one pending attachment and notify. Matched by IDENTITY — pass the same\n * `File` object the composer handed you, not an equal one; two picks of the same\n * file on disk are two distinct objects.\n */\n removeAttachment(file: File): void {\n this._attachments = this._attachments.filter(f => f !== file);\n this._emit('attachments-change', { attachments: this._attachments });\n }\n\n /** Drop every pending attachment and notify. */\n clearAttachments(): void {\n this._attachments = [];\n this._emit('attachments-change', { attachments: [] });\n }\n\n /**\n * Inject a panel into the composer. The send button calls `onSubmit` when clicked.\n *\n * While a panel is up, the composer is answering a QUESTION, not composing a\n * message — so the affordances that lead nowhere go away with the text input:\n * the attachment picker above all, which stayed clickable while the user was\n * being asked something (\"on voyait encore l'icône de upload\", reported from a\n * real session). Ratified decision #8: an affordance nothing can honour is not\n * rendered.\n *\n * What STAYS, deliberately: the attachments strip, because pending attachments\n * are the user's state and not an action to offer — hiding them would look like\n * losing them; and the toolbar, because switching model still does something.\n *\n * The send button is the part the PANEL decides, through `mode`. `'submit'`\n * keeps it; `'none'` says this panel has no act for it and it is not drawn. That third value is what a panel whose options settle on the first click\n * needs — a single-choice question, an approval — and until it existed such a\n * panel left a permanently disabled button beside options that never routed\n * through it. Flip between them at any time with {@link setPanelSubmitEnabled},\n * which is how a panel that grows an act (an \"Other…\" field, a written\n * instruction) turns the button back on.\n *\n * Declared with an attribute + CSS rather than the inline `style.display` this\n * used to set on a child: an attribute is themeable, is visible to a consumer's\n * own rules, and does not clobber a `display` the consumer had set (the restore\n * wrote `''`, not the previous value).\n *\n * Returns the TOKEN for this panel. Pass it back to `hidePanel` so a presenter\n * that has already lost the slot cannot close the panel that replaced it, and\n * supply `onEvict` to be told when that happens — an owner that is not told is an\n * owner whose promise nobody can settle.\n */\n showPanel(\n panel: HTMLElement,\n options?: {\n submitEnabled?: boolean;\n onSubmit?: () => void;\n /**\n * What the send button means for this panel — `'none'` if it has no act\n * for it, in which case the button is not drawn. See\n * {@link AparteComposerPanelMode}.\n */\n mode?: AparteComposerPanelMode;\n /** Called when something other than this owner closes the panel. */\n onEvict?: () => void;\n },\n ): symbol {\n // Evict rather than hide: the previous owner is awaiting an answer it will\n // never get, and it is the only thing that can settle its own promise.\n this._evictPanel();\n // 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 * BOTH forms notify. The no-token form used to call `_teardownPanel` directly,\n * which nulls `_panelOnEvict` without ever calling it — so the documented public\n * `hidePanel()` closed an open approval panel and left its request pending\n * forever. The turn hung on \"waiting for you\", and because\n * `AparteConfig.requestUserInput` chains each request on the previous one, NO\n * further question or approval on that config was ever presented again, for the\n * life of the page.\n *\n * The old JSDoc justified the silent branch as \"what `reset()` needs\". It was not:\n * `reset()` calls `_evictPanel()`, which notifies. The branch had no consumer and\n * one failure mode.\n *\n * The two forms differ on purpose, and the difference is who already knows:\n *\n * - **With a matching token** the OWNER is closing its own panel, which is what\n * `<aparte-elicitation>`'s `close()` does right after it resolves. It must NOT be\n * notified — telling it \"you were evicted\" for a request it just settled would\n * fire `onEvict` against a finished promise.\n * - **With no token** somebody else is closing a panel they do not own, so the owner\n * cannot know and has to be told. That includes the presenter's own defensive\n * `hidePanel(undefined)` when it settles before `showPanel` ran: whatever is open\n * then belongs to another request, and that request must not orphan.\n */\n hidePanel(token?: symbol): void {\n if (token !== undefined) {\n if (token !== this._panelToken) return;\n this._teardownPanel();\n return;\n }\n this._evictPanel();\n }\n\n /** Close the panel AND tell its owner, so a pending request never orphans. */\n private _evictPanel(): void {\n const onEvict = this._panelOnEvict;\n // State first, callback second: the owner's settle path calls `hidePanel` with\n // its own token, which must find the slot already empty rather than recurse.\n this._teardownPanel();\n onEvict?.();\n }\n\n private _teardownPanel(): void {\n const existing = this.querySelector('[data-aparte-panel]') as HTMLElement | null;\n if (existing) existing.remove();\n this.removeAttribute('data-panel-active');\n this._panelActive = false;\n this._panelSubmitEnabled = false;\n this._panelMode = 'submit';\n this._reflectPanelMode();\n this._panelOnSubmit = null;\n this._panelOnEvict = null;\n this._panelToken = null;\n this._emit('panel-change', { active: false, submitEnabled: false, mode: 'submit' });\n this.focus();\n }\n\n /**\n * Update the send button's state while a panel is active.\n *\n * `mode` moves with it because both 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 and the same one that took\n * the old `style.display` off a child: an attribute is themeable, is visible to\n * a consumer's own rules, and does not clobber what the consumer set. It is what\n * lets `'none'` remove the send button without this component reaching into it.\n */\n private _reflectPanelMode(): void {\n if (this._panelActive) this.setAttribute('data-panel-mode', this._panelMode);\n else this.removeAttribute('data-panel-mode');\n }\n\n get panelActive(): boolean { return this._panelActive; }\n\n /**\n * Recompute the model gate from the resolved config. When\n * `requireModelSelection` is on and no model is selected, block sending and\n * reflect `data-model-gated` so the shipped CSS greys the composer. Re-runs on\n * every config change (e.g. the model selector's auto-select firing).\n */\n private _evaluateModelGate(): void {\n const gated = this._cfg.getRequireModelSelection() && !this._cfg.hasSelectedModel();\n if (gated === this._modelGated) return;\n this._modelGated = gated;\n this.toggleAttribute('data-model-gated', gated);\n }\n\n /** Submit the current value. Called by aparte-composer-send or programmatically. */\n submit(): void {\n if (this._panelActive) {\n // `'none'` is authoritative over `submitEnabled`, and deliberately so: the\n // two are set by the same caller and can disagree, and the mode is the one\n // that says whether an act exists at all. Reached by Enter inside the panel\n // and by a consumer calling `submit()` directly — the button itself is not\n // drawn in this mode.\n if (this._panelMode !== 'none' && this._panelSubmitEnabled) this._panelOnSubmit?.();\n return;\n }\n if (this._streaming) {\n this.cancel();\n return;\n }\n const value = this._value.trim();\n if (!value && this._attachments.length === 0) return;\n if (this.disabled) return;\n if (this._modelGated) return; // no model selected yet (require-model gate)\n\n this._emit('submit', { value, attachments: this._attachments });\n\n const detail: AparteSendEventDetail = {\n content: value,\n timestamp: Date.now(),\n targetId: this.targetId ?? undefined,\n files: this._attachments.length > 0 ? [...this._attachments] : undefined,\n };\n\n this.dispatchEvent(new CustomEvent<AparteSendEventDetail>('aparte-send', {\n bubbles: true,\n composed: true,\n detail,\n }));\n\n // Clear after send\n this.setValue('');\n this.clearAttachments();\n }\n\n /** Cancel the current streaming response. */\n cancel(): void {\n this._emit('cancel', {});\n // Public, element-scoped signal — symmetric with `aparte-send` on submit,\n // for consumers that want to observe cancel on the composer itself.\n this.dispatchEvent(new CustomEvent('aparte-cancel', { bubbles: true, composed: true }));\n // aparte-abort → tells AparteClient to actually stop the stream\n // aparte-message-aborted → resets the composer's own streaming state\n // Scope the abort to this composer's host so cancelling one chat doesn't\n // abort every scoped client / reset every composer on the page.\n /*\n * `_ownTargetId()`, not the bare attribute.\n *\n * The receive side got this fix and the SEND side did not, which left the whole\n * scoping inert in raw core: nothing writes the `target` attribute in the\n * hand-written markup the quick start shows, so this detail carried\n * `targetId: undefined` — and `_isForThisComposer` treats a missing id as \"for\n * everyone\". So Stop in chat A still tore down chat B's open panel, which is\n * exactly the failure `_ownTargetId`'s own docblock describes as fixed.\n *\n * Both sides now resolve the same way: the attribute if the wrapper set one,\n * otherwise the id of the chat host above us.\n */\n const abortDetail = { targetId: this._ownTargetId() };\n window.dispatchEvent(new CustomEvent('aparte-abort', { bubbles: false, detail: abortDetail }));\n window.dispatchEvent(new CustomEvent('aparte-message-aborted', { bubbles: false, detail: abortDetail }));\n }\n\n /**\n * Reset the composer to its initial state.\n * Clears value, attachments, and hides any active panel.\n * Call this when switching conversations.\n */\n reset(): void {\n this.setValue('');\n this.clearAttachments();\n if (this._panelActive) this._evictPanel();\n }\n\n /** Focus the input primitive inside this composer. */\n override focus(): void {\n const input = this.querySelector('aparte-composer-input') as HTMLElement | null;\n input?.focus();\n }\n\n // ── Internal pub/sub ────────────────────────────────────────────────────\n\n _emit<K extends AparteComposerEventType>(event: K, payload: AparteComposerEventMap[K]): void {\n this._listeners.get(event)?.forEach(cb => cb(payload));\n // Mirror state changes to a public DOM event so elements outside the\n // composer package can observe them without the private bus.\n if (AparteComposer._STATE_EVENTS.has(event)) {\n this.dispatchEvent(new CustomEvent<AparteComposerChangeEventDetail>('aparte-composer-change', {\n bubbles: true,\n composed: true,\n detail: { state: this.getState(), composer: this },\n }));\n }\n }\n\n _on<K extends AparteComposerEventType>(event: K, cb: (payload: AparteComposerEventMap[K]) => void): () => void {\n if (!this._listeners.has(event)) this._listeners.set(event, new Set());\n this._listeners.get(event)!.add(cb as unknown as (payload: unknown) => void);\n return () => this._listeners.get(event)?.delete(cb as unknown as (payload: unknown) => void);\n }\n\n // ── Window events ───────────────────────────────────────────────────────\n\n /** A window lifecycle event is for THIS composer when neither side is scoped\n * (single-instance broadcast) or the target ids match (multi-chat page).\n * Without this filter, streaming in one chat flips every composer's state. */\n private _isForThisComposer(e: Event): boolean {\n const evtTargetId = (e as CustomEvent).detail?.targetId as string | undefined;\n return !evtTargetId || !this._ownTargetId() || evtTargetId === this._ownTargetId();\n }\n\n /**\n * Which chat this composer belongs to: its `target` attribute, or the id of the\n * chat host above it.\n *\n * All four wrappers set `target` themselves, so the attribute alone identified a\n * composer there. In RAW core — the documented quick start, where the markup is\n * hand-written — nothing sets it, so `!this.targetId` was true and this composer\n * accepted every chat's lifecycle events: on a two-chat page, one chat's Stop\n * tore down the other's open elicitation panel while its tool call kept waiting,\n * i.e. the question vanished and the turn hung.\n *\n * Found by a two-chat test written for the elicitation presenter, which is the\n * only reason it surfaced: raw core with two chats is a shape nothing exercised.\n * The hosts matched are the ones `aparte-chat-bubble._resolveTargetId()` matches,\n * for the reason written there — Angular's wrapper root IS `<aparte-chat>`, the\n * other three render a `[data-aparte-chat]` div.\n */\n /**\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 private _handleMessageStart(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streaming = true;\n this._emit('streaming-change', { streaming: true });\n }\n\n private _handleMessageDone(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streaming = false;\n this._emit('streaming-change', { streaming: false });\n // Always hide any active panel when a message lifecycle ends\n if (this._panelActive) this._evictPanel();\n }\n}\n\nif (!customElements.get('aparte-composer')) {\n customElements.define('aparte-composer', AparteComposer);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Contenteditable text input primitive.\n *\n * The element owns its subtree: on connect it writes one `.aparte-ci-editor`\n * contenteditable and binds its listeners to that node, so children you place inside are\n * replaced. There is nothing to project here — style the generated editor through the CSS\n * variables below, or replace the whole primitive.\n *\n * Enter submits and Shift+Enter inserts a newline; `submit-on-enter=\"false\"` on the\n * composer inverts that mapping, and Enter never submits mid-IME-composition — the key\n * that confirms a CJK candidate must not send the message. The editor auto-expands with\n * its content up to `max-height`, then scrolls. Paste is intercepted: text lands as plain\n * text with its markup stripped, and a pasted image goes to the composer's attachments.\n *\n * Without an `<aparte-composer>` ancestor it still works, and that is deliberate: a\n * submitting Enter then dispatches `aparte-composer-submit` instead of calling\n * `root.submit()`, which is how the bubble's inline editor reuses this primitive.\n * Everything the root owns goes with it though — the mirrored value, the placeholder\n * fallback, the disabled 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=44px] - Single-line min-height of the editor.\n * Inside the `.aparte-composer-row` layout helper the composer's buttons read\n * the same token, so one value resizes that whole control set and the row stays\n * aligned.\n * @cssprop [--aparte-input-padding-y=10px] - Vertical padding inside the editor.\n * @cssprop [--aparte-input-padding-x=12px] - Horizontal padding inside the editor.\n * @cssprop [--aparte-input-font-size=14px] - Editor font size.\n * @cssprop [--aparte-input-line-height=1.5] - Editor line height — also what the\n * auto-expand measures, so changing it changes the height the editor settles at\n * (until `max-height` clamps it).\n * @cssprop --aparte-text - Text and caret colour of the editor.\n * @cssprop --aparte-input-placeholder - Colour of the placeholder drawn by\n * `:empty::before` (falls back to `--aparte-text-muted`).\n * @cssprop --aparte-input-bg - Field background, applied only when this input is the\n * bubble's inline editor (`.aparte-message[data-editing]`) — inside a composer\n * the shell paints the surface instead.\n * @cssprop --aparte-input-border - Border colour of that same edit-mode box.\n * @cssprop [--aparte-radius-input=8px] - Corner radius of the edit-mode box.\n * @cssprop --aparte-input-focus-border - Border colour of the edit-mode box while it\n * holds focus (`:focus-within`).\n *\n * @example\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input placeholder=\"Ask anything…\" max-height=\"320\" style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerInput extends HTMLElement {\n private _editor: HTMLDivElement | null = null;\n private _maxHeight = 200;\n private _minHeight = 44;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handlers\n private _onInput = this._handleInput.bind(this);\n private _onKeydown = this._handleKeydown.bind(this);\n private _onFocus = this._handleFocus.bind(this);\n private _onBlur = this._handleBlur.bind(this);\n private _onPaste = this._handlePaste.bind(this);\n\n static get observedAttributes(): string[] {\n return ['placeholder', 'max-height', 'min-height', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n this._scheduleInitialReflow();\n // The placeholder is the one string in this composer a sighted user can\n // actually read, and it was frozen at the language of the first render.\n // `_updatePlaceholder` already exists for the attribute path — a locale\n // change is the same refresh, from a different trigger.\n this._unsubscribes.push(subscribeConfigChange(this, () => this._updatePlaceholder()));\n }\n\n disconnectedCallback(): void {\n this._editor?.removeEventListener('input', this._onInput);\n this._editor?.removeEventListener('keydown', this._onKeydown);\n this._editor?.removeEventListener('focus', this._onFocus);\n this._editor?.removeEventListener('blur', this._onBlur);\n this._editor?.removeEventListener('paste', this._onPaste);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'placeholder') this._updatePlaceholder();\n if (name === 'max-height') this._maxHeight = parseInt(value || '200', 10);\n if (name === 'min-height') this._minHeight = parseInt(value || '44', 10);\n if (name === 'disabled') this._updateDisabled(value !== null);\n }\n\n // ── Public API ──────────────────────────────────────────────────────────\n\n /**\n * The editor's text, with `<br>` serialized back to newlines — `textContent`\n * alone would collapse a multi-line draft onto a single line.\n */\n getValue(): string {\n // `textContent` drops `<br>`, so multi-line content would collapse onto one\n // line. Serialize the editor ourselves: text nodes as-is, `<br>` → newline.\n // We keep the editor flat (text + <br>, see _handleKeydown), but descend into\n // any stray wrapper for safety.\n if (!this._editor) return '';\n let out = '';\n const walk = (node: Node): void => {\n node.childNodes.forEach(child => {\n if (child.nodeType === Node.TEXT_NODE) out += child.textContent ?? '';\n else if (child.nodeName === 'BR') out += '\\n';\n else walk(child);\n });\n };\n walk(this._editor);\n return out.trim();\n }\n\n /** Replace the editor's content and mirror the value onto the parent composer. */\n setValue(value: string): void {\n if (!this._editor) return;\n this._editor.textContent = value;\n this._updatePlaceholderVisibility();\n this._adjustHeight();\n this._getRoot()?.setValue(value);\n }\n\n /** Empty the editor and mirror the empty value onto the parent composer. */\n clear(): void {\n if (!this._editor) return;\n this._editor.innerHTML = '';\n this._updatePlaceholderVisibility();\n this._adjustHeight();\n this._getRoot()?.setValue('');\n }\n\n /**\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 this._editor?.addEventListener('input', this._onInput);\n this._editor?.addEventListener('keydown', this._onKeydown);\n this._editor?.addEventListener('focus', this._onFocus);\n this._editor?.addEventListener('blur', this._onBlur);\n this._editor?.addEventListener('paste', this._onPaste);\n\n this._adjustHeight();\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n // Sync disabled state from root\n this._unsubscribes.push(\n root._on('disabled-change', ({ disabled }) => this._updateDisabled(disabled))\n );\n\n // The editor shows what the composer says.\n //\n // Compared, not special-cased. While the user types, `_handleInput` has just\n // pushed this very value up, so the two sides are equal and nothing is\n // rewritten — which is what keeps the caret where the user left it, and was the\n // entire reason the old form acted on `''` alone. Any other value arrived from\n // somewhere else: `setValue()` on the composer, or the `''` that `submit()`\n // writes on its way out. Both now land, where only the second one used to.\n //\n // Compared against `value.trim()` because `getValue()` trims. A padded value\n // would never look equal otherwise, and the mirror back through `setValue`\n // would re-enter this callback forever.\n this._unsubscribes.push(\n root._on('value-change', ({ value }) => {\n if (this.getValue() === value.trim()) return;\n if (value === '') this.clear();\n else this.setValue(value);\n })\n );\n\n // 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\n const imageFile = Array.from(cd.items).find(i => i.type.startsWith('image/'))?.getAsFile();\n if (imageFile) {\n this._getRoot()?.addAttachments([imageFile]);\n return;\n }\n\n // Plain text paste\n const text = cd.getData('text/plain');\n if (text) {\n document.execCommand('insertText', false, text);\n }\n }\n\n private _adjustHeight(): void {\n if (!this._editor) return;\n // Measure with height:0 (not auto): an explicit height opts the editor\n // OUT of any parent flex `align-items: stretch`, so scrollHeight reflects\n // the real content — not the (taller) row it may be stretched into. With\n // `auto`, a stretching parent inflates scrollHeight and the editor gets\n // stuck tall until the next reflow.\n this._editor.style.height = '0px';\n // Floor: the `min-height` ATTRIBUTE when explicitly set; otherwise defer\n // to the stylesheet (CSS min-height caps an inline height anyway). A\n // hardcoded JS floor would override theme CSS with an inline style and\n // break editor/controls alignment in restyled composers.\n const floor = this.hasAttribute('min-height') ? this._minHeight : 0;\n const contentHeight = this._editor.scrollHeight;\n const h = Math.min(Math.max(contentHeight, floor), this._maxHeight);\n this._editor.style.height = `${h}px`;\n this._editor.style.overflowY = contentHeight > this._maxHeight ? 'auto' : 'hidden';\n }\n\n /**\n * The first `_adjustHeight()` runs synchronously in `_render()` on connect —\n * before the stylesheet, flex layout and web fonts have necessarily settled.\n * On an unstabilized layout `scrollHeight` can read inflated, leaving the\n * editor stuck tall (misaligned with the composer controls) until the first\n * keystroke re-measures it. Re-measure once the layout is ready so it's\n * correct from the first paint.\n */\n private _scheduleInitialReflow(): void {\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => this._adjustHeight());\n }\n const fonts = (document as Document & { fonts?: { ready?: Promise<unknown> } }).fonts;\n fonts?.ready?.then(() => this._adjustHeight()).catch(() => { /* fonts unavailable — rAF path covers it */ });\n }\n\n private _updatePlaceholder(): void {\n if (this._editor) {\n const p = this._getPlaceholder();\n this._editor.setAttribute('data-placeholder', p);\n this._editor.setAttribute('aria-label', p);\n }\n }\n\n private _updatePlaceholderVisibility(): void {\n // Handled by CSS :empty — nothing to do\n }\n\n private _updateDisabled(disabled: boolean): void {\n if (!this._editor) return;\n this._editor.setAttribute('contenteditable', String(!disabled));\n this._editor.setAttribute('aria-disabled', String(disabled));\n // 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=44px] - Width/height of the button inside the\n * `.aparte-composer-row` layout helper, shared with the input's single-line\n * height so the row stays aligned. It wins over `--aparte-send-btn-size` there.\n * @cssprop [--aparte-send-btn-size=36px] - Width/height of the button outside that row\n * helper. On coarse pointers it is raised to `--aparte-touch-target-size`.\n * @cssprop [--aparte-touch-target-size=44px] - Hit-area floor applied to the button\n * under `@media (pointer: coarse)`.\n * @cssprop [--aparte-radius-send-btn=6px] - Corner radius of the button.\n * @cssprop --aparte-primary - Button background.\n * @cssprop --aparte-primary-hover - Button background on hover, while enabled.\n * @cssprop --aparte-on-primary - The glyph's colour. Undeclared by default, which means\n * the recipe derives it from `--aparte-primary` itself, so a theme that changes\n * the fill gets a readable glyph with no second edit. Declare it to choose one\n * — it then applies to every primary control, which is the honest scope.\n * @cssprop [--aparte-ink-flip=0.57] - Fill lightness at which the derived ink flips from\n * dark to light, for every solid control.\n * @cssprop [--aparte-ink-dark=0.176] - How dark that derived ink goes. Not 0: at zero\n * lightness OKLCH drops the chroma, and the ink loses the fill's own hue.\n * @cssprop --aparte-send-disabled-bg - Background while disabled (falls back to\n * `--aparte-primary`, which is then dimmed by opacity).\n *\n * @example\n * <!-- One button for both halves of the turn: it submits, and while a reply streams it\n * becomes the stop button. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerSend extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n /**\n * The last `panel-change` payload.\n *\n * This button has four meanings — send, stop, submit an answer, advance to the\n * next question — and three of them are decided by state it does not own: the\n * root's `streaming`, and this payload. It was read straight out of the event's\n * arguments and thrown away, so nothing could recompute the button's chrome\n * afterwards; a config change had no way to know which of the four to write.\n */\n private _panel: { active: boolean; submitEnabled: boolean; mode: AparteComposerPanelMode } | null = null;\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-cs-button')) return;\n\n const label = resolveConfig(this).t('sendButton') || 'Send';\n const icon = this._getSendIcon();\n const root = this._getRoot();\n const disabled = !root || root.disabled || root.value.trim() === '';\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--primary aparte-btn--solid aparte-btn--icon aparte-cs-button aparte-send-button\"\n aria-label=\"${escapeAttr(label)}\"\n title=\"${escapeAttr(label)}\"\n ${disabled ? 'disabled' : ''}\n >${icon}</button>`; // safe-text: _getSendIcon() returns the provider's SVG markup — escaping it would print the source\n\n this._button = this.querySelector('.aparte-cs-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('value-change', () => this._syncState())\n );\n this._unsubscribes.push(\n root._on('disabled-change', () => this._syncState())\n );\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n // If panel is active, streaming state change doesn't affect the button —\n // the panel controls it (submit answer, not stop stream)\n if (this._getRoot()?.panelActive) return;\n this._syncStreamingState(streaming);\n })\n );\n this._unsubscribes.push(\n root._on('attachments-change', () => this._syncState())\n );\n this._unsubscribes.push(\n root._on('panel-change', (payload) => {\n this._panel = payload;\n this._refreshChrome();\n })\n );\n // A config change — a new icon set, another language — has to write the\n // chrome for whichever of the four meanings the button currently carries.\n this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));\n }\n\n /**\n * Write the chrome for the mode the button is IN, deciding before writing.\n *\n * Never `_render()`: it returns early once the button exists, and its own\n * disabled/icon computation consults neither `root.streaming` nor the panel — so\n * rebuilding mid-turn would put a paper plane back while a reply was still\n * streaming, and rebuilding with the question panel open would silently drop out\n * of answer mode. It would also take the focus off the one control in this\n * composer most likely to be holding it.\n */\n private _refreshChrome(): void {\n if (!this._button) return;\n if (this._panel?.active) { this._syncPanelState(); return; }\n if (this._getRoot()?.streaming) { this._syncStreamingState(true); return; }\n this._syncState();\n }\n\n /**\n * Panel open: this one button now means \"submit the answer\".\n *\n * The icon has to move with the meaning: it drew a paper plane while the label\n * already said \"Submit\", so it read as \"send a message\" while it meant \"answer\n * this question\". It used to draw a chevron too, while a form had questions\n * ahead, and turn into a \"Next\" — a second way to do what the panel's chips do,\n * and a button whose meaning changed under the pointer. It is disabled instead\n * 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=44px] - Width/height of the button inside the\n * `.aparte-composer-row` layout helper, shared with the composer's other\n * controls so the row stays aligned.\n * @cssprop [--aparte-radius-action-btn=4px] - Corner radius of the button.\n * @cssprop --aparte-neutral - Icon colour at rest (the button's background is\n * transparent).\n * @cssprop --aparte-text - Icon colour on hover.\n * @cssprop --aparte-surface-2 - Button background on hover.\n *\n * @example\n * <!-- Only needed when you want a SEPARATE stop button: <aparte-composer-send> already\n * turns into one while streaming. This stays hidden until then. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-cancel></aparte-composer-cancel>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerCancel extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-cc-button')) return;\n\n const label = resolveConfig(this).t('stopButton') || 'Stop';\n const icon = this._getStopIcon();\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--icon aparte-cc-button\"\n aria-label=\"${escapeAttr(label)}\"\n title=\"${escapeAttr(label)}\"\n hidden\n >${icon}</button>`; // safe-text: _getStopIcon() returns the provider's SVG markup — escaping it would print the source\n\n this._button = this.querySelector('.aparte-cc-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n if (this._button) this._button.hidden = !streaming;\n })\n );\n\n // Sync initial state\n if (root.streaming && this._button) this._button.hidden = false;\n }\n\n private _handleClick(e: MouseEvent): void {\n e.preventDefault();\n this._getRoot()?.cancel();\n }\n\n /**\n * Re-read the accessible name and the icon in place.\n *\n * `hidden` is NOT touched: `_render()` always renders this button hidden and only\n * the root's `streaming-change` listener ever un-hides it, so a rebuild would make\n * the stop button vanish in the middle of a turn.\n */\n private _refreshChrome(): void {\n if (!this._button) return;\n const label = resolveConfig(this).t('stopButton') || 'Stop';\n this._button.setAttribute('aria-label', label);\n this._button.setAttribute('title', label);\n this._button.innerHTML = this._getStopIcon();\n }\n\n private _getStopIcon(): string {\n return resolveConfig(this).getIcon('stop');\n }\n}\n\nif (!customElements.get('aparte-composer-cancel')) {\n customElements.define('aparte-composer-cancel', AparteComposerCancel);\n}\n","import type { AparteComposer } from './aparte-composer.js';\nimport { resolveConfig } from '../../config/config-context.js';\nimport { escapeAttr, 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=56px] - Tile edge. The stylesheet sets 56px on\n * this element (the `:root` default is 72px, and the sent-message strip re-sets 40px on\n * itself), so a theme-level value reaches neither strip — target\n * `aparte-composer-attachments` to resize these tiles.\n * @cssprop [--aparte-thumb-radius=var(--aparte-radius-lg)] - Tile corner radius.\n * @cssprop [--aparte-attachment-chip-bg=var(--aparte-surface-2)] - Tile background, seen\n * behind a non-image file.\n * @cssprop [--aparte-attachment-chip-border=var(--aparte-border)] - Tile border colour.\n * @cssprop [--aparte-thumb-name-color=#ffffff] - Filename colour on the hover overlay.\n * @cssprop [--aparte-thumb-name-scrim=linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0))] - Background behind the filename; a bottom-up black\n * gradient by default, so the name stays legible over any picture.\n * @cssprop [--aparte-thumb-name-padding=14px 5px 4px] - Padding of that overlay.\n * @cssprop [--aparte-thumb-remove-size=18px] - Diameter of the ✗ button.\n * @cssprop [--aparte-thumb-remove-inset=3px] - Its inset from the tile's top and right\n * edges (physical `right`, so it does not flip in a right-to-left locale).\n * @cssprop [--aparte-thumb-remove-bg=rgba(0, 0, 0, 0.6)] - Its background.\n * @cssprop [--aparte-thumb-remove-bg-hover=rgba(0, 0, 0, 0.85)] - Its hover background.\n * @cssprop [--aparte-thumb-remove-color=#ffffff] - Its glyph colour.\n *\n * @example\n * <!-- The strip hides itself while nothing is attached. Pair it with the picker, and\n * only if your loop actually reads the files from the send event. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <aparte-composer-attachments></aparte-composer-attachments>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-add-attachment></aparte-composer-add-attachment>\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerAttachments extends HTMLElement {\n private _unsubscribes: (() => void)[] = [];\n /** Object URLs minted for image previews — revoked on re-render/disconnect. */\n private _objectUrls: string[] = [];\n\n connectedCallback(): void {\n this._render([]);\n this._connectToRoot();\n }\n\n disconnectedCallback(): void {\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n this._revokeUrls();\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('attachments-change', ({ attachments }) => this._render(attachments))\n );\n\n // Sync initial state\n this._render(root.attachments);\n }\n\n /** Release the previous render's blob URLs so they don't leak. */\n private _revokeUrls(): void {\n this._objectUrls.forEach(url => URL.revokeObjectURL(url));\n this._objectUrls = [];\n }\n\n private _render(files: File[]): void {\n this.hidden = files.length === 0;\n // Free the previous render's preview URLs before minting new ones.\n this._revokeUrls();\n\n 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 &amp; 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-thumb__img\" 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-thumb__ext\">${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 this.querySelectorAll('.aparte-thumb--image').forEach(tile => {\n tile.setAttribute('role', 'button');\n tile.setAttribute('tabindex', '0');\n const open = (): void => {\n const img = tile.querySelector('.aparte-thumb__img') as HTMLImageElement | null;\n if (!img) return;\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true,\n composed: true,\n detail: { url: img.src, name: tile.getAttribute('title') ?? '' },\n }));\n };\n tile.addEventListener('click', open);\n tile.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key !== 'Enter' && key !== ' ') return;\n e.preventDefault();\n open();\n });\n });\n }\n\n /** Uppercased file extension (≤4 chars), or 'FILE' when there is none. */\n private _ext(filename: string): string {\n const dot = filename.lastIndexOf('.');\n return dot > 0 ? filename.slice(dot + 1).toUpperCase().slice(0, 4) : 'FILE';\n }\n}\n\nif (!customElements.get('aparte-composer-attachments')) {\n customElements.define('aparte-composer-attachments', AparteComposerAttachments);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * File picker button for <aparte-composer>.\n *\n * Opens a native file picker on click, then pushes picked files to root.addAttachments().\n * Also sets up drag & drop on the nearest <aparte-composer> root.\n *\n * It only COLLECTS files: it never reads, uploads or renders them.\n * `<aparte-composer-attachments>` draws the pending strip, and sending is the host's job\n * (`event.detail.files` on `aparte-send`) — which is why the default `<aparte-chat>` shell\n * only includes this button when the `attachments` attribute is set. With nothing reading\n * the files, an attach button is an affordance core cannot honour (ratified decision #8).\n *\n * Drag & drop is installed on the composer ROOT, not on this button, so a drop anywhere\n * over the composer attaches and the root carries `aparte-is-dragover` while a drag is\n * over it. The dashed outline is drawn on `.aparte-composer-shell` when the markup has one\n * and on the composer element itself when it does not — width from\n * `--aparte-focus-outline-width`, colour from `--aparte-primary`, radius from\n * `--aparte-radius-input`, none of them declared here. The drop handler always calls\n * `preventDefault()`, even while disabled, so the browser can never navigate away to the\n * dropped file. `disabled` on the ROOT removes the drop target 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=36px] - Square size of the button. On a coarse\n * pointer the stylesheet re-sets it to `--aparte-touch-target-size` (44px) on\n * `.aparte-action-button` itself, which wins over a value inherited from your theme.\n * @cssprop [--aparte-input-action-btn-icon-size=20px] - Size of the `<svg>` inside it.\n * @cssprop [--aparte-radius-action-btn=var(--aparte-radius-sm)] - Corner radius.\n *\n * @example\n * <!-- Opt-in: nothing consumes the files unless your host does (an AparteClient, or\n * your own listener reading `event.detail.files` off `aparte-send`). -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-add-attachment accept=\"image/*,.pdf\"></aparte-composer-add-attachment>\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerAddAttachment extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _dragCleanup: (() => void) | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n static get observedAttributes(): string[] {\n return ['accept', 'multiple', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n this._setupDragDrop();\n this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._dragCleanup?.();\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'disabled' && this._button) {\n this._button.disabled = value !== null;\n }\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n /**\n * Re-read the label and the icon on the button that already exists.\n *\n * Deliberately not a re-render: `_render()` returns early once the button is\n * there, and 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 this._button = this.querySelector('.aparte-caa-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('disabled-change', ({ disabled }) => {\n if (this._button) this._button.disabled = disabled;\n })\n );\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n // 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, type AparteIconName } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Generic action button primitive for <aparte-composer>.\n *\n * The consumer declares it directly in markup — no global registration needed.\n *\n * It is the escape hatch for a button core has no opinion about: it renders one icon\n * button wearing `.aparte-action-button` (the shared icon-button look — colour from\n * `--aparte-neutral`, hover tint derived from `--aparte-primary`) and emits\n * `aparte-action-click`. It carries no behaviour of its own and nothing in core listens\n * for that event, so the app is the only thing that can make it do something. Prefer the\n * dedicated element wherever one exists — `<aparte-composer-send>`,\n * `<aparte-composer-cancel>`, `<aparte-composer-add-attachment>` — since those already\n * talk to the composer.\n *\n * The host is `display: contents`, so the `<button>` rather than this element is the flex\n * child of the surrounding `.aparte-composer-row`. It subscribes to the nearest composer's\n * `disabled` and `streaming` changes, so it greys out while a turn is running without the\n * app tracking that. Used outside a composer it still mounts and still fires, with\n * `composer: null` in the detail.\n *\n * A child already carrying `class=\"aparte-cact-button\"` suppresses core's own render — and\n * core then wires nothing to it: no click listener (so no `aparte-action-click`), no\n * `label` → `aria-label`/`title` write, no `icon` write, no disabled/streaming sync. Take\n * that path only for a button your own code drives end to end. Any other child is replaced\n * on the first render.\n *\n * @element aparte-composer-action\n *\n * @attr {string} icon - Icon key for aparteGlobalConfig.getIcon(), or raw SVG/HTML starting with `<`\n * @attr {string} label - Accessible label (also used as tooltip)\n * @attr {boolean} disabled - Disables the button\n * @attr {string} action-id - Identifies WHICH button fired; carried as\n * `AparteActionClickEventDetail.actionId`. Read lazily at dispatch time rather than\n * observed, so changing it takes effect on the next click.\n *\n * @fires {CustomEvent<AparteActionClickEventDetail>} aparte-action-click - Bubbles up when\n * the button is clicked, carrying which button it was and the composer it belongs to.\n * The type argument is not decoration: a BARE `@fires` records `CustomEvent` with no\n * argument, and the bindings generator then emits `EventEmitter<void>` with a\n * listener that drops `$event` — so an Angular consumer with two custom buttons\n * could not tell which one fired.\n * detail: { actionId: string, composer: AparteComposer | null }\n *\n * @cssprop [--aparte-input-action-btn-size=36px] - Square size of the button. On a coarse\n * pointer the stylesheet re-sets it to `--aparte-touch-target-size` (44px) on\n * `.aparte-action-button` itself, which wins over a value inherited from your theme.\n * @cssprop [--aparte-input-action-btn-icon-size=20px] - Size of the `<svg>` inside it.\n * @cssprop [--aparte-radius-action-btn=var(--aparte-radius-sm)] - Corner radius.\n *\n * @example\n * <!-- Inside a composer, because that is what it resolves with `closest()`. `action-id`\n * is what tells two custom buttons apart: it comes back on the event's detail, and\n * a second button without one is indistinguishable from the first. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-action icon=\"star\" label=\"Favourite\" action-id=\"favourite\"></aparte-composer-action>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n *\n * <script>\n * // The event bubbles, so one listener above the composer serves every action.\n * document.addEventListener('aparte-action-click', (event) => {\n * if (event.detail.actionId === 'favourite') console.log('starred');\n * });\n * </script>\n */\nexport class AparteComposerAction extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n static get observedAttributes(): string[] {\n return ['icon', 'label', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n this._connectToRoot();\n // Icon only, and no locale: this element's label is the consumer's `label`\n // ATTRIBUTE, so the app owns that string and a locale change is correctly a\n // no-op here. The write is the same one `attributeChangedCallback` does for\n // the `icon` attribute — `_resolveIcon` already decides between a provider\n // key and raw markup, so calling it again is idempotent.\n this._unsubscribes.push(subscribeConfigChange(this, () => {\n if (this._button) this._button.innerHTML = this._resolveIcon(this.getAttribute('icon') ?? '');\n }));\n }\n\n disconnectedCallback(): void {\n this._button?.removeEventListener('click', this._onClick);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (!this._button) return;\n if (name === 'disabled') {\n this._button.disabled = value !== null;\n }\n if (name === 'label') {\n this._button.setAttribute('aria-label', value ?? '');\n this._button.setAttribute('title', value ?? '');\n }\n if (name === 'icon') {\n this._button.innerHTML = this._resolveIcon(value ?? '');\n }\n }\n\n // ── Private ─────────────────────────────────────────────────────────────\n\n private _getRoot(): AparteComposer | null {\n return this.closest('aparte-composer') as AparteComposer | null;\n }\n\n private _render(): void {\n if (this.querySelector('.aparte-cact-button')) return;\n\n // `label` is a host-set attribute (often bound to dynamic/translated\n // text by the consumer) — escape before it lands in a double-quoted\n // attribute so a stray `\"` can't break out and inject markup.\n const label = escapeAttr(this.getAttribute('label') ?? '');\n const icon = this._resolveIcon(this.getAttribute('icon') ?? ''); // safe-text: _resolveIcon returns provider SVG, or the host-set icon attribute verbatim when it starts with < — documented as trusted markup, same contract as AparteIconProvider\n const disabled = this.hasAttribute('disabled') || this._getRoot()?.disabled || false;\n\n this.innerHTML = `<button\n class=\"aparte-btn aparte-btn--icon aparte-cact-button aparte-action-button\"\n aria-label=\"${label}\"\n title=\"${label}\"\n type=\"button\"\n ${disabled ? 'disabled' : ''}\n >${icon}</button>`;\n\n this._button = this.querySelector('.aparte-cact-button');\n this._button?.addEventListener('click', this._onClick);\n }\n\n private _connectToRoot(): void {\n const root = this._getRoot();\n if (!root) return;\n\n this._unsubscribes.push(\n root._on('disabled-change', ({ disabled }) => {\n if (this._button) this._button.disabled = disabled || this.hasAttribute('disabled');\n })\n );\n this._unsubscribes.push(\n root._on('streaming-change', ({ streaming }) => {\n if (this._button) this._button.disabled = streaming || root.disabled || this.hasAttribute('disabled');\n })\n );\n }\n\n private _handleClick(_e: MouseEvent): void {\n this.dispatchEvent(new CustomEvent<AparteActionClickEventDetail>('aparte-action-click', {\n bubbles: true,\n composed: true,\n detail: { actionId: this.getAttribute('action-id') ?? '', composer: this._getRoot() },\n }));\n }\n\n private _resolveIcon(icon: string): string {\n if (!icon) return '';\n if (icon.trimStart().startsWith('<')) return icon;\n return resolveConfig(this).getIcon(icon as AparteIconName) ?? icon;\n }\n}\n\nif (!customElements.get('aparte-composer-action')) {\n customElements.define('aparte-composer-action', AparteComposerAction);\n}\n\n/**\n * Detail payload for `aparte-action-click`.\n *\n * `<aparte-composer-action>` is a publicly exported element whose only purpose is\n * to emit this event, and nothing in core listens for it — so the app IS the\n * consumer, and it had no type to read `e.detail` with. The shape was already\n * published in prose in the generated API reference; this makes it compile.\n *\n * @event aparte-action-click\n */\nexport interface AparteActionClickEventDetail {\n /** The `action-id` attribute of the button that was clicked, or `''`. */\n actionId: string;\n /** The owning composer, or `null` when the button is used outside one. */\n composer: AparteComposer | null;\n}\n","/**\n * The composer's bottom row — the strip a mode picker, a model selector or a token\n * counter belongs in, rather than a bar of your own floating below the chat. Purely\n * structural: it lays its children out in a row and gets out of the way.\n *\n * **Position is the DOM order.** `margin-inline-start: auto` on a child pushes it (and\n * everything after it) to the end of the row. That is the whole placement API on\n * purpose: there is no `left`/`right` to be wrong about, so the row reads correctly in a\n * right-to-left locale without the author thinking about it.\n *\n * The controls the row is made of can be any element, or plain text. Nothing is wrapped or\n * reordered — the children ARE the row, laid out by flex in DOM order, and they may arrive\n * after connection (a framework commits children in its own order). Non-whitespace TEXT\n * counts as content too, so a hand-written row holding a bare token count stays visible\n * instead of tripping the `data-empty` hide.\n *\n * The row is not part of the default `<aparte-chat>` shell — nothing is drawn until you\n * put something in it.\n *\n * It declares no custom property of its own: the gap, the padding and the top separator\n * come from the global `--aparte-space-*` and `--aparte-border*` tokens, so it inherits a\n * theme rather than exposing knobs to re-set.\n *\n * @element aparte-composer-toolbar\n *\n * @attr {boolean} data-empty - Reflected BY the element while it holds neither an element\n * child nor non-whitespace text; the stylesheet hides it then.\n * Read-only, do not set it yourself.\n *\n * @example\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n *\n * <!-- `aparte-model-selector` is NOT part of core: importing\n * `@aparte/plugin-model-selector` is what defines it. Until then the tag\n * renders empty and inert with no error, and upgrades by itself when the\n * definition arrives. Any element of your own works here too. -->\n * <aparte-composer-toolbar>\n * <my-mode-picker></my-mode-picker>\n * <aparte-model-selector style=\"margin-inline-start:auto\"></aparte-model-selector>\n * </aparte-composer-toolbar>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerToolbar extends HTMLElement {\n private _observer: MutationObserver | null = null;\n\n connectedCallback(): void {\n this._syncEmpty();\n // Children can arrive after connection — a framework commits the element and its\n // children in whichever order suits it, and a consumer may add a control later.\n this._observer ??= new MutationObserver(() => this._syncEmpty());\n this._observer.observe(this, { childList: true });\n }\n\n disconnectedCallback(): void {\n this._observer?.disconnect();\n this._observer = null;\n }\n\n /**\n * Reflect `data-empty` from the presence of an ELEMENT child.\n *\n * Not `:empty` in CSS: that selector does not match an element holding a whitespace\n * text node, so a template that indents its content keeps the row — separator,\n * padding and all — while it looks empty to the user. Every framework template\n * indents. An empty row must not draw its own separator (the same rule as an empty\n * bubble action bar).\n *\n * Non-whitespace TEXT counts as content, not just an element child: a hand-written\n * row holding a bare token count (`<aparte-composer-toolbar>1 240 tokens</…>`) is\n * not empty, and hiding it would be a twenty-minute mystery for whoever wrote it.\n */\n private _syncEmpty(): void {\n const hasContent = Boolean(this.firstElementChild) || this.textContent?.trim() !== '';\n if (hasContent) this.removeAttribute('data-empty');\n else this.setAttribute('data-empty', '');\n }\n}\n\nif (!customElements.get('aparte-composer-toolbar')) {\n customElements.define('aparte-composer-toolbar', AparteComposerToolbar);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { APARTE_DEFAULT_LOCALE } from '../../config/locale.js';\nimport { escapeAttr, escapeHtml } from '../../utils/escape.js';\nimport { cssEscape } from '../../utils/css-escape.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' | 'deleteConversation' | 'archiveConversation' | 'unarchiveConversation'\n | 'conversationActions' | 'renameConversation' | 'conversationTitle'\n | 'pinConversation' | 'unpinConversation' | 'deleteConversationConfirm' | 'cancel'\n | 'conversationGroupPinned' | 'conversationGroupToday' | 'conversationGroupYesterday'\n | 'conversationGroupWeek' | 'conversationGroupMonth';\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, never acting on it itself.\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 `no-groups` 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. 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} no-groups - Render the rows flat, in host order, with no date headings.\n *\n * @fires {CustomEvent<AparteConversationSelectDetail>} aparte-select-conversation - A row's title was activated; the host loads that conversation.\n * @fires {CustomEvent<AparteConversationRenameDetail>} aparte-rename-conversation - A rename was committed with a new, non-empty title. Nothing is renamed here.\n * @fires {CustomEvent<AparteConversationPinDetail>} aparte-pin-conversation - The pin item was chosen on an unpinned row.\n * @fires {CustomEvent<AparteConversationPinDetail>} aparte-unpin-conversation - The same item on a pinned row; same detail shape, opposite intent.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-archive-conversation - The archive item was chosen on a live conversation.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-unarchive-conversation - The same item on an already-archived one; same detail shape, opposite intent.\n * @fires {CustomEvent<AparteConversationDeleteDetail>} aparte-delete-conversation - The delete was confirmed. Nothing is removed here.\n *\n * @cssprop [--aparte-conv-list-gap=2px] - Vertical gap between rows, and between a group's heading and its rows.\n * @cssprop [--aparte-conv-item-padding=7px 10px] - Padding of a row's title button.\n * @cssprop [--aparte-conv-item-gap=6px] - 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=0.8125rem] - Font size of a row's title.\n * @cssprop [--aparte-conv-item-color=var(--aparte-text-muted)] - Title colour of an inactive row.\n * @cssprop [--aparte-conv-item-bg-hover=var(--aparte-surface-3)] - Row background on hover.\n * @cssprop [--aparte-conv-item-bg-active=var(--aparte-surface-3)] - Background of the row matching `active-id`.\n * @cssprop [--aparte-conv-item-color-active=var(--aparte-text)] - Title colour of the active row.\n * @cssprop [--aparte-conv-item-font-weight-active=var(--aparte-font-weight-medium, 500)] - Title weight of the active row.\n * @cssprop [--aparte-conv-action-btn-size=20px] - Square size of the `⋯` button. Under `(pointer: coarse)` the stylesheet redeclares it as 28px 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-select-conversation', (e) => load(e.detail.id));\n * list.addEventListener('aparte-rename-conversation', (e) => manager.updateTitle(e.detail.id, e.detail.title));\n * list.addEventListener('aparte-pin-conversation', (e) => manager.pin(e.detail.id));\n * list.addEventListener('aparte-delete-conversation', (e) => manager.delete(e.detail.id));\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\n static get observedAttributes(): string[] {\n return ['active-id', 'no-groups'];\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._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 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 === 'no-groups' && 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 private _render(): void {\n // The menu and the rename input live inside the rows; a render replaces them.\n this._closeMenu();\n this._renaming = null;\n this.innerHTML = this._groups().map(g => this._renderGroup(g)).join('');\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 `no-groups`: a heading over the only group would say\n * nothing.\n */\n private _groups(): ConversationGroup[] {\n const items = this._conversations;\n if (this.hasAttribute('no-groups') || !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 private _confirmMarkup(conv: AparteConversationListItem): string {\n const question = escapeHtml(this._t('deleteConversationConfirm').replace('{title}', conv.title || this._t('newChat')));\n const cancel = escapeHtml(this._t('cancel'));\n const remove = escapeHtml(this._t('deleteConversation'));\n return `\n<div class=\"aparte-conv-menu__confirm\" role=\"group\" aria-label=\"${escapeAttr(question)}\">\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 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 window.removeEventListener('scroll', this._onWindowScroll, true);\n window.removeEventListener('resize', this._onWindowScroll);\n if (returnFocus && open.button.isConnected) open.button.focus();\n }\n\n private _onDocumentPointerDown = (e: Event): void => {\n const open = this._open;\n if (!open) return;\n const target = e.target as Node;\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 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 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 this.dispatchEvent(new CustomEvent<AparteConversationPinDetail>(\n 'aparte-unpin-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n } else {\n this.dispatchEvent(new CustomEvent<AparteConversationPinDetail>(\n 'aparte-pin-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n }\n return;\n case 'archive':\n this._closeMenu(true);\n if (conv.archivedAt) {\n this.dispatchEvent(new CustomEvent<AparteConversationArchiveDetail>(\n 'aparte-unarchive-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n } else {\n this.dispatchEvent(new CustomEvent<AparteConversationArchiveDetail>(\n 'aparte-archive-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n }\n return;\n case 'delete':\n // Ask first. The safe answer takes the focus.\n open.menu.innerHTML = this._confirmMarkup(conv); // safe-text: _confirmMarkup escapes the title and every locale string.\n this._placeMenu(open.button, open.menu);\n open.menu.querySelector<HTMLElement>('[data-menu-action=\"cancel\"]')?.focus();\n return;\n case 'cancel':\n this._closeMenu(true);\n return;\n case 'confirm-delete':\n this._closeMenu(true);\n this.dispatchEvent(new CustomEvent<AparteConversationDeleteDetail>(\n 'aparte-delete-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n return;\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 const index = items.indexOf(document.activeElement 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', () => this._finishRename(true, false));\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 private _finishRename(commit: boolean, viaKeyboard: 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 (viaKeyboard) {\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 if (commit && conv && title && title !== conv.title) {\n this.dispatchEvent(new CustomEvent<AparteConversationRenameDetail>(\n 'aparte-rename-conversation',\n { detail: { id: conv.id, title }, bubbles: true, composed: true }\n ));\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-select-conversation',\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';\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\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, and the example app used to\n * hand-roll them: four `<button class=\"chip\">`, a click handler, a CSS recipe of its\n * own. This element is that pattern 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 once sent 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 * Tier (a) of ratified decision #8: core honours the click alone, so it is live by\n * 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 - Hides the row (`hidden`) once its composer has sent\n * something. Remove the attribute, or `hidden`, to show it again.\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 style=\"flex: 1\"></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'];\n }\n\n private _suggestions: AparteSuggestion[] = [];\n private _composer: AparteComposer | null = null;\n\n private _onSend = (): void => {\n if (this.hasAttribute('empty-only')) this.hidden = true;\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 set suggestions(value: AparteSuggestion[]) {\n this._suggestions = 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 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 }\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 try {\n const parsed: unknown = JSON.parse(raw);\n this._suggestions = Array.isArray(parsed) ? parsed.filter(isSuggestion) : [];\n } catch {\n console.warn('[aparte-suggestions] `suggestions` is not a JSON array. Use [\"…\"] or [{\"label\": \"…\", \"prompt\": \"…\"}].');\n this._suggestions = [];\n }\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 }\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 composer.setValue(detail.prompt);\n if (this.getAttribute('mode') === 'fill') composer.focus();\n else composer.submit();\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`, reaching `danger` dispatches\n * `aparte-compact` for its chat — once, until the level drops again — which is what\n * a gauge that turns red and then does nothing was missing. 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} 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 reaching `danger` (once per crossing).\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 the gauge first reaches `danger`: compact this target's transcript. Once per crossing, 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 * @cssprop [--aparte-context-ring-stroke=4] - Thickness of the ring, in its own units (the ring is drawn on a 36-unit box).\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'];\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 `null` before the first turn. */\n get used(): number | null {\n return this._used;\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 /** 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(): void {\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';\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\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 (ratified decision #8, tier a).\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, vertically centred, and\n * hides it under a coarse pointer, where a 2px tick is not a target. Under two ticks it\n * renders nothing — a rail with one mark says nothing.\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=20px] - The rail's column, the ticks right-aligned in it.\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-gap=8px] - Space between ticks.\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></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 _intersections: IntersectionObserver | null = null;\n private _rebuildQueued = false;\n private _currentId: string | null = null;\n /** Visible ratio per message id, from the intersection observer. */\n private _visible = new Map<string, number>();\n\n private _onConfigChange = (): void => this._rebuild();\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.\n if (typeof MutationObserver !== 'undefined') {\n this._mutations = new MutationObserver(() => this._queueRebuild());\n this._mutations.observe(this._host, { childList: true, subtree: true, characterData: true });\n }\n this._rebuild();\n }\n\n private _detach(): void {\n this._mutations?.disconnect();\n this._mutations = null;\n this._intersections?.disconnect();\n this._intersections = null;\n this._visible.clear();\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 _rebuild(): void {\n const bubbles = this._bubbles();\n const locale = resolveConfig(this).getLocale();\n this.setAttribute('aria-label', locale.scrollRailLabel ?? 'Conversation outline');\n\n if (bubbles.length < 2) {\n this.setAttribute('data-empty', '');\n this.replaceChildren();\n this._intersections?.disconnect();\n this._intersections = null;\n this._visible.clear();\n this._currentId = null;\n return;\n }\n this.removeAttribute('data-empty');\n\n const list = document.createElement('ol');\n list.className = 'aparte-scroll-rail__list';\n const known = new Set<string>();\n for (const bubble of bubbles) {\n const id = bubble.getAttribute('message-id')!;\n known.add(id);\n const 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 const label = this._tickLabel(bubble);\n tick.setAttribute('aria-label', label);\n tick.title = label;\n if (id === this._currentId) tick.setAttribute('aria-current', 'true');\n item.appendChild(tick);\n list.appendChild(item);\n }\n this.replaceChildren(list);\n\n if (this._currentId && !known.has(this._currentId)) this._currentId = null;\n this._observe(bubbles);\n }\n\n /**\n * Which tick is current is read, not computed: the scroll surface is the root, and\n * the bubble with the largest visible share of a reading band near the top wins.\n * Scroll arithmetic was the alternative, and every scroll-position rule in the\n * viewport carries a regression note — this one has none to carry.\n */\n private _observe(bubbles: HTMLElement[]): void {\n this._intersections?.disconnect();\n this._intersections = null;\n this._visible.clear();\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(bubbles);\n }, { root, rootMargin: '-15% 0px -55% 0px', threshold: [0, 0.1, 0.25, 0.5, 0.75, 1] });\n for (const bubble of bubbles) this._intersections.observe(bubble);\n }\n\n private _pickCurrent(bubbles: HTMLElement[]): void {\n let best: string | null = null;\n let bestRatio = 0;\n for (const bubble of bubbles) {\n const id = bubble.getAttribute('message-id')!;\n const ratio = this._visible.get(id) ?? 0;\n if (ratio > bestRatio) {\n bestRatio = ratio;\n best = id;\n }\n }\n // Nothing in the band (a reply taller than the band, or the very bottom): keep\n // the last mark rather than clearing it.\n if (best !== null) this._setCurrent(best);\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) tick.setAttribute('aria-current', 'true');\n else tick.removeAttribute('aria-current');\n }\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 const index = ticks.indexOf(document.activeElement 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 */\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 viewport = this._viewport();\n const bubble = viewport?.querySelector<HTMLElement>(`aparte-chat-bubble[message-id=\"${cssEscape(messageId)}\"]`); // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n if (!bubble) return;\n const reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n bubble.scrollIntoView({ block: 'start', behavior: reduced ? 'auto' : 'smooth' });\n this._setCurrent(messageId);\n }\n}\n\nif (!customElements.get('aparte-scroll-rail')) {\n customElements.define('aparte-scroll-rail', AparteScrollRail);\n}\n","import { resolveConfig } from '../../config/index.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. `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 under 48rem. 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-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\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', 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 // 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 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 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 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 }\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 this.querySelector<HTMLElement>(FOCUSABLE)?.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 (e.key === 'Escape' && this.drawer && !this.collapsed) {\n e.preventDefault();\n this.close();\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 const control = (e.target as HTMLElement | null)?.closest?.<HTMLElement>('[data-aparte-sidebar-toggle]');\n if (!control) return;\n const named = control.getAttribute('data-aparte-sidebar-toggle');\n if (named) {\n if (named !== this.id) return;\n } else if (nearestSidebar(control) !== this) {\n return;\n }\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 * Hide the conversation rows whose title does not contain `query`, and the date\n * groups left empty. Case- and accent-insensitive. An empty query shows everything.\n */\n filter(query: string): void {\n const needle = fold(query);\n for (const list of this.querySelectorAll<HTMLElement>('aparte-conversation-list')) {\n for (const row of list.querySelectorAll<HTMLElement>('[data-conv-id]')) {\n const title = row.querySelector('.aparte-conv-item__title')?.textContent ?? row.textContent ?? '';\n row.hidden = needle !== '' && !fold(title).includes(needle);\n }\n for (const group of list.querySelectorAll<HTMLElement>('.aparte-conv-group')) {\n group.hidden = needle !== '' && !Array.from(group.querySelectorAll<HTMLElement>('[data-conv-id]')).some((r) => !r.hidden);\n }\n }\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/** 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/** The sidebar a toggle belongs to: the closest ancestor, else the first on the page. */\nfunction nearestSidebar(control: HTMLElement): Element | null {\n return control.closest('aparte-sidebar') ?? document.querySelector('aparte-sidebar');\n}\n\n/** Lower-case, accents stripped: \"Épingler\" matches \"epingler\". */\nfunction fold(text: string): string {\n return text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim();\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';\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 under its breakpoint and showing one pane. */\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} 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=20rem] - 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=4px] - The visible seam.\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></aparte-chat-viewport>\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input style=\"flex: 1\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n * <section class=\"aparte-split__pane\">\n * <iframe title=\"Preview\" style=\"inline-size: 100%; block-size: 100%; border: 0\" srcdoc=\"<h1>Your pane</h1>\"></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'];\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', 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', 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.classList.contains('aparte-split--only-start')\n || this.classList.contains('aparte-split--only-end')\n );\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 }\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 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 '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 is how tapping \"Preview\" on a phone used to write `position=\"0\"`\n * into the attribute, the event and the host's storage.\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) {\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 const control = (e.target 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 if ((control.closest('aparte-split') ?? document.querySelector('aparte-split')) !== this) {\n return;\n }\n this.showPane(which ?? (this.pane === 'start' ? 'end' : 'start'));\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. **Node resolves `index.node.ts` instead**\n * (via the `node` condition in package.json) — a DOM-free entry with the client,\n * host, transports, the chat handler and every type. This file sits first in the\n * exports map only because of the repo-local `@aparte-workspace/source` condition,\n * which is why reading it can look like \"this package can't run in Node\".\n * See the \"Node / SSR\" section of the README.\n *\n * @packageDocumentation\n */\nimport './styles/theme.css';\nimport './styles/base.css';\nimport './styles/button.css';\nimport './styles/field.css';\nimport './styles/display/avatar.css';\nimport './styles/display/icon.css';\nimport './styles/display/badge.css';\nimport './styles/display/tag.css';\nimport './styles/display/thumbnail.css';\nimport './styles/display/spinner.css';\nimport './styles/display/progress.css';\nimport './styles/display/skeleton.css';\nimport './styles/display/divider.css';\nimport './styles/display/alert.css';\nimport './styles/display/card.css';\nimport './styles/display/kbd.css';\nimport './styles/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 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 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 AparteChatResponse,\n AparteChatMessage,\n AparteContentPart,\n AparteTextPart,\n AparteImagePart,\n AparteFilePart,\n AparteStreamEvent,\n AparteStreamEventMap,\n AparteUsage,\n // Tool types\n AparteTool,\n AparteToolCall,\n AparteApprovalPolicy,\n AparteApprovalRuling,\n AparteToolResult,\n AparteToolHandler,\n AparteToolContext,\n AparteToolRenderer,\n AparteToolApprovalRequestDetail,\n // Canonical imperative surface (aliased by every wrapper's handle type).\n AparteChatImperativeApi,\n // The attribute surface of every element, for the wrappers to map over.\n AparteElementAttributes,\n AparteElementTagName,\n AparteAttrValue,\n AparteTemplateAttrs,\n AparteNoAttributes,\n AparteChatAttributes,\n AparteChatViewportAttributes,\n AparteChatBubbleAttributes,\n AparteChatStatusAttributes,\n AparteComposerAttributes,\n AparteComposerInputAttributes,\n AparteComposerActionAttributes,\n AparteComposerAddAttachmentAttributes,\n AparteComposerToolbarAttributes,\n AparteConversationListAttributes,\n AparteSelectAttributes,\n AparteOptionAttributes,\n AparteOptgroupAttributes,\n AparteProgressSpinnerAttributes,\n} from './types/index.js';\n\nexport { AparteErrorCode, AparteError, contentToText } from './types/index.js';\n\n// Export renderers\nexport {\n registerSegmentRenderer,\n unregisterSegmentRenderer,\n getSegmentRenderer,\n collectRendererStyles,\n registerDefaultRenderers,\n // The three the public barrel left behind. `renderers/index.ts` has always\n // exported all eight; this one published five, which made the registry\n // half-public: `declineDefaultRenderers` is the ONLY way to say \"do not install\n // the built-ins on this config\" without constructing an `AparteClient`\n // (`autoRegister: false`), and the bring-your-own-loop guide tells you not to\n // construct one. `installDefaultRenderersOnce` is what a hand-written bubble\n // needs, and `getAllRenderers` is the introspection half — the same reason\n // `hasHighlightProvider` and `renderMarkdown` are public.\n installDefaultRenderersOnce,\n declineDefaultRenderers,\n getAllRenderers\n} from './renderers/index.js';\n\n// Export components\nexport { AparteChat } from './components/index.js';\nexport { AparteChatBubble, populateBubbleFromMessage } from './components/index.js';\nexport type { SyncableBubble } from './components/index.js';\nexport { AparteChatStatus } from './components/index.js';\nexport { AparteChatViewport } from './components/index.js';\n\n// Export composer primitives\nexport { AparteComposer, AparteComposerInput, AparteComposerSend, AparteComposerCancel, AparteComposerAttachments, AparteComposerAddAttachment, AparteComposerAction, AparteComposerToolbar } from './components/index.js';\nexport type { AparteComposerEventMap, AparteComposerEventType, AparteComposerState, AparteComposerChangeEventDetail, AparteComposerPanelMode, AparteActionClickEventDetail } from './components/index.js';\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} from './conversations/index.js';\nexport { APARTE_CONVERSATION_SCHEMA_VERSION } from './conversations/index.js';\nexport { AparteConversationManager, type ConversationManagerOptions } from './conversations/index.js';\nexport {\n AparteConversationController,\n type AparteChatBinding,\n type AparteConversationControllerOptions,\n} from './conversations/index.js';\n\n// Export the framework-agnostic chat-host orchestrator (streaming/branch/\n// host-method layer that every framework wrapper binds to).\nexport {\n AparteChatHost,\n type AparteChatHostBinding,\n type AparteChatHostOptions,\n} from './host/index.js';\n\n// Export parsers\nexport { AparteStreamParser, parseMarkdownToSegments } 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, 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 type { AparteStreamRunEvent, AparteStreamRunEmitter, StreamAdapterTarget, CreateStreamAdapterOptions, AparteStreamRunner, AparteStreamRunOptions } from './client/stream-adapter.js';\n\n// Export transport seam (where chat requests go + how auth is handled)\nexport { AparteDirectTransport, AparteBackendTransport, createAparteChatHandler, isFormatAdapter } from './transport/index.js';\nexport type { AparteTransport, AparteTransportContext, AparteFormatAdapter, AparteVendorRequest, BackendTransportOptions, DirectTransportOptions, AparteChatHandlerOptions } from './transport/index.js';\n\n// Export runtime utilities\nexport { AparteMessageRepository } from './runtime/message-repository.js';\nexport type { ExportedMessageRepository } from './runtime/message-repository.js';\n\n// Export elicitation (human-in-the-loop typed input)\nexport { requestUserInput, buildElicitationPanel, buildApprovalPanel, AparteElicitationAbortError } from './elicitation/index.js';\nexport type {\n AparteElicitationSchema,\n AparteElicitationField,\n AparteElicitationEnumField,\n AparteElicitationBooleanField,\n AparteElicitationStringField,\n AparteElicitationObjectSchema,\n AparteElicitationRequest,\n AparteElicitationResult,\n AparteElicitationPresenter,\n AparteApprovalOption,\n AparteApprovalAnswer,\n BuiltApprovalPanel,\n BuiltElicitationPanel,\n} from './elicitation/index.js';\n\n// Export the default elicitation presenter Web Component\nexport { AparteElicitation } from './components/elicitation/aparte-elicitation.js';\n\n// 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 used to check four tags out of twenty-four and log \"Some components may not be\n * registered\", which is the two failures a guide cannot survive: a consumer whose\n * bundler dropped `<aparte-split>` got a silent green, and a consumer who got the\n * warning had no idea which element to import.\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 again 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 * Make sure every element core defines is in the registry, and say which are not.\n *\n * Importing `@aparte/core` already registers all of them. Call this when a bundler with\n * aggressive side-effect elimination, or a dynamic `import()`, might have dropped the\n * modules — the call itself is what keeps them, because it reaches `APARTE_ELEMENTS`.\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","panel","el","a","b","c","m","f","h","p","g","d","k","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/primitives/select/aparte-option.ts","../src/primitives/select/aparte-optgroup.ts","../src/primitives/select/aparte-select.ts","../src/primitives/progress-spinner/aparte-progress-spinner.ts","../src/primitives/icon/aparte-icon.ts","../src/components/elicitation/aparte-elicitation.ts","../src/components/chat/aparte-chat.ts","../src/components/bubble/aparte-chat-bubble.ts","../src/components/status/aparte-chat-status.ts","../src/components/viewport/aparte-chat-viewport.ts","../src/components/composer/aparte-composer.ts","../src/components/composer/aparte-composer-input.ts","../src/components/composer/aparte-composer-send.ts","../src/components/composer/aparte-composer-cancel.ts","../src/components/composer/aparte-composer-attachments.ts","../src/components/composer/aparte-composer-add-attachment.ts","../src/components/composer/aparte-composer-action.ts","../src/components/composer/aparte-composer-toolbar.ts","../src/components/conversation-list/aparte-conversation-list.ts","../src/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 * AparteOption\n * \n * Option element for aparte-select dropdown.\n *\n * One selectable row. Only meaningful inside `<aparte-select>` (directly, or nested in an\n * `<aparte-optgroup>`): the parent owns `selected` outright — on its first render, and on\n * every value change after it, it sets that attribute on the option whose `value` matches\n * its own and strips it from all the others. So `selected` written by hand does not\n * survive; set `value` on the select instead. Outside a select nothing selects it: it only\n * styles the row and sets `role=\"option\"` / `aria-selected` from its own attributes.\n *\n * It is not an `<option>`. It carries no form value, `disabled` blocks the click and the\n * keyboard walk but is not a form-disabled state, and when the `value` attribute is\n * absent the trimmed text content is used as the value instead.\n *\n * Keep the label one text node: the `label` property reads only the FIRST text node — that\n * is what keeps the injected status dot out of it — so wrapping the label in an element\n * makes `label` fall back to `value`. The select's own trigger label and its search filter\n * read the full `textContent`, so a wrapped label still displays and still matches.\n *\n * `data-status` is a rendering hook, not state core interprets: any non-empty value\n * appends an `aria-hidden` `.aparte-status-dot` span as the last child, and only\n * `ready`, `cached` and `not-downloaded` have a colour in the stylesheet — anything else\n * renders an uncoloured dot until you style it.\n *\n * @element aparte-option\n * @attr {string} value - Option value\n * @attr {boolean} disabled - Disabled state\n * @attr {boolean} selected - Selected state\n * @attr {string} data-status - Free-form status the host sets; styled, never read by core.\n *\n * @cssprop [--aparte-select-text=var(--aparte-text, #1e293b)] - Option text colour.\n * @cssprop [--aparte-select-option-hover=var(--aparte-surface-2, #f1f5f9)] - Background on hover, and for the keyboard-active row (`[data-active]`), which adds an inset `--aparte-primary` ring on top so the two are distinguishable.\n * @cssprop [--aparte-select-option-selected=color-mix(in srgb, var(--aparte-primary, #3b82f6) 18%, transparent)] - Background of the selected row. A tint by default: a solid accent fill with white text failed WCAG AA in both themes.\n * @cssprop [--aparte-select-option-selected-text=var(--aparte-select-text, var(--aparte-text, #1e293b))] - Text colour of the selected row. Set both this and the background to go back to a solid fill.\n *\n * @example\n * <aparte-select placeholder=\"Pick a model\" value=\"gpt-4o-mini\">\n * <aparte-option value=\"gpt-4o-mini\">GPT-4o mini</aparte-option>\n * <aparte-option value=\"o3\" disabled>o3 (no access)</aparte-option>\n * </aparte-select>\n */\n\nexport class AparteOption extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['value', 'disabled', 'selected', 'data-status'];\n }\n\n connectedCallback(): void {\n this.setAttribute('role', 'option');\n this._updateAriaSelected();\n this._updateStatusDot();\n }\n\n attributeChangedCallback(name: string): void {\n if (name === 'selected') {\n this._updateAriaSelected();\n }\n if (name === 'disabled') {\n this.setAttribute('aria-disabled', this.hasAttribute('disabled') ? 'true' : 'false');\n }\n if (name === 'data-status') {\n this._updateStatusDot();\n }\n }\n\n get value(): string {\n return this.getAttribute('value') || this.textContent?.trim() || '';\n }\n\n set value(val: string) {\n this.setAttribute('value', val);\n }\n\n get label(): string {\n // Use only the first text node, ignoring injected spans (e.g. status dot)\n const textNode = Array.from(this.childNodes).find(n => n.nodeType === Node.TEXT_NODE);\n return textNode?.textContent?.trim() || this.value;\n }\n\n get disabled(): boolean {\n return this.hasAttribute('disabled');\n }\n\n set disabled(val: boolean) {\n if (val) {\n this.setAttribute('disabled', '');\n } else {\n this.removeAttribute('disabled');\n }\n }\n\n get selected(): boolean {\n return this.hasAttribute('selected');\n }\n\n set selected(val: boolean) {\n if (val) {\n this.setAttribute('selected', '');\n } else {\n this.removeAttribute('selected');\n }\n }\n\n private _updateAriaSelected(): void {\n this.setAttribute('aria-selected', this.selected ? 'true' : 'false');\n }\n\n private _updateStatusDot(): void {\n const status = this.getAttribute('data-status');\n let dot = this.querySelector<HTMLSpanElement>('.aparte-status-dot');\n\n if (!status) {\n dot?.remove();\n return;\n }\n\n if (!dot) {\n dot = document.createElement('span');\n dot.className = 'aparte-status-dot';\n dot.setAttribute('aria-hidden', 'true');\n this.appendChild(dot);\n }\n\n dot.setAttribute('data-status', status);\n }\n}\n\n// Register\nif (!customElements.get('aparte-option')) {\n customElements.define('aparte-option', AparteOption);\n}\n","import { resolveConfig } from '../../config/config-context.js';\n\n/**\n * AparteOptgroup\n * \n * Option group element for aparte-select dropdown.\n *\n * A labelled band of options inside `<aparte-select>`. Presentational only: the group\n * holds no value, and collapsing it just sets `display: none` on its `<aparte-option>`\n * descendants — they stay in the DOM, which is what keeps the select's keyboard walk\n * skipping them, since it works off `display`. The label header is inserted before the\n * children and the loading row appended after them, so both live inside the group, and\n * neither is hidden when it collapses: only the options are.\n *\n * Two consequences of that worth knowing before you reach for it. The select's search\n * writes that same `display` property on every option in the select, so filtering can\n * reveal matches inside a collapsed group. And the header is built once, on the first\n * render that finds a `label`: changing `label` afterwards does not rewrite it — set the\n * label before inserting the group, or replace the group.\n *\n * `loading` is a display state, not a fetch. It appends a spinner row to the group and\n * nothing else happens — the host still owns the request. \"Fetch on expand\" is driven by\n * `aparte-optgroup-toggle`, whose `detail.collapsed` says which way the group just went;\n * the attribute and the options' `display` are already updated when it fires, since\n * setting the attribute runs `attributeChangedCallback` synchronously.\n *\n * @element aparte-optgroup\n * @attr {string} label - Group label\n * @attr {boolean} collapsible - Adds the chevron and the click handler to the header — so it needs a `label`, and it is read only when that header is first built.\n * @attr {boolean} collapsed - Collapsed state\n * @attr {boolean} loading - Appends a spinner row to the group; the options stay visible.\n *\n * @fires {CustomEvent<AparteOptgroupToggleEventDetail>} aparte-optgroup-toggle - The group was collapsed or expanded.\n *\n * @cssprop --aparte-text-muted - Colour of the group header label and of the loading row.\n * The shared theme token: there is no optgroup-specific override, so restyling one group's\n * header means setting this on that element.\n *\n * @example\n * <!-- Collapsed groups keep a long list readable; the label is the group's header. -->\n * <aparte-select grouped placeholder=\"Pick a model\">\n * <aparte-optgroup label=\"Ollama\" collapsible collapsed>\n * <aparte-option value=\"ollama::llama3\">Llama 3</aparte-option>\n * </aparte-optgroup>\n * <aparte-optgroup label=\"OpenRouter\" collapsible>\n * <aparte-option value=\"openrouter::gpt-4o-mini\">GPT-4o mini</aparte-option>\n * </aparte-optgroup>\n * </aparte-select>\n */\n\nexport class AparteOptgroup extends HTMLElement {\n /** Ids for the label span the group points `aria-labelledby` at. */\n private static _labelIdSeq = 0;\n\n static get observedAttributes(): string[] {\n return ['label', 'collapsible', 'collapsed', 'loading'];\n }\n\n connectedCallback(): void {\n this.setAttribute('role', 'group');\n this._render();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n if (name === 'collapsed') {\n this._updateCollapsedState();\n }\n\n if (this.isConnected) {\n this._render();\n }\n }\n\n get label(): string {\n return this.getAttribute('label') || '';\n }\n\n set label(val: string) {\n this.setAttribute('label', val);\n }\n\n get collapsible(): boolean {\n return this.hasAttribute('collapsible');\n }\n\n get collapsed(): boolean {\n return this.hasAttribute('collapsed');\n }\n\n set collapsed(val: boolean) {\n if (val) {\n this.setAttribute('collapsed', '');\n } else {\n this.removeAttribute('collapsed');\n }\n }\n\n get loading(): boolean {\n return this.hasAttribute('loading');\n }\n\n set loading(val: boolean) {\n if (val) this.setAttribute('loading', '');\n else this.removeAttribute('loading');\n }\n\n private _render(): void {\n // Only render header if we have a label\n if (this.label) {\n const existingHeader = this.querySelector('.aparte-optgroup-header');\n if (!existingHeader) {\n const header = document.createElement('div');\n header.className = 'aparte-optgroup-header';\n // The header stays a GENERIC node on purpose: naming it (it used to\n // carry `aria-label`) makes it a real element inside a `listbox`,\n // where only options and groups may live (axe:\n // aria-required-children, critical). The name belongs on the group,\n // via aria-labelledby below.\n // `label` is an attribute value — with the model-selector it carries a\n // provider-supplied name — so it goes through textContent, never\n // innerHTML (a hostile name would otherwise inject here).\n const labelSpan = document.createElement('span');\n labelSpan.className = 'aparte-optgroup-label';\n labelSpan.id = `aparte-optgroup-label-${++AparteOptgroup._labelIdSeq}`;\n labelSpan.textContent = this.label;\n this.setAttribute('aria-labelledby', labelSpan.id);\n header.appendChild(labelSpan);\n\n if (this.collapsible) {\n const chevron = document.createElement('span');\n chevron.className = 'aparte-optgroup-chevron';\n // The library's own `expand`, not a shape drawn here. This span was\n // painted as a CSS border-triangle — the second hand-drawn chevron in\n // core, built from PHYSICAL border sides where the tool row's used\n // logical ones: two markers, two constructions, which is the divergence\n // the icons rule was written after.\n chevron.innerHTML = resolveConfig(this).getIcon('expand');\n header.appendChild(chevron);\n header.style.cursor = 'pointer';\n header.addEventListener('click', (e) => {\n e.stopPropagation();\n this._toggleCollapse();\n });\n }\n\n this.insertBefore(header, this.firstChild);\n }\n }\n\n // Update loading state\n this._updateLoadingState();\n\n // Update collapsed state\n this._updateCollapsedState();\n }\n\n private _updateLoadingState(): void {\n let loader = this.querySelector('.aparte-optgroup-loader');\n if (this.loading) {\n if (!loader) {\n loader = document.createElement('div');\n loader.className = 'aparte-optgroup-loader';\n loader.innerHTML = '<span class=\"aparte-spinner-small\"></span> Fetching models...';\n this.appendChild(loader);\n }\n } else if (loader) {\n loader.remove();\n }\n }\n\n private _toggleCollapse(): void {\n this.collapsed = !this.collapsed;\n\n // Dispatch event before updating UI to allow parent to react (e.g. fetch data)\n this.dispatchEvent(new CustomEvent<AparteOptgroupToggleEventDetail>('aparte-optgroup-toggle', {\n bubbles: true,\n composed: true,\n detail: {\n label: this.label,\n collapsed: this.collapsed\n }\n }));\n\n this._updateCollapsedState();\n }\n\n private _updateCollapsedState(): void {\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n (opt as HTMLElement).style.display = this.collapsed ? 'none' : '';\n });\n }\n}\n\n// Register\nif (!customElements.get('aparte-optgroup')) {\n customElements.define('aparte-optgroup', AparteOptgroup);\n}\n\n/**\n * Detail payload for `aparte-optgroup-toggle`.\n *\n * `types/event-map.ts` types `aparte-optgroup-toggle` with this detail, and\n * `@aparte/plugin-model-selector` reads both of its fields, from another package,\n * through an untyped cast — while the event is published in the generated CEM event\n * table. It is a contract.\n *\n * @event aparte-optgroup-toggle\n */\nexport interface AparteOptgroupToggleEventDetail {\n /** The group's label. */\n label: string;\n /** `true` when the group just collapsed. */\n collapsed: boolean;\n}\n","import './aparte-option.js';\nimport './aparte-optgroup.js';\nimport { resolveConfig } from '../../config/config-context.js';\n\nexport interface AparteSelectChangeDetail {\n value: string;\n label: string;\n previousValue: string;\n}\n\n/**\n * Dropdown select for aparté — a vanilla web component with optional grouping, a\n * search filter and a keyboard-driven listbox.\n *\n * The element is light DOM and takes `<aparte-option>` / `<aparte-optgroup>` children, in\n * the order they should appear. On its first render it captures its children, keeps those\n * two kinds and moves them into the `role=\"listbox\"` container it builds, then rewrites\n * its own `innerHTML` — so any other child is dropped, and a wrapper element around your\n * options takes the options down with it: only DIRECT children are captured.\n *\n * Children written later are picked up by a `subtree` MutationObserver and moved into that\n * same container, and the keyboard highlight is re-asserted on the new elements. The move\n * EMPTIES the container first, so a later write replaces the list instead of adding to it:\n * write the whole list, not one option. Writing straight into `.aparte-select-options`\n * skips the move (the observer then only re-asserts the highlight), and that is the path\n * `@aparte/plugin-model-selector` takes to refresh a live list in place.\n *\n * It is not a form control: no `name`, no `multiple`, no participation in form submission.\n * It holds exactly one value and reports it through `aparte-select-change`.\n *\n * The dropdown is `position: fixed` and placed from script so it escapes an\n * `overflow: hidden` ancestor — which is why its stacking order is a variable\n * (`--aparte-select-z`) rather than a fixed rule, and why an `open` dropdown does not\n * scroll with the trigger.\n *\n * @element aparte-select\n * @attr {string} value - The selected option's value.\n * @attr {string} placeholder - Shown while nothing is selected.\n * @attr {boolean} disabled - Blocks opening the dropdown.\n * @attr {boolean} grouped - Observed, never read: `<aparte-optgroup>` children render as groups without it.\n * @attr {boolean} searchable - Adds a filter field above the options. Read on the first render only.\n * @attr {boolean} open - Reflects (and controls) whether the dropdown is open.\n *\n * @fires {CustomEvent<AparteSelectChangeDetail>} aparte-select-change - The selection changed; carries the new value, its label and the previous value.\n * @fires aparte-select-open - The dropdown opened. No detail.\n * @fires aparte-select-close - The dropdown closed. No detail.\n *\n * @cssprop [--aparte-select-bg=var(--aparte-surface-1, #fff)] - Trigger background. (The dropdown panel has its own token, `--aparte-select-dropdown-bg`, in both themes.)\n * @cssprop [--aparte-select-border=var(--aparte-border, #e2e8f0)] - Border of the trigger and of the dropdown.\n * @cssprop [--aparte-select-border-hover=var(--aparte-primary, #3b82f6)] - Trigger border on hover.\n * @cssprop [--aparte-select-border-focus=var(--aparte-primary, #3b82f6)] - Trigger border while focused.\n * @cssprop [--aparte-select-ring=rgba(59, 130, 246, 0.2)] - Colour of the 2px focus ring around the trigger.\n * @cssprop [--aparte-select-radius=0.5rem] - Corner radius of the trigger and the dropdown.\n * @cssprop [--aparte-select-text=var(--aparte-text, #1e293b)] - Colour of the trigger label (and of the options).\n * @cssprop [--aparte-select-chevron=var(--aparte-text-muted, #94a3b8)] - Colour of the chevron, which rotates 180° while open.\n * @cssprop [--aparte-select-dropdown-bg=var(--aparte-surface-1, #fff)] - Dropdown panel background, in both themes (`--aparte-surface-1` is dark-aware). It used to be read in the light theme only, the dark rule taking the TRIGGER's background instead — so a transparent trigger made the panel see-through in the dark.\n * @cssprop [--aparte-select-shadow=0 4px 12px rgba(0, 0, 0, 0.1)] - Dropdown panel shadow.\n * @cssprop [--aparte-select-z=1000] - `z-index` of the dropdown. It is `position: fixed`, so this is the one knob that decides whether it lands above the rest of your page.\n *\n * @example\n * <aparte-select placeholder=\"Pick a model\" searchable value=\"gpt-4o-mini\">\n * <aparte-option value=\"gpt-4o-mini\">GPT-4o mini</aparte-option>\n * <aparte-option value=\"llama-3.1-8b\">Llama 3.1 8B</aparte-option>\n * </aparte-select>\n *\n * <script>\n * document.querySelector('aparte-select').addEventListener('aparte-select-change', (e) => {\n * console.log(e.detail.value, e.detail.label, e.detail.previousValue);\n * });\n * </script>\n */\nexport class AparteSelect extends HTMLElement {\n private static _optIdSeq = 0;\n /** Fallback ids for the listbox `aria-controls` target when the host has no id. */\n private static _listboxSeq = 0;\n\n private _value = '';\n private _isOpen = false;\n private _activeIndex = -1;\n private _trigger: HTMLElement | null = null;\n private _dropdown: HTMLElement | null = null;\n private _searchInput: HTMLInputElement | null = null;\n private _observer: MutationObserver | null = null;\n\n // Bound handlers for cleanup\n /**\n * Bound, like its two neighbours below — because an inline arrow on `this` can\n * never be removed.\n *\n * `_setupEventListeners()` runs on EVERY `connectedCallback`, and `_render()`'s\n * idempotency guard means the host element survives a re-connect. So each move\n * of the element (a portal, a Vue teleport, any framework re-parent) added\n * another option-click listener: measured, one click fired the change handler\n * FIVE times after five re-connects. `disconnectedCallback` removed the two\n * document-level handlers and could not touch this one.\n *\n * This is verbatim the bug class `aparte-chat-viewport` documents having fixed\n * for its own listeners.\n */\n private _boundHandleOptionClick = this._handleOptionClick.bind(this);\n private _boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n private _boundHandleKeydown = this._handleKeydown.bind(this);\n\n static get observedAttributes(): string[] {\n return ['value', 'placeholder', 'disabled', 'grouped', 'searchable', 'open'];\n }\n\n connectedCallback(): void {\n this._value = this.getAttribute('value') || '';\n this._isOpen = this.hasAttribute('open');\n this._render();\n this._setupEventListeners();\n this._setupMutationObserver();\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('click', this._boundHandleOptionClick);\n document.removeEventListener('click', this._boundHandleDocumentClick);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n this._observer?.disconnect();\n }\n\n attributeChangedCallback(name: string, oldValue: string, newValue: string): void {\n if (!this.isConnected) return;\n\n if (name === 'value' && oldValue !== newValue && newValue !== this._value) {\n this._value = newValue || '';\n this._updateTriggerLabel();\n }\n if (name === 'open') {\n this._isOpen = this.hasAttribute('open');\n if (this._isOpen) {\n this._dropdown?.removeAttribute('hidden');\n this._searchInput?.focus();\n } else {\n this._dropdown?.setAttribute('hidden', '');\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public API\n // ─────────────────────────────────────────────────────────────────────────\n\n get value(): string {\n return this._value;\n }\n\n set value(val: string) {\n if (val === this._value) return;\n const previousValue = this._value;\n this._value = val;\n this.setAttribute('value', val);\n this._updateTriggerLabel();\n this._emitChange(previousValue);\n }\n\n get open(): boolean {\n return this._isOpen;\n }\n\n set open(val: boolean) {\n if (val) {\n this.setAttribute('open', '');\n } else {\n this.removeAttribute('open');\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Rendering\n // ─────────────────────────────────────────────────────────────────────────\n\n private _render(): void {\n const placeholder = this.getAttribute('placeholder') || 'Select...';\n const searchable = this.hasAttribute('searchable');\n\n // Check if already rendered (has dropdown structure)\n if (this.querySelector('.aparte-select-dropdown')) {\n this._updateTriggerLabel();\n return;\n }\n\n // First render: capture any slotted children before modifying DOM\n const slottedChildren = Array.from(this.children);\n // 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 trigger.setAttribute('role', 'combobox');\n trigger.setAttribute('aria-haspopup', 'listbox');\n trigger.setAttribute('aria-expanded', 'false');\n // Accessible name (axe aria-input-field-name): the visible label span is\n // the combobox VALUE, not its name — name it from the host's aria-label\n // when provided, else the placeholder (\"Select model…\" etc.).\n trigger.setAttribute('aria-label', this.getAttribute('aria-label') || placeholder);\n // The label is placeholder text (consumer/attribute-supplied) → textContent,\n // same path as _updateTriggerLabel(), never innerHTML. Only the static SVG\n // chevron uses innerHTML.\n const labelSpan = document.createElement('span');\n labelSpan.className = 'aparte-select-label';\n // 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 if (searchable) {\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 dropdown.appendChild(searchInput);\n }\n\n const optionsContainer = document.createElement('div');\n optionsContainer.className = 'aparte-select-options';\n optionsContainer.setAttribute('role', 'listbox');\n // A listbox is an ARIA input field, so it needs its own name; reuse the\n // combobox's (axe: aria-input-field-name).\n optionsContainer.setAttribute('aria-label', trigger.getAttribute('aria-label') ?? placeholder);\n // `role=\"combobox\"` REQUIRES aria-controls (axe: aria-required-attr).\n optionsContainer.id = this.id ? `${this.id}-listbox` : `aparte-listbox-${++AparteSelect._listboxSeq}`;\n trigger.setAttribute('aria-controls', optionsContainer.id);\n\n // Move slotted children (aparte-option, aparte-optgroup) into options container\n slottedChildren.forEach(child => {\n if (child.tagName === 'APARTE-OPTION' || child.tagName === 'APARTE-OPTGROUP') {\n optionsContainer.appendChild(child);\n }\n });\n\n dropdown.appendChild(optionsContainer);\n\n // Clear and rebuild DOM\n this._observer?.disconnect();\n this.innerHTML = '';\n this.appendChild(trigger);\n this.appendChild(dropdown);\n\n this._trigger = trigger;\n this._dropdown = dropdown;\n this._searchInput = dropdown.querySelector('.aparte-select-search');\n\n if (this.isConnected) {\n this._setupMutationObserver();\n }\n\n // Update label based on current value\n this._updateTriggerLabel();\n }\n\n private _setupMutationObserver(): void {\n this._observer = new MutationObserver(() => {\n this._updateDropdownContent();\n // 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: the highlight disappeared,\n * `aria-activedescendant` kept pointing at an id no longer in the document, and\n * the next ArrowDown moved from the stale index — skipping an option. Only when\n * open and only when a position was held, so a refresh never invents one.\n */\n private _restoreActive(): void {\n if (!this._isOpen || this._activeIndex < 0) return;\n if (this.querySelector('aparte-option[data-active]')) return;\n this._setActive(this._activeIndex);\n }\n\n private _updateDropdownContent(): void {\n // If trigger is gone, the component was likely wiped by innerHTML\n if (!this._trigger || !this.contains(this._trigger)) {\n this._render();\n return;\n }\n\n const optionsContainer = this.querySelector('.aparte-select-options');\n if (!optionsContainer) {\n // If internal UI is present but container is gone\n this._render();\n return;\n }\n\n // Collect all potential options from light DOM\n // (those aren't internal UI elements)\n const lightChildren = Array.from(this.children).filter(child =>\n child.className !== 'aparte-select-trigger' &&\n child.className !== 'aparte-select-dropdown'\n );\n\n if (lightChildren.length === 0) return;\n\n // Pause observer to prevent self-triggering loop\n this._observer?.disconnect();\n\n // Clear container (but keep internal stuff if any)\n optionsContainer.innerHTML = '';\n\n // Move/Append children to container\n lightChildren.forEach(child => {\n optionsContainer.appendChild(child);\n });\n\n // Resume observer\n if (this.isConnected) {\n this._observer?.observe(this, { childList: true, subtree: true });\n }\n\n this._updateTriggerLabel();\n this._restoreActive();\n }\n\n private _setupEventListeners(): void {\n // Trigger click\n this._trigger?.addEventListener('click', () => this._toggle());\n\n // Trigger keyboard\n this._trigger?.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n // When open, Enter/Space selects the active option — handled by\n // the document-level nav handler. Only toggle when closed, so we\n // don't close the dropdown on the very keystroke meant to select.\n if (this._isOpen) return;\n e.preventDefault();\n this._toggle();\n }\n if (e.key === 'ArrowDown' && !this._isOpen) {\n e.preventDefault();\n this._openDropdown();\n }\n });\n\n // Option selection. Removed first: `_setupEventListeners` runs on every\n // connect, and adding the same bound reference twice is a no-op per spec —\n // but being explicit costs nothing and survives a future refactor that\n // rebinds.\n this.removeEventListener('click', this._boundHandleOptionClick);\n this.addEventListener('click', this._boundHandleOptionClick);\n\n // Search filter\n this._searchInput?.addEventListener('input', (e) => {\n const query = (e.target as HTMLInputElement).value.toLowerCase();\n this._filterOptions(query);\n });\n\n // Close on outside click\n document.addEventListener('click', this._boundHandleDocumentClick);\n\n // Keyboard navigation\n document.addEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _handleOptionClick(e: Event): void {\n const option = (e.target as HTMLElement).closest('aparte-option');\n if (option && !option.hasAttribute('disabled')) {\n this._selectOption(option as HTMLElement);\n }\n }\n\n private _handleDocumentClick(e: Event): void {\n if (!this.contains(e.target as Node)) {\n this._closeDropdown();\n }\n }\n\n private _handleKeydown(e: KeyboardEvent): void {\n if (!this._isOpen) return;\n\n // Home/End move the caret when typing in the search box; only hijack\n // them for option navigation when focus is not in the search field.\n const inSearch = document.activeElement === this._searchInput;\n\n switch (e.key) {\n case 'Escape':\n e.preventDefault();\n this._closeDropdown();\n this._trigger?.focus();\n break;\n case 'ArrowDown':\n e.preventDefault();\n this._moveActive(1);\n break;\n case 'ArrowUp':\n e.preventDefault();\n this._moveActive(-1);\n break;\n case 'Home':\n if (inSearch) break;\n e.preventDefault();\n this._setActive(0);\n break;\n case 'End':\n if (inSearch) break;\n e.preventDefault();\n this._setActive(this._visibleOptions().length - 1);\n break;\n case 'Enter': {\n const active = this._visibleOptions()[this._activeIndex];\n if (active) {\n e.preventDefault();\n this._selectOption(active);\n }\n break;\n }\n }\n }\n\n /** Non-disabled, non-filtered options in DOM order. */\n private _visibleOptions(): HTMLElement[] {\n return Array.from(this.querySelectorAll<HTMLElement>('aparte-option')).filter(\n opt => !opt.hasAttribute('disabled') && opt.style.display !== 'none',\n );\n }\n\n /** Move the active (keyboard-highlighted) option by `delta`, clamped. */\n private _moveActive(delta: number): void {\n const opts = this._visibleOptions();\n if (opts.length === 0) return;\n const base = this._activeIndex < 0 ? (delta > 0 ? -1 : 0) : this._activeIndex;\n this._setActive(base + delta);\n }\n\n /** Highlight the option at `index` (clamped) and point aria-activedescendant at it. */\n private _setActive(index: number): void {\n const all = this.querySelectorAll<HTMLElement>('aparte-option');\n all.forEach(o => o.removeAttribute('data-active'));\n\n const opts = this._visibleOptions();\n if (opts.length === 0) {\n this._activeIndex = -1;\n this._trigger?.removeAttribute('aria-activedescendant');\n return;\n }\n const clamped = Math.max(0, Math.min(index, opts.length - 1));\n this._activeIndex = clamped;\n\n const active = opts[clamped]!;\n if (!active.id) active.id = `aparte-option-${++AparteSelect._optIdSeq}`;\n active.setAttribute('data-active', '');\n this._trigger?.setAttribute('aria-activedescendant', active.id);\n active.scrollIntoView?.({ block: 'nearest' });\n }\n\n /** Clear the keyboard highlight (on close). */\n private _clearActive(): void {\n this._activeIndex = -1;\n this._trigger?.removeAttribute('aria-activedescendant');\n this.querySelectorAll('aparte-option').forEach(o => o.removeAttribute('data-active'));\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Actions\n // ─────────────────────────────────────────────────────────────────────────\n\n private _toggle(): void {\n if (this._isOpen) {\n this._closeDropdown();\n } else {\n this._openDropdown();\n }\n }\n\n private _openDropdown(): void {\n if (this.hasAttribute('disabled')) return;\n\n this._isOpen = true;\n this._dropdown?.removeAttribute('hidden');\n this._trigger?.setAttribute('aria-expanded', 'true');\n this.setAttribute('open', '');\n\n // Smart Positioning\n this._updatePosition();\n\n // Focus search if available\n this._searchInput?.focus();\n\n // Seed the keyboard highlight on the current selection (or the first\n // option) so ArrowUp/Down have an anchor and screen readers announce it.\n const opts = this._visibleOptions();\n const selectedIdx = opts.findIndex(o => o.getAttribute('value') === this._value);\n this._setActive(selectedIdx >= 0 ? selectedIdx : 0);\n\n this.dispatchEvent(new CustomEvent('aparte-select-open', { bubbles: true }));\n }\n\n private _closeDropdown(): void {\n this._isOpen = false;\n this._clearActive();\n this._dropdown?.setAttribute('hidden', '');\n this._trigger?.setAttribute('aria-expanded', 'false');\n this.removeAttribute('open');\n this.removeAttribute('position'); // Reset position attribute\n\n // Clear position styles set by _updatePosition\n if (this._dropdown) {\n this._dropdown.style.top = '';\n this._dropdown.style.bottom = '';\n this._dropdown.style.left = '';\n this._dropdown.style.width = '';\n }\n\n // Clear search\n if (this._searchInput) {\n this._searchInput.value = '';\n this._filterOptions('');\n }\n\n this.dispatchEvent(new CustomEvent('aparte-select-close', { bubbles: true }));\n }\n\n private _updatePosition(): void {\n if (!this._dropdown || !this._trigger) return;\n\n const rect = this._trigger.getBoundingClientRect();\n const dropdownHeight = this._dropdown.offsetHeight || 300;\n const viewportHeight = window.innerHeight;\n const spaceBelow = viewportHeight - rect.bottom;\n const GAP = 4; // px gap between trigger and dropdown\n\n // Always size to trigger width\n this._dropdown.style.left = `${rect.left}px`;\n this._dropdown.style.width = `${rect.width}px`;\n\n // Decide whether to open upward or downward\n if (spaceBelow < dropdownHeight && rect.top > dropdownHeight) {\n // Open upward\n this._dropdown.style.top = '';\n this._dropdown.style.bottom = `${viewportHeight - rect.top + GAP}px`;\n this.setAttribute('position', 'top');\n } else {\n // Open downward\n this._dropdown.style.top = `${rect.bottom + GAP}px`;\n this._dropdown.style.bottom = '';\n this.removeAttribute('position');\n }\n }\n\n private _selectOption(option: HTMLElement): void {\n const value = option.getAttribute('value') || option.textContent?.trim() || '';\n const previousValue = this._value;\n\n this._value = value;\n this.setAttribute('value', value);\n this._updateTriggerLabel();\n this._closeDropdown();\n this._emitChange(previousValue);\n this._trigger?.focus();\n }\n\n private _updateTriggerLabel(): void {\n // 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) => o.textContent?.trim() || '')];\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 // Update selected state on options\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n const isSelected = opt.getAttribute('value') === this._value;\n if (isSelected) {\n opt.setAttribute('selected', '');\n } else {\n opt.removeAttribute('selected');\n }\n });\n }\n\n private _getSelectedLabel(): string {\n // Match by property, not an interpolated attribute selector — a value with\n // `\"`/`]` (e.g. a remote model id) would make querySelector throw SyntaxError.\n for (const opt of this.querySelectorAll('aparte-option')) {\n if (opt.getAttribute('value') === this._value) return opt.textContent?.trim() || '';\n }\n return '';\n }\n\n private _filterOptions(query: string): void {\n const options = this.querySelectorAll('aparte-option');\n options.forEach(opt => {\n const label = opt.textContent?.toLowerCase() || '';\n const matches = label.includes(query);\n (opt as HTMLElement).style.display = matches ? '' : 'none';\n });\n // Re-anchor the keyboard highlight on the first still-visible option.\n if (this._isOpen) this._setActive(0);\n }\n\n private _emitChange(previousValue: string): void {\n const detail: AparteSelectChangeDetail = {\n value: this._value,\n label: this._getSelectedLabel(),\n previousValue\n };\n\n this.dispatchEvent(new CustomEvent<AparteSelectChangeDetail>('aparte-select-change', {\n bubbles: true,\n composed: true,\n detail\n }));\n }\n}\n\n// Register\nif (!customElements.get('aparte-select')) {\n customElements.define('aparte-select', AparteSelect);\n}\n","/**\n * AparteProgressSpinner\n *\n * Circular progress spinner web component.\n * - Indeterminate (no `value` attribute): continuous rotation animation\n * - Determinate (`value=\"0–100\"`): fills the arc proportionally\n *\n * The ABSENCE of the attribute is what selects indeterminate, so `value=\"\"` is not\n * \"unknown progress\" — it parses to 0, i.e. an empty determinate arc. `value` is clamped\n * to 0–100 and anything non-numeric reads as 0; nothing throws.\n *\n * It renders its own SVG into itself on connect and on every `value` change, so it takes\n * no children: whatever you put inside is overwritten. The SVG is `aria-hidden` and the\n * ARIA lives on the host (`role=\"progressbar\"`, `aria-valuemin`/`aria-valuemax`, plus\n * `aria-valuenow` only when determinate) — there is no accessible NAME, so give the\n * element an `aria-label` unless the surrounding text already says what is loading.\n *\n * It draws an arc; it does not manage a loading lifecycle — no delay before appearing, no\n * timeout, no label, no live announcement. Under `prefers-reduced-motion: reduce` the\n * rotation stops (aparte.css scopes that rule to the library's own elements), which is the\n * other reason the indeterminate arc must not be the only signal that work is in flight.\n *\n * @element aparte-progress-spinner\n * @attr {number} value - Progress percentage 0–100 (omit for indeterminate)\n *\n * @cssprop [--aparte-spinner-size=16px] - Width and height of the element; the SVG fills it.\n * @cssprop [--aparte-spinner-stroke=2.5] - Stroke width of both arcs, in the units of the 24×24 viewBox.\n * @cssprop [--aparte-spinner-color=currentColor] - Stroke of the filled (progress) arc.\n * @cssprop [--aparte-spinner-track=color-mix(in srgb, currentColor 15%, transparent)] - Stroke of the track arc behind it.\n *\n * @example\n * <!-- Omit `value` for the indeterminate spin; set it to show real progress. -->\n * <aparte-progress-spinner></aparte-progress-spinner>\n * <aparte-progress-spinner value=\"62\"></aparte-progress-spinner>\n */\nexport class AparteProgressSpinner extends HTMLElement {\n static get observedAttributes(): string[] { return ['value']; }\n\n /** Radius of the SVG circle (viewBox is 0 0 24 24, center at 12,12) */\n private readonly _r = 9;\n private get _circ(): number { return 2 * Math.PI * this._r; }\n\n connectedCallback(): void { this._render(); }\n attributeChangedCallback(): void { this._render(); }\n\n private _render(): void {\n const raw = this.getAttribute('value');\n const value = raw !== null\n ? Math.min(100, Math.max(0, parseFloat(raw) || 0))\n : null;\n\n this.setAttribute('role', 'progressbar');\n this.setAttribute('aria-valuemin', '0');\n this.setAttribute('aria-valuemax', '100');\n if (value !== null) {\n this.setAttribute('aria-valuenow', String(value));\n } else {\n this.removeAttribute('aria-valuenow');\n }\n\n // Determinate: dashoffset shrinks from circ→0 as value goes 0→100\n const dashoffset = value !== null ? this._circ * (1 - value / 100) : 0;\n // Indeterminate: fixed partial arc (~72% of circumference)\n const dasharray = value !== null\n ? `${this._circ.toFixed(2)}`\n : `${(this._circ * 0.72).toFixed(2)} ${(this._circ * 0.28).toFixed(2)}`;\n\n this.innerHTML = `<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><circle class=\"aparte-spinner-track\" cx=\"12\" cy=\"12\" r=\"${this._r}\"/><circle class=\"aparte-spinner-fill\" cx=\"12\" cy=\"12\" r=\"${this._r}\" stroke-dasharray=\"${dasharray}\" stroke-dashoffset=\"${dashoffset.toFixed(2)}\"/></svg>`;\n }\n}\n\nif (!customElements.get('aparte-progress-spinner')) {\n customElements.define('aparte-progress-spinner', AparteProgressSpinner);\n}\n","import { resolveConfig } from '../../config/config-context.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\nimport type { AparteIconName } from '../../config/icon-provider.js';\n\n/**\n * AparteIcon\n *\n * The library's icon set, reachable from MARKUP.\n *\n * It existed only in JavaScript. Core ships 25 glyphs and `setIconProvider` is sold as the\n * lever that swaps them, but the only door in was `getIcon(name)` — so a consumer writing\n * plain HTML could not place one, and the icon provider could not reach a single icon that\n * consumer wrote themselves. `<aparte-composer-action>`'s own documentation tells you to\n * put an `<svg>` inside it, which is the same gap stated as an instruction.\n *\n * That gap is why every example on the CSS-classes reference carried 265 characters of\n * path data to demonstrate a 60-character class: there was no shorter way to say \"an icon\n * goes here\" that actually drew one. `<aparte-icon name=\"copy\">` is that way.\n *\n * It routes through `getIcon`, so it is not a second icon mechanism — it is a markup\n * entrance to the one that exists. Register a provider and every `<aparte-icon>` on the\n * page follows, including the ones in your own templates.\n *\n * ONE CONSEQUENCE, stated because it is the real cost: the 25 glyph NAMES become public\n * API. `expand`, `copy`, `nextBranch` were internal identifiers; renaming one now breaks a\n * consumer's markup.\n *\n * It renders into itself and takes no children — whatever you put inside is replaced. The\n * SVG is `aria-hidden`, because an icon beside a label is decoration; when the icon IS the\n * button's only content, name the BUTTON (`aria-label`), not this.\n *\n * @element aparte-icon\n * @attr {string} name - Which glyph to draw. One of the names `setIconProvider` accepts;\n * an unknown name draws nothing rather than a broken-image box.\n *\n * @cssprop [--aparte-icon-size=16px] - Width and height. `--sm`/`--lg`/`--xl` set it.\n *\n * @example\n * <aparte-icon name=\"copy\"></aparte-icon>\n * <aparte-icon name=\"check\" class=\"aparte-icon--lg\"></aparte-icon>\n * <button class=\"aparte-btn aparte-btn--icon\" aria-label=\"Copy\">\n * <aparte-icon name=\"copy\"></aparte-icon>\n * </button>\n */\nexport class AparteIcon extends HTMLElement {\n static get observedAttributes(): string[] { return ['name']; }\n\n private _unsubscribe: (() => void) | null = null;\n\n connectedCallback(): void {\n this._render();\n /*\n * A provider registered AFTER this element mounted still reaches it. Without\n * this the icons already on the page kept the built-in glyph while everything\n * rendered later got the consumer's — the same split `<aparte-composer-send>`\n * subscribes to avoid.\n */\n this._unsubscribe = subscribeConfigChange(this, () => this._render());\n }\n\n disconnectedCallback(): void {\n this._unsubscribe?.();\n this._unsubscribe = null;\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this._render();\n }\n\n private _render(): void {\n const name = this.getAttribute('name');\n /*\n * `getIcon` is typed to the known names and falls back per name, so an unknown\n * one would land on `undefined` and print it. Drawing NOTHING is the honest\n * failure: a misspelled name leaves a gap the author can see, where the string\n * \"undefined\" in a button would read as a rendering bug in the library.\n */\n const glyph = name ? resolveConfig(this).getIconProvider()[name as AparteIconName]?.() : null;\n this.innerHTML = glyph ?? '';\n // Decoration by default — see the class note above for when to name what instead.\n this.firstElementChild?.setAttribute('aria-hidden', 'true');\n }\n}\n\nif (!customElements.get('aparte-icon')) {\n customElements.define('aparte-icon', AparteIcon);\n}\n","/**\n * <aparte-elicitation> — the default elicitation presenter.\n *\n * Registers itself as the presenter for the config governing its subtree\n * (`resolveConfig(this)`), so `requestUserInput()` from a tool handler is routed\n * here WITHOUT any window events — the typed presenter contract replaces the\n * stringly-typed `aparte-ask-user-*` events that drifted in Phase 1.\n *\n * On a request it builds the schema-appropriate panel (enum / boolean / string /\n * object) and mounts it inside the nearest `<aparte-composer>` via its\n * `showPanel` API, resolving:\n * - accept — the send button (panel submit), when all fields are complete\n * - decline — the inline \"Skip\" affordance\n * - cancel — the assistant turn was stopped/errored while pending\n *\n * Its CSS ships in `@aparte/core/styles.css` like every other component — it used\n * to inject its own <style> from here, which made it the one surface that could not\n * be themed and whose variables were missing from the generated CSS reference.\n *\n * Place anywhere inside the chat (it renders nothing itself):\n * <aparte-elicitation></aparte-elicitation>\n */\n\nimport { resolveConfig, runWithConfig, type AparteConfigAware } from '../../config/config-context.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\nimport type { AparteConfig } from '../../config/aparte-config.js';\nimport type { AparteComposer, AparteComposerPanelMode } from '../composer/aparte-composer.js';\nimport { buildElicitationPanel, type BuiltElicitationPanel } from '../../elicitation/panel.js';\nimport { buildApprovalPanel } from '../../elicitation/approval-panel.js';\nimport type { AparteElicitationRequest, AparteElicitationResult, AparteElicitationPresenter } from '../../elicitation/types.js';\nimport { AparteElicitationAbortError } from '../../elicitation/types.js';\n\n/**\n * The slice of the composer this presenter drives.\n *\n * DERIVED from the component rather than re-typed by hand. It used to be a literal\n * copy of the three signatures, which is a twin: adding a parameter to the real\n * `setPanelSubmitEnabled` left this one behind, and the compiler pointed at the\n * CALLER instead of the stale declaration. `import type` is erased, so nothing here\n * pulls the composer element into a runtime import — which is why the copy existed.\n */\ntype ComposerEl = HTMLElement & Pick<AparteComposer, 'showPanel' | 'hidePanel' | 'setPanelSubmitEnabled'>;\n\ninterface Pending {\n /** End the request without an answer — see `AparteElicitationAbortError`. */\n abort(): void;\n composer: ComposerEl;\n /**\n * Re-apply every string this request took from the locale, in place.\n *\n * ONE function, where this used to hold the panel and the skip button separately\n * so the component could relabel each. That only worked because there was one kind\n * of panel; an approval's strings live elsewhere, and a second field per kind is\n * how the two would drift.\n */\n relabel(): void;\n}\n\n/**\n * The default presenter for a request to the human. It renders nothing itself: it\n * registers as the presenter for the config governing its subtree, and mounts a panel\n * inside the nearest `<aparte-composer>` when something asks — a tool handler calling\n * `requestUserInput`, or core's own approval gate.\n *\n * It dispatches no events on purpose. A request is answered through the typed presenter\n * contract, not by listening for one; the `aparte-tool-decision` event this replaced\n * existed only because the buttons used to live in a segment renderer with no reference\n * to the client.\n *\n * It has no children to project: `connectedCallback` sets\n * `display: none`, and the composer it presents in is found by walking UP from\n * `this.parentElement` — anything placed inside this element is only hidden with it.\n * So its position matters but its content does not: mount it anywhere inside the\n * `<aparte-chat>` whose questions it should answer.\n *\n * Not the element to reach for when you want a question UI of your own shape. It is\n * one caller of `setElicitationPresenter`, and among presenters registered for the\n * same chat the most recent one wins — so an app with a framework-native presenter\n * registers that and does not mount this, and a second `<aparte-elicitation>` in one\n * chat is redundant rather than additive. It is also not usable outside a chat that\n * has an `<aparte-composer>`: with nowhere to mount a panel the request is REJECTED,\n * on purpose, rather than borrowed into another chat's composer.\n *\n * The panel it mounts is styled by `@aparte/core/styles.css` — the `--aparte-elic-*`\n * and `--aparte-approval-*` knobs below theme it. They are declared here rather than\n * on the composer because this presenter is what builds the panel; the composer only\n * lends it the slot.\n *\n * @element aparte-elicitation\n *\n * @cssprop [--aparte-elic-gap=6px] - Vertical gap between the panel's rows (message, body, tabs).\n * @cssprop [--aparte-elic-padding=6px 4px] - Padding inside the panel.\n * @cssprop [--aparte-elic-max-height=50vh] - Cap on the panel's height; its body scrolls, the panel does not.\n * @cssprop [--aparte-elic-field-gap=8px] - Space and separator padding between two fields of an object schema.\n * @cssprop [--aparte-elic-message-size=0.82rem] - Font size of the question text at the top of the panel.\n * @cssprop [--aparte-elic-title-size=0.8rem] - Font size of a field's title.\n * @cssprop [--aparte-elic-desc-size=0.76rem] - Font size of a field's description.\n * @cssprop [--aparte-elic-option-padding=7px 10px] - Padding of one enum/boolean option row.\n * @cssprop [--aparte-elic-option-radius=8px] - Corner radius of an option row.\n * @cssprop [--aparte-elic-option-title-size=0.875rem] - Font size of an option's label, and of the text inputs.\n * @cssprop [--aparte-elic-option-desc-size=0.78rem] - Font size of an option's secondary line.\n * @cssprop [--aparte-elic-control-size=15px] - Size of the radio/checkbox control in an option row.\n * @cssprop [--aparte-elic-input-radius=6px] - Corner radius of the text inputs and of the Skip button.\n * @cssprop [--aparte-elic-textarea-min-height=64px] - Minimum height of a multi-line string field.\n * @cssprop [--aparte-elic-input-size=0.85rem] - Font size of the approval panel's instruction field (the free-text note the user writes).\n * @cssprop [--aparte-elic-skip-size=0.8rem] - Font size of the corner \"Skip\" affordance (the decline).\n * @cssprop [--aparte-elic-step-size=0.78rem] - Font size of a step tab, when the schema is asked one field at a time.\n * @cssprop [--aparte-elic-step-padding=4px 2px] - Padding of a step tab.\n * @cssprop [--aparte-elic-step-gap=14px] - Gap between step tabs.\n * @cssprop [--aparte-elic-step-underline=2px] - Thickness of the current step's underline (a tab, not a pill).\n * @cssprop [--aparte-elic-dismiss-room=72px] - Space kept clear at the end of the tab rail for the corner escape.\n * @cssprop [--aparte-approval-gap=4px] - Gap between the stacked options of an approval request.\n * @cssprop [--aparte-approval-option-size=0.85rem] - Font size of an approval option button.\n * @cssprop [--aparte-approval-option-padding=8px 10px] - Padding of an approval option button.\n * @cssprop [--aparte-approval-option-radius=8px] - Corner radius of an approval option button.\n * @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-row\">\n * <aparte-composer-input></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\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` and had no instance filter at all, so a\n * Stop (or an error) in one chat cancelled the question a DIFFERENT chat was\n * waiting on — and that chat's model was told the user had refused a question\n * the user was still looking at. Same defect as the `compact()` handler that\n * emptied both chats, which its four sibling handlers in `AparteClient` already\n * guarded against.\n *\n * The leniency rule is the composer's, deliberately: an event with no\n * `targetId` is for everyone (a single-chat app never sets one), and a chat we\n * cannot identify accepts everything rather than becoming deaf. Only a\n * MISMATCH is ignored.\n */\n private _onTurnEnd = (e: Event): void => {\n const evtTargetId = (e as CustomEvent).detail?.targetId as string | undefined;\n const own = this._pendingTargetId();\n if (evtTargetId && own && evtTargetId !== own) return;\n this._cancelPending();\n };\n\n private _unsubscribeConfig: (() => void) | null = null;\n\n connectedCallback(): void {\n this.style.display = 'none';\n // A language switch while a question is OPEN. Every other live-config\n // consumer in core got this seam; the panel could not use it because it kept\n // no reference to itself — see `Pending.panel`.\n this._unsubscribeConfig = subscribeConfigChange(this, () => this._relabelPending());\n // Become the presenter for this instance's config (or the global one).\n // `this` as the owner: it is what lets a request naming a `target` reach the\n // presenter in the SAME chat, instead of whichever one mounted last.\n resolveConfig(this).setElicitationPresenter(this._present, this);\n // Safety net: if the turn is stopped/errored while a request is open,\n // resolve it as cancelled so the client loop unblocks and the composer\n // input is restored.\n window.addEventListener('aparte-message-aborted', this._onTurnEnd);\n window.addEventListener('aparte-message-error', this._onTurnEnd);\n }\n\n /**\n * The boundary above us appeared, changed, or went away — move the\n * registration with it.\n *\n * `connectedCallback` alone is not enough and cannot be: registering is a\n * WRITE, and under all four wrappers it happens before `attachConfig` runs, so\n * it lands on the global singleton. `requestUserInput()` then resolves the\n * instance config, finds nothing, and rejects the request — the model\n * hears the user refuse a question the user never saw.\n *\n * See {@link AparteConfigAware}.\n */\n aparteConfigChanged(next: AparteConfig, previous: AparteConfig): void {\n // Withdraw OURS by name. `setElicitationPresenter(null)` cleared the whole\n // registry, so moving one chat's registration took every other mounted chat's\n // presenter down with it.\n previous.removeElicitationPresenter(this._present);\n next.setElicitationPresenter(this._present, this);\n }\n\n disconnectedCallback(): void {\n // Ours only. This used to clear the slot whenever it happened to hold our\n // presenter, which left a still-mounted sibling chat unable to ask anything for\n // the life of the page — silently, since the no-presenter warning fires once.\n resolveConfig(this).removeElicitationPresenter(this._present);\n window.removeEventListener('aparte-message-aborted', this._onTurnEnd);\n window.removeEventListener('aparte-message-error', this._onTurnEnd);\n this._unsubscribeConfig?.();\n this._unsubscribeConfig = null;\n this._cancelPending();\n }\n\n /**\n * Re-apply the open question's strings, in place.\n *\n * Two owners, and both have to move or the panel goes bilingual: the panel's own\n * defaults (`relabel`), and the Skip button, which this file builds and this file\n * therefore has to re-text. The composer's one button is a third, and it already\n * follows — `aparte-composer-send` remembers the panel state it was given.\n */\n private _relabelPending(): void {\n if (!this._pending) return;\n const cfg = resolveConfig(this);\n runWithConfig(cfg, () => this._pending!.relabel());\n }\n\n private _present: AparteElicitationPresenter = (request: AparteElicitationRequest) => {\n /*\n * No concurrency guard here any more — `AparteConfig.requestUserInput` queues,\n * so a second request arrives only once this one has settled.\n *\n * What was here answered the second request `cancel` immediately: a refusal\n * invented for a question nobody was shown. And it protected only requests\n * that came through THIS presenter, so a consumer's own presenter had nothing.\n * If two ever do overlap, `showPanel` now evicts and NOTIFIES the first, which\n * degrades to a settled request instead of a wedged one.\n */\n const composer = this._getComposer();\n // Mounted outside a chat that has a composer: there is nowhere to put the\n // panel, which is the same situation as no presenter at all.\n if (!composer) return Promise.reject(new AparteElicitationAbortError('no-presenter'));\n\n return new Promise<AparteElicitationResult>((resolve, reject) => {\n let done = false;\n /**\n * The slot this request owns, once `showPanel` has handed it over.\n *\n * `settle` can run BEFORE that — an already-aborted signal settles on the\n * spot — so it starts absent, and `hidePanel(undefined)` then closes whatever\n * is there, which is correct because nothing of ours is open yet.\n *\n * A holder rather than a `let`: `settle` reads it before `showPanel` assigns\n * it, which is exactly the shape `prefer-const` rejects, and the object says\n * \"not handed over yet\" more plainly than an unassigned binding.\n */\n const slot: { token?: symbol } = {};\n /**\n * Who had the focus before the panel took it.\n *\n * The panel focuses itself on open (`panel.focus()`, both branches below)\n * and nothing gave it back: a keyboard user who approved a tool call landed\n * at the top of the document and had to tab through the whole page to write\n * their next message. That is SC 2.4.3, level A, on the one flow the library\n * puts forward — and the ARIA Authoring Practices Guide requires it of every\n * dialogue-shaped pattern.\n *\n * Captured HERE rather than beside `panel.focus()` because there are two\n * branches that open a panel and one `close()` that ends both; a value read\n * once, before either, cannot disagree with itself.\n */\n const focusedBefore = document.activeElement;\n const close = (): boolean => {\n if (done) return false;\n done = true;\n this._pending = null;\n /*\n * Read BEFORE `hidePanel`, because that removes the focused element and\n * the browser then drops focus to `<body>` — after which there is no\n * way to tell whether the user was still in the panel or had moved on.\n *\n * And only if they were still in it. A request can settle late — an\n * abort, or a model that answered while the reader clicked elsewhere —\n * and pulling the focus back from wherever they went would be the same\n * theft in the other direction.\n */\n const active = document.activeElement;\n const focusWasInPanel = active instanceof Node && composer.contains(active);\n\n // Scoped to our own panel: finishing late must not tear down the panel\n // that replaced ours.\n composer.hidePanel(slot.token);\n\n /*\n * `isConnected` because the element that had the focus may itself have\n * been inside what just closed. Nothing further if it is gone: inventing\n * a destination — the editor, the send button — would be a policy this\n * component has no standing to set, and `<body>` is where the browser\n * puts it anyway.\n */\n if (focusWasInPanel && focusedBefore instanceof HTMLElement && focusedBefore.isConnected) {\n focusedBefore.focus();\n }\n return true;\n };\n const settle = (result: AparteElicitationResult): void => {\n if (close()) resolve(result);\n };\n /**\n * End it without an answer.\n *\n * A rejection rather than a third `action`, because a value is easy to\n * mistake for an answer and this one was: the approval gate read the old\n * `cancel` as a refusal and told the model the user had refused a tool they\n * had only stopped.\n */\n const fail = (reason: 'aborted' | 'no-presenter' = 'aborted'): void => {\n if (close()) reject(new AparteElicitationAbortError(reason));\n };\n\n // Caller-side cancellation (tool handler signal: timeout / turn abort).\n if (request.signal) {\n if (request.signal.aborted) { fail(); return; }\n request.signal.addEventListener('abort', () => fail(), { once: true });\n }\n\n // Built INSIDE this instance's config, so the panel's own strings come\n // from the locale of the chat that asked — `contextConfig()` reads the\n // ambient render config, and without this it would fall back to the\n // global one on a page where each chat has its own.\n const cfg = resolveConfig(this);\n\n /*\n * An APPROVAL: a decision, not a value.\n *\n * Same slot, same queue, same teardown — only what goes inside the panel\n * differs, which is the whole claim of one mechanism with two\n * presentations. The options come with the request because only the\n * requester can write them.\n *\n * There is always an exit here without touching the composer's own\n * controls: every option is a button. That matters because a panel takes\n * the send button over, so Stop is unreachable while one is open — for a\n * question the escape is the corner, for an approval it is a refusal.\n */\n if (request.kind === 'approval') {\n /*\n * The button exists only once an INSTRUCTION has been written.\n *\n * The options never route through it — a decision is its own click —\n * so with `mode: 'submit'` from the start it sat there permanently\n * disabled beside them, offering an act that did not exist. It is the\n * written text, and only that, which is the act this button already\n * means; until there is some, the panel has none for it.\n */\n const approvalMode = (): AparteComposerPanelMode => (panel.isComplete() ? 'submit' : 'none');\n const panel = runWithConfig(cfg, () =>\n buildApprovalPanel(request.message, request.options ?? [], () => {\n composer.setPanelSubmitEnabled(panel.isComplete(), approvalMode());\n }, request.details));\n panel.onSettle((answer) => settle({ action: 'accept', content: answer }));\n this._pending = { abort: () => fail(), composer, relabel: () => panel.relabel() };\n slot.token = composer.showPanel(panel.el, {\n submitEnabled: panel.isComplete(),\n mode: approvalMode(),\n onSubmit: () => { if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() }); },\n onEvict: () => fail(),\n });\n panel.focus();\n return;\n }\n // A question without a schema has nothing to collect; that is an approval,\n // and it was handled above. The fallback keeps this branch total rather\n // than throwing on a shape the type already forbids.\n const schema = request.schema ?? { type: 'string' as const };\n const panel: BuiltElicitationPanel = runWithConfig(cfg, () =>\n buildElicitationPanel(request.message, schema, () => {\n composer.setPanelSubmitEnabled(panel.canProceed(), panel.mode());\n }));\n\n // \"Skip\" → decline (MCP's decline: the user chose not to answer), in the\n // panel's CORNER. It sat beside the button that advances through the form,\n // and that adjacency read as \"skip this question\" while it declines the\n // whole request — see `dismiss` on BuiltElicitationPanel.\n const skip = document.createElement('button');\n skip.type = 'button';\n skip.className = 'aparte-btn aparte-elic-skip';\n skip.textContent = cfg.t('elicitationSkip');\n skip.addEventListener('click', () => settle({ action: 'decline' }));\n panel.dismiss.appendChild(skip);\n\n /*\n * The click that IS the answer — a single question whose options are\n * buttons. Same wiring as the approval panel's, because it is the same\n * act; the panel decides which of its shapes has it, and reports through\n * `mode()` that the composer's button has nothing to do.\n */\n panel.onSettle((content) => settle({ action: 'accept', content }));\n\n this._pending = {\n abort: () => fail(),\n composer,\n relabel: () => { panel.relabel(); skip.textContent = resolveConfig(this).t('elicitationSkip'); },\n };\n slot.token = composer.showPanel(panel.el, {\n submitEnabled: panel.canProceed(),\n mode: panel.mode(),\n /*\n * Something else took the slot — another request, a conversation switch,\n * or a turn ending. The composer tears the panel down either way; only\n * this callback can settle the promise, and without it the request hung\n * AND `_pending` stayed set, so every later question was short-circuited\n * for the life of the page. `cancel`, not `decline`: nobody declined\n * anything, the question was taken away.\n */\n onEvict: () => fail(),\n onSubmit: () => {\n // One meaning: submit the lot. Moving between questions is the chips'.\n if (panel.isComplete()) settle({ action: 'accept', content: panel.getContent() });\n },\n });\n panel.focus();\n });\n };\n\n private _cancelPending(): void {\n this._pending?.abort();\n }\n\n /**\n * The host id of the chat whose composer holds the open panel.\n *\n * Walks up from the composer rather than from `this`, because the panel lives in\n * the composer and that is what the turn belongs to. Matches the hosts\n * `aparte-chat-bubble._resolveTargetId()` matches, for the reason written there:\n * Angular's wrapper root IS the `<aparte-chat>` element, while the plain-root\n * wrappers render a `[data-aparte-chat]` div instead, so matching only the tag\n * resolves `undefined` on three wrappers out of four.\n */\n private _pendingTargetId(): string | undefined {\n let el: HTMLElement | null = this._pending?.composer ?? null;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n /**\n * The composer to present in: the nearest one in an ancestor subtree, and\n * nothing else.\n *\n * There used to be a `document.querySelector('aparte-composer')` fallback, which\n * is the \"first chat on the page\" bug this repo has now fixed in four other\n * places: on a page with two chats, an elicitation that could not find its own\n * composer mounted its panel in the OTHER chat's — so one conversation's question\n * appeared under the other conversation, and answering it resolved a tool call\n * belonging to a chat the user was not looking at.\n *\n * Returning `null` instead REJECTS the request, which is honest: nothing was\n * shown, so nothing was answered. The warning names the fix, because this is a\n * setup mistake and only the developer can correct it — the guide's own example\n * puts `<aparte-elicitation>` inside `<aparte-chat>`.\n */\n private _getComposer(): ComposerEl | null {\n let node: Element | null = this.parentElement;\n while (node) {\n const composer = node.querySelector('aparte-composer') as ComposerEl | null;\n if (composer && typeof composer.showPanel === 'function') return composer;\n // Stop AT the chat boundary. Removing the explicit\n // `document.querySelector` fallback was not enough on its own: this walk\n // reached `<body>`, and a `querySelector` from there searches the whole\n // document — so it found another chat's composer anyway, by a longer\n // route. The two-chat test caught exactly that.\n const tag = node.tagName?.toLowerCase();\n const isChatBoundary = tag === 'aparte-chat' || node.hasAttribute?.('data-aparte-chat');\n if (isChatBoundary) break;\n node = node.parentElement;\n }\n console.warn(\n '[aparte-elicitation] No <aparte-composer> in this element\\'s subtree, so the request '\n + 'could not be shown, so it REJECTED and the turn halted. Nothing was told to the '\n + 'model — there is nothing true to tell it. Move <aparte-elicitation> '\n + 'inside the <aparte-chat> it belongs to. It is deliberately NOT borrowing another '\n + 'chat\\'s composer: on a page with two chats that put the question under the wrong one.',\n );\n return null;\n }\n}\n\nif (typeof customElements !== 'undefined' && !customElements.get('aparte-elicitation')) {\n customElements.define('aparte-elicitation', AparteElicitation);\n}\n","import type { AparteChatViewport } from '../viewport/aparte-chat-viewport.js';\nimport type { AparteComposer } from '../composer/aparte-composer.js';\n// Defines <aparte-elicitation>, which the default composition below writes. A tag\n// nothing has defined is an inert unknown element, so the import is the difference\n// between a presenter and a placeholder.\nimport '../elicitation/aparte-elicitation.js';\nimport { escapeAttr } from '../../utils/escape.js';\n\n/**\n * AparteChat - The Shell\n *\n * The container element for a chat. It lays out its Light DOM children as a flex\n * column: an `<aparte-chat-viewport>` takes the space left over (`flex: 1 1 auto`)\n * and scrolls, an `<aparte-composer>` keeps its own height below it. Light DOM on\n * purpose, so the page's own global CSS reaches inside.\n *\n * The presence of an `<aparte-chat-viewport>` child at connect is the exact test for\n * \"the author composed this\". Find one and the children are used as given — this\n * element moves none of them, so anything else you drop in (a header, a banner above\n * the composer) is simply another row of that column, in DOM order. Find none and\n * `innerHTML` is OVERWRITTEN with a default composition — a viewport, an\n * `<aparte-elicitation>` presenter, and a composer shell holding an input and a send\n * button, plus the two attachment primitives when `attachments` is set — so children\n * written without a viewport anywhere inside them are destroyed, that header included.\n * The test is a DESCENDANT query, so a viewport nested in a wrapper of your own still\n * counts — compose it yourself with the viewport somewhere in the tree, or leave the tag\n * empty. Angular's wrapper sets `framework-managed` instead of relying on that test,\n * because its children do not exist yet when this element upgrades; React, Vue and\n * Svelte never create this element at all, so the question does not arise for them.\n *\n * Being a component (not a bare `<div>`), it also owns behaviour a wrapper div\n * can't: with `center-empty`, it watches its own viewport and keeps the composer\n * centered as a welcome state until the first `<aparte-chat-bubble>` lands, then\n * slides to the normal layout — no external JavaScript. While centered it carries\n * `data-empty` on itself (set and cleared by that same watcher), which is the hook to\n * style the welcome state from an app's own CSS. The watcher needs a viewport somewhere\n * inside, and hand-written markup always has one because composing the default injects\n * it — so the only path where no watcher starts and `data-empty` is never set is\n * `framework-managed`, where the framework owns the subtree anyway. The stylesheet\n * centers through\n * `aparte-chat[center-empty][data-empty]` and its DIRECT viewport child, so a\n * framework-managed host that nests the viewport inside a container of its own gets\n * nothing from the attribute — the wrappers ship their own centered layout.\n *\n * It is also one of the anchors where core re-declares its derived CSS layer, so\n * overriding a master — `--aparte-primary`, a surface, a text colour — on a single\n * `<aparte-chat>` re-derives the values computed from it for that instance rather\n * than moving one button. That is per-instance theming. The literal palette is\n * deliberately not re-declared here, so a chat nested in a dark wrapper stays dark.\n *\n * Presentational only: it does NOT wire a transport/client. Attach an\n * `AparteClient`, or handle `aparte-send` yourself, as with the primitives.\n * Size the element via CSS (a height, or let it fill a sized parent).\n *\n * @element aparte-chat\n * @attr {string} placeholder - Placeholder for the composer input (default composition)\n * @attr {boolean} disabled - Disables the composer\n * @attr {boolean} center-empty - Center the composer as a welcome state until the first message, then slide to the normal layout\n * @attr {boolean} 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, 16px)] - Space below the\n * composer, as `padding-block-end` on the shell (the same rule covers a wrapper's\n * `[data-aparte-chat]` root). The gap belongs to this element because padding applied\n * from outside would also shrink the scroll area, stopping the transcript short of the\n * edge instead of scrolling to it.\n *\n * Composing it yourself is the other form, and the container still lays it out and still\n * runs `center-empty`. It is written out here rather than as a second `@example` for a\n * mechanical reason: every element-own example is concatenated into ONE live frame on the\n * generated reference page, so a second `<aparte-chat>` there rendered as a second whole\n * chat — two empty composers with 600px of nothing between them.\n *\n * ```html\n * <aparte-chat center-empty attachments style=\"height: 320px\">\n * <aparte-chat-viewport></aparte-chat-viewport>\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n * </aparte-chat>\n * ```\n *\n * @example\n * <!-- Left empty it fills in a viewport, an input and a send button. -->\n * <aparte-chat center-empty placeholder=\"Say something…\" style=\"height: 320px\"></aparte-chat>\n *\n * <script>\n * // Seeded so the frame shows a real exchange rather than an empty box: this\n * // example is RENDERED, not only read.\n * const chat = document.querySelector('aparte-chat');\n * chat.viewport.appendMessage({ id: 'u1', role: 'user', content: 'What is a transport?' });\n * chat.viewport.appendMessage({\n * id: 'a1',\n * role: 'assistant',\n * content: 'The object that talks to the model. Swap it and the UI does not change.',\n * });\n * </script>\n */\nexport class AparteChat extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['placeholder', 'disabled', 'center-empty', 'attachments'];\n }\n\n private _observer: MutationObserver | null = null;\n\n /**\n * True only for the composition THIS element injected. An author-provided\n * composer (or a `framework-managed` host) is never edited by the attachments\n * toggle below — those own their own markup.\n */\n private _ownsShell = false;\n\n connectedCallback(): void {\n this._render();\n this._forwardAttr('placeholder');\n this._forwardAttr('disabled');\n this._syncEmptyWatch();\n }\n\n disconnectedCallback(): void {\n this._observer?.disconnect();\n this._observer = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n if (name === 'center-empty') {\n this._syncEmptyWatch();\n return;\n }\n if (name === 'attachments') {\n this._syncAttachments();\n return;\n }\n // placeholder / disabled forward to the inner composer. An explicit removal\n // mirrors through; a never-set attribute is left alone (so a caller-provided\n // composer keeps its own).\n const composer = this.querySelector('aparte-composer');\n if (!composer) return;\n if (newValue !== null) composer.setAttribute(name, newValue);\n else composer.removeAttribute(name);\n }\n\n /** The message viewport (yours or the default), or `null` before connect. */\n get viewport(): AparteChatViewport | null {\n return this.querySelector('aparte-chat-viewport');\n }\n\n /** The composer (yours or the default), or `null` before connect. */\n get composer(): AparteComposer | null {\n return this.querySelector('aparte-composer');\n }\n\n private _render(): void {\n // A framework wrapper renders the composition itself — and its children do not\n // exist yet when this runs (the element is upgraded on insert, before the\n // framework's template renders), so the viewport check below can't see them.\n // `framework-managed` is the wrapper's explicit \"hands off\" signal: without it\n // the default composition below would be injected UNDER the framework's own.\n if (this.hasAttribute('framework-managed')) return;\n\n // Author-provided composition wins — if a viewport is already inside, use the\n // children as given and only lay them out (via CSS). Otherwise fill in a\n // default viewport + composer so the empty tag \"just works\".\n if (this.querySelector('aparte-chat-viewport')) return;\n\n // The composer's `placeholder` is read by its input via `closest()` at upgrade\n // time (no event), so it must be on the element in the initial markup.\n const placeholder = this.getAttribute('placeholder');\n const composerAttrs =\n (placeholder !== null ? ` placeholder=\"${escapeAttr(placeholder)}\"` : '') +\n (this.hasAttribute('disabled') ? ' disabled' : '');\n\n // Attachments are opt-in: the picker only makes sense when the host consumes\n // the files (an `AparteClient` inlines them per `rawFileInject`; a hand-rolled\n // loop must read `event.detail.files`). Offering it unconditionally would show\n // a button that silently drops what the user attached.\n const attachments = this.hasAttribute('attachments');\n\n /*\n * The presenter ships in the default composition, and that is a change of tier.\n *\n * It renders nothing by itself — it registers as the presenter for this subtree and\n * mounts a panel in the composer when something asks. It used to be opt-in, which\n * was right while asking the user was a plugin's business. It is not any more: the\n * BUILT-IN approval gate asks through it, so a chat without one cannot honour\n * `needsApproval` at all. An affordance core can honour end to end is on by default\n * (ratified decision #8, tier a); leaving this out would have made the gate depend\n * on a tag nobody was told to write.\n */\n this.innerHTML = `\n <aparte-chat-viewport></aparte-chat-viewport>\n <aparte-elicitation></aparte-elicitation>\n <aparte-composer${composerAttrs}>\n <div class=\"aparte-composer-shell\">\n ${attachments ? '<aparte-composer-attachments></aparte-composer-attachments>' : ''}\n <div class=\"aparte-composer-row\">\n ${attachments ? '<aparte-composer-add-attachment></aparte-composer-add-attachment>' : ''}\n <aparte-composer-input></aparte-composer-input>\n <aparte-composer-send></aparte-composer-send>\n </div>\n </div>\n </aparte-composer>\n `;\n this._ownsShell = true;\n }\n\n /**\n * Add/remove the two attachment primitives on the composition we injected, so\n * toggling the attribute after mount works like the wrappers' reactive prop\n * (there, a re-render does it). Author-provided markup is left alone.\n */\n private _syncAttachments(): void {\n if (!this._ownsShell) return;\n const composer = this.composer;\n const shell = composer?.querySelector('.aparte-composer-shell');\n const row = shell?.querySelector('.aparte-composer-row');\n if (!composer || !shell || !row) return;\n\n const strip = shell.querySelector('aparte-composer-attachments');\n const picker = row.querySelector('aparte-composer-add-attachment');\n\n if (this.hasAttribute('attachments')) {\n if (!strip) shell.insertBefore(document.createElement('aparte-composer-attachments'), row);\n if (!picker) row.insertBefore(document.createElement('aparte-composer-add-attachment'), row.firstChild);\n return;\n }\n\n strip?.remove();\n picker?.remove();\n // Files picked before the capability was withdrawn would otherwise ride on\n // the next send with nothing in the UI showing them.\n composer.clearAttachments();\n }\n\n /** Set an attribute on the inner composer only when the shell carries it. */\n private _forwardAttr(name: string): void {\n if (!this.hasAttribute(name)) return;\n this.querySelector('aparte-composer')?.setAttribute(name, this.getAttribute(name) ?? '');\n }\n\n /** Start/stop watching the viewport so `center-empty` toggles itself. */\n private _syncEmptyWatch(): void {\n this._observer?.disconnect();\n this._observer = null;\n\n if (!this.hasAttribute('center-empty')) {\n this.removeAttribute('data-empty');\n return;\n }\n\n const viewport = this.querySelector('aparte-chat-viewport');\n if (!viewport) return;\n\n this._updateEmpty();\n // A message is an <aparte-chat-bubble>; watch the viewport for the first one.\n this._observer = new MutationObserver(() => this._updateEmpty());\n this._observer.observe(viewport, { childList: true, subtree: true });\n }\n\n private _updateEmpty(): void {\n const viewport = this.querySelector('aparte-chat-viewport');\n const empty = !viewport || !viewport.querySelector('aparte-chat-bubble');\n this.toggleAttribute('data-empty', empty);\n }\n\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat')) {\n customElements.define('aparte-chat', AparteChat);\n}\n","import type {\n AparteBubbleRole,\n AparteSegment,\n AparteAttachment,\n AparteBranchNavigateEventDetail,\n 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 } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig } 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';\n\n/**\n * Warn ONCE when a segment has no renderer — now only for types core has never\n * heard of, since the built-ins install themselves on first use.\n */\nlet _warnedNoRenderer = false;\nfunction warnMissingRenderer(type: string): void {\n if (_warnedNoRenderer) return;\n _warnedNoRenderer = true;\n console.warn(`[aparte] No renderer for segment \"${type}\". Register one with registerSegmentRenderer({ type: '${type}', render }) from @aparte/core — see https://apartejs.dev/guides/customization/#custom-segment-types`);\n}\n\n/**\n * What a segment renders as when no renderer claims its type.\n *\n * `AparteCustomSegment.fallback` is documented as \"Optional fallback text\n * representation\" and was read by NOTHING — the field existed, the type published it,\n * and a custom segment arriving where its renderer is not registered (a conversation\n * replayed in another app, a client that loads its views lazily, an export) showed\n * `[Unknown segment type: custom]` while carrying the sentence written for exactly that\n * moment. Found while writing the segment's own `@example`, which is the kind of dead\n * declaration documentation is good at surfacing.\n *\n * The developer warning is skipped when a fallback is present: an author who supplied\n * one has already said this can happen, and warning then is crying wolf. Without one it\n * still fires, because a missing renderer is otherwise silent.\n *\n * `textContent`, so a fallback is text and cannot carry markup — the same rule the rest\n * of the library follows for anything a model or a host can produce.\n */\nfunction unrenderedSegment(segment: { type: string; fallback?: unknown }): HTMLElement {\n const fallback = typeof segment.fallback === 'string' && segment.fallback.trim() ? segment.fallback : null;\n if (!fallback) warnMissingRenderer(segment.type);\n const el = document.createElement('div');\n el.className = fallback ? 'aparte-segment aparte-segment-fallback' : 'aparte-segment aparte-segment-unknown';\n el.textContent = fallback ?? `[Unknown segment type: ${segment.type}]`;\n return el;\n}\n\n/**\n * The renderer for `type`, installing core's built-ins the first time a segment\n * finds the registry empty of its type. `registerDefaultRenderers()` therefore\n * becomes optional rather than a call you discover by seeing\n * `[Unknown segment type: text]` on screen (it is still honoured, and\n * `AparteClient({ autoRegister: false })` still keeps the built-ins out).\n */\nfunction resolveSegmentRenderer(\n type: string,\n config: AparteConfig,\n): ReturnType<typeof getSegmentRenderer> {\n // The CONFIG is passed in, not read ambiently.\n //\n // `runWithConfig` wrapped only `render` / `setup` / `update`, so the renderer's\n // OWN work was per-instance while the question \"which renderer is this?\" was\n // answered from a module-level registry. Two chats on a page therefore shared\n // their segment renderers no matter what `config` prop the wrapper was given —\n // half of the promise those props make.\n const renderer = getSegmentRenderer(type, config);\n if (renderer) return renderer;\n installDefaultRenderersOnce(config);\n return getSegmentRenderer(type, config);\n}\n\n/**\n * Normalize a segment renderer's output to a single element. Renderers may return\n * an HTML **string** (parsed via innerHTML — the built-in renderers) or a ready\n * **HTMLElement** (used directly, so custom renderers can wire event listeners /\n * framework nodes with no innerHTML XSS surface). See {@link AparteSegmentRenderer}.\n */\nfunction segmentRenderResultToElement(result: string | HTMLElement, 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 * One message: plain content or a list of rich segments, in light DOM.\n *\n * Normally created for you by `<aparte-chat-viewport>`, one per message in the store;\n * you write the tag by hand only when you drive the DOM yourself. It is ONE message\n * with one role — a transcript is the viewport's job, and a bubble is not a\n * general-purpose card.\n *\n * **Not a slot host.** `_render()` writes its own markup into the light DOM on\n * connect, so children placed inside the tag are replaced rather than projected.\n * Everything customizable is a registered hook instead of a child: the structural\n * shell (`setBubbleShellRenderer` — it must root at `.aparte-message` and carry the\n * region hooks, since every query here is null-guarded and a partial shell silently\n * loses that region), the avatar (`setAvatarProvider`), the attachment chips\n * (`setAttachmentRenderer`), the `‹1/2›` position indicator\n * (`setSiblingNavRenderer`) and the body itself (`registerSegmentRenderer`).\n *\n * Two content paths, mutually exclusive: the `content` attribute (plain text run\n * through the configured Markdown provider, then highlighted once — after streaming\n * ends, not per token) and `setSegments()` / `addSegment()`. Segments win:\n * `.aparte-content` stays hidden for as long as any exist. The painted\n * `.aparte-message-content` box hides itself when there is nothing in it, so a\n * message that is only attachments is not a coloured rectangle.\n *\n * The bubble owns no transport and no host behaviour. The action bar and the branch\n * picker only dispatch the events below; nothing here retries a turn, persists an\n * edit, opens a stats popover or switches a branch. Which buttons exist follows from\n * that: `copy` is on by default, `edit` / `retry` / `feedback` need\n * `setBubbleActions`, `info` needs both that flag and a prior `setUsage()` (a details\n * button over no numbers is a dead button), and an image attachment becomes a preview\n * button only once `setHostHandlers` declares a lightbox — undeclared it stays a\n * picture, with no role, tab stop or pointer.\n *\n * The error state is derived from the segments (an `error` segment sets `data-error`\n * on `.aparte-message`), never from a status attribute, so it behaves identically in\n * vanilla and in every wrapper.\n *\n * All seven events are declared by hand rather than left to the analyser, which\n * found six. `aparte-branch-navigate` is dispatched from the `_onBranchPickerClick`\n * arrow class field, and the auto-detection visits `ts.isMethodDeclaration` only —\n * so the one event belonging to the branch picker was the one missing from the\n * manifest, and from the generated reference, for as long as both existed.\n *\n * @element aparte-chat-bubble\n *\n * @attr {string} 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 no longer reads 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: `_updateTimestamp` accepts either and only coerces when the value is numeric.\n * @attr {string} message-id - How streaming and the action bar address this bubble.\n * @attr {boolean} streaming - Hides the action bar and shows the caret while a reply is in flight.\n * @attr {string} name - The display name in the header.\n *\n * @fires {CustomEvent<AparteActionEventDetail>} aparte-action - A custom action-bar button was pressed.\n * @fires {CustomEvent<AparteRetryEventDetail>} aparte-retry - Retry was pressed; the host forks the turn.\n * @fires {CustomEvent<AparteEditEventDetail>} aparte-edit - An edit was saved.\n * @fires {CustomEvent<AparteFeedbackEventDetail>} aparte-feedback - Thumbs up or down.\n * @fires {CustomEvent<AparteMessageInfoEventDetail>} aparte-message-info - The info affordance was pressed.\n * @fires {CustomEvent<AparteBranchNavigateEventDetail>} aparte-branch-navigate - The `‹1/2›` picker moved between sibling versions.\n * @fires {CustomEvent<AparteAttachmentPreviewEventDetail>} aparte-attachment-preview - An attached image was clicked, asking the app to open it full-size.\n * @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=12px] - Gap between the avatar column and the body (the viewport reuses it between messages).\n * @cssprop [--aparte-message-padding=16px 12px] - Padding around one message row.\n * @cssprop [--aparte-message-max-width=800px] - Width of the centred message row.\n *\n * @cssprop [--aparte-message-content-radius=14px] - Radius of the painted content box.\n * @cssprop [--aparte-message-content-padding=10px 14px] - Padding of the USER box only; the assistant's content is plain full-width prose.\n * @cssprop --aparte-message-content-bg-user - 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=var(--aparte-text)] - Text colour inside the user box.\n * @cssprop [--aparte-message-content-text-assistant=var(--aparte-text)] - Text colour inside the assistant box.\n *\n * @cssprop [--aparte-avatar-size=32px] - Square size of the avatar slot.\n * @cssprop [--aparte-avatar-radius=var(--aparte-radius-avatar)] - Avatar corner radius.\n * @cssprop [--aparte-avatar-font-size=14px] - Size of the initial, for a shell that renders one (the default shell leaves the slot empty, and `.aparte-avatar:empty` hides it).\n * @cssprop [--aparte-avatar-bg-user=var(--aparte-primary)] - Avatar background, user role.\n * @cssprop [--aparte-avatar-text-user=var(--aparte-text-inverse)] - Avatar text colour, user role.\n * @cssprop [--aparte-avatar-bg-assistant=var(--aparte-surface-3)] - Avatar background, assistant role.\n * @cssprop [--aparte-avatar-text-assistant=var(--aparte-text-inverse)] - Avatar text colour, assistant role.\n * @cssprop [--aparte-avatar-image-user=none] - `background-image` for the user avatar — a logo with no AvatarProvider and no JS.\n * @cssprop [--aparte-avatar-image-assistant=none] - `background-image` for the assistant avatar.\n * @cssprop [--aparte-avatar-image-size=90%] - `background-size` for both avatar images.\n *\n * @cssprop [--aparte-name-font-size=14px] - Sender name in the header.\n * @cssprop [--aparte-name-color=var(--aparte-text)] - Sender name colour.\n * @cssprop [--aparte-timestamp-font-size=12px] - Timestamp in the header.\n * @cssprop [--aparte-timestamp-color=var(--aparte-text-muted)] - Timestamp colour.\n * @cssprop [--aparte-content-font-size=15px] - Body type size, applied to both the plain-content and the segments container.\n * @cssprop [--aparte-content-color=var(--aparte-text)] - Body text colour.\n * @cssprop [--aparte-content-line-height=var(--aparte-line-height-loose)] - Body line height.\n *\n * @cssprop [--aparte-attachments-max-height=140px] - Cap on the sent-attachment strip; past it the strip scrolls instead of growing.\n * @cssprop [--aparte-attachment-image-size=40px] - Tile size in the strip. The strip re-declares the global 72px down to 40px, since these are thumbnails inside a conversation.\n * @cssprop [--aparte-thumb-radius=var(--aparte-radius-lg)] - Attachment tile radius (shared with the composer's preview tiles).\n * @cssprop [--aparte-thumb-name-color=#ffffff] - Filename overlaid on a tile.\n * @cssprop --aparte-thumb-name-scrim - Gradient behind that filename, so it stays legible over any image.\n * @cssprop [--aparte-thumb-name-padding=14px 5px 4px] - Padding of the filename overlay.\n *\n * @cssprop [--aparte-action-bar-gap=4px] - Gap between action buttons (and between the footer's two regions).\n * @cssprop [--aparte-action-bar-btn-size=28px] - Square size of an action button; also the footer's reserved height.\n * @cssprop [--aparte-action-bar-btn-color=var(--aparte-text-muted)] - Action icon colour at rest.\n * @cssprop [--aparte-action-bar-btn-hover-bg=var(--aparte-surface-2)] - Action button hover background (the branch arrows reuse it).\n * @cssprop [--aparte-action-bar-btn-hover-color=var(--aparte-text)] - Action icon colour on hover.\n *\n * @cssprop [--aparte-branch-picker-gap=4px] - Gap between the arrows and the position label.\n * @cssprop [--aparte-branch-picker-btn-size=20px] - Square size of each arrow.\n * @cssprop [--aparte-branch-picker-btn-icon-size=16px] - Glyph size inside an arrow.\n * @cssprop [--aparte-branch-picker-btn-color=var(--aparte-text-muted)] - Arrow colour at rest.\n * @cssprop [--aparte-branch-picker-btn-hover-color=var(--aparte-text)] - Arrow colour on hover (a disabled arrow is dimmed instead).\n * @cssprop [--aparte-branch-picker-label-size=12px] - Type size of the position label.\n * @cssprop [--aparte-branch-picker-label-color=var(--aparte-text-muted)] - Colour of the position label.\n * @cssprop [--aparte-branch-picker-label-min-width=32px] - Reserved label width, so `9 / 9` growing to `10 / 12` does not shift the arrows.\n *\n * @cssprop [--aparte-waiting-height=1.5em] - Min height of the waiting region, so the first token does not jump the layout.\n * @cssprop [--aparte-waiting-dot-gap=4px] - Gap between the three waiting dots.\n * @cssprop [--aparte-status-dot-size=6px] - Diameter of a waiting dot (shared with the status indicator).\n * @cssprop [--aparte-status-color=var(--aparte-text-muted)] - Colour of the waiting dots (shared with the status indicator).\n *\n * @cssprop [--aparte-error-solid=#dc2626] - Ring drawn around the avatar while `data-error` is set. The error CARD itself belongs to the error segment renderer.\n *\n * @example\n * <!-- Rendered for you by the viewport. Written by hand only when you drive the DOM\n * yourself: `message-id` is what streaming and the action bar address it by. -->\n * <aparte-chat-bubble\n * message-id=\"a1\"\n * data-role=\"assistant\"\n * name=\"Assistant\"\n * content=\"Hello.\"\n * ></aparte-chat-bubble>\n *\n * <!-- While a reply is in flight: `streaming` hides the action bar and shows the caret. -->\n * <aparte-chat-bubble message-id=\"a2\" data-role=\"assistant\" streaming></aparte-chat-bubble>\n *\n * <!-- One reply among several. `setSiblings(count, index)` is what draws the picker, and\n * it is a METHOD, not an attribute — so a branch cannot be shown by markup alone.\n * Retry forks a sibling instead of overwriting the reply, and this is the control\n * that walks them; each press dispatches `aparte-branch-navigate` for a host to\n * answer. Kept in the example because a guide that describes branching has no other\n * way to SHOW it. -->\n * <aparte-chat-bubble\n * message-id=\"a3\"\n * data-role=\"assistant\"\n * name=\"Assistant\"\n * content=\"A second take on the same question.\"\n * ></aparte-chat-bubble>\n *\n * <script>\n * document.querySelector('aparte-chat-bubble[message-id=\"a3\"]').setSiblings(2, 0);\n * </script>\n */\nexport class AparteChatBubble extends HTMLElement {\n private _contentEl: HTMLDivElement | null = null;\n private _segmentsEl: HTMLDivElement | null = null;\n private _attachmentsEl: HTMLDivElement | null = null;\n private _actionBarEl: HTMLDivElement | null = null;\n private _branchPickerEl: HTMLDivElement | null = null;\n private _footerEl: HTMLDivElement | null = null;\n private _content = '';\n private _streaming = false;\n private _segments: AparteSegment[] = [];\n private _role: AparteBubbleRole = 'assistant';\n private _attachments: AparteAttachment[] = [];\n private _usage: AparteUsage | null = null;\n /** Cleanup returned by the avatar provider — called on disconnect/re-render. */\n private _avatarCleanup: (() => void) | null = null;\n /** Sibling count for tree-based branch navigation (set by setSiblings()) */\n private _siblingCount = 1;\n /** Sibling index for tree-based branch navigation (set by setSiblings()) */\n private _siblingIndex = 0;\n /** True while the user-message inline editor is open. */\n private _editing = false;\n /** The live inline editor (the composer's contenteditable primitive), present only while `_editing`. */\n private _editInput: AparteComposerInput | null = null;\n\n static get observedAttributes(): string[] {\n // `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 };\n\n /**\n * Ask every rendered segment to re-read its config-derived text.\n *\n * Not `_renderSegments()`, which wipes the container and rebuilds: that destroys a\n * mounted artifact preview, reverts a reasoning block the reader expanded by\n * clicking `<summary>` (the DOM's real state is never written back to `collapsed`),\n * resets scroll inside long terminal panes, drops focus from an Approve/Reject\n * gate, and throws away the incremental Markdown parser's buffered lookahead\n * mid-stream — for a change that added no content. It also fires container-wide\n * childList mutations, which is what the viewport's observer reads as \"scroll to\n * the bottom\".\n *\n * `relabel` is the narrow alternative, bound by the same no-child-node rule as\n * `update()`. A renderer that has no config-derived text does not implement it,\n * and this loop simply skips it.\n */\n private _relabelSegments(): void {\n if (!this._segmentsEl) return;\n for (const segment of this._segments) {\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (!renderer?.relabel) continue;\n const el = this._segmentsEl.querySelector(\n `:scope > [data-segment-id=\"${cssEscape(segment.id)}\"]`,\n ) as HTMLElement | null;\n if (!el) continue;\n runWithConfig(this._cfg, () => renderer.relabel!(el, segment));\n }\n }\n\n /**\n * Re-apply the locale strings written straight into the markup by `_render()` —\n * the accessible names a screen reader reads, which nothing else refreshes.\n */\n private _updateLocalizedLabels(): void {\n const locale = this._cfg.getLocale();\n const set = (selector: string, label: string): void => {\n this.querySelector(selector)?.setAttribute('aria-label', label);\n };\n set('.aparte-branch-prev', locale.previousResponse ?? 'Previous response');\n set('.aparte-branch-next', locale.nextResponse ?? 'Next response');\n set('.aparte-action-bar', locale.messageActions ?? 'Message actions');\n }\n\n /**\n * Config governing this bubble: the instance config of the nearest\n * `[data-aparte-host]` boundary, else the global singleton. Resolved live\n * (a single `closest()`) rather than cached — the boundary may be attached\n * AFTER this bubble mounts (AparteChatHost.bind() runs post-mount), so a\n * connect-time cache would freeze the wrong config.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n\n connectedCallback(): void {\n this._render();\n this._updateContent();\n // Populate the timestamp from the current attribute. Frameworks that set\n // attributes BEFORE the element is connected (e.g. the Svelte wrapper) fire\n // attributeChangedCallback while _render() hasn't created `.aparte-timestamp`\n // yet, so the initial time would otherwise stay blank. No-ops when the\n // attribute is absent (set later → attributeChangedCallback handles it).\n this._updateTimestamp(this.getAttribute('timestamp'));\n window.addEventListener('aparte-config-change', this._onConfigChange);\n // Delegated, so a re-render cannot lose a click on the branch arrows.\n this.addEventListener('click', this._onBranchPickerClick);\n this.addEventListener('click', this._onLinkClick);\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 }\n\n /** Set content directly */\n setContent(content: string): void {\n this._content = content;\n this.setAttribute('content', content);\n // A REPLACE, not an append: the incremental parser tracks how many characters it has\n // already written, so leaving its state behind would make the next token's delta a\n // slice of the wrong string. A retry does exactly this — clear, then re-stream.\n this._resetMarkdownStream();\n this._updateContent();\n }\n\n /**\n * Drop the incremental Markdown parser's state.\n *\n * Only needed where `_content` is REPLACED rather than grown. `appendToken` grows it, so\n * the parser's cursor stays valid there — which is the whole point of the seam.\n */\n private _resetMarkdownStream(): void {\n const host = this as AparteMarkdownStreamHost;\n if (host._aparteSmd) host._aparteSmd.renderer.end();\n host._aparteSmd = undefined;\n }\n\n /** Get current content */\n getContent(): string {\n return this._content;\n }\n\n /** Set segments for rich content */\n setSegments(segments: AparteSegment[]): void {\n /*\n * Copy the array IN, the way `getSegments()` already copies it OUT.\n *\n * That asymmetry was the bug: the bubble defended its list on the way out and\n * adopted the caller's on the way in. `populateBubbleFromMessage` hands over\n * `message.segments` — the repository's own array — so the bubble and the model\n * ended up advancing ONE array. `appendToSegment` then wrote each chunk twice:\n * the viewport replaced the slot with `{...segment, content: old + chunk}`, the\n * bubble looked the segment up in what it thought was its own list, found that\n * replacement (chunk already in it) and appended the chunk again. Measured:\n * \"ThatThat deletesdeletes aa filefile\".\n *\n * This is the same failure 3b026bb fixed for `addSegment` — where it does not\n * happen, because the bubble pushes into a list it created itself, so the\n * viewport's replacement decouples the two immediately. A message arriving with\n * its segments already populated went around that fix, exactly as `AparteClient`\n * went around the one before it. One copy here closes the last shared array:\n * `setSegments` has a single production caller, so all three paths through\n * `populateBubbleFromMessage` are covered by this line.\n *\n * The objects stay shared, deliberately — that is the arrangement `addSegment`\n * produces and that `appendToSegment` is written for: the first write on either\n * side replaces its own slot and the two are independent from then on.\n */\n this._segments = [...segments];\n this._renderSegments();\n this._updateWaiting();\n }\n\n /** Add a segment */\n addSegment(segment: AparteSegment): void {\n this._segments.push(segment);\n this._appendSegmentEl(segment);\n this._updateWaiting();\n }\n\n /** Update a specific segment */\n updateSegment(segmentId: string, updates: Partial<AparteSegment>): void {\n const index = this._segments.findIndex(s => s.id === segmentId);\n if (index !== -1) {\n const updated = mergeSegmentUpdate(this._segments[index]!, updates);\n this._segments[index] = updated;\n this._applySegmentUpdate(segmentId, updated, updates);\n }\n }\n\n /** Append content to a segment */\n appendToSegment(segmentId: string, content: string): void {\n const segment = this._segments.find(s => s.id === segmentId);\n if (segment && 'content' in segment) {\n (segment as { content: string }).content += content;\n this._applySegmentUpdate(segmentId, segment, { content: (segment as AparteSegment & { content: string }).content });\n }\n }\n\n /** Get all segments */\n getSegments(): AparteSegment[] {\n return [...this._segments];\n }\n\n /** Remove a segment by id (e.g. to discard a transient waiting indicator) */\n /**\n * Scoped to DIRECT children on purpose.\n *\n * Segments are appended as direct children of the container, but a descendant\n * query returns the first match in document order — and sanitized model\n * markdown renders inside that same container, with `data-*` attributes\n * deliberately preserved (they are inert). So a decoy `data-segment-id` planted\n * in an earlier segment's prose used to win over the real segment element.\n *\n * Parser ids are unguessable UUIDs, but a tool segment is `tool-${toolCallId}`\n * and the MODEL chooses that id — so this was reachable, and pointing an update\n * at a decoy left a rejected tool rendering as still-running: a spoof against\n * the human-in-the-loop control.\n */\n removeSegment(segmentId: string): void {\n const index = this._segments.findIndex(s => s.id === segmentId);\n if (index !== -1) {\n this._segments.splice(index, 1);\n }\n const el = this._segmentsEl?.querySelector(`:scope > [data-segment-id=\"${cssEscape(segmentId)}\"]`);\n el?.remove();\n this._updateWaiting();\n }\n\n /** Set attachments (chips shown above message content, user role only) */\n setAttachments(attachments: AparteAttachment[]): void {\n this._attachments = attachments;\n this._updateAttachments();\n // 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 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 if (shell) {\n const out = runWithConfig(this._cfg, () => shell({ role: this._role, name: displayName, avatarInitial: initial }));\n if (out instanceof HTMLElement) this.replaceChildren(out);\n else this.innerHTML = out;\n } else {\n this.innerHTML = `\n <div class=\"aparte-message\" data-role=\"${escapeAttr(role)}\" role=\"article\" aria-label=\"${escapeAttr(this._getAriaLabel())}\">\n <div class=\"aparte-avatar\" data-role=\"${escapeAttr(role)}\"></div>\n <div class=\"aparte-body\">\n <div class=\"aparte-header\">\n <span class=\"aparte-name\">${escapeHtml(displayName)}</span>\n <span class=\"aparte-timestamp\"></span>\n </div>\n <div class=\"aparte-attachments\" hidden></div>\n <div class=\"aparte-message-content\">\n <div class=\"aparte-segments\"></div>\n <div class=\"aparte-content\"></div>\n <div class=\"aparte-waiting\" hidden>\n <span class=\"aparte-dots\" aria-hidden=\"true\"><span class=\"aparte-dot\"></span><span class=\"aparte-dot\"></span><span class=\"aparte-dot\"></span></span>\n <span class=\"aparte-sr-only\"></span>\n </div>\n </div>\n <div class=\"aparte-footer\">\n <div class=\"aparte-branch-picker\" hidden>\n <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev\" aria-label=\"${escapeAttr(this._cfg.getLocale().previousResponse ?? 'Previous response')}\">&#8249;</button>\n <span class=\"aparte-branch-label\">1 / 1</span>\n <!-- The move has to be ANNOUNCED. Pressing the arrows deliberately does not\n take focus, so without a live region a screen-reader user gets the new\n branch and no indication anything changed. The visible label cannot be\n the region itself: a custom sibling-nav renderer may replace it with\n dots, which reads as nothing. No new locale key — the position is\n digits, and the buttons beside it already carry translated labels. -->\n <span class=\"aparte-sr-only aparte-branch-status\" aria-live=\"polite\"></span>\n <button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next\" aria-label=\"${escapeAttr(this._cfg.getLocale().nextResponse ?? 'Next response')}\">&#8250;</button>\n </div>\n <div class=\"aparte-action-bar\" role=\"toolbar\" aria-label=\"${escapeAttr(this._cfg.getLocale().messageActions ?? 'Message actions')}\"></div>\n </div>\n </div>\n </div>\n `;\n }\n\n this._contentEl = this.querySelector('.aparte-content');\n this._segmentsEl = this.querySelector('.aparte-segments');\n this._attachmentsEl = this.querySelector('.aparte-attachments');\n this._actionBarEl = this.querySelector('.aparte-action-bar');\n // 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, which\n * used to be a bubble with a name and an empty body.\n *\n * The dots are CSS (no per-token work, themable, honours reduced-motion); the\n * accessible name is `locale.typing`, next to the `aria-busy` the streaming state\n * already sets. A custom bubble shell without the region simply has no indicator\n * (same null-guarded degradation as the other region hooks).\n */\n private _updateWaiting(): void {\n const empty = this._segments.length === 0 && !this._content.trim();\n const waiting = this._streaming && this._role !== 'user' && empty;\n\n // The painted box is hidden when it has nothing to paint. It carries the user\n // bubble's background, padding and radius, so an empty one is a coloured\n // rectangle with nothing in it — which is exactly what a message that is ONLY\n // attachments produced: the chips render ABOVE this box, so the box had no\n // content, no segments and no dots, and still drew itself.\n //\n // `hidden` and not `style.display`, deliberately: nothing sets an explicit\n // `display` on this class, so the UA sheet's rule applies. Where a component\n // DOES set one, `[hidden]` loses — a trap this repo has already paid for.\n const box = this.querySelector('.aparte-message-content') as HTMLElement | null;\n if (box) box.hidden = empty && !waiting;\n\n // 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 is left exactly as the shell rendered it — which for\n * the default shell means EMPTY, and hidden by `.aparte-avatar:empty`. This used\n * to claim it \"falls back to the default initial rendered by `_render()`\"; there\n * is no such initial, and believing there was is what made `_updateRole` write\n * one.\n */\n private _renderAvatar(): void {\n const avatar = this.querySelector('.aparte-avatar') as HTMLElement | null;\n if (!avatar) return;\n\n // Tear down any previously-mounted live component before re-rendering.\n if (this._avatarCleanup) {\n try { this._avatarCleanup(); } catch { /* ignore */ }\n this._avatarCleanup = null;\n }\n\n const provider = this._cfg.getAvatarProvider();\n if (!provider) return; // leave the slot as the shell rendered it\n\n avatar.textContent = '';\n const cleanup = provider.render(this._role, avatar);\n if (typeof cleanup === 'function') this._avatarCleanup = cleanup;\n }\n\n private _updateRole(): void {\n const message = this.querySelector('.aparte-message');\n const avatar = this.querySelector('.aparte-avatar');\n const nameEl = this.querySelector('.aparte-name');\n\n if (message) {\n message.setAttribute('data-role', this._role);\n message.setAttribute('aria-label', this._getAriaLabel());\n }\n if (avatar) {\n avatar.setAttribute('data-role', this._role);\n // Refresh an initial that is ALREADY there; never create one — the default\n // shell renders this slot empty and the stylesheet hides it while it stays\n // empty. Same rule as `_updateName`, which is where it was actually costing\n // something; the reasoning is written out there.\n if (avatar.textContent) avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) {\n nameEl.textContent = this._getDisplayName();\n }\n // Re-render the action bar so buttons match the correct role\n // (critical when the role attribute is set after connectedCallback)\n this._updateActionBar();\n this._renderAvatar();\n }\n\n private _updateName(): void {\n const avatar = this.querySelector('.aparte-avatar') as HTMLElement | null;\n const nameEl = this.querySelector('.aparte-name');\n /*\n * Two conditions, and the second one is the fix.\n *\n * No provider: otherwise a name change would wipe a live avatar component.\n *\n * Already non-empty: the default shell renders this slot EMPTY and the\n * stylesheet hides it while it stays empty — `.aparte-avatar:empty { display:\n * none }`, with the comment \"No message avatar by default — the slot only shows\n * once an AvatarProvider (or a consumer) fills it\". Writing the initial\n * unconditionally contradicted that, and `_onConfigChange` calls this method, so\n * ANY notifying config change filled the slot: `setLocale` (a language switcher\n * is enough), `setBubbleActions`, `setIconProvider`. Avatars appeared across the\n * transcript on a click that had nothing to do with them, and undoing the click\n * did not remove them, because the text was already written.\n *\n * The guard is \"already non-empty\" rather than \"no provider\" on purpose:\n * `avatarInitial` is part of the shell contract, so a CUSTOM shell may render an\n * initial and must still see it refreshed. Empty stays empty; filled stays in\n * sync.\n */\n if (avatar && avatar.textContent && !this._cfg.getAvatarProvider()) {\n avatar.textContent = this._getAvatarInitial();\n }\n if (nameEl) nameEl.textContent = this._getDisplayName();\n }\n\n private _updateContent(): void {\n if (!this._contentEl) return;\n\n // If we have segments, don't render simple content\n if (this._segments.length > 0) {\n this._contentEl.style.display = 'none';\n this._updateWaiting();\n return;\n }\n\n this._contentEl.style.display = '';\n /*\n * The SAME incremental seam the text and thinking segment renderers use.\n *\n * This line used to be `innerHTML = renderMarkdown(this._content)` — the whole message\n * re-parsed, re-sanitised and re-inserted on every token. That is the hot path of the\n * first thing getting-started teaches (`appendMessage` / `appendToken` /\n * `completeMessage`), and it made a published promise false: `setStreamingMarkdownProvider`\n * says \"the chat bubble uses it to render the assistant message token-by-token\n * instead of re-parsing the whole string on every token\", and the plugin's own page\n * repeats it. Only the segment path honoured it. Found by a cold audit.\n *\n * With no streaming provider registered, `writeStreamedMarkdown` falls through to the\n * one-shot render — so a consumer who has not installed the plugin sees exactly what\n * they saw before.\n *\n * `runWithConfig`, because the seam reads its provider from the ambient config and this\n * bubble may be one of several with configs of their own.\n */\n runWithConfig(this._cfg, () =>\n writeStreamedMarkdown(this as AparteMarkdownStreamHost, this._contentEl!, this._content, this._streaming),\n );\n // The first token retires the waiting indicator (and a cleared content brings\n // it back, e.g. a retry that resets the bubble before re-streaming).\n this._updateWaiting();\n // The Markdown provider only emits plain <pre><code>; apply the registered\n // syntax highlighter (if any) to those blocks. Skipped while streaming —\n // re-run once on completion (see _updateStreaming) to avoid per-token churn.\n if (!this._streaming) this._highlightContentCode();\n }\n\n /**\n * Apply the registered syntax-highlight provider to the code blocks produced\n * by the Markdown provider in the simple-content path. Provider-agnostic: a\n * full-block provider (e.g. Shiki) returns `<pre>…</pre>` so we replace the\n * element; a token provider (e.g. Prism, highlight.js) returns inner HTML so\n * we fill the existing `<code>`. No-op when no highlighter is installed.\n */\n private _highlightContentCode(): void {\n if (!this._contentEl || !this._cfg.hasHighlightProvider()) return;\n this._contentEl.querySelectorAll('pre > code').forEach((codeEl) => {\n const code = codeEl.textContent ?? '';\n if (!code.trim()) return;\n const match = codeEl.className.match(/language-([\\w+#-]+)/i);\n const lang = match?.[1] ?? '';\n const pre = codeEl.parentElement;\n Promise.resolve(this._cfg.highlightCode(code, lang)).then((html) => {\n const out = (html ?? '').trim();\n if (!out || !pre || !pre.isConnected) return;\n if (/^<pre[\\s>]/i.test(out)) {\n pre.outerHTML = out; // full block (Shiki)\n } else {\n (codeEl as HTMLElement).innerHTML = out; // inner tokens (Prism, hljs)\n }\n }).catch(() => { /* keep the plain block on failure */ });\n });\n }\n\n private _renderSegments(): void {\n if (!this._segmentsEl) return;\n\n // Clear existing segments\n this._segmentsEl.innerHTML = '';\n\n for (const segment of this._segments) {\n const renderer = resolveSegmentRenderer(segment.type, this._cfg);\n if (renderer) {\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => renderer.render(segment)), segment);\n if (el) {\n this._segmentsEl.appendChild(el);\n runWithConfig(this._cfg, () => renderer.setup?.(el, segment));\n }\n } else {\n this._segmentsEl.appendChild(unrenderedSegment(segment));\n }\n }\n\n // Hide simple content when segments are present\n if (this._contentEl) {\n this._contentEl.style.display = this._segments.length > 0 ? 'none' : '';\n }\n this._reflectError();\n }\n\n /**\n * Reflect the error state on the bubble: `data-error` on `.aparte-message` while\n * an error segment is present. Derived from segments (not the message `status`\n * attribute) so it works identically in vanilla and in every wrapper — the\n * error segment flows through the reactive list in all of them. CSS themes\n * `.aparte-message[data-error]`; custom error content is via setErrorRenderer.\n */\n private _reflectError(): void {\n const message = this.querySelector('.aparte-message');\n if (!message) return;\n const hasError = this._segments.some(s => s.type === 'error');\n if (hasError) message.setAttribute('data-error', '');\n else message.removeAttribute('data-error');\n }\n\n private _updateTimestamp(value: string | number | null): void {\n const timestampEl = this.querySelector('.aparte-timestamp');\n if (!timestampEl || !value) return;\n\n try {\n const date = new Date(isNaN(Number(value)) ? value : Number(value));\n // The locale's own tag, not `undefined`. `undefined` means \"follow the\n // BROWSER\", which is why a French chat on an en-US browser still read\n // `7:32 PM` — the app had chosen a language and the clock had not heard.\n // Still `undefined` when no tag is declared: that is the documented default\n // and the behaviour every consumer has today.\n timestampEl.textContent = date.toLocaleTimeString(this._cfg.getLocale().tag || undefined, {\n hour: '2-digit',\n minute: '2-digit'\n });\n } catch {\n timestampEl.textContent = '';\n }\n }\n\n private _getAriaLabel(): string {\n const locale = this._cfg.getLocale();\n return this._role === 'user'\n ? (locale.yourMessage ?? 'Your message')\n : (locale.assistantResponse ?? 'Assistant response');\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Attachments\n // ─────────────────────────────────────────────────────────────────────────\n\n private _updateAttachments(): void {\n if (!this._attachmentsEl) return;\n\n if (this._role !== 'user' || this._attachments.length === 0) {\n this._attachmentsEl.hidden = true;\n this._attachmentsEl.innerHTML = '';\n return;\n }\n\n this._attachmentsEl.hidden = false;\n\n // Custom attachment chips (aparteGlobalConfig.setAttachmentRenderer) — one node per\n // attachment; the consumer owns markup + interactions (no default preview wiring).\n const customAttachment = this._cfg.getAttachmentRenderer?.();\n if (customAttachment) {\n this._attachmentsEl.replaceChildren();\n for (const a of this._attachments) {\n const el = segmentRenderResultToElement(runWithConfig(this._cfg, () => customAttachment(a)));\n if (el) this._attachmentsEl.appendChild(el);\n }\n return;\n }\n\n this._attachmentsEl.innerHTML = this._attachments.map(a => {\n const name = escapeHtml(a.name);\n if (a.type.startsWith('image/')) {\n // `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-thumb__img\" src=\"${escapeHtml(a.url)}\" alt=\"${name}\" loading=\"lazy\" />`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }\n return `<div class=\"aparte-thumbnail aparte-thumb aparte-thumb--file\" title=\"${name}\">`\n + `<span class=\"aparte-thumb__ext\">${escapeHtml(this._fileExt(a.name))}</span>`\n + `<span class=\"aparte-thumb__name\">${name}</span></div>`;\n }).join('');\n\n // Image tiles ask for a full-size preview — but the lightbox is the app's, so\n // the tile only becomes a button once the app declared it opens one. Otherwise\n // it stays a plain picture: no role, no tab stop, no pointer (see the CSS,\n // which keys the cursor off role=\"button\").\n if (!this._cfg.getHostHandlers().attachmentPreview) return;\n this._attachmentsEl.querySelectorAll('.aparte-thumb--image').forEach(tile => {\n tile.setAttribute('role', 'button');\n tile.setAttribute('tabindex', '0');\n const open = (): void => {\n const img = tile.querySelector('.aparte-thumb__img') as HTMLImageElement | null;\n if (!img) return;\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true, composed: true,\n detail: { url: img.src, name: tile.getAttribute('title') ?? '' },\n }));\n };\n tile.addEventListener('click', open);\n tile.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key !== 'Enter' && key !== ' ') return;\n e.preventDefault();\n open();\n });\n });\n }\n\n /** Uppercased file extension (≤4 chars), or 'FILE' when there is none. */\n private _fileExt(filename: string): string {\n const dot = filename.lastIndexOf('.');\n return dot > 0 ? filename.slice(dot + 1).toUpperCase().slice(0, 4) : 'FILE';\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Branch Picker\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * The branch arrows are handled by DELEGATION, on this element, bound once.\n *\n * They used to get a fresh listener each, attached by `_render()` to the buttons\n * `_render()` had just created. So a click that landed while a re-render was\n * swapping those nodes hit an element about to be discarded, and did nothing at\n * all — not late, nothing. Invisible on a fast machine; reproducible on\n * WebKit-Linux in CI, where `‹` left the picker on \"2 / 2\" and a 20-second\n * assertion watched it stay there.\n *\n * Delegation makes `_render()` irrelevant to it: the listener lives on the host,\n * which is never replaced, and `closest()` finds whichever button exists at the\n * moment of the click. It is also less work — one listener per bubble instead of\n * two per bubble per render.\n *\n * Bound in `connectedCallback` and removed in `disconnectedCallback` as a stable\n * field, because an inline arrow re-added on every re-connect is how this repo has\n * stacked listeners twice before (the viewport, and `aparte-select`).\n */\n private _onBranchPickerClick = (event: Event): void => {\n const target = event.target as HTMLElement | null;\n const button = target?.closest?.('.aparte-branch-prev, .aparte-branch-next');\n if (!button || !this.contains(button)) return;\n const direction = button.classList.contains('aparte-branch-prev') ? 'prev' : 'next';\n const messageId = this.getAttribute('message-id');\n if (!messageId) return;\n const detail: AparteBranchNavigateEventDetail = { messageId, direction };\n // Tree-based navigation: let the viewport handle the branch switch\n this.dispatchEvent(new CustomEvent<AparteBranchNavigateEventDetail>('aparte-branch-navigate', {\n bubbles: true,\n composed: true,\n detail,\n }));\n };\n\n /**\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 (issue #38). 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 private _updateActionBar(): void {\n if (!this._actionBarEl) return;\n // While the inline editor is open the bar shows save (✓) / cancel (✗).\n if (this._editing) {\n this._renderEditActions();\n return;\n }\n const config = this._cfg.getBubbleActions();\n const icons = this._cfg.getIconProvider();\n const locale = this._cfg.getLocale();\n const buttons: string[] = [];\n\n if (this._role === 'user') {\n if (config.user) {\n // Explicit ordered set replaces the flag defaults for user bubbles.\n for (const a of config.user) buttons.push(this._actionButtonHtml(a, icons, locale));\n } else {\n // Flag-driven set. Only `copy` is on by default — see\n // APARTE_DEFAULT_BUBBLE_ACTIONS: edit needs a host to keep the new text.\n if (config.copy) buttons.push(this._actionButtonHtml('copy', icons, locale));\n if (config.edit) buttons.push(this._actionButtonHtml('edit', icons, locale));\n }\n } else if (this._role === 'assistant') {\n if (config.assistant) {\n // Explicit ordered set replaces the flag defaults (incl. the info button).\n for (const a of config.assistant) buttons.push(this._actionButtonHtml(a, icons, locale));\n } else {\n // Flag-driven set. Only `copy` is on by default — retry, feedback and\n // info all need a host or a listener to mean anything.\n if (config.copy) buttons.push(this._actionButtonHtml('copy', icons, locale));\n if (config.retry) buttons.push(this._actionButtonHtml('retry', icons, locale));\n if (config.feedback) {\n buttons.push(this._actionButtonHtml('thumbUp', icons, locale));\n buttons.push(this._actionButtonHtml('thumbDown', icons, locale));\n }\n if (config.info) buttons.push(this._actionButtonHtml('info', icons, locale));\n }\n }\n\n this._actionBarEl.innerHTML = buttons.join('');\n\n // Custom actions registered via aparteGlobalConfig.registerAction — appended\n // after the built-ins, built as DOM (label goes to attributes, never\n // interpolated into innerHTML) so a consumer label can't inject markup.\n this._appendCustomActions(icons);\n 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 * The bar has announced itself as a toolbar since it existed, and a toolbar is ONE\n * tab stop whose members are reached with the arrow keys — that is the whole of what\n * the role means to a screen-reader user. What shipped was five independent tab\n * stops per message, so the role described a behaviour that did not exist: a reader\n * told to press Right heard nothing move, and tabbing through a long transcript\n * walked 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 already run through (the flag-driven bar, the edit\n * bar, and the branch picker's own visibility pass). Re-deriving it there rather\n * than in each builder is what keeps the invariant true after a `setBubbleActions`\n * rebuild — the drift this pattern usually dies of.\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 was still a\n * `role=\"toolbar\"` with nothing in it (announced as such), and it still reserved\n * its fixed height plus the footer's under every bubble. So both follow their\n * contents — the footer stays as long as the branch picker or the bar has\n * something to show.\n */\n private _syncFooterVisibility(): void {\n if (this._actionBarEl) this._actionBarEl.hidden = this._actionBarEl.children.length === 0;\n 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(icons: ReturnType<AparteConfig['getIconProvider']>): void {\n if (!this._actionBarEl) return;\n for (const a of this._cfg.getActions('bubble')) {\n const roles = a.bubble?.roles ?? ['user', 'assistant'];\n if (!roles.includes(this._role)) continue;\n const btn = document.createElement('button');\n btn.className = 'aparte-btn aparte-btn--icon aparte-action-btn aparte-action-custom';\n btn.dataset['action'] = `custom:${a.id}`;\n // aria-label/title via setAttribute — safe for consumer-provided strings.\n btn.setAttribute('aria-label', a.label);\n btn.setAttribute('title', a.label);\n // Icon: raw inline SVG/HTML, else an icon-provider key (trusted output).\n const fromProvider = (icons as unknown as Record<string, (() => string) | undefined>)[a.icon];\n btn.innerHTML = a.icon.startsWith('<')\n ? a.icon\n : (typeof fromProvider === 'function' ? fromProvider() : (a.iconFallback ?? ''));\n this._actionBarEl.appendChild(btn);\n }\n }\n\n /** Build the `<button>` HTML for a single named action (shared by flag + per-role rendering). */\n private _actionButtonHtml(\n action: string,\n icons: ReturnType<AparteConfig['getIconProvider']>,\n locale: ReturnType<AparteConfig['getLocale']>,\n ): string {\n switch (action) {\n case 'copy': {\n const l = locale.copy ?? 'Copy';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-copy\" data-action=\"copy\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.copy()}</button>`;\n }\n case 'edit': {\n const l = locale.edit ?? 'Edit message';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-edit\" data-action=\"edit\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.edit()}</button>`;\n }\n case 'retry': {\n const l = locale.retry ?? 'Retry';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-retry\" data-action=\"retry\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.retry()}</button>`;\n }\n case 'thumbUp': {\n const l = locale.feedbackPositive ?? 'Good response';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-pos\" data-action=\"feedback-positive\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.thumbUp()}</button>`;\n }\n case 'thumbDown': {\n const l = locale.feedbackNegative ?? 'Bad response';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-neg\" data-action=\"feedback-negative\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${icons.thumbDown()}</button>`;\n }\n case 'info': {\n // Only when there are numbers to show: a details button over nothing is a\n // dead button. The popover itself is the app's (see `aparte-message-info`).\n if (!this._usage) return '';\n const l = locale.messageInfo ?? 'Details';\n return `<button class=\"aparte-btn aparte-btn--icon aparte-action-btn aparte-action-info\" data-action=\"info\" aria-label=\"${escapeAttr(l)}\" title=\"${escapeAttr(l)}\">${this._cfg.getIcon('info')}</button>`;\n }\n default:\n return '';\n }\n }\n\n /** Render the edit-mode action bar: ✓ save (green) + ✗ cancel (red). */\n private _renderEditActions(): void {\n if (!this._actionBarEl) return;\n const locale = this._cfg.getLocale();\n const saveLabel = locale.editConfirm ?? 'Save';\n const cancelLabel = locale.editCancel ?? 'Cancel';\n this._actionBarEl.innerHTML =\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--success aparte-action-btn aparte-action-edit-save\" data-action=\"edit-save\" ` +\n `aria-label=\"${escapeAttr(saveLabel)}\" title=\"${escapeAttr(saveLabel)}\">${this._cfg.getIcon('check')}</button>` +\n `<button class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--danger aparte-action-btn aparte-action-edit-cancel\" data-action=\"edit-cancel\" ` +\n `aria-label=\"${escapeAttr(cancelLabel)}\" title=\"${escapeAttr(cancelLabel)}\">${this._cfg.getIcon('close')}</button>`;\n this._actionBarEl.querySelectorAll('.aparte-action-btn').forEach(btn => {\n btn.addEventListener('click', (e) => this._handleActionClick(e as MouseEvent));\n });\n // Save/cancel must show even when every action flag is off.\n this._syncFooterVisibility();\n }\n\n private _handleActionClick(e: MouseEvent): void {\n const btn = (e.currentTarget as HTMLElement);\n const action = btn.dataset['action'];\n // Read dynamically — attribute may not be set yet at render time\n const messageId = this.getAttribute('message-id');\n\n // Custom actions (aparteGlobalConfig.registerAction) emit a generic aparte-action\n // event carrying the action id — same DOM-event contract as retry/feedback.\n if (action?.startsWith('custom:') && messageId) {\n const actionId = action.slice('custom:'.length);\n const detail: AparteActionEventDetail = {\n actionId,\n zone: 'bubble',\n messageId,\n role: this._role,\n targetId: this._resolveTargetId(),\n };\n this.dispatchEvent(new CustomEvent<AparteActionEventDetail>('aparte-action', {\n bubbles: true, composed: true, detail,\n }));\n this._cfg.getActions('bubble').find(x => x.id === actionId)?.onClick?.(e);\n return;\n }\n\n switch (action) {\n case 'copy': {\n // The reply, not the reasoning: a `thinking` segment is the model's scratchpad,\n // and the client already keeps it out of the history it sends back\n // (`_segmentsToText`) for the same reason. Copying it along with the answer\n // pasted a paragraph of deliberation above every reply — no assistant on the\n // market does that.\n const text = this._content || this._segments\n .filter(s => s.type !== 'thinking')\n .map(s => (s as { content?: string }).content ?? '')\n .join('\\n');\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 this._editing = true;\n this.querySelector('.aparte-message')?.setAttribute('data-editing', '');\n\n const input = document.createElement('aparte-composer-input') as AparteComposerInput;\n input.setAttribute('placeholder', this._cfg.getLocale().edit ?? 'Edit message');\n this._editInput = input;\n\n this._contentEl.style.display = 'none';\n this._contentEl.insertAdjacentElement('afterend', input);\n // `insertAdjacentElement` upgrades + connects synchronously, so the editor is\n // ready — seed it with the current text (autosizes to fit).\n input.setValue(this._content);\n\n // Enter (via the primitive's standalone submit event) saves; Esc cancels.\n input.addEventListener('aparte-composer-submit', () => this._exitEditMode(true));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape' && !e.isComposing) {\n e.preventDefault();\n this._exitEditMode(false);\n }\n });\n\n // Swap the action bar over to ✓ / ✗.\n this._updateActionBar();\n\n input.focusEnd();\n }\n\n /**\n * Leave edit mode. When `save` is true and the text actually changed, emits\n * `aparte-edit`; otherwise restores the original message untouched. Always\n * restores the normal action bar and removes the inline editor.\n */\n private _exitEditMode(save: boolean): void {\n if (!this._editing) return;\n const newContent = this._editInput?.getValue() ?? '';\n const original = this._content;\n\n this._editInput?.remove();\n this._editInput = null;\n if (this._contentEl) this._contentEl.style.display = '';\n this.querySelector('.aparte-message')?.removeAttribute('data-editing');\n this._editing = false;\n this._updateActionBar();\n\n if (save && newContent && newContent !== original) {\n const messageId = this.getAttribute('message-id');\n if (messageId) {\n const detail: AparteEditEventDetail = {\n messageId,\n content: newContent,\n targetId: this._resolveTargetId(),\n };\n this.dispatchEvent(new CustomEvent<AparteEditEventDetail>('aparte-edit', {\n bubbles: true, composed: true,\n detail,\n }));\n }\n }\n }\n\n private _resolveTargetId(): string | undefined {\n // Walk up to the chat host element with an id. Angular's wrapper root IS the\n // `<aparte-chat>` element (its component selector); the plain-root wrappers\n // (React/Vue/Svelte) render a `<div class=\"aparte-chat-container\" data-aparte-chat\n // id=\"…\">` instead — so match `[data-aparte-chat]` too. Without this, retry/edit\n // resolved to `undefined` outside Angular and AparteClient's fallback hit the\n // bare `<aparte-chat-viewport>` (a different message store) → retry regenerated\n // into the void.\n let el: HTMLElement | null = this.parentElement;\n while (el) {\n const tag = el.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || el.hasAttribute?.('data-aparte-chat');\n if (isHost && el.id) return el.id;\n el = el.parentElement;\n }\n return undefined;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Utilities\n // ─────────────────────────────────────────────────────────────────────────\n\n\n private _updateStreaming(streaming: boolean): void {\n const wasStreaming = this._streaming;\n this._streaming = streaming;\n const message = this.querySelector('.aparte-message');\n if (message) {\n message.setAttribute('data-streaming', String(streaming));\n if (streaming) {\n // Signal \"in progress\" to assistive tech; clearing it on completion\n // cues screen readers (via the viewport's aria-live region) to read\n // the finished response.\n message.setAttribute('aria-busy', 'true');\n message.classList.add('aparte-message-streaming');\n } else {\n message.removeAttribute('aria-busy');\n message.classList.remove('aparte-message-streaming');\n }\n }\n this._updateWaiting();\n // Streaming just finished: highlight the final content once (skipped during\n // streaming to avoid re-highlighting on every token).\n if (wasStreaming && !streaming) this._highlightContentCode();\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-bubble')) {\n customElements.define('aparte-chat-bubble', AparteChatBubble);\n}\n","import { AparteConfig } from '../../config/aparte-config.js';\nimport { resolveConfig, runWithConfig } from '../../config/config-context.js';\n\n/**\n * A standalone status line — a light-DOM indicator the APP owns. Nothing in core turns\n * it on: the framework host only ever flips it back OFF (on the first streamed token),\n * and the four wrappers render one inside the viewport driven by their own `isTyping`\n * prop.\n *\n * It dispatches nothing, deliberately: it reports, it does not ask.\n *\n * Use it for a state only the app knows about — \"Searching the docs…\", \"Uploading…\",\n * a queue position. It is NOT the indicator for the gap between a send and the first\n * token: that one is built into the bubble (`.aparte-waiting`, shown while `streaming`\n * is set on a non-user bubble that has nothing to display yet). Turning this element on\n * for that gap is how a page ends up showing two indicators for one wait.\n *\n * It does not project children: `_render()` writes the subtree — a\n * `.aparte-status-container` row holding an empty avatar div and an `.aparte-body` that\n * wraps `.aparte-status-content` — so markup authored between the tags does not\n * survive. That avatar div never gets contents here, and `.aparte-avatar:empty` is\n * `display: none`, so it is not a spacer: the line sits flush with the row padding\n * rather than indented under an assistant bubble's text column.\n *\n * The seam for custom contents is `setStatusRenderer`, scoped or global: the container\n * keeps owning show/hide (`data-visible`), the accessible name (`aria-label`) and its\n * `.aparte-message` row metrics whatever the renderer returns, but the pulsing dot and\n * the text node belong to the default path only.\n *\n * A `text` attribute renders the visible label and feeds the accessible name; with no\n * `text` the line is dots-only and the name falls back to the literal `Typing` — this\n * element does not read the locale. Hiding happens twice over: the host element is\n * `display: none` without `[visible]`, and `data-visible` drives the fade/translate on\n * the container.\n *\n * The config is resolved live rather than cached, so a `setStatusRenderer` call that\n * lands after this element has already upgraded still reaches it: the element\n * re-renders on `aparte-config-change`, filtered to its own config.\n *\n * The two borrowed row variables below have one scope caveat: inside a viewport\n * narrower than 520px core REASSIGNS `--aparte-message-padding` on `.aparte-message`\n * itself, so a declaration on this host element loses to it there.\n *\n * @element aparte-chat-status\n * @attr {boolean} visible - Shows or hides the indicator.\n * @attr {string} text - The line to show. Absent, the line is dots-only and the\n * accessible name falls back to the literal `Typing` (not the locale's string).\n *\n * @cssprop [--aparte-status-color=var(--aparte-text-muted)] - Colour of the label text and of the pulsing dot in the default line.\n * @cssprop [--aparte-status-font-size=13px] - Size of the visible label (italic by default) in the default line.\n * @cssprop [--aparte-status-dot-size=6px] - Diameter of the single pulsing dot in the default line.\n * @cssprop [--aparte-message-padding=16px 12px] - Padding of the row, read because the container also carries `.aparte-message` — the status line borrows a bubble's row metrics so it lines up with the transcript.\n * @cssprop [--aparte-message-max-width=800px] - Width cap of that same row.\n *\n * @example\n * <!-- The app owns this indicator: core turns it on for nobody, which is also why it\n * is the wrong tool for the wait before the first token — the bubble's built-in\n * waiting state already covers that one. -->\n * <aparte-chat-status visible text=\"Searching the docs…\"></aparte-chat-status>\n */\nexport class AparteChatStatus extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['visible', 'text'];\n }\n\n /**\n * Resolved LIVE, not cached. Caching it at connect made this element\n * permanently deaf to its own instance: `_onConfigChange` filters on\n * `detail.config !== this._cfg`, so once `_cfg` had latched the global config no\n * change for the real instance ever matched, and the filter meant to isolate\n * chats became the thing that silenced one.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n\n constructor() {\n super();\n }\n\n connectedCallback(): void {\n // Cache the resolved config (instance boundary or global fallback), like the\n // other Aparte elements — so a scoped setStatusRenderer applies here too.\n this._render();\n // Re-render on a live config change (e.g. setStatusRenderer called after this\n // element already upgraded — it self-registers on import, so a persistent\n // <aparte-chat-status> in the page mounts before any config runs). Mirrors the\n // bubble's config-change subscription.\n window.addEventListener('aparte-config-change', this._onConfigChange);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener('aparte-config-change', this._onConfigChange);\n }\n\n private _onConfigChange = (e: Event): void => {\n // Only react to OUR config (an instance-scoped change elsewhere must not touch\n // us). A bare notify (no detail.config) always re-renders.\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n // Clear so _render's re-entrancy guard doesn't bail; visible/text are read\n // from attributes, so the shown state is preserved across the re-render.\n this.innerHTML = '';\n this._render();\n };\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'visible':\n this._updateVisibility(newValue !== null);\n break;\n case 'text':\n this._updateText(newValue);\n break;\n }\n }\n\n /**\n * Show the typing indicator\n */\n show(): void {\n this.setAttribute('visible', '');\n }\n\n /**\n * Hide the typing indicator\n */\n hide(): void {\n this.removeAttribute('visible');\n }\n\n /**\n * Toggle visibility\n */\n toggle(): void {\n if (this.hasAttribute('visible')) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n /**\n * Check if visible\n */\n isVisible(): boolean {\n return this.hasAttribute('visible');\n }\n\n private _render(): void {\n const text = this.getAttribute('text') || 'Typing';\n const visible = this.hasAttribute('visible');\n\n // Re-entrancy check\n if (this.querySelector('.aparte-status-container')) return;\n\n // Custom typing indicator (charter §6 render hook): replace the inner markup\n // while the container keeps owning show/hide (data-visible) + accessible name.\n const custom = this._cfg?.getStatusRenderer?.();\n if (custom) {\n this.innerHTML =\n `<div class=\"aparte-message aparte-status-container\" data-visible=\"${visible}\" role=\"status\" aria-live=\"polite\"></div>`;\n const container = this.querySelector('.aparte-status-container') as HTMLElement;\n // `text` set via setAttribute, never interpolated — a `\"` would break out.\n container.setAttribute('aria-label', text);\n const result = runWithConfig(this._cfg, () => custom(text));\n if (result instanceof HTMLElement) container.appendChild(result);\n else container.innerHTML = result;\n return;\n }\n\n this.innerHTML = `\n <div\n class=\"aparte-message aparte-status-container\"\n data-visible=\"${visible}\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <div class=\"aparte-avatar\" data-role=\"assistant\" style=\"visibility: hidden\"></div>\n <div class=\"aparte-body\">\n <div class=\"aparte-status-content\">\n <div class=\"aparte-dots\" aria-hidden=\"true\">\n <span class=\"aparte-dot\"></span>\n </div>\n <span class=\"aparte-status-text\"></span>\n </div>\n </div>\n </div>\n `;\n // Set the (public, attacker-controllable) `text` via setAttribute/textContent\n // rather than interpolating it into the innerHTML template — a `\"` in the\n // attribute would otherwise break out and inject arbitrary attributes.\n this.querySelector('.aparte-status-container')?.setAttribute('aria-label', text);\n // Visible text only when explicitly requested — the default stays dots-only\n // (the aria-label above always carries the accessible name).\n if (this.hasAttribute('text')) {\n const textEl = this.querySelector('.aparte-status-text');\n if (textEl) textEl.textContent = text;\n }\n }\n\n private _updateVisibility(visible: boolean): void {\n const container = this.querySelector('.aparte-status-container');\n if (container) {\n container.setAttribute('data-visible', String(visible));\n }\n }\n\n private _updateText(text: string | null): void {\n const textEl = this.querySelector('.aparte-status-text');\n const container = this.querySelector('.aparte-status-container');\n if (!container) return; // not rendered yet — _render() reads the attribute\n // Removing the attribute restores the dots-only default (empty visible\n // text); the aria-label always keeps an accessible name.\n if (textEl) textEl.textContent = text ?? '';\n container.setAttribute('aria-label', text || 'Typing');\n }\n}\n\n// Register the custom element\nif (!customElements.get('aparte-chat-status')) {\n customElements.define('aparte-chat-status', AparteChatStatus);\n}\n","import type {\n AparteMessage,\n AparteViewportConfig,\n AparteSegment,\n AparteSegmentUpdateEventDetail,\n ApartePathChangedEventDetail,\n AparteSiblingInfo,\n AparteUsage,\n} from '../../types/index.js';\nimport { resolveConfig } from '../../config/index.js';\nimport { AparteMessageRepository } from '../../runtime/message-repository.js';\nimport type { ExportedMessageRepository } from '../../runtime/message-repository.js';\nimport { populateBubbleFromMessage, type SyncableBubble } from '../bubble/bubble-sync.js';\nimport { cssEscape } from '../../utils/css-escape.js';\nimport { isAwaitingReply } from '../../utils/is-awaiting-reply.js';\nimport { revokeAttachmentUrls } from '../../utils/files-to-attachments.js';\nimport { uuid } from '../../utils/uuid.js';\nimport {\n stampSegmentOnInsert,\n adoptMessageSegments,\n stampSegmentOnUpdate,\n mergeSegmentUpdate,\n renumberSegments,\n openSegmentIds,\n stampSegmentActivity,\n isTerminalStatus,\n} from '../../utils/segments.js';\n\n/**\n * The transcript surface: a light-DOM container with sticky scrolling, token\n * streaming and segment-aware rendering.\n *\n * Features:\n * - Smart Scroll: Sticks to bottom when user is at bottom, stops on manual scroll up\n * - appendToken(): For simple content streaming\n * - appendToSegment(): For segment-aware streaming (thinking, code, etc.)\n *\n * Two DOM modes. By default the element builds its own scroll surface\n * (`.aparte-viewport-container`) around a `.aparte-messages-wrapper`, and creates the\n * `<aparte-chat-bubble>` elements itself (the last `max-rendered-bubbles` of the active\n * path). With `framework-managed` set it builds neither wrapper: the HOST is the scroll\n * surface, the framework owns the bubble elements, and the bottom spacer becomes additive\n * host padding instead of an element — a relocated or removed child is what desynchronises\n * a framework's view tree from the live DOM, so this mode touches neither. The one child\n * it appends in both modes is the scroll-to-bottom button, kept trailing.\n *\n * Children you write inside the element are just children: there is no shadow root and no\n * slot to target. In the default mode they are MOVED into the internal\n * `.aparte-messages-wrapper` at first render, ahead of the bottom spacer, so pre-rendered\n * `<aparte-chat-bubble>` elements land in the transcript flow. A custom element of your own\n * is relocated the same way, and if it carries `data-aparte-bubble` plus a matching\n * `message-id` it also receives the live token and segment pushes, not just a restyle.\n * Do not expect such a child to outlive the transcript, though: anything that re-renders the\n * active path (`addBranch`, `addSiblingOf`, `navigateBranch`, `importTree`) empties the\n * wrapper and rebuilds it from the repository, so only what the repository holds comes back —\n * and `clearAll()` removes `<aparte-chat-bubble>` nodes only, so a `[data-aparte-bubble]`\n * element of your own is left behind with nothing left to render. With `framework-managed`\n * set children are not relocated: they stay direct children of the host, which is itself the\n * scroll surface.\n *\n * Messages are held as a TREE (siblings, branches, an active path), which is what lets\n * a retry fork and a bubble's sibling picker navigate with no host object involved.\n *\n * What it is NOT is storage. `max-rendered-bubbles` is a DOM ceiling and never evicts\n * from the repository — the full tree and its snapshot stay complete, `exportTree()` /\n * `importTree()` hand that snapshot to whoever owns persistence, and real history\n * retention is configured on the conversation manager instead. It is not a chat either:\n * a bare viewport IS a valid `AparteClient` target, but the composer, the transport and\n * the shell layout are other elements.\n *\n * @element aparte-chat-viewport\n *\n * @attr {boolean} framework-managed - The wrapper's explicit hands-off signal: set it and this\n * element builds no wrapper of its own and relocates none of the nodes the FRAMEWORK renders\n * into it, because the framework owns them. Not \"none of its children\": core's own\n * scroll-to-bottom button is re-appended whenever it stops being last, and that path runs in\n * this mode only. All four wrappers set it.\n * @attr {number} scroll-threshold - How close to the bottom still counts as \"at the bottom\".\n * @attr {number} max-rendered-bubbles - Caps how many bubbles stay in the DOM; older ones are released.\n *\n * @fires {CustomEvent<AparteSegmentUpdateEventDetail>} aparte-segment-update - A segment grew or settled during a stream.\n * @fires aparte-reset-done - `clearAll()` finished emptying the transcript. No detail.\n * @fires {CustomEvent<ApartePathChangedEventDetail>} aparte-path-changed - The active branch path changed, after a retry fork or a navigation.\n *\n * @cssprop [--aparte-viewport-padding=16px] - Padding around the transcript — on\n * `.aparte-messages-wrapper`, or on the host itself in framework-managed mode, where the\n * auto-scroll spacer is added on top of it. A container narrower than 520px tightens it in\n * the default mode only: that rule reassigns the variable on `.aparte-messages-wrapper`,\n * which framework-managed mode never builds.\n * @cssprop [--aparte-message-gap=12px] - Gap between consecutive bubbles in the transcript\n * column (both DOM modes). Shared: it is also the avatar-to-content gap inside a bubble.\n * @cssprop [--aparte-scrollbar-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-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=36px] - Diameter of the scroll-to-bottom button. A\n * coarse pointer raises it to `--aparte-touch-target-size`.\n * @cssprop [--aparte-scroll-btn-shadow=0 2px 8px rgba(0, 0, 0, 0.12)] - Its shadow; the dark\n * theme sets a heavier one.\n *\n * @example\n * <!-- On its own, outside `<aparte-chat>`. Give it a height: it fills what it is given\n * and owns the scrolling inside that box, so a viewport in an auto-height parent\n * grows forever instead of scrolling. Messages are pushed in — it fetches nothing. -->\n * <aparte-chat-viewport style=\"height: 320px\"></aparte-chat-viewport>\n *\n * <script>\n * const viewport = document.querySelector('aparte-chat-viewport');\n * viewport.appendMessage({ id: 'u1', role: 'user', content: 'What is a transport?', timestamp: Date.now() });\n * viewport.appendMessage({\n * id: 'a1',\n * role: 'assistant',\n * content: 'The object that talks to the model. Swap it and the UI does not change.',\n * timestamp: Date.now(),\n * });\n * </script>\n *\n * @example\n * // Three calls are a whole streamed turn.\n * const viewport = document.querySelector('aparte-chat-viewport')!;\n *\n * viewport.appendMessage({ id: 'a1', role: 'assistant', content: '', timestamp: Date.now() });\n * for await (const chunk of tokens) viewport.appendToken('a1', chunk);\n * viewport.completeMessage('a1'); // stops the streaming caret\n */\nexport class AparteChatViewport extends HTMLElement {\n // The scroll surface: an internal `.aparte-viewport-container` div (core mode)\n // or the host element itself (framework-managed mode). HTMLElement covers both.\n private _container: HTMLElement | null = null;\n private _scrollBtn: HTMLButtonElement | null = null;\n private _bottomSpacer: HTMLDivElement | null = null;\n /**\n * In framework-managed mode there is no spacer ELEMENT (an extra child would\n * collide with the framework's own DOM reconciliation). The spacer is an\n * additive `padding-bottom` on the host, tracked here so `_recalculateSpacer`\n * can read the current value without measuring an element.\n */\n private _fwSpacerHeight = 0;\n private _spacerRafId: number | null = null;\n private _spacerFrozenUntil: number = 0;\n private _layoutTransitionMs: number = 0;\n private _repo = new AparteMessageRepository();\n private _isAutoScrollEnabled: boolean = true;\n /** The last scroll position we saw, so growth can be told from a gesture. */\n private _lastScrollTop = 0;\n /** The scroll height at the last scroll event: how much it moved since bounds what churn can do. */\n private _lastScrollHeight = 0;\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 /** 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 /** 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 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'];\n }\n\n constructor() {\n super();\n this._handleScroll = this._handleScroll.bind(this);\n }\n\n connectedCallback(): void {\n // Framework wrappers set `framework-managed` DECLARATIVELY so the flag is\n // known BEFORE _render() builds the DOM. Otherwise _render()'s child\n // relocation runs at connect — before the host's setFrameworkManagedDOM()\n // call — moving the framework's bubbles into an internal wrapper and\n // breaking its reconciliation (insertBefore NotFoundError on the next\n // append). See _setupFrameworkDOM().\n if (this.hasAttribute('framework-managed')) this._frameworkManagedDOM = true;\n this._render();\n this._setupEventListeners();\n this._setupObservers();\n /**\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` was applied once at\n * render — so a chat already on screen never flipped to RTL until a reload.\n * Only OUR config: an instance-scoped change elsewhere must not touch us.\n */\n private _onConfigChange = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== resolveConfig(this)) return;\n this._applyDirection();\n // 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 — was unreadable\n * past the first screen for a Safari keyboard user, with no error and nothing on\n * screen to say so.\n *\n * `tabindex=\"0\"` on the surface itself, in BOTH DOM modes. The framework mode\n * appeared to work already, and only by accident: the scroll button is inside the\n * host and stays tabbable while it is visually hidden, so Tab happened to land\n * somewhere that scrolled. A tab stop that exists because a hidden button happens\n * to sit there is not an affordance, it is a coincidence.\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 }\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 /** The id of the message currently streaming, if any. */\n private _streamingMessageId(): string | null {\n for (const message of this._repo.getMessages()) {\n if ((message as { isStreaming?: boolean }).isStreaming) return message.id;\n }\n return null;\n }\n\n /**\n * While a reply streams, the transcript is read-only except for Stop.\n *\n * The streaming message's own footer was already hidden; every OTHER message kept\n * its branch picker and its retry/edit buttons live. Swapping a branch mid-stream\n * re-rendered the active path under the reply being written, and a retry cut that\n * reply off to start another — both seen on the landing page. 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 driven by `AparteClient` no longer drops text\n * (the args used to bind one position short, creating a phantom message).\n */\n addSegment(segment: AparteSegment): void;\n addSegment(messageId: string, segment: AparteSegment): void;\n addSegment(messageIdOrSegment: string | AparteSegment, maybeSegment?: AparteSegment): void {\n const messageId = typeof messageIdOrSegment === 'string' ? messageIdOrSegment : this._activeMessageId();\n const segment = typeof messageIdOrSegment === 'string' ? maybeSegment : messageIdOrSegment;\n if (!messageId || !segment) return;\n\n const message = this._getOrCreateMessage(messageId);\n if (!message.segments) {\n message.segments = [];\n }\n // Identity and start time land BEFORE anyone sees the object: the repo and\n // the bubble are handed the same segment, so a later stamp would leave one\n // of the two holding an unstamped copy. This is one of exactly two places\n // that writes those fields (`aparte-chat-host` is the other) — see\n // `utils/segments.ts` for why it is not the parser.\n const stamped = stampSegmentOnInsert(\n message.segments, segment, messageId,\n // THIS chat's defaults, not the page's: two chats on one page can be\n // configured differently, and the config seam is per instance.\n resolveConfig(this).getSegmentDefaults(segment.type),\n );\n message.segments.push(stamped);\n\n // Notify bubble to render the new segment\n this._notifyBubble(messageId, 'addSegment', stamped);\n this._autoScroll();\n }\n\n /**\n * Update a segment. `updateSegment(segmentId, updates)` (1-arg client\n * convention → current message) or `updateSegment(messageId, segmentId,\n * updates)` (explicit). Disambiguated by arity: the 3rd arg is absent and\n * the 2nd is the `updates` object in the 1-arg form.\n */\n updateSegment(segmentId: string, updates: Partial<AparteSegment>): void;\n updateSegment(messageId: string, segmentId: string, updates: Partial<AparteSegment>): void;\n updateSegment(a: string, b: string | Partial<AparteSegment>, c?: Partial<AparteSegment>): void {\n const clientForm = c === undefined && typeof b === 'object';\n const messageId = clientForm ? this._activeMessageId() : a;\n const segmentId = clientForm ? a : (b as string);\n const updates = clientForm ? (b as Partial<AparteSegment>) : (c as Partial<AparteSegment>);\n if (!messageId) return;\n\n const message = this._repo.getMessageById(messageId);\n if (!message?.segments) return;\n\n const segmentIndex = message.segments.findIndex(s => s.id === segmentId);\n if (segmentIndex !== -1) {\n const current = message.segments[segmentIndex]!;\n // An update that settles the segment carries its `endedAt`. Stamped\n // here rather than at each call site, so `completeSegment`, a tool\n // resolution and an app's own `updateSegment` all measure alike.\n const stamped = stampSegmentOnUpdate(current, updates);\n message.segments[segmentIndex] = mergeSegmentUpdate(current, stamped);\n\n this._notifyBubble(messageId, 'updateSegment', { segmentId, updates: stamped });\n }\n }\n\n /**\n * Remove a segment. `removeSegment(segmentId)` (1-arg client convention →\n * current message) or `removeSegment(messageId, segmentId)` (explicit).\n */\n removeSegment(segmentId: string): void;\n removeSegment(messageId: string, segmentId: string): void;\n removeSegment(a: string, b?: string): void {\n const clientForm = b === undefined;\n const messageId = clientForm ? this._activeMessageId() : a;\n const segmentId = clientForm ? a : b;\n if (!messageId || !segmentId) return;\n\n const message = this._repo.getMessageById(messageId);\n if (message?.segments) {\n const idx = message.segments.findIndex(s => s.id === segmentId);\n if (idx !== -1) {\n message.segments.splice(idx, 1);\n // `index` is a position, so a removal has to close the gap it left.\n renumberSegments(message.segments);\n }\n }\n this._notifyBubble(messageId, 'removeSegment', segmentId);\n }\n\n /**\n * Start a new streaming segment (e.g., thinking or code block)\n * Creates the segment and marks it as streaming\n */\n startSegment(messageId: string, segment: AparteSegment): void {\n const streamingSegment = { ...segment, isStreaming: true };\n this.addSegment(messageId, streamingSegment);\n }\n\n /**\n * Complete a streaming segment\n */\n completeSegment(messageId: string, segmentId: string): void {\n this.updateSegment(messageId, segmentId, { isStreaming: false });\n }\n\n /**\n * Persist token usage on a message and propagate to the live bubble, which is\n * what allows the info (\"i\") action to render — provided the app declared it\n * with `aparteGlobalConfig.setBubbleActions({ info: true })`; it is off by default,\n * since the popover it opens belongs to the app.\n */\n setUsage(messageId: string, usage: AparteUsage): void {\n const message = this._repo.getMessageById(messageId);\n if (message) message.usage = usage;\n this._notifyBubble(messageId, 'setUsage', usage);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Message Management\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Mark a message as finished streaming\n */\n completeMessage(messageId: string): void {\n const message = this._repo.getMessageById(messageId);\n if (message) {\n message.isStreaming = false;\n message.status = 'completed';\n\n // The message's end IS its segments' end: nothing in the stream says a\n // thinking block is over. Routed through `updateSegment` rather than\n // written onto the objects, so the bubble is told as well — a silent\n // mutation stamped the model and left the renderer thinking it was still\n // streaming, which is exactly what a browser run showed.\n this._settleSegments(messageId, message);\n\n this._notifyBubble(messageId, 'complete', { status: 'completed' });\n this._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 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 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 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 this._recalculateSpacer();\n // User sending always anchors to bottom regardless of scroll position.\n if (message.role === 'user') {\n this._isAutoScrollEnabled = true;\n // Smooth scroll for user-initiated sends. Streaming auto-scroll stays\n // instant (via _autoScroll) so it can keep up with rapid token bursts.\n requestAnimationFrame(() => { 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 this._repo.import({\n ...tree,\n messages: tree.messages.map((entry) => ({\n ...entry,\n message: adoptMessageSegments(entry.message),\n })),\n });\n this._reRenderActivePath();\n }\n\n /**\n * Clear all messages and remove all bubble elements from the DOM.\n * Also dispatches a aparte-reset-done event.\n *\n * In framework-managed mode the DOM is owned by the host framework\n * (Angular @for, React, etc.) and we must not clear `innerHTML` — doing\n * so desynchronises the framework's view tree from the live DOM and the\n * next change-detection pass throws `NotFoundError` on insertBefore.\n */\n clearAll(options?: { revokeAttachments?: boolean }): void {\n /*\n * Release the attachments' object URLs before dropping the messages: after\n * `_repo.clear()` there is no way left to reach them, and nothing else\n * revoked them — so every `File` a session had sent stayed reachable for\n * the life of the page.\n *\n * UNLESS the caller is about to put the same messages back. Two callers do:\n * `setMessages` and `importTree`, and `ConversationController._load` runs\n * BOTH in sequence over one conversation. `export()` stores live `node.current`\n * references, so `conv.messages` and `conv.tree` share the very same\n * attachment objects — meaning the second clear revoked the object URLs of\n * the conversation being opened. Every image and file chip was dead on load,\n * and re-opening revoked twice.\n *\n * A reset (`aparte-reset`, the public `clearAll()`) still revokes: there the\n * messages really are gone.\n */\n if (options?.revokeAttachments !== false) {\n for (const message of this._repo.getMessages()) {\n revokeAttachmentUrls(message.attachments);\n }\n }\n this._repo.clear();\n // 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\n */\n setAutoScroll(enabled: boolean): void {\n this._isAutoScrollEnabled = enabled;\n }\n\n /**\n * Signal that a framework (e.g. Angular) manages the bubble DOM.\n * When true, branch navigation dispatches `aparte-path-changed` without\n * clearing/rebuilding the messages wrapper — the framework re-renders instead.\n */\n setFrameworkManagedDOM(managed: boolean): void {\n this._frameworkManagedDOM = managed;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private _getOrCreateMessage(messageId: string): AparteMessage {\n const existing = this._repo.getMessageById(messageId);\n if (existing) return existing;\n\n const message: AparteMessage = {\n id: messageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now(),\n isStreaming: true,\n status: 'streaming'\n };\n this._repo.addOrUpdateMessage(this._repo.headId, message);\n return message;\n }\n\n private _notifyBubble(messageId: string, action: string, payload?: unknown, segmentId?: string): void {\n // Find the bubble element — the native `<aparte-chat-bubble>` OR a custom\n // element opting into live streaming via `data-aparte-bubble` (so a raw-core\n // consumer can replace the bubble tag and still receive token/segment\n // pushes, not just a CSS restyle).\n const bubble = this.querySelector(\n `aparte-chat-bubble[message-id=\"${cssEscape(messageId)}\"], [data-aparte-bubble][message-id=\"${cssEscape(messageId)}\"]`,\n ) as HTMLElement & {\n appendToken?: (chunk: string) => void;\n appendToSegment?: (segmentId: string, chunk: string) => void;\n addSegment?: (segment: AparteSegment) => void;\n updateSegment?: (segmentId: string, updates: Partial<AparteSegment>) => void;\n removeSegment?: (segmentId: string) => void;\n setUsage?: (usage: AparteUsage) => void;\n updateMessage?: (updates: Partial<AparteMessage>) => void;\n };\n\n if (!bubble) return;\n\n switch (action) {\n case 'appendToken':\n bubble.appendToken?.(payload as string);\n break;\n case 'appendToSegment':\n bubble.appendToSegment?.(segmentId!, payload as string);\n break;\n case 'addSegment':\n bubble.addSegment?.(payload as AparteSegment);\n break;\n case 'updateSegment': {\n const { segmentId: sid, updates: segUpdates } = payload as { segmentId: string; updates: Partial<AparteSegment> };\n bubble.updateSegment?.(sid, segUpdates);\n break;\n }\n case 'removeSegment':\n bubble.removeSegment?.(payload as string);\n break;\n case 'setUsage':\n bubble.setUsage?.(payload as AparteUsage);\n break;\n case 'update': {\n // Atomic update: forward it when it carries anything the bubble\n // renders. `content` and `attachments` used to be filtered out\n // here, so an edit (which sends `{ content }`) updated the repo —\n // and therefore the history sent to the model — while the bubble\n // kept displaying the old text.\n const updates = payload as Record<string, unknown>;\n const renderable = ['status', 'segments', 'content', 'attachments', 'usage'];\n if (renderable.some((key) => key in updates)) {\n bubble.updateMessage?.(payload as Partial<AparteMessage>);\n }\n break;\n }\n case 'complete':\n bubble.updateMessage?.(payload as Partial<AparteMessage>);\n break;\n }\n }\n\n /**\n * Re-render the active path: clears the messages wrapper and rebuilds bubbles\n * for every message on the current active branch path (root → head).\n * Calls `setSiblings(count, index)` on each bubble that has siblings, and\n * dispatches `aparte-path-changed` so Angular wrapper can sync its signal.\n *\n * When `_frameworkManagedDOM` is true (set via setFrameworkManagedDOM), the DOM\n * manipulation is skipped — only `aparte-path-changed` is dispatched so the\n * framework can re-render from updated signal state.\n */\n private _reRenderActivePath(): void {\n const activeMessages = this._repo.getMessages();\n\n // Compute sibling metadata once and reuse — keeps the event payload\n // identical between framework-managed and default DOM modes.\n const siblingsInfo: AparteSiblingInfo[] = activeMessages.map(m => {\n const sibs = this._repo.getBranches(m.id);\n return { id: m.id, count: sibs.length, index: sibs.indexOf(m.id) };\n });\n\n if (this._frameworkManagedDOM) {\n this._dispatchPathChanged(activeMessages, siblingsInfo);\n return;\n }\n\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (!wrapper) return;\n wrapper.innerHTML = '';\n\n // Only materialise the last N messages of the active path (DOM render cap).\n // The repository keeps the full path; this is a perf ceiling, not eviction.\n const startIdx = Math.max(0, activeMessages.length - this._maxRenderedBubbles);\n for (let i = startIdx; i < activeMessages.length; i++) {\n const message = activeMessages[i]!;\n const sibInfo = siblingsInfo[i];\n\n const bubble = document.createElement('aparte-chat-bubble');\n bubble.setAttribute('message-id', message.id);\n bubble.setAttribute('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 if (this._smoothScrollOnce) {\n this._smoothScrollOnce = false;\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) this._smoothScrollToBottom(); });\n } else {\n requestAnimationFrame(() => { if (this._isAutoScrollEnabled) this._scrollToBottom(); });\n }\n }\n this._pruneRenderedBubbles();\n }\n\n /**\n * Request that the next auto-scroll triggered by a DOM mutation uses\n * smooth behaviour instead of instant. Call this just before adding a\n * user message bubble so the viewport animates down rather than jumping.\n * Resets automatically after the first auto-scroll fires.\n */\n requestSmoothScroll(): void {\n this._smoothScrollOnce = true;\n }\n\n private _render(): void {\n // Framework-managed: the framework owns the bubble children directly.\n // Do NOT build the internal container/wrapper or relocate children.\n if (this._frameworkManagedDOM) {\n this._setupFrameworkDOM();\n return;\n }\n // Light DOM rendering\n // Preserving existing children in render allows framework composition\n if (!this.querySelector('.aparte-viewport-container')) {\n const container = document.createElement('div');\n container.className = 'aparte-viewport-container';\n\n // Set direction based on current locale\n const locale = resolveConfig(this).getLocale();\n if (locale.direction) {\n container.setAttribute('dir', locale.direction);\n }\n\n container.setAttribute('role', 'log');\n container.setAttribute('aria-live', 'polite');\n container.setAttribute('aria-atomic', 'false');\n container.setAttribute('aria-relevant', 'additions');\n 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\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 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 };\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 // 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._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 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(() => {\n // Keep the sticky scroll button trailing after framework appends.\n if (this._frameworkManagedDOM) this._keepScrollButtonLast();\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 });\n\n // Framework-managed: bubbles are direct children of the host (no wrapper).\n const observeTarget = this._frameworkManagedDOM ? this : wrapper;\n if (observeTarget) {\n this._mutationObserver.observe(observeTarget, {\n childList: true,\n subtree: true\n });\n }\n }\n\n /** The overlay shell's element children that are not this viewport — the floating bottom stack. */\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 );\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 /** 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 is bounded by the EVIDENCE of churn — how much the\n * scroll height moved since the last scroll event — rather than by a number: a\n * first version capped it at 100px and a branch swap on React refuted that\n * (the rebuild flickers the height by ~200px, measured in `navigateBranch`,\n * and WebKit moves scrollTop by as much); a second version dropped the cap\n * altogether, and during a stream — where every token refreshes\n * `_ownScrollAt`, so the shadow never closes — a reader drag-selecting text\n * upward, whose press lands on the text and not in the gutter, was snapped\n * back to the bottom by the next token. Churn moves scrollTop by at most the\n * height it changed; a reader, a find-in-page jump or a host's `scrollTo` move\n * it with the height standing still.\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 const settlingOurs = drop <= churn + 2\n && now - this._ownScrollAt < this._ownScrollWindowMs\n && now - this._readerInputAt >= this._ownScrollWindowMs;\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 this._ownScrollAt = performance.now();\n this._container.scrollTop = this._container.scrollHeight;\n this._settleAtBottom();\n }\n\n /**\n * Confirm over the next few frames that we actually reached the bottom.\n *\n * One assignment is not enough, and the reason is measured rather than guessed.\n * A timeline of a streamed turn on Safari (framework mode) recorded the content\n * settling in TWO layout passes — 1118 → 1121 → 1152 px. `scrollTop =\n * scrollHeight` ran against the middle one, clamped to that layout's max (603),\n * and nothing ran afterwards: the last 31px never closed. Auto-follow stayed\n * armed the whole time, so the component was not disarmed — it was SATISFIED.\n * `_isAtBottom()` answers \"yes\" for any gap under `_scrollThreshold` (50), which\n * is the right rule for keeping auto-follow armed and the wrong one as a\n * definition of \"anchored\".\n *\n * Ruled out on the way here, so nobody pays for it twice: not a WebKit\n * padding-accounting difference (a probe writing `scrollTop = 1e7` reached\n * exactly `scrollHeight - clientHeight`), not a missing `characterData`\n * mutation, and not a child resize a ResizeObserver could see.\n *\n * A BOUNDED retry, not one corrective frame: a single frame lands on the same\n * stale layout and was measured leaving a wider gap than doing nothing. Bounded\n * so it always terminates; re-reads `_isAutoScrollEnabled` every frame so a\n * reader who scrolls away mid-settle is left alone.\n *\n * Bounded by TIME, and it does not stop at the first closed gap — both of those\n * were the second half of the same bug. It used to be four frames (~64ms on an\n * idle 60Hz machine) and to return permanently the frame the gap first closed.\n * A branch swap at the bottom of a long transcript on react-webkit falsified\n * both at once: the rebuild's scrollable max churned 891 -> 1091 -> 891, the gap\n * WAS closed for a frame against the tall layout, the chain returned, the height\n * fell back with WebKit holding `scrollTop` at 720 — and the transcript stood\n * 171px short with auto-follow still armed. A frame count is a proxy for time\n * that fails exactly on the slow engine, so the budget is real milliseconds now,\n * and a closed gap only ends the chain when the window is over. One chain, not\n * one per call: during a stream every token used to 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 if (max - this._container.scrollTop > 1) {\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._container.scrollTo({ top: this._container.scrollHeight, behavior: 'smooth' });\n } else {\n this._container.scrollTop = this._container.scrollHeight;\n }\n }\n\n private _prefersReducedMotion(): boolean {\n return typeof matchMedia === 'function'\n && matchMedia('(prefers-reduced-motion: reduce)').matches;\n }\n\n /**\n * Show/hide the scroll-to-bottom button from the **current geometry**, not from\n * `_isAutoScrollEnabled`.\n *\n * The two answer different questions: the flag is intent (\"should new content\n * pull the view down\"), the button is a fact (\"is there anything below the\n * fold\"). Mirroring the flag made the button lie whenever the two diverged —\n * `navigateBranch` deliberately disarms auto-follow, so swapping a branch while\n * already at the bottom of a scrollable transcript left the button offering to\n * scroll nowhere (reported from a React consumer). 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 const needed = this._container.clientHeight - contentBelowUserTop;\n // Hard cap: the spacer can never exceed the visible viewport height.\n // This acts as a safety net against stale layout reads (e.g. mid-swap)\n // that could produce an astronomical value and push content off-screen.\n const maxSpacer = this._container.clientHeight;\n this._setSpacerHeight(Math.min(Math.max(0, needed), maxSpacer));\n\n // Re-scroll after the spacer height changes so scrollTop is always\n // consistent with the new scrollHeight. Without this, the MutationObserver\n // schedules _scrollToBottom() one RAF *before* _recalculateSpacer() runs,\n // leaving scrollTop based on the pre-spacer scrollHeight. On the next\n // recalculation (e.g. from syncMessagesWithBubbles or a resize) the formula\n // reads a stale scrollTop and may grow the spacer to the clientHeight cap.\n if (this._isAutoScrollEnabled) {\n this._scrollToBottom();\n }\n }\n\n /**\n * Schedule a spacer recalculation on the next animation frame.\n * Batches multiple rapid calls (e.g. during token streaming) into one.\n *\n * Single-RAF intentional: both the scroll-to-bottom queued by MutationObserver\n * and this spacer recalculation must land in the *same* frame so the browser\n * paints exactly once — with the correct scroll position *and* the correct\n * spacer height. A double-RAF would put the spacer shrink one frame after the\n * scroll, causing a 1-frame layout jump during streaming.\n */\n private _scheduleSpacerUpdate(): void {\n if (this._spacerRafId !== null) return;\n this._spacerRafId = requestAnimationFrame(() => {\n this._spacerRafId = null;\n this._recalculateSpacer();\n // The DOM just changed (new bubble, streamed token, framework re-render):\n // whether anything sits below the fold changed with it.\n this._updateScrollButton();\n });\n }\n\n /**\n * Cap the number of rendered bubbles in the DOM (perf ceiling only).\n *\n * Drops the oldest `<aparte-chat-bubble>` elements beyond `_maxRenderedBubbles`\n * from the DOM. It **never** touches the AparteMessageRepository — the conversation\n * model and its persistence snapshot stay complete (retention/eviction is a\n * consumer/persistence concern, not the viewport's). No-op when a framework\n * owns the DOM.\n */\n private _pruneRenderedBubbles(): void {\n if (this._frameworkManagedDOM) return;\n const wrapper = this.querySelector('.aparte-messages-wrapper');\n if (!wrapper) return;\n const bubbles = wrapper.querySelectorAll('aparte-chat-bubble');\n const excess = bubbles.length - this._maxRenderedBubbles;\n for (let i = 0; i < excess; i++) {\n bubbles[i]?.remove();\n }\n }\n\n private _cleanup(): void {\n this._container?.removeEventListener('scroll', this._handleScroll);\n for (const type of ['wheel', 'touchmove', 'pointerdown', 'keydown'] as const) {\n this._container?.removeEventListener(type, this._noteReaderInput);\n }\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._overlayObserver?.disconnect();\n this._overlayObserver = null;\n this._overlayRoot = null;\n this._overlayInset = -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';\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. (`'advance'` — the button as a \"next\" through a form — is gone:\n * the chips are the navigation, and a button that changes meaning under the pointer\n * was one more thing to explain.)\n *\n * `'none'` exists because a panel could not previously say it. The composer's panel\n * mode was one fixed policy — hide the input and the attachment picker, keep the\n * strip and the toolbar, and ALWAYS keep the send button — and the approval panel\n * showed what that costs: its options settle on the first click by design, so the\n * button beside them sat permanently disabled, meaning nothing, until the optional\n * instruction field was opened. A control that is never the way forward is not\n * disabled, it is absent (ratified decision #8).\n *\n * Named rather than inlined at each of its six readers: this union is exactly the\n * kind of list this repo has watched drift when every reader kept its own copy.\n */\nexport type AparteComposerPanelMode = '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 * Prose first, on purpose: when `@element` opens a docblock there is no free text\n * left for the analyser to use, and this component's description came out empty in\n * the manifest and blank on the generated reference page.\n *\n * `aparte-abort` and `aparte-message-aborted` have to be declared by hand and always\n * will: they go out through `window.dispatchEvent` (they concern the whole page, not\n * this subtree), and the analyser's fallback only recognises `this.dispatchEvent`.\n *\n * @element aparte-composer\n *\n * @attr {string} placeholder - Fallback placeholder for `<aparte-composer-input>`, which\n * reads it off this element when it carries none of its own. 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, which is why it was missing from the\n * manifest — and so from every typed surface — while all four wrappers wrote it.\n *\n * @fires {CustomEvent<AparteSendEventDetail>} aparte-send - A message was submitted: the text, its attachments and the target.\n * @fires aparte-cancel - The stop button was pressed. No detail; the two window events below carry the target.\n * @fires {CustomEvent<AparteAbortEventDetail>} aparte-abort - Dispatched on `window`: stop the run for this target.\n * @fires {CustomEvent<AparteMessageAbortedEventDetail>} aparte-message-aborted - The run for this target ended early — the user pressed Stop, or `abort()` was called. This element dispatches it on `window`; `AparteClient` also dispatches it on the chat host, so it is listenable on either.\n * @fires {CustomEvent<AparteComposerChangeEventDetail>} aparte-composer-change - Any of value / streaming / disabled / attachments / panel changed, folded into one event.\n *\n * @cssprop [--aparte-composer-control-size=44px] - Width and height of the composer's\n * own control buttons (each is an `.aparte-btn--icon`, so it needs the opt-in\n * `.aparte-composer-row` wrapper, which is what carries the size down) and the\n * minimum height of the input's editor, which needs no wrapper. One knob for the\n * whole control set, so buttons stay aligned with a single line of text and anchored\n * to the bottom once the input grows. It reaches the buttons by declaration, not by\n * out-specifying them, so a panel mounted in the row keeps its own content's sizing.\n * @cssprop [--aparte-input-bg=var(--aparte-surface-1)] - Background of the opt-in\n * `.aparte-composer-shell` wrapper.\n * @cssprop [--aparte-input-border=var(--aparte-border)] - Border colour of that shell.\n * Its `:focus-within` colour is `--aparte-primary`, a global token rather than a\n * composer one.\n * @cssprop [--aparte-radius-input=var(--aparte-radius-lg)] - Corner radius of the shell,\n * and of the dashed outline drawn while files are dragged over the composer.\n * @cssprop [--aparte-message-max-width=800px] - Max width of the shell, which is\n * `margin: 0 auto` at this width — the same width `.aparte-message` uses, so the\n * composer keeps the transcript's column. Set on THIS element it moves the shell only:\n * custom properties inherit downward and the transcript is a sibling subtree, so set\n * it on a shared ancestor (the chat host, `:root`) to move both.\n * @cssprop [--aparte-viewport-padding=16px] - 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, `showPanel` empties it unconditionally, and nothing\n * used to say whose it was. Three paths therefore closed a panel whose owner was\n * still awaiting an answer, and none of them told the owner: a second `showPanel`,\n * the owner-of-record's own `hidePanel`, and — the one that actually bit —\n * `_handleMessageDone`, which fires on EVERY turn end. A question still open when a\n * turn finished lost its panel while `<aparte-elicitation>` kept `_pending` set,\n * so the request never settled AND every later request was short-circuited for the\n * life of the page.\n */\n private _panelToken: symbol | null = null;\n private _panelOnEvict: (() => void) | null = null;\n\n // Internal bus events that represent an observable STATE change — these are\n // mirrored to the public `aparte-composer-change` DOM event. `submit`/`cancel`\n // are actions, not state, and are covered by `aparte-send`/`aparte-cancel`.\n private static readonly _STATE_EVENTS: ReadonlySet<AparteComposerEventType> = new Set([\n 'value-change', 'streaming-change', 'disabled-change', 'attachments-change', 'panel-change',\n ]);\n\n // Window event bindings\n private _onMessageStart = this._handleMessageStart.bind(this);\n private _onMessageDone = this._handleMessageDone.bind(this);\n\n /** Config governing THIS composer (nearest instance boundary, else global). */\n /**\n * Resolved LIVE, not cached at connect.\n *\n * A wrapper runs `AparteChatHost.bind()` — which calls `attachConfig` — from its\n * POST-mount hook, so this element connects BEFORE the boundary exists. Caching\n * here latched the global config forever: an instance `config` carrying an RTL\n * locale flipped the transcript and not the composer, and the\n * `requireModelSelection` gate was read off the wrong object. `aparte-chat-bubble`\n * has always resolved live, and its JSDoc names this exact race.\n */\n private get _cfg(): AparteConfig {\n return resolveConfig(this);\n }\n /** Only OUR config: a change on another chat's instance must not touch us. */\n private _onConfigChangeEvent = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n if (detail?.config && detail.config !== this._cfg) return;\n this._onConfigChange();\n };\n /** True while `requireModelSelection` is on AND no model is selected — blocks send. */\n private _modelGated = false;\n private _onConfigChange = (): void => { this._evaluateModelGate(); this._applyDirection(); };\n\n /**\n * Mirror `locale.direction` onto ourselves, so everything inside — input, buttons,\n * the toolbar row and whatever the consumer put in it — inherits it.\n *\n * The direction used to stop at the transcript: only the viewport applied `dir`, so\n * an RTL locale flipped the conversation and left the composer left-to-right. It is\n * also what makes the toolbar's placement idiom real: `margin-inline-start: auto` in\n * a subtree that inherits no direction is just `margin-left`.\n *\n * One attribute on the host rather than a stamp per child: inheritance is the\n * mechanism, so nothing needs to know about the consumer's markup.\n */\n private _applyDirection(): void {\n const direction = (this._cfg ?? resolveConfig(this)).getLocale().direction;\n if (direction) this.setAttribute('dir', direction);\n else this.removeAttribute('dir');\n }\n\n static get observedAttributes(): string[] {\n return ['placeholder', 'disabled', 'target'];\n }\n\n connectedCallback(): void {\n window.addEventListener('aparte-message-start', this._onMessageStart);\n window.addEventListener('aparte-message-done', this._onMessageDone);\n window.addEventListener('aparte-message-error', this._onMessageDone);\n window.addEventListener('aparte-message-aborted', this._onMessageDone);\n // Model-selection gate (opt-in via aparteGlobalConfig.setRequireModelSelection).\n // A window listener rather than `_cfg.subscribe(...)`: subscribing binds to\n // whichever config was resolvable AT CONNECT, which is the bug above wearing\n // a different hat. `_notify()` dispatches `aparte-config-change` with\n // `detail.config`, so the same information arrives without the early binding\n // — and the filter below compares against the LIVE config.\n window.addEventListener('aparte-config-change', this._onConfigChangeEvent);\n // 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 * When false, Shift+Enter submits and a bare Enter inserts a newline —\n * the inverse of the default. Driven by the `submit-on-enter` attribute.\n */\n get submitOnEnter(): boolean { return this.getAttribute('submit-on-enter') !== 'false'; }\n get attachments(): File[] { return this._attachments; }\n get placeholder(): string { return this.getAttribute('placeholder') ?? ''; }\n get targetId(): string | null { return this.getAttribute('target'); }\n\n /**\n * Snapshot of the composer's observable state. Pair with the\n * `aparte-composer-change` DOM event to drive a custom send button or footer\n * control that lives outside the composer package:\n *\n * @example\n * // A custom send button. Keep it CLICKABLE while streaming — submit()\n * // routes to cancel() when a response is in flight, so one button is\n * // Send/Stop. Disabling it on `streaming` would make \"stop\" unreachable.\n * composer.addEventListener('aparte-composer-change', (e) => {\n * const { streaming, disabled, value, attachments } = e.detail.state;\n * myButton.textContent = streaming ? 'Stop' : 'Send';\n * myButton.disabled = disabled || (!streaming && !value.trim() && attachments.length === 0);\n * });\n * myButton.addEventListener('click', () => composer.submit()); // send or stop\n */\n getState(): AparteComposerState {\n return {\n value: this._value,\n streaming: this._streaming,\n disabled: this.disabled,\n attachments: [...this._attachments],\n panelActive: this._panelActive,\n submitEnabled: this._panelSubmitEnabled,\n };\n }\n\n /**\n * Set the composer's value — both what a send will submit and what the editor\n * shows. `<aparte-composer-input>` writes through any value it does not already\n * hold, so this prefills the visible field (a template button, a restored draft)\n * as readily as it stages text for an immediate `submit()`.\n */\n setValue(value: string): void {\n this._value = value;\n this._emit('value-change', { value });\n }\n\n /** Append files to the pending attachments and notify. Does not de-duplicate. */\n addAttachments(files: FileList | File[]): void {\n this._attachments = [...this._attachments, ...Array.from(files)];\n this._emit('attachments-change', { attachments: this._attachments });\n }\n\n /**\n * Drop one pending attachment and notify. Matched by IDENTITY — pass the same\n * `File` object the composer handed you, not an equal one; two picks of the same\n * file on disk are two distinct objects.\n */\n removeAttachment(file: File): void {\n this._attachments = this._attachments.filter(f => f !== file);\n this._emit('attachments-change', { attachments: this._attachments });\n }\n\n /** Drop every pending attachment and notify. */\n clearAttachments(): void {\n this._attachments = [];\n this._emit('attachments-change', { attachments: [] });\n }\n\n /**\n * Inject a panel into the composer. The send button calls `onSubmit` when clicked.\n *\n * While a panel is up, the composer is answering a QUESTION, not composing a\n * message — so the affordances that lead nowhere go away with the text input:\n * the attachment picker above all, which stayed clickable while the user was\n * being asked something (\"on voyait encore l'icône de upload\", reported from a\n * real session). Ratified decision #8: an affordance nothing can honour is not\n * rendered.\n *\n * What STAYS, deliberately: the attachments strip, because pending attachments\n * are the user's state and not an action to offer — hiding them would look like\n * losing them; and the toolbar, because switching model still does something.\n *\n * The send button is the part the PANEL decides, through `mode`. `'submit'`\n * keeps it; `'none'` says this panel has no act for it and it is not drawn. That third value is what a panel whose options settle on the first click\n * needs — a single-choice question, an approval — and until it existed such a\n * panel left a permanently disabled button beside options that never routed\n * through it. Flip between them at any time with {@link setPanelSubmitEnabled},\n * which is how a panel that grows an act (an \"Other…\" field, a written\n * instruction) turns the button back on.\n *\n * Declared with an attribute + CSS rather than the inline `style.display` this\n * used to set on a child: an attribute is themeable, is visible to a consumer's\n * own rules, and does not clobber a `display` the consumer had set (the restore\n * wrote `''`, not the previous value).\n *\n * Returns the TOKEN for this panel. Pass it back to `hidePanel` so a presenter\n * that has already lost the slot cannot close the panel that replaced it, and\n * supply `onEvict` to be told when that happens — an owner that is not told is an\n * owner whose promise nobody can settle.\n */\n showPanel(\n panel: HTMLElement,\n options?: {\n submitEnabled?: boolean;\n onSubmit?: () => void;\n /**\n * What the send button means for this panel — `'none'` if it has no act\n * for it, in which case the button is not drawn. See\n * {@link AparteComposerPanelMode}.\n */\n mode?: AparteComposerPanelMode;\n /** Called when something other than this owner closes the panel. */\n onEvict?: () => void;\n },\n ): symbol {\n // Evict rather than hide: the previous owner is awaiting an answer it will\n // never get, and it is the only thing that can settle its own promise.\n this._evictPanel();\n // 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 * BOTH forms notify. The no-token form used to call `_teardownPanel` directly,\n * which nulls `_panelOnEvict` without ever calling it — so the documented public\n * `hidePanel()` closed an open approval panel and left its request pending\n * forever. The turn hung on \"waiting for you\", and because\n * `AparteConfig.requestUserInput` chains each request on the previous one, NO\n * further question or approval on that config was ever presented again, for the\n * life of the page.\n *\n * The old JSDoc justified the silent branch as \"what `reset()` needs\". It was not:\n * `reset()` calls `_evictPanel()`, which notifies. The branch had no consumer and\n * one failure mode.\n *\n * The two forms differ on purpose, and the difference is who already knows:\n *\n * - **With a matching token** the OWNER is closing its own panel, which is what\n * `<aparte-elicitation>`'s `close()` does right after it resolves. It must NOT be\n * notified — telling it \"you were evicted\" for a request it just settled would\n * fire `onEvict` against a finished promise.\n * - **With no token** somebody else is closing a panel they do not own, so the owner\n * cannot know and has to be told. That includes the presenter's own defensive\n * `hidePanel(undefined)` when it settles before `showPanel` ran: whatever is open\n * then belongs to another request, and that request must not orphan.\n */\n hidePanel(token?: symbol): void {\n if (token !== undefined) {\n if (token !== this._panelToken) return;\n this._teardownPanel();\n return;\n }\n this._evictPanel();\n }\n\n /** Close the panel AND tell its owner, so a pending request never orphans. */\n private _evictPanel(): void {\n const onEvict = this._panelOnEvict;\n // State first, callback second: the owner's settle path calls `hidePanel` with\n // its own token, which must find the slot already empty rather than recurse.\n this._teardownPanel();\n onEvict?.();\n }\n\n private _teardownPanel(): void {\n const existing = this.querySelector('[data-aparte-panel]') as HTMLElement | null;\n if (existing) existing.remove();\n this.removeAttribute('data-panel-active');\n this._panelActive = false;\n this._panelSubmitEnabled = false;\n this._panelMode = 'submit';\n this._reflectPanelMode();\n this._panelOnSubmit = null;\n this._panelOnEvict = null;\n this._panelToken = null;\n this._emit('panel-change', { active: false, submitEnabled: false, mode: 'submit' });\n this.focus();\n }\n\n /**\n * Update the send button's state while a panel is active.\n *\n * `mode` moves with it because both 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 and the same one that took\n * the old `style.display` off a child: an attribute is themeable, is visible to\n * a consumer's own rules, and does not clobber what the consumer set. It is what\n * lets `'none'` remove the send button without this component reaching into it.\n */\n private _reflectPanelMode(): void {\n if (this._panelActive) this.setAttribute('data-panel-mode', this._panelMode);\n else this.removeAttribute('data-panel-mode');\n }\n\n get panelActive(): boolean { return this._panelActive; }\n\n /**\n * Recompute the model gate from the resolved config. When\n * `requireModelSelection` is on and no model is selected, block sending and\n * reflect `data-model-gated` so the shipped CSS greys the composer. Re-runs on\n * every config change (e.g. the model selector's auto-select firing).\n */\n private _evaluateModelGate(): void {\n const gated = this._cfg.getRequireModelSelection() && !this._cfg.hasSelectedModel();\n if (gated === this._modelGated) return;\n this._modelGated = gated;\n this.toggleAttribute('data-model-gated', gated);\n }\n\n /** Submit the current value. Called by aparte-composer-send or programmatically. */\n submit(): void {\n if (this._panelActive) {\n // `'none'` is authoritative over `submitEnabled`, and deliberately so: the\n // two are set by the same caller and can disagree, and the mode is the one\n // that says whether an act exists at all. Reached by Enter inside the panel\n // and by a consumer calling `submit()` directly — the button itself is not\n // drawn in this mode.\n if (this._panelMode !== 'none' && this._panelSubmitEnabled) this._panelOnSubmit?.();\n return;\n }\n if (this._streaming) {\n this.cancel();\n return;\n }\n const value = this._value.trim();\n if (!value && this._attachments.length === 0) return;\n if (this.disabled) return;\n if (this._modelGated) return; // no model selected yet (require-model gate)\n\n this._emit('submit', { value, attachments: this._attachments });\n\n const detail: AparteSendEventDetail = {\n content: value,\n timestamp: Date.now(),\n targetId: this.targetId ?? undefined,\n files: this._attachments.length > 0 ? [...this._attachments] : undefined,\n };\n\n this.dispatchEvent(new CustomEvent<AparteSendEventDetail>('aparte-send', {\n bubbles: true,\n composed: true,\n detail,\n }));\n\n // Clear after send\n this.setValue('');\n this.clearAttachments();\n }\n\n /** Cancel the current streaming response. */\n cancel(): void {\n this._emit('cancel', {});\n // Public, element-scoped signal — symmetric with `aparte-send` on submit,\n // for consumers that want to observe cancel on the composer itself.\n this.dispatchEvent(new CustomEvent('aparte-cancel', { bubbles: true, composed: true }));\n // aparte-abort → tells AparteClient to actually stop the stream\n // aparte-message-aborted → resets the composer's own streaming state\n // Scope the abort to this composer's host so cancelling one chat doesn't\n // abort every scoped client / reset every composer on the page.\n /*\n * `_ownTargetId()`, not the bare attribute.\n *\n * The receive side got this fix and the SEND side did not, which left the whole\n * scoping inert in raw core: nothing writes the `target` attribute in the\n * hand-written markup the quick start shows, so this detail carried\n * `targetId: undefined` — and `_isForThisComposer` treats a missing id as \"for\n * everyone\". So Stop in chat A still tore down chat B's open panel, which is\n * exactly the failure `_ownTargetId`'s own docblock describes as fixed.\n *\n * Both sides now resolve the same way: the attribute if the wrapper set one,\n * otherwise the id of the chat host above us.\n */\n const abortDetail = { targetId: this._ownTargetId() };\n window.dispatchEvent(new CustomEvent('aparte-abort', { bubbles: false, detail: abortDetail }));\n window.dispatchEvent(new CustomEvent('aparte-message-aborted', { bubbles: false, detail: abortDetail }));\n }\n\n /**\n * Reset the composer to its initial state.\n * Clears value, attachments, and hides any active panel.\n * Call this when switching conversations.\n */\n reset(): void {\n this.setValue('');\n this.clearAttachments();\n if (this._panelActive) this._evictPanel();\n }\n\n /** Focus the input primitive inside this composer. */\n override focus(): void {\n const input = this.querySelector('aparte-composer-input') as HTMLElement | null;\n input?.focus();\n }\n\n // ── Internal pub/sub ────────────────────────────────────────────────────\n\n _emit<K extends AparteComposerEventType>(event: K, payload: AparteComposerEventMap[K]): void {\n this._listeners.get(event)?.forEach(cb => cb(payload));\n // Mirror state changes to a public DOM event so elements outside the\n // composer package can observe them without the private bus.\n if (AparteComposer._STATE_EVENTS.has(event)) {\n this.dispatchEvent(new CustomEvent<AparteComposerChangeEventDetail>('aparte-composer-change', {\n bubbles: true,\n composed: true,\n detail: { state: this.getState(), composer: this },\n }));\n }\n }\n\n _on<K extends AparteComposerEventType>(event: K, cb: (payload: AparteComposerEventMap[K]) => void): () => void {\n if (!this._listeners.has(event)) this._listeners.set(event, new Set());\n this._listeners.get(event)!.add(cb as unknown as (payload: unknown) => void);\n return () => this._listeners.get(event)?.delete(cb as unknown as (payload: unknown) => void);\n }\n\n // ── Window events ───────────────────────────────────────────────────────\n\n /** A window lifecycle event is for THIS composer when neither side is scoped\n * (single-instance broadcast) or the target ids match (multi-chat page).\n * Without this filter, streaming in one chat flips every composer's state. */\n private _isForThisComposer(e: Event): boolean {\n const evtTargetId = (e as CustomEvent).detail?.targetId as string | undefined;\n return !evtTargetId || !this._ownTargetId() || evtTargetId === this._ownTargetId();\n }\n\n /**\n * Which chat this composer belongs to: its `target` attribute, or the id of the\n * chat host above it.\n *\n * All four wrappers set `target` themselves, so the attribute alone identified a\n * composer there. In RAW core — the documented quick start, where the markup is\n * hand-written — nothing sets it, so `!this.targetId` was true and this composer\n * accepted every chat's lifecycle events: on a two-chat page, one chat's Stop\n * tore down the other's open elicitation panel while its tool call kept waiting,\n * i.e. the question vanished and the turn hung.\n *\n * Found by a two-chat test written for the elicitation presenter, which is the\n * only reason it surfaced: raw core with two chats is a shape nothing exercised.\n * The hosts matched are the ones `aparte-chat-bubble._resolveTargetId()` matches,\n * for the reason written there — Angular's wrapper root IS `<aparte-chat>`, the\n * other three render a `[data-aparte-chat]` div.\n */\n /**\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 private _handleMessageStart(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streaming = true;\n this._emit('streaming-change', { streaming: true });\n }\n\n private _handleMessageDone(e: Event): void {\n if (!this._isForThisComposer(e)) return;\n this._streaming = false;\n this._emit('streaming-change', { streaming: false });\n // Always hide any active panel when a message lifecycle ends\n if (this._panelActive) this._evictPanel();\n }\n}\n\nif (!customElements.get('aparte-composer')) {\n customElements.define('aparte-composer', AparteComposer);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Contenteditable text input primitive.\n *\n * The element owns its subtree: on connect it writes one `.aparte-ci-editor`\n * contenteditable and binds its listeners to that node, so children you place inside are\n * replaced. There is nothing to project here — style the generated editor through the CSS\n * variables below, or replace the whole primitive.\n *\n * Enter submits and Shift+Enter inserts a newline; `submit-on-enter=\"false\"` on the\n * composer inverts that mapping, and Enter never submits mid-IME-composition — the key\n * that confirms a CJK candidate must not send the message. The editor auto-expands with\n * its content up to `max-height`, then scrolls. Paste is intercepted: text lands as plain\n * text with its markup stripped, and a pasted image goes to the composer's attachments.\n *\n * Without an `<aparte-composer>` ancestor it still works, and that is deliberate: a\n * submitting Enter then dispatches `aparte-composer-submit` instead of calling\n * `root.submit()`, which is how the bubble's inline editor reuses this primitive.\n * Everything the root owns goes with it though — the mirrored value, the placeholder\n * fallback, the disabled 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=44px] - Single-line min-height of the editor.\n * Inside the `.aparte-composer-row` layout helper the composer's buttons read\n * the same token, so one value resizes that whole control set and the row stays\n * aligned.\n * @cssprop [--aparte-input-padding-y=10px] - Vertical padding inside the editor.\n * @cssprop [--aparte-input-padding-x=12px] - Horizontal padding inside the editor.\n * @cssprop [--aparte-input-font-size=14px] - Editor font size.\n * @cssprop [--aparte-input-line-height=1.5] - Editor line height — also what the\n * auto-expand measures, so changing it changes the height the editor settles at\n * (until `max-height` clamps it).\n * @cssprop --aparte-text - Text and caret colour of the editor.\n * @cssprop --aparte-input-placeholder - Colour of the placeholder drawn by\n * `:empty::before` (falls back to `--aparte-text-muted`).\n * @cssprop --aparte-input-bg - Field background, applied only when this input is the\n * bubble's inline editor (`.aparte-message[data-editing]`) — inside a composer\n * the shell paints the surface instead.\n * @cssprop --aparte-input-border - Border colour of that same edit-mode box.\n * @cssprop [--aparte-radius-input=8px] - Corner radius of the edit-mode box.\n * @cssprop --aparte-input-focus-border - Border colour of the edit-mode box while it\n * holds focus (`:focus-within`).\n *\n * @example\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input placeholder=\"Ask anything…\" max-height=\"320\"></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerInput extends HTMLElement {\n private _editor: HTMLDivElement | null = null;\n private _maxHeight = 200;\n private _minHeight = 44;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handlers\n private _onInput = this._handleInput.bind(this);\n private _onKeydown = this._handleKeydown.bind(this);\n private _onFocus = this._handleFocus.bind(this);\n private _onBlur = this._handleBlur.bind(this);\n private _onPaste = this._handlePaste.bind(this);\n\n static get observedAttributes(): string[] {\n return ['placeholder', 'max-height', 'min-height', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n // 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 // The placeholder is the one string in this composer a sighted user can\n // actually read, and it was frozen at the language of the first render.\n // `_updatePlaceholder` already exists for the attribute path — a locale\n // change is the same refresh, from a different trigger.\n this._unsubscribes.push(subscribeConfigChange(this, () => this._updatePlaceholder()));\n }\n\n disconnectedCallback(): void {\n this._editor?.removeEventListener('input', this._onInput);\n this._editor?.removeEventListener('keydown', this._onKeydown);\n this._editor?.removeEventListener('focus', this._onFocus);\n this._editor?.removeEventListener('blur', this._onBlur);\n this._editor?.removeEventListener('paste', this._onPaste);\n this._unsubscribes.forEach(fn => fn());\n this._unsubscribes = [];\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'placeholder') this._updatePlaceholder();\n if (name === 'max-height') this._maxHeight = parseInt(value || '200', 10);\n if (name === 'min-height') this._minHeight = parseInt(value || '44', 10);\n if (name === 'disabled') this._updateDisabled(value !== null);\n }\n\n // ── Public API ──────────────────────────────────────────────────────────\n\n /**\n * The editor's text, with `<br>` serialized back to newlines — `textContent`\n * alone would collapse a multi-line draft onto a single line.\n */\n getValue(): string {\n // `textContent` drops `<br>`, so multi-line content would collapse onto one\n // line. Serialize the editor ourselves: text nodes as-is, `<br>` → newline.\n // We keep the editor flat (text + <br>, see _handleKeydown), but descend into\n // any stray wrapper for safety.\n if (!this._editor) return '';\n let out = '';\n const walk = (node: Node): void => {\n node.childNodes.forEach(child => {\n if (child.nodeType === Node.TEXT_NODE) out += child.textContent ?? '';\n else if (child.nodeName === 'BR') out += '\\n';\n else walk(child);\n });\n };\n walk(this._editor);\n return out.trim();\n }\n\n /** Replace the editor's content and mirror the value onto the parent composer. */\n setValue(value: string): void {\n if (!this._editor) return;\n this._editor.textContent = value;\n this._updatePlaceholderVisibility();\n this._adjustHeight();\n this._getRoot()?.setValue(value);\n }\n\n /** Empty the editor and mirror the empty value onto the parent composer. */\n clear(): void {\n if (!this._editor) return;\n this._editor.innerHTML = '';\n this._updatePlaceholderVisibility();\n this._adjustHeight();\n this._getRoot()?.setValue('');\n }\n\n /**\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\n const imageFile = Array.from(cd.items).find(i => i.type.startsWith('image/'))?.getAsFile();\n if (imageFile) {\n this._getRoot()?.addAttachments([imageFile]);\n return;\n }\n\n // Plain text paste\n const text = cd.getData('text/plain');\n if (text) {\n document.execCommand('insertText', false, text);\n }\n }\n\n private _adjustHeight(): void {\n if (!this._editor) return;\n // Measure with height:0 (not auto): an explicit height opts the editor\n // OUT of any parent flex `align-items: stretch`, so scrollHeight reflects\n // the real content — not the (taller) row it may be stretched into. With\n // `auto`, a stretching parent inflates scrollHeight and the editor gets\n // stuck tall until the next reflow.\n this._editor.style.height = '0px';\n // Floor: the `min-height` ATTRIBUTE when explicitly set; otherwise defer\n // to the stylesheet (CSS min-height caps an inline height anyway). A\n // hardcoded JS floor would override theme CSS with an inline style and\n // break editor/controls alignment in restyled composers.\n const floor = this.hasAttribute('min-height') ? this._minHeight : 0;\n const contentHeight = this._editor.scrollHeight;\n const h = Math.min(Math.max(contentHeight, floor), this._maxHeight);\n this._editor.style.height = `${h}px`;\n this._editor.style.overflowY = contentHeight > this._maxHeight ? 'auto' : 'hidden';\n }\n\n /**\n * The first `_adjustHeight()` runs synchronously in `_render()` on connect —\n * before the stylesheet, flex layout and web fonts have necessarily settled.\n * On an unstabilized layout `scrollHeight` can read inflated, leaving the\n * editor stuck tall (misaligned with the composer controls) until the first\n * keystroke re-measures it. Re-measure once the layout is ready so it's\n * correct from the first paint.\n */\n private _scheduleInitialReflow(): void {\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => this._adjustHeight());\n }\n const fonts = (document as Document & { fonts?: { ready?: Promise<unknown> } }).fonts;\n fonts?.ready?.then(() => this._adjustHeight()).catch(() => { /* fonts unavailable — rAF path covers it */ });\n }\n\n private _updatePlaceholder(): void {\n if (this._editor) {\n const p = this._getPlaceholder();\n this._editor.setAttribute('data-placeholder', p);\n this._editor.setAttribute('aria-label', p);\n }\n }\n\n private _updatePlaceholderVisibility(): void {\n // Handled by CSS :empty — nothing to do\n }\n\n private _updateDisabled(disabled: boolean): void {\n if (!this._editor) return;\n this._editor.setAttribute('contenteditable', String(!disabled));\n this._editor.setAttribute('aria-disabled', String(disabled));\n // 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=44px] - Width/height of the button inside the\n * `.aparte-composer-row` layout helper, shared with the input's single-line\n * height so the row stays aligned. It wins over `--aparte-send-btn-size` there.\n * @cssprop [--aparte-send-btn-size=36px] - Width/height of the button outside that row\n * helper. On coarse pointers it is raised to `--aparte-touch-target-size`.\n * @cssprop [--aparte-touch-target-size=44px] - Hit-area floor applied to the button\n * under `@media (pointer: coarse)`.\n * @cssprop [--aparte-radius-send-btn=6px] - Corner radius of the button.\n * @cssprop --aparte-primary - Button background.\n * @cssprop --aparte-primary-hover - Button background on hover, while enabled.\n * @cssprop --aparte-on-primary - The glyph's colour. Undeclared by default, which means\n * the recipe derives it from `--aparte-primary` itself, so a theme that changes\n * the fill gets a readable glyph with no second edit. Declare it to choose one\n * — it then applies to every primary control, which is the honest scope.\n * @cssprop [--aparte-ink-flip=0.57] - Fill lightness at which the derived ink flips from\n * dark to light, for every solid control.\n * @cssprop [--aparte-ink-dark=0.176] - How dark that derived ink goes. Not 0: at zero\n * lightness OKLCH drops the chroma, and the ink loses the fill's own hue.\n * @cssprop --aparte-send-disabled-bg - Background while disabled (falls back to\n * `--aparte-primary`, which is then dimmed by opacity).\n *\n * @example\n * <!-- One button for both halves of the turn: it submits, and while a reply streams it\n * becomes the stop button. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerSend extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n /**\n * The last `panel-change` payload.\n *\n * This button has four meanings — send, stop, submit an answer, advance to the\n * next question — and three of them are decided by state it does not own: the\n * root's `streaming`, and this payload. It was read straight out of the event's\n * arguments and thrown away, so nothing could recompute the button's chrome\n * afterwards; a config change had no way to know which of the four to write.\n */\n private _panel: { active: boolean; submitEnabled: boolean; mode: AparteComposerPanelMode } | null = null;\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n connectedCallback(): void {\n this._render();\n // 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 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 now means \"submit the answer\".\n *\n * The icon has to move with the meaning: it drew a paper plane while the label\n * already said \"Submit\", so it read as \"send a message\" while it meant \"answer\n * this question\". It used to draw a chevron too, while a form had questions\n * ahead, and turn into a \"Next\" — a second way to do what the panel's chips do,\n * and a button whose meaning changed under the pointer. It is disabled instead\n * 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=44px] - Width/height of the button inside the\n * `.aparte-composer-row` layout helper, shared with the composer's other\n * controls so the row stays aligned.\n * @cssprop [--aparte-radius-action-btn=4px] - Corner radius of the button.\n * @cssprop --aparte-neutral - Icon colour at rest (the button's background is\n * transparent).\n * @cssprop --aparte-text - Icon colour on hover.\n * @cssprop --aparte-surface-2 - Button background on hover.\n *\n * @example\n * <!-- Only needed when you want a SEPARATE stop button: <aparte-composer-send> already\n * turns into one while streaming. This stays hidden until then. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input></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 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=56px] - Tile edge. The stylesheet sets 56px on\n * this element (the `:root` default is 72px, and the sent-message strip re-sets 40px on\n * itself), so a theme-level value reaches neither strip — target\n * `aparte-composer-attachments` to resize these tiles.\n * @cssprop [--aparte-thumb-radius=var(--aparte-radius-lg)] - Tile corner radius.\n * @cssprop [--aparte-attachment-chip-bg=var(--aparte-surface-2)] - Tile background, seen\n * behind a non-image file.\n * @cssprop [--aparte-attachment-chip-border=var(--aparte-border)] - Tile border colour.\n * @cssprop [--aparte-thumb-name-color=#ffffff] - Filename colour on the hover overlay.\n * @cssprop [--aparte-thumb-name-scrim=linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0))] - Background behind the filename; a bottom-up black\n * gradient by default, so the name stays legible over any picture.\n * @cssprop [--aparte-thumb-name-padding=14px 5px 4px] - Padding of that overlay.\n * @cssprop [--aparte-thumb-remove-size=18px] - Diameter of the ✗ button.\n * @cssprop [--aparte-thumb-remove-inset=3px] - Its inset from the tile's top and right\n * edges (physical `right`, so it does not flip in a right-to-left locale).\n * @cssprop [--aparte-thumb-remove-bg=rgba(0, 0, 0, 0.6)] - Its background.\n * @cssprop [--aparte-thumb-remove-bg-hover=rgba(0, 0, 0, 0.85)] - Its hover background.\n * @cssprop [--aparte-thumb-remove-color=#ffffff] - Its glyph colour.\n *\n * @example\n * <!-- The strip hides itself while nothing is attached. Pair it with the picker, and\n * only if your loop actually reads the files from the send event. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <aparte-composer-attachments></aparte-composer-attachments>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-add-attachment></aparte-composer-add-attachment>\n * <aparte-composer-input></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 &amp; 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-thumb__img\" 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-thumb__ext\">${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 this.querySelectorAll('.aparte-thumb--image').forEach(tile => {\n tile.setAttribute('role', 'button');\n tile.setAttribute('tabindex', '0');\n const open = (): void => {\n const img = tile.querySelector('.aparte-thumb__img') as HTMLImageElement | null;\n if (!img) return;\n this.dispatchEvent(new CustomEvent('aparte-attachment-preview', {\n bubbles: true,\n composed: true,\n detail: { url: img.src, name: tile.getAttribute('title') ?? '' },\n }));\n };\n tile.addEventListener('click', open);\n tile.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key !== 'Enter' && key !== ' ') return;\n e.preventDefault();\n open();\n });\n });\n }\n\n /** Uppercased file extension (≤4 chars), or 'FILE' when there is none. */\n private _ext(filename: string): string {\n const dot = filename.lastIndexOf('.');\n return dot > 0 ? filename.slice(dot + 1).toUpperCase().slice(0, 4) : 'FILE';\n }\n}\n\nif (!customElements.get('aparte-composer-attachments')) {\n customElements.define('aparte-composer-attachments', AparteComposerAttachments);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * File picker button for <aparte-composer>.\n *\n * Opens a native file picker on click, then pushes picked files to root.addAttachments().\n * Also sets up drag & drop on the nearest <aparte-composer> root.\n *\n * It only COLLECTS files: it never reads, uploads or renders them.\n * `<aparte-composer-attachments>` draws the pending strip, and sending is the host's job\n * (`event.detail.files` on `aparte-send`) — which is why the default `<aparte-chat>` shell\n * only includes this button when the `attachments` attribute is set. With nothing reading\n * the files, an attach button is an affordance core cannot honour (ratified decision #8).\n *\n * Drag & drop is installed on the composer ROOT, not on this button, so a drop anywhere\n * over the composer attaches and the root carries `aparte-is-dragover` while a drag is\n * over it. The dashed outline is drawn on `.aparte-composer-shell` when the markup has one\n * and on the composer element itself when it does not — width from\n * `--aparte-focus-outline-width`, colour from `--aparte-primary`, radius from\n * `--aparte-radius-input`, none of them declared here. The drop handler always calls\n * `preventDefault()`, even while disabled, so the browser can never navigate away to the\n * dropped file. `disabled` on the ROOT removes the drop target 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=36px] - Square size of the button. On a coarse\n * pointer the stylesheet re-sets it to `--aparte-touch-target-size` (44px) on\n * `.aparte-action-button` itself, which wins over a value inherited from your theme.\n * @cssprop [--aparte-input-action-btn-icon-size=20px] - Size of the `<svg>` inside it.\n * @cssprop [--aparte-radius-action-btn=var(--aparte-radius-sm)] - Corner radius.\n *\n * @example\n * <!-- Opt-in: nothing consumes the files unless your host does (an AparteClient, or\n * your own listener reading `event.detail.files` off `aparte-send`). -->\n * <aparte-composer>\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-add-attachment accept=\"image/*,.pdf\"></aparte-composer-add-attachment>\n * <aparte-composer-input></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, type AparteIconName } from '../../config/index.js';\nimport type { AparteComposer } from './aparte-composer.js';\nimport { escapeAttr } from '../../utils/escape.js';\nimport { subscribeConfigChange } from '../../config/config-subscribe.js';\n\n/**\n * Generic action button primitive for <aparte-composer>.\n *\n * The consumer declares it directly in markup — no global registration needed.\n *\n * It is the escape hatch for a button core has no opinion about: it renders one icon\n * button wearing `.aparte-action-button` (the shared icon-button look — colour from\n * `--aparte-neutral`, hover tint derived from `--aparte-primary`) and emits\n * `aparte-action-click`. It carries no behaviour of its own and nothing in core listens\n * for that event, so the app is the only thing that can make it do something. Prefer the\n * dedicated element wherever one exists — `<aparte-composer-send>`,\n * `<aparte-composer-cancel>`, `<aparte-composer-add-attachment>` — since those already\n * talk to the composer.\n *\n * The host is `display: contents`, so the `<button>` rather than this element is the flex\n * child of the surrounding `.aparte-composer-row`. It subscribes to the nearest composer's\n * `disabled` and `streaming` changes, so it greys out while a turn is running without the\n * app tracking that. Used outside a composer it still mounts and still fires, with\n * `composer: null` in the detail.\n *\n * A child already carrying `class=\"aparte-cact-button\"` suppresses core's own render — and\n * core then wires nothing to it: no click listener (so no `aparte-action-click`), no\n * `label` → `aria-label`/`title` write, no `icon` write, no disabled/streaming sync. Take\n * that path only for a button your own code drives end to end. Any other child is replaced\n * on the first render.\n *\n * @element aparte-composer-action\n *\n * @attr {string} icon - Icon key for aparteGlobalConfig.getIcon(), or raw SVG/HTML starting with `<`\n * @attr {string} label - Accessible label (also used as tooltip)\n * @attr {boolean} disabled - Disables the button\n * @attr {string} action-id - Identifies WHICH button fired; carried as\n * `AparteActionClickEventDetail.actionId`. Read lazily at dispatch time rather than\n * observed, so changing it takes effect on the next click.\n *\n * @fires {CustomEvent<AparteActionClickEventDetail>} aparte-action-click - Bubbles up when\n * the button is clicked, carrying which button it was and the composer it belongs to.\n * The type argument is not decoration: a BARE `@fires` records `CustomEvent` with no\n * argument, and the bindings generator then emits `EventEmitter<void>` with a\n * listener that drops `$event` — so an Angular consumer with two custom buttons\n * could not tell which one fired.\n * detail: { actionId: string, composer: AparteComposer | null }\n *\n * @cssprop [--aparte-input-action-btn-size=36px] - Square size of the button. On a coarse\n * pointer the stylesheet re-sets it to `--aparte-touch-target-size` (44px) on\n * `.aparte-action-button` itself, which wins over a value inherited from your theme.\n * @cssprop [--aparte-input-action-btn-icon-size=20px] - Size of the `<svg>` inside it.\n * @cssprop [--aparte-radius-action-btn=var(--aparte-radius-sm)] - Corner radius.\n *\n * @example\n * <!-- Inside a composer, because that is what it resolves with `closest()`. `action-id`\n * is what tells two custom buttons apart: it comes back on the event's detail, and\n * a second button without one is indistinguishable from the first. -->\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input></aparte-composer-input>\n * <aparte-composer-action icon=\"star\" label=\"Favourite\" action-id=\"favourite\"></aparte-composer-action>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n * </div>\n * </aparte-composer>\n *\n * <script>\n * // The event bubbles, so one listener above the composer serves every action.\n * document.addEventListener('aparte-action-click', (event) => {\n * if (event.detail.actionId === 'favourite') console.log('starred');\n * });\n * </script>\n */\nexport class AparteComposerAction extends HTMLElement {\n private _button: HTMLButtonElement | null = null;\n private _unsubscribes: (() => void)[] = [];\n\n // Bound handler\n private _onClick = this._handleClick.bind(this);\n\n static get observedAttributes(): string[] {\n return ['icon', 'label', 'disabled'];\n }\n\n connectedCallback(): void {\n this._render();\n // 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 as AparteIconName) ?? icon;\n }\n}\n\nif (!customElements.get('aparte-composer-action')) {\n customElements.define('aparte-composer-action', AparteComposerAction);\n}\n\n/**\n * Detail payload for `aparte-action-click`.\n *\n * `<aparte-composer-action>` is a publicly exported element whose only purpose is\n * to emit this event, and nothing in core listens for it — so the app IS the\n * consumer, and it had no type to read `e.detail` with. The shape was already\n * published in prose in the generated API reference; this makes it compile.\n *\n * @event aparte-action-click\n */\nexport interface AparteActionClickEventDetail {\n /** The `action-id` attribute of the button that was clicked, or `''`. */\n actionId: string;\n /** The owning composer, or `null` when the button is used outside one. */\n composer: AparteComposer | null;\n}\n","/**\n * The composer's bottom row — the strip a mode picker, a model selector or a token\n * counter belongs in, rather than a bar of your own floating below the chat. Purely\n * structural: it lays its children out in a row and gets out of the way.\n *\n * **Position is the DOM order.** `margin-inline-start: auto` on a child pushes it (and\n * everything after it) to the end of the row. That is the whole placement API on\n * purpose: there is no `left`/`right` to be wrong about, so the row reads correctly in a\n * right-to-left locale without the author thinking about it.\n *\n * The controls the row is made of can be any element, or plain text. Nothing is wrapped or\n * reordered — the children ARE the row, laid out by flex in DOM order, and they may arrive\n * after connection (a framework commits children in its own order). Non-whitespace TEXT\n * counts as content too, so a hand-written row holding a bare token count stays visible\n * instead of tripping the `data-empty` hide.\n *\n * The row is not part of the default `<aparte-chat>` shell — nothing is drawn until you\n * put something in it.\n *\n * It declares no custom property of its own: the gap, the padding and the top separator\n * come from the global `--aparte-space-*` and `--aparte-border*` tokens, so it inherits a\n * theme rather than exposing knobs to re-set.\n *\n * @element aparte-composer-toolbar\n *\n * @attr {boolean} data-empty - Reflected BY the element while it holds neither an element\n * child nor non-whitespace text; the stylesheet hides it then.\n * Read-only, do not set it yourself.\n *\n * @example\n * <aparte-composer>\n * <div class=\"aparte-composer-shell\">\n * <div class=\"aparte-composer-row\">\n * <aparte-composer-input></aparte-composer-input>\n * <aparte-composer-send></aparte-composer-send>\n * </div>\n *\n * <!-- `aparte-model-selector` is NOT part of core: importing\n * `@aparte/plugin-model-selector` is what defines it. Until then the tag\n * renders empty and inert with no error, and upgrades by itself when the\n * definition arrives. Any element of your own works here too. -->\n * <aparte-composer-toolbar>\n * <my-mode-picker></my-mode-picker>\n * <aparte-model-selector style=\"margin-inline-start:auto\"></aparte-model-selector>\n * </aparte-composer-toolbar>\n * </div>\n * </aparte-composer>\n */\nexport class AparteComposerToolbar extends HTMLElement {\n private _observer: MutationObserver | null = null;\n\n connectedCallback(): void {\n this._syncEmpty();\n // Children can arrive after connection — a framework commits the element and its\n // children in whichever order suits it, and a consumer may add a control later.\n this._observer ??= new MutationObserver(() => this._syncEmpty());\n this._observer.observe(this, { childList: true });\n }\n\n disconnectedCallback(): void {\n this._observer?.disconnect();\n this._observer = null;\n }\n\n /**\n * Reflect `data-empty` from the presence of an ELEMENT child.\n *\n * Not `:empty` in CSS: that selector does not match an element holding a whitespace\n * text node, so a template that indents its content keeps the row — separator,\n * padding and all — while it looks empty to the user. Every framework template\n * indents. An empty row must not draw its own separator (the same rule as an empty\n * bubble action bar).\n *\n * Non-whitespace TEXT counts as content, not just an element child: a hand-written\n * row holding a bare token count (`<aparte-composer-toolbar>1 240 tokens</…>`) is\n * not empty, and hiding it would be a twenty-minute mystery for whoever wrote it.\n */\n private _syncEmpty(): void {\n const hasContent = Boolean(this.firstElementChild) || this.textContent?.trim() !== '';\n if (hasContent) this.removeAttribute('data-empty');\n else this.setAttribute('data-empty', '');\n }\n}\n\nif (!customElements.get('aparte-composer-toolbar')) {\n customElements.define('aparte-composer-toolbar', AparteComposerToolbar);\n}\n","import { resolveConfig } from '../../config/index.js';\nimport { APARTE_DEFAULT_LOCALE } from '../../config/locale.js';\nimport { escapeAttr, escapeHtml } from '../../utils/escape.js';\nimport { cssEscape } from '../../utils/css-escape.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' | 'deleteConversation' | 'archiveConversation' | 'unarchiveConversation'\n | 'conversationActions' | 'renameConversation' | 'conversationTitle'\n | 'pinConversation' | 'unpinConversation' | 'deleteConversationConfirm' | 'cancel'\n | 'conversationGroupPinned' | 'conversationGroupToday' | 'conversationGroupYesterday'\n | 'conversationGroupWeek' | 'conversationGroupMonth';\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, never acting on it itself.\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 `no-groups` 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. 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} no-groups - Render the rows flat, in host order, with no date headings.\n *\n * @fires {CustomEvent<AparteConversationSelectDetail>} aparte-select-conversation - A row's title was activated; the host loads that conversation.\n * @fires {CustomEvent<AparteConversationRenameDetail>} aparte-rename-conversation - A rename was committed with a new, non-empty title. Nothing is renamed here.\n * @fires {CustomEvent<AparteConversationPinDetail>} aparte-pin-conversation - The pin item was chosen on an unpinned row.\n * @fires {CustomEvent<AparteConversationPinDetail>} aparte-unpin-conversation - The same item on a pinned row; same detail shape, opposite intent.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-archive-conversation - The archive item was chosen on a live conversation.\n * @fires {CustomEvent<AparteConversationArchiveDetail>} aparte-unarchive-conversation - The same item on an already-archived one; same detail shape, opposite intent.\n * @fires {CustomEvent<AparteConversationDeleteDetail>} aparte-delete-conversation - The delete was confirmed. Nothing is removed here.\n *\n * @cssprop [--aparte-conv-list-gap=2px] - Vertical gap between rows, and between a group's heading and its rows.\n * @cssprop [--aparte-conv-item-padding=7px 10px] - Padding of a row's title button.\n * @cssprop [--aparte-conv-item-gap=6px] - 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=0.8125rem] - Font size of a row's title.\n * @cssprop [--aparte-conv-item-color=var(--aparte-text-muted)] - Title colour of an inactive row.\n * @cssprop [--aparte-conv-item-bg-hover=var(--aparte-surface-3)] - Row background on hover.\n * @cssprop [--aparte-conv-item-bg-active=var(--aparte-surface-3)] - Background of the row matching `active-id`.\n * @cssprop [--aparte-conv-item-color-active=var(--aparte-text)] - Title colour of the active row.\n * @cssprop [--aparte-conv-item-font-weight-active=var(--aparte-font-weight-medium, 500)] - Title weight of the active row.\n * @cssprop [--aparte-conv-action-btn-size=20px] - Square size of the `⋯` button. Under `(pointer: coarse)` the stylesheet redeclares it as 28px 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-select-conversation', (e) => load(e.detail.id));\n * list.addEventListener('aparte-rename-conversation', (e) => manager.updateTitle(e.detail.id, e.detail.title));\n * list.addEventListener('aparte-pin-conversation', (e) => manager.pin(e.detail.id));\n * list.addEventListener('aparte-delete-conversation', (e) => manager.delete(e.detail.id));\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\n static get observedAttributes(): string[] {\n return ['active-id', 'no-groups'];\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._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 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 === 'no-groups' && 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 private _render(): void {\n // The menu and the rename input live inside the rows; a render replaces them.\n this._closeMenu();\n this._renaming = null;\n this.innerHTML = this._groups().map(g => this._renderGroup(g)).join('');\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 `no-groups`: a heading over the only group would say\n * nothing.\n */\n private _groups(): ConversationGroup[] {\n const items = this._conversations;\n if (this.hasAttribute('no-groups') || !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 private _confirmMarkup(conv: AparteConversationListItem): string {\n const question = escapeHtml(this._t('deleteConversationConfirm').replace('{title}', conv.title || this._t('newChat')));\n const cancel = escapeHtml(this._t('cancel'));\n const remove = escapeHtml(this._t('deleteConversation'));\n return `\n<div class=\"aparte-conv-menu__confirm\" role=\"group\" aria-label=\"${escapeAttr(question)}\">\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 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 window.removeEventListener('scroll', this._onWindowScroll, true);\n window.removeEventListener('resize', this._onWindowScroll);\n if (returnFocus && open.button.isConnected) open.button.focus();\n }\n\n private _onDocumentPointerDown = (e: Event): void => {\n const open = this._open;\n if (!open) return;\n const target = e.target as Node;\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 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 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 this.dispatchEvent(new CustomEvent<AparteConversationPinDetail>(\n 'aparte-unpin-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n } else {\n this.dispatchEvent(new CustomEvent<AparteConversationPinDetail>(\n 'aparte-pin-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n }\n return;\n case 'archive':\n this._closeMenu(true);\n if (conv.archivedAt) {\n this.dispatchEvent(new CustomEvent<AparteConversationArchiveDetail>(\n 'aparte-unarchive-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n } else {\n this.dispatchEvent(new CustomEvent<AparteConversationArchiveDetail>(\n 'aparte-archive-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n }\n return;\n case 'delete':\n // Ask first. The safe answer takes the focus.\n open.menu.innerHTML = this._confirmMarkup(conv); // safe-text: _confirmMarkup escapes the title and every locale string.\n this._placeMenu(open.button, open.menu);\n open.menu.querySelector<HTMLElement>('[data-menu-action=\"cancel\"]')?.focus();\n return;\n case 'cancel':\n this._closeMenu(true);\n return;\n case 'confirm-delete':\n this._closeMenu(true);\n this.dispatchEvent(new CustomEvent<AparteConversationDeleteDetail>(\n 'aparte-delete-conversation',\n { detail: { id }, bubbles: true, composed: true }\n ));\n return;\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 const index = items.indexOf(document.activeElement 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', () => this._finishRename(true, false));\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 private _finishRename(commit: boolean, viaKeyboard: 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 (viaKeyboard) {\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 if (commit && conv && title && title !== conv.title) {\n this.dispatchEvent(new CustomEvent<AparteConversationRenameDetail>(\n 'aparte-rename-conversation',\n { detail: { id: conv.id, title }, bubbles: true, composed: true }\n ));\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-select-conversation',\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';\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\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, and the example app used to\n * hand-roll them: four `<button class=\"chip\">`, a click handler, a CSS recipe of its\n * own. This element is that pattern 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 once sent 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 * Tier (a) of ratified decision #8: core honours the click alone, so it is live by\n * 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 - Hides the row (`hidden`) once its composer has sent\n * something. Remove the attribute, or `hidden`, to show it again.\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'];\n }\n\n private _suggestions: AparteSuggestion[] = [];\n private _composer: AparteComposer | null = null;\n\n private _onSend = (): void => {\n if (this.hasAttribute('empty-only')) this.hidden = true;\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 set suggestions(value: AparteSuggestion[]) {\n this._suggestions = 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 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 }\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 try {\n const parsed: unknown = JSON.parse(raw);\n this._suggestions = Array.isArray(parsed) ? parsed.filter(isSuggestion) : [];\n } catch {\n console.warn('[aparte-suggestions] `suggestions` is not a JSON array. Use [\"…\"] or [{\"label\": \"…\", \"prompt\": \"…\"}].');\n this._suggestions = [];\n }\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 }\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 composer.setValue(detail.prompt);\n if (this.getAttribute('mode') === 'fill') composer.focus();\n else composer.submit();\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`, reaching `danger` dispatches\n * `aparte-compact` for its chat — once, until the level drops again — which is what\n * a gauge that turns red and then does nothing was missing. 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} 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 reaching `danger` (once per crossing).\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 the gauge first reaches `danger`: compact this target's transcript. Once per crossing, 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 * @cssprop [--aparte-context-ring-stroke=4] - Thickness of the ring, in its own units (the ring is drawn on a 36-unit box).\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'];\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 `null` before the first turn. */\n get used(): number | null {\n return this._used;\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 /** 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(): void {\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';\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\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 (ratified decision #8, tier a).\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, vertically centred, and\n * hides it under a coarse pointer, where a 2px tick is not a target. Under two ticks it\n * renders nothing — a rail with one mark says nothing.\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=20px] - The rail's column, the ticks right-aligned in it.\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-gap=8px] - Space between ticks.\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></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 _intersections: IntersectionObserver | null = null;\n private _rebuildQueued = false;\n private _currentId: string | null = null;\n /** Visible ratio per message id, from the intersection observer. */\n private _visible = new Map<string, number>();\n\n private _onConfigChange = (): void => this._rebuild();\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.\n if (typeof MutationObserver !== 'undefined') {\n this._mutations = new MutationObserver(() => this._queueRebuild());\n this._mutations.observe(this._host, { childList: true, subtree: true, characterData: true });\n }\n this._rebuild();\n }\n\n private _detach(): void {\n this._mutations?.disconnect();\n this._mutations = null;\n this._intersections?.disconnect();\n this._intersections = null;\n this._visible.clear();\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 _rebuild(): void {\n const bubbles = this._bubbles();\n const locale = resolveConfig(this).getLocale();\n this.setAttribute('aria-label', locale.scrollRailLabel ?? 'Conversation outline');\n\n if (bubbles.length < 2) {\n this.setAttribute('data-empty', '');\n this.replaceChildren();\n this._intersections?.disconnect();\n this._intersections = null;\n this._visible.clear();\n this._currentId = null;\n return;\n }\n this.removeAttribute('data-empty');\n\n const list = document.createElement('ol');\n list.className = 'aparte-scroll-rail__list';\n const known = new Set<string>();\n for (const bubble of bubbles) {\n const id = bubble.getAttribute('message-id')!;\n known.add(id);\n const 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 const label = this._tickLabel(bubble);\n tick.setAttribute('aria-label', label);\n tick.title = label;\n if (id === this._currentId) tick.setAttribute('aria-current', 'true');\n item.appendChild(tick);\n list.appendChild(item);\n }\n this.replaceChildren(list);\n\n if (this._currentId && !known.has(this._currentId)) this._currentId = null;\n this._observe(bubbles);\n }\n\n /**\n * Which tick is current is read, not computed: the scroll surface is the root, and\n * the bubble with the largest visible share of a reading band near the top wins.\n * Scroll arithmetic was the alternative, and every scroll-position rule in the\n * viewport carries a regression note — this one has none to carry.\n */\n private _observe(bubbles: HTMLElement[]): void {\n this._intersections?.disconnect();\n this._intersections = null;\n this._visible.clear();\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(bubbles);\n }, { root, rootMargin: '-15% 0px -55% 0px', threshold: [0, 0.1, 0.25, 0.5, 0.75, 1] });\n for (const bubble of bubbles) this._intersections.observe(bubble);\n }\n\n private _pickCurrent(bubbles: HTMLElement[]): void {\n let best: string | null = null;\n let bestRatio = 0;\n for (const bubble of bubbles) {\n const id = bubble.getAttribute('message-id')!;\n const ratio = this._visible.get(id) ?? 0;\n if (ratio > bestRatio) {\n bestRatio = ratio;\n best = id;\n }\n }\n // Nothing in the band (a reply taller than the band, or the very bottom): keep\n // the last mark rather than clearing it.\n if (best !== null) this._setCurrent(best);\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) tick.setAttribute('aria-current', 'true');\n else tick.removeAttribute('aria-current');\n }\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 const index = ticks.indexOf(document.activeElement 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 */\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 viewport = this._viewport();\n const bubble = viewport?.querySelector<HTMLElement>(`aparte-chat-bubble[message-id=\"${cssEscape(messageId)}\"]`); // safe-attr: a selector, not markup — cssEscape() is the right escape here.\n if (!bubble) return;\n const reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n bubble.scrollIntoView({ block: 'start', behavior: reduced ? 'auto' : 'smooth' });\n this._setCurrent(messageId);\n }\n}\n\nif (!customElements.get('aparte-scroll-rail')) {\n customElements.define('aparte-scroll-rail', AparteScrollRail);\n}\n","import { resolveConfig } from '../../config/index.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. `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 under 48rem. 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-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\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', 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 // 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 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 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 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 }\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 this.querySelector<HTMLElement>(FOCUSABLE)?.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 (e.key === 'Escape' && this.drawer && !this.collapsed) {\n e.preventDefault();\n this.close();\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 const control = (e.target as HTMLElement | null)?.closest?.<HTMLElement>('[data-aparte-sidebar-toggle]');\n if (!control) return;\n const named = control.getAttribute('data-aparte-sidebar-toggle');\n if (named) {\n if (named !== this.id) return;\n } else if (nearestSidebar(control) !== this) {\n return;\n }\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 * Hide the conversation rows whose title does not contain `query`, and the date\n * groups left empty. Case- and accent-insensitive. An empty query shows everything.\n */\n filter(query: string): void {\n const needle = fold(query);\n for (const list of this.querySelectorAll<HTMLElement>('aparte-conversation-list')) {\n for (const row of list.querySelectorAll<HTMLElement>('[data-conv-id]')) {\n const title = row.querySelector('.aparte-conv-item__title')?.textContent ?? row.textContent ?? '';\n row.hidden = needle !== '' && !fold(title).includes(needle);\n }\n for (const group of list.querySelectorAll<HTMLElement>('.aparte-conv-group')) {\n group.hidden = needle !== '' && !Array.from(group.querySelectorAll<HTMLElement>('[data-conv-id]')).some((r) => !r.hidden);\n }\n }\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/** 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/** The sidebar a toggle belongs to: the closest ancestor, else the first on the page. */\nfunction nearestSidebar(control: HTMLElement): Element | null {\n return control.closest('aparte-sidebar') ?? document.querySelector('aparte-sidebar');\n}\n\n/** Lower-case, accents stripped: \"Épingler\" matches \"epingler\". */\nfunction fold(text: string): string {\n return text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim();\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';\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 under its breakpoint and showing one pane. */\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} 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=20rem] - 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=4px] - The visible seam.\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></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 * <iframe title=\"Preview\" style=\"inline-size: 100%; block-size: 100%; border: 0\" srcdoc=\"<h1>Your pane</h1>\"></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'];\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', 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', 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.classList.contains('aparte-split--only-start')\n || this.classList.contains('aparte-split--only-end')\n );\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 }\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 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 '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 is how tapping \"Preview\" on a phone used to write `position=\"0\"`\n * into the attribute, the event and the host's storage.\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) {\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 const control = (e.target 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 if ((control.closest('aparte-split') ?? document.querySelector('aparte-split')) !== this) {\n return;\n }\n this.showPane(which ?? (this.pane === 'start' ? 'end' : 'start'));\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. **Node resolves `index.node.ts` instead**\n * (via the `node` condition in package.json) — a DOM-free entry with the client,\n * host, transports, the chat handler and every type. This file sits first in the\n * exports map only because of the repo-local `@aparte-workspace/source` condition,\n * which is why reading it can look like \"this package can't run in Node\".\n * See the \"Node / SSR\" section of the README.\n *\n * @packageDocumentation\n */\nimport './styles/theme.css';\nimport './styles/base.css';\nimport './styles/button.css';\nimport './styles/field.css';\nimport './styles/display/avatar.css';\nimport './styles/display/icon.css';\nimport './styles/display/badge.css';\nimport './styles/display/tag.css';\nimport './styles/display/thumbnail.css';\nimport './styles/display/spinner.css';\nimport './styles/display/progress.css';\nimport './styles/display/skeleton.css';\nimport './styles/display/divider.css';\nimport './styles/display/alert.css';\nimport './styles/display/card.css';\nimport './styles/display/kbd.css';\nimport './styles/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 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 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 AparteChatResponse,\n AparteChatMessage,\n AparteContentPart,\n AparteTextPart,\n AparteImagePart,\n AparteFilePart,\n AparteStreamEvent,\n AparteStreamEventMap,\n AparteUsage,\n // Tool types\n AparteTool,\n AparteToolCall,\n AparteApprovalPolicy,\n AparteApprovalRuling,\n AparteToolResult,\n AparteToolHandler,\n AparteToolContext,\n AparteToolRenderer,\n AparteToolApprovalRequestDetail,\n // Canonical imperative surface (aliased by every wrapper's handle type).\n AparteChatImperativeApi,\n // The attribute surface of every element, for the wrappers to map over.\n AparteElementAttributes,\n AparteElementTagName,\n AparteAttrValue,\n AparteTemplateAttrs,\n AparteNoAttributes,\n AparteChatAttributes,\n AparteChatViewportAttributes,\n AparteChatBubbleAttributes,\n AparteChatStatusAttributes,\n AparteComposerAttributes,\n AparteComposerInputAttributes,\n AparteComposerActionAttributes,\n AparteComposerAddAttachmentAttributes,\n AparteComposerToolbarAttributes,\n AparteConversationListAttributes,\n AparteSelectAttributes,\n AparteOptionAttributes,\n AparteOptgroupAttributes,\n AparteProgressSpinnerAttributes,\n} from './types/index.js';\n\nexport { AparteErrorCode, AparteError, contentToText } from './types/index.js';\n\n// Export renderers\nexport {\n registerSegmentRenderer,\n unregisterSegmentRenderer,\n getSegmentRenderer,\n collectRendererStyles,\n registerDefaultRenderers,\n // The three the public barrel left behind. `renderers/index.ts` has always\n // exported all eight; this one published five, which made the registry\n // half-public: `declineDefaultRenderers` is the ONLY way to say \"do not install\n // the built-ins on this config\" without constructing an `AparteClient`\n // (`autoRegister: false`), and the bring-your-own-loop guide tells you not to\n // construct one. `installDefaultRenderersOnce` is what a hand-written bubble\n // needs, and `getAllRenderers` is the introspection half — the same reason\n // `hasHighlightProvider` and `renderMarkdown` are public.\n installDefaultRenderersOnce,\n declineDefaultRenderers,\n getAllRenderers\n} from './renderers/index.js';\n\n// Export components\nexport { AparteChat } from './components/index.js';\nexport { AparteChatBubble, populateBubbleFromMessage } from './components/index.js';\nexport type { SyncableBubble } from './components/index.js';\nexport { AparteChatStatus } from './components/index.js';\nexport { AparteChatViewport } from './components/index.js';\n\n// Export composer primitives\nexport { AparteComposer, AparteComposerInput, AparteComposerSend, AparteComposerCancel, AparteComposerAttachments, AparteComposerAddAttachment, AparteComposerAction, AparteComposerToolbar } from './components/index.js';\nexport type { AparteComposerEventMap, AparteComposerEventType, AparteComposerState, AparteComposerChangeEventDetail, AparteComposerPanelMode, AparteActionClickEventDetail } from './components/index.js';\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} from './conversations/index.js';\nexport { APARTE_CONVERSATION_SCHEMA_VERSION } from './conversations/index.js';\nexport { AparteConversationManager, type ConversationManagerOptions } from './conversations/index.js';\nexport {\n AparteConversationController,\n type AparteChatBinding,\n type AparteConversationControllerOptions,\n} from './conversations/index.js';\n\n// Export the framework-agnostic chat-host orchestrator (streaming/branch/\n// host-method layer that every framework wrapper binds to).\nexport {\n AparteChatHost,\n type AparteChatHostBinding,\n type AparteChatHostOptions,\n} from './host/index.js';\n\n// Export parsers\nexport { AparteStreamParser, parseMarkdownToSegments } 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, 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 type { AparteStreamRunEvent, AparteStreamRunEmitter, StreamAdapterTarget, CreateStreamAdapterOptions, AparteStreamRunner, AparteStreamRunOptions } from './client/stream-adapter.js';\n\n// Export transport seam (where chat requests go + how auth is handled)\nexport { AparteDirectTransport, AparteBackendTransport, createAparteChatHandler, isFormatAdapter } from './transport/index.js';\nexport type { AparteTransport, AparteTransportContext, AparteFormatAdapter, AparteVendorRequest, BackendTransportOptions, DirectTransportOptions, AparteChatHandlerOptions } from './transport/index.js';\n\n// Export runtime utilities\nexport { AparteMessageRepository } from './runtime/message-repository.js';\nexport type { ExportedMessageRepository } from './runtime/message-repository.js';\n\n// Export elicitation (human-in-the-loop typed input)\nexport { requestUserInput, buildElicitationPanel, buildApprovalPanel, AparteElicitationAbortError } from './elicitation/index.js';\nexport type {\n AparteElicitationSchema,\n AparteElicitationField,\n AparteElicitationEnumField,\n AparteElicitationBooleanField,\n AparteElicitationStringField,\n AparteElicitationObjectSchema,\n AparteElicitationRequest,\n AparteElicitationResult,\n AparteElicitationPresenter,\n AparteApprovalOption,\n AparteApprovalAnswer,\n BuiltApprovalPanel,\n BuiltElicitationPanel,\n} from './elicitation/index.js';\n\n// Export the default elicitation presenter Web Component\nexport { AparteElicitation } from './components/elicitation/aparte-elicitation.js';\n\n// 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 used to check four tags out of twenty-four and log \"Some components may not be\n * registered\", which is the two failures a guide cannot survive: a consumer whose\n * bundler dropped `<aparte-split>` got a silent green, and a consumer who got the\n * warning had no idea which element to import.\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 again 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 * Make sure every element core defines is in the registry, and say which are not.\n *\n * Importing `@aparte/core` already registers all of them. Call this when a bundler with\n * aggressive side-effect elimination, or a dynamic `import()`, might have dropped the\n * modules — the call itself is what keeps them, because it reaches `APARTE_ELEMENTS`.\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","panel","el","a","b","c","m","f","h","p","g","d","k","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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"}