@aparte/core 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/client/aparte-client.d.ts +25 -0
  2. package/dist/client/aparte-client.d.ts.map +1 -1
  3. package/dist/components/bubble/aparte-chat-bubble.d.ts +117 -0
  4. package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
  5. package/dist/components/chat/aparte-chat.d.ts +70 -15
  6. package/dist/components/chat/aparte-chat.d.ts.map +1 -1
  7. package/dist/components/composer/aparte-composer-action.d.ts +48 -7
  8. package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
  9. package/dist/components/composer/aparte-composer-add-attachment.d.ts +37 -4
  10. package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
  11. package/dist/components/composer/aparte-composer-attachments.d.ts +35 -3
  12. package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
  13. package/dist/components/composer/aparte-composer-cancel.d.ts +25 -2
  14. package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
  15. package/dist/components/composer/aparte-composer-input.d.ts +54 -5
  16. package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
  17. package/dist/components/composer/aparte-composer-send.d.ts +47 -4
  18. package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
  19. package/dist/components/composer/aparte-composer-toolbar.d.ts +15 -4
  20. package/dist/components/composer/aparte-composer-toolbar.d.ts.map +1 -1
  21. package/dist/components/composer/aparte-composer.d.ts +139 -7
  22. package/dist/components/composer/aparte-composer.d.ts.map +1 -1
  23. package/dist/components/composer/index.d.ts +1 -1
  24. package/dist/components/composer/index.d.ts.map +1 -1
  25. package/dist/components/conversation-list/aparte-conversation-list.d.ts +50 -8
  26. package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
  27. package/dist/components/elicitation/aparte-elicitation.d.ts +45 -0
  28. package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
  29. package/dist/components/index.d.ts +1 -1
  30. package/dist/components/index.d.ts.map +1 -1
  31. package/dist/components/status/aparte-chat-status.d.ts +47 -5
  32. package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
  33. package/dist/components/viewport/aparte-chat-viewport.d.ts +78 -8
  34. package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
  35. package/dist/config/aparte-config.d.ts +22 -0
  36. package/dist/config/aparte-config.d.ts.map +1 -1
  37. package/dist/config/icon-provider.d.ts +17 -15
  38. package/dist/config/icon-provider.d.ts.map +1 -1
  39. package/dist/custom-elements.json +6249 -4138
  40. package/dist/elicitation/approval-panel.d.ts.map +1 -1
  41. package/dist/elicitation/panel.d.ts +23 -2
  42. package/dist/elicitation/panel.d.ts.map +1 -1
  43. package/dist/generated/element-attributes.d.ts +21 -15
  44. package/dist/generated/element-attributes.d.ts.map +1 -1
  45. package/dist/glyphs-QXfbL3n_.js +81 -0
  46. package/dist/glyphs-QXfbL3n_.js.map +1 -0
  47. package/dist/icons/extended.d.ts +153 -0
  48. package/dist/icons/extended.d.ts.map +1 -0
  49. package/dist/icons/glyphs.d.ts +98 -0
  50. package/dist/icons/glyphs.d.ts.map +1 -0
  51. package/dist/icons/index.d.ts +24 -0
  52. package/dist/icons/index.d.ts.map +1 -0
  53. package/dist/icons.d.ts +14 -0
  54. package/dist/icons.d.ts.map +1 -0
  55. package/dist/icons.js +157 -0
  56. package/dist/icons.js.map +1 -0
  57. package/dist/index.css +5082 -2119
  58. package/dist/index.d.ts +37 -3
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +167 -67
  61. package/dist/index.js.map +1 -1
  62. package/dist/index.node.d.ts +2 -2
  63. package/dist/index.node.d.ts.map +1 -1
  64. package/dist/index.node.js +1 -1
  65. package/dist/index.node.js.map +1 -1
  66. package/dist/{is-awaiting-reply-BWTDvQ__.js → is-awaiting-reply-DrO_oR32.js} +333 -147
  67. package/dist/is-awaiting-reply-DrO_oR32.js.map +1 -0
  68. package/dist/primitives/icon/aparte-icon.d.ts +49 -0
  69. package/dist/primitives/icon/aparte-icon.d.ts.map +1 -0
  70. package/dist/primitives/icon/index.d.ts +2 -0
  71. package/dist/primitives/icon/index.d.ts.map +1 -0
  72. package/dist/primitives/index.d.ts +1 -0
  73. package/dist/primitives/index.d.ts.map +1 -1
  74. package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts +20 -5
  75. package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts.map +1 -1
  76. package/dist/primitives/select/aparte-optgroup.d.ts +30 -7
  77. package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
  78. package/dist/primitives/select/aparte-option.d.ts +29 -3
  79. package/dist/primitives/select/aparte-option.d.ts.map +1 -1
  80. package/dist/primitives/select/aparte-select.d.ts +36 -11
  81. package/dist/primitives/select/aparte-select.d.ts.map +1 -1
  82. package/dist/renderers/segments/artifact/card.d.ts.map +1 -1
  83. package/dist/renderers/segments/thinking.d.ts.map +1 -1
  84. package/dist/renderers/segments/tool-call.d.ts.map +1 -1
  85. package/dist/types/element-map.d.ts +2 -0
  86. package/dist/types/element-map.d.ts.map +1 -1
  87. package/dist/types/index.d.ts +1 -1
  88. package/dist/types/index.d.ts.map +1 -1
  89. package/dist/types/segments.d.ts +92 -7
  90. package/dist/types/segments.d.ts.map +1 -1
  91. package/dist/types/tools.d.ts +2 -0
  92. package/dist/types/tools.d.ts.map +1 -1
  93. package/package.json +7 -1
  94. package/dist/is-awaiting-reply-BWTDvQ__.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { a4 as subscribeConfigChange, $ as resolveConfig, a1 as runWithConfig, p as AparteElicitationAbortError, x as buildApprovalPanel, y as buildElicitationPanel, K as escapeAttr, F as cssEscape, a7 as mergeSegmentUpdate, L as escapeHtml, O as getSegmentRenderer, P as installDefaultRenderersOnce, s as AparteMessageRepository, a8 as stampSegmentActivity, a9 as stampSegmentOnInsert, aa as stampSegmentOnUpdate, ab as renumberSegments, ac as openSegmentIds, ad as isTerminalStatus, ae as adoptMessageSegments, Q as isAwaitingReply, W as populateBubbleFromMessage, a6 as uuid, a0 as revokeAttachmentUrls } from "./is-awaiting-reply-BWTDvQ__.js";
2
- import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, q, r, t, u, v, w, z, B, C, D, E, G, H, I, J, M, N, R, S, T, U, V, X, Y, Z, _, a2, a3, a5 } from "./is-awaiting-reply-BWTDvQ__.js";
1
+ import { $ as resolveConfig, a4 as subscribeConfigChange, a1 as runWithConfig, p as AparteElicitationAbortError, x as buildApprovalPanel, y as buildElicitationPanel, K as escapeAttr, F as cssEscape, a7 as mergeSegmentUpdate, L as escapeHtml, a8 as writeStreamedMarkdown, O as getSegmentRenderer, P as installDefaultRenderersOnce, s as AparteMessageRepository, a9 as stampSegmentActivity, aa as stampSegmentOnInsert, ab as stampSegmentOnUpdate, ac as renumberSegments, ad as openSegmentIds, ae as isTerminalStatus, af as adoptMessageSegments, Q as isAwaitingReply, W as populateBubbleFromMessage, a6 as uuid, a0 as revokeAttachmentUrls } from "./is-awaiting-reply-DrO_oR32.js";
2
+ import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, q, r, t, u, v, w, z, B, C, D, E, G, H, I, J, M, N, R, S, T, U, V, X, Y, Z, _, a2, a3, a5 } from "./is-awaiting-reply-DrO_oR32.js";
3
3
  class AparteOption extends HTMLElement {
4
4
  static get observedAttributes() {
5
5
  return ["value", "disabled", "selected", "data-status"];
@@ -132,6 +132,7 @@ class AparteOptgroup extends HTMLElement {
132
132
  if (this.collapsible) {
133
133
  const chevron = document.createElement("span");
134
134
  chevron.className = "aparte-optgroup-chevron";
135
+ chevron.innerHTML = resolveConfig(this).getIcon("expand");
135
136
  header.appendChild(chevron);
136
137
  header.style.cursor = "pointer";
137
138
  header.addEventListener("click", (e2) => {
@@ -204,8 +205,7 @@ class AparteSelect extends HTMLElement {
204
205
  * document-level handlers and could not touch this one.
205
206
  *
206
207
  * This is verbatim the bug class `aparte-chat-viewport` documents having fixed
207
- * for its own listeners; a repo sweep found this as the last inline-arrow
208
- * listener on a persistent node.
208
+ * for its own listeners.
209
209
  */
210
210
  _boundHandleOptionClick = this._handleOptionClick.bind(this);
211
211
  _boundHandleDocumentClick = this._handleDocumentClick.bind(this);
@@ -289,7 +289,7 @@ class AparteSelect extends HTMLElement {
289
289
  labelSpan.textContent = placeholder;
290
290
  const chevronSpan = document.createElement("span");
291
291
  chevronSpan.className = "aparte-select-chevron";
292
- chevronSpan.innerHTML = `<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor"><path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/></svg>`;
292
+ chevronSpan.innerHTML = resolveConfig(this).getIcon("expand");
293
293
  trigger.append(labelSpan, chevronSpan);
294
294
  const dropdown = document.createElement("div");
295
295
  dropdown.className = "aparte-select-dropdown";
@@ -631,6 +631,32 @@ class AparteProgressSpinner extends HTMLElement {
631
631
  if (!customElements.get("aparte-progress-spinner")) {
632
632
  customElements.define("aparte-progress-spinner", AparteProgressSpinner);
633
633
  }
634
+ class AparteIcon extends HTMLElement {
635
+ static get observedAttributes() {
636
+ return ["name"];
637
+ }
638
+ _unsubscribe = null;
639
+ connectedCallback() {
640
+ this._render();
641
+ this._unsubscribe = subscribeConfigChange(this, () => this._render());
642
+ }
643
+ disconnectedCallback() {
644
+ this._unsubscribe?.();
645
+ this._unsubscribe = null;
646
+ }
647
+ attributeChangedCallback() {
648
+ if (this.isConnected) this._render();
649
+ }
650
+ _render() {
651
+ const name = this.getAttribute("name");
652
+ const glyph = name ? resolveConfig(this).getIconProvider()[name]?.() : null;
653
+ this.innerHTML = glyph ?? "";
654
+ this.firstElementChild?.setAttribute("aria-hidden", "true");
655
+ }
656
+ }
657
+ if (!customElements.get("aparte-icon")) {
658
+ customElements.define("aparte-icon", AparteIcon);
659
+ }
634
660
  class AparteElicitation extends HTMLElement {
635
661
  _pending = null;
636
662
  /**
@@ -727,17 +753,15 @@ class AparteElicitation extends HTMLElement {
727
753
  }
728
754
  const cfg = resolveConfig(this);
729
755
  if (request.kind === "approval") {
756
+ const approvalMode = () => panel2.isComplete() ? "submit" : "none";
730
757
  const panel2 = runWithConfig(cfg, () => buildApprovalPanel(request.message, request.options ?? [], () => {
731
- composer.setPanelSubmitEnabled(panel2.isComplete(), "submit");
758
+ composer.setPanelSubmitEnabled(panel2.isComplete(), approvalMode());
732
759
  }));
733
760
  panel2.onSettle((answer) => settle({ action: "accept", content: answer }));
734
761
  this._pending = { abort: () => fail(), composer, relabel: () => panel2.relabel() };
735
762
  slot.token = composer.showPanel(panel2.el, {
736
763
  submitEnabled: panel2.isComplete(),
737
- mode: "submit",
738
- // The composer's button carries the INSTRUCTION, which is written
739
- // text — exactly the act that button already means. The options
740
- // never route through it: a decision is its own click.
764
+ mode: approvalMode(),
741
765
  onSubmit: () => {
742
766
  if (panel2.isComplete()) settle({ action: "accept", content: panel2.getContent() });
743
767
  },
@@ -752,10 +776,11 @@ class AparteElicitation extends HTMLElement {
752
776
  }));
753
777
  const skip = document.createElement("button");
754
778
  skip.type = "button";
755
- skip.className = "aparte-elic-skip";
779
+ skip.className = "aparte-btn aparte-elic-skip";
756
780
  skip.textContent = cfg.t("elicitationSkip");
757
781
  skip.addEventListener("click", () => settle({ action: "decline" }));
758
782
  panel.dismiss.appendChild(skip);
783
+ panel.onSettle((content) => settle({ action: "accept", content }));
759
784
  this._pending = {
760
785
  abort: () => fail(),
761
786
  composer,
@@ -968,6 +993,14 @@ function warnMissingRenderer(type) {
968
993
  _warnedNoRenderer = true;
969
994
  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`);
970
995
  }
996
+ function unrenderedSegment(segment) {
997
+ const fallback = typeof segment.fallback === "string" && segment.fallback.trim() ? segment.fallback : null;
998
+ if (!fallback) warnMissingRenderer(segment.type);
999
+ const el = document.createElement("div");
1000
+ el.className = fallback ? "aparte-segment aparte-segment-fallback" : "aparte-segment aparte-segment-unknown";
1001
+ el.textContent = fallback ?? `[Unknown segment type: ${segment.type}]`;
1002
+ return el;
1003
+ }
971
1004
  function resolveSegmentRenderer(type, config) {
972
1005
  const renderer = getSegmentRenderer(type, config);
973
1006
  if (renderer) return renderer;
@@ -980,7 +1013,6 @@ function segmentRenderResultToElement(result) {
980
1013
  wrapper.innerHTML = result;
981
1014
  return wrapper.firstElementChild;
982
1015
  }
983
- const INFO_ICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>';
984
1016
  class AparteChatBubble extends HTMLElement {
985
1017
  _contentEl = null;
986
1018
  _segmentsEl = null;
@@ -1106,6 +1138,7 @@ class AparteChatBubble extends HTMLElement {
1106
1138
  break;
1107
1139
  case "content":
1108
1140
  this._content = newValue || "";
1141
+ this._resetMarkdownStream();
1109
1142
  this._updateContent();
1110
1143
  break;
1111
1144
  case "timestamp":
@@ -1131,8 +1164,20 @@ class AparteChatBubble extends HTMLElement {
1131
1164
  setContent(content) {
1132
1165
  this._content = content;
1133
1166
  this.setAttribute("content", content);
1167
+ this._resetMarkdownStream();
1134
1168
  this._updateContent();
1135
1169
  }
1170
+ /**
1171
+ * Drop the incremental Markdown parser's state.
1172
+ *
1173
+ * Only needed where `_content` is REPLACED rather than grown. `appendToken` grows it, so
1174
+ * the parser's cursor stays valid there — which is the whole point of the seam.
1175
+ */
1176
+ _resetMarkdownStream() {
1177
+ const host = this;
1178
+ if (host._aparteSmd) host._aparteSmd.renderer.end();
1179
+ host._aparteSmd = void 0;
1180
+ }
1136
1181
  /** Get current content */
1137
1182
  getContent() {
1138
1183
  return this._content;
@@ -1267,11 +1312,7 @@ class AparteChatBubble extends HTMLElement {
1267
1312
  runWithConfig(this._cfg, () => renderer.setup?.(el, segment));
1268
1313
  }
1269
1314
  } else {
1270
- warnMissingRenderer(segment.type);
1271
- const fallback = document.createElement("div");
1272
- fallback.className = "aparte-segment aparte-segment-unknown";
1273
- fallback.textContent = `[Unknown segment type: ${segment.type}]`;
1274
- this._segmentsEl.appendChild(fallback);
1315
+ this._segmentsEl.appendChild(unrenderedSegment(segment));
1275
1316
  }
1276
1317
  if (this._contentEl) this._contentEl.style.display = "none";
1277
1318
  this._reflectError();
@@ -1350,9 +1391,16 @@ class AparteChatBubble extends HTMLElement {
1350
1391
  </div>
1351
1392
  <div class="aparte-footer">
1352
1393
  <div class="aparte-branch-picker" hidden>
1353
- <button class="aparte-branch-prev" aria-label="${escapeAttr(this._cfg.getLocale().previousResponse ?? "Previous response")}">&#8249;</button>
1394
+ <button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev" aria-label="${escapeAttr(this._cfg.getLocale().previousResponse ?? "Previous response")}">&#8249;</button>
1354
1395
  <span class="aparte-branch-label">1 / 1</span>
1355
- <button class="aparte-branch-next" aria-label="${escapeAttr(this._cfg.getLocale().nextResponse ?? "Next response")}">&#8250;</button>
1396
+ <!-- The move has to be ANNOUNCED. Pressing the arrows deliberately does not
1397
+ take focus, so without a live region a screen-reader user gets the new
1398
+ branch and no indication anything changed. The visible label cannot be
1399
+ the region itself: a custom sibling-nav renderer may replace it with
1400
+ dots, which reads as nothing. No new locale key — the position is
1401
+ digits, and the buttons beside it already carry translated labels. -->
1402
+ <span class="aparte-sr-only aparte-branch-status" aria-live="polite"></span>
1403
+ <button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next" aria-label="${escapeAttr(this._cfg.getLocale().nextResponse ?? "Next response")}">&#8250;</button>
1356
1404
  </div>
1357
1405
  <div class="aparte-action-bar" role="toolbar" aria-label="${escapeAttr(this._cfg.getLocale().messageActions ?? "Message actions")}"></div>
1358
1406
  </div>
@@ -1453,7 +1501,10 @@ class AparteChatBubble extends HTMLElement {
1453
1501
  return;
1454
1502
  }
1455
1503
  this._contentEl.style.display = "";
1456
- this._contentEl.innerHTML = this._cfg.renderMarkdown(this._content);
1504
+ runWithConfig(
1505
+ this._cfg,
1506
+ () => writeStreamedMarkdown(this, this._contentEl, this._content, this._streaming)
1507
+ );
1457
1508
  this._updateWaiting();
1458
1509
  if (!this._streaming) this._highlightContentCode();
1459
1510
  }
@@ -1496,11 +1547,7 @@ class AparteChatBubble extends HTMLElement {
1496
1547
  runWithConfig(this._cfg, () => renderer.setup?.(el, segment));
1497
1548
  }
1498
1549
  } else {
1499
- warnMissingRenderer(segment.type);
1500
- const fallback = document.createElement("div");
1501
- fallback.className = "aparte-segment aparte-segment-unknown";
1502
- fallback.textContent = `[Unknown segment type: ${segment.type}]`;
1503
- this._segmentsEl.appendChild(fallback);
1550
+ this._segmentsEl.appendChild(unrenderedSegment(segment));
1504
1551
  }
1505
1552
  }
1506
1553
  if (this._contentEl) {
@@ -1649,6 +1696,11 @@ class AparteChatBubble extends HTMLElement {
1649
1696
  label.textContent = `${this._siblingIndex + 1} / ${this._siblingCount}`;
1650
1697
  }
1651
1698
  }
1699
+ const status = this._branchPickerEl.querySelector(".aparte-branch-status");
1700
+ if (status) {
1701
+ const position = `${this._siblingIndex + 1} / ${this._siblingCount}`;
1702
+ if (status.textContent !== position) status.textContent = position;
1703
+ }
1652
1704
  const prevBtn = this._branchPickerEl.querySelector(".aparte-branch-prev");
1653
1705
  const nextBtn = this._branchPickerEl.querySelector(".aparte-branch-next");
1654
1706
  if (prevBtn) prevBtn.disabled = this._siblingIndex === 0;
@@ -1715,7 +1767,7 @@ class AparteChatBubble extends HTMLElement {
1715
1767
  const roles = a4.bubble?.roles ?? ["user", "assistant"];
1716
1768
  if (!roles.includes(this._role)) continue;
1717
1769
  const btn = document.createElement("button");
1718
- btn.className = "aparte-action-btn aparte-action-custom";
1770
+ btn.className = "aparte-btn aparte-btn--icon aparte-action-btn aparte-action-custom";
1719
1771
  btn.dataset["action"] = `custom:${a4.id}`;
1720
1772
  btn.setAttribute("aria-label", a4.label);
1721
1773
  btn.setAttribute("title", a4.label);
@@ -1729,28 +1781,28 @@ class AparteChatBubble extends HTMLElement {
1729
1781
  switch (action) {
1730
1782
  case "copy": {
1731
1783
  const l2 = locale.copy ?? "Copy";
1732
- return `<button class="aparte-action-btn aparte-action-copy" data-action="copy" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.copy()}</button>`;
1784
+ return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-copy" data-action="copy" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.copy()}</button>`;
1733
1785
  }
1734
1786
  case "edit": {
1735
1787
  const l2 = locale.edit ?? "Edit message";
1736
- return `<button class="aparte-action-btn aparte-action-edit" data-action="edit" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.edit()}</button>`;
1788
+ return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-edit" data-action="edit" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.edit()}</button>`;
1737
1789
  }
1738
1790
  case "retry": {
1739
1791
  const l2 = locale.retry ?? "Retry";
1740
- return `<button class="aparte-action-btn aparte-action-retry" data-action="retry" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.retry()}</button>`;
1792
+ return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-retry" data-action="retry" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.retry()}</button>`;
1741
1793
  }
1742
1794
  case "thumbUp": {
1743
1795
  const l2 = locale.feedbackPositive ?? "Good response";
1744
- return `<button class="aparte-action-btn aparte-action-feedback-pos" data-action="feedback-positive" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbUp()}</button>`;
1796
+ return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-pos" data-action="feedback-positive" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbUp()}</button>`;
1745
1797
  }
1746
1798
  case "thumbDown": {
1747
1799
  const l2 = locale.feedbackNegative ?? "Bad response";
1748
- return `<button class="aparte-action-btn aparte-action-feedback-neg" data-action="feedback-negative" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbDown()}</button>`;
1800
+ return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-neg" data-action="feedback-negative" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbDown()}</button>`;
1749
1801
  }
1750
1802
  case "info": {
1751
1803
  if (!this._usage) return "";
1752
1804
  const l2 = locale.messageInfo ?? "Details";
1753
- return `<button class="aparte-action-btn aparte-action-info" data-action="info" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${INFO_ICON_SVG}</button>`;
1805
+ return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-info" data-action="info" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${this._cfg.getIcon("info")}</button>`;
1754
1806
  }
1755
1807
  default:
1756
1808
  return "";
@@ -1762,7 +1814,7 @@ class AparteChatBubble extends HTMLElement {
1762
1814
  const locale = this._cfg.getLocale();
1763
1815
  const saveLabel = locale.editConfirm ?? "Save";
1764
1816
  const cancelLabel = locale.editCancel ?? "Cancel";
1765
- this._actionBarEl.innerHTML = `<button class="aparte-action-btn aparte-action-edit-save" data-action="edit-save" aria-label="${escapeAttr(saveLabel)}" title="${escapeAttr(saveLabel)}">${this._cfg.getIcon("check")}</button><button class="aparte-action-btn aparte-action-edit-cancel" data-action="edit-cancel" aria-label="${escapeAttr(cancelLabel)}" title="${escapeAttr(cancelLabel)}">${this._cfg.getIcon("close")}</button>`;
1817
+ this._actionBarEl.innerHTML = `<button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--success aparte-action-btn aparte-action-edit-save" data-action="edit-save" aria-label="${escapeAttr(saveLabel)}" title="${escapeAttr(saveLabel)}">${this._cfg.getIcon("check")}</button><button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--danger aparte-action-btn aparte-action-edit-cancel" data-action="edit-cancel" aria-label="${escapeAttr(cancelLabel)}" title="${escapeAttr(cancelLabel)}">${this._cfg.getIcon("close")}</button>`;
1766
1818
  this._actionBarEl.querySelectorAll(".aparte-action-btn").forEach((btn) => {
1767
1819
  btn.addEventListener("click", (e2) => this._handleActionClick(e2));
1768
1820
  });
@@ -2572,6 +2624,11 @@ class AparteChatViewport extends HTMLElement {
2572
2624
  getMessage(messageId) {
2573
2625
  return this._repo.getMessageById(messageId);
2574
2626
  }
2627
+ /**
2628
+ * The messages on the currently ACTIVE path, root → head — not the whole tree.
2629
+ * A message that was retried contributes only the branch currently selected;
2630
+ * `exportTree()` is what returns every sibling.
2631
+ */
2575
2632
  getMessages() {
2576
2633
  return this._repo.getMessages();
2577
2634
  }
@@ -2836,11 +2893,11 @@ class AparteChatViewport extends HTMLElement {
2836
2893
  this.appendChild(container);
2837
2894
  this._container = container;
2838
2895
  this._scrollBtn = document.createElement("button");
2839
- this._scrollBtn.className = "aparte-scroll-btn aparte-scroll-btn--hidden";
2896
+ this._scrollBtn.className = "aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden";
2840
2897
  this._scrollBtn.setAttribute("type", "button");
2841
2898
  this._scrollBtn.setAttribute("aria-label", "Scroll to bottom");
2842
2899
  const scrollIcon = resolveConfig(this).getIcon("scrollDown");
2843
- this._scrollBtn.innerHTML = scrollIcon || `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>`;
2900
+ this._scrollBtn.innerHTML = scrollIcon;
2844
2901
  this.appendChild(this._scrollBtn);
2845
2902
  } else {
2846
2903
  this._container = this.querySelector(".aparte-viewport-container");
@@ -2869,11 +2926,11 @@ class AparteChatViewport extends HTMLElement {
2869
2926
  }
2870
2927
  this.classList.add("aparte-viewport--framework");
2871
2928
  const scrollBtn = document.createElement("button");
2872
- scrollBtn.className = "aparte-scroll-btn aparte-scroll-btn--hidden";
2929
+ scrollBtn.className = "aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden";
2873
2930
  scrollBtn.setAttribute("type", "button");
2874
2931
  scrollBtn.setAttribute("aria-label", "Scroll to bottom");
2875
2932
  const scrollIcon = resolveConfig(this).getIcon("scrollDown");
2876
- scrollBtn.innerHTML = scrollIcon || `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>`;
2933
+ scrollBtn.innerHTML = scrollIcon;
2877
2934
  this.appendChild(scrollBtn);
2878
2935
  this._scrollBtn = scrollBtn;
2879
2936
  }
@@ -3293,18 +3350,31 @@ class AparteComposer extends HTMLElement {
3293
3350
  submitEnabled: this._panelSubmitEnabled
3294
3351
  };
3295
3352
  }
3353
+ /**
3354
+ * Set the composer's value — both what a send will submit and what the editor
3355
+ * shows. `<aparte-composer-input>` writes through any value it does not already
3356
+ * hold, so this prefills the visible field (a template button, a restored draft)
3357
+ * as readily as it stages text for an immediate `submit()`.
3358
+ */
3296
3359
  setValue(value) {
3297
3360
  this._value = value;
3298
3361
  this._emit("value-change", { value });
3299
3362
  }
3363
+ /** Append files to the pending attachments and notify. Does not de-duplicate. */
3300
3364
  addAttachments(files) {
3301
3365
  this._attachments = [...this._attachments, ...Array.from(files)];
3302
3366
  this._emit("attachments-change", { attachments: this._attachments });
3303
3367
  }
3368
+ /**
3369
+ * Drop one pending attachment and notify. Matched by IDENTITY — pass the same
3370
+ * `File` object the composer handed you, not an equal one; two picks of the same
3371
+ * file on disk are two distinct objects.
3372
+ */
3304
3373
  removeAttachment(file) {
3305
3374
  this._attachments = this._attachments.filter((f2) => f2 !== file);
3306
3375
  this._emit("attachments-change", { attachments: this._attachments });
3307
3376
  }
3377
+ /** Drop every pending attachment and notify. */
3308
3378
  clearAttachments() {
3309
3379
  this._attachments = [];
3310
3380
  this._emit("attachments-change", { attachments: [] });
@@ -3323,6 +3393,15 @@ class AparteComposer extends HTMLElement {
3323
3393
  * are the user's state and not an action to offer — hiding them would look like
3324
3394
  * losing them; and the toolbar, because switching model still does something.
3325
3395
  *
3396
+ * The send button is the part the PANEL decides, through `mode`. `'submit'` and
3397
+ * `'advance'` keep it; `'none'` says this panel has no act for it and it is not
3398
+ * drawn. That third value is what a panel whose options settle on the first click
3399
+ * needs — a single-choice question, an approval — and until it existed such a
3400
+ * panel left a permanently disabled button beside options that never routed
3401
+ * through it. Flip between them at any time with {@link setPanelSubmitEnabled},
3402
+ * which is how a panel that grows an act (an "Other…" field, a written
3403
+ * instruction) turns the button back on.
3404
+ *
3326
3405
  * Declared with an attribute + CSS rather than the inline `style.display` this
3327
3406
  * used to set on a child: an attribute is themeable, is visible to a consumer's
3328
3407
  * own rules, and does not clobber a `display` the consumer had set (the restore
@@ -3346,6 +3425,7 @@ class AparteComposer extends HTMLElement {
3346
3425
  this._panelActive = true;
3347
3426
  this._panelSubmitEnabled = options?.submitEnabled ?? false;
3348
3427
  this._panelMode = options?.mode ?? "submit";
3428
+ this._reflectPanelMode();
3349
3429
  this._panelOnSubmit = options?.onSubmit ?? null;
3350
3430
  this._panelOnEvict = options?.onEvict ?? null;
3351
3431
  const token = Symbol("aparte-composer-panel");
@@ -3405,6 +3485,7 @@ class AparteComposer extends HTMLElement {
3405
3485
  this._panelActive = false;
3406
3486
  this._panelSubmitEnabled = false;
3407
3487
  this._panelMode = "submit";
3488
+ this._reflectPanelMode();
3408
3489
  this._panelOnSubmit = null;
3409
3490
  this._panelOnEvict = null;
3410
3491
  this._panelToken = null;
@@ -3423,8 +3504,21 @@ class AparteComposer extends HTMLElement {
3423
3504
  if (!this._panelActive) return;
3424
3505
  this._panelSubmitEnabled = enabled;
3425
3506
  if (mode) this._panelMode = mode;
3507
+ this._reflectPanelMode();
3426
3508
  this._emit("panel-change", { active: true, submitEnabled: enabled, mode: this._panelMode });
3427
3509
  }
3510
+ /**
3511
+ * Publish the panel mode as an attribute, so CSS can act on it.
3512
+ *
3513
+ * The same reasoning `data-panel-active` is set for and the same one that took
3514
+ * the old `style.display` off a child: an attribute is themeable, is visible to
3515
+ * a consumer's own rules, and does not clobber what the consumer set. It is what
3516
+ * lets `'none'` remove the send button without this component reaching into it.
3517
+ */
3518
+ _reflectPanelMode() {
3519
+ if (this._panelActive) this.setAttribute("data-panel-mode", this._panelMode);
3520
+ else this.removeAttribute("data-panel-mode");
3521
+ }
3428
3522
  get panelActive() {
3429
3523
  return this._panelActive;
3430
3524
  }
@@ -3443,7 +3537,7 @@ class AparteComposer extends HTMLElement {
3443
3537
  /** Submit the current value. Called by aparte-composer-send or programmatically. */
3444
3538
  submit() {
3445
3539
  if (this._panelActive) {
3446
- if (this._panelSubmitEnabled) this._panelOnSubmit?.();
3540
+ if (this._panelMode !== "none" && this._panelSubmitEnabled) this._panelOnSubmit?.();
3447
3541
  return;
3448
3542
  }
3449
3543
  if (this._streaming) {
@@ -3596,6 +3690,10 @@ class AparteComposerInput extends HTMLElement {
3596
3690
  if (name === "disabled") this._updateDisabled(value !== null);
3597
3691
  }
3598
3692
  // ── Public API ──────────────────────────────────────────────────────────
3693
+ /**
3694
+ * The editor's text, with `<br>` serialized back to newlines — `textContent`
3695
+ * alone would collapse a multi-line draft onto a single line.
3696
+ */
3599
3697
  getValue() {
3600
3698
  if (!this._editor) return "";
3601
3699
  let out = "";
@@ -3609,6 +3707,7 @@ class AparteComposerInput extends HTMLElement {
3609
3707
  walk(this._editor);
3610
3708
  return out.trim();
3611
3709
  }
3710
+ /** Replace the editor's content and mirror the value onto the parent composer. */
3612
3711
  setValue(value) {
3613
3712
  if (!this._editor) return;
3614
3713
  this._editor.textContent = value;
@@ -3616,6 +3715,7 @@ class AparteComposerInput extends HTMLElement {
3616
3715
  this._adjustHeight();
3617
3716
  this._getRoot()?.setValue(value);
3618
3717
  }
3718
+ /** Empty the editor and mirror the empty value onto the parent composer. */
3619
3719
  clear() {
3620
3720
  if (!this._editor) return;
3621
3721
  this._editor.innerHTML = "";
@@ -3623,9 +3723,11 @@ class AparteComposerInput extends HTMLElement {
3623
3723
  this._adjustHeight();
3624
3724
  this._getRoot()?.setValue("");
3625
3725
  }
3726
+ /** Focus the inner contenteditable rather than the host element. */
3626
3727
  focus() {
3627
3728
  this._editor?.focus();
3628
3729
  }
3730
+ /** Blur the inner contenteditable rather than the host element. */
3629
3731
  blur() {
3630
3732
  this._editor?.blur();
3631
3733
  }
@@ -3678,7 +3780,9 @@ class AparteComposerInput extends HTMLElement {
3678
3780
  );
3679
3781
  this._unsubscribes.push(
3680
3782
  root._on("value-change", ({ value }) => {
3681
- if (value === "" && this.getValue() !== "") this.clear();
3783
+ if (this.getValue() === value.trim()) return;
3784
+ if (value === "") this.clear();
3785
+ else this.setValue(value);
3682
3786
  })
3683
3787
  );
3684
3788
  this._unsubscribes.push(
@@ -3814,7 +3918,7 @@ class AparteComposerSend extends HTMLElement {
3814
3918
  const root = this._getRoot();
3815
3919
  const disabled = !root || root.disabled || root.value.trim() === "";
3816
3920
  this.innerHTML = `<button
3817
- class="aparte-cs-button aparte-send-button"
3921
+ class="aparte-btn aparte-btn--primary aparte-btn--solid aparte-btn--icon aparte-cs-button aparte-send-button"
3818
3922
  aria-label="${escapeAttr(label)}"
3819
3923
  title="${escapeAttr(label)}"
3820
3924
  ${disabled ? "disabled" : ""}
@@ -3884,6 +3988,10 @@ class AparteComposerSend extends HTMLElement {
3884
3988
  const panel = this._panel;
3885
3989
  if (!this._button || !panel?.active) return;
3886
3990
  const cfg = resolveConfig(this);
3991
+ if (panel.mode === "none") {
3992
+ this._button.disabled = true;
3993
+ return;
3994
+ }
3887
3995
  const advancing = panel.mode === "advance";
3888
3996
  this._button.disabled = !panel.submitEnabled;
3889
3997
  this._button.innerHTML = advancing ? cfg.getIcon("nextBranch") : this._getSubmitIcon();
@@ -3962,7 +4070,7 @@ class AparteComposerCancel extends HTMLElement {
3962
4070
  const label = resolveConfig(this).t("stopButton") || "Stop";
3963
4071
  const icon = this._getStopIcon();
3964
4072
  this.innerHTML = `<button
3965
- class="aparte-cc-button"
4073
+ class="aparte-btn aparte-btn--icon aparte-cc-button"
3966
4074
  aria-label="${escapeAttr(label)}"
3967
4075
  title="${escapeAttr(label)}"
3968
4076
  hidden
@@ -4005,7 +4113,6 @@ class AparteComposerCancel extends HTMLElement {
4005
4113
  if (!customElements.get("aparte-composer-cancel")) {
4006
4114
  customElements.define("aparte-composer-cancel", AparteComposerCancel);
4007
4115
  }
4008
- const REMOVE_ICON = '<svg width="10" height="10" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><path d="M9.5 3.205 8.795 2.5 6 5.295 3.205 2.5 2.5 3.205 5.295 6 2.5 8.795l.705.705L6 6.705l2.795 2.795.705-.705L6.705 6z"/></svg>';
4009
4116
  class AparteComposerAttachments extends HTMLElement {
4010
4117
  _unsubscribes = [];
4011
4118
  /** Object URLs minted for image previews — revoked on re-render/disconnect. */
@@ -4041,13 +4148,13 @@ class AparteComposerAttachments extends HTMLElement {
4041
4148
  this._revokeUrls();
4042
4149
  this.innerHTML = files.map((file) => {
4043
4150
  const name = this._escape(file.name);
4044
- const remove = `<button class="aparte-thumb__remove" type="button" aria-label="Remove ${name}">${REMOVE_ICON}</button>`;
4151
+ const remove = `<button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-thumb__remove" type="button" aria-label="Remove ${name}">${resolveConfig(this).getIcon("close")}</button>`;
4045
4152
  if (file.type.startsWith("image/")) {
4046
4153
  const url = URL.createObjectURL(file);
4047
4154
  this._objectUrls.push(url);
4048
- return `<div class="aparte-thumb aparte-thumb--image" title="${escapeAttr(name)}"><img class="aparte-thumb__img" src="${escapeAttr(url)}" alt="${escapeAttr(name)}" /><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
4155
+ return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--image" title="${escapeAttr(name)}"><img class="aparte-thumb__img" src="${escapeAttr(url)}" alt="${escapeAttr(name)}" /><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
4049
4156
  }
4050
- return `<div class="aparte-thumb aparte-thumb--file" title="${escapeAttr(name)}"><span class="aparte-thumb__ext">${this._escape(this._ext(file.name))}</span><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
4157
+ return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--file" title="${escapeAttr(name)}"><span class="aparte-thumb__ext">${this._escape(this._ext(file.name))}</span><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
4051
4158
  }).join("");
4052
4159
  this.querySelectorAll(".aparte-thumb__remove").forEach((btn, i2) => {
4053
4160
  btn.addEventListener("click", (e2) => {
@@ -4136,15 +4243,15 @@ class AparteComposerAddAttachment extends HTMLElement {
4136
4243
  const label = cfg.t("actionUpload") || "Attach file";
4137
4244
  this._button.setAttribute("aria-label", label);
4138
4245
  this._button.setAttribute("title", label);
4139
- this._button.innerHTML = cfg.getIcon("paperclip") || this._defaultIcon();
4246
+ this._button.innerHTML = cfg.getIcon("paperclip");
4140
4247
  }
4141
4248
  _render() {
4142
4249
  if (this.querySelector(".aparte-caa-button")) return;
4143
4250
  const label = resolveConfig(this).t("actionUpload") || "Attach file";
4144
- const icon = resolveConfig(this).getIcon("paperclip") || this._defaultIcon();
4251
+ const icon = resolveConfig(this).getIcon("paperclip");
4145
4252
  const disabled = this.hasAttribute("disabled") || this._getRoot()?.disabled || false;
4146
4253
  this.innerHTML = `<button
4147
- class="aparte-caa-button aparte-action-button"
4254
+ class="aparte-btn aparte-btn--icon aparte-caa-button aparte-action-button"
4148
4255
  aria-label="${escapeAttr(label)}"
4149
4256
  title="${escapeAttr(label)}"
4150
4257
  type="button"
@@ -4213,9 +4320,6 @@ class AparteComposerAddAttachment extends HTMLElement {
4213
4320
  root.removeEventListener("drop", onDrop);
4214
4321
  };
4215
4322
  }
4216
- _defaultIcon() {
4217
- return `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>`;
4218
- }
4219
4323
  }
4220
4324
  if (!customElements.get("aparte-composer-add-attachment")) {
4221
4325
  customElements.define("aparte-composer-add-attachment", AparteComposerAddAttachment);
@@ -4263,7 +4367,7 @@ class AparteComposerAction extends HTMLElement {
4263
4367
  const icon = this._resolveIcon(this.getAttribute("icon") ?? "");
4264
4368
  const disabled = this.hasAttribute("disabled") || this._getRoot()?.disabled || false;
4265
4369
  this.innerHTML = `<button
4266
- class="aparte-cact-button aparte-action-button"
4370
+ class="aparte-btn aparte-btn--icon aparte-cact-button aparte-action-button"
4267
4371
  aria-label="${label}"
4268
4372
  title="${label}"
4269
4373
  type="button"
@@ -4298,7 +4402,6 @@ class AparteComposerAction extends HTMLElement {
4298
4402
  if (icon.trimStart().startsWith("<")) return icon;
4299
4403
  return resolveConfig(this).getIcon(icon) ?? icon;
4300
4404
  }
4301
- /** Escape a value before it lands in a double-quoted HTML attribute. */
4302
4405
  }
4303
4406
  if (!customElements.get("aparte-composer-action")) {
4304
4407
  customElements.define("aparte-composer-action", AparteComposerAction);
@@ -4400,10 +4503,10 @@ class AparteConversationList extends HTMLElement {
4400
4503
  const unarchiveLabel = this._esc(locale["unarchiveConversation"] ?? "Unarchive conversation");
4401
4504
  const archiveAction = isArchived ? "unarchive" : "archive";
4402
4505
  const archiveAriaLabel = isArchived ? unarchiveLabel : archiveLabel;
4403
- const archiveIcon = isArchived ? `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 8 21 8"/><path d="M5 8v11a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8"/><polyline points="9 14 12 11 15 14"/><line x1="12" y1="11" x2="12" y2="19"/></svg>` : `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 8 21 8"/><path d="M5 8v11a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8"/><polyline points="9 14 12 17 15 14"/><line x1="12" y1="11" x2="12" y2="17"/></svg>`;
4506
+ const archiveGlyph = resolveConfig(this).getIcon(isArchived ? "unarchive" : "archive");
4404
4507
  return `
4405
4508
  <div
4406
- class="aparte-conv-item${activeClass}${archivedClass}"
4509
+ class="aparte-menu__item aparte-conv-item${activeClass}${archivedClass}"
4407
4510
  role="button"
4408
4511
  tabindex="0"
4409
4512
  data-conv-id="${escapedId}"
@@ -4411,24 +4514,21 @@ class AparteConversationList extends HTMLElement {
4411
4514
  >
4412
4515
  <span class="aparte-conv-item__title">${escapedTitle}</span>
4413
4516
  <button
4414
- class="aparte-conv-item__archive"
4517
+ class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__archive"
4415
4518
  type="button"
4416
4519
  data-archive-id="${escapedId}"
4417
4520
  data-archive-action="${escapeAttr(archiveAction)}"
4418
4521
  aria-label="${escapeAttr(archiveAriaLabel)}"
4419
4522
  tabindex="0"
4420
- >${archiveIcon}</button>
4523
+ >${archiveGlyph}</button>
4421
4524
  <button
4422
- class="aparte-conv-item__delete"
4525
+ class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__delete"
4423
4526
  type="button"
4424
4527
  data-delete-id="${escapedId}"
4425
4528
  aria-label="${deleteLabel}"
4426
4529
  tabindex="0"
4427
4530
  >
4428
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
4429
- stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
4430
- <path d="M18 6L6 18M6 6l12 12"/>
4431
- </svg>
4531
+ ${resolveConfig(this).getIcon("close")}
4432
4532
  </button>
4433
4533
  </div>`;
4434
4534
  }
@@ -4471,11 +4571,10 @@ class AparteConversationList extends HTMLElement {
4471
4571
  };
4472
4572
  _onKeydown = (e2) => {
4473
4573
  if (e2.key !== "Enter" && e2.key !== " ") return;
4474
- const item = e2.target.closest("[data-conv-id]");
4475
- if (item) {
4476
- e2.preventDefault();
4477
- item.click();
4478
- }
4574
+ const target = e2.target;
4575
+ if (!target.matches("[data-conv-id]")) return;
4576
+ e2.preventDefault();
4577
+ target.click();
4479
4578
  };
4480
4579
  /** Update active class without full re-render (perf optimisation). */
4481
4580
  _updateActiveState() {
@@ -4535,6 +4634,7 @@ export {
4535
4634
  AparteElicitationAbortError,
4536
4635
  q as AparteError,
4537
4636
  r as AparteErrorCode,
4637
+ AparteIcon,
4538
4638
  AparteMessageRepository,
4539
4639
  AparteOptgroup,
4540
4640
  AparteOption,